com.xenomachina.parser.Rule.kt Maven / Gradle / Ivy
// 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.Option
import arrow.data.Validated
import com.xenomachina.chain.Chain
import com.xenomachina.chain.buildChain
import com.xenomachina.chain.chainOf
import com.xenomachina.chain.plus
import java.util.IdentityHashMap
import kotlin.coroutines.experimental.SequenceBuilder
abstract class Rule {
internal abstract fun partialParse(
consumed: Int,
// TODO: change breadcrumbs to use a Chain instead of Map?
breadcrumbs: Map, Int>,
chain: Chain
): Chain.NonEmpty>
// TODO: rename this to invoke???
internal fun call(
consumed: Int,
breadcrumbs: Map, Int>,
chain: Chain
): Chain.NonEmpty> {
if (breadcrumbs.get(this) == consumed) {
throw IllegalStateException("Left recursion detected")
} else {
return partialParse(consumed, IdentityHashMap(breadcrumbs).apply { put(this@Rule, consumed) }, chain)
}
}
internal abstract fun computeRuleProperties(
result: MutableMap, Properties>,
seen: MutableMap, Boolean>
): Properties?
data class Properties(
val nullable: Boolean
)
}
private inline fun Rule<*, *>.computeRulePropertiesHelper(
result: MutableMap, Rule.Properties>,
seen: MutableMap, Boolean>,
crossinline body: () -> Rule.Properties?
): Rule.Properties? {
val props: Rule.Properties?
if (seen.containsKey(this)) {
props = result.get(this)
} else {
seen.put(this, false)
props = body() ?: Rule.Properties(nullable = false)
result.put(this, props)
seen.put(this, true)
}
return props
}
fun Rule.map(transform: (A) -> B): Rule = MappingRule(this, transform)
class MappingRule(val original: Rule, val transform: (A) -> B) : Rule() {
override fun computeRuleProperties(
result: MutableMap, Properties>,
seen: MutableMap, Boolean>
) = computeRulePropertiesHelper(result, seen) { original.computeRuleProperties(result, seen) }
override fun partialParse(
consumed: Int,
breadcrumbs: Map, Int>,
chain: Chain
): Chain.NonEmpty> =
original.call(consumed, breadcrumbs, chain).map { it.map(transform) }
}
/**
* A [Rule] tha matches zero tokens.
*/
object epsilon : Rule() {
override fun computeRuleProperties(
result: MutableMap, Properties>,
seen: MutableMap, Boolean>
) = Properties(nullable = true)
override fun partialParse(
consumed: Int,
breadcrumbs: Map, Int>,
chain: Chain
): Chain.NonEmpty> =
chainOf(PartialResult( consumed, Validated.Valid(Unit), chain))
}
object endOfInput : Rule() {
override fun computeRuleProperties(
result: MutableMap, Properties>,
seen: MutableMap, Boolean>
) = Properties(nullable = false)
override fun partialParse(
consumed: Int,
breadcrumbs: Map, Int>,
chain: Chain
): Chain.NonEmpty> =
when (chain) {
is Chain.Empty ->
chainOf>(PartialResult(consumed, Validated.Valid(Unit), chain))
is Chain.NonEmpty ->
chainOf(PartialResult(
consumed,
Validated.Invalid(ParseError(consumed, chain.maybeHead) { "Expected end of input, found: ${chain.head}" }),
chain))
}
}
class Terminal(val predicate: (T) -> Boolean) : Rule() {
override fun computeRuleProperties(
result: MutableMap, Properties>,
seen: MutableMap, Boolean>
) = Properties(nullable = false)
override fun partialParse(
consumed: Int,
breadcrumbs: Map, Int>,
chain: Chain
): Chain.NonEmpty> =
when (chain) {
is Chain.Empty ->
chainOf(PartialResult(
consumed,
Validated.Invalid(ParseError(consumed, chain.maybeHead) { "Unexpected end of input" }),
chain))
is Chain.NonEmpty ->
if (predicate(chain.head)) {
chainOf(PartialResult(
consumed + 1,
Validated.Valid(chain.head),
chain.tail))
} else {
chainOf(PartialResult(
consumed,
Validated.Invalid(ParseError(consumed, chain.maybeHead) { "Unexpected: ${chain.head}" }),
chain))
}
}
}
/**
* A lazy wrapper around another Parser. This is useful for creating recursive parsers. For example:
*
* val listOfWidgets = oneOf(epsilon(), seq(widget, L(listOfWidgets)))
*/
class LazyRule(inner: () -> Rule) : Rule() {
val inner by lazy(inner)
override fun computeRuleProperties(
result: MutableMap, Properties>,
seen: MutableMap, Boolean>
) = computeRulePropertiesHelper(result, seen) { inner.computeRuleProperties(result, seen) }
override fun partialParse(
consumed: Int,
breadcrumbs: Map, Int>,
chain: Chain
): Chain.NonEmpty> =
this.inner.call(consumed, breadcrumbs, chain)
}
class AlternativeRule(private val rule1: Rule, vararg rules: Rule) : Rule() {
override fun computeRuleProperties(
result: MutableMap, Properties>,
seen: MutableMap, Boolean>
) = computeRulePropertiesHelper(result, seen) {
val props1 = rule1.computeRuleProperties(result, seen)
var nullable = props1?.nullable ?: false
for (rule in rules) {
val props = rule.computeRuleProperties(result, seen)
nullable = nullable || (props?.nullable ?: false)
}
Properties(nullable)
}
private val rules = listOf(*rules)
override fun partialParse(
consumed: Int,
breadcrumbs: Map, Int>,
chain: Chain
): Chain.NonEmpty> {
var result: Chain.NonEmpty> = rule1.call(consumed, breadcrumbs, chain)
for (parser in rules) {
result = result + { parser.call(consumed, breadcrumbs, chain) }
}
return result
}
}
sealed class SequenceRule (
private val ruleA: Rule,
private vararg val rules: Rule
) : Rule() {
override fun computeRuleProperties(
result: MutableMap, Properties>,
seen: MutableMap, Boolean>
) = computeRulePropertiesHelper(result, seen) {
val propsA = ruleA.computeRuleProperties(result, seen)
var nullable = propsA?.nullable ?: false
for (rule in rules) {
val props = rule.computeRuleProperties(result, seen)
nullable = nullable && (props?.nullable ?: false)
}
Properties(nullable)
}
}
class Sequence2Rule(
val ruleA: Rule,
val ruleB: Rule,
val constructor: (A, B) -> Z
) : SequenceRule(ruleA, ruleB) {
override fun partialParse(
consumed: Int,
breadcrumbs: Map, Int>,
chain: Chain
): Chain.NonEmpty> =
// TODO: remove type params when Kotlin compiler can infer without crashing
buildChain> {
forSequenceSubRule(ruleA, consumed, breadcrumbs, chain) { partialA, a ->
forSequenceSubRule(ruleB, partialA.consumed, breadcrumbs, partialA.remaining) { partialB, b ->
yield(PartialResult(consumed,
Validated.Valid(constructor(a, b)),
partialB.remaining))
}
}
} as Chain.NonEmpty>
}
class Sequence3Rule(
val ruleA: Rule,
val ruleB: Rule,
val ruleC: Rule,
val constructor: (A, B, C) -> Z
) : SequenceRule(ruleA, ruleB, ruleC) {
override fun partialParse(
consumed: Int,
breadcrumbs: Map, Int>,
chain: Chain
): Chain.NonEmpty> =
// TODO: remove type params when Kotlin compiler can infer without crashing
buildChain> {
forSequenceSubRule(ruleA, consumed, breadcrumbs, chain) { partialA, a ->
forSequenceSubRule(ruleB, partialA.consumed, breadcrumbs, partialA.remaining) { partialB, b ->
forSequenceSubRule(ruleC, partialB.consumed, breadcrumbs, partialB.remaining) { partialC, c ->
yield(PartialResult(consumed,
Validated.Valid(constructor(a, b, c)),
partialC.remaining))
}
}
}
} as Chain.NonEmpty>
}
class Sequence4Rule(
val ruleA: Rule,
val ruleB: Rule,
val ruleC: Rule,
val ruleD: Rule,
val constructor: (A, B, C, D) -> Z
) : SequenceRule(ruleA, ruleB, ruleC) {
override fun partialParse(
consumed: Int,
breadcrumbs: Map, Int>,
chain: Chain
): Chain.NonEmpty> =
// TODO: remove type params when Kotlin compiler can infer without crashing
buildChain> {
forSequenceSubRule(ruleA, consumed, breadcrumbs, chain) { partialA, a ->
forSequenceSubRule(ruleB, partialA.consumed, breadcrumbs, partialA.remaining) { partialB, b ->
forSequenceSubRule(ruleC, partialB.consumed, breadcrumbs, partialB.remaining) { partialC, c ->
forSequenceSubRule(ruleD, partialC.consumed, breadcrumbs, partialC.remaining) { partialD, d ->
yield(PartialResult(
consumed, Validated.Valid(constructor(a, b, c, d)), partialD.remaining))
}
}
}
}
} as Chain.NonEmpty>
}
class Sequence5Rule(
val ruleA: Rule,
val ruleB: Rule,
val ruleC: Rule,
val ruleD: Rule,
val ruleE: Rule,
val constructor: (A, B, C, D, E) -> Z
) : SequenceRule(ruleA, ruleB, ruleC) {
override fun partialParse(
consumed: Int,
breadcrumbs: Map, Int>,
chain: Chain
): Chain.NonEmpty> =
// TODO: remove type params when Kotlin compiler can infer without crashing
buildChain> {
forSequenceSubRule(ruleA, consumed, breadcrumbs, chain) { partialA, a ->
forSequenceSubRule(ruleB, partialA.consumed, breadcrumbs, partialA.remaining) { partialB, b ->
forSequenceSubRule(ruleC, partialB.consumed, breadcrumbs, partialB.remaining) { partialC, c ->
forSequenceSubRule(ruleD, partialC.consumed, breadcrumbs, partialC.remaining) { partialD, d ->
forSequenceSubRule(ruleE, partialD.consumed, breadcrumbs, partialD.remaining) { partialE, e ->
yield(PartialResult(consumed,
Validated.Valid(constructor(a, b, c, d, e)),
partialE.remaining))
}
}
}
}
}
} as Chain.NonEmpty>
}
class Sequence6Rule(
val ruleA: Rule,
val ruleB: Rule,
val ruleC: Rule,
val ruleD: Rule,
val ruleE: Rule,
val ruleF: Rule,
val constructor: (A, B, C, D, E, F) -> Z
) : SequenceRule(ruleA, ruleB, ruleC) {
override fun partialParse(
consumed: Int,
breadcrumbs: Map, Int>,
chain: Chain
): Chain.NonEmpty> =
// TODO: remove type params when Kotlin compiler can infer without crashing
buildChain> {
forSequenceSubRule(ruleA, consumed, breadcrumbs, chain) { partialA, a ->
forSequenceSubRule(ruleB, partialA.consumed, breadcrumbs, partialA.remaining) { partialB, b ->
forSequenceSubRule(ruleC, partialB.consumed, breadcrumbs, partialB.remaining) { partialC, c ->
forSequenceSubRule(ruleD, partialC.consumed, breadcrumbs, partialC.remaining) { partialD, d ->
forSequenceSubRule(ruleE, partialD.consumed, breadcrumbs, partialD.remaining) { partialE, e ->
forSequenceSubRule(ruleF, partialE.consumed, breadcrumbs, partialE.remaining) { partialF, f ->
yield(PartialResult(consumed,
Validated.Valid(constructor(a, b, c, d, e, f)),
partialF.remaining))
}
}
}
}
}
}
} as Chain.NonEmpty>
}
class Sequence7Rule(
val ruleA: Rule,
val ruleB: Rule,
val ruleC: Rule,
val ruleD: Rule,
val ruleE: Rule,
val ruleF: Rule,
val ruleG: Rule,
val constructor: (A, B, C, D, E, F, G) -> Z
) : SequenceRule(ruleA, ruleB, ruleC) {
override fun partialParse(
consumed: Int,
breadcrumbs: Map, Int>,
chain: Chain
): Chain.NonEmpty> =
// TODO: remove type params when Kotlin compiler can infer without crashing
buildChain> {
forSequenceSubRule(ruleA, consumed, breadcrumbs, chain) { partialA, a ->
forSequenceSubRule(ruleB, partialA.consumed, breadcrumbs, partialA.remaining) { partialB, b ->
forSequenceSubRule(ruleC, partialB.consumed, breadcrumbs, partialB.remaining) { partialC, c ->
forSequenceSubRule(ruleD, partialC.consumed, breadcrumbs, partialC.remaining) { partialD, d ->
forSequenceSubRule(ruleE, partialD.consumed, breadcrumbs, partialD.remaining) { partialE, e ->
forSequenceSubRule(ruleF, partialE.consumed, breadcrumbs, partialE.remaining) { partialF, f ->
forSequenceSubRule(ruleG, partialF.consumed, breadcrumbs, partialF.remaining) { partialG, g ->
yield(PartialResult(consumed,
Validated.Valid(constructor(a, b, c, d, e, f, g)),
partialG.remaining))
}
}
}
}
}
}
}
} as Chain.NonEmpty>
}
private suspend inline fun SequenceBuilder>.forSequenceSubRule(
rule: Rule,
consumed: Int,
breadcrumbs: Map, Int>,
chain: Chain,
crossinline body: suspend SequenceBuilder>.(PartialResult, R) -> Unit
) {
for (partial in rule.call(consumed, breadcrumbs, chain)) {
when (partial.value) {
is Validated.Invalid -> {
// TODO: This object should be identical to partial.value, but we have to rebuild it to get the types
// right. An unchecked cast would probably work here.
yield(PartialResult(partial.consumed, partial.value, partial.remaining))
}
is Validated.Valid -> {
body(partial, partial.value.a)
}
}
}
}
// TODO: flip this inside-out so that Validated is on the outside? Then R can be Nothing in case of error.
/**
* @property consumed how many input tokens were successfully consumed to construct the successful result or before
* failing
* @property value either the parsed value, or a `ParseError` in the case of failure
* @property remaining the remaining chain after the parsed value, or at the point of failure
*/
internal data class PartialResult(
val consumed: Int,
val value: Validated, R>,
val remaining: Chain
) {
fun map(f: (R) -> F) = PartialResult(consumed, value.map(f), remaining)
}
val Chain.maybeHead
get() = when (this) {
is Chain.NonEmpty -> Option.just(head)
is Chain.Empty -> Option.empty()
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy