我使用Sprache来解析文件的一部分,如下所示:
OneThing=Foo
AnotherThing=Bar
YetAnotherThing=Baz这三行都是强制的,但它们可以按任意顺序出现。我为各行提供了解析器,如下所示:
public static readonly Parser<string> OneThing = (
from open in Parse.String("OneThing=")
from rest in Parse.AnyChar.Except(Parse.LineTerminator).Many().Text()
from newLine in Parse.LineEnd
select rest
);我将它们组合在一起来解析整个部分,如下所示:
public static readonly Parser<MyClass> Section = (
from oneThing in SectionGrammar.OneThing
from anaotherThing in SectionGrammar.AnotherThing
from yetAnotherThing in SectionGrammar.YetAnotherThing
select new MyClass(oneThing, anotherThing, yetAnotherThing)
);但是,只有当这些行以OneThing、AnotherThing、YetAnotherThing的顺序出现时,这种方法才有效。我如何才能改变这一点,以允许行以任何顺序出现,但仍然强制每行都应该出现一次?
任何帮助都非常感谢!谢谢
发布于 2020-05-28 17:53:34
我对Sprache很天真,但一种可能比较冗长的方法是为每行选择每个选项的元组,然后在最后的select中过滤元组数组。类似于:
public static readonly Parser<MyClass> Section = (
select a from (from oneThing in SectionGrammar.OneThing select Tuple.Create(oneThing, null, null))
.Or(from anotherThing in SectionGrammar.AnotherThing select Tuple.Create(null, anotherThing, null))
.Or(from yetAnotherThing in SectionGrammar.YetAnotherThing select Tuple.Create(null, null, yetAnotherThing))
select b from (from oneThing in SectionGrammar.OneThing select Tuple.Create(oneThing, null, null))
.Or(from anotherThing in SectionGrammar.AnotherThing select Tuple.Create(null, anotherThing, null))
.Or(from yetAnotherThing in SectionGrammar.YetAnotherThing select Tuple.Create(null, null, yetAnotherThing))
select c from (from oneThing in SectionGrammar.OneThing select Tuple.Create(oneThing, null, null))
.Or(from anotherThing in SectionGrammar.AnotherThing select Tuple.Create(null, anotherThing, null))
.Or(from yetAnotherThing in SectionGrammar.YetAnotherThing select Tuple.Create(null, null, yetAnotherThing))
select new MyClass(
new[] { a, b, c }.Where(i => i.Item1 != null).Select(i => i.Item1).First(),
new[] { a, b, c }.Where(i => i.Item2 != null).Select(i => i.Item2).First(),
new[] { a, b, c }.Where(i => i.Item3 != null).Select(i => i.Item3).First()
));但我觉得应该有更好的方法。如果你告诉我有20行代码有独特的解析,并且可以有不同的顺序,那么上述代码的可扩展性也不是很好。
发布于 2020-08-05 23:01:38
我不认为您可以单独使用Sprache解析器来完成此任务,但可以与集成到其中的其他一些自定义逻辑结合使用。
public static List<string> ExpectedThings = new List<string>(new[] {
"OneThing",
"AnotherThing",
"YetAnotherThing"
});
public static string SelectThingValue(string thingKey, string thingVal)
{
if (ExpectedThings.IndexOf(thingKey) == -1)
{
throw new ParseException($"Already parsed an instance of '{thingKey}'.");
}
ExpectedThings.Remove(thingKey);
return thingVal;
}
public static readonly Parser<string> ThingParser = (
from key in ExpectedThings.Aggregate((Parser<string>)null, (acc, thing) => {
var nextThingParser = Parse.String(thing).Text();
return acc == null ? nextThingParser : acc.Or(nextThingParser);
})
from eq in Parse.Char('=')
from val in Parse.AnyChar.Except(Parse.LineTerminator).Many().Text()
select SelectThingValue(key, val)
);
public static MyClass ParseThings()
{
const string input = @"OneThing=Foo
AnotherThing=Bar
YetAnotherThing=Baz";
string[] vals = ThingParser.DelimitedBy(Parse.LineEnd).Parse(input).ToArray();
if (ExpectedThings.Any())
{
throw new ParseException($"Missing things in input string: {string.Join(", ", ExpectedThings.Select(thing => $"'{thing}'"))}");
}
return new MyClass(vals[0], vals[1], vals[2]);
}
static void Main(string[] args)
{
MyClass myClass = ParseThings();
}这里的想法是将您期望的“内容”输入到ExpectedThings列表中。然后,通过使用LINQ的Aggregate()函数动态链接列表中每个项目的.Or()调用来构建ThingParser。在解析器的select部分,它调用SelectThingValue()来从列表中删除刚刚解析的内容,这样我们就知道该内容已经被解析了。它还会检查以确保这个东西还没有被解析,如果已经解析了,它将抛出一个异常。它所做的最后一次检查是查看ExpectedThings中是否还有任何项,如果有,这意味着它没有解析其中的一个项。因为所有这些都是必需的,所以我们在这里抛出一个错误。
您完全可以根据您的实际用例使其更具结构化和动态性,但这是基于您的问题中的示例。这里的一切也是静态的,但您也可以更改它,以允许在ExpectedThings中使用动态值。
https://stackoverflow.com/questions/62060732
复制相似问题