所謂的狀態機實質其實很很簡單,其存在的目的也是把大量複雜的處理分散,使處理變得簡單化一些。狀態機只有一個當前狀態,並且在當前狀態下根據輸入進行處理,然後再決定是否改變當前狀態,然後再處理下一個輸入,如此往復直到所有輸入結束。
所以,相同的輸入在不同的當前狀態下的處理是不一樣的,以字符串的處理爲例,我們來看看怎麼處理下面這條語句:
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));
這就是狀態機的運作方式,不過要寫全各種狀態這種事真特麼不是人乾的~~