据我了解,大多数语言都是上下文无关的,但也有一些例外。例如,在 C++ 中a * b
可能代表或乘法。type * pointer_declaration
哪个发生取决于上下文,第一个标识符的含义。另一个例子是name
用 VHDL 制作
enum_literal ::= char_literal | identifer
physical_literal ::= [num] unit_identifier
func_call ::= func_identifier [parenthized_args]
array_indexing ::= arr_name (index_expr)
name ::= func_call | physical_literal | enum_litral | array_indexing
您会看到语法形式是不同的,但如果省略可选参数,它们可以匹配,例如f
,它是否代表 func_call、physical_literal,例如隐含可选量为 1 的 1 米或 enum_literal。
在与 Scala 插件设计人员交谈时,我了解到您构建 AST 是为了在依赖关系发生变化时重新评估它。如果您有 AST,则无需重新解析文件。AST 也值得显示文件内容。但是,如果语法是上下文相关的(假设这f
是一个函数,在另一个文件中定义,但后来用户将其重新限定为枚举文字或未定义),则 AST 无效。在这种情况下,AST 会发生变化。每当您更改依赖项时,AST 都会更改。我要求评估并让我知道如何制作的另一种选择是构建一个模棱两可的 AST。
据我所知,解析器组合器是PEG 类型的。他们通过返回第一个匹配的产品来隐藏歧义,并且f
会匹配一个函数调用,因为它是我语法中的第一个替代方案。我要求一个组合器,它不会退回到第一次成功,而是继续下一个替代方案。最后,它会返回给我一个所有匹配选项的列表。它会让我感到模棱两可。
我不知道您将如何向用户显示不明确的文件内容树,但它会消除重新解析依赖文件的需要。我也很高兴知道现代语言设计如何解决这个问题。
一旦解析了不明确的节点并返回了不明确的结果,我希望解析器收敛,因为我想继续解析,name
并且我不想在每个歧义之后解析到文件末尾。这种情况因诸如 之类的情况而变得复杂f(10)
,它可以是带有单个参数的函数调用,也可以是返回一个数组的空函数调用,该数组随后被索引。因此,f(10) 可以通过两种方式匹配名称,func_call
直接匹配或递归匹配arr_indexing -> name ~ (expr)
。因此,它不会像几个并行规则那样模棱两可,例如fcall | literal
. 在重新收敛之前,某些分支可能比 1 个解析器长,例如fcall ~ (expr) | fcall
.
你将如何解决它?是否可以将歧义组合器添加到 PEG?