优先级声明在替代词汇之间是否有歧义?

时间:2018-07-10 20:35:15

标签: rascal

在我的previous question中,示例中有一个优先级>声明。事实证明这并不重要,因为那里的解决方案实际上并没有调用优先级,而是通过使替代方案脱节而避免了优先级。在这个问题上,我想问的是,是否可以使用优先级来选择一个词法生成而不是另一个。在下面的示例中,产品WordInitialDigit的语言故意是WordAny的子集。产生Word看起来应该正确地消除两者之间的歧义,但是结果分析树的顶部有一个歧义节点。优先级声明是否能够在不同的词汇化简之间做出决定,还是需要有共同的词汇要素基础?还是其他?

该示例是人为设计的(语法中没有任何操作),但其产生的情况并非如此。例如,我想使用类似的方法进行错误恢复,在该方法中,我可以识别语法单位的自然边界,并为其编写生产代码。这种通用生产将是优先链中的最后一个要素;如果减少,则表示没有有效的解析。更一般而言,我需要能够根据句法上下文选择词法元素。我曾希望,因为Rascal没有扫描仪,所以这将是无缝的。也许是,尽管我目前看不到。

我在不稳定分支上,版本为0.10.0.201807050853。

编辑:该问题与用于定义表达式语法的>无关。 documentation for priority declarations主要讨论表达式,但是第一句话提供了看起来非常清晰的定义:

  

优先级声明在单个非终端中的产品之间定义了部分排序。

因此,该示例有两个产生式,它们之间声明了顺序,但是在明确存在消歧规则的情况下,解析器仍在生成歧义节点。因此,要想更好地说明我的问题,似乎我不知道这两种情况中的哪一种有关。要么(1),如果这不起作用,则说明的语言定义存在缺陷,编译器的错误报告存在缺陷,并且语言设计决策介于反直觉和用户敌视之间。或(2)如果应该这样做,则说明编译器和/或解析器存在缺陷(可能是因为最初的重点是表达式),并且在某些时候示例将通过其测试。

module ssce

import analysis::grammars::Ambiguity;
import ParseTree;
import IO;
import String;

lexical WordChar = [0-9A-Za-z] ;
lexical Digit = [0-9] ;
lexical WordInitialDigit = Digit WordChar* !>> WordChar;
lexical WordAny = WordChar+ !>> WordChar;
syntax Word =
    WordInitialDigit
    > WordAny
    ;

test bool WordInitialDigit_0() = parseAccept( #Word, "4foo" );
test bool WordInitialDigit_1() = parseAccept( #WordInitialDigit, "4foo" );
test bool WordInitialDigit_2() = parseAccept( #WordAny, "4foo" );

bool verbose = false;

bool parseAccept( type[&T<:Tree] begin, str input )
{
    try
    {
        parse(begin, input, allowAmbiguity=false);
    }
    catch ParseError(loc _):
    {
        return false;
    }
    catch Ambiguity(loc l, str a, str b):
    {
        if (verbose)
        {
            println("[Ambiguity] #<a>, \"<b>\"");
            Tree tt = parse(begin, input, allowAmbiguity=true) ;
            iprintln(tt);
            list[Message] m = diagnose(tt) ;
            println( ToString(m) );
        }
        fail;
    }
    return true;
}

bool parseReject( type[&T<:Tree] begin, str input )
{
    try
    {
        parse(begin, input, allowAmbiguity=false);
    }
    catch ParseError(loc _):
    {
        return true;
    }
    return false;
}

str ToString( list[Message] msgs ) =
    ( ToString( msgs[0] ) | it + "\n" + ToString(m) | m <- msgs[1..]  );

str ToString( Message msg)
{
    switch(msg)
    {
        case error(str s, loc _): return "error: " + s;
        case warning(str s, loc _): return "warning: " + s;
        case info(str s, loc _): return "info: " + s;
    }
    return "";
}

2 个答案:

答案 0 :(得分:1)

>消歧机制用于递归定义,例如表达式语法。

所以要解决以下歧义:

syntax E 
   = [0-9]+
   | E "+" E
   | E "-" E
   ;

字符串1 + 3 - 4不能解析为1 + (3 - 4)(1 + 3) - 4

>对该语法进行了排序,其结果应位于树的顶部。

layout L = " "*;
syntax E 
   = [0-9]+
   | E "+" E
   > E "-" E
   ;

这现在仅允许(1 + 3) - 4树。

要结束这个故事,1 + 1 + 1怎么样?可以是1 + (1 + 1)(1 + 1) + 1

这就是leftrightnon-assoc的用途。它们定义了应如何处理同一生产中的递归。

syntax E 
   = [0-9]+
   | left E "+" E
   > left E "-" E
   ;

现在将强制执行:1 + (1 + 1)

当您获取运算符优先级表时,例如,像c operator precedance table,您几乎可以从字面上复制它们。

请注意,这两个歧义消除功能并不完全相反。第一个歧义也可以通过将两个产生都放在这样的左组中来解决:

syntax E 
   = [0-9]+
   | left ( 
         E "+" E
        | E "-" E
     )
   ;

由于偏爱树的左侧,因此您现在将获得另一棵树1 + (3 - 4)。因此,它有所作为,但这完全取决于您想要什么。

更多详细信息,请访问tutor pages on disambiguation

答案 1 :(得分:1)

很好的问题。

TL; DR:

    规则优先级机制无法对非终端的替代项进行算法排序。尽管优先级声明产生的附加语法约束中涉及某种部分偏序,但是先没有一个“先尝试”规则,再到另一个规则。因此,它根本无法做到这一点。好消息是,优先级机制具有独立于任何解析算法的形式语义,只是根据上下文无关的语法规则和归约痕迹进行定义。
  • 使用模棱两可的规则进行错误恢复或“健壮的解析”是一个好主意。但是,如果此类规则太多,则解析器最终将开始显示二次或什至三次行为,并且解析后的树构建甚至可能具有更高的多项式。我相信生成的解析器算法应该具有(参数化的)错误恢复模式,而不是在语法级别进行表达。
  • 建议在解析时接受歧义,并在解析后过滤/选择树。
  • 文档中所有有关“排序”的说法都是令人误解的。歧义消除是令人困惑的术语的雷区。目前,我推荐这份SLE论文,其中包含一些定义:https://homepages.cwi.nl/~jurgenv/papers/SLE2013-1.pdf

详细信息

无法在替代方案中进行选择的优先机制

使用>运算符和leftright会在相互递归的规则(例如在表达式语言中找到)之间生成部分顺序,并且仅限于每个规则中的特定项位置:即重叠的最左递归位置和最右递归位置。层次结构中较低的规则不允许在语法上扩展为层次结构中较高的规则的“子级”。因此,在E "*" E中,如果E,则E "+" E都不能扩展为E "*" E > E "+" E

其他任何选择的E都不会选择其他约束。不,它们只是假设 other 扩展仍然有效并因此解决了歧义,所以他们根本不允许某些扩展。

限制在特定位置的原因是对于这些位置,解析器生成器可以“证明”它们将产生歧义,因此通过禁止某些嵌套来过滤这两种选择之一将不会导致其他解析错误。 (请考虑数组索引规则:E "[" E "]",该规则不应对第二个E附加约束。这是所谓的“语法安全”的歧义消除机制。

所有这些在算法上都是一个很弱的机制,特别是为相互递归的组合器/类似表达式的语言量身定制的。该机制的最终目标是确保整个表达式语言只使用1个非终结符,并且解析树的外观与抽象语法树非常相似。顺便说一下,R​​ascal通过SDF2从SDF继承了所有这些注意事项。

当前的实现实际上以某种方式无形地“分解”了语法或解析表以获得相同的效果,就好像有人会完全分解语法一样。但是,这些内在的实现非常特定于所讨论的解析算法。 GLR版本与GLL版本完全不同,GLL版本又与DataDependent版本完全不同。

解析后过滤

当然,任何树,包括由解析器生成的模糊解析森林,都可以由Rascal程序使用模式匹配,访问等操作。您可以编写任何算法来删除所需的树。但是,这需要首先构建整个森林。这可能并且通常足够快,但是有一个更快的选择。

由于树是在解析后从解析图以自底向上的方式构建的,因此我们也可以在树的构建过程中应用“重写规则”,并删除某些替代方案。

例如:

Tree amb({Tree a, *Tree others}) = amb(others) when weDoNotWant(a);
Tree amb({Tree a}) = a;  

该第一条规则将在所有树的模糊度群集上匹配,并删除所有weDoNotWant的替代项。如果只剩下一个替代方案,则第二条规则将删除集群,让我们最后一棵树“赢”。

如果您要选择其他选项:

Tree amb({Tree a, Tree b, *Tree others}) = amb({a, others} when weFindPeferable(a, b);

如果您不想使用Tree,但也应该使用更具体的非终结符(例如Statement)。

此示例模块在语法定义中使用@prefer标签来“优先”已被其他规则标记的规则,作为后解析重写规则:

https://github.com/usethesource/rascal/blob/master/src/org/rascalmpl/library/lang/sdf2/filters/PreferAvoid.rsc

遇到额外的词汇约束

除了优先级歧义消除和解析后重写之外,我们在工具包中仍然具有词汇级别的歧义消除机制:

  • `NT \关键字”-拒绝来自非终端的有限(关键字)语言
  • CC << NTNT >> CCCC !<< NTNT !>> CC遵循并超越限制(其中CC代表字符类,NT代表非终结符)

可以尝试使用除运算符优先级之外的其他方式解决歧义,特别是如果不同选择之间不同子句的长度更短/更长,!>>可以做到“最大mu或“最长匹配”的东西。所以我在大声思考:

lexical C = A? B?;

其中A是一个词汇选择,而B是另一个词汇选择。如果对!>>有适当的A限制,对!<<B限制,那么可能会欺骗语法,使其始终希望将所有字符都放入A,除非它们不适合A作为一种语言,在这种情况下,它们将默认为B

明显/讨厌的建议

请更加认真地思考一个明确而又简单的语法。

有时这意味着要提取语法中的更多句子,并避免使用语法对树进行“类型检查”。最好过分逼近该语言的语法,然后使用(静态)语义分析(在较简单的树上)来获取所需的内容,而不是盯着复杂的歧义语法。

一个典型示例:仅在开始处带有声明的C块要比在任何地方都允许声明的C块更难定义。对于C90模式,您要做的就是标记声明,该声明不在块的开头。

这个例子

lexical WordChar = [0-9A-Za-z] ;
lexical Digit = [0-9] ;
lexical WordInitialDigit = Digit WordChar* !>> WordChar;
lexical WordAny = WordChar+ !>> WordChar;
syntax Word =
    WordInitialDigit
    | [0-9] !<< WordAny // this would help!
    ;

总结

很好的问题,感谢您的耐心配合。希望这会有所帮助!