Создание парсеров

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