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

com.xenomachina.parser.Parser.kt Maven / Gradle / Ivy

There is a newer version: 0.0.3
Show newest version
// 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