Разбор списка списков с помощью Superpower - PullRequest
0 голосов
/ 18 октября 2019

Я бы хотел разобрать книги библиотеки, выраженные в таком формате:

#Book title 1
Chapter 1
Chapter 2
#Book title 2
Chapter 1
Chapter 2
Chapter 3

Как видите, названиям загрузки предшествуют # и главы каждой книгиявляются следующие строки. Для этого должно быть довольно легко создать парсер.

Пока у меня есть этот код (парсеры + токенизатор):

void Main()
{
    var tokenizer = new TokenizerBuilder<PrjToken>()
                    .Match(Superpower.Parsers.Character.EqualTo('#'), PrjToken.Hash)
                    .Match(Span.Regex("[^\r\n#:=-]*"), PrjToken.Text)
                    .Match(Span.WhiteSpace, PrjToken.WhiteSpace)
                    .Build();


    var input = @"#Book 1
Chapter 1
Chapter 2
#Book 2
Chapter 1
Chapter 2
Chapter 3";

    var library = MyParsers.Library.Parse(tokenizer.Tokenize(input));
}


public enum PrjToken
{
    WhiteSpace,
    Hash,
    Text
}


public class Book
{
    public string Title { get; }
    public string[] Chapters { get; }

    public Book(string title, string[] chapters)
    {
        Title = title;
        Chapters = chapters;
    }
}

public class Library
{
    public Book[] Books { get; }

    public Library(Book[] books)
    {
        Books = books;
    }
}


public class MyParsers
{
    public static readonly TokenListParser<PrjToken, string> Text = from text in Token.EqualTo(PrjToken.Text)
                                                                    select text.ToStringValue();

    public static readonly TokenListParser<PrjToken, Superpower.Model.Token<PrjToken>> Whitespace = from text in Token.EqualTo(PrjToken.WhiteSpace)
                                                                                   select text;

    public static readonly TokenListParser<PrjToken, string> Title =
        from hash in Token.EqualTo(PrjToken.Hash)
        from text in Text
        from wh in Whitespace
        select text;

    public static readonly TokenListParser<PrjToken, Book> Book =
        from title in Title
        from chapters in Text.ManyDelimitedBy(Whitespace)
        select new Book(title, chapters);

    public static readonly TokenListParser<PrjToken, Library> Library =
        from books in Book.ManyDelimitedBy(Whitespace)
        select new Library(books);
}

Приведенный выше код готов для запуска в .NETСкрипка по этой ссылке https://dotnetfiddle.net/3P5dAJ

Все выглядит отлично. Однако что-то не так с анализатором, потому что я получаю эту ошибку:

Синтаксическая ошибка (строка 4, столбец 1): неожиданный хеш #, ожидаемый текст.

Что не так с моими парсерами?

1 Ответ

1 голос
/ 18 октября 2019

Вы можете решить эту проблему, проанализировав главы в виде отдельного списка, где каждая глава заканчивается символом пробела:

    public static readonly TokenListParser<PrjToken, string> Chapter =
        from chapterName in Text
        from wh in Whitespace
        select chapterName;

    public static readonly TokenListParser<PrjToken, Book> Book =
        from title in Title
        from chapters in Chapter.Many()
        select new Book(title, chapters);

По сути, я думаю, что когда Text.ManyDelimitedBy(Whitespace) встречает конечный пробел (новая строка)в конце Chapter 2 он ожидает другой экземпляр Имени главы, а не начало новой книги.

Анализатор не может различить разделитель между Chapters и разделитель между Books (обапробел (новая строка)), и поэтому он ожидает другую главу, а не начало новой Book.

Разбив синтаксический анализатор главы на Text с последующим токеном Whitespaceсломал эту двусмысленность.

Поскольку теперь вы проглотили Whitespace в конце главы, каждая книга не разделена Whitespace, и вам придется изменить работу синтаксического анализатора Bookа также:

    public static readonly TokenListParser<PrjToken, Book> Book =
        from title in Title
        from chapters in Chapter.Many()
        select new Book(title, chapters);

В дополнение к этому, если вы хотите, чтобы файл был проанализирован без перевода строки в конце файла, вы также должны сделать Whitespace в end из Chapter будет необязательным:

    public static readonly TokenListParser<PrjToken, string> Chapter =
        from chapterName in Text
        from wh in Whitespace.Optional()
        select chapterName;

В итоге мы получим (полный анализатор):

public class MyParsers
{
    public static readonly TokenListParser<PrjToken, string> Text = from text in Token.EqualTo(PrjToken.Text)
        select text.ToStringValue();

    public static readonly TokenListParser<PrjToken, Superpower.Model.Token<PrjToken>> Whitespace = from text in Token.EqualTo(PrjToken.WhiteSpace)
        select text;

    public static readonly TokenListParser<PrjToken, string> Title =
        from hash in Token.EqualTo(PrjToken.Hash)
        from text in Text
        from wh in Whitespace
        select text;

    public static readonly TokenListParser<PrjToken, string> Chapter =
        from chapterName in Text
        from wh in Whitespace.Optional()
        select chapterName;

    public static readonly TokenListParser<PrjToken, Book> Book =
        from title in Title
        from chapters in Chapter.Many()
        select new Book(title, chapters);

    public static readonly TokenListParser<PrjToken, Library> Library =
        from books in Book.Many()
        select new Library(books);
}
...