• 词法解析与语法树构建系列1


    前言:代码参考来自于《两周自制脚本语言》, 但此系列目的并不是通读此书,仅仅只是为了学习其中一小部分-词法解析跟抽象语法树构建这一过程。

    词法解析跟语法解析可以说应用相当广泛,对测试工具团队来说,会用到很多静态扫描工具,这些工具就是对代码块做词法解析与语法分析,构造一个抽象语法树。因此,如果有必要自己写一个静态工具的轮子,这部分的知识不能绕过,例如coverity检查,就是先将全部待检查代码解析成一棵抽象语法树,再通过不同的检查规则进行语法检查。嗯,下面先来讲一下词法解析器。

    首先我们需要明白代码解析是一个怎样的过程呢,其实我们输入的所有代码,都是通过不同的转义来实现连接,最终编译器接收到的都是一长串字符串而已。本节的目的就是构建一个词法解析器,达到分词的目的并进行测试

    用来测试的代码块为:

    test block:

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

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

    1. 我们要有一个能够解析出不同字符面量的正则表达式,这样就能将while i<10 { 拆成 "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 = '
    ';
                    }
                }
                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
    => {
    => 
    
    => sum
    => =
    => sum
    => +
    => i
  • 相关阅读:
    python操作redis
    python正则表达式-案例
    hive序列化和反序列化serde
    python配置文件
    Java写入的常用技巧
    Java从string数组创建临时文件
    Java官方操纵byte数组的方式
    python实例方法、静态方法和类方法
    ast.literal_eval(转)
    impala学习笔记
  • 原文地址:https://www.cnblogs.com/alexkn/p/6336996.html
Copyright © 2020-2023  润新知