是C#的lambdaexpression式语法LALR(1)?

我希望问的问题在标题中得到了肯定。 让我举一个有关语法的例子:

identifier_list : identifier | identifier_list identifier; lambda_arguments : '(' identifier_list ')' | identifier; lambda : lambda_arguments '=>' expression 

然后我们添加正常的C语言expression语法 – 特别是,

 primary_expression : '(' expression ')' | identifier | lambda; 

真正的问题是,这个语法LALR(1)是可parsing的,即能够被自动parsing器生成器parsing吗? 还是需要手动滚动或GLRparsing器? 请注意,我希望具体了解这一小节,而不是上下文相关的关键字或其他部分。

我现在想的是,如果parsing器看到'(' identifier ')' ,这有两个有效的parsing,所以如果parsing器看到identifier ,向前看')' ,它将无法决定parsing树下去。 这可能只是一个转换/减less冲突,但是,我可以通过分配一些任意优先级(可能有利于'(' identifier ')' )来消除。

编辑:其实,我正在考虑用新语言中的这个语法分段来窃取类似的function。 我已经有类似于JavaScript的匿名函数的语法forms,但是我的天竺鼠吱吱声反馈抱怨说他们对于很多用途来说太冗长了,并且指出C#lambdaexpression式是更理想的解决scheme。 我担心这个解决scheme可能导致模糊。 所以,真的,我只对这一部分感兴趣。 其他类似generics和演员对我来说不是问题。

以前版本的语法是机械可分解的,我不想失去这个属性,而我之前使用机械发生器的经验告诉我,最好在这里检查一下,而不是尝试自己。 对于我的手滚parsing器,我当然可以简单地使用特殊情况'(' identifier比正常情况稍微向前看一些。

用C#风格的lambdaexpression式增加的expression式语法不是LALR(1),但可能是LALR(2)。 因此,生成一个等价的LALR(1)语法是可能的(尽pipe不一定是微不足道的):参见下面的编辑。

你将得到一个input减less/减less的冲突:

 ( id ) 

因为id可以简化为identifier_listexpression (间接的,在第二种情况下),并且parsing器根据一个先行标记( )不能分辨哪一个是正确的。

它可以基于两个前瞻符号来说明,因为只有在第二个下一个符号=> identifier_list减less才有可能,只要=>不是语言中的运算符,那么如果第二个下一个符号是=> 。 所以我认为这可能是LALR(2),虽然我不能肯定地说。

因为有多个标识符的情况是没有问题的

 ( id1 id2 ) 

id1 id2不能简化为一个expression式(在大多数expression式语言中,当然可能不同)。 如果一个单一的未被标识的标识符后面紧接着=> ,那么假设'=>'不是一个有效的运算符,也是没有问题的。

编辑

在我原来的回答中,我忽略了提到没有LALR(2)语言这样的东西。 LALR(2)语法识别的语言也被一些LALR(1)语法识别。 事实上,这个断言有一个build设性的certificate,它允许机械创build这样一个LALR(1)语法,以及恢复原始parsing树的过程。

在这种情况下,生成LALR(1)文法就更简单了,因为如上所述,只有一个生产需要额外的预测。 解决办法是延迟一个令牌的减less。 换句话说,在原始语法中包含如下内容:

 primary: '(' expression ')' lambda_parameters: '(' id_list ')' 

id_listexpression派生terminalID 。 除ID之外,这两个非terminal的派生是不相交的,所以我们可以这样解决这个问题:

 primary: '(' expression_not_id ')' | '(' ID ')' lambda_parameters: '(' id_list_not_id ')' | '(' ID ')' " 

只是将expressionid_list生成分开以便将ID事件分开,这不是很困难。 下面是一个简单的例子,可以很容易地扩展; 它仅限于添加,乘法和函数应用(我包括了这两个表示两个逗号分隔的列表不是问题):

 %token ID LITERAL RIGHT_ARROW %start expr %% primary: primary_not_id | ID ; term: term_not_id | ID ; sum: sum_not_id | ID ; expr: expr_not_id | ID ; expr_list: expr | expr_list ',' expr ; arguments: '(' ')' | '(' expr_list ')' ; ids: ID ',' ID | ids ',' ID ; parameters: '(' ID ')' | '(' ids ')' ; primary_not_id: LITERAL | '(' expr_not_id ')' | '(' ID ')' | primary arguments ; term_not_id: primary_not_id | term '*' primary ; sum_not_id: term_not_id | sum '+' term ; expr_not_id: sum_not_id | parameters RIGHT_ARROW expr ; 

注意:OP中的语法生成具有多个参数的lambdas,作为不以逗号分隔的标识符序列: (ab) => a + b 。 我认为实际意图是使用逗号: (a, b) => a + b ,这就是我在上面的语法中所做的。 如果你的语言有一个逗号运算符,那么区别就很重要,因为在这种情况下,expression式可能是'(' expression_list ')' ,它与lambda参数列表冲突。 一个简单的实现会导致expression中的第一个expression的减less/减less冲突,由于expression_list可能是任意长的,所以不能用有限的前瞻来解决。

不过,这种情况也有一个解决scheme:它将id_listexpression_list分开,如下所示:

 id_list: ID | id_list ',' ID ; expression_list_not_id_list: expression_not_id | id_list ',' expression_not_id | expression_list_not_id_list ',' expression ; expression_list: expression_list_not_id_list | id_list ; 

但是我没有写出完整的语法,因为我不知道目标语言要求什么。

首先,parsing器理论总是我的一个弱点。 我主要工作在语义分析器上。

其次,我所做过的所有C#parsing器都是手工生成的recursion下降parsing器。 我以前在parsing器理论方面拥有很强的背景的同事中,有一位build立了自己的parsing器生成器,并成功地将C#语法join其中,但是我不知道这样做会带来什么样的恶意攻击。

所以我在这里说的是以适当的怀疑态度来回答这个问题。


就像你注意到的那样,lambdaexpression式有点令人费解,因为你必须小心这个带括号的expression式 – 它可能是一个加括号的expression式,一个转换操作符或一个lambda参数列表,lambda参数列表可能有几种不同的forms。 但是考虑到所有事情,在C#3.0中添加lambdaexpression式在语法上相对容易; 破解parsing器并不是太困难 – 语义分析对于lambda来说是一个负担。

就C#语法而言,真正令人头疼的问题就是genericscast

在C#2中添加generics,在语言已经具有>>><运算符之后,所有这些在将generics引入混合时都会引起奇怪的问题。

经典问题当然是A ( B < C, D > ( E ) )方法A的调用有两个参数: B < CD > (E)还是一个, B<C,D>( E )

消除歧义的规则是:

如果可以将一系列标记parsing为以types参数列表结束的简单名称,成员访问或指针成员访问,则会检查紧接在closures>标记之后的标记。 如果是( ) ] : ; , . ? == != ( ) ] : ; , . ? == != ( ) ] : ; , . ? == !=那么types参数列表被保留为简单名称,成员访问或指针成员访问的一部分,任何其他可能的令牌序列parsing都将被丢弃。 否则,即使没有其他可能的令牌序列parsing,types参数列表也不被视为简单名称,成员访问或指针成员访问的一部分。

语法的第二个问题可以追溯到C#1.0,​​那就是演员。 问题是(x)-y可能意味着“铸造 – 键入x ”或它可能意味着从x减去y 。 这里的规则是:

只有在满足以下至less一个条件的情况下,括在括号内的一个或多个令牌的序列才被认为是cast-expression的开始:

令牌的顺序是一个types的正确语法,但不是一个expression式。

令牌的顺序是一个types的正确的语法,紧接着括号后面的标记是标记“〜”,标记“!”,标记“(”,标识符,文字或除了is

消除这两种情况的规则在理论上涉及到很大的前瞻性,但在实践中,你很less必须将parsing器备份到很远的地方。

是的,这种情况是一个简单的减less/减less冲突。

 %token identifier ARROW %% program : expression | program expression ; identifier_list : identifier | identifier_list identifier; lambda_arguments : '(' identifier_list ')' | identifier; lambda : lambda_arguments ARROW expression; primary_expression : '(' expression ')' | identifier | lambda; expression : primary_expression $ yacc -v test.6.y conflicts: 1 reduce/reduce 

这正是不知道下一个符号是什么时候减less的) :我们是在减less一个lambda_arguments列表还是一个primary_expression

parsing器生成器已经通过偏好lambda列表以错误的方式解决了它。 但是这意味着一个加上括号的expression是永远不会产生的。

这个混乱有几种方法。 这可能是最简单的方法,一个不包含冲突的修改过的语法:

 %token identifier ARROW %% program : expression | program expression ; identifier_list : identifier | identifier_list identifier ; lambda_arguments : '(' identifier identifier_list ')' | identifier ; primary_expression : '(' expression ')' | '(' expression ')' ARROW expression | lambda_arguments ARROW expression | identifier ; expression : primary_expression 

我们将lambda语法折叠到primary_expression ,而lambda_arguments现在可以是一个单独的未标识的标识符,或者是至less两个标识符的列表。

此外,lambdaexpression式现在有两个语法情况:

 | '(' expression ')' ARROW expression | lambda_arguments ARROW expression 

所以必须写出两个语义动作规则。 一些逻辑将是常见的,所以它可以被扩展到为lambda构build语法树节点的辅助函数。

第一个语法变体的动作必须检查$2右手符号,并检查它是一个由标识符标记组成的简单主expression式。 如果是这种情况,操作会打开expression式,取出标识符并从该标识符中构build一个lambda列表,并使用该列表生成以规则的输出结尾的lambda句法节点( $$值,在Yacc条款)。 如果$2是任何其他types的expression式,则会发出诊断:这是错误的lambda语法,如( 2 + 2 ) => foo 。 当然,这被parsing器接受了,这是如何调用规则的。 但它现在正在语义上被拒绝( 语义上指的是“语义”一词的低卡路里版本)。

第二个变体的操作很简单:像以前一样,使用lambda表,bodyexpression式和lambda节点。

简而言之,lambda语法如此紧密地集成到expression式语法中,使得它不能被简单地分解成完全独立的规则,这些规则是通过一个生产引入的,这个生产要求将lambda简化为primary_expression 。 这是一厢情愿的想法,因为shift-reduce分析器的规则不是函数调用。

我不认为lambdaexpression式语法问题本身是有趣的,除非知道其余的语言是LALR(1)。

如果您想知道答案,请将您的语法提交给LALR(1)parsing器生成器。 如果它抱怨减less冲突或减less冲突,那不是LALR(1)。 语法是否 LALR(1)取决于是否可以为其构build转换表。

大多数人想要整个语言的parsing器。

这里有两个有趣的问题。

1)C#4.5是一种语言LALR(1)吗? (例如,是否有一些语法是LALR(1)?注意,不是LALR(1)的特定语法并不意味着没有另一个语法。

2)Microsoft(以其多种forms)LALR(1)发布的任何C#语法?

我认为埃里克告诉我们1)是不正确的。 这表明2)也是不正确的。

C ++需要无限的前瞻来parsing它的模板,很大程度上是由“>”的本地可能性被解释为“结束模板参数”或“大于”引起的。 由于C#复制这个,我希望它也有模板parsing无限的超前需求。 这绝对不是LALR(1)。 [关于“>>”是否可以当作移位运算符,还有一个额外的问题,“>>”不能,因为看不到空格,所以不能修复语法。]

我的公司使用GLR作为其语言处理工具,而且我们有一个C#4.5语法运行良好。 GLR意味着我们不必考虑如何将上下文无关的语法转换为与LALR(1)兼容的forms(例如,弯曲,扭转,左/右因子,随机播放),或者代码特定的前瞻等等。因此我们可以专注于处理代码的问题,而不是parsing。

这确实意味着演员和其他构造在parsing时会产生不明确的树,但是如果您有types信息,则这些树很容易解决。