使用 Superpower 解析简单的文本语法

Parsing a simple text grammar with Superpower

我正在尝试使用 Superpower 创建解析器。我已经查看了我在 repo 中找到的示例,但它们有点难以理解,至少对于像我这样的初学者来说 :) 所以我提出了这个小挑战。

我发明了一个非常基本的语法只是为了学习。我想到了一种电梯,它会按照一系列指令上楼、下楼并等待。

示例:

(UP 100),
(DOWN 200),
(DOWN 100),
(DOWN @1),
(UP @3),
(WAIT),
(UP 300)

如您所见,它由逗号分隔的移动动词列表组成,例如电梯。

我真的很想学习如何为这个语法创建一个基于标记的解析器作为开始,以便了解如何使用 SuperPower。

编写任何 Superpower 解析器的第 1 步 是弄清楚令牌类型是什么。你有这样的东西:

// ECL - Elevator Control Language ;-)
enum EclToken {
    LParen,
    RParen,
    UpKeyword,
    DownKeyword,
    WaitKeyword,
    AtSymbol,
    Number,
    Comma
}

第2步,写一个Tokenizer<EclToken>。这是 Superpower v1 留下的直接编程任务 - 没有多少可以依靠的助手,你只需要编写代码 the examples.

分词器获取输入字符串,去除空格,并计算出分词序列是什么。

对于您的示例输入,第一行将是:

// (UP 100),
LParen, UpKeyword, Number, RParen, Comma

对于像 Number 这样包含内容的标记,与 Result<EclToken> 关联的跨度将指向与标记对应的输入字符串部分。在这一行中,数字将是 TextSpan 覆盖 100.

第3步是想把输入解析成。对于具有嵌套表达式的编程语言,这通常是 AST。对于 ECL 示例,它非常简单,因此您可以将其缩减为:

struct ElevatorCommand {        
    public int Distance; // + or -
    public bool IsRelative;
}

步骤 4,解析器。这通常嵌入在静态 class 中。解析器的工作是从更简单的结果(数字、运动)构建更复杂的结果(这里是 ElevatorCommand[])。

这是 Superpower 承担重任的地方,尤其是在预期和错误方面。

static class EclParser 
{
    static TokenListParser<EclToken, int> Number =
        Token.EqualTo(EclToken.Number).Apply(Numerics.IntegerInt32);
}

我们做的第一件事是定义数字解析器;这个将内置 TextParser<int> 应用于 EclToken.Number 范围的内容。

您可以在 this example.

中看到更多解析机制

更多的线索来帮助你找到方法(没有语法检查,更不用说compiled/tested):

    static TokenListParser<EclToken, ElevatorCommand> Up =
        from _ in Token.EqualTo(EclToken.UpKeyword)
        from distance in Number
        select new ElevatorCommand {
            Distance = distance,
            IsRelative = false
        };

    static TokenListParser<EclToken, ElevatorCommand> Command =
        from lp in Token.EqualTo(EclToken.LParen)
        from command in Up // .Or(Down).Or(Wait)
        from rp in Token.EqualTo(EclToken.RParen)
        select command;

    static TokenListParser<EclToken, ElevatorCommand[]> Commands =
        Command.ManyDelimitedBy(Token.EqualTo(EclToken.Comma));
}

Commands 是可以应用于输入的完整解析器。

最好逐步构建解析器,测试每个较小的解析器对它们预期解析的输入块。

好的,我终于搞定了。这并不难 @Nicholas Blumhardt 的指导:)

我创建了 a project in GitHub 来说明场景。由于 类 对于 post 来说很大,我正在链接到文件: