• 词法分析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

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

  • 相关阅读:
    投资理财的方式
    创造新的行业需要经历四个时期
    django 架构最佳实践相关整理
    Tornado 框架使用详解, 从懵逼到开撸
    wsl 安装 Ubuntu18.04 以及 docker
    ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
    多测师拱墅校区肖sir_高级金牌讲师_第二个月基本情况
    多测师拱墅校区肖sir_高级金牌讲师_python 的基本运用
    多测师拱墅校区肖sir_高级金牌讲师_html
    多测师拱墅校区肖sir_高级金牌讲师_python 安装和pycharm
  • 原文地址:https://www.cnblogs.com/Frandy/p/parser_simple_lexer.html
Copyright © 2020-2023  润新知