實現一個簡單的詞法分析器

實現一個簡單的詞法分析器

詞法分析

詞法分析的工作是將一個長長的字符串識別出一個個的單詞,這一個個單詞就是 Token。而且詞法分析的工作是一邊讀取一邊識別字符串的,不是把字符串都讀到內存再識別

詞法單元:

  • 單詞的內部表示是詞法單元 token

  • 編程語言中最小的語法單元

  • 詞法單元的表示

目標程序語句:

  • age >= 45

解析age >= 45

過程圖:

圖1.png

標識符、比較操作符和數字字面量這三種 Token 的詞法規則:

  • 標識符:第一個字符必須是字母,後面的字符可以是字母或數字

  • 比較操作符:> 和 >=(其他比較操作符暫時忽略)

  • 數字字面量:全部由數字構成(像帶小數點的浮點數,暫時不管它)

有限自動機:

圖2.png

    1. 初始狀態:剛開始啓動詞法分析的時候,程序所處的狀態
    1. 標識符狀態:在初始狀態時,當第一個字符是字母的時候,遷移到狀態 2。當後續字符是字母和數字時,保留在狀態 2。如果不是,就離開狀態 2,寫下該 Token,回到初始狀態
    1. 大於操作符(GT):在初始狀態時,當第一個字符是 > 時,進入這個狀態。它是比較操作符的一種情況
    1. 大於等於操作符(GE):如果狀態 3 的下一個字符是 =,就進入狀態 4,變成 >=。它也是比較操作符的一種情況
    1. 數字字面量:在初始狀態時,下一個字符是數字,進入這個狀態。如果後續仍是數字,就保持在狀態 5

圓圈有單線的也有雙線的。雙線的意思是這個狀態已經是一個合法的 Token 了,單線的意思是這個狀態還是臨時狀態。

標識符和關鍵字規則的衝突

解析“int age = 40”這個語句,以這個語句爲例研究一下詞法分析中會遇到的問題:多個規則之間的衝突。

問題:

  • int 這個關鍵字,與標識符很相似,都是以字母開頭,後面跟着其他字母

換句話說,int 這個字符串,既符合標識符的規則,又符合 int 這個關鍵字的規則,這兩個規則發生了重疊。這樣就起衝突了,我們掃描字符串的時候,到底該用哪個規則呢?

當然int 這個關鍵字的規則,比標識符的規則優先級高。普通的標識符是不允許跟這些關鍵字重名的。

關鍵字是語言設計中作爲語法要素的詞彙,例如表示數據類型的 int、char,表示程序結構的 while、if,表述特殊數據取值的 null、NAN 等。

在詞法分析器中,如何把關鍵字和保留字跟標識符區分開

修改自動機爲下面的形式:

圖4.png

當第一個字符是 i 的時候,我們讓它進入一個特殊的狀態。接下來,如果它遇到 n 和 t,就進入狀態 4。但這還沒有結束,如果後續的字符還有其他的字母和數字,它又變成了普通的標識符。比如,我們可以聲明一個 intA(int 和 A 是連着的)這樣的變量,而不會跟 int 關鍵字衝突。

代碼實現

以 int x = 45爲分析目標

定義Token:

自定義的Token:


public interface Token {
    
    // 獲取類型
    TokenType getTokenType();
    
    
    // 獲取文本值
    String getText();
    
}



TokenType:Token類型

是標識符,比較操作符還是數字字面量?

public enum TokenType {

// 先定義了少量的類型 主要了解 整個過程 有需要可以後續再添加
    Assignment,// =

    Identifier,     //標識符

    IntLiteral,     //整型字面量
    StringLiteral,   //字符串字面量

    GE,     // >=

    Int

}

在主類中定義一個私有類:SimpleToken

圖示:


    private final class SimpleToken implements Token {

        private TokenType tokenType = null;

        private String text = null;

        @Override
        public TokenType getTokenType() {
            return tokenType;
        }

        @Override
        public String getText() {
            return text;
        }
    }

使用枚舉類來表示有限狀態機的各種狀態



  private enum DfaState {
        Initial,

        If, Id_if1, Id_if2, Else, Id_else1, Id_else2, Id_else3, Id_else4, Int, Id_int1, Id_int2, Id_int3, Id, GT, GE,

        Assignment,

        Plus, Minus, Star, Slash,

        SemiColon,
        LeftParen,
        RightParen,

        IntLiteral
    }

現在考慮數據的處理:

圖示:

圖5.png

  // 開始解析 進入初始化狀態
    // 解析完一個Token 也進入初始化狀態
    private DfaState initToken(char ch) {

        if (tokenText.length() > 0) {

            current_token.text = tokenText.toString();

            tokens.add(current_token);


            tokenText = new StringBuilder();

            current_token = new SimpleToken();



        }

        //初始狀態
        DfaState dfaState = DfaState.Initial;

        if (isAlpha(ch)) {              //第一個字符是字母
            // 出現了字符 'i'
            if (ch == 'i') {
                dfaState = DfaState.Id_int1;
            } else {
                dfaState = DfaState.Id; //進入Id狀態
            }
            current_token.tokenType = TokenType.Identifier;
            tokenText.append(ch);
        } else if (isDigit(ch)) {   //第一個字符是數字


            dfaState = DfaState.IntLiteral;

            current_token.tokenType = TokenType.IntLiteral;

            tokenText.append(ch);

        } else if (ch == '=') {
            dfaState = DfaState.Assignment;
            current_token.tokenType = TokenType.Assignment;
            tokenText.append(ch);
        } else {
            dfaState = DfaState.Initial;
        }

        return dfaState;


    }


    public SimpleTokens lexicalanalyz(String resource) throws IOException {


        tokens = new ArrayList<>();

        CharArrayReader reader = new CharArrayReader(resource.toCharArray());

        tokenText = new StringBuilder();

        current_token = new SimpleToken();

        int int_for_char = 0;

        char char_for_int = 0;

        DfaState dfaState = DfaState.Initial;

        while ( (int_for_char = reader.read()) != -1 ) {

            char_for_int = (char) int_for_char;

            switch (dfaState) {



                case Initial:

                    dfaState = initToken(char_for_int);
                    break;

                case Id:
                    if (isDigit(char_for_int) || isAlpha(char_for_int)) {
                        tokenText.append(char_for_int);
                    } else {
                        dfaState = initToken(char_for_int);
                    }
                    break;

                case GT:
                    if (char_for_int == '=') {

                        current_token.tokenType = TokenType.GE;

                        dfaState = DfaState.GE;

                        tokenText.append(char_for_int);
                    } else {

                        dfaState = initToken(char_for_int);

                    }

                    break;

                case GE:
                case Assignment:
                case IntLiteral:

                    if (isDigit(char_for_int)) {

                        tokenText.append(char_for_int);

                    } else {

                        dfaState = initToken(char_for_int);

                    }

                    break;
                case Id_int1:

                    if (char_for_int == 'n') {

                        dfaState = DfaState.Id_int2;

                        tokenText.append(char_for_int);

                    } else if (isDigit(char_for_int) || isAlpha(char_for_int)) {

                        dfaState = DfaState.Id;

                        tokenText.append(char_for_int);
                    } else {

                        dfaState = initToken(char_for_int);

                    }

                    break;

                case Id_int2:
                    if (char_for_int == 't') {
                        dfaState = DfaState.Id_int3;
                        tokenText.append(char_for_int);
                    }
                    else if (isDigit(char_for_int) || isAlpha(char_for_int)){
                        dfaState = DfaState.Id;    //切換回id狀態
                        tokenText.append(char_for_int);
                    }
                    else {

                        dfaState = initToken(char_for_int);

                    }
                    break;
                case Id_int3:
                    if (isBlank(char_for_int)) {
                        current_token.tokenType = TokenType.Int;
                        dfaState = initToken(char_for_int);
                    }
                    else{
                        dfaState = DfaState.Id;    //切換回Id狀態
                        tokenText.append(char_for_int);
                    }
                    break;

                default:

            }
            
        }


        if (tokenText.length() > 0) {
            initToken(char_for_int);
        }

        return new SimpleTokens(tokens);
    }


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