词法解析与语法树构建系列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

 

posted @ 2017-01-21 19:35  李雷雷alexkn  阅读(4935)  评论(0编辑  收藏  举报