com.xenomachina.parser.Parser.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kessel Show documentation
Show all versions of kessel Show documentation
Parser combinators for Kotlin
// Copyright © 2017 Laurence Gonsalves
//
// This file is part of kessel, a library which can be found at
// http://github.com/xenomachina/kessel
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, see http://www.gnu.org/licenses/
package com.xenomachina.parser
import arrow.core.Either
import arrow.core.None
import arrow.core.Option
import arrow.data.NonEmptyList
import arrow.data.Validated
import arrow.data.ValidatedNel
import com.xenomachina.chain.Chain
import com.xenomachina.chain.asChain
import java.util.IdentityHashMap
typealias ParseResult = ValidatedNel, R>
class Parser(private val start: Rule) {
val ruleProps = computeRuleProperties()
private fun computeRuleProperties(): Map, Rule.Properties> {
val result = IdentityHashMap, Rule.Properties>()
val seen = IdentityHashMap, Boolean>()
start.computeRuleProperties(result, seen)
for ((key, value) in seen.entries) {
assert (value) { "$key Properties not computed!?" }
}
return result
}
private val Rule<*, *>.properties
get() = ruleProps.get(this)!!
fun parse(sequence: Sequence): ParseResult = parse(sequence.asChain())
private fun parse(chain: Chain): ParseResult {
val (head, tail) = start.call(0, IdentityHashMap(), chain)
return when (head.value) {
is Validated.Invalid -> {
data class Accumulated(
val errors: NonEmptyList>,
var bestConsumed: Int
)
tail.fold(Accumulated(NonEmptyList.of(head.value.e), head.consumed)) {
accumulated, partial ->
when (partial.value) {
is Validated.Invalid ->
// Collect the errors that get us the furthest into the input.
when {
partial.consumed > accumulated.bestConsumed -> Accumulated(
errors = NonEmptyList.of(partial.value.e),
bestConsumed = partial.consumed)
partial.consumed == accumulated.bestConsumed -> Accumulated(
errors = accumulated.errors.plus(partial.value.e),
bestConsumed = accumulated.bestConsumed)
else -> accumulated
}
is Validated.Valid ->
return@parse Validated.Valid(partial.value.a)
}
}.let { Validated.Invalid(it.errors) }
}
is Validated.Valid -> Validated.Valid(head.value.a)
}
}
class Builder (private val block: Builder.Companion.() -> Rule) {
fun build(): Parser = Parser(block(Companion))
companion object {
/**
* Matches a single input element if and only if [predicate] returns `true` given that element.
*/
fun terminal(predicate: (T) -> Boolean) = Terminal(predicate)
/**
* Matches a single input element if and only if it is of type `T`.
*/
inline fun isA(): Rule = terminal { it is T }.map { it as T }
/**
* Matches any one of the supplied rules.
*/
fun oneOf(rule1: Rule, vararg rules: Rule) = AlternativeRule(rule1, *rules)
/**
* Matches either of the supplied rules.
*/
fun either(left: Rule, right: Rule): Rule> =
AlternativeRule(left.map { Either.left(it) }, right.map { Either.right(it) })
/**
* Matches 0 or more of the supplied rule.
*/
fun repeat(rule: Rule): Rule> {
return object {
val me: Rule> = recur { myself }
val myself: Rule> =
oneOf>(
epsilon.map { Chain.Empty },
seq(rule, me) { x, chain -> Chain.NonEmpty(x) { chain } }
)
}.myself.map { it.toList() }
}
/**
* Lazily refers to another rule. This is necessary for recursive grammars.
*/
fun recur(inner: () -> Rule): Rule = LazyRule(inner)
/**
* Matches a sequence of one sub-rule. Included for completeness, map is equivalent.
*/
fun seq(
ruleA: Rule,
constructor: (A) -> Z
): Rule = ruleA.map(constructor)
/**
* Matches a sequence of two sub-rules.
*/
fun seq(
ruleA: Rule,
ruleB: Rule,
constructor: (A, B) -> Z
): Rule = Sequence2Rule(ruleA, ruleB, constructor)
/**
* Matches a sequence of three sub-rules.
*/
fun seq(
ruleA: Rule,
ruleB: Rule,
ruleC: Rule,
constructor: (A, B, C) -> Z
): Rule = Sequence3Rule(ruleA, ruleB, ruleC, constructor)
/**
* Matches a sequence of four sub-rules.
*/
fun seq(
ruleA: Rule,
ruleB: Rule,
ruleC: Rule,
ruleD: Rule,
constructor: (A, B, C, D) -> Z
): Rule = Sequence4Rule(ruleA, ruleB, ruleC, ruleD, constructor)
/**
* Matches a sequence of five sub-rules.
*/
fun seq(
ruleA: Rule,
ruleB: Rule,
ruleC: Rule,
ruleD: Rule,
ruleE: Rule,
constructor: (A, B, C, D, E) -> Z
): Rule = Sequence5Rule(ruleA, ruleB, ruleC, ruleD, ruleE, constructor)
/**
* Matches a sequence of six sub-rules.
*/
fun seq(
ruleA: Rule,
ruleB: Rule,
ruleC: Rule,
ruleD: Rule,
ruleE: Rule,
ruleF: Rule,
constructor: (A, B, C, D, E, F) -> Z
): Rule = Sequence6Rule(ruleA, ruleB, ruleC, ruleD, ruleE, ruleF, constructor)
/**
* Matches a sequence of seven sub-rules.
*/
fun seq(
ruleA: Rule,
ruleB: Rule,
ruleC: Rule,
ruleD: Rule,
ruleE: Rule,
ruleF: Rule,
ruleG: Rule,
constructor: (A, B, C, D, E, F, G) -> Z
): Rule = Sequence7Rule(ruleA, ruleB, ruleC, ruleD, ruleE, ruleF, ruleG, constructor)
fun optional(inner: Rule): Rule> =
oneOf(epsilon.map { None }, inner.map { Option.just(it) })
/**
* Matches zero tokens.
*/
val epsilon = com.xenomachina.parser.epsilon
/**
* Matches the end of input.
*/
val END_OF_INPUT = com.xenomachina.parser.endOfInput
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy