状态机——Javascript词法扫描示例
所谓的状态机实质其实很很简单,其存在的目的也是把大量复杂的处理分散,使处理变得简单化一些。状态机只有一个当前状态,并且在当前状态下根据输入进行处理,然后再决定是否改变当前状态,然后再处理下一个输入,如此往复直到所有输入结束。
所以,相同的输入在不同的当前状态下的处理是不一样的,以字符串的处理为例,我们来看看怎么处理下面这条语句:
str="123\"abc";
我们需要得到的结果序列应该是:
标识符str,标点符号=,字面量"123\"abc",标点符号;
首先我们会建立起几种处理的状态(这里只是针对这个列子,实际开发的状态比这多得多T_T):
a.一般状态处理;
b.标识符状态处理;
c.标点符号状态处理;
d.双引号字符串字面量状态处理;
e.双引号字符串字面量遇到\符号时的状态处理;
建立完成状态处理方法后,我们将语句作为字符串输入流,一个个字符地进行输入处理:
1)输入s,首先进入状态a进行一般处理,判断出该字符符合js标识符规则,记录当前字符,将当前状态转换为状态b;
2)继续输入下一个字符t,进入状态b进行字符处理,字符t符合js标识符规则,记录当前字符,并且当前状态还是状态b,不发生改变;
3)继续输入下一个字符r,进入状态b进行字符处理,字符r符合js标识符规则,记录当前字符,并且当前状态还是状态b,不发生改变;
4)继续输入下一个字符=,进入状态b进行字符处理,字符=不符合当前状态需要的js标识符规则,于是保存之前记录的字符集,并标记为id类型,即["id","str"]。再将当前状态转换为状态a;
5)在当前状态a下继续输入刚才未处理的字符=,判断出其符合js标点符号规则,记录当前字符,并将当前状态转换为状态c;
6)继续输入下一个字符",进入状态c进行标点符号处理,判读出字符"并不符合标点符号规则,于是保存记录的字符集,并标记为标点符号类型["pun","="]。再将当前状态转换为状态a;
7)在当前状态a下继续输入刚才未处理的字符",判断出其符合js字符串字面量规则,记录当前字符,并将当前状态转换为状态d;
8)继续输入下一个字符1,在状态d下处理,符合js字符串字面量规则,记录当前字符;
9)继续输入下一个字符2,在状态d下处理,符合js字符串字面量规则,记录当前字符;
10)继续输入下一个字符3,在状态d下处理,符合js字符串字面量规则,记录当前字符;
11)继续输入下一个字符\,在状态d下处理,\字符在状态d里会触发状态转换,记录当前字符,将当前状态转换为状态e;
12)继续输入下一个字符",在状态e下处理,判断符合当前的处理规则,记录当前字符",将状态转换为状态d;
13)继续输入下一个字符a,在状态d下处理,符合js字符串字面量规则,记录当前字符;
14)继续输入下一个字符b,在状态d下处理,符合js字符串字面量规则,记录当前字符;
15)继续输入下一个字符c,在状态d下处理,符合js字符串字面量规则,记录当前字符;
16) 继续输入下一个字符",在状态d下处理,状态d接收到"时就可以判断出当前状态结束了,于是保存当前的记录的字符集,并标记为字符串字面量类型["str","\"123\\\"abc\""],再将当前状态转换为状态a;
17)继续输入下一个字符;,在状态a下处理,判断出其符合js标点符号规则,记录当前字符,将状态转换为状态c;
18)现在所有字符都扫描完了,我们可以人为加一个终止符,当再读到最后的终止符时,判断出不符合标点符号规则,保存字符集,标记为标点符号类型["pun",";"];
19)处理结束。
于是我们就得到了我们需要的词法序列:
[["id","str"], ["pun","="], ["str","\"123\\\"abc\""], ["pun",";""]]
简化版的代码看起来大概就是这个样子:
var Reader= function(str){ var index=0; var stream=str; stream +=" "; var me={ get char(){ return stream[index]; }, get length(){ return stream.length; }, get stream(){ return stream; }, get pchar(){ return stream[index-1]; }, get nchar(){ return stream[index+1]; }, get eof(){ return index === stream.length; }, next : function(){ index++; }, prev : function(){ index--; } }; return me; }; var statement="str=\"123\\\"abc\";"; var reader=Reader(statement); var l=reader.length; var i; var newState; var state; var tokenList=[]; var word=""; var punctuatorList=["{", "}", "(", ")", "[", "]", ".", ";", ",", "<", ">", "<=", ">=", "==", "!=", "===", "!==", "+", "-", "*", "%", "++", "--", "<<", ">>", ">>>", "&", "|", "^", "!", "~", "&&", "||", "?", ":", "=", "+=", "-=", "*=", "%=", "<<=", ">>=", ">>>=", "&=", "|=", "^="]; function checkUnicodeLetter(c){ return c.match(/[a-z]/i); //囧oz } function checkUnicodeNumber(c){ return (c.charCodeAt() >= "\u0030".charCodeAt() && c.charCodeAt() <= "\u0039".charCodeAt()) || (c.charCodeAt() >= "\u1D7CE".charCodeAt() && c.charCodeAt() <= "\u1D7FF".charCodeAt()); } function emitToken(type){ tokenList.push([type, word]); word=""; } function dataState(c){ if(punctuatorList.indexOf(c) > -1){ word=c; return punctuatorState; }else if(checkUnicodeLetter(c) || c==="_" || c==="$" || c==="\\"){ word=c; return identifierState; }else if(c==="\""){ word=c; return doubleStringLiteralState; } } function punctuatorState(c){ if(punctuatorList.indexOf(word+c) === -1){ emitToken("pun"); reader.prev(); return dataState; }else{ word += c; } } function identifierState(c){ if(checkUnicodeLetter(c) || checkUnicodeNumber(c)){ word += c; }else{ emitToken("id"); reader.prev(); return dataState; } } function doubleStringLiteralState(c){ if(c==="\\"){ word += c; return doubleStringLiteralEscapeSequenceState; }else if(c==="\""){ word += c; emitToken("str"); return dataState; }else{ word += c; } } function doubleStringLiteralEscapeSequenceState(c){ word+=c; return doubleStringLiteralState; } state=dataState; while(!reader.eof){ newState=state(reader.char); newState && (state=newState); reader.next(); } alert(JSON.stringify(tokenList));
这就是状态机的运作方式,不过要写全各种状态这种事真特么不是人干的~~