How are parenthesis vs. lambda vs. tuple expressions parsed in C#
我正在编写自己的编程语言,并且在排除了其他大部分内容之后,开始解析元组/ lambda表达式。 语法非常类似于C#,但有一些区别。 我想知道C#是否可以确定是否要生成一个简单的带括号的表达式(元组或lambda),因为前瞻(k)是不确定的。
现在,我通过偷看某些指标来为解决方案共同努力,这些指标应为解析器提供有关其试图解析内容的线索。 即在@ 2处检查标识符,然后在@ 2处检查'=',','或':'(冒号用于用类型信息注释标识符)。
我的lambda语法比C#中的可用语法灵活一些,因此您可以在其中猜测标识符列表,直到不再有逗号为止,然后检查下两个标记中的')'和'=>'。
我缺少某种技巧吗?
-
基本上,自定义前瞻。例如看一下。
-
是的,似乎是我遇到的问题:p谢谢,很好找到:)
-
对于我认为与该答案中的C#lambda语法相似的内容,有一个LALR(1)语法。我之所以没有将其标记为重复项是因为它不处理元组,并且因为您说所需的语法是不同的(不说它是什么),并且因为您不清楚要构建哪种解析器。但是也许答案仍然有用。
-
C#Roslyn编译器是开源的。
-
@rici我不相信它可以使用LALR(1)语法完成,您必须知道标识符之后是什么,以确定它是否是纯ol表达式,lambda参数列表等。因此,充其量最好是LALR(2) )。
-
@rtlayzell:如果语言有lalr(2)语法,则也存在lalr(1)语法。这可能是违反直觉的,但是lalr(1)语法的机械构造并不复杂。 (但是,它非常笨重,因此通常最好手动完成。)链接的答案显示了一个可能是类似语法的示例。在不了解有关您提议的语法的更多详细信息的情况下,我无法提供更精确的建议。
-
我猜你的语言的区别是元组的语法,它看起来像是lambda参数列表。但是,那并不是一个内在的问题,可以通过与链接的答案类似的方式来解决。您需要一个中间的非终结符,它是ID列表,并且避免看到元组或lambda,直到看到下面的内容。这似乎是挥舞着的手,但是正如我所说的,没有细节很难做到精确。
-
@rici那几乎是我正在做的事情,本质上是我在构建"绑定表达式"的中间列表,然后根据")"之后的标记创建一个元组或lambda,如果它是lambda,则我访问绑定表达式列表并进行转换将" typed_binding"和" named_binding"转换为参数以构造lambda表达式。我无法将其作为BNF进行演示,但是下面是几个元组和lambda看起来像的例子(x,y)(x = 10,y = 20)(x:int,y:int)(x:int = 10,y:int = 20)只有第一个和第三个对lambda和元组有效。
-
因此,我想您正在尝试手动构建一个预测分析器,这意味着存在KAKR(1)语法的事实或多或少都是不相关的。恕我直言,任何手工构建的预测解析器在某处注定都是有点笨拙,这很可能就是为什么您不能从其中提取BNF以便查看要解析的语言的原因。如果我对我的假设正确,那么我不完全知道您在这里寻找什么。另一方面,如果您需要帮助来创建适合自动??代码生成的语法,则使用描述(在Q中)将很有用。
-
实际上,我自己的问题在大多数情况下已经由您本人和线程中的其他人回答。正如您所说,我不一定需要一种适用于代码生成的语法,只是试图重申自己的方法或找到更好的方法。到目前为止,已经提出了两种方法,即您提到的中间非终结符和回溯,而回溯似乎是解析表达式的更简单方法,我无法找出一种以这种方式产生有用语法错误的方法,因此与中间方法。 Ive在接受任何问题之前未解决任何问题,以获得更多答案。
-
@rtlayzell:第三种方法是使用GLR / GLL解析器,该解析器有效地使用了聪明的数据结构来并行进行不同的可能解析。为此,它必须避免执行具有副作用(如果有)的语义动作,直到算法解析为单个解析(如果有); GLR也可以处理歧义语法。如今,许多解析器生成器实现了这些算法之一,并且在大多数情况下是我最喜欢的解决方案,因为它不需要对语法进行模糊的修改。我可能会在当天晚些时候添加答案
是的,您需要无限前行,因为在某些情况下,(a, b, c, ...)可以是元组,也可以是lambda参数列表,具体取决于后面跟随的标记。还有一个歧义,因为(a.b.c.d...可以是强制类型转换中的类型表达式,也可以是带括号的正则表达式。
您可以在解析器源代码中看到C#如何解决此问题:
http://source.roslyn.io/#Microsoft.CodeAnalysis.CSharp/Parser/LanguageParser.cs,bab23e84fb31bf9e,参考
基本上,它保存当前位置并尝试解析可能的结果之一。如果解析失败,它将返回到保存的位置(也就是回溯),然后尝试下一次可能的生产。如果所有可能的生产均失败,则报告语法错误。
它似乎在两遍中运行潜在的解析。在第一遍中,它仅检查是否可以进行解析,并返回布尔值。如果此测试成功,它将运行第二遍,并在此实际构造解析树。
从理论上讲,无限制的超前处理会破坏解析器的性能,但是实际上,这可能不是一个大问题,因为它仅在特定的特定情况下才会发生,并且元组和lambda参数列表的大小通常有限。
语言设计者倾向于避免语法要求超前,因为它增加了解析器的复杂性和性能影响。但是C#的设计人员可能认为,添加漂亮的元组语法值得付出成本。
-
很有意思,我曾想过在解析失败后保存解析状态并回溯,尽管似乎很难衡量用户的意图并产生正确的语法错误。 例如 如果解析lambda仅因为lambda的主体有错误而失败,那么我们应该推送该错误。 但是,如果由于缺少期望的=>令牌而导致无法解析lambda,则我们不应该推送error(?)。
-
另外,我的语言不使用带括号的引号作为强制类型转换,对此我有一个更清晰/明确的语法。