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

commonMain.agl.automaton.FirstFollowCache3.kt Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (C) 2021 Dr. David H. Akehurst (http://dr.david.h.akehurst.net)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.akehurst.language.agl.automaton

import net.akehurst.language.agl.runtime.structure.*
import net.akehurst.language.agl.util.Debug
import net.akehurst.language.agl.util.debug
import net.akehurst.language.collections.LazyMutableMapNonNull
import net.akehurst.language.collections.lazyMutableMapNonNull
import net.akehurst.language.collections.mutableQueueOf

/**
 * RP.firstOf(ifAtEnd)
 *   isAtEnd -> ifAtEnd
 *   else -> RP.symbols.rulePositionsAtStart.firstOf(ifAtEnd)
 *
 * R.followIn(ctx)
 *   R.parentOfIn(ctx).next.isAtEnd -> R.parentOfIn(ctx).followIn(R.parentOfIn(ctx).context)
 *   else -> R.parentOfIn(ctx).next.firstOf(
 */

internal class FirstFollowCache3 {

    // prev/context -> ( RulePosition -> Boolean )
    private val _doneFollow = lazyMutableMapNonNull> { mutableMapOf() }

    // prev/context -> ( RulePosition -> Set )
    private val _firstTerminal = lazyMutableMapNonNull>> { lazyMutableMapNonNull { hashSetOf() } }

    // prev/context -> ( TerminalRule -> ParentRulePosition )
    private val _parentInContext = lazyMutableMapNonNull>> { lazyMutableMapNonNull { hashSetOf() } }

    private val _possibleContexts = lazyMutableMapNonNull> { hashSetOf() }

    fun clear() {
        this._doneFollow.clear()
        this._firstTerminal.clear()
        this._parentInContext.clear()
        this._possibleContexts.clear()
    }

    val firstTerminalContexts
        get() = _firstTerminal.flatMap { (ctx, values) ->
            values.map { (rp, ft) ->
                Pair(ctx, rp)
            }
        }

    fun possibleContextsFor(rp: RulePosition): Set = _possibleContexts[rp]

    // entry point from calcWidth
    // target states for WIDTH transition, rulePosition should NOT be atEnd
    //fun firstTerminalInContext(context: RulePosition, rulePosition: RulePosition, nextContext:Set, nextContextFollow: FollowDeferred): Set {
    fun firstTerminalInContext(context: RulePosition, rulePosition: RulePosition, parentFollow: LookaheadSetPart): Set {
        if (Debug.CHECK) check(context.isAtEnd.not()) { "firstTerminal($context,$rulePosition)" }
        return  if (this._firstTerminal.containsKey(context) && this._firstTerminal[context].containsKey(rulePosition)) {
            this._firstTerminal[context][rulePosition]
        } else {
            processClosureFor(context, rulePosition, parentFollow)
            this._firstTerminal[context][rulePosition]
        }
    }

    // target states for HEIGHT or GRAFT transition, rulePosition should be atEnd
    // entry point from calcHeightGraft
    fun parentInContext(contextContext: RulePosition, context: RulePosition, completedRule: RuntimeRule): Set {
        val ctx = if (context.isAtStart) contextContext else context
        return if (this._parentInContext.containsKey(ctx) && this._parentInContext[ctx].containsKey(completedRule)) {
            this._parentInContext[ctx][completedRule]
        } else {
            //error("Internal Error: a call to firstTerminalInContext should set all needed entries in _parentInContext")
            processClosureFor(contextContext, context, LookaheadSetPart.RT)
            this._parentInContext[ctx][completedRule]
        }
    }

    /**
     * Calculate the first position closures for the given (context,rulePosition)
     * and record the firstTerminals, firstOf and follow information.
     * return true if the next position is needed to complete the 'first/follow' information
     * typically true if there is an 'empty' terminal involved or the 'end' of a rule is reached
     */
    // internal so we can use in testing
    //internal fun processClosureFor(context: RulePosition, rulePosition: RulePosition, nextContext:Set, nextContextFollow: FollowDeferred) {
    private fun processClosureFor(context: RulePosition, rulePosition: RulePosition, parentFollow: LookaheadSetPart) {
        //val cls = ClosureItemRoot(this, context, rulePosition, parentFollow)
        val graph = ClosureGraph(context, rulePosition, parentFollow)
        val cls = graph.root
        val doit = when (this._doneFollow[context][cls]) {
            null -> true
            true -> false
            else -> false
        }
        if (doit) {
            this._doneFollow[context][cls] = true
            this.calcFirstTermClosure(graph)
        }
    }

    fun processAllClosures(context: RulePosition, rulePosition: RulePosition, parentExpectedAt: LookaheadSetPart) {
        this.clear()
        val graph = ClosureGraph(context, rulePosition, parentExpectedAt)
        calcAllClosure(graph)
    }

    /**
     * only add firstOf if not empty
     */
    private fun addFirstTerminalInContext(prev: RulePosition, rulePosition: RulePosition, firstTerminalInfo: FirstTerminalInfo) {
        if (Debug.OUTPUT_SM_BUILD) debug(Debug.IndentDelta.NONE) { "add firstTerm($prev,$rulePosition) = $firstTerminalInfo" }
        if (Debug.CHECK) check(prev.isAtEnd.not())
        this._firstTerminal[prev][rulePosition].add(firstTerminalInfo)
    }

    //private fun addParentInContext(prev: RulePosition, completedRule: RuntimeRule, parentOf: ParentOfInContext) {
    private fun addParentInContext(prev: RulePosition, completedRule: RuntimeRule, parentNext: Set) {
        if (Debug.OUTPUT_SM_BUILD) debug(Debug.IndentDelta.NONE) { "add parentOf($prev,${completedRule.tag}) = $parentNext" }
        this._parentInContext[prev][completedRule].addAll(parentNext)
    }

    private fun addPossibleContext(rp: RulePosition, ctx: RulePosition) {
        this._possibleContexts[rp].add(ctx)
    }

    //internal not private so we can test it
    internal fun calcFirstTermClosure(graph: ClosureGraph) {
        //TODO: when we get duplicate closureItem, connect it don't discard it
        // creating a upside down tree.
        // then process closure needs to work from bottom up doing all branches.

        if (Debug.OUTPUT_SM_BUILD) debug(Debug.IndentDelta.INC_AFTER) { "START calcFirstTermClosure: ${graph.root}" }

        if (graph.root.rulePosition.isGoal && graph.root.rulePosition.isAtEnd) {
            return
        } //TODO: else

        val todoList = mutableQueueOf()
        todoList.enqueue(graph.root)
        while (todoList.isNotEmpty) {
            val cls = todoList.dequeue()
            for (item in cls.rulePosition.items) {
                when {
                    item.isTerminal -> graph.addChild(cls,item.asTerminalRulePosition)
                    item.isNonTerminal -> {
                        val childRps = item.rulePositionsAtStart
                        for (childRp in childRps) {
                            val child = graph.addChild(cls,childRp)
                            if (null == child) {
                                // don't follow down the closure
                                //val short = child.shortString
                            } else {
                                todoList.enqueue(child)
                                //println("todo: ${childCls.shortString}")
                            }
                        }
                    }

                    else -> error("Internal Error: should never happen")
                }
            }
        }
        graph.resolveAllChildParentInfo()

        this.cacheStuff(graph)

        if (Debug.OUTPUT_SM_BUILD) debug(Debug.IndentDelta.DEC_BEFORE) { "FINISH calcFirstTermClosure: ${graph.root}" }
    }

    //internal not private so we can test it
    internal fun calcAllClosure(graph: ClosureGraph) {
        //TODO: when we get duplicate closureItem, connect it don't discard it
        // creating a upside down tree.
        // then process closure needs to work from bottom up doing all branches.

        if (Debug.OUTPUT_SM_BUILD) debug(Debug.IndentDelta.INC_AFTER) { "START calcFirstTermClosure: ${graph.root}" }

        if (graph.root.rulePosition.isGoal && graph.root.rulePosition.isAtEnd) {
            return
        }else {
            val todoList = mutableQueueOf()
            todoList.enqueue(graph.root)
            while (todoList.isNotEmpty) {
                val cls = todoList.dequeue()
                for (item in cls.rulePosition.items) {
                    when {
                        item.isTerminal -> graph.addChild(cls, item.asTerminalRulePosition)
                        item.isNonTerminal -> {
                            val childRps = item.rulePositions
                            for (childRp in childRps) {
                                val child = graph.addChild(cls, childRp)
                                if (null == child) {
                                    // don't follow down the closure
                                    //val short = child.shortString
                                } else {
                                    todoList.enqueue(child)
                                    //println("todo: ${childCls.shortString}")
                                }
                            }
                        }

                        else -> error("Internal Error: should never happen")
                    }
                }
            }
            graph.resolveAllChildParentInfo()

            this.cacheStuff(graph)
        }
        if (Debug.OUTPUT_SM_BUILD) debug(Debug.IndentDelta.DEC_BEFORE) { "FINISH calcFirstTermClosure: ${graph.root}" }
    }

    //internal so can be tested
    /*    internal fun calcAllClosures(closureStart: ClosureItem): Set {
            if (Debug.OUTPUT_SM_BUILD) debug(Debug.IndentDelta.INC_AFTER) { "START calcClosures: $closureStart" }
            val completeClosures = mutableSetOf()
            val todoList = mutableStackOf()
            todoList.push(closureStart)
            while (todoList.isNotEmpty) {
                val cls = todoList.pop()
                when {
                    cls.rulePosition.isTerminal -> completeClosures.add(cls)
                    cls.rulePosition.item!!.isTerminal -> {
                        val childRp = cls.rulePosition.item!!.asTerminalRulePosition
                        todoList.push(cls.createChild(childRp))
                    }

                    else -> {
                        val childRulePositions = cls.rulePosition.item!!.rulePositionsAt[0]
                        for (childRp in childRulePositions) {
                            val childCls = cls.createChild(childRp)
                            if (completeClosures.contains(childCls).not()) {
                                completeClosures.add(childCls)
                                todoList.push(childCls)
                            } else {
                                // already done
                            }
                        }
                    }
                }
            }
            if (Debug.OUTPUT_SM_BUILD) debug(Debug.IndentDelta.DEC_BEFORE) { "FINISH calcClosures: $closureStart" }
            return completeClosures
        }
    */
    /**
     * iterate up a closure and set firstTerm,firstOf,follow as required

    private fun processClosurePaths(graph: ClosureGraph, bottom: ClosureItem) {
    if (Debug.OUTPUT_SM_BUILD) debug(Debug.IndentDelta.INC_AFTER) { "START processClosurePath: ${bottom}" }
    for (bottomParentRel in bottom.parentRels) {
    for (bottomParentInfo in bottomParentRel.upInfo) {
    val firstTerminalInfo = when {
    bottom.rulePosition.isTerminal -> FirstTerminalInfo(bottom.rulePosition.runtimeRule, bottom.rulePosition.runtimeRule, bottomParentInfo.nextContextFollow)
    bottom.rulePosition.isEmbedded -> {
    val item = bottom.rulePosition.runtimeRule
    val embeddedRuleSet = item.embeddedRuntimeRuleSet ?: error("Internal Error: should never be null")
    val embeddedRule = item.embeddedStartRule ?: error("Internal Error: should never be null")
    val embeddedStateSet = embeddedRuleSet.fetchStateSetFor(embeddedRule, this.stateSet.automatonKind)
    val embeddedFfc = FirstFollowCache3(embeddedStateSet)
    val s = embeddedFfc.firstTerminalInContext(
    bottomParentInfo.context,
    embeddedStateSet.startRulePosition,
    bottomParentInfo.nextContextFollow
    ) //bottomParentInfo.childNextContext, bottomParentInfo.childNextContextFollow)
    val tr = s.first().terminalRule //FIXME: could be more than 1
    val fn = FollowDeferredComposite.constructOrDelegate(s.map { it.nextContextFollow }.toSet())
    FirstTerminalInfo(bottom.rulePosition.runtimeRule, tr, fn)
    }

    else -> null
    }
    if (Debug.CHECK) firstTerminalInfo?.let { check(firstTerminalInfo.terminalRule.isTerminal) }

    //TODO: no need to start with terminal, start with next one up
    graph.traverseUpPaths(bottom) { isClosureRoot, rp, cpInfo, childNeedsNext ->
    this.setFirsts(isClosureRoot, firstTerminalInfo, rp, cpInfo, childNeedsNext)
    }
    }
    }
    if (Debug.OUTPUT_SM_BUILD) debug(Debug.IndentDelta.DEC_BEFORE) { "FINISH processClosurePath: $bottom" }
    }
     */

    private fun cacheStuff(graph: ClosureGraph) {
        // End/Bottom of closure is always a terminal
        // Root/Start/Top of closure might be notAtEnd
        // every closure-item in between will always be atStart
        // when X we need to calculate {
        //   startState -> WIDTH - targetState = firstTerminals, lookahead = follow(targetState)
        //   atStart -> Nothing needed (unless startState)
        //   atEnd -> HEIGHT/GRAFT - targetState = parent.next, lookahead = firstOf(targetState)
        //   else (inMiddle) -> WIDTH - targetState = firstTerminals, lookahead = follow(targetState)
        // }
        // therefore
        //   parentOf is always set if not root of closure
        //   when {
        //      isTerminal (always atEnd) -> set follow
        //      aEnd -> set firstOf to firstOf nextClosure - first parent with next not atEnd
        //
        //   }
        // other than the start state, firstTerm & firstOf are never called on RPs at the start
        // also never called on a Terminal
        for (dwn in graph.root.downInfo) {
            doForRoot(graph.root.rulePosition, graph.root.context, dwn)
        }
        for (cls in graph.nonRootClosures) {
            if (cls.downInfo.isEmpty()) {
                this.addPossibleContext(cls.rulePosition, cls.context)
            } else {
                when {
                    cls.rulePosition.isAtStart -> doForNonRoot(cls)
                    cls.rulePosition.isTerminal -> doForNonRoot(cls)
                    else -> cls.downInfo.forEach { dwn -> doForRoot(cls.rulePosition, cls.context, dwn) }
                }
                //TODO handle closure not at start!
                //for (dwn in cls.downInfo) {
                //    when (cls.isRoot) {
                //        true -> doForRoot(cls.rulePosition, cls.upInfo, dwn)
                //        false -> doForNonRoot(cls.upInfo.context, cls.rulePosition, cls.parentNext)
                //    }
                //    when (cls.rulePosition.isEmbedded) {
                //        true -> doForEmbedded(cls.rulePosition, cls.upInfo, dwn)
                //        false -> Unit
                //    }
                //}
            }
        }
    }

    /**
     * RulePositionUpInfo(parent <-- child)-rulePosition-RulePositionDownInfo(parent <-- child)
     * in RulePositionUpInfo, rulePosition is the Child
     * in RulePositionDownInfo, rulePosition is the parent
     */
    private fun doForRoot(rulePosition: RulePosition, context: RulePosition, downInfo: FirstTerminalInfo) {
        this.addFirstTerminalInContext(context, rulePosition, downInfo)
        this.addPossibleContext(rulePosition, context)
    }

    private fun doForNonRoot(cls:ClosureItem) {
        val context = cls.context
        val rulePosition = cls.rulePosition
        val parentNext = cls.parentNext
        val completedRule = rulePosition.rule
        this.addParentInContext(context, completedRule, parentNext)
        this.addPossibleContext(rulePosition, context)
    }

    private fun doForEmbedded(rulePosition: RulePosition, upInfo: RulePositionUpInfo, downInfo: FirstTerminalInfo) {
        this.addFirstTerminalInContext(upInfo.context, rulePosition, downInfo)
        this.addPossibleContext(rulePosition, upInfo.context)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy