码迷,mamicode.com
首页 > 其他好文 > 详细

自制计算器(一):Scanner

时间:2016-05-07 23:34:19      阅读:957      评论:0      收藏:0      [点我收藏+]

标签:

  今天来讲第一部分Scanner,俗称扫描器,也叫词法分析器。想要了解Scanner究竟做了什么,我们要从整个流程讲起。 首先,计算器得到的输入的是一串字符,如 ”1 + 2“。 如果不学编译原理,应该如何计算出结果呢?可能会利用栈,一个数字栈一个符号栈云云,但这样处理简单运算还好,如果有大于10的数,小数或者含有括号的情况,情况会很糟糕,何况编程语言本就比四则运算复杂许多。一般编译器的做法,则是将字符串以Token为单位分割开来。Token,中译为”标记、令牌“,不直观,你可以理解为表达式中的最小类型单位(《编译语言实现模式》中举例自然语言的句子成分也很贴切)。例如上文提到的”1 + 2“可以分解为“数字+符号+数字”的组合。得出Token之后,我们就可以在接下来的语法分析部分(Parser)中根据文法对其进行处理,具体方法我们下节再说。
     词法分析可以采用lex等工具自动实现,但为了学习,我们还是人肉实现。这里引入一个概念,状态机(state machine),也叫自动机(automation),顾名思义,就是拥有状态集合的机器,并可以通过边进行状态的迁移(类似图,我突然想是否能直接用图实现状态机,等活干完试一下)。
     先举一个简单的例子,下图表示一个个位整数,state 1为开始状态,此时向状态机输入一个0-9的整数,状态机迁移到state 2结束(接受)状态,当字符输入完毕并达到结束状态时,表示匹配成功。
  
     技术分享技术分享
     多位整数应该如何表示?可以在结束状态上添加一个循环,表示接受多次输入,相当于正则中的”+“。下方的边表示单独的0。
     技术分享技术分享
     PS:其实状态机也可以表示状态。如下图,表示二元运算式子,当然,这里不讲,只是让大家了解下Token到底是用来干什么的。
     技术分享技术分享
     好了,状态机的概念理解后,我们来讲如何实现它。先理清思路,我们刚才讲到,Scanner的目的就是构造出Token,所以,首先要创建class Token
    class Token 
    {
        //

    private:
        TokenType type_;
        int intValue_;
    };

 

     type_代表Token的类型,这里把Token分成四大类,即数字(暂只支持整数)、符号(+-*/)、左右括号以及无效状态(invalid)
     intValue_表示整数的值
     
    enum class TokenType 
    {
        INT,
        FLOAT,
        ADD,        // +
        SUB,        // -
        MUL,        // * 
        DIV,        // /
        LEFT_PAR,    // (
        RIGHT_PAR,    // )

        INVALID,    // 无效类型
    };

 

 
     其次,由于operator多而杂,我们可以写一个dictionary以键值来存储它们。它有HasToken、FindToken等方便以供调用(太长不写了,详见代码)。
 
     有了Token之后,就可以构建提取Token的函数了。这里主要讲一下GetNextToken,他是整个状态机的核心,处于while循环中,共分为两部分。第一部分为状态处理,根据当前状态选择处理方式,第二部分state迁移。此阶段对char进行识别,判定迁移state。初始状态为start,会直接进入state迁移,所以本质上就是根据前一个 or 几个char确定状态后,交由handle函数处理。
    
    Token Scanner::GetNextToken(stringstream &expression)
    {
        // first char
        auto currectChar = GetNextChar(expression);

        // state judge
        while (!expression.eof())
        {
            // 第一部分
            switch (state_)
            {
            case State::START:
                break;

            case State::NUMBER:
                return HandleNumberState(expression, currectChar);
                break;

            case State::OPERATOR:
                return HandleOperatorState(expression, currectChar);
                break;

            case State::ERROR:
                ErrorToken("error input");
                return Token(TokenType::INVALID);
            }

            string currectStr;
            currectStr.push_back(currectChar);

            //第二部分
            if (iswdigit(currectChar))
            {
                state_ = State::NUMBER;
            }
            else if (dict_.HasToken(currectStr))
            {
                state_ = State::OPERATOR;
            }
            else
            {
                state_ = State::ERROR;
            }
        }
        return Token(TokenType::INVALID);
    }        

 

     两个handle函数的会把符合条件的char存入buffer,返回构建成Token。
     HandleNumberState:接收两个参数字符stream和currectChar(刚才iswdigit()的数字)。构建后重置state为start表示匹配结束。代码如下:
  
    Token Scanner::HandleNumberState(stringstream &expression, char currectChar)
    {
        // first char
        string buffer;
        buffer.push_back(currectChar);

        while (!expression.eof() && isdigit(expression.peek()))
        { 
            buffer += GetNextChar(expression);
        }

        // reset state
        state_ = State::START;

        // string to int
        int value;
        std::stringstream stream(buffer);
        stream >> value;

        return Token(TokenType::INT, value);
    }

 

 
     HandleOperatorState:与number唯一不同的是Token通过自建的Dictionary类构建。
  
    Token Scanner::HandleOperatorState(stringstream &expression, char currectChar)
    {
        // first char
        string buffer;
        buffer.push_back(currectChar);
        auto token = dict_.FindToken(buffer);

        //reset state
        state_ = State::START;

        return token;
    }

  GetTokenList、GetNextTokenList就是以此为基础获得一行内的所有Token。

  Scanner的内容至此就讲完了,接下来是Parser部分。另外,写东西果然好难,磕磕巴巴,很多思路根本无法表达( ╯□╰ )

               

自制计算器(一):Scanner

标签:

原文地址:http://www.cnblogs.com/elninovt9/p/5469305.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!