算术表达式的YACC语法,没有周围的括号

时间:2017-05-14 20:25:18

标签: parsing yacc bnf

我想在YACC中编写算术表达式的规则;定义了以下操作:

+   -   *   /   ()

但是,我不希望该陈述有周围的括号。也就是说,a+(b*c)应该有匹配的规则,但(a+(b*c))不应该。

我怎样才能做到这一点?

动机:

在我的语法中,我定义了一个这样的集合:(1,2,3,4),我想将(5)视为1元素集。模棱两可导致减少/减少冲突。

1 个答案:

答案 0 :(得分:4)

这是一个非常简单的算术语法。它处理你提到的四个运算符和赋值语句:

stmt:      ID '=' expr ';'
expr:      term | expr '-' term | expr '+' term
term:      factor | term '*' factor | term '/' factor
factor:    ID | NUMBER | '(' expr ')' | '-' factor

定义“set”文字很容易:

set:       '(' ')' | '(' expr_list ')'
expr_list: expr | expr_list ',' expr

如果我们假设set literal只能作为赋值语句中的值出现,而不是算术运算符的操作数,那么我们将为“expression或set literals”添加语法:

value:     expr | set

并修改赋值语句的语法以使用它:

stmt:      ID '=' value ';'

但这导致您提及的减少/减少冲突,因为(5)可能是expr,通过展开exprtermfactor'(' expr ')'

以下是解决这种歧义的三种方法:

1。明确删除歧义

消除歧义是乏味的,但并不是特别困难;我们只是在每个优先级别定义两种子表达式,一种可能是括号的,另一种绝对不是括号括起来的。我们从一些带括号的表达式的简写开始:

paren:     '(' expr ')'

然后对于每个子表达式X,我们添加一个作品pp_X

pp_term:   term | paren

并通过允许带括号的子表达式作为操作数来修改现有的生产:

term:      factor | pp_term '*' pp_factor | pp_term '/' pp_factor

不幸的是,由于expr_list的定义方式,我们仍然会遇到转变/减少冲突。面对任务声明的开头:

a = ( 5 )

完成5后,)是前瞻标记,解析器不知道(5)set(在这种情况下是下一个) token将是; )或paren(仅当下一个标记是操作数时才有效)。这不是歧义 - 使用LR(2)解析表可以简单地解析解析 - 但是没有很多工具可以生成LR(2)解析器。因此,我们坚持认为expr_list必须有两个表达式,并将paren添加到set的作品中来回避这个问题:

set:       '(' ')' | paren | '(' expr_list ')'
expr_list: expr ',' expr | expr_list ',' expr

现在解析器不需要在赋值语句中选择expr_listexpr;它只是将 5 减少为paren并等待下一个令牌以澄清解析。

所以最终得到:

stmt:      ID '=' value ';'
value:     expr | set

set:       '(' ')' | paren | '(' expr_list ')'
expr_list: expr ',' expr | expr_list ',' expr

paren:     '(' expr ')'
pp_expr:   expr | paren
expr:      term | pp_expr '-' pp_term | pp_expr '+' pp_term
pp_term:   term | paren
term:      factor | pp_term '*' pp_factor | pp_term '/' pp_factor
pp_factor: factor | paren
factor:    ID | NUMBER | '-' pp_factor

没有冲突。

2。使用GLR解析器

虽然可以明确地消除歧义,但是结果语法很臃肿而且不是很清楚,这是不幸的。

Bison可以生成GLR解析器,这将允许更简单的语法。事实上,原始语法几乎不需要修改;我们只需要使用Bison %dprec动态优先级声明来指示如何消除歧义:

%glr-parser
%%
stmt:      ID '=' value ';'
value:     expr    %dprec 1
     |     set     %dprec 2
expr:      term | expr '-' term | expr '+' term
term:      factor | term '*' factor | term '/' factor
factor:    ID | NUMBER | '(' expr ')' | '-' factor
set:       '(' ')' | '(' expr_list ')'
expr_list: expr | expr_list ',' expr

%dprec的两个作品中的value声明告诉解析器如果两个制作都可能,则更喜欢value: set。 (它们在只有一种生产可能的情况下没有效果。)

3。修复语言

虽然可能按指定的方式解析语言,但我们可能不会对任何人有任何好处。甚至可能会有人在改变时感到惊讶的投诉

a = ( some complicated expression ) * 2

a = ( some complicated expression )

突然a成为一组而不是标量。

通常情况下,语法不明显的语言也难以解析。 (例如,参见C ++的“最令人烦恼的解析”)。

使用( expression list )创建元组文字的Python采用了一种非常简单的方法:( expression )始终是一个表达式,因此元组需要为空或至少包含一个逗号。为了使后者成为可能,Python允许使用尾随逗号来编写元组文字;除非元组包含单个元素,否则尾随逗号是可选的。因此(5)是一个表达式,而()(5,)(5,6)(5,6,)都是元组(最后两个在语义上相同)。

Python列表写在方括号之间;在这里,再次允许使用尾随逗号,但从不需要它,因为[5]不明确。因此,[][5][5,][5,6][5,6,]都是列表。