65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Parakeet 在 C# 中进行文本解析入门

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2024 年 3 月 19 日

MIT

4分钟阅读

viewsIcon

9895

介绍使用 Parakeet 解析库在 C# 中构建递归下降解析器

使用 Parakeet 在 C# 中解析文本

Parakeet 是一个开源的 C# 递归下降解析库,它使用运算符重载,可以方便地以可读的格式声明语法。它试图结合解析器生成器工具和手工编写的解析器的优点。

Parakeet 特别适用于解析非平凡的编程语言,并支持错误恢复、标记化和解析树生成。 Plato 语言实现正在使用 Parakeet 进行构建。

为了帮助您入门,以下是一个解析 CSV 文件的语法示例

public class CsvGrammar : BaseCommonGrammar
{
    public static readonly CsvGrammar Instance = new CsvGrammar();
    public override Rule StartRule => File;

    public Rule StringChar => Named(AnyChar.Except('"') | "\"\"");
    public Rule String => Node(DoubleQuotedString(StringChar));
    public Rule Text => Node(AnyChar.Except(",\n\r\"").OneOrMore());
    public Rule Field => Node(Text | String);
    public Rule Row => Node(Field.ZeroOrMore() + Optional('\r') + '\n');
    public Rule File => Node(Row.ZeroOrMore());
}

解析规则

解析器定义为相互关联的一组解析规则。规则使用“组合子”组合在一起。在某些上下文中,单个规则也可能被称为“解析器”。规则是派生自 Rule 的类的实例,该类提供一个成员函数

ParserState Match(ParserState state)

如果规则在 ParserState 表示的位置匹配输入,则将返回一个新的 ParserState 实例,否则该函数返回 null

规则组合子

规则组合子是一个函数,它从其他规则创建规则,类似于 PEG 语法的核心操作。例如:ChoiceSequenceZeroOrMoreOneOrMoreNotAt 等。

在 Parakeet 中,有几种方法可以从其他规则创建规则

  • 创建组合子类的实例(即使用 new
  • 调用 Rule 上的某个扩展方法(例如 Optional()NotAt() 等)
  • 使用运算符重载
    • + => Sequence
    • | => Choice
    • ! => NotAt

有关规则组合子的更多信息,请参阅 维基百科上关于解析表达式语法的文章

ParserState - 规则匹配的输入和输出

ParserState 是一个不可变的类,它包含

  • 指向输入的指针 - 一个 string 结合查找表,用于快速从索引确定行和列)
  • 从输入 string 开始的偏移量
  • 指向解析节点链表的指针
  • 指向错误链表的指针

ParserState 实例的字段是

public class ParserState 
{
    public readonly ParserInput Input;
    public readonly int Position;
    public readonly ParserNode Node;
    public readonly ParserError LastError;
    ...
}

定义语法

语法是解析规则的集合,它们共同描述了如何解析输入 string。 Parakeet 语法是一个派生自 Grammar 类的类,该类提供了起始规则的重写定义,以及一个可选的空格规则。

  • abstract Rule StartRule {get;}
  • virtual Rule WS {get;}

大多数规则被定义为计算属性,但也可以是函数或字段。这取决于程序员。

直接与语法中的属性关联的规则通常是 Node 规则或 Named 规则。 Named 规则仅返回匹配子规则的结果,并与属性名称相关联,有助于语法调试和诊断。

节点规则

Node 规则类似于 NamedRule ,它也有一个名称,但如果匹配成功,则返回一个 ParserState ,该 ParserState 至少有两个新的 ParserNode 实例添加到节点链表中。

一个节点指向匹配的开始,另一个节点指向匹配的结束。
ParserNode 如下所示

public class ParserNode
{
    public readonly ParserRange Range;
    public readonly string Name;
    public readonly ParserNode Previous;
    ...
}

ParserNode 实例的链表可以使用 ParseTreeNode ParserNode.ToParseTree() 方法转换为解析树。

使用 OnFail 规则生成解析错误

解析规则可能不匹配成功有几种方式

  • 返回 null - 这表示正常匹配失败
  • 未到达输入末尾:ParserState.AtEnd == false
  • 累积一个或多个 ParserError 实例。

一个称为 OnFail 的特殊规则用于生成 ParserError 实例。 OnFail 规则应出现在 SequenceRule 的子项中。

OnFail 规则表示,如果前面的子规则成功,则后面规则中的任何失败都将生成 ParserError。可以选择提供恢复规则作为参数,允许解析器前进到下一个位置,例如语句结束之后,并尝试继续。

以下是 Plato 语法中的一个片段,演示了错误处理和恢复是如何发生的。

public Rule AdvanceOnFail => 
    OnFail(Token.RepeatUntilPast(EOS | "}"));

public Rule IfStatement =>
    Node(Keyword("if") + AdvanceOnFail + ParenthesizedExpression 
       + Statement + ElseClause.Optional());

IfStatement 规则表示,如果匹配到关键字 if,则后面必须跟着一个用括号括起来的表达式,然后是一个有效的语句,然后可以选择性地跟着一个 else 子句。
如果在关键字 if 之后发生失败,那么我们就知道发生了解析错误。解析器将消耗令牌,直到它越过语句结束 (EOS) 标记 (;) 或右花括号 (})。 IfStatement 规则将返回一个有效的 ParserState,但会在错误链表的前面添加一个新的 ParserError

结束语

了解 Parakeet 的最佳方法是阅读测试和示例语法,并尝试一下。源代码和几个示例语法托管在 Github 上,地址为 https://github.com/ara3d/parakeet

历史

  • 2024 年 3 月 19 日:初始版本
© . All rights reserved.