狀態機——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));
View Code

  這就是狀態機的運作方式,不過要寫全各種狀態這種事真特麼不是人乾的~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章