上一章提到我要手写词法分析器这个状态机,嗯,那就让我们开始吧。
public class LexicalAnalysis {
private static enum State {
Normal,
Identifier, Sign, Annotation,
String, RegEx, Space;
}
public LexicalAnalysis(Reader reader) {
//TODO
}
Token read() throws IOException, LexicalAnalysisException {
//TODO
return null;
}
private State state;
private final LinkedList<Token> tokenBuffer = new LinkedList<>();
private StringBuilder readBuffer = null;
private void refreshBuffer(char c) {
readBuffer = new StringBuilder();
readBuffer.append(c);
}
private void createToken(Type type) {
Token token = new Token(type, readBuffer.toString());
tokenBuffer.addFirst(token);
readBuffer = null;
}
private boolean readChar(char c) throws LexicalAnalysisException {
//TODO 状态机逻辑...
}
}
于是我又添加了一点代码。可以看到,我放着 readChar() 函数的 TODO 不管,又添加了一个 readChar(char c) 的函数。因为我有这么一个考虑:对于readChar()方法而言,这个是一个被动调用的函数,外部调用一下,就读到一个Token。这样设计,今后写 Parser 时读取 Token 会要简单许多。而readChar(char c)是一个主动调用的函数,它的实现部分直接处理接受的参数 char 就行了,而且也不必立即返回 Token。这样我在写 readChar(char c) 本身的时候会简单许多。
实际上,在状态机不断接受字符的过程中,会先调用 readBuffer.append(...) 将其缓存,并在适当的时机调用 createToken(...) 生成 Token。
至此,readChar(char c) 就变成了一个纯粹用于实现状态机功能的函数了。让我们开始写这个函数吧。
private State state;
private boolean readChar(char c) throws LexicalAnalysisException {
boolean moveCursor = true;
Type createType = null;
if(state == State.Normal) {
} else if(state == State.Identifier) {
} else if(state == State.Sign) {
} else if(state == State.Annotation) {
} else if(state == State.String) {
} else if(state == State.RegEx) {
} else if(state == State.Space) {
}
if(createType != null) {
createToken(createType);
}
return moveCursor;
}
一个典型的状态机,处于不同状态,对于接受的参数 char 进行不同的操作。同时,我可以通过 state = ?; 来改变状态机的状态。
这个函数会返回一个 boolean 类型的变量,即 moveCursor,这个变量表示,在读完当前 char 之后,是否移动游标读取下一个字符。如果为 false,则该函数的下一次调用的参数与前一次调用的参数会一模一样(因为游标没有移动嘛)。
最自然的情况下 moveCursor == true,就是读了一个字符以后再读下一个字符嘛。
嗯,然后开始填充这些 if-else 之间的空白吧,先从 Normal 状态开始。
private boolean readChar(char c) throws LexicalAnalysisException {
boolean moveCursor = true;
Type createType = null;
if(state == State.Normal) {
if(inIdentifierSetButNotRear(c)) {
state = State.Identifier;
}
else if(SignParser.inCharSet(c)) {
state = State.Sign;
}
else if(c == '#') {
state = State.Annotation;
}
else if(c == '\"' | c == '\'') {
state = State.String;
}
else if(c == '`') {
state = State.RegEx;
}
else if(include(Space, c)) {
state = State.Space;
}
else if(c == '\n') {
createType = Type.NewLine;
}
else if(c == '\0') {
createType = Type.EndSymbol;
}
else {
throw new LexicalAnalysisException(c);
}
refreshBuffer(c);
} else if(state == State.Identifier) {
} else if(state == State.Sign) {
} else if(state == State.Annotation) {
} else if(state == State.String) {
} else if(state == State.RegEx) {
} else if(state == State.Space) {
}
if(createType != null) {
createToken(createType);
}
return moveCursor;
}
填充的代码中出现了一些新函数和变量,我将补充在下面。
private static final char[] Space = new char[] {' ', '\t'};
private boolean inIdentifierSetButNotRear(char c) {
return (c >= 'a' & c <= 'z' ) | (c >='A' & c <= 'Z') | (c >= '0' & c <= '9')|| (c == '_');
}
private boolean include(char[] range, char c) {
boolean include = false;
for(int i=0; i<range.length; ++i) {
if(range[i] == c) {
include = true;
break;
}
}
return include;
}
其中 include 函数用于判断字符是否归属于某个集合。而 inIdentifierSetButNotRear 用于判定字符是否属于标示符字符。这里的标示符不仅限于用户定义的标示符,还包括关键字和数字(第5章有提及)。
当然,还有一个 SignParser.inCharSet(c) ,不过这个我想推迟到今后的章节说明,此处就不多做展开。