我在Bison中为一种简单的动态类型语言创建语法。我有一个"将军" expression
规则,有点类似于C中rvalue的概念;表达式出现在赋值的右侧,它们也可以作为参数等发送给函数。规则的大大简化的版本如下:
constantExpression
: TOK_INTEGER_CONSTANT
| TOK_FLOAT_CONSTANT
| stringLiteral
;
expression
: constantExpression
| identifier
| booleanExpression
| booleanExpression TOK_QUESTION_MARK expression TOK_COLON expression
| TOK_LPAREN expression TOK_RPAREN
| expression TOK_PLUS expression
| expression TOK_MINUS expression
;
我也有一个专用的布尔表达式规则;布尔表达式在if
语句中最突出地使用,但任何其他需要二进制真值的上下文当然也很好:
booleanExpression
: identifier
| expression '<' expression
| expression '<=' expression
| expression '==' expression
| expression '!=' expression
| expression '>' expression
| expression '>=' expression
| booleanExpression '&&' booleanExpression
| booleanExpression '||' booleanExpression
| '!' booleanExpression
| 'true'
| 'false'
;
问题:显然上述规则遭受了大量的减少 - 减少冲突。根据上下文,标识符应减少到expression
(例如,在诸如此类的语句中myVar2 = myVar1
)或booleanExpression
(显而易见的示例:{{ 1}})。
不仅如此,还有与if (myBoolVar)
减少为booleanExpresssion
的事实相关的减少偏移的错误;当解析器解析了expression
并且它遇到booleanExpression
令牌时,它可以移位并继续前进,或者缩减为&&
。 expression
需要缩减为表达式以允许代码,例如
booleanExpression
我知道与运营商优先级相关的转移减少冲突,这不是问题,我已经使用运营商的conditionVar = (var1 == 5) || (var2 > 10 && var3 < 20);
if (conditionVar) { ... }
规则修复了它。
我的问题:这个问题的最佳解决方案是什么?我的想法是
%left
规则并移动
这一切都是booleanExpression
- 好的,但需要更多检查
语义分析阶段;在我的语言中,字符串不是含蓄的
可转换为布尔值,因此像expression
这样的代码不合法,但是
将被解析器接受以上哪个是最好的主意?还有其他可能性我还没考虑过吗?
答案 0 :(得分:6)
传统观点是:不要尝试在语法中进行语义分析。
首先,正如您所见,即使可能,它也会使语法复杂化。相比之下,类型检查规则在AST上的树遍历中执行时非常简单。
其次,这是不可能的。由于您的语言是动态的,因此您并不确切知道任何变量的类型。因此,编译时检查可能导致三种情况,而不是两种:好的,坏的和未知的。这在语法上会更复杂,但在语义分析中只会稍微复杂一些。
但是,根据您的语言的确切性质,可能选择中间立场。通常,一些运算符 - 布尔运算符和比较---肯定会返回布尔值,而某些上下文肯定需要布尔值。所以你可以添加一个boolean_expression
非终端,用于指示结果肯定是布尔值的位置,以及值必须是布尔值的位置。然后你可以在你的语法中插入一个单位生产
boolean_expression: expression
使用语义操作将运行时检查节点插入AST。
在语义分析期间,如果确定它将始终成功或者如果确定它将始终失败则产生错误,则可以消除该检查。否则,最终将发出代码以进行检查。
这个解决方案的优点是语法然后显示需要布尔值的上下文,而不会受到完全执行要求所必需的拜占庭修改。
(在下面的例子中,我只展示了一个布尔运算符,一个比较运算符和一个算术运算符。显然,真正的语言会有更多,但它根本不会改变表示。我也没有#39 ;打扰序言,序言必须包括运算符的优先声明。)
program : stmt_list
stmt_list:%empty
| stmt_list stmt
stmt : assign
| call
| empty
| while
| '{' stmt_list '}'
assign : IDENTIFIER '=' expr ';'
call : expr '(' expr_list ')' ';'
| expr '(' ')' ';'
empty : ';'
while : "while" '(' boolean_expr ')' stmt
expr_list
: expr
| expr_list ',' expr
boolean_expr
: boolean_term
| boolean_expr "or" boolean_expr
| expr '<' expr
boolean_term
: "true" | "false"
| expr { /* insert conversion from expr to boolean */ }
expr : term
| expr '+' expr
term : INTEGER
| IDENTIFIER
| '(' expr ')'
但它确实对语言有一些限制。在上面提到的最简单的化身中,除了布尔上下文之外,永远不能使用布尔值,这会阻止布尔值成为第一类值。它们不能用作赋值的右侧或函数调用中的参数,例如,从上面的语法中可以清楚地看出。
此外,上述语法不允许在布尔表达式周围使用冗余括号。
这不是很令人满意,但我们可以通过将布尔值与布尔值分开来做得更好,代价是语法中有轻微的复杂性。
在大多数语言中,可以根据其他值定义的规则创建布尔值;按照惯例,转换为布尔值true
的值称为&#34; truthy&#34;值。这可能非常方便,但如果强制性质的纬度太大,它也会有点危险。 [注1]为了控制损坏,我们可能只允许在显式布尔上下文中自动强制布尔值,并且永远不允许布尔值自动强制转换为非布尔值。如果您愿意接受这些限制,那么我们仍然可以使用语法作为记录布尔上下文和强制的工具。
在下文中,我们介绍了四个非终端,都代表了一些表达方式:
expr
:非布尔表达式boolean_expr
:一个特殊的布尔表达式;相应的产品列出了必须具有布尔结果的语法。truthy_expr
:布尔表达式或非布尔表达式,可以强制转换为布尔表达式。这个非终端用于需要布尔值的地方。 [注2] either_expr
:上下文中的布尔表达式或非布尔表达式,其中任何一个都可能在没有强制的情况下出现(例如,赋值和函数参数)。请注意,最后两个非终端具有完全相同的产品,但语义非常不同(因此语义动作也不同)。因为它们可能出现的背景是不相交的,所以不会产生冲突。
除了上述非终端的定义及其在各种情况下的使用外,语法没有太大变化:
program : stmt_list
stmt_list:%empty
| stmt_list stmt
stmt : assign
| call
| empty
| while
| '{' stmt_list '}'
assign : IDENTIFIER '=' either_expr ';'
call : expr '(' expr_list ')' ';'
| expr '(' ')' ';'
empty : ';'
while : "while" '(' truthy_expr ')' stmt
expr_list
: either_expr
| expr_list ',' either_expr
truthy_expr
: boolean_expr
| expr { /* insert conversion from expr to boolean */ }
either_expr
: boolean_expr
| expr
boolean_expr
: boolean_term
| truthy_expr "or" truthy_expr
| expr '<' expr
boolean_term
: "true"
| "false"
| '(' boolean_expr ')'
expr : term
| expr '+' expr
term : INTEGER
| IDENTIFIER
| '(' expr ')'
如果您认为上述内容过于复杂,那么请遵循传统观点,避免在语法中穿插语义。另一方面,如果您认为它具有说明性价值且您的语言是可接受的,那么请根据您的目的进行调整。
该计划并不依赖于任何&#34; truthy&#34;强制,但如果布尔值是第一类,则会有一些表达式只能在运行时确定为布尔值(布尔变量,返回布尔值的函数等)。考虑运行时检查,布尔上下文中使用的值是布尔值,是强制转换为真实性的形式,其中只有true
为真,只有false
为假,而所有其他值抛出错误。
就个人而言,我已经喜欢有限的自动布尔强制。例如,如果文件对象处于错误状态,那么文件对象就是假的,或者如果容器是非空的,那么它就是真实的。将这些转换限制为显式布尔上下文,例如条件语句中的条件,使得我可以接受自动强制。但我并不坚持这个想法;如果你不喜欢它,请忽略这个想法。
这不是一个非常好的名字,但truthy_or_falsy_expr
似乎太长了,boolish_expr
似乎太奇怪了。欢迎提出建议。