从一个小巧的计算器,体会递归下降的方法
来源:互联网 发布: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
用分模块的思想,实现一个计算器
此程序源自于《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;
}
驱动程序部分
文件名: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;
}
- 从一个小巧的计算器,体会递归下降的方法
- 用switch编写的小巧计算器
- 一个小巧的Python Shell
- 一个小巧的MySQL Shell
- 一个小巧的PDF阅读器
- 递归下降的语法分析
- 递归下降的语法分析
- 递归下降方法和LL(1)实现计算器C++
- 使用递归下降算法分析数学表达式 -- 基于堆栈的计算器实现算法
- 一个递归下降的数字表达式解析器
- 递归实现的计算器
- 简单的递归下降分析
- 发现一个小巧的建模工具Jude
- PHP一个小巧的缓存类
- 一个小巧的反汇编引擎
- 一个小巧简单的c++代码库
- 递归的又一次深刻体会
- 如何编写从M中选N的组合数程序,一个不用递归方法设计,一个用递归方法设计
- 忠告
- 水龙吟·春恨
- C语言的转义字符(11)
- 开发人员应该看得电影
- 快哉《君子》万里风
- 从一个小巧的计算器,体会递归下降的方法
- php学习笔记7-(数组操作)
- C语言的库文件查找次序就是include问题(12)
- Solaris 10 ADSL 拨号上网
- MS Visio Professional 生成sql
- 欢迎使用WINX!
- 关于.net程序员必读的几本好书
- 软件项目管理框架
- 家妹完婚,心感欣慰,也不舍。