前言:代碼參考來自於《兩周自制腳本語言》, 但此系列目的並不是通讀此書,僅僅只是為了學習其中一小部分-詞法解析跟抽象語法樹構建這一過程。
詞法解析跟語法解析可以說應用相當廣泛,對測試工具團隊來說,會用到很多靜態掃描工具,這些工具就是對代碼塊做詞法解析與語法分析,構造一個抽象語法樹。因此,如果有必要自己寫一個靜態工具的輪子,這部分的知識不能繞過,例如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