从零开始写个编译器吧 - 符号分析,编写 SignParser.java 文件

673 查看

上一章留下的那个大大的 TODO 表明,似乎还有什么东西没写完。没错,所以这一章我们来写 Sign 状态下的代码。

所谓 Sign 状态,即是用来处理和生成 Sign 类型的 Token ,即运算符。诸如此类,都是运算符:

+、-、*、/、>=、==、&&、||

相对于 String 字符串啊、Identifier 标示符之类的可以通过字符本身来划分开端和结尾的类型,Sign 运算符运算符类型却只能以既定形式来划分开端和结尾。

例如,

"hello world" +-&& str_hello_wold

这段“源码”可以被分割成如下一组 Token,

["\"hello world\"", " ", "+", "-", "&&", "str_hello_world"]

注意 Sign 运算符类型和 String 字符串类型、Identifier 标示符划分的区别。对于 “+”、“-” 两个字符被各自分在一个 Token 中,而 “&&” 却是由两个字符组合成一个 Token。因为 tao 语言中没有 “&” 运算符(没有与位运算,也没有短路与非短路与之分),却拥有 “&&”。

此外,

>=

对于这个字符串,到底应该划分成

[">", "="]

这样两个运算符呢,还是应该划分成

[">="]

这样一个运算符呢?

因为 “>"、"="、">=" 这三个都是合法的 tao 语言运算符。这里为了消除歧义,我规定必须划分成 ">=" 。另外,如果存在多种划分方式,尽量将短的运算符凑成长运算符。

OK,我现在决定把有关 Sign 运算符类型的代码单独写一个类。即创建一个 SignParser.java 文件,并写下如下内容。

package com.taozeyu.taolan.analysis;

class SignParser {

    static boolean inCharSet(char c) {
        //TODO
    }

    static List<String> parse(String str)  {
        //TODO
    }
}

对于,LexicalAnalysis.java ,它只会调用 SignParser.inCharset(...) 来判断当前读入的字符是不是某个既定运算符的某个字符。然后以此缓存一个字符串,然后,通过调用 SignParser.parse(str) 来将这个字符串分割成一个一个运算符,并生成 Token。

现把 LexicalAnalysis.java 中 boolean readChar(char c) 函数中TODO 消灭掉吧。

} else if(state == State.Sign) {

    if(SignParser.inCharSet(c)) {
        readBuffer.append(c);

    } else {
        List<String> list = SignParser.parse(readBuffer.toString());
        for(String signStr:list) {
            createToken(Type.Sign, signStr);
        }
        createType = null;
        state = State.Normal;
        moveCursor = false;
    }

}

当然,这里需要重载 createToken 一下。

private void createToken(Type type, String value) {
    Token token = new Token(type, value);
    tokenBuffer.addFirst(token);
    readBuffer = null;
}

OK,我们在回过头来写 SignParser.java 吧。首先,定义几个静态常量。

package com.taozeyu.taolan.analysis;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;

class SignParser {

    private final static List<HashSet<String>> signSetList;
    private final static HashSet<Character> signCharSet;
    private final static int MaxLength, MinLength;

    static {
        String[] signArray = new String[] {
            "+", "-", "*", "/", "%", 
            ">", "<", ">=", "<=", "=", "!=", "==", "=~",
            "+=", "-=", "*=", "/=", "%=",
            "&&", "||", "!", "^",
            "&&=", "||=", "^=",
            "<<", ">>", "->", "<-",
            "?", ":",
            ".", ",", ";", "..",
            "(", ")", "[", "]", "{", "}",
            "@", "@@", "$",
        };

        int maxLength = Integer.MIN_VALUE,
            minLength = Integer.MAX_VALUE;

        signCharSet = new HashSet<>();
        for(String sign:signArray) {
            int length = sign.length();
            if(length > maxLength) {
                maxLength = length;
            }
            if(length < minLength) {
                minLength = length;
            }
            for(int i=0; i<length; ++i) {
                signCharSet.add(sign.charAt(i));
            }
        }
        signSetList = new ArrayList<>(maxLength - minLength + 1);
        for(int i=0; i< maxLength - minLength + 1; ++i) {
            signSetList.add(new HashSet<>());
        }
        for(String sign:signArray) {
            int length = sign.length();
            HashSet<String> signSet = signSetList.get(length - minLength);
            signSet.add(sign);
        }
        MaxLength = maxLength;
        MinLength = minLength;
    }

    static boolean inCharSet(char c) {
        //TODO
    }

    static List<String> parse(String str)  {
        //TODO
    }
}

static 块中的 signArray 变量定义了 tao 语言所用到的所有运算符。之后的代码解析 signArray 并生成 4 个静态常量。

  1. signCharSet:tao 语言所有运算符中可能出现的字符集合。
  2. signSetList:tao 语言中根据运算符长度分开储存的 List。具体来说,如果运算符对应的字符串最短的 length 是 n,最长的是 m。那么 List 的索引范围从 0 到 (m - n) 分别储存字符 length 从 n 到 m 的所有运算符。
  3. MinLength:运算符中字符串 length 的最小长度。
  4. MaxLength:运算符中字符串 length 的最大长度。

然后是填满那两个写着 TODO 的函数。

static boolean inCharSet(char c) {
    return signCharSet.contains(c);
}

static List<String> parse(String str) throws LexicalAnalysisException {
    LinkedList<String> rsContainer = new LinkedList<>(); 
    int startIndex = 0;
    while(startIndex < str.length()) {
        String matchStr = match(startIndex, str);
        if(matchStr == null) {
            throw new LexicalAnalysisException(str.substring(startIndex));
        } else {
            rsContainer.add(matchStr);
            startIndex += matchStr.length();
        }
    }
    return rsContainer;
}

private static String match(int startIndex, String str) {
    String matchStr = null;
    int length = str.length() - startIndex;
    length = Math.min(length, MaxLength);
    if(length >= MinLength) {
        for(int i=length - MinLength; i>=0; i--) {
            int matchLength = i + MinLength;
            HashSet<String> signSet = signSetList.get(i);
            matchStr = str.substring(startIndex, startIndex + matchLength);
            if(signSet.contains(matchStr)) {
                break;
            }
            matchStr = null;
        }
    }
    return matchStr;
}

OK,搞定。特别的,如果 parse 划分运算符过程中发现留下一个尾巴匹配不了,那么将抛出一个词法错误异常。