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





5.00/5 (6投票s)
介绍使用 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 语法的核心操作。例如:Choice
、Sequence
、ZeroOrMore
、OneOrMore
、NotAt
等。
在 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 日:初始版本