在表达式解析中处理模糊但破坏语法

时间:2018-04-28 14:27:44

标签: parsing compiler-construction expression recursive-descent

上下文

我最近提出了一个问题,我自己在我正在编写的解析器中无法解决这个问题。

这个解析器是我构建的编译器中的一个组件,问题在于编程语言解析所需的表达式解析。

我的解析器使用递归下降来解析表达式。

问题

我使用普通的常规语言解析规则解析表达式,我在所有规则中都消除了左递归,但是有一个句法"歧义"我的解析器根本无法处理,它涉及泛型。

comparison → addition ( ( ">" | ">=" | "<" | "<=" ) addition )* ;

是我用于解析表达式

中的比较节点的规则

另一方面,我决定用这种方式解析泛型表达式:

generic → primary ( "<" arguments ">" ) ;

,其中

arguments → expression ( "," expression )* ;

现在因为泛型表达式具有更高的优先级,因为它们是语言结构而不是数学表达式,所以它会导致通用解析器在不应该解析表达式时尝试解析表达式。

例如,在a<2中,它将解析&#34; a&#34;作为标识符类型的主要元素,之后立即找到泛型类型的语法,解析并失败,因为它无法找到结束标记。

这种情况的解决方案是什么?特别是在像C ++这样的语言中,如果我没有弄错arr<1<2>,那么泛型也可以在其中包含表达式。

这是一个特殊的边缘情况还是需要修改我不知道的语法定义?

谢谢

1 个答案:

答案 0 :(得分:3)

  

例如在&lt; 2中它将解析&#34; a&#34;作为标识符类型的主要元素,后来发现泛型类型的语法,解析并失败,因为它无法找到结束标记

这种特殊情况可以通过回溯或无限前瞻来解决。正如您所说,解析器在将其解释为泛型时最终会失败,因此当发生这种情况时,您可以返回并将其解析为关系运算符。前瞻性变体是在看到<时向前看,以检查<后面是逗号分隔的类型名称和>,如果是x<y>z则只进入通用规则案件。

然而,如果两种解释在语法上都有效(意味着语法实际上不明确),则该方法不再有效。其中一个例子是z,它可以是类型为x<y>的变量x<y>z的声明,也可以是两个比较。这个例子有点没有问题,因为后者的含义几乎从来都不是预期的,所以可以将它解释为前者(例如在C#中发生)。

现在,如果我们允许表达式,它会变得更复杂。对于a<b<c>()来说,它很容易说这绝不应该被解释为两个比较,因为将比较结果与其他比较进行比较是没有意义的(在许多语言中使用布尔运算符上的关系运算符是无论如何都要输入错误)。但对于类似a的内容,有两种解释可能都有效:b<c是使用泛型参数b调用的泛型函数,或c是泛型函数泛型参数a(和primary与调用该函数的结果进行比较)。在这一点上,仅凭语法规则不再可能解决这种歧义:

为了支持这一点,您需要检查给定的primary是否引用泛型函数并根据该函数做出不同的解析决策,或者让您的解析器在出现歧义时生成多个树然后在稍后的阶段选择正确的一个。前一个选项意味着您的解析器需要跟踪当前定义的哪些泛型函数(以及范围内),然后只有在给定的LL(*)是其中一个函数的名称时才进入通用规则。请注意,如果允许在使用函数后定义函数,则会变得更加复杂。

因此,总结支持表达式作为泛型参数需要您在解析时跟踪哪些函数在范围内并使用该信息来做出解析决策(意味着您的解析器对上下文敏感)或生成多个可能的AST。没有表达式,你可以保持上下文的自由和明确,但需要回溯或任意前瞻(意思是obj.<T>foo())。

由于这些都不是理想的,因此某些语言会更改使用显式类型参数调用泛型函数的语法,以使其成为LL(1)。例如:

  • Java将方法的通用参数列表放在方法名称之前,即obj.foo<T>()而不是::
  • Rust在通用参数列表前需要foo::<T>()foo<T>()而不是foo[T]()
  • Scala使用方括号作为泛型而不是其他任何内容(数组下标使用括号):foo<T>()而不是SELECT NAMES , SUM(CASE WHEN PART = 'F001' THEN 1 ELSE 0 END) AS F001 , SUM(CASE WHEN PART = 'F002' THEN 1 ELSE 0 END) AS F002 , SUM(CASE WHEN PART = 'F003' THEN 1 ELSE 0 END) AS F003 , SUM(QTY) AS alias FROM FROM MY_TABLE GROUP BY NAMES ORDER BY NAMES ASC