All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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