编译原理_计算器_flex、bison实现(详细辅助理解)

2023-11-19

编译原理_计算器_flex、bison实现(详细辅助理解)

个人博客 https://www.yuque.com/_ngp/blog/tuanh6

目标:参考范例程序, 用 Flex 和 Bison 实现一个功能更为强大的计算器,包含以下运算:
a) 加、减、乘、除运算
b) 乘方、开方运算
c) 位运算
– 与 & 、或 |、非 ~…
d) 阶乘运算 !
e)sin cos tan

sin(SIN*pi/180.0)//把角度变成弧度即把180度变成π

如果要写实验报告的话,请先看报告书的要求,一边截图一边编程,免得写报告的时候,浪费时间。

P.S. 这篇文章只能助你理解并修改程序,博主也只理解了部分, 而且很久没用了, 其他的都忘了. 附赠安装配置环境的教程(毕竟很多人卡在第一步嘛( ̄▽ ̄)/)

一 环境配置:Windows/Ubuntu+flex、bison

\1 使用Windows+CodeBlocks+flex、bison的环境(有两种方式)

方式1 借助codeblcoks编译、运行。

flex_bison 下载 百度云密码:usk6
flex_bison 备用下载链接
1)下载百度云里的flex和bison。放到windows环境下。
2)把.l文件和.y文件复制到该文件下
3)在
2)文件夹的地址栏(也就是下图画红圈的地方),输入cmd
, 然后按 回车
地址栏cmd
4)在 cmd弹出的控制台 里输入

flex -ocalc.c calc.l
bison -ocalc.tab.h calc.y  //注意-o后面没有空格

这样,会生成两个文件,calc.tab.h 和 calc.c
然后,把生成的.c文件(calc.c),丢进 codeblocks里,编译,运行。


方式2 配置MinGW直接在cmd下编译、运行。

flex_bison 下载 百度云密码:usk6
flex_bison 备用下载链接
1)下载百度云里的flex和bison。放到windows环境下。
2)把.l文件和.y文件复制到该文件下
3)把%codeblocks%\MinGW\bin添加到 电脑\属性\高级系统设置\环境变量\PATH(即把codeblocks的编译器的路径放到环境变量PATH里)
4)在
2)文件夹的地址栏(也就是下图画红圈的地方),输入cmd
, 然后按 回车
地址栏cmd

flex  calc.l
bison -o calc.tab.h calc.y  //注意-o后面有没有空格都可以 ==
gcc -o aa lex.yy.c calc.tab.h  //编译
aa		//运行aa.exe

这种方式,会生成两个文件,calc.tab.h 、calc.tab.c 和 calc.c
这样,就直接在 在 cmd弹出的控制台 界面,运行程序,而不要通过codeblocks。

\2 使用Ubuntu+flex、bison的环境,来编译、运行。

vm12+ubuntukylin16.04 虚拟机安装ヾ(o◕∀◕)ノヾ (❁´︶`❁)
然后,在ubuntu安装flex、bison并完成编译
老版本的ubuntu可能这样安装不了,这种情况,我只能说。。。。。重装一下ubuntu?(逃

ubuntu下打开终端,安装flex、bison:

sudo apt-get install flex bison		//安装flex和bison
flex -h
bison -h		//如果有提示信息表示安装成功

编译和运行:

cd ........./calcSimple		//移动到程序的当前目录
bison -d calc.y
flex calc.l
/*
-lm在提示pow未定义引用时添加。
编译lex.yy.c calc.tab.c 用-o输出到calc
*/
gcc -o calc lex.yy.c calc.tab.c  -lm 
./calc		//运行calc

如果有 正确的 Makefile文件 的话,直接输入:

sudo make
./calc

这里写图片描述

二 一个简单的示例代码calcSimple下载

链接:http://pan.baidu.com/s/1slc9aPn 密码:uyyi

calc.l

%option noyywrap

%{
    /*
     *  一个简单计算器的Lex词法文件
     */
	void yyerror(char*);
	#include "calc.tab.h"
%}

%%

     /* a-z为变量 */   
[a-z]	{
            yylval = *yytext - 'a';
            return VARIABLE;
    	}

    /* 整数 */
[0-9]+	{
            yylval = atoi(yytext);
            return INTEGER;
    	}

    /* 运算符 */
[-+()=/*\n]	{return *yytext;}

    /* 空白被忽略 */
[ \t]    ;

    /* 其他字符都是非法的 */
.    yyerror("无效的输入字符");

%%

##calc.y

%token    INTEGER VARIABLE
%left    '+' '-'
%left    '*' '/'

%{
	
/*for Visual studio */
/*	#define  __STDC__   0   */   

	#include <stdio.h>   
    void yyerror(char*);
    int yylex(void);
	
    int sym[26];
%}

%%
program:
    program statement '\n'
    |
    ;
statement:
     expr    {printf("%d\n", $1);}
     |VARIABLE '=' expr    {sym[$1] = $3;}
     ;
expr:
    INTEGER
    |VARIABLE{$$ = sym[$1];}
    |expr '+' expr    {$$ = $1 + $3;}
    |expr '-' expr    {$$ = $1 - $3;}
    |expr '*' expr    {$$ = $1 * $3;}
    |expr '/' expr    {$$ = $1 / $3;}
    |'('expr')'    {$$ = $2;}
    ;
	
%%

void yyerror(char* s)
{
    fprintf(stderr, "%s\n", s);
}

int main(void)
{
    printf("A simple calculator.\n");
    yyparse();
    return 0;
}


三 把 calcSimple 修改成 完整版的计算器 全攻略

此小节简略说明一下.l文件和.y文件,如果想更多的了解这个程序的意义,请看下文
在calcSimple的基础上,

在.l文件里添加:(注意中/英文的标点符号不一样)
/* 运算符 /
[-+()=/
!\n] {return *yytext;}

在.y文件里添加文法部分:
|expr ‘!’ {int i,s=1;for(int i=1;i<=$2;i++)s*=i;$$=s;}
这里用到了c语言,所以要在.y程序第二部分即%{}%里面添加#include<stdio.h>

然后在.y文件开头添加 %right ‘!’
这里表示左/右结合性,以及运算符优先级,越是在下面优先级越高

仿照以下两个程序,把运算符都添加进去,就完成了基本完整的计算器。
http://blog.csdn.net/xiaofeige567/article/details/28301877
http://blog.csdn.net/ly_624/article/details/51125482

四 非常重要的 两个学长学姐的 示例程序。

两个学长学姐,写的很清晰易懂,但不能直接使用,不清楚为什么,仅供参考。==
两个代码差别挺大的。
http://blog.csdn.net/xiaofeige567/article/details/28301877
http://blog.csdn.net/ly_624/article/details/51125482

五 理解 .l 文件和 .y 文件

\1 查阅龙书(编译原理)中文第二版(P86和P170 )

这里写图片描述
(lex和yacc是Unix的软件,而flex和bison是其在ubantu(linux下)的兼容版本)
P86 详细解释了flex(lex)软件的 代码。也就是calc.l文件的详细解释
P170 详细解释了bison(yacc)软件的 代码。也就是calc.y文件的详细解释

\2 老师课件 上的解释

链接: 老师课件
2-词法分析-RE-Lex.pptx
YACC.pptx
实验-补充-LEX.pdf


六 词法分析&语法分析

\1 词法分析

首先来看flex的使用:
简单来说分为两步:
1 先定义一个flex的输入文件,描述词法。
2 用flex程序处理这个文件,生成对应的C语言源代码文件。
(一般flex的输入文件以.l文件结尾, 比如这个文件calc.l)

文件分成三个部分

第一部分是从 %{ 到 }% 标记的部分。 
这个部分会原封不动的复制到flex的生成代码中。
文件开头定义了一个YYSTYPE宏。
每个TOKEN可以有一个lval值属性,
YYSTYPE定义类型就是token的lval的类型。
_EasyTData是我们的web服务层和web页面层公用的通用数据结构。
然后就是一些要include的头文件,第一部分就完了。

lex的输入文件的第二部分,是从 % } 到 % % 之间的部分,
这部分用正则表达式定义了一些数据类型。
 比如int num string ignore_char identifier等。 
注意这里使用的正则表达式的形式是ERE而不是BRE。 
ERE与BRE比较明显的区别就是,
ERE使用+表示字符重复一次以上,*表示字符重复0次以上。
BRE使用{1,}这种方式表示字符重a

文件的第三部分,是% % 到% % 的部分。
这里定义了词法分析器在解析的处理动作。
yytext是一个flex内部的标识符,表示匹配到的字符串。
上文介绍了,lval也是一个内部标识符,表示TOKEN的值。
json2tdata_是标识符的前缀, 在执行flex的时候,用-P指定。

flex输入文件写完之后,使用下面这条命令,
就可以把flex的输入文件转换为C语言的源代码了。
flex calc.l//生成lex.yy.c

\2 语法分析

语法分析是使用bison工具。
使用bison工具也是分为两步,
第一步写bison的输入文件,第二步用bison程序生成C语言源码。
(bison的输入文件一般用.y作为后缀名。)

和flex的词法分析输入文件类似,bison的输入文件也是分成3部分。

第一部分% {和% }之间,是原封不动拷贝到输出的C语言源文件中的。 
json2tdata_lex这个函数是flex生成的。 
json2tdata_error是用来处理错误信息的函数。
通过定义和实现这个函数你可以把错误信息写到任何地方。
与flex类似,json2tdata也是自定义的前缀。

第二部分是%token INT NUM STRING R_BRACKET COLON 
SEMICOLON COMMA IDENTIFIER TRUE FALSE NIL这一行,
这一行的作用就是声明在flex中定义的那些TOKEN。

第三部分是% % % %包围的部分。
这部分就是语法的推导过程。  
可以比较轻松的看出,这部分主要就是采用BNF对语法进行描述。
比如Array, 它有两种形式。
第一种是 L_BRACKET ELEMENTS R_BRACKET,
第二种则是L_BRACKET R_BRACKET, 
这表示一个空的Array。
Bison能够完全支持LR(1)文法。 
这种文法的特点是只要多向前看一个TOKEN,
就能够决定如何解析。
因此如果bison告诉你语法ambiguous的时候,
可以想一想如何把自己的文法改成LR(1)型文法。
另外,每一条规则的后面可以用{}来定义解析的动作
bison用$$表示规则左边的对象,
用$1 $2 $3 等依次表示规则右边的对象。

七 编译、运行的时候,常见错误以及对策

1) …shift/reduce conflict…

最常见的情况是:在.l 和.y文件中没有添加相应 符号,或者没有写优先级

2) 在原来的只能用整数的示例程序里添加 小数 的功能

  在 .l 和 .y 文件里添加 	#define YYSTYPE double

  在.l文件里 atoi(yytext)改为 atof(yytext)

//一般会有错误提示,按照错误提示一个个改就好了。
  为所有用到整数型的地方,添加强制类型转换 (int) 

3) pow的未定义引用

两种可能
	  .y文件里没有添加math.h头文件
	  gcc -o calc lex.yy.c calc.tab.c -lm //没有添加-lm

转自:
http://blog.csdn.net/li740207611/article/details/51072111

原因:Linux下用math.h库的pow()函数,
gcc编译的时候报错返回:对‘pow’未定义的引用 
查了下资料,需要在gcc编译的时候加上-lm参数才能正常编译。 
这是为什么呢?再查了下资料: 
使用math.h中声明的库函数还有一点特殊之处,
gcc命令行必须加-lm选项,因为数学函数位于libm.so库文件中
(这些库文件通常位于/lib目录下),-lm选项告诉编译器,
我们程序中用到的数学函数要到这个库文件里找。
本书用到的大部分库函数(例如printf)位于libc.so库文件中,
使用libc.so中的库函数在编译时不需要加-lc选项,
当然加了也不算错,因为这个选项是gcc的默认选项。

以上,如有疏漏,敬请指正。

八 源代码

a.l 文件

%{
    /*
     *  一个简单计算器的Lex词法文件
     */
	int yywrap();
    #define YYSTYPE double
	void yyerror(char*);
	#include "a.tab.h"
%}

%%

     /* a-z为变量 */   
[a-z]	{
            yylval = *yytext - 'a';
            return VARIABLE;
    	}
    /*16进制数*/
0x\.?[a-f0-9]+|0x[a-f0-9]+\.[a-f0-9]* {
	        yylval=atof(yytext);
		    return HEXADECIMAL;
        }

    /* 整数或者小数 */
\.?[0-9]+|[0-9]+\.[0-9]*	{
            yylval = atof(yytext);
            return INTEGER;
    	}

    /* 运算符 */
[-+()=/*&|~!^@\n]	{return *yytext;}

    /* 三角函数 */
sin {
	return SIN;
    }

cos {
	return COS;
    }
tan {
	return TAN;
    }
    /* 空白被忽略 */
[ \t]    ;

    /* 其他字符都是非法的 */
.    yyerror("无效的输入字符");

%%
int yywrap()
{return 1;}

a.y 文件

%token   HEXADECIMAL INTEGER VARIABLE SIN COS TAN
%left    '+' '-'
%left    '*' '/'
%left    '&'
%left    '|'
%left    '^'
%right   '@''~'
%left    '!'

%{
	
/*for Visual studio */
/*	#define  __STDC__   0   */   

	#include <stdio.h>   
    #include <math.h>
    #define YYSTYPE double
    #define pi 3.1415926 
	void yyerror(char*);
    int yylex(void);
	
    double sym[26];
%}

%%
program:
    program statement '\n'
    |
    ;
statement:
     expr    {printf("%lf\n", $1);}
     |VARIABLE '=' expr    {sym[(int)$1] = $3;}
     ;
expr:
    INTEGER
	|HEXADECIMAL
    |VARIABLE{$$ = sym[(int)$1];}
    |expr '+' expr    {$$ = $1 + $3;}
    |expr '-' expr    {$$ = $1 - $3;}
    |expr '*' expr    {$$ = $1 * $3;}
    |expr '/' expr    {$$ = $1 / $3;}
    |expr '&' expr    {$$ = (int)$1 & (int)$3;}
    |expr '|' expr    {$$ = (int)$1 | (int)$3;}
    |'~' expr         {$$ = ~(int)$2;}
    |'@' expr         {$$ = sqrt($2);}
    |expr '@' expr    {$$ = $1*sqrt($3);}
    |expr '!'         {int i=1,s=1;for(;i<=$2;i++)s*=i;$$=s;}
    |expr '^' expr    {$$=pow($1,$3);}
    |'('expr')'       {$$ = $2;}
    |SIN'('expr')'       {$$ = sin($3*pi/180.0);}
    |COS'('expr')'       {$$ = cos($3*pi/180.0);}
    |TAN'('expr')'       {$$ = tan($3*pi/180.0);}
    ;
	
%%

void yyerror(char* s)
{
    fprintf(stderr, "%s\n", s);
}

int main(void)
{
    printf("A simple calculator.\n可以用的运算符:+-*/&|~!^@ \n要注意的是三角函数使用时要加括号。 例:sin(60)\n");
    yyparse();
    return 0;
}

Makefile 文件(只在linux下可用,注意文件名得是Makefile,大小写敏感)

  • Makefile 文件最基础教程:
    https://blog.csdn.net/qq_35208390/article/details/78488099
all: prog clean

prog:	1a.l 1a.y
	flex 1a.l
	bison -d 1a.y
	gcc -o a lex.yy.c 1a.tab.c -lm

clean:	
	rm lex.yy.c 1a.tab.c 1a.tab.h

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

编译原理_计算器_flex、bison实现(详细辅助理解) 的相关文章

  • 编译原理LL(1)文法之提取左公因子,消除左递归

    在判断LL 1 文法是否符合的时候 需要判断LL 1 文法是否存在左公因子 和左递归的情况 以下给出相应的判断方法以及通过提取左公因子和消除左递归使非LL 1 文法转换为LL 1 法的方法 第一种情况 存在左公因子 解决方法 提取左公因子
  • 深入理解react native布局(一)居中

    刚刚做完了一个项目 基本上把react native各种布局方式都用上了 发现了很多坑 也学会和很多 这里给大家分享一下哈 首先我们要有个概念 react native里面是兼容大部分我们在css里面用到的布局方式 此外接触过css里面fl
  • Compiler- volatile关键字

    为了直观的感受编译器为程序所做的编译优化 我们通过以下的C 程序来进行演示 只能体现编译优化的一小部分hh 请大家预测一下下面代码的输出结果 include
  • 小米造车?年轻人的第一辆电动车?

    素来有着价格屠夫称号的 小米 终于要对电动车出手了 事件简讯 昨天下午 据 晚点LatePost 爆料 小米 已确定造车 并视其为战略级决策 不过具体形式和路径还未确定 或许仍有变数 一位知情人士称 小米造车或将由小米集团创始人雷军亲自带队
  • c# - 作业1:计算器

    c 作业1 计算器 资源下载 总体视图 废话集结区 代码 全 Form1 cs Program cs 现存bug 我觉得我也要搞一个有条理的东西 资源下载 c 作业1 计算器 点击下载 总体视图 废话集结区 第一个c 成品 当时可激动了 大
  • 如何使用缩进作为 bison 和 flex 的块分隔符

    我很想知道如何在 bison flex 中实现缩进作为块分隔符 就像在 python 中一样 我正在编写自己的编程语言 主要是为了好玩 但我打算将其与游戏引擎一起使用 我将尝试想出一些特殊的东西来最小化样板文件并最大化开发速度 我已经写了一
  • 为什么这个野牛代码会产生意外的输出?

    弹性代码 1 option noyywrap nodefault yylineno case insensitive 2 3 include stdio h 4 include tp tab h 5 6 7 8 return 9 retur
  • 如何在 yacc 中将 yylval 与字符串一起使用

    我想传递令牌的实际字符串 如果我有一个名为 ID 的令牌 那么我希望我的 yacc 文件真正知道 ID 的名称 我想我必须使用 yylval 将字符串从 Flex 文件传递 到 yacc 文件 我怎么做 通过 yylval 返回字符串或任何
  • 如何使用 Bison 实现“隐式乘法”规则?

    我正在为数学表达式解析器编写 Bison 文件 到目前为止 一切都很好 但我面临着隐式乘法的问题 你看 我想支持这样的表达2x sin 4x cos 4x 它应该像这样解析2 x sin 4 x cos 4 x 这里没什么太糟糕的 但请考虑
  • yylloc 在此范围内未定义

    我在编译文件时遇到以下问题 我已经重写了 YYLTYPE 的定义 如下所示 虽然它与默认值相同 但我将扩展它 typedef struct YYLTYPE int first line int first column int last l
  • Lex 正则表达式获得一些额外的字符

    我的 lex 文件中有以下定义 L a zA Z A a zA Z 0 9 L A yylval id yytext return IDENTIFIER 我在 YACC 文件中执行以下操作 primary expression IDENTI
  • 编写编译器……什么是对的,什么是错的? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • Flex/Lex 和 Yacc/Bison 有什么区别?

    Flex Lex 和 Yacc Bison 之间有什么区别 我在网上疯狂搜索 没有找到任何可靠的答案 我可以在 Ubuntu 上安装纯 Lex 和 Yacc 还是可以只安装 flex 和 bison 我很困惑 Lex 或 Yacc 是否仍然
  • 向 bison/jison 计算器语言添加函数

    我正在尝试扩展吉森计算器示例 http zaach github io jison try 具有一些简单的功能 我对解析和 bison jison 相当陌生 但这是我到目前为止所拥有的一些内容 lexical grammar lex var
  • 从flex+bison输出AST到main.cpp

    免责声明 虽然我已经完成了本教程 但我是一名 flex bison 菜鸟 http ds9a nl lex yacc cvs lex yacc howto html http ds9a nl lex yacc cvs lex yacc ho
  • LR(k) 到 LR(1) 语法转换

    我对以下内容感到困惑quote http en wikipedia org wiki LR parser Theory来自维基百科 换句话说 如果一种语言足够合理 允许 高效的单遍解析器 可以用 LR k 语法来描述 语法总是可以机械地转化
  • 构建 gcc 4.6 时遇到问题:对“yylex”的未定义引用

    我正在尝试构建 gcc 4 6 但我收到一些链接器错误 看起来这意味着 bison 或 flex 没有链接到 当 makefile 发出此命令时 gcc g fkeep inline functions DIN GCC W Wall Wwr
  • 如何解决2+2和2++2冲突

    在更大的程序中 我给出了以下内容 flex bison In flex pn dig 0 9 exp e E dig printf detected n return PLUS SIGN pn dig printf digit detect
  • 如何将 C++ flex 与 C++ Bison 连接起来?

    我正在尝试将 C flex 与 C bison 连接起来 但我被难住了 Bison 3 8 1 手册有一个带有 C Flex 的 C Bison 示例 Flex 2 6 4 没有示例 我试图解决的问题是如何向 C 或 C Bison 提供指
  • Yacc/Bison:伪变量($$、$1、$2、..)以及如何使用 printf 打印它们

    我有一个用 flex 编写的词法分析器 它将标记传递给用 bison 编写的解析器 以下是我的词法分析器的一小部分 ID a z a z0 9 rule printf A rule s n yytext return RULE ID pri

随机推荐