Как я могу анализировать строки, которые могут появляться в любом порядке с помощью Sprache? - PullRequest
2 голосов
/ 28 мая 2020

Я использую 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. Как я могу изменить это, чтобы строки отображались в любом порядке, но при этом обеспечить, чтобы каждая строка отображалась один раз?

Любая помощь очень ценится! Спасибо

Ответы [ 2 ]

0 голосов
/ 05 августа 2020

Я не думаю, что вы можете сделать это только с помощью парсера Sprache, но это возможно в сочетании с некоторыми другими настраиваемыми c logi, интегрированными в него.

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. Затем ThingParser создается путем динамического связывания вызовов .Or() для каждого из этих элементов в списке с использованием функции LINQ Aggregate(). В части парсера select он вызывает SelectThingValue(), который предназначен для удаления того, что было только что проанализировано, из списка, чтобы мы знали, что это уже было проанализировано. Он также проверяет, что вещь еще не была проанализирована, и если да, то выдаст исключение. Последняя проверка, которую он выполняет, - это увидеть, есть ли еще какие-либо элементы в ExpectedThings, и если да, это означает, что он не проанализировал один из них. И поскольку все они необходимы, мы выдаем здесь ошибку.

Вы можете сделать это намного более структурированным и динамичным c в зависимости от вашего фактического варианта использования, но это работает на основе примера в вашем вопросе . Здесь все также является stati c, но вы также можете изменить это, чтобы позволить вам иметь значения Dynami c в ExpectedThings.

0 голосов
/ 28 мая 2020

Я довольно наивен с Sprache, но один, возможно, подробный способ - выбрать кортеж каждого параметра для каждой строки, а затем отфильтровать массив кортежей в вашем окончательном выборе. Что-то вроде:

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 строк, которые имеют уникальный синтаксический анализ и могут быть в разном порядке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...