Compiler construction 漂亮的模棱两可的语法

Compiler construction 漂亮的模棱两可的语法,compiler-construction,abstract-syntax-tree,pretty-print,ambiguous-grammar,Compiler Construction,Abstract Syntax Tree,Pretty Print,Ambiguous Grammar,我已经实现了解析器组合器,它可以解析可能包含歧义的语法。当语法模棱两可时会出现错误,但事实证明,朝另一个方向走会更加困难。问题是如何用最少的括号将抽象语法树漂亮地打印成可能不明确的语法。使用运算符优先级有帮助,但不是万能药。在同一优先级内,问题仍然存在 确切的运算符直到运行时才知道,当用户引入新运算符时,可以在执行过程中更改。我支持前缀、后缀和中缀(左、右和非关联)运算符。中缀左运算符和后缀运算符同时在优先级别混合。这同样适用于中缀右运算符和前缀运算符。运算符还可以嵌入完整表达式,因此if-th

我已经实现了解析器组合器,它可以解析可能包含歧义的语法。当语法模棱两可时会出现错误,但事实证明,朝另一个方向走会更加困难。问题是如何用最少的括号将抽象语法树漂亮地打印成可能不明确的语法。使用运算符优先级有帮助,但不是万能药。在同一优先级内,问题仍然存在

确切的运算符直到运行时才知道,当用户引入新运算符时,可以在执行过程中更改。我支持前缀、后缀和中缀(左、右和非关联)运算符。中缀左运算符和后缀运算符同时在优先级别混合。这同样适用于中缀右运算符和前缀运算符。运算符还可以嵌入完整表达式,因此if-then-else和if-then都可以作为前缀运算符实现。(尽管这可能不是明智之举。)

下面是一个使用所提到的if-then-else和if-then运算符的示例,这些运算符在这里假定处于相同的优先级。显然,表达式
if a then if b then c else d
是不明确的,因为它可以解释为
if a then(if b then c)else d
if a then(if b then c else d)
。在漂亮的打印过程中,算法应该知道使用括号,即使两个运算符处于相同的优先级,并且具有兼容的关联性(右侧)

一个值得注意的示例:添加另一个前缀运算符,例如inc,其优先级与if-then-else和if-then相同。现在假设一个任意的集合
P⊂ H x O
其中
H
是操作器孔集,
O
是操作器集。这个集合是一个关系,它告诉我们何时需要添加括号。检查表达式
if a then inc b else c
if a then(inc if b then c)else d
。第一个要求
(if then else.2 inc)
不在
P
中,第二个要求相反。这与问题可以通过某种关系或顺序来解决的假设相矛盾。你可以试着说让
(inc.1,if-then)
P
中,如果a-then inc(if-b-then c)else d,那么
inc如果a-then b
变成
inc(if-a-then b)
,括号太多了

据我所知,语法与上下文无关。不过我对这个定义有点动摇

解析器松散地基于一篇论文。 我用的是Haskell


更新:正如NieDzejkob所证明的,这个问题通常是无法解决的。我愿意接受可能失败的算法。如果这还不足以使事情变得实用,那么一个好的启发式方法就可以了。

您可以根据所有运算符的实际关联性和定义的优先级,在它们之间构建排序的偏序关系

由于运算符的优先级取决于递归发生在规则中的哪个位置(最左侧、中间或最右侧),因此关系应包括父节点的哪个位置具有优先级

假设关系的类型为
rel[Operator parent,int pos,Operator child]

假设您可以在运行时应用优先级和关联性声明时从它们生成此关系,那么在漂亮的打印过程中使用此关系添加括号是很容易的。如果元组[parent,pos,child]在关系中,则打印方括号,否则不打印(反之亦然,如果关系颠倒)

如何获得这种关系?这里有Rascal语法形式主义的示例代码,它根据操作符之间的相对优先级生成:

它从如下规则开始:

E = left E "*" E  
  > left E "+" E
  ;
并产生类似于:

{<"*", 0, "+">, <"*", 2, "+"> // priority between * and + 
,<"+", 2, "+">, <"*", 2, "*"> // left associativity of * and +
}
或者一些徒劳的东西,那么一个相似的关系就可以建立起来。我们必须为表中的每个
i,j
级别生成一个元组,其中
i
。当然,您必须为每个操作员查找规则,以找出正确的位置

对于这些表和相对优先级(如Rascal中),重要的是以传递方式关闭关系,但是如果不想在打印时生成太多括号,则不能添加某些元组

也就是说,如果父规则是最右递归的,而子规则是最左递归的,则需要一个括号。反之亦然。但除此之外,情况并非如此。 考虑这个例子:

E = "if" E "then "E"
  > E "+" E
  ;
在这种情况下,我们确实希望括号位于最右边的孔中,但不希望括号位于“if”和“then”之间的防护孔中。索引规则的类似示例,如
E“[“E”]”

为了确保这一点,您可以首先计算哪些规则是最右的,哪些规则是最左的递归规则,然后从传递闭包中过滤元组,这些元组不会因为不明确而不含糊

因此,对于上面的示例,我们将生成:

{<"if", 3, "+">, // and not <"if", 1, "+"> because the 1 position is not left-most recursive, even though the "+" is right-most recursive.
}
{,//这不是因为1位置不是最左边的递归,即使“+”是最右边的递归。
}
关于此主题的论文,但它们使用相同的关系进行解析,而不是进行解析:


一般来说,这是不可能的。考虑运算符<代码> Ayb < /COD>,<代码> < C> ,<代码> AycCuB。表达式
A_C_B 1 2
(即
A 1 C 2 B
)不可能插入括号,因此无法解析为
A(1 C 2)B

我猜您想要的是打印一个由明确语法识别的明确句子的AST,并使用最少的括号,而不会产生歧义。我不知道这个公式是否更容易理解,但我花了一段时间才弄明白您想要什么?或
{<"if", 3, "+">, // and not <"if", 1, "+"> because the 1 position is not left-most recursive, even though the "+" is right-most recursive.
}