从一个小巧的计算器,体会递归下降的方法

来源:互联网 发布:java编译过程详解 编辑:程序博客网 时间:2024/05/17 03:51

很遗憾,大二下学期买的那本编译原理,至今还没看完第一章。由于自己不是计科的学生,当时完全是出于一种好奇心,把它买了下来。又由于那个暑假,没有带电脑回家,光看着那本书,总觉得太单调,后来就放弃了自学的计划。一定要找一个时间,把这方面的基础知识补回来。

今天,看到了《The C++ Programming Language》的第六章,上面介绍了一个桌面计算器的程序。程序里洽好涉及到编译的知识。我犹如遇到了救星,趁此机会,让自己对编译原理有一个具体的认识,对以后的学习肯定有益无害。由于书中的程序是分散的,因为作者的目的是利用这一个小程序来介绍C++特征,所以,不太适合我们从一个整体上去把握程序的思路。于是,我老老实实把书中的程序重新组织了一遍,还发现其中有一个小错误(有没有感觉到我是一个很喜欢挑剌的人^_^),现在我把整理好的程序,全部奉献出来,像其中的三个函数expr(),term(),prim(),如果觉得它们之间的谐作关系,难以理解的话,不妨把程序放在机子上,一步步跟踪一遍。

词法分析器(get_token(),其实就是输入函数)、语法分析器(expr(),term(),prim())等,放在文件Caculate.h中:

 

/*
用分模块的思想,实现一个计算器
此程序源自于《The C++ Programming Language》
书中把程序的代码拆分成七零八散来讲解,我现在把
它组合起来,也许更利于我们去理解它的思想。
文件名:Caculate.h
*/

#ifndef CACULATE_H
#define CACULATE_H

#include 
<iostream>
#include 
<istream>
#include 
<string>
#include 
<map>
#include 
<cctype>    //isalpha 等

/*该计算器所接受的语言的语法:
program:
    END    //表示输入结果
    expr_list END
expr_list:
    expression PRINT    //PRINT是分号
    expression PRINT expr_list
expression:
    expression + term
    expression - term
    term
term:
    term / primary
    term * primary
    primary
primary:
    NUMBER
    NAME
    NAME = expression
    - primary
    (expression)
*/

using namespace std;

namespace zyk{

    
enum Token_value
    {
        NAME,        NUMBER,        END,
        PLUS
='+',    MINUS='-',    MUL='*',    DIV='/',
        PRINT
=';',    ASSIGN='=',    LP='(',        RP=')'
    };

    Token_value curr_tok 
= PRINT;
    
string string_value;    //存储最近遇到的NAME字符串
    double number_value;    //存储最近遇到的NUMBER值
    int no_of_errors;    //统计错误次数
    map<string,double> table;//映射表,表明变量所对应的数值

    
//声明一个输入流指针,以便替代cin,以便可以用命令行的方式来向程序传递表达式
    istream * input;

    
//词法分析函数
    Token_value get_token();//返回被读单词的枚举值

    
//语法分析函数,参数get用来指明是否需要调用get_token()去取得下一个单词
    double expr(bool get);    //处理+和-,并返回表达式结果
    double term(bool get);    //处理*和/,并返回表达式结果,乘、除的优先级高于加、减
    double prim(bool get);    //处理初等项

    
//错误处理
    double error(const string & s);
}

zyk::Token_value zyk::get_token()
{
    
char ch;
    
/*********************************************
    这一个循环的意义在于:
    1.如果读到一个既不是换行' '符也不是空格" "符给ch,则此循环结束
    2.如果输入流中,已经没字符,即(*input).get(ch)返回值为0,则结束这次函数
      调用,并返回一个END单词
    *********************************************
*/
    
do
    {
        
if (!(*input).get(ch)) return curr_tok = END;

    }
while(ch != ' ' && isspace(ch));

    
switch(ch)
    {
    
case ';':
    
case ' ':
        
return curr_tok = PRINT;    //一个表达式结束

    
//以下是操作符
    case '*':case '/':case '+':case '-':case '(':case ')':
    
case '=':
        
return curr_tok = Token_value(ch);

    
//如果第一个为数字或小数点,则说明这是一个数值,把这个数值传给number_value
    case '0':case '1':case '2':case '3':case '4':case '5':
    
case '6':case '7':case '8':case '9':case '.':
        (
*input).putback(ch);
        (
*input)>>number_value;
        
return curr_tok=NUMBER;

    
//遇到NAME, NAME =, 或者非法单词 
    default:    
        
if (isalpha(ch))//符何变量命名规则,变量的第一个字符应该是字母
        {
            zyk::string_value 
= ch;
            
while ((*input).get(ch) && isalnum(ch))//中间字符,可以是字母或数字
            {
                string_value.push_back(ch);
                
//(*input).putback(ch);    //原书如此,其实是一个疏漏
                return curr_tok = NAME;    //指示当前的单词为NAME

            }

        }
        error(
"bad token");
        
return curr_tok=PRINT;
    }
}

double zyk::prim(bool get)
{
    
if (get
    {
        get_token();    
//读取下一次单词
    }

    
switch(curr_tok)    //根据当前的单词值
    {
    
case zyk::NUMBER:    //如果是浮点数
        {
            
double v = number_value;
            
//这一步非常重要,就是说,读完一个数值后,再读下一个字符,这样,当prim返回的时候,
            
//curr_tok是下一个新的单词
            get_token();    
            
return v;
        }
    
case zyk::NAME:
        {
            
double & v = table[string_value];
            
if (get_token() == zyk::ASSIGN)    //表明是给变量赋值
            {
                v 
= zyk::expr(true);    //读取ASSIGN右边的数值,并赋给它
            }    
            
return v;
        }
    
case zyk::MINUS:    //一元,二元的减法由expr()处理
        {
            
return -prim(true);
        }
    
case zyk::LP:
        {
            
double e = zyk::expr(true);
            
if (curr_tok != RP)//调用完expr后,curr_tok指示下一个新的单词
            {
                
return error(") expected");
            }
            get_token();    
//吃掉')',即使curr_tok指示')'后的下一个单词
            return e;
        }
    
default:
        
return error("primary expected");
    }

}

double zyk::term(bool get)
{
    
double left = prim(get);    //调用prim,获得左值
    while(true)
    {
        
switch (curr_tok)
        {
        
case zyk::MUL:
            {
                left 
*= prim(true);
                
break;
            }
        
case zyk::DIV:
            {
                
if (double d = prim(true))
                {
                    left 
/= d;
                    
break;
                }
                
return error("divide by 0");
            }
        
default:
            
return left;
        }
    }
}

double zyk::expr(bool get)
{
    
double left = term(get);
    
while (true)
    {
        
switch (curr_tok)
        {
        
case zyk::PLUS:
            {
                left 
+= term(true);
                
break;
            }
        
case zyk::MINUS:
            {
                left 
-= term(true);
                
break;
            }
        
default:
            
return left;
            
        }
    }
}

double zyk::error(const string & s)
{
    
++no_of_errors;
    cerr
<<"error:"<<s<<' ';
    
return 1;
}

#endif

 

驱动程序(main函数)放在main.cpp中: 

/*
驱动程序部分
文件名:main.cpp
*/
#include 
<iostream>
#include 
<sstream>    //istringstream
#include <conio.h>
#include 
"Caculate.h"
using namespace std;
using namespace zyk;


//采用命令行参数的方式,向程序传递表达式 
//最多只能传两个参数,一个是程序文件名,
//一个是表达式。多个表达式,可以这样作为一个
//参数传递,如:"5+2*3; 2*pi-4"

int main(int argc,char * argv[])
{
    
    
string t;
    
switch(argc)
    {
    
case 1:    //只有一个参数,即程序文件名
        zyk::input = & cin;
        
break;
    
case 2:
        zyk::input 
= new istringstream(argv[1]);//把argv[1]传到输入流*input上。
        break;
    
default:
        error(
"too many arguments");
        
return 1;
    }
    
    
    table[
"pi"= 3.1415926535;
    table[
"e"= 2.71828182845;

    
while (*input)
    {
        get_token();
        
if (curr_tok == END) break;
        
if (curr_tok == PRINT) continue;
        cout
<<expr(false)<<endl;
    }

    
if (input != &cin) delete input;
    
return no_of_errors;
}
原创粉丝点击