【问题标题】:Can this grammar be parsed by both predictive recursive descent parser and the parser with backtracking预测递归下降解析器和回溯解析器都可以解析这个语法吗
【发布时间】:2018-03-25 22:21:08
【问题描述】:

我的语法如下:

IdentifierName ::
    IdentifierStart
    IdentifierName IdentifierPart

使用git这个词应该被解析成下面的解析树:

                 IdentifierName
                /              \
        IdentifierName IdentifierPart
       /              \         |
IdentifierName IdentifierPart  't'
       |                |
IdentiiferStart        'i'
       |
      'g'

我想写一个递归下降算法来做到这一点。现在我有两个选择,要么编写带有 backtracking 的递归下降解析器,要么编写 predictive 递归下降解析器。这两个都是表驱动解析器。但是,我已经读过,对于带有回溯的递归下降,我需要消除左递归。问题中的语法似乎是递归的。

那么我是否需要重构语法或使用预测算法?

【问题讨论】:

    标签: parsing recursive-descent left-recursion compiler-construction


    【解决方案1】:

    是的,语法是左递归的,因此不是 LL。回溯和预测 LL 解析器都不能处理这样的语法。因此,您要么需要更改语法,要么使用其他算法,例如 LR 解析算法。

    注意这个文法是正则的,所以实际上可以翻译成正则表达式或者直接翻译成有限自动机。

    在编写 JavaScript 的实际实现时,词法规则(例如这个)将在词法分析器中处理,而只有其他规则将由解析器处理(但是那些指定的左递归也是如此,所以它们d 也必须重写才能被 LL 解析器解析)。

    【讨论】:

    • @AngularInDepth.com 我如何知道语法是正则的:正则语法是每个产生式至多包含一个非终结符且终结符始终是产生式的最左侧部分或总是最右边的部分。这里就是这种情况。 TS 解析器是如何实现的:他们使用 while 循环而不是递归来解析列表(在规范的语法中使用左递归)。在扩展的 BNF 表示法中,这相当于使用重复运算符,所以你可以说他们将 FooList :: FooList ',' Foo 重写为 FooList :: Foo ("," Foo)*,然后实现了。
    • PS:如果你看tryParse函数,它实现了回溯,所以解析器会回溯。
    • @AngularInDepth.com 是的,没错。我不是在谈论特定的语法,而是基本上每个名称中都有“列表”的语法,包括 StatementList(尽管我的 FooList 示例使用逗号分隔的列表,所以像 ElementList、ArgumentList 或 FormalParameterList 这样的东西会更接近匹配) .请注意,在 TS 解析器中,所有不带分隔符的列表都使用 parseList 函数,所有带分隔符的列表都使用 parseDelimitedList 函数,所以它们都在一个地方(嗯,两个地方)。
    • 另一个使用左递归的地方是左关联中缀运算符(AdditiveExpression、RelationalExpression 等)。这些由parseBinaryExpressionOrHigher 函数处理,该函数也使用while-loops(或者更确切地说是它调用的parseBinaryExpressionRest 函数。PS:在查看规范中的语法时,您可能想要进入附录A,其中将整个语法集中在一个位置,而不是在主文档中零零散散地查看它。
    • @AngularInDepth.com 乍一看,它似乎在使用parseBinaryExpressionRest 中的优先级攀升。不,我没有推特。
    【解决方案2】:

    这往往是词法分析器的工作,而不是解析器的工作。通常,词法分析器一次处理一个字符,在一个循环中,使用一个大的 switch 语句(或等效的“初始字符表”,如果数据驱动。)

    // near the end of the big "switch (ch) {" statement ...
    default:
        if (!isIdentifierStart(chInit))
            {
            log(Severity.ERROR, ILLEGAL_CHAR, new Object[]{quotedChar(chInit)},
                    lInitPos, source.getPosition());
            }
    // fall through
    case 'A':case 'B':case 'C':case 'D':case 'E':case 'F':case 'G':
    case 'H':case 'I':case 'J':case 'K':case 'L':case 'M':case 'N':
    case 'O':case 'P':case 'Q':case 'R':case 'S':case 'T':case 'U':
    case 'V':case 'W':case 'X':case 'Y':case 'Z':
    case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':case 'g':
    case 'h':case 'i':case 'j':case 'k':case 'l':case 'm':case 'n':
    case 'o':case 'p':case 'q':case 'r':case 's':case 't':case 'u':
    case 'v':case 'w':case 'x':case 'y':case 'z':
    case '_':
        {
        while (source.hasNext())
            {
            if (!isIdentifierPart(nextChar()))
                {
                source.rewind();
                break;
                }
            }
        String name = source.toString(lInitPos, source.getPosition());
        // ...
        }
    

    如果手动构建,我发现拥有一个专用的词法分析器(从字符流生成标记)和解析器(从标记流生成 AST)比尝试将它们组合成一个解析器要容易得多。

    【讨论】:

      猜你喜欢
      • 2015-06-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-07
      • 1970-01-01
      • 2011-01-26
      • 2012-05-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多