Pidgin:一个轻量级、快速且灵活的 C# 解析库

推荐一个专为 C# 开发者设计的轻量级、快速且灵活的解析库。

01
项目简介

Pidgin是基于C#的开源项目,是一个解析组合器库,提供了一个高级别的声明性工具来构建解析器,使得编写解析器变得简单而直观。
1、轻量化与高效

Pidgin专注于提供轻量级的解决方案,旨在减少内存占用和提高解析速度。通过精心设计的数据结构和算法,Pidgin 能够在不牺牲功能的前提下实现高效的解析。
2、灵活性
Pidgin 支持解析各种复杂的数据格式,不仅限于文本数据。由于其能够处理任意类型的输入令牌(tokens),Pidgin 可以用于解析二进制协议、标记化输入等多种场景。
3、易于使用
与正则表达式相比,Pidgin 提供了更强大的解析能力,而与 ANTLR 等解析生成器相比,它又更简单易用。
Pidgin 的 API 设计直观,允许开发者以声明性的方式定义语法规则,而无需编写复杂的代码。

02
解析器分类

1、原始解析器
Any:表示一个使用单个字符并返回该字符的解析器。
Char:指定特定字符并返回该字符。如果遇到其他字符,则失败。
Digit:解析并返回单个数字字符。
String:解析并返回特定字符串。
Return:返回给定值。
// 使用一个解析器Char(‘a’)来检查输入字符串”a”是否可以被解析为单个字符’a’。
// 如果可以,Assert.AreEqual将验证解析结果是否为’a’。
Parser parser = Char(‘a’);
Assert.AreEqual(‘a’, parser.ParseOrThrow(“a”));

// 使用Digit解析器来检查输入字符串”3″是否可以被解析为一个数字字符,并期望得到整数形式的’3’(尽管这里实际上得到的是字符’3’)。
// 注意:如果目标是得到整数类型的3,而不是字符’3’,则示例可能需要调整或明确说明转换。
// 但按照代码字面意思,Assert.AreEqual验证的是字符’3’。
Assert.AreEqual(‘3’, Digit.ParseOrThrow(“3”));

// 创建一个解析器String(“foo”),该解析器尝试从输入字符串中提取子字符串”foo”。
// 如果输入字符串确实是”foo”,则Assert.AreEqual将验证解析结果是否为字符串”foo”。
Parser parser = String(“foo”);
Assert.AreEqual(“foo”, parser.ParseOrThrow(“foo”));

// 使用Return(3)创建一个解析器,该解析器不依赖于输入字符串,而是直接返回整数3。
// 这意味着无论输入字符串是什么,解析结果都将是3。
// 因此,即使输入是”foo”,Assert.AreEqual仍然验证解析结果是否为整数3。
Parser parser = Return(3);
Assert.AreEqual(3, parser.ParseOrThrow(“foo”));
2、排序解析器
Then:顺序执行两个解析器,仅保留第二个的结果。
Before:顺序执行两个解析器,但仅保留第一个的结果。
Map:对解析器结果进行转换,不改变解析流程。
Bind:基于第一个解析器的结果选择并执行第二个解析器。
// 示例1: 使用Then(假设它保留第二个解析器的结果)
Parser parser1 = String(“foo”);
Parser parser2 = String(“bar”);
// 注意:这里可能需要一个特殊的Then实现,因为标准的Then可能不直接丢弃第一个结果
// 假设有一个名为ThenThatOnlyKeepsSecond的扩展方法
Parser sequencedParser = parser1.ThenThatOnlyKeepsSecond(parser2); // 假设的方法名
Assert.AreEqual(“bar”, sequencedParser.ParseOrThrow(“foobar”));

// 示例2: 使用Before(假设它存在且保留第一个解析器的结果)
// 注意:这通常不是标准库中的操作,但我们可以假设它存在
Parser beforeParser = parser1.Before(parser2); // 假设的Before实现
Assert.AreEqual(“foo”, beforeParser.ParseOrThrow(“foobar”));

// 示例3: 使用Map来合并两个解析器的结果
// 注意:这里我们假设有一个能够处理两个解析器的Map实现
Parser mappedParser = Map((foo, bar) => bar + foo, parser1, parser2); // 假设的Map实现
Assert.AreEqual(“barfoo”, mappedParser.ParseOrThrow(“foobar”));

// 示例4: Bind的使用(但这里给出的例子并不典型)
// 假设我们想要根据输入的第一个字符来构造一个解析器,但这里我们简化它
Parser anyCharParser = AnyChar(); // 假设的AnyChar解析器
Parser specificCharParser = anyCharParser.Bind(c => Char(c));
// 但为了符合您的示例,我们假设它工作如下(实际上这并不实用)
Assert.AreEqual(‘a’, specificCharParser.ParseOrThrow(“a”)); // 这将总是返回输入的第一个字符

3、选择替代方案
Or:表示可以解析两个备选方案之一的解析器。
OneOf:等同于Or,只不过它接受可变数量的参数。
// 定义一个能够解析字符串 “foo” 或 “bar” 的解析器
// 使用 Or 方法组合两个 String 解析器
Parser parser = String(“foo”).Or(String(“bar”));

// 测试解析器是否能正确解析 “foo”
Assert.AreEqual(“foo”, parser.ParseOrThrow(“foo”)); // 应该成功解析并返回 “foo”

// 测试解析器是否能正确解析 “bar”
Assert.AreEqual(“bar”, parser.ParseOrThrow(“bar”)); // 应该成功解析并返回 “bar”

// 用于创建一个能够解析多个选项中任意一个的解析器
Parser parser = OneOf(String(“foo”), String(“bar”));

// 测试解析器是否能正确解析 “foo”
Assert.AreEqual(“foo”, parser.ParseOrThrow(“foo”)); // 应该成功解析并返回 “foo”

// 测试解析器是否能正确解析 “bar”
Assert.AreEqual(“bar”, parser.ParseOrThrow(“bar”)); // 应该成功解析并返回 “bar”
4、LINQ语法
Parser parser =
from c in Any
from c2 in Char(c)
select c2
5、递归语法
Parser expr = null;
Parser parenthesised = Char(‘(‘)
.Then(Rec(() => expr))
.Before(Char(‘)’));
expr = Digit.Or(parenthesised);
Assert.AreEqual(‘1’, expr.ParseOrThrow(“1”));
Assert.AreEqual(‘1’, expr.ParseOrThrow(“(1)”));
Assert.AreEqual(‘1’, expr.ParseOrThrow(“(((1)))”));

03
与解析工具对比

Pidgin vs Sprache
Sprache 是另一个 C# 解析组合器库,但与 Pidgin 相比,Sprache 存在一些限制:
输入类型:Sprache 只支持字符串输入,而 Pidgin 支持任意类型的输入令牌。
性能:Pidgin 在速度和内存分配方面优于 Sprache。
文档覆盖:Pidgin 提供了更全面的文档覆盖。
Pidgin vs FParsec
FParsec 是一个为 F# 设计的解析组合器库,与 Pidgin 相比,它有以下不同:
语言支持:FParsec 是 F# 库,从 C# 调用可能不太方便。Pidgin 是为 C# 设计的。
输入类型:FParsec 仅支持字符输入流,而 Pidgin 支持任意类型的输入令牌。
性能:虽然 FParsec 在某些情况下更快,但 Pidgin 正在不断改进其性能。

04
项目地址

https://github.com/benjamin-hodgson/Pidgin

声明:文中观点不代表本站立场。本文传送门:http://eyangzhen.com/421888.html

(0)
联系我们
联系我们
分享本页
返回顶部