flex和bison

360 阅读3分钟

1.Flex

image.png

2.Bison

image.png

3.flex代码分析

%{
    /*
     *  一个简单计算器的Lex词法文件
     */
    int yywrap();
    #define YYSTYPE double
    void yyerror(char*);
    #include "calc.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;
}

词法分析器flex的作用是处理输入并将记号传递给语法分析器bison

image.png

4.bison代码分析

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

%{
#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(); /*分析器函数,调用了yyparse(),bison是会去寻找yylex()的实现*/
    return 0;
}

其他分析

image.png

  • line:一行
  • expr:表达式
  • term:词语
  • factor:因子
// 该产生式的意思是,输入是一个表达式后跟一个换行字符,语义动作是打印该表达式的值并换行
line : expr '\n' {printf("%d\n", $1);}
  1. 声明部分:声明部分由可任选的两节组成。第一节处于分界符%{和%}之间,它是一些不同的C语言声明。第二部分和第三部分的翻译规则或过程中所使用的所有临时声明都放在这里。第二节是文法记号的声明,这一节声明的记号可用于bison程序的第二部分和第三部分
  2. 翻译规则部分:这部分位于第一个%%后面,用于放置翻译规则,每条规则由一个文法产生式和有关的语义动作组成。
<left side> : <alt 1> {语义动作1}
            | <alt 2> {语义动作2}
           ...
            ;

在产生式中,单引号括起来的字符'c'是由终结符号c组成的记号;没有引号的字母数字串若没有声明为记号,则是非终结符。语义动作是C语言序列。在语义动作中,符号$$表示左部非终结符的属性值,而$i表示右部第i个文法符号(终结符或非终结符)的值。每当归约一个产生式时,就执行与之相关联的语义动作。

  1. 支持例程部分。第三部分是一些用C语言编写的支持例程。必须提供名字为yylex()的词法分析器。其他的过程,如错误恢复例程,如果需要的话,也可以加上。yylex()返回二元组(记号,属性值)。返回的记号必须在第一部分声明。属性值必须通过定义的变量yylval传给语法分析器。
  • %left '+' '-'

    • 在声明部分可以为终结符指定优先级和结合规则,例如上式表示+和-具有同样的优先级和左结合规则
    • 记号的优先级按它们在声明部分出现的次序来确定,先出现的记号的优先级低
  • yylval

    • 当前终结符语义值的全局变量,词法分析器返回记号给语法分析器时,记号值正式保存在变量yylval中
  • YYSTYPE

    • 语义值数据类型
  • bison将终结符分为两类:符号(Symbol)和记号(Token)。Token需要声明,符号在产生式中直接用''括起来

  • 关于YYSTYPE,bison里的YYSTYPE默认是int类型的,可以用%union将YYSTYPE定义为联合体。bison生成代码时,将会在name.tab.c文件中定义YYSTYPE的yylval变量

  • 使用%type声明非终结符的类型。每个type的名字必须是用%union定义过。而每个name就是非终结符的名字

  • $在语义动作中表示引入一个值引用,例如,$3代表规则右部第三个符号的值。

  • @在语义动作中表示引入一个位置引用,例如,@2代表规则右部第二个符号的位置。

  • YY_USER_ACTION:提供了一种始终在匹配 action 之前执行的操作。

  • YY_USER_INIT:供了在第一次扫描之前或者内部初始化之前,执行的操作。比如说打开日志文件

  • error:作为保留标识用于错误处理

5.代码运行

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

6.起始状态

还有一个强大的flex特性-起始状态,它允许我们指定在特定时刻哪些模式可以被用来匹配。在靠近文件顶端的%x行把IFILE定义为起始状态,它将在我们寻找#include雨中的文件名时被使用。在任何时候,这个词法分析器都处在一个起始状态中,并且只匹配这个状态所激活的那些模式。实际上,起始状态定义了一个不同的词法分析器,拥有它自己的规则。

可以定义任意多的起始状态,在这个程序中,除了flex本身会定义的INITIAL状态之外,我们只需要再额外定义一个状态。当一个模式紧随在尖括号起的起始状态名字之后,表示这个模式只在该状态中被激活。%x把IFILE标记为一个独占的起始状态,这意味着当该状态被激活时,只有这个状态中的模式才可以进行匹配。(%s可以用来声明包含的起始状态,它允许未标记为任何状态的模式也可以进行匹配。)在动作代码中,宏BEGIN用来切换到另外一个起始状态。