从零开始写个编译器吧 - 开始写词法分析器(1)

550 查看

上一章提到我要手写词法分析器这个状态机,嗯,那就让我们开始吧。

    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) ,不过这个我想推迟到今后的章节说明,此处就不多做展开。