上一章留下的那个大大的 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 个静态常量。
- signCharSet:tao 语言所有运算符中可能出现的字符集合。
- signSetList:tao 语言中根据运算符长度分开储存的 List。具体来说,如果运算符对应的字符串最短的 length 是 n,最长的是 m。那么 List 的索引范围从 0 到 (m - n) 分别储存字符 length 从 n 到 m 的所有运算符。
- MinLength:运算符中字符串 length 的最小长度。
- 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 划分运算符过程中发现留下一个尾巴匹配不了,那么将抛出一个词法错误异常。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。