[编译原理]lex和bison实现逻辑表达式求值以及短路计算次数统计

目录

内容:逻辑表达式的求值工具

具体要求

解决方案

设计文法

设计语法制导定义

编写词法分析器

编辑语法和语义分析:

实验难点

完整代码

实验结果


内容:逻辑表达式的求值工具

输入一个包含数值的逻辑表达式,输出计算其真值,和因短路操作而跳过的数值比较的次数。输出格式为Output: [true or false], [次数]。注意,逻辑表达式的计算符合短路算法。其中,!计入逻辑运算次数,而 == 或 != 只用于非布尔型的数值比较。例如: 

  • 输入:3 == 2
    输出:Output: false, 0 
  • 输入:3 > 2
    输出:Output: true, 0 
  • 输入:3 > 2 && 10 > 1
    输出:Output: true, 0 
  • 输入:3 > 2 && 10 > 12
    输出:Output: false, 0 
  • 输入:3 < 2 && 10 > 12
    输出:Output: false, 1 
  • 输入:3 < 2 && 10 < 12
    输出:Output: false, 1 
  • 输入:( 3 <= 2 || 13 <= 19 ) && 1 < 2 
    输出:Output: true, 0 
  • 输入:( 3 >= 2 || 13 <= 19 ) && 1 < 2 
    输出:Output: true, 1 
  • 输入:( 3 >= 2 || 13 <= 19 ) || 1 < 2 
    输出:Output: true, 2 
  • 输入:! ( 3 > 2 )
    输出:Output: false, 0 
  • 输入:! ( 3 > 2 || ( 13 < 19 || 1 < 2 ) )
    输出:Output: false, 2 

具体要求

(1) 为了解决上面问题,需要设计一个文法描述该问题的逻辑表达式,并设计语法制导定义求解最后的输出值。

(2) 根据(1)的文法,设计基于flex的词法分析器。具体形式为1个flex文件的源代码。

(3) 在(2)的基础上,设计基于bison的语法(同时包含语义)分析器。具体形式为1个bison文件的源代码。

(4) 在文档中,描述(1)的内容,以及(2)(3)的设计方案(不需要粘贴很多源代码到文档中)。文档中可以包括问题的难点、求解思路等,但不要包含显而易见的步骤。文档以清楚描述内容为主,不易过长,字数和篇幅不是采分点。

(5) 已知输入的表达式中不包含中文字符。文法应该能够兼容下面字符:数值比较<、<=、>、>=、==、!=,布尔比较&&、||、!,以及括号(、)。注意:== 或 != 只用于非布尔型的数值比较

(6) 词法分析和语法分析应具有基本的报错能力,例如对于不符合的语法,如输入了字母或者小数,输出”Error.”。

解决方案

  • 设计文法

(1)S -> R
(2)R -> R or R
(3)R -> R and R

(4)R -> ! R
(5)R -> ( R )
(6)R -> value < value

(7)R -> value <= value
(8)R -> value > value

(9)R -> value >= value

(10)R -> value == value

(11)R -> value !=value

  • 设计语法制导定义

该程序输出为逻辑表达式的值以及因短路机制而跳过的运算步骤,因此,为非终结符定义综合属性val、total和cut。value值为其对应的真假值,total为其包含的总运算次数,cut为其已跳过的运算次数。

产生式

语义规则

S -> R

If(R.val) print(“Output: true,”+ R.cut);

Else print(“Output:false,”+ R.cut)

R -> R1 or R2

R.val = R1.val || R2.val; R.total = R1.total + R2.total;

If(R1.val) R.cut = R1.cut + R2.total;

Else R.cut = R1.cut + R2.cut;

R -> R1 and R2

R.val = R1.val && R2.val; R.total = R1.total + R2.total;

If(R1.val) R.cut = R1.cut + R2.cut;

Else R.cut = R1.cut + R2.total;

R -> ! R1

R.val = !R1.val; R.total = R1.total + 1; R.cut = R1.cut;

R - > ( R )

R.val = R1.val; R.total = R1.total; R.cut = R1.cut;

R -> value < value

R.val = value < value; R.total = 1; R.cut = 0;

R -> value <= value

R.val = value <= value; R.total = 1; R.cut = 0;

R -> value > value

R.val = value > value; R.total = 1; R.cut = 0;

R -> value >= value

R.val = value >= value; R.total = 1; R.cut = 0;

R -> value == value

R.val = value == value; R.total = 1; R.cut = 0;

R -> value != value

R.val = value != value; R.total = 1; R.cut = 0;

  • 编写词法分析器

定义双符号非终结符:

NLT >=

NGT <=

EQL ==

NEQ !=

AND &&

OR \|\|
{NLT} {

yylval = *yytext;

return nlt;

}/* 此处以NLT为例 */

定义数字digit非终结符,需要做数值转换,将字符串转换为整型,用到atoi函数:

digit [0-9]

%%

{digit}+ {

yylval = atoi(yytext);

return VALUE; 

}

定义单符号终结符:

[()=!><\n] {return *yytext;}

定义空白:

{whitespace}	{ ; }


{newline}	{ return 0; }

.	{
	printf("Error! Wrong token '%s'.\n", yytext);
	exit(0);
}
  • 编译语法和语义分析器

设计非终结符结构,包含val、total和cut综合属性:

typedef struct

{

     int val;

     int total;

     int cut;

}myStruct;

定义token以及左右结合规则;其中除! 为右结合外,其余比较符号皆为左结合:

%token VALUE

%token nlt ngt eql neq and or 

%right   '!'

%left   '<'  '>' 

%left   nlt ngt eql neq and or

 

编辑语法和语义分析:

S: R {{if($1.val)printf("Output: true,%d",$1.cut);else printf("Output: false,%d",$1.cut); return 0;}}

;

R: 

  |R or R    {$$.val = $1.val || $3.val;

$$.total = $1.total + $3.total;

if($1.val) $$.cut = $1.cut + $3.total;

else $$.cut = $1.cut + $3.cut;}

  |R and R    {$$.val = $1.val && $3.val;

$$.total = $1.total + $3.total;

if($1.val) $$.cut = $1.cut + $3.cut;

else $$.cut = $1.cut + $3.total;}

  |'!' R         {$$.val =  ! $2.val; $$.total = $2.total + 1; $$.cut = $2.cut;}

  |'('R')'       {$$.val = $2.val; $$.total = $2.total; $$.cut = $2.cut;}

  |VALUE '<' VALUE   {$$.val = ($1.val < $3.val); $$.total = 1; $$.cut = 0;}

  |VALUE ngt VALUE   {$$.val = ($1.val <= $3.val); $$.total = 1; $$.cut = 0;}

  |VALUE '>' VALUE   {$$.val = ($1.val > $3.val); $$.total = 1; $$.cut = 0;}

  |VALUE nlt VALUE   {$$.val = ($1.val >= $3.val); $$.total = 1; $$.cut = 0;}

  |VALUE eql VALUE   {$$.val = ($1.val == $3.val); $$.total = 1; $$.cut = 0;}

  |VALUE neq VALUE   {$$.val = ($1.val != $3.val); $$.total = 1; $$.cut = 0;}

  • 实验难点

1、本实验在macOS系统上进行,与windows不同,在安装flex、bison和gcc后,可编写.l和.y文件,编译过程如下:

bison -d mytool.y

flex -omytool.yy.c mytool.l

gcc mytool.tab.c mytool.yy.c

./a.out

2、由于非终结符有多个综合属性,因此需要定义struct添加多个属性。

3、语义设计时需要注意优先级,依次为or and ! () < <= > >= == !=

4、在词法定义中,需要给双符号比较符定义别名,才可在之后设计词法;digit非终结符需要使用atoi方法将输入的字符串转换为int格式,否则在后续的比较中无法比较两位以上的数字。

  • 完整代码

mytool.l

%{
	#include <stdio.h>
	#include "mytool.tab.h"
%}

NLT >=
NGT <=
EQL ==
NEQ !=
AND &&
OR \|\|

whitespace [ \t\f\v]
newline [\r\n]

digit	[0-9]
%%

{whitespace}	{ ; }


{newline}	{ return 0; }

{digit}+ {
yylval = atoi(yytext);
return VALUE; 
}



[()=!><\n]	{return *yytext;}


{NLT}	{
yylval = *yytext;
return nlt;
}
{NGT}	{
yylval = *yytext;
return ngt;
}
{EQL}	{
yylval = *yytext;
return eql;
}
{NEQ}	{
yylval = *yytext;
return neq;
}
{AND}	{
yylval = *yytext;
return and;
}
{OR}	{
yylval = *yytext;
return or;
}

.	{
	printf("Error! Wrong token '%s'.\n", yytext);
	exit(0);
}

%%
int yywrap(void)
{
	return 1;
}

mytool.y

%{
	#include <stdio.h>

	#ifndef BTOD_H_INCLUDED
	#define BTOD_H_INCLUDED
	typedef struct
	{
   	 int val;
   	 int total;
   	 int cut;
	}myStruct;


	#endif

	#define YYSTYPE myStruct
	int yylex(void);  
	void yyerror(char const *);      
%}
%token VALUE
%token nlt ngt eql neq and or 
%right   '!'
%left   '<'  '>' 
%left   nlt ngt eql neq and or 

%%
S: R {{if($1.val)printf("Output: true,%d",$1.cut);else printf("Output: false,%d",$1.cut); return 0;}}
;
R: 
  |R or R    {$$.val = $1.val || $3.val;
		$$.total = $1.total + $3.total;
		if($1.val) $$.cut = $1.cut + $3.total;
		else $$.cut = $1.cut + $3.cut;}
  |R and R    {$$.val = $1.val && $3.val;
		$$.total = $1.total + $3.total;
		if($1.val) $$.cut = $1.cut + $3.cut;
		else $$.cut = $1.cut + $3.total;}
  |'!' R         {$$.val =  ! $2.val; $$.total = $2.total + 1; $$.cut = $2.cut;}
  |'('R')'       {$$.val = $2.val; $$.total = $2.total; $$.cut = $2.cut;}
  |VALUE '<' VALUE   {$$.val = ($1.val < $3.val); $$.total = 1; $$.cut = 0;}
  |VALUE ngt VALUE   {$$.val = ($1.val <= $3.val); $$.total = 1; $$.cut = 0;}
  |VALUE '>' VALUE   {$$.val = ($1.val > $3.val); $$.total = 1; $$.cut = 0;}
  |VALUE nlt VALUE   {$$.val = ($1.val >= $3.val); $$.total = 1; $$.cut = 0;}
  |VALUE eql VALUE   {$$.val = ($1.val == $3.val); $$.total = 1; $$.cut = 0;}
  |VALUE neq VALUE   {$$.val = ($1.val != $3.val); $$.total = 1; $$.cut = 0;}
 
;
%%
int main()
{
	yyparse();
	return 0;
}
void yyerror(char const *msg)  
{  
    fprintf(stderr, "%s\n",msg);  
}  
  • 实验结果

        (太尴尬了,把experiment拼错了) 以下是要求给的数据:

(base) joseph@MacBook-Pro exepriment % ./a.out          
3>5&&1<2
Output: false,1%                                                                (base) joseph@MacBook-Pro exepriment % ./a.out 
3 == 2      
  Output: false,0%                                                              (base) joseph@eMacBook-Pro exepriment % ./a.out
3 > 2
 Output: true,0%                                                                (base) joseph@MacBook-Pro exepriment % ./a.out
3 > 2 && 10 > 1
      Output: true,0%                                                           (base) joseph@MacBook-Pro exepriment % ./a.out
3 > 2 && 10 > 12
      Output: false,0%                                                          (base) joseph@MacBook-Pro exepriment % ./a.out
3 > 2 && 10 > 12
      Output: false,0%                                                          (base) joseph@MacBook-Pro exepriment % ./a.out
3 < 2 && 10 > 12
      Output: false,1%                                                          (base) joseph@MacBook-Pro exepriment % ./a.out
3 < 2 && 10 < 12
      Output: false,1%                                                          (base) joseph@MacBook-Pro exepriment % ./a.out
( 3 <= 2 || 13 <= 19 ) && 1 < 2 
             Output: true,0%                                                    (base) joseph@MacBook-Pro exepriment % ./a.out
( 3 >= 2 || 13 <= 19 ) && 1 < 2
            Output: true,1%                                                     (base) joseph@MacBook-Pro exepriment % ./a.out
( 3 >= 2 || 13 <= 19 ) || 1 < 2
            Output: true,2%                                                     (base) joseph@MacBook-Pro exepriment % ./a.out
! ( 3 > 2 )
     Output: false,0%                                                           (base) joseph@MacBook-Pro exepriment % ./a.out
! ( 3 > 2 || ( 13 < 19 || 1 < 2 ) )
               Output: false,2%                 

以下是扩展的数据和错误输入:

(base) joseph@MacBook-Pro exepriment % ./a.out 
a > b
Error! Wrong token 'a'.
(base) joseph@MacBook-Pro exepriment % ./a.out
3.4 > 4.4
Error! Wrong token '.'.
(base) joseph@MacBook-Pro exepriment % ./a.out
3 > 4 , 2>3
Error! Wrong token ','.
(base) joseph@MacBook-Pro exepriment % ./a.out
1 == 1 == 1
Output: true,0%                                                                 (base) joseph@MacBook-Pro exepriment % ./a.out
! ( 3 > 2 || ! ( 13 < 19 || 1 < 2 ) )
Output: false,3%                                                                (base) joseph@MacBook-Pro exepriment % ./a.out
! ( 3 > 2 && 4 < 5 ) && !( 3 > 1 || ! ( 4 < 1 && 2 < 1))
Output: true,3%                                                                 (base) joseph@MacBook-Pro exepriment % ./a.out
  (! ( 3 > 2 && 4 < 5 ) )&& !( 3 > 1 || ! ( 4 < 1 && 2 < 1))
Output: false,5%   

实验结果与预期相符。

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
lexbison是两个在编译原理中使用的工具,可以帮助开发者构建编译器或解析器。通过结合使用lexbison,我们可以制作一个简单的计算器。 首先,我们需要使用lex来定义我们的词法规则。词法规则会对输入的字符序列进行分类,并且生成对应的标记。比如,我们可以定义一些基本的词法规则,如加号、减号、数字等等。 接下来,我们使用bison来定义我们的语法规则。语法规则会描述输入字符序列如何组成合法的表达式。比如,我们可以定义加法表达式等等。 在我们的语法规则中,可以使用词法的标记进行组合,并且指定操作的优先级和结合性等等。然后,bison将会自动生成解析器代码。 在解析器代码中,我们可以编写相应的语义动作。这样,当解析器通过输入的字符序列来构建语法树时,我们可以在特定的语法规则或标记上执行一些操作,如计算出表达式的结果。 最后,我们可以使用编译器或解释器来编译或解释这些生成的代码。当我们输入一个表达式时,词法分析器会将其转化为字符序列,并且传递给语法分析器。语法分析器根据我们的语法规则来构建语法树,并且执行相应的动作。最终,我们可以得到表达式的计算结果。 通过使用lexbison,我们能够轻松地实现一个简单的计算器,让用户可以输入表达式,并且得到相应的计算结果。当然,对于更复杂的计算器,我们可以进一步扩展词法和语法规则,以支持更多的操作符和函数等等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值