当前位置: 首页 > news >正文

【原创】Flex和Bison中巧用单双引号提升语法文件的可读性

 

使用Win Flex 和 Bison有一段时间了,期间搞了几个小型语言的编译器,也整理了C和C++的语法文件,在使用过程中我发现,如果按照传统的%token标记,将运算符,如“+”、“-”、“*”、“/”等搞成文字记号,比如:%token PLUS、%token MINUS,在声明语法时,就会出现像下面这样的定义:

simple_exp       : simple_exp PLUS simple_exp

                            | simple_exp MINUS simple_exp;

这样一来感觉可读性不是很高,尤其是在像C++这样的大型语言的语法文件中,如果所有符号都被这种文字记号定义代替,阅读、理解、修改都成了比较恐怖的噩梦,就连简单的“,”、“;”、“{}”等等记号都要被替换成文字,满篇的全字母语法定义,翻个页看着都头晕。此时另一个比较传统的做法是,对于单字符运算符不要定义记号,直接在yylex函数中返回字母即可,这需要在lex文件中如下定义:

"+"                               |

"-"                                |

"*"                               |

"/"                                |

"%"                              |

"^"                               {return yytext[0];}  //直接返回字母本身

然后在yacc(Bison)文件中如下定义语法:

.......

%left '+' '-'

%left '*' '/'

.......

exp            : exp '+' exp               {$$ = $1 + $3;}

                   |exp '-' exp                 {$$ = $1 - $3;}

                   |exp '*' exp                {$$ = $1 * $3;}

                   |exp '/' exp                {$$ = $1 / $3;}

                   ;

这样一来这段语法定义的含义一眼就看明白了,可读性比之前的全记号字母方式的定义要高很多,毫不夸张的说但凡写过程序的人基本都能看懂这个语法文件。同时这种写法,也避免了,开头一堆的%token声明,有效缩短了文件长度。但是遗憾的是,这种写法只能用来应付单字母符号的语法定义,对于像“++”、“--”、“>=”之类的多字母符号,就无能为力了。

正所谓“山穷水尽疑无路,柳暗花明又一村。”,其实在Bison中提供了定义记号时声明等价字符串的功能,这样我们就可以将所有的%token符号声明使用其等价字符串替代的方式,一方面保留标点符号串原型提高可读性的,另一方面又可以保留记号定义本身,方便在Flex中读取不同的几个符号返回同一个记号值,具体做法如下:

首先在yacc(Bison)语法文件的%token定义中这样定义:

%token ASSIGN        ":="

%token EQ                 "=="

%token LT                  "<"

%token LE                  "<="

%token GT                 ">"

%token GE                 ">="

%token NE                 "!="

%token PLUS             "+"

%token MINUS         "-"

%token TIMES          "*"

%token OVER            "/"

%token POW             "^"

%token MOD            "%"

%token LPAREN        "("

%token RPAREN       ")"

%token SEMI             ";"

这里需要注意的就是记号的等价字符串,必须使用双引号“”,这样我们看到所有的不论单字母还是双字母多字母的标点符号都可以明确的定义,声明本身也提高了可读性。具体声明语法时,就可以像下面这样使用这些记号:

......

stmt                   : compound_stmt                                                            { $$ = $1;}

                                     | if_stmt                                                                   { $$ = $1;}

                                     | repeat_stmt ";"                                                   { $$ = $1;}

                                     | assign_stmt ";"                                                   { $$ = $1;}

                                     | read_stmt       ";"                                                       { $$ = $1;}

                                     | write_stmt  ";"                                                  { $$ = $1;}

                                     | error                                                                                 { $$ = NULL;}

                                     ;

......

exp                     : simple_exp "<" simple_exp            { $$ = MakeExpNode($1,$3,LT);}

                            | simple_exp "==" simple_exp         { $$ = MakeExpNode($1,$3,EQ);}

                            | simple_exp ">" simple_exp           { $$ = MakeExpNode($1,$3,GT);}

                            | simple_exp "<=" simple_exp         { $$ = MakeExpNode($1,$3,LE);}

                            | simple_exp ">=" simple_exp         { $$ = MakeExpNode($1,$3,GE);}

                            | simple_exp "!=" simple_exp { $$ = MakeExpNode($1,$3,NE);}

                            | simple_exp                                         { $$ = $1;}

                            ;

.......

simple_exp       : simple_exp "+" simple_exp            { $$ = MakeExpNode($1,$3,PLUS);}

                            | simple_exp "-" simple_exp            { $$ = MakeExpNode($1,$3,MINUS);}

                            | simple_exp "*" simple_exp           { $$ = MakeExpNode($1,$3,TIMES);}

                            | simple_exp "/" simple_exp            { $$ = MakeExpNode($1,$3,OVER);}

                            | simple_exp "%" simple_exp          { $$ = MakeExpNode($1,$3,MOD);}

                            | simple_exp "^" simple_exp           { $$ = MakeExpNode($1,$3,POW);}

                            |  "(" simple_exp ")"                         { $$ = $2; }                          

                            | NUM                                                    { $$ = MakeConstNode( atoi(yytext) );}

                            | ID                                                          { $$ = MakeIDNode(yytext);}

                            | error                                                     { $$ = NULL;}

                            ;

......

这样一来,整个语法文件看上去就一目了然,符号、语义含义都比较清晰了。当然需要重点注意的是,在使用时一样使用的是双引号,其实这里也很好理解,如果你熟悉C语言的话,就知道,在C语言中,单引号只能用来声明单字符常量,而双引号则用来声明字符串,同样在与C语言有着千丝万缕联系的yacc(Bison)文件中也有类似的单双引号用法上的区别,这里明显使用的是字符串。在修改调优较大型的语言如C++这样量级的语法文件时,这样的写法,会大大提高可读性,而可读性是理解和修改整个语法文件的基础。举例来说,我在C++语法文件中像下面这样定义(注意其中混用了单双引号):

postfix_expression

         : primary_expression

    | postfix_expression '[' expression ']'

    | postfix_expression '(' expression_listopt ')'

    | simple_type_specifier '(' expression_listopt ')'

    | postfix_expression '.' templateopt domainopt id_expression

    | postfix_expression "->" templateopt domainopt id_expression

    | postfix_expression '.' pseudo_destructor_name

    | postfix_expression "->" pseudo_destructor_name

    | postfix_expression "++"

    | postfix_expression "--"

    | DYNAMIC_CAST '<' type_id '>' '(' expression ')'

    | STATIC_CAST '<' type_id '>' '(' expression ')'

    | REINTERPRET_CAST '<' type_id '>' '(' expression ')'

    | CONST_CAST '<' type_id '>' '(' expression ')'

    | TYPEID '(' expression ')'

    | TYPEID '(' type_id ')'

;

......

这看起来就像一段C++代码本身一样,任何一个懂C++语言的人看到这样的语法声明时都会立即明白其含义,这对于大型的yacc(Bison)语法声明文件来说是至关重要的,因为编写编译器的首要目标就是正确性,而可读性是这一切的基础。

当然,对于使用双引号字符串作为记号等价物时,与使用单个字符不同,在Bison内部,为每个记号和等价字符串都生成了相同的状态值,通常是从258开始编号的,而使用单引号的单字符符号时,生成的状态值是记号本身的ASCII码值,通常<=256。在阅读Bison生成的状态机文件或最终代码文件中,需要注意这个区别。

最终在具体使用中,个人推荐使用双引号字符串记号等价声明的方式,在语法文件中直接嵌入标点符号终结符这种方式的,因为这为那些有多个不同符号表示相同含义的场合,可以方便的在lex文件中为不同的符号串返回相同的记号值,比如“!=”和“<>”这样的都可以表示不等于的情形下,在yacc中可以声明:

%token NE                 "!="

在Lex文件中就可以像下面这样处理:

"!="                             |

"<>"                             {return NE;}

最终在语法文件中引用字符串“!=”即可。当然这种技巧在实际的语言语法文件中不推荐使用,不同的符号串表达相同的含义,这本身就是比较多余的语言语法设计,对于最终语言的使用者来说无疑只是增加了学习和使用的负担。同时在复杂的可重载运算符的语言中,这也反倒会降低代码的可读性和简洁性,试想之前的例子如果允许“!=”和“<>”都能重载,那么为了可写性,程序员不得不为这两个运算符都编写内容重复的重载运算符函数,这显然也不利于代码的维护性。

http://www.dtcms.com/a/319997.html

相关文章:

  • 21点(人机)
  • 学习设计模式《二十一》——装饰模式
  • 深入解析Three.js中的BufferAttribute:源码与实现机制
  • 微信小程序与后台管理系统开发全流程指南
  • 用LaTeX优化FPGA开发:结合符号计算与Vivado工具链
  • 广东省省考备考(第六十九天8.7)——判断推理(强化训练)
  • 从零实现RPC框架:Go语言版
  • newlib库中malloc函数依赖_sbrk函数,该函数使用链接脚本中的_end符号作为堆的初始地址.
  • 古法笔记 | 通过查表进行ASCII字符编码转换
  • change和watch
  • Event Stream输出优化:Vue3节流函数的正确实现
  • Flink的运行模式
  • 【算法训练营Day22】回溯算法part4
  • Linux中进程地址空间
  • Godot ------ 中级人物血条制作01
  • 【LLM】扩散模型与自回归模型:文本生成的未来对决
  • GPT-5今夜亮相?OpenAI神秘直播预告,暗示新模型将至
  • 无人机未来的通信脉络:深度解析远距离无线通信模块的革新
  • 【源码】AndroidPlayer
  • 为何毫米波需要采用不同的DPD方法?如何量化其值?
  • pma_init reset_pb
  • 服务器Docker安装教程
  • openGauss3.10企业版单机部署(openEuler20.03 SP3)
  • 嵌入式学习硬件(一)ARM体系架构
  • 【数字图像处理系列笔记】Ch05:傅里叶变换与频率域滤波
  • 哈勃网络计划大规模升级卫星以创建全球蓝牙层
  • AI代码审查大文档处理技术实践
  • mysql如何实现备份某个数据库并还原备份
  • RHCA - CL260 | Day04:对象存储、存储池、用户认证
  • Mysql自定义顺序查询