詞法解析與語法樹構建系列1


前言:代碼參考來自於《兩周自制腳本語言》, 但此系列目的並不是通讀此書,僅僅只是為了學習其中一小部分-詞法解析跟抽象語法樹構建這一過程。

 

詞法解析跟語法解析可以說應用相當廣泛,對測試工具團隊來說,會用到很多靜態掃描工具,這些工具就是對代碼塊做詞法解析與語法分析,構造一個抽象語法樹。因此,如果有必要自己寫一個靜態工具的輪子,這部分的知識不能繞過,例如coverity檢查,就是先將全部待檢查代碼解析成一棵抽象語法樹,再通過不同的檢查規則進行語法檢查。嗯,下面先來講一下詞法解析器。

 

首先我們需要明白代碼解析是一個怎樣的過程呢,其實我們輸入的所有代碼,都是通過不同的轉義來實現連接,最終編譯器接收到的都是一長串字符串而已。本節的目的就是構建一個詞法解析器,達到分詞的目的並進行測試

用來測試的代碼塊為:

test block:

while i<10 {
    sum = sum + i
    i = i + 1
}

 我們需要了解我們的流程是什么。

1. 我們要有一個能夠解析出不同字符面量的正則表達式,這樣就能將while i<10 {\n拆成 "while", "i", "<", "10"的token對象;

2. 我們需要搜集每個字符面量的當前信息,比如當前的坐標信息;

3. 提供預讀方式,這樣后續在語法樹構建出錯時,可以隨時回退

 

下面就來看一下具體代碼實現,增加了方法多注釋,詳情可以參考具體的注釋說明

Lexer.java

package stone;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Lexer {
    // 拆分字符面量的正則表達式,此處可以復用於以后自己的輪子
    public static String regexPat
        = "\\s*((//.*)|([0-9]+)|(\"(\\\\\"|\\\\\\\\|\\\\n|[^\"])*\")"
          + "|[A-Z_a-z][A-Z_a-z0-9]*|==|<=|>=|&&|\\|\\||\\p{Punct})?";
    private Pattern pattern = Pattern.compile(regexPat);
    private ArrayList<Token> queue = new ArrayList<Token>();
    // 按行讀取時用來判斷還有沒有其他內容輸入
    private boolean hasMore;
    // 用於按行讀取內容
    private LineNumberReader reader;

    public Lexer(Reader r) {
        hasMore = true;
        reader = new LineNumberReader(r);
    }

    // 按行讀取token對象
    public Token read() throws ParseException {
        if (fillQueue(0))
            return queue.remove(0);
        else
            return Token.EOF;
    }

    // 從隊列中預讀token對象
    public Token peek(int i) throws ParseException {
        if (fillQueue(i))
            return queue.get(i);
        else
            return Token.EOF; 
    }

    // 往隊列中增加token對象
    private boolean fillQueue(int i) throws ParseException {
        while (i >= queue.size())
            if (hasMore)
                readLine();
            else
                return false;
        return true;
    }

    protected void readLine() throws ParseException {
        String line;
        try {
            line = reader.readLine();
        } catch (IOException e) {
            throw new ParseException(e);
        }
        if (line == null) {
            hasMore = false;
            return;
        }
        int lineNo = reader.getLineNumber();
        Matcher matcher = pattern.matcher(line);
        matcher.useTransparentBounds(true).useAnchoringBounds(false);
        int pos = 0;
        int endPos = line.length();
        while (pos < endPos) {
            matcher.region(pos, endPos);
            if (matcher.lookingAt()) {
                addToken(lineNo, matcher);
                pos = matcher.end();
            }
            else
                throw new ParseException("bad token at line " + lineNo);
        }
        queue.add(new IdToken(lineNo, Token.EOL));
    }

    // 實例化成不同的token對象
    protected void addToken(int lineNo, Matcher matcher) {
        String m = matcher.group(1);
        if (m != null) // if not a space
            if (matcher.group(2) == null) { // if not a comment
                Token token;
                if (matcher.group(3) != null)
                    token = new NumToken(lineNo, Integer.parseInt(m));
                else if (matcher.group(4) != null)
                    token = new StrToken(lineNo, toStringLiteral(m));
                else
                    token = new IdToken(lineNo, m);
                queue.add(token);
            }
    }
    protected String toStringLiteral(String s) {
        StringBuilder sb = new StringBuilder();
        int len = s.length() - 1;
        for (int i = 1; i < len; i++) {
            char c = s.charAt(i);
            if (c == '\\' && i + 1 < len) {
                int c2 = s.charAt(i + 1);
                if (c2 == '"' || c2 == '\\')
                    c = s.charAt(++i);
                else if (c2 == 'n') {
                    ++i;
                    c = '\n';
                }
            }
            sb.append(c);
        }
        return sb.toString();
    }

    protected static class NumToken extends Token {
        private int value;

        protected NumToken(int line, int v) {
            super(line);
            value = v;
        }
        public boolean isNumber() { return true; }
        public String getText() { return Integer.toString(value); }
        public int getNumber() { return value; }
    }

    protected static class IdToken extends Token {
        private String text; 
        protected IdToken(int line, String id) {
            super(line);
            text = id;
        }
        public boolean isIdentifier() { return true; }
        public String getText() { return text; }
    }

    protected static class StrToken extends Token {
        private String literal;
        StrToken(int line, String str) {
            super(line);
            literal = str;
        }
        public boolean isString() { return true; }
        public String getText() { return literal; }
    }
}

 

測試代碼

public class LexerRunner {
    public static void main(String[] args) throws ParseException {
        Lexer l = new Lexer(new CodeDialog());
        for (Token t; (t = l.read()) != Token.EOF; )
            System.out.println("=> " + t.getText());
    }
}

結果輸出

while i<10 {
    sum = sum + i
    i = i + 1
}
=> while
=> i
=> <
=> 10
=> {
=> \n
=> sum
=> =
=> sum
=> +
=> i

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM