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

8
我正在尝试使用Superpower创建一个解析器。我已经查看了在存储库中找到的示例,但对于像我这样的初学者来说,它们有点难以理解:)所以我提出了这个小挑战。
我发明了一个非常基本的语法来学习。我想到了一个电梯,它遵循一系列指令向上、向下和等待。
例如:
(UP 100),
(DOWN 200),
(DOWN 100),
(DOWN @1),
(UP @3),
(WAIT),
(UP 300)

正文翻译:如你所见,它由一个逗号分隔的动词列表组成,例如移动电梯。
  • 这些动词是UPDOWNWAIT
  • 每个动词都被括在括号中:( )
  • UPDOWN需要一个绝对数字或一个相对数字,表示电梯应该移动到哪一层。相对楼层数字前面带有@。
  • WAIT不接受任何数字,因为它会暂停电梯一段时间。

我真的很想学习如何创建基于令牌的解析器来理解如何使用SuperPower。


这里有一个关于程序开发的内容需要翻译,链接在此 this。除了一份 readme 之外,文档并不多,但是他们提供了一些示例和测试,这些测试展示了语法构建的方法。 - Cee McSharpface
@PatrickArtner 感谢您让StackOverflow变得更加伟大。 - SuperJMN
@dlatikay 谢谢,我知道那些示例,但我仍然无法处理它。这就是为什么我创建了这个愚蠢的示例:只是为了学习而已。如果您能够使用现有文档创建我所要求的解析器,请分享一下。 - SuperJMN
@PatrickArtner 由于这是一个相对不知名的库的新标签,今天它并不针对StackOverflow中99.9%的用户,因此它旨在由一小部分用户(该领域的专家)回答此问题,他们通过在SO中回答此问题将帮助未来的用户解决常见情况。此外,我认为任何使用该库的人都有足够的上下文来回答。顺便说一句,该库的创建者指向了此标签(https://github.com/datalust/superpower#getting-help)。 - SuperJMN
@PatrickArtner 您可以放心,我已经在他们的 Gitter 房间里联系了相关人员,以引起正在使用该库的人们的注意。StackOverflow 目前是许多社区的中心,甚至包括那些几乎没有人知道的新社区。让它们自己解决吧。如果您不了解某个主题,就跳过这个问题,就像我们大多数人一样。 - SuperJMN
2个回答

14

编写Superpower解析器的第一步是弄清楚令牌种类。你可以像这样:

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

第二步,编写一个 Tokenizer<EclToken>。这是由Superpower v1直接编程任务-没有太多可依靠的帮手,您只需要像示例中一样编写代码。

该分词器将输入字符串剥离空格,并确定令牌序列是什么。

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

// (UP 100),
LParen, UpKeyword, Number, RParen, Comma
对于像Number这样包含内容的标记,与Result<EclToken>相关联的跨度将指向对应于该标记的输入字符串部分。在这行代码中,数字将是一个TextSpan,覆盖100
第三步是确定您要将输入解析为什么。对于具有嵌套表达式的编程语言,通常是AST。在ECL示例的情况下,它非常简单,因此您可能会将其缩减为:
struct ElevatorCommand {        
    public int Distance; // + or -
    public bool IsRelative;
}

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

这就是Superpower发挥作用的地方,特别是在期望和错误方面。

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

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

您可以在这个示例中看到更多的解析机制。

还有一些提示,以帮助您找到正确的方法(未经过语法检查,更不用说编译/测试了):

    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是一个完成的解析器,可以应用到输入中。

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


好的,我想我明白了。现在,我正在尝试创建解析器 :) 目前,我有一个疑问。您仅使用距离和是否为相对命令来建模ElevatorCommand。但是等待命令应该如何表示? - SuperJMN
1
来自吹毛求疵者的小提醒,Superpower中的 p 应该是小写的 :-) - Nicholas Blumhardt
1
太酷了!抱歉,我觉得这是我的PascalCase狂热 :-P - SuperJMN

3

好的,我终于成功了。在@Nicholas Blumhardt的指导下,这并不难 :)

我在GitHub上创建了一个项目来说明情况。由于类太长了,不适合发帖子,所以我链接到文件:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接