Создание парсеров
Хотя вы можете создавать собственные подклассы парсера (Parser), которые реализуют вашу логику синтаксического анализа, обычно лучше использовать встроенные примитивы парсера, предоставляемые Kudzu. Ниже приведен базовый пример построения парсера, который распознает либо полное слово, либо число:
Update
Автор ответил по поводу ChoiceParser
package com.github.KuzyaGlebkin
import com.copperleaf.kudzu.parser.ParserContext
import com.copperleaf.kudzu.parser.chars.DigitParser
import com.copperleaf.kudzu.parser.chars.LetterParser
import com.copperleaf.kudzu.parser.many.ManyParser
import com.copperleaf.kudzu.parser.sequence.SequenceParser
import com.copperleaf.kudzu.parser.text.OptionalWhitespaceParser
import com.copperleaf.kudzu.parser.choice.ChoiceNParser
fun main() {
val wordParser = ManyParser(LetterParser())
val numberParser = ManyParser(DigitParser())
// в оригинале — ChoiceParser, но я его в пакете не нашёл
val tokenParser = ExactChoiceParser(
wordParser,
numberParser
)
val statement = ManyParser(
SequenceParser(
tokenParser,
OptionalWhitespaceParser()
)
)
val output = statement.parse(ParserContext.fromString("one two 1234 asdf 56 qwerty 7890"))
println(output)
}
Эта простая грамматика будет соответствовать входной строке типа one two 1234 asdf 56 qwerty 7890
и демонстрирует, насколько сложными могут быть парсеры. Парсеры могут быть построены из более мелких, и представляет несколько важных доступных встроенных парсеров. Ниже приведено описание некоторых из этих типов парсеров (просмотрите исходный код для всех доступных парсеров)
LetterParser
: Использует одну букву из входных данных, распознается при помощи предоставляемого Котлиномchar.isLetter()
DigitParser
: Потребляет одну цифру из входных данных, распознается при помощи предоставляемого Котлиномchar.isDigit()
ManyParser
: Принимает другой парсер и повторно использует его на входных данных до тех пор, пока этот парсер может их обрабатывать. Поскольку он сам по себе являетсяParser
и принимаетParser
в качестве входных данных, полная грамматика теперь определяется рекурсивно и использует прогностический (predictive*) подход для определения, может ли продолжаться следующая итерация его парсера. Вы можете передатьManyParser
любой другой парсер, а не только парсеры символьного типа, и поэтому сколь угодно сложные вложенные грамматики можно повторять по мере необходимости. Вы заметите, что мы дали парсеруname
. Это имя привязывается к создаваемым им узлам, так что при оценке дерева синтаксического анализа мы можем искать узлы с именемword
илиnumber
и соответственно выполнять различные действия.PredictiveChoiceParser
: Получает список парсеров и предикативно (predicatively*) выбирает один из них для продолжения синтаксического анализа.SequenceParser
: Получает список парсеров и выполняет каждый из них по порядку один раз.OptionalWhitespaceParser
: Принимает и удаляет пробелы, если они существуют. Поскольку пробелы необязательны входные данные, такие какtwo1234
, все равно будут совпадать и будут проанализированы правильно.LazyParser
: Некоторые грамматики имеют производственные правила (production rules), которые сами по себе являются рекурсивными, например,A := B A
.LazyParser
Действует как заполнитель (placeholder), просто делегируя его другому парсеру. Рекурсивные правила должны быть построены с использованием этих ленивых типов, поскольку нам нужен конкретный экземпляр для передачи другим парсерам. Этот ленивый парсер позволяет нам создавать ссылку на парсер, передавая ее тем парсер, которым это нужно, и на более позднем этапе заполнять детали парсер по мере необходимости.- Прогностическая грамматика (predictive grammar) проверяет, можно ли использовать парсер, предварительно вызвав его
predict
метод. Ожидается, что этот метод проверит, способен ли он использовать следующий символ, и если он не может использовать следующий символ, то весь парсер не сможет продолжить работу. Для парсеров типаmany
эта предсказуемость используется для определения того, когда следует прекратить выполнение итерации. Для парсеров типаchoice
это определяет, какой субпродукт будет выбран: будет использоваться первый субпарсер, для которогоpredict
возвращает true, и другие правила тестироваться не будут. Это сделано для повышения производительности и предотвращения бесконечной рекурсии.