[C++][第3篇]main() 和 活起来的文法 expression() primary() term()

使用全局变量

Token_stream ts;   // provides get() and putback()  // to-do[5]

全部文法回顾

// a simple expression grammar:
Expression:
  Term
  Expression "+" Term 
  Expression "–" Term
Term:
  Primary
  Term "*" Primary 
  Term "/" Primary 
  Term "%" Primary 
Primary:
  Number
  "(" Expression ")" 
Number:
  floating-point-literal

to-do[?] int main() { }

int main() /* .to-do[ ]. */ 
try
{
    double val = 0;
    while (cin) {
        Token t = ts.get();

        if (t.kind == 'q') break;       // 'q'  停止程序
        if (t.kind == ';')                  //   ';'  立即输出运算结果
            cout << "=" << val << '\n';
        else
            ts.putback(t);
        val = expression();
    }
}
catch (exception& e) {
    cerr << "error: " << e.what() << '\n'; 
    return 1;
}
catch (...) {
    cerr << "Oops: unknown exception!\n"; 
    return 2;
}

测试用例及其输出效果

用例截图

[C++][第3篇]main() 和 活起来的文法 expression() primary() term()_第1张图片
第6章 代码段 测试用例.jpg

用例使用

  • 运行代码后在命令行窗口输入用例 2+3*7+5; ;
  • 2+3*7+5; 不用刻意留下空格,这是代码的功能,能正确识别成Token ;
  • 注意以分号;作为单个算术表达式的结束 ;
  • 命令行窗口立刻给出计算答案 =28

计算结果

2+3*7+5;
=28
3-(9/2);
=-1.5
33.33+22.22*7*2;
=344.41
7-8/2;
=3
q

to-do[6] double expression() {}

expression 文法回顾

Expression:
  Term
  Expression "+" Term 
  Expression "–" Term

expression() C++ 实现代码


double expression() {   /* . to-do[8] . */ 
    double left = term();      // 读入并计算一个 Term
    Token t = ts.get();        // 获取下一个 Token

    while(true) {    
        switch(t.kind) {
        case '+':
            left += term();    // 计算一个 Term 并且 相加
            t = ts.get();
            break;
        case '-':
            left -= term();    // 计算一个 Term 并且 相减
            t = ts.get();
            break;
        default: 
            ts.putback(t);     // 将 t 放回到 token 流
            return left;       // 再没有+ - 时返回计算结果
        }
    }

}   // 处理 + 法 以及 - 法

to-do[7] double term() {}

term 文法回顾

Term:
  Primary
  Term "*" Primary 
  Term "/" Primary 
  Term "%" Primary 

term C++ 实现代码

double term() {   /* . to-do[7] . */ 
    double left = primary();
    Token t = ts.get();        // 从 token流 中获取下一个 token

    while(true) {
        switch (t.kind) {
        case '*':
            left *= primary();
            t = ts.get();
            break;
        case '/':
            {    
                double d = primary();
                if (d == 0) error("divide by zero");
                left /= d; 
                t = ts.get();
                break;
            }
        default: 
            ts.putback(t);     // put t back into the token stream
            return left;
        }
    }

}   // deal with * and /

to-do[8] double primary() { }

primary 文法回顾

Primary:
  Number
  "(" Expression ")" 

( Expression ) 是一个 primary

  • 体现到具体的C++代码实现之中,在定义primary()之前一定要写上一句关于expression()的声明,回顾代码结构如下:
double expression();   // 仅仅是声明 让primary() 可以调用 expression()

double primary() {   /* . to-do[8] . */ }   // deal with numbers and parentheses
double term() {   /* . to-do[7] . */ }   // deal with * and /
double expression() {   /* . to-do[6] . */ }   // deal with + and –

primary() C++ 实现代码

double primary() {   /* . to-do[8] . */ 
 Token t = ts.get();
    switch (t.kind) {
    case '(':    //  处理 '(' expression ')'
        {    
            double d = expression();
            t = ts.get();
            if (t.kind != ')') error("')' expected");
            return d;
        }
    case '8':            // 使用 '8' 来标记一个浮点数
        return t.value;  // 返回浮点数的值
    default:
        error("primary expected");
    }
}   // deal with numbers and parentheses

t = ts.get(); ts.putback(t); 作用是什么?

答:以Expression来说,文法显示任何一个Expression都是以一个Term开始,Term后面可以接一个+或者-。因此,必须先寻找Term,看它后面是否有一个+或者一个-,并且重复此步骤直到Term后面没有加号或者减号为止,为此写出一个while(true){}的循环;对于像1+2+3;这样的表达式应该给出答案6,对于1-2-3这样的表达式应该给出答案 -4。用t = ts.get();就是在帮助循环取下一个运算符,同时,ts.putback(t);把读取的单词放回到全局变量的输入流ts里面,结合main()里面的条件语句,如果遇到了分号;就可以直接输出答案了,比如输入用例2;应该是马上输出结果=2;

至今为止的全部代码

#include "std_lib_facilities.h"

class Token {   /* . to-do[1] . */ 
public:
    char kind;      // 记录token的类型
    double value;   // 对于浮点数而言:这是浮点数的值
    Token(char ch) 
        : kind{ch}, value{0} {}
    Token(char ch, double val) 
        : kind{ch}, value{val} {}
};

class Token_stream {   /* . to-do[2] . */ 
public:
    Token_stream(); // 创建一个Token流,从cin读取字符
    Token get();    // 获取一个Token
    void putback(Token t); // 放回一个Token
private:
    bool full{false}; // 缓冲区中是否有Token
    Token buffer;    // 存放着通过putback(t)操作放回的Token,这个地方称之为缓冲区
};

void Token_stream::putback(Token t) {   /* . to-do[3] . */ 
    buffer = t;     // 拷贝t到缓冲区
    full = true;    // 表示缓冲区被占用
}



Token Token_stream::get() {   /* . to-do[4] . */ 
    if(full) {
        full = false;
        return buffer;
    }   

    char ch;
    cin >> ch; // >> 会跳过空白(空格、换行、制表符等)

    switch(ch) {
        case ';':   // 表示立即输出结果
        case 'q':   // 表示退出
        case '(': case ')': case '+': case '-': case '*': case '/':
            return Token{ch};   // 每个字符自身表示一个Token
        case '.':
        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
        { 
            cin.putback(ch);        // 将数字(或小数点)放回到 标准输入cin 中
            double val;
            cin >> val;             // cin 自动读取一个 double
            return Token{'8',val};  // 用'8'表示读取了一个浮点数
        }
        default:
            error("Bad token");
    }

}

// to-do[5]
// 构造函数 Token_stream()
Token_stream::Token_stream()
    :full{false}, buffer{0}   // no Token in buffer
{
}


Token_stream ts;   // provides get() and putback()  // to-do[5]

double expression();   // declaration so that primary() can call expression()

double primary() {   /* . to-do[8] . */ 
 Token t = ts.get();
    switch (t.kind) {
    case '(':    //  处理 '(' expression ')'
        {    
            double d = expression();
            t = ts.get();
            if (t.kind != ')') error("')' expected");
            return d;
        }
    case '8':            // 使用 '8' 来标记一个浮点数
        return t.value;  // 返回浮点数的值
    default:
        error("primary expected");
    }
}   // deal with numbers and parentheses

double term() {   /* . to-do[7] . */ 
    double left = primary();
    Token t = ts.get();        // 从 token流 中获取下一个 token

    while(true) {
        switch (t.kind) {
        case '*':
            left *= primary();
            t = ts.get();
            break;
        case '/':
            {    
                double d = primary();
                if (d == 0) error("divide by zero");
                left /= d; 
                t = ts.get();
                break;
            }
        default: 
            ts.putback(t);     // put t back into the token stream
            return left;
        }
    }

}   // deal with * and /

double expression() {   /* . to-do[6] . */ 
    double left = term();      // 读入并计算一个 Term
    Token t = ts.get();        // 获取下一个 Token

    while(true) {    
        switch(t.kind) {
        case '+':
            left += term();    // 计算一个 Term 并且 相加
            t = ts.get();
            break;
        case '-':
            left -= term();    // 计算一个 Term 并且 相减
            t = ts.get();
            break;
        default: 
            ts.putback(t);     // 将 t 放回到 token 流
            return left;       // 再没有+ - 时返回计算结果
        }
    }

}   // 处理 + 法 以及 - 法

int main() /* .to-do[ ]. */ 
try
{
    double val = 0;
    while (cin) {
        Token t = ts.get();

        if (t.kind == 'q') break; // 'q' for quit
        if (t.kind == ';')        // ';' for "print now"
            cout << "=" << val << '\n';
        else
            ts.putback(t);
        val = expression();
    }
}
catch (exception& e) {
    cerr << "error: " << e.what() << '\n'; 
    return 1;
}
catch (...) {
    cerr << "Oops: unknown exception!\n"; 
    return 2;
}
  • 第六章的代码到此为止,已经可以出截图里面的效果了;
  • 第七章的开始增加 变量功能 并且 对代码进行清理 等,之后代码长度会增加,变量个数会增加,但是这个算术表达式解释器的基本功能已经都实现了。

基于文法的简单算术表达式计算器 系列索引

  • [C++][第0篇] 系列索引 基于文法的算术表达式解释器
    关键词 系列索引

你可能感兴趣的:([C++][第3篇]main() 和 活起来的文法 expression() primary() term())