C# 用于平衡嵌套圆括号的Superpower解析器
我正在努力为下面的部分输入集(嵌套的、带“|”分隔符的平衡圆括号)设计一个解析器 任意文本可以进入参数内,包括空格、其他标记和“()”。这里只有“|”和“(”,“)”有特殊含义(换行符也会结束序列)。为了有效,每个平衡的括号组必须有一个“|”和至少一个不是“(”或“)”的字符 理想情况下,解析器会将每个输入拆分为一个列表,其中包含元素(终端)字符串或字符串数组,如下所示: 有效期:C# 用于平衡嵌套圆括号的Superpower解析器,c#,parsing,superpower,C#,Parsing,Superpower,我正在努力为下面的部分输入集(嵌套的、带“|”分隔符的平衡圆括号)设计一个解析器 任意文本可以进入参数内,包括空格、其他标记和“()”。这里只有“|”和“(”,“)”有特殊含义(换行符也会结束序列)。为了有效,每个平衡的括号组必须有一个“|”和至少一个不是“(”或“)”的字符 理想情况下,解析器会将每个输入拆分为一个列表,其中包含元素(终端)字符串或字符串数组,如下所示: 有效期: (a|) -> { "a", "" } (a | b)
(a|) -> { "a", "" }
(a | b) -> { "a", "b" }
(a | b.c()) -> { "a", "b.c()" }
(aa | bb cc ) -> { "aa" "bb cc" }
(a | b | c #dd) -> { "a", "b", "c #dd"}
((a | b) | $c) -> { { "a", "b" }, "$c" }
((a | b) | (c | d)) -> { { "a", "b" }, { "c", "d" } }
(((a | b) | c) | d) -> { { { "a", "b" }, "c" }, "d" }
...
无效/被忽略:
()
())
(()
(|)
(|())
(.)
(())
(()|())
(abc)
(a bc)
(a.bc())
...
我的代币(用于此处)如下所示:
公共枚举令牌
{
[令牌(示例=“(”)]
阿尔帕伦,
[令牌(示例=”)“”]
帕伦,
[令牌(示例=“|”)]
管
[令牌(Description=“其他一切”)]
一串
}
这很棘手,主要是因为您需要保留空白,但我能够找到满足您需要的解析器。首先,我必须稍微更改您的标记
枚举:
public enum Tokens
{
None,
String,
Number,
[Token(Example = "()")]
OpenCloseParen,
[Token(Example = "(")]
LParen,
[Token(Example = ")")]
RParen,
[Token(Example = "#")]
Hash,
[Token(Example = "$")]
Dollar,
[Token(Example = "|")]
Pipe,
[Token(Example = ".")]
Dot,
[Token(Example = " ")]
Whitespace,
}
接下来,我们可以构建以下标记器:
var tokenizer = new TokenizerBuilder<Tokens>()
.Match(Span.EqualTo("()"), Tokens.OpenCloseParen)
.Match(Character.EqualTo('('), Tokens.LParen)
.Match(Character.EqualTo(')'), Tokens.RParen)
.Match(Character.EqualTo('#'), Tokens.Hash)
.Match(Character.EqualTo('$'), Tokens.Dollar)
.Match(Character.EqualTo('.'), Tokens.Dot)
.Match(Character.EqualTo('|'), Tokens.Pipe)
.Match(Character.EqualTo(' '), Tokens.Whitespace)
.Match(Span.MatchedBy(Character.AnyChar), Tokens.String)
.Match(Numerics.Natural, Tokens.Number)
.Build();
然后我们创建解析器:
public static class MyParsers
{
/// <summary>
/// Parses any whitespace (if any) and returns a resulting string
/// </summary>
public readonly static TokenListParser<Tokens, string> OptionalWhitespace =
from chars in Token.EqualTo(Tokens.Whitespace).Many().OptionalOrDefault()
select chars == null ? "" : new string(' ', chars.Length);
/// <summary>
/// Parses a valid text expression
/// e.g. "abc", "a.c()", "$c", etc.
/// </summary>
public readonly static TokenListParser<Tokens, Node> TextExpression =
from tokens in
Token.EqualTo(Tokens.OpenCloseParen)
.Or(Token.EqualTo(Tokens.Hash))
.Or(Token.EqualTo(Tokens.Dollar))
.Or(Token.EqualTo(Tokens.Dot))
.Or(Token.EqualTo(Tokens.Number))
.Or(Token.EqualTo(Tokens.String))
.Or(Token.EqualTo(Tokens.Whitespace))
.Many()
// if this side of the pipe is all whitespace, return null
select (Node) (
tokens.All(x => x.ToStringValue() == " ")
? null
: new TextNode {
Value = string.Join("", tokens.Select(t => t.ToStringValue())).Trim()
}
);
/// <summary>
/// Parses a full expression that may contain text expressions or nested sub-expressions
/// e.g. "(a | b)", "( (a.c() | b) | (123 | c) )", etc.
/// </summary>
public readonly static TokenListParser<Tokens, Node> Expression =
from leadWs in OptionalWhitespace
from lp in Token.EqualTo(Tokens.LParen)
from nodes in TextExpression
.Or(Parse.Ref(() => Expression))
.ManyDelimitedBy(Token.EqualTo(Tokens.Pipe))
.OptionalOrDefault()
from rp in Token.EqualTo(Tokens.RParen)
from trailWs in OptionalWhitespace
where nodes.Length > 1 && nodes.Any(node => node != null) // has to have at least two sides and one has to be non-null
select (Node)new Expression {
Nodes = nodes.Select(node => node ?? new TextNode { Value = "" }).ToArray()
};
}
这适用于几乎所有的测试用例,除了像这样的(()|())
,其中打开/关闭参数是管道两侧的值。也许还有一种更好的方法来进行一些解析,因为我刚刚习惯于增强自己的能力,但我认为这是一个很好的起点,因此您可以优化它和/或将所有的边缘案例集成到其中
编辑
是空白把一切都搞砸了。我必须在表达式
解析器中添加更多的空格检查,还必须添加一个条件来检查非空的文本表达式
,然后还要检查可能为空的表达式。这是为了处理管道一侧空白的情况。以下是工作解析器:
public readonly static TokenListParser<Tokens, Node> Expression =
from _1 in OptionalWhitespace
from lp in Token.EqualTo(Tokens.LParen)
from _2 in OptionalWhitespace
from nodes in
TextExpression.Where(node => node != null) // check for actual text node first
.Or(Expression)
.Or(TextExpression) // then check to see if it's empty
.ManyDelimitedBy(Token.EqualTo(Tokens.Pipe))
from _3 in OptionalWhitespace
from rp in Token.EqualTo(Tokens.RParen)
from _4 in OptionalWhitespace
where nodes.Length > 1 && nodes.Any(node => node != null) // has to have at least two sides and one has to be non-null
select (Node)new Expression {
Nodes = nodes.Select(node => node ?? new TextNode { Value = "" }).ToArray()
};
public只读静态TokenListParser表达式=
来自可选空白中的_1
来自Token.EqualTo(Tokens.LParen)中的lp
来自可选空白中的_2
从中的节点
TextExpression.Where(node=>node!=null)//首先检查实际的文本节点
.或(表达)
.或(TextExpression)//然后检查它是否为空
.ManyDelimitedBy(Token.EqualTo(Tokens.Pipe))
来自可选空白中的_3
来自Token.EqualTo(Tokens.RParen)中的rp
来自可选空白中的_4
其中nodes.Length>1&&nodes.Any(node=>node!=null)//必须至少有两条边,且其中一条必须为非null
选择(节点)新表达式{
节点=节点。选择(节点=>节点??新文本节点{Value=”“})。ToArray()
};
您已经编写了标记器了吗?是的(您可以在上面的源代码中看到标记…)谢谢您想要什么样的输出?我假设您需要将每个paren组解析为一个对象,该对象包含有关内部内容的信息,但是您能否显示希望将数据解析为的特定类?那会有帮助,所以你想要原始字符串?你能用你正在寻找的一些ouptut的例子来更新这个问题吗?我已经根据你的建议@jtate在上面添加了输出。理想情况下,解析器会将每个输入拆分为一个列表,其中的元素可以是一个(终端)字符串,也可以是一个子字符串数组(递归),这要感谢@jtate。它适用于某些输入,但似乎无法解析嵌套的参数,例如((a | b)| c)
。您可以找到完整的类,它对嵌套输入非常有效,因为这是我测试的主要内容之一。当你说它没有解析它们时,你是说TryParse
失败了还是说输出不正确?奇怪的是,它现在对我也不起作用了。我来看看,看看。虽然我现在无法访问我的源代码,但我将使用您提供的源代码。当我创建答案时,可能在翻译过程中丢失了一些东西。让我知道你认为这很好,非常感谢(接受/投票)。如果您有时间,也请检查一下。。。
string input = "(a b | c.())";
var tokens = tokenizer.Tokenize(input);
var result = MyParsers.Expression.TryParse(tokens);
if (result.HasValue)
{
// input is valid
var expression = (Expression)result.Value;
// do what you need with it here, i.e. loop through the nodes, output the text, etc.
}
else
{
// not valid
}
public readonly static TokenListParser<Tokens, Node> Expression =
from _1 in OptionalWhitespace
from lp in Token.EqualTo(Tokens.LParen)
from _2 in OptionalWhitespace
from nodes in
TextExpression.Where(node => node != null) // check for actual text node first
.Or(Expression)
.Or(TextExpression) // then check to see if it's empty
.ManyDelimitedBy(Token.EqualTo(Tokens.Pipe))
from _3 in OptionalWhitespace
from rp in Token.EqualTo(Tokens.RParen)
from _4 in OptionalWhitespace
where nodes.Length > 1 && nodes.Any(node => node != null) // has to have at least two sides and one has to be non-null
select (Node)new Expression {
Nodes = nodes.Select(node => node ?? new TextNode { Value = "" }).ToArray()
};