我想在YACC中编写算术表达式的规则;定义了以下操作:
+ - * / ()
但是,我不希望该陈述有周围的括号。也就是说,a+(b*c)
应该有匹配的规则,但(a+(b*c))
不应该。
我怎样才能做到这一点?
动机:
在我的语法中,我定义了一个这样的集合:(1,2,3,4)
,我想将(5)
视为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
,通过展开expr
→term
→factor
→ '(' expr ')'
。
以下是解决这种歧义的三种方法:
消除歧义是乏味的,但并不是特别困难;我们只是在每个优先级别定义两种子表达式,一种可能是括号的,另一种绝对不是括号括起来的。我们从一些带括号的表达式的简写开始:
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_list
和expr
;它只是将( 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
没有冲突。
虽然可以明确地消除歧义,但是结果语法很臃肿而且不是很清楚,这是不幸的。
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
。 (它们在只有一种生产可能的情况下没有效果。)
虽然可能按指定的方式解析语言,但我们可能不会对任何人有任何好处。甚至可能会有人在改变时感到惊讶的投诉
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,]
都是列表。