词法分析Simple Lexer

以前尝试阅读《编译原理》,但都没有读下来,现在看《编程语言实现模式》,感觉轻松多了。其实,目前我只对解析感兴趣,只要看这本书的第一部分解析起步就可以了,确实没有必要去啃《编译原理》。下面就是学习的内容了。

解析,分为两步,先是进行词发分析,将输入转换成一个一个的Token,然后是进行语法分析。一个一个的Token组成语句,对应一定的语法。根据这些Toke,匹配一定的语法。词法分析器,lexer,是语法分析器,parser,的基础。

先来看看词发分析器。例如语句,1024+ 78*pi,一个简单的表达式,
词发分析就是要得出上面的语句由这些Token组成,1024, + , 78, * ,pi。Token可以是运算符,可以是数字,也可以是字符串。在处理原来的字符串时,遇到第一个字符'1',把它放入一个buf里面,然后遇到0,仍然是合法的数字,放入buf里面,直到遇到'+',这时就可以把buf返回,得到1024这一个token。然后从刚才的位置继续,'+‘,直接返回这个Token,可以把其作为运算符一类,也可以是运算符细分之后的‘+’一类。后面遇到空格,跳过。后面的类似处理。

在做这些操作的时候,由lexer的数据结构支持,所以lexer需要保存原始的字符串,需要知道当前的位置,而Token应该有两个元素,一个保存它的内容,另一个保存它的类型。在解析的时候,就可以知道所得到的Token类型,直接保存下来,便于以后处理。而对应的操作,就需要有,判断当前字符是什么类型,字母,数字,或是运算符等。然后就是收集Token的操作。当遇到一个数字的时候,需要循环处理,知道遇到的不再是数字,这是对简单的整数,如果需要处理浮点数,科学记数法等,就会复杂一些了。当遇到一个字母时,如果不允许字符串里面包含数字,那么就只收集字母,如果可以包含数字,那么就是遇到运算符的时候结束了。

下面就是一个简单的lexer的实现,支持字符串,运算符,数字,的词法分析。
首先是Token,这里用到了TokenType,type用整数表示,本来是想用enum class TokenType的,但是在继承的时候遇到了问题。虽然这个enum class定义很像class,但是不支持继承。那么我想添加type的时候,就不是很方便。然后就有了下面的一个类来定义TokenType。

class TokenType
{
public:
    TokenType():_EOF(0),TEXT(1),NUMBER(2),name({"EOF","TEXT","NUMBER"}){
    }
    string Name(int t) {
        return name.at(t);
    }
public:
    const int _EOF;
    const int TEXT;
    const int NUMBER;
    vector<string> name;// = {"EOF","TEXT"};
}TokenType;

class Token
{
public:
    Token(){}
    Token(int tp, string tx) {
        type = tp;
        text = tx;
    }
    int Type() const {
        return type;
    }
    string Text() const {
        return text;
    }
    friend ostream& operator << (ostream& out,const Token& token){
        out << "<" << TokenType.Name(token.Type()) << "," << token.Text() << ">" << endl;
    }
protected:
    int type;
    string text;
};

在Lexer里面,比较重要的是如何收集Token,对应的是NextToken这个方法,而在这里面用到的判断当前字符类型的isLETTER,isWS,isDIGIT,都很容易实现,而TEXT,NUMBER则是在触发收集对应Token的时候开始执行。这几个方法如下。

/*! \brief consume the current char, and get the next char
     * if it is the end of input, set c as EOF
     */
    void consume(){
        p++;
        if (p >= text.length())
            c = _eof;
        else
            c = text[p];
    }
    /*! \brief match the current char to target char
     *
     * @param x
     */

    void match(char x){
        if(c==x)
            consume();
        else
            LexerError err(x,c,p);
    }

    /*! \brief get token from the text,
     * here only support text word and digital number,
     * integer, float or scientific number
     * @return the next token
     */
    Token NextToken(){
        while(c!=_eof){
            if(isWS())
                WS();
            else if(isLETTER())
                return TEXT();
            else if(isDIGIT())
                return NUMBER();
            else
                LexerError err(c,p);
        }
        return _EOF();
    }

    bool isWS(){
        return (c==' ' || c=='\t' || c=='\r');
    }
    bool isLETTER(){
        return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));
    }
    bool isDIGIT(){
        return (c >= '0' && c <= '9');
    }

    /*! \brief Get all the following digits as an integer
     *
     * @return return the integer in the string format
     */
    string Digit() {
        string buf;
        do {
            buf.push_back(c);
            consume();
        } while (isDIGIT());
        return buf;
    }

    /*! \brief Skip all whitespace
     *
     */
    void WS(){
        while(isWS())
            consume();
    }

    Token _EOF(){
        return Token(TokenType._EOF,toString(_eof));
    }
    /*! \brief Get all the following letter into a text word
     *
     * @return return the word as a TEXT token
     */
    Token TEXT() {
        string buf;
        do {
            buf.push_back(c);
            consume();
        } while (isLETTER());
        return Token(TokenType.TEXT, buf);
    }

    /*! \brief Get the number, whether it is a integer, or a float,
     * or even in scientific format
     *
     * @return
     */
    Token NUMBER() {
            string buf = Digit();
        if (c == '.')
            buf += Digit();
        if (c == 'e' || c == 'E') {
            consume();
            buf.push_back(c);
            if (c == '+' || c == '-') {
                consume();
                buf.push_back(c);
                buf += Digit();
            }
        }
        return Token(TokenType.NUMBER, buf);
    }

可以看看收集NUMBER的过程,如果使用正则表达式,对应的模式为 (\d)+([.](\d)+)?([e|E][-|+]?(\d)+)?。或许正则表达式的过程就是上面的过程。

下面是测试结果,

开始使用github了,这个项目的完整代码可以参考,https://github.com/Frandy/anapig.git

希望能与大家多交流,谢谢。

posted @ 2012-10-06 17:57  Frandy.CH  阅读(1672)  评论(0编辑  收藏  举报