Разбор списка списков с помощью Superpower
Я хотел бы разобрать книги библиотеки, представленные в таком формате:
#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 Fiddle по этой ссылке https://dotnetfiddle.net/3P5dAJ
Все выглядит нормально. Однако что-то не так с парсером, потому что я получаю такую ошибку:
Синтаксическая ошибка (строка 4, столбец 1): неожиданный хэш
#
, ожидаемый текст.
Что не так с моими парсерами?
1 ответ
Вы можете решить эту проблему, анализируя главы в виде отдельного списка, где каждая глава заканчивается символом пробела:
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
в конце 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);
}