From 57f206b2b0806ff4fcf544b6a7a208a75a6a9ef4 Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Tue, 5 Dec 2023 17:38:29 +0300 Subject: [PATCH 01/15] Base implementation of union --- .../grammar/combinator/regexp/Regexp.kt | 39 ++- src/main/kotlin/org/srcgll/rsm/Incremental.kt | 257 ++++++++++++++++++ src/main/kotlin/org/srcgll/rsm/RSMState.kt | 8 + src/test/kotlin/rsm/api/UnionWithRsmTest.kt | 43 +++ 4 files changed, 325 insertions(+), 22 deletions(-) create mode 100644 src/main/kotlin/org/srcgll/rsm/Incremental.kt create mode 100644 src/test/kotlin/rsm/api/UnionWithRsmTest.kt diff --git a/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Regexp.kt b/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Regexp.kt index 5da3a865f..29a6ddf37 100644 --- a/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Regexp.kt +++ b/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Regexp.kt @@ -3,52 +3,47 @@ package org.srcgll.grammar.combinator.regexp import org.srcgll.rsm.symbol.Nonterminal -sealed interface Regexp -{ +sealed interface Regexp { /* Based on Brzozowski derivative */ - fun derive(symbol : DerivedSymbol) : Regexp - fun getNonterminal() : Nonterminal? = null + fun derive(symbol: DerivedSymbol): Regexp + fun getNonterminal(): Nonterminal? = null /* Does the expression accept an epsilon */ - fun acceptEpsilon() : Boolean - { + fun acceptEpsilon(): Boolean { return when (this) { - is Empty -> false - is Epsilon -> true + is Empty -> false + is Epsilon -> true is DerivedSymbol -> false - is Concat -> head.acceptEpsilon() && tail.acceptEpsilon() - is Alternative -> left.acceptEpsilon() || right.acceptEpsilon() - is Many -> true + is Concat -> head.acceptEpsilon() && tail.acceptEpsilon() + is Alternative -> left.acceptEpsilon() || right.acceptEpsilon() + is Many -> true } } - fun getAlphabet() : Set - { + fun getAlphabet(): Set { return when (this) { - is Empty -> emptySet() - is Epsilon -> emptySet() + is Empty -> emptySet() + is Epsilon -> emptySet() is DerivedSymbol -> setOf(this) - is Concat -> head.getAlphabet() + tail.getAlphabet() - is Alternative -> left.getAlphabet() + right.getAlphabet() - is Many -> exp.getAlphabet() + is Concat -> head.getAlphabet() + tail.getAlphabet() + is Alternative -> left.getAlphabet() + right.getAlphabet() + is Many -> exp.getAlphabet() } } } -data object Epsilon : Regexp -{ +data object Epsilon : Regexp { override fun derive(symbol: DerivedSymbol): Regexp = Empty } /* Regular expression that does not accept any input string. */ -data object Empty : Regexp -{ +data object Empty : Regexp { override fun derive(symbol: DerivedSymbol): Regexp = this } diff --git a/src/main/kotlin/org/srcgll/rsm/Incremental.kt b/src/main/kotlin/org/srcgll/rsm/Incremental.kt new file mode 100644 index 000000000..21d0c3f54 --- /dev/null +++ b/src/main/kotlin/org/srcgll/rsm/Incremental.kt @@ -0,0 +1,257 @@ +package org.srcgll.rsm + +import org.srcgll.rsm.symbol.Nonterminal +import org.srcgll.rsm.symbol.Symbol +import org.srcgll.rsm.symbol.Terminal + +class Incremental(private val origin: RSMState) { + data class CloneState(val origin: RSMState, val delta: RSMState) + + private val botDelta = RSMState(origin.nonterminal) + private val botOrigin = RSMState(origin.nonterminal) + private val cloneStatesToParents = hashMapOf() + private val register = mutableSetOf() + private val rsmProxy = RsmProxyInfo(origin) + + /** + * Only linear input + */ + fun constructIncremental(delta: RSMState, isRemoving: Boolean) { + writeRSMToTXT(origin, "inc/1_origin.txt") + // 1. Add all states from original RSM + for (state in rsmProxy.originAllStates) { + //optimization: common states are states of original rsm + register.add(state) + cloneStatesToParents[state] = CloneState(state, botDelta) + } + var commonStart = clone(origin, delta) + cloneStatesToParents[commonStart] = CloneState(origin, delta) + writeRSMToTXT(commonStart, "inc/2_common.txt") + addDeltaStates(commonStart) + writeRSMToTXT(commonStart, "inc/3_common.txt") + + rsmProxy.incomingEdges.putAll(rsmProxy.calculateIncomingEdges(commonStart)) + // 2. Find unreachable states and remove them from Register + val unreachable = rsmProxy.originAllStates + .filter { isUnreachable(it) } + .toHashSet() + register.removeAll(unreachable) + writeRSMToTXT(commonStart, "inc/4_restore.txt") + + // 2.5. Optimization: "save" unreachable states from original RSM + commonStart = restoreUnreachable(commonStart, unreachable) + writeRSMToTXT(commonStart, "inc/5_restore.txt") + + // 3. Find and merge equivalent states between register and new states + replaceOrRegister(commonStart) + writeRSMToTXT(commonStart, "inc/6_replace.txt") + } + + private fun isUnreachable(state: RSMState): Boolean { + val edges = rsmProxy.incomingEdges[state] + return edges == null || edges.size == 0 + } + + /** + * Check queue and cloned states one by one from the end + */ + private fun replaceOrRegister(startState: RSMState) { + val used = hashSetOf() + fun replaceByDfs(state: RSMState) { + if (!used.contains(state)) { + used.add(state) + for (outEdge in rsmProxy.getOutgoingEdges(state)) { + replaceByDfs(outEdge.state) + } + val equivState = register.find { + state.equivalent(it) + } + if (equivState != null) { + replace(state, equivState) + } + } + } + replaceByDfs(startState) + } + + private fun restoreUnreachable( + newStart: RSMState, + unreachable: HashSet, + ): RSMState { + val used = mutableSetOf() + val queue = ArrayDeque() + var updatedStart: RSMState? = null + queue.add(newStart) + while (queue.isNotEmpty()) { + val state = queue.removeFirst() + if (!used.contains(state)) { + used.add(state) + val originState = cloneStatesToParents[state]!!.origin + if (unreachable.contains(originState)) { + unreachable.remove(originState) + replace(state, originState) + if (state == newStart) { + updatedStart = originState + } + } + for (edge in rsmProxy.getOutgoingEdges(state)) { + queue.add(edge.state) + } + } + } + return updatedStart ?: throw Exception("Start state should be updated!!") + } + + private fun clone(origin: RSMState, delta: RSMState): RSMState { + val newState = RSMState(origin.nonterminal, origin.isStart || delta.isStart, origin.isFinal || delta.isFinal) + cloneStatesToParents[newState] = CloneState(origin, delta) + rsmProxy.cloneOutgoingEdges(origin, newState) + return newState + } + + private fun addDeltaStates(commonStart: RSMState) { + fun cloneStep( + qLast: RSMState, + s: Symbol, + newDelta: RSMState, + origins: HashMap> + ): RSMState { + val newOrigin = origins[s]?.first() ?: botOrigin + val newState = clone(newOrigin, newDelta) + qLast.addEdge(s, newState) + return newState + } + + var qLast = commonStart + do { + val parents = cloneStatesToParents[qLast]!! + val termEdges = parents.delta.outgoingTerminalEdges.entries + val nonTermEdges = parents.delta.outgoingNonterminalEdges.entries + for (t in termEdges) { + qLast = cloneStep(qLast, t.key, t.value.first(), parents.origin.outgoingTerminalEdges) + } + for (nt in nonTermEdges) { + qLast = cloneStep(qLast, nt.key, nt.value.first(), parents.origin.outgoingNonterminalEdges) + } + } while (termEdges.isNotEmpty() || nonTermEdges.isNotEmpty()) + } + + private fun replace(oldState: RSMState, newState: RSMState) { + val edges = rsmProxy.incomingEdges[oldState] ?: emptySet() + for (edge in edges) { + edge.state.removeEdge(Edge(oldState, edge.symbol)) + edge.state.addEdge(edge.symbol, newState) + } + for (edge in rsmProxy.getOutgoingEdges(oldState)) { + newState.addEdge(edge.symbol, edge.state) + } + } + + data class RsmProxyInfo(val startState: RSMState) { + private var outgoingEdges = hashMapOf>() + var originAllStates: HashSet + var incomingEdges: HashMap> + + init { + originAllStates = getAllStates() + incomingEdges = calculateIncomingEdges(startState) + } + + fun cloneOutgoingEdges(srcState: RSMState, destState: RSMState) { + val srcOutgoingEdges = getOutgoingEdges(srcState) + val destOutgoingEdges = getOutgoingEdges(destState) + + for (edge in srcOutgoingEdges) { + if (destOutgoingEdges.find { it.symbol == edge.symbol } == null) { + destState.addEdge(edge.symbol, edge.state) + incomingEdges.getOrPut(edge.state) { hashSetOf() }.add(Edge(destState, edge.symbol)) + } + } + } + + fun getOutgoingEdges(state: RSMState): HashSet { + var states = outgoingEdges[state] + return if (states == null) { + states = hashSetOf() + state.outgoingNonterminalEdges.map { entry -> states.addAll(entry.value.map { Edge(it, entry.key) }) } + state.outgoingTerminalEdges.map { entry -> states.addAll(entry.value.map { Edge(it, entry.key) }) } + states + } else { + states + } + } + + private fun getAllStates(): HashSet { + val states = hashSetOf() + val queue = ArrayDeque() + queue.add(startState) + while (queue.isNotEmpty()) { + val state = queue.removeFirst() + if (!states.contains(state)) { + states.add(state) + for (edge in getOutgoingEdges(state)) { + queue.add(edge.state) + } + } + } + return states + } + + /** + * For each state get set of state which contains output edge to it + * and Symbol on this edge + */ + fun calculateIncomingEdges(state: RSMState): HashMap> { + val used = hashSetOf() + val queue = ArrayDeque() + queue.add(state) + val incomingEdges = hashMapOf>() + while (queue.isNotEmpty()) { + val nextState = queue.removeFirst() + if (!used.contains(nextState)) { + used.add(nextState) + for (edge in getOutgoingEdges(nextState)) { + incomingEdges.getOrPut(edge.state) { hashSetOf() }.add(Edge(nextState, edge.symbol)) + queue.add(edge.state) + } + } + } + return incomingEdges + } + } +} + +data class Edge(val state: RSMState, val symbol: Symbol) + +fun RSMState.equivalent(other: RSMState): Boolean { + if (nonterminal != other.nonterminal) { + return false + } + if (isFinal != other.isFinal || isStart != other.isStart) { + return false + } + if (outgoingTerminalEdges != other.outgoingTerminalEdges) { + return false + } + return outgoingNonterminalEdges == other.outgoingNonterminalEdges +} + +fun RSMState.add(delta: RSMState) { + Incremental(this).constructIncremental(delta, false) +} + +fun RSMState.remove(delta: RSMState) { + Incremental(this).constructIncremental(delta, true) +} + +fun RSMState.removeEdge(edge: Edge){ + when(edge.symbol){ + is Terminal<*> -> outgoingTerminalEdges[edge.symbol]!!.remove(edge.state) + is Nonterminal -> outgoingNonterminalEdges[edge.symbol]!!.remove(edge.state) + } +} + + + + + diff --git a/src/main/kotlin/org/srcgll/rsm/RSMState.kt b/src/main/kotlin/org/srcgll/rsm/RSMState.kt index ddcd569bf..8d2b5712c 100644 --- a/src/main/kotlin/org/srcgll/rsm/RSMState.kt +++ b/src/main/kotlin/org/srcgll/rsm/RSMState.kt @@ -1,6 +1,7 @@ package org.srcgll.rsm import org.srcgll.rsm.symbol.Nonterminal +import org.srcgll.rsm.symbol.Symbol import org.srcgll.rsm.symbol.Terminal class RSMState @@ -41,4 +42,11 @@ class RSMState outgoingNonterminalEdges[edge.nonterminal] = hashSetOf(edge.head) } } + + fun addEdge(label: Symbol, head: RSMState){ + when (label){ + is Terminal<*> -> addTerminalEdge(RSMTerminalEdge(label, head)) + is Nonterminal -> addNonterminalEdge(RSMNonterminalEdge(label, head)) + } + } } diff --git a/src/test/kotlin/rsm/api/UnionWithRsmTest.kt b/src/test/kotlin/rsm/api/UnionWithRsmTest.kt new file mode 100644 index 000000000..571bdd3c8 --- /dev/null +++ b/src/test/kotlin/rsm/api/UnionWithRsmTest.kt @@ -0,0 +1,43 @@ +package rsm.api + +import org.junit.jupiter.api.Test +import org.srcgll.grammar.combinator.Grammar +import org.srcgll.grammar.combinator.regexp.NT +import org.srcgll.grammar.combinator.regexp.Term +import org.srcgll.grammar.combinator.regexp.times +import org.srcgll.rsm.RSMState +import org.srcgll.rsm.add +import org.srcgll.rsm.symbol.Terminal +import org.srcgll.rsm.writeRSMToDOT +import rsm.RsmTest +import kotlin.test.assertEquals + +class UnionWithRsmTest : RsmTest { + class DyckLanguage : Grammar() { + var S by NT() + + init { + setStart(S) + S = Term("(") * S * Term(")") + } + } + + @Test + fun testSimpleAddition() { + val grammar = DyckLanguage() + val startOrigin = grammar.getRsm() + val oldOrigin = startOrigin + val deltaStart = RSMState(grammar.S.getNonterminal()!!, isStart = true, isFinal = false) + val st1 = RSMState(grammar.S.getNonterminal()!!, isStart = false, isFinal = false) + val st2 = RSMState(grammar.S.getNonterminal()!!, isStart = false, isFinal = false) + val st3 = RSMState(grammar.S.getNonterminal()!!, isStart = false, isFinal = true) + deltaStart.addEdge(Terminal("["), st1) + st1.addEdge(grammar.S.getNonterminal()!!, st2) + st2.addEdge(Terminal("]"), st3) + writeRSMToDOT(deltaStart, "delta.dot") + writeRSMToDOT(startOrigin, "source.dot") + startOrigin.add(deltaStart) + writeRSMToDOT(startOrigin, "result.dot") + assertEquals(oldOrigin, startOrigin) + } +} \ No newline at end of file From e9a091b1f6da6d31e4051650821bf09bc7401394 Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Thu, 7 Dec 2023 14:42:51 +0300 Subject: [PATCH 02/15] Add `option` and `some` --- .../org/srcgll/grammar/combinator/README.md | 107 ++++++++++++++---- .../grammar/combinator/regexp/Alternative.kt | 37 +++--- .../srcgll/grammar/combinator/regexp/Many.kt | 6 +- src/test/kotlin/rsm/builder/OptionalTest.kt | 49 ++++++++ 4 files changed, 150 insertions(+), 49 deletions(-) create mode 100644 src/test/kotlin/rsm/builder/OptionalTest.kt diff --git a/src/main/kotlin/org/srcgll/grammar/combinator/README.md b/src/main/kotlin/org/srcgll/grammar/combinator/README.md index 55e9ec592..baaf2025c 100644 --- a/src/main/kotlin/org/srcgll/grammar/combinator/README.md +++ b/src/main/kotlin/org/srcgll/grammar/combinator/README.md @@ -1,4 +1,4 @@ -# Grammar combinator +# Grammar combinator Kotlin DSL for describing context-free grammars. @@ -15,14 +15,14 @@ S = A* *DSL* ```kotlin class AStar : Grammar() { - var A = Term("a") - var S by NT() + var A = Term("a") + var S by NT() - init { - setStart(S) - S = Many(A) - } + init { + setStart(S) + S = Many(A) } +} ``` ### Non-terminals @@ -32,13 +32,13 @@ Non-terminals must be fields of the grammar class. Be sure to declare using dele Start non-terminal set with method `setStart(nt)`. Can be set once for grammar. -### Terminals +### Terminals `val A = Term("a")` `val B = Term(42)` -Terminal is a generic class. Can store terminals of any type. Terminals are compared based on their content. +Terminal is a generic class. Can store terminals of any type. Terminals are compared based on their content. They can be declared as fields of a grammar class or directly in productions. @@ -55,19 +55,19 @@ S3 = '{' S '}' S *DSL* ```kotlin class DyckGrammar : Grammar() { - var S by NT() - var S1 by NT() - var S2 by NT() - var S3 by NT() - - init { - setStart(S) - S = S1 or S2 or S3 or Epsilon - S1 = Term("(") * S * Term(")") * S - S2 = Term("[") * S * Term("]") * S - S3 = Term("{") * S * Term("}") * S - } + var S by NT() + var S1 by NT() + var S2 by NT() + var S3 by NT() + + init { + setStart(S) + S = S1 or S2 or S3 or Epsilon + S1 = Term("(") * S * Term(")") * S + S2 = Term("[") * S * Term("]") * S + S3 = Term("{") * S * Term("}") * S } +} ``` ### Production A → B = A = B @@ -76,24 +76,81 @@ A → B = A = B (.): Σ∗ × Σ∗ → Σ∗ a . b = a * b +```kotlin +class AB : Grammar() { + var S by NT() + init { + setStart(S) + S = Term("a") * Term("b") + } + } +``` ### Alternative a | b = a or b +```kotlin +class AStar : Grammar() { + var S by NT() + + init { + setStart(S) + S = Term("a") or S or Epsilon + } + } +``` + ### Kleene Star $a* = U_{i=0}^{\inf}a^i$ a* = Many(a) -`todo: a+ = some(a)` +```kotlin +class AStar : Grammar() { + var S by NT() + + init { + setStart(S) + S = many(Term("a")) + } + } +``` + +### Some +$a* = U_{i=1}^{\inf}a^i$ + +a+ = some(a) -### Optional +```kotlin +class AStar : Grammar() { + var S by NT() + + init { + setStart(S) + S = some(Term("a")) or Epsilon + } + } +``` + +### Optional a? -> a | Epsilon Epsilon -- constant terminal with behavior corresponding to the $\epsilon$ terminal (empty string). -`todo: a? = opt(a)` +a? = opt(a) + +```kotlin +class AStar : Grammar() { + var S by NT() + + init { + setStart(S) + S = opt(Term("a")) * S + } + } +``` -## RSM +## RSM DSL allows to get the RSM corresponding to the grammar using the `getRsm` method. The algorithm of RSM construction is based on Brzozowski derivations. + diff --git a/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Alternative.kt b/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Alternative.kt index e2b23df8b..25ba4bdc3 100644 --- a/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Alternative.kt +++ b/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Alternative.kt @@ -2,16 +2,13 @@ package org.srcgll.grammar.combinator.regexp data class Alternative -( - internal val left : Regexp, - internal val right : Regexp, -) - : Regexp -{ + ( + internal val left: Regexp, + internal val right: Regexp, +) : Regexp { companion object { - fun makeAlternative(left : Regexp, right : Regexp) : Regexp - { - if (left is Empty) return right + fun makeAlternative(left: Regexp, right: Regexp): Regexp { + if (left is Empty) return right if (right is Empty) return left if (left is Alternative && (right == left.left || right == left.right)) { @@ -22,22 +19,22 @@ data class Alternative } return if (left == right) left else Alternative(left, right) } + + fun makeAlternative(literals: Iterable): Regexp { + val terms = literals.map { Term(it) } + val initial: Regexp = terms[0] or terms[1] + + return terms.subList(2, terms.size) + .fold(initial) { acc: Regexp, i: Term -> Alternative.makeAlternative(acc, i) } + } } - override fun derive(symbol : DerivedSymbol) : Regexp - { + override fun derive(symbol: DerivedSymbol): Regexp { return makeAlternative(left.derive(symbol), right.derive(symbol)) } } -infix fun Regexp.or(other : Regexp) : Regexp = Alternative.makeAlternative(left = this, other) - -fun makeAlternative(literals : Iterable) : Regexp -{ - val terms = literals.map { Term(it) } - val initial : Regexp = terms[0] or terms[1] +infix fun Regexp.or(other: Regexp): Regexp = Alternative.makeAlternative(left = this, other) - return terms.subList(2, terms.size) - .fold(initial) { acc : Regexp, i : Term -> Alternative.makeAlternative(acc, i) } -} \ No newline at end of file +fun opt(exp: Regexp): Regexp = Alternative.makeAlternative(exp, Epsilon) \ No newline at end of file diff --git a/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Many.kt b/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Many.kt index 7780780e4..f88b2701b 100644 --- a/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Many.kt +++ b/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Many.kt @@ -8,9 +8,8 @@ data class Many { override fun derive(symbol : DerivedSymbol) : Regexp { - val newReg = exp.derive(symbol) - return when (newReg) { + return when (val newReg = exp.derive(symbol)) { Epsilon -> Many(exp) Empty -> Empty else -> Concat(newReg, Many(exp)) @@ -18,5 +17,4 @@ data class Many } } -val Regexp.many : Many - get() = Many(this) \ No newline at end of file +fun some(exp: Regexp) = (exp * Many(exp)) \ No newline at end of file diff --git a/src/test/kotlin/rsm/builder/OptionalTest.kt b/src/test/kotlin/rsm/builder/OptionalTest.kt new file mode 100644 index 000000000..455a37a33 --- /dev/null +++ b/src/test/kotlin/rsm/builder/OptionalTest.kt @@ -0,0 +1,49 @@ +package rsm.builder + +import org.junit.jupiter.api.Test +import org.srcgll.grammar.combinator.Grammar +import org.srcgll.grammar.combinator.regexp.NT +import org.srcgll.grammar.combinator.regexp.Term +import org.srcgll.grammar.combinator.regexp.opt +import org.srcgll.grammar.combinator.regexp.times +import org.srcgll.rsm.RSMNonterminalEdge +import org.srcgll.rsm.RSMState +import org.srcgll.rsm.RSMTerminalEdge +import org.srcgll.rsm.symbol.Nonterminal +import org.srcgll.rsm.symbol.Terminal +import org.srcgll.rsm.writeRSMToTXT +import rsm.RsmTest +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class OptionalTest : RsmTest { + class AStar : Grammar() { + var S by NT() + + init { + setStart(S) + S = opt(Term("a")) * S + } + } + override fun getAStar(stateName: String): RSMState { + val s = Nonterminal(stateName) + val a = Terminal("a") + val st0 = RSMState(s, isStart = true) + s.startState = st0 + val st1 = RSMState(s) + val st2 = RSMState(s, isFinal = true) + st0.addTerminalEdge(RSMTerminalEdge(a, st1)) + st1.addNonterminalEdge(RSMNonterminalEdge(s, st2)) + st0.addNonterminalEdge(RSMNonterminalEdge(s, st2)) + return s.startState + } + + @Test + fun testRsm() { + val aStar = AStar() + assertNotNull(aStar.S.getNonterminal()) + writeRSMToTXT(aStar.getRsm(), "actual.txt") + writeRSMToTXT(getAStar("S"), "expected.txt") + assertTrue { equalsByNtName(getAStar("S"), aStar.getRsm()) } + } +} \ No newline at end of file From 02f6608a749b1b43aaba83f45f332f79e81e4197 Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Thu, 7 Dec 2023 15:06:51 +0300 Subject: [PATCH 03/15] Fix RSMWrite --- src/main/kotlin/org/srcgll/rsm/RSMWrite.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/org/srcgll/rsm/RSMWrite.kt b/src/main/kotlin/org/srcgll/rsm/RSMWrite.kt index ba77521a2..b503e2221 100644 --- a/src/main/kotlin/org/srcgll/rsm/RSMWrite.kt +++ b/src/main/kotlin/org/srcgll/rsm/RSMWrite.kt @@ -7,8 +7,8 @@ fun writeRSMToTXT(startState: RSMState, pathToTXT: String) { var lastId = 0 val stateToId: HashMap = HashMap() - fun getId(state: RSMState) { - stateToId.getOrPut(state) { lastId++ } + fun getId(state: RSMState): Int { + return stateToId.getOrPut(state) { lastId++ } } val states: ArrayList = ArrayList() @@ -100,8 +100,8 @@ fun writeRSMToDOT(startState: RSMState, pathToTXT: String) { var lastId = 0 val stateToId: HashMap = HashMap() - fun getId(state: RSMState) { - stateToId.getOrPut(state) { lastId++ } + fun getId(state: RSMState): Int { + return stateToId.getOrPut(state) { lastId++ } } val states: HashSet = HashSet() From d16fbd6248cd19c301c365ecdbb900bde57e3671 Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Thu, 7 Dec 2023 19:14:54 +0300 Subject: [PATCH 04/15] Refactor algorithm code --- src/main/kotlin/org/srcgll/rsm/Incremental.kt | 296 ++++++++++-------- .../kotlin/org/srcgll/rsm/symbol/Symbol.kt | 2 +- src/test/kotlin/rsm/api/UnionWithRsmTest.kt | 88 +++++- 3 files changed, 236 insertions(+), 150 deletions(-) diff --git a/src/main/kotlin/org/srcgll/rsm/Incremental.kt b/src/main/kotlin/org/srcgll/rsm/Incremental.kt index 21d0c3f54..0382efb6a 100644 --- a/src/main/kotlin/org/srcgll/rsm/Incremental.kt +++ b/src/main/kotlin/org/srcgll/rsm/Incremental.kt @@ -11,77 +11,97 @@ class Incremental(private val origin: RSMState) { private val botOrigin = RSMState(origin.nonterminal) private val cloneStatesToParents = hashMapOf() private val register = mutableSetOf() - private val rsmProxy = RsmProxyInfo(origin) + private lateinit var commonStart: RSMState /** * Only linear input */ fun constructIncremental(delta: RSMState, isRemoving: Boolean) { - writeRSMToTXT(origin, "inc/1_origin.txt") - // 1. Add all states from original RSM - for (state in rsmProxy.originAllStates) { - //optimization: common states are states of original rsm - register.add(state) - cloneStatesToParents[state] = CloneState(state, botDelta) - } - var commonStart = clone(origin, delta) - cloneStatesToParents[commonStart] = CloneState(origin, delta) - writeRSMToTXT(commonStart, "inc/2_common.txt") - addDeltaStates(commonStart) - writeRSMToTXT(commonStart, "inc/3_common.txt") - - rsmProxy.incomingEdges.putAll(rsmProxy.calculateIncomingEdges(commonStart)) - // 2. Find unreachable states and remove them from Register - val unreachable = rsmProxy.originAllStates - .filter { isUnreachable(it) } - .toHashSet() - register.removeAll(unreachable) - writeRSMToTXT(commonStart, "inc/4_restore.txt") - - // 2.5. Optimization: "save" unreachable states from original RSM - commonStart = restoreUnreachable(commonStart, unreachable) - writeRSMToTXT(commonStart, "inc/5_restore.txt") - - // 3. Find and merge equivalent states between register and new states - replaceOrRegister(commonStart) - writeRSMToTXT(commonStart, "inc/6_replace.txt") + registerOriginStates() + commonStart = clone(origin, delta) + addDeltaStates() + restoreUnreachableOrigins(unregisterUnreachable()) + mergeOrRegister() } - private fun isUnreachable(state: RSMState): Boolean { - val edges = rsmProxy.incomingEdges[state] - return edges == null || edges.size == 0 - } /** * Check queue and cloned states one by one from the end */ - private fun replaceOrRegister(startState: RSMState) { + private fun mergeOrRegister() { val used = hashSetOf() - fun replaceByDfs(state: RSMState) { + + /** + * Redirect into newState those transitions coming into oldState + * States must be equivalent on output + */ + fun merge(oldState: RSMState, newState: RSMState) { + val incomingEdges = calculateIncomingEdges(commonStart) + val edges = incomingEdges[oldState] ?: emptySet() + for (edge in edges) { + edge.state.removeEdge(Edge(oldState, edge.symbol)) + edge.state.addEdge(edge.symbol, newState) + } + } + + fun mergeRecursive(state: RSMState) { if (!used.contains(state)) { used.add(state) - for (outEdge in rsmProxy.getOutgoingEdges(state)) { - replaceByDfs(outEdge.state) + for (outEdge in getOutgoingEdges(state)) { + mergeRecursive(outEdge.state) } val equivState = register.find { state.equivalent(it) } if (equivState != null) { - replace(state, equivState) - } + merge(state, equivState) + } else (register.add(state)) } } - replaceByDfs(startState) + mergeRecursive(commonStart) } - private fun restoreUnreachable( - newStart: RSMState, - unreachable: HashSet, - ): RSMState { + + /** + * Find unreachable origin states and remove them from Register + */ + private fun unregisterUnreachable(): HashSet { + val incomingEdges = calculateIncomingEdges(commonStart) + val unreachable = getAllStates(origin).filter { + incomingEdges[it].isNullOrEmpty() + }.toHashSet() + register.removeAll(unreachable) + return unreachable + } + + /** + * Replace unreachable states from original Rsm as kotlin objects + * in equivalent place in result rsm + */ + private fun restoreUnreachableOrigins(unreachable: HashSet) { + /** + * Replace all incoming and outgoing transition from oldState to newState + * Remove all transition of oldState + */ + fun replace(oldState: RSMState, newState: RSMState) { + getOutgoingEdges(newState).forEach { newState.removeEdge(it) } + getOutgoingEdges(oldState).forEach { (state, symbol) -> newState.addEdge(symbol, state) } + calculateIncomingEdges(commonStart)[oldState]?.forEach { (state, symbol) -> + state.removeEdge( + Edge( + newState, symbol + ) + ) + state.addEdge(symbol, newState) + } + } + val used = mutableSetOf() val queue = ArrayDeque() var updatedStart: RSMState? = null - queue.add(newStart) + queue.add(commonStart) + + while (queue.isNotEmpty()) { val state = queue.removeFirst() if (!used.contains(state)) { @@ -90,134 +110,129 @@ class Incremental(private val origin: RSMState) { if (unreachable.contains(originState)) { unreachable.remove(originState) replace(state, originState) - if (state == newStart) { + if (state == commonStart) { updatedStart = originState } } - for (edge in rsmProxy.getOutgoingEdges(state)) { + for (edge in getOutgoingEdges(state)) { queue.add(edge.state) } } } - return updatedStart ?: throw Exception("Start state should be updated!!") + commonStart = updatedStart ?: throw Exception("Start state should be updated!!") } private fun clone(origin: RSMState, delta: RSMState): RSMState { + /** + * All outgoing transitions point to the corresponding intact states in , + * except for the transition with symbol a : xa ∈ Pr(w), + * which will points to the corresponding cloned state + */ + fun cloneOutgoingEdges(srcState: RSMState, destState: RSMState) { + val srcOutgoingEdges = getOutgoingEdges(srcState) + val destOutgoingEdges = getOutgoingEdges(destState) + for (srcEdge in srcOutgoingEdges) { + destState.addEdge(srcEdge.symbol, srcEdge.state) + } + } + val newState = RSMState(origin.nonterminal, origin.isStart || delta.isStart, origin.isFinal || delta.isFinal) cloneStatesToParents[newState] = CloneState(origin, delta) - rsmProxy.cloneOutgoingEdges(origin, newState) + cloneOutgoingEdges(origin, newState) return newState } - private fun addDeltaStates(commonStart: RSMState) { + private fun registerOriginStates() { + for (state in getAllStates(origin)) { + //optimization: common states are states of original rsm + register.add(state) + cloneStatesToParents[state] = CloneState(state, botDelta) + } + } + + private fun addDeltaStates() { + /** + * If source rsm contains edge with deltaSymbol -- returns clone state + * in form (, ) + * Else (, ) + */ fun cloneStep( - qLast: RSMState, - s: Symbol, - newDelta: RSMState, - origins: HashMap> + qLast: RSMState, deltaSymbol: Symbol, newDelta: RSMState, origins: HashMap> ): RSMState { - val newOrigin = origins[s]?.first() ?: botOrigin + val newOrigin = origins[deltaSymbol]?.first() ?: botOrigin val newState = clone(newOrigin, newDelta) - qLast.addEdge(s, newState) + val destEdge = getOutgoingEdges(qLast).find { it.symbol == deltaSymbol } + if (destEdge != null) { + qLast.removeEdge(destEdge) + } + qLast.addEdge(deltaSymbol, newState) return newState } var qLast = commonStart do { - val parents = cloneStatesToParents[qLast]!! - val termEdges = parents.delta.outgoingTerminalEdges.entries - val nonTermEdges = parents.delta.outgoingNonterminalEdges.entries + val (originState, deltaState) = cloneStatesToParents[qLast]!! + val termEdges = deltaState.outgoingTerminalEdges.entries + val nonTermEdges = deltaState.outgoingNonterminalEdges.entries for (t in termEdges) { - qLast = cloneStep(qLast, t.key, t.value.first(), parents.origin.outgoingTerminalEdges) + qLast = cloneStep(qLast, t.key, t.value.first(), originState.outgoingTerminalEdges) } for (nt in nonTermEdges) { - qLast = cloneStep(qLast, nt.key, nt.value.first(), parents.origin.outgoingNonterminalEdges) + qLast = cloneStep(qLast, nt.key, nt.value.first(), originState.outgoingNonterminalEdges) } } while (termEdges.isNotEmpty() || nonTermEdges.isNotEmpty()) } - private fun replace(oldState: RSMState, newState: RSMState) { - val edges = rsmProxy.incomingEdges[oldState] ?: emptySet() - for (edge in edges) { - edge.state.removeEdge(Edge(oldState, edge.symbol)) - edge.state.addEdge(edge.symbol, newState) - } - for (edge in rsmProxy.getOutgoingEdges(oldState)) { - newState.addEdge(edge.symbol, edge.state) - } - } - - data class RsmProxyInfo(val startState: RSMState) { - private var outgoingEdges = hashMapOf>() - var originAllStates: HashSet - var incomingEdges: HashMap> - - init { - originAllStates = getAllStates() - incomingEdges = calculateIncomingEdges(startState) - } - - fun cloneOutgoingEdges(srcState: RSMState, destState: RSMState) { - val srcOutgoingEdges = getOutgoingEdges(srcState) - val destOutgoingEdges = getOutgoingEdges(destState) - for (edge in srcOutgoingEdges) { - if (destOutgoingEdges.find { it.symbol == edge.symbol } == null) { - destState.addEdge(edge.symbol, edge.state) - incomingEdges.getOrPut(edge.state) { hashSetOf() }.add(Edge(destState, edge.symbol)) - } - } - } - - fun getOutgoingEdges(state: RSMState): HashSet { - var states = outgoingEdges[state] - return if (states == null) { - states = hashSetOf() - state.outgoingNonterminalEdges.map { entry -> states.addAll(entry.value.map { Edge(it, entry.key) }) } - state.outgoingTerminalEdges.map { entry -> states.addAll(entry.value.map { Edge(it, entry.key) }) } - states - } else { - states - } - } + /** + * + */ + private fun getOutgoingEdges(state: RSMState): HashSet { + val states = hashSetOf() + state.outgoingNonterminalEdges.map { entry -> states.addAll(entry.value.map { Edge(it, entry.key) }) } + state.outgoingTerminalEdges.map { entry -> states.addAll(entry.value.map { Edge(it, entry.key) }) } + return states + } - private fun getAllStates(): HashSet { - val states = hashSetOf() - val queue = ArrayDeque() - queue.add(startState) - while (queue.isNotEmpty()) { - val state = queue.removeFirst() - if (!states.contains(state)) { - states.add(state) - for (edge in getOutgoingEdges(state)) { - queue.add(edge.state) - } + /** + * Get all states of RSM reachable from startState + */ + private fun getAllStates(startState: RSMState): HashSet { + val states = hashSetOf() + val queue = ArrayDeque() + queue.add(startState) + while (queue.isNotEmpty()) { + val state = queue.removeFirst() + if (!states.contains(state)) { + states.add(state) + for (edge in getOutgoingEdges(state)) { + queue.add(edge.state) } } - return states } + return states + } - /** - * For each state get set of state which contains output edge to it - * and Symbol on this edge - */ - fun calculateIncomingEdges(state: RSMState): HashMap> { - val used = hashSetOf() - val queue = ArrayDeque() - queue.add(state) - val incomingEdges = hashMapOf>() - while (queue.isNotEmpty()) { - val nextState = queue.removeFirst() - if (!used.contains(nextState)) { - used.add(nextState) - for (edge in getOutgoingEdges(nextState)) { - incomingEdges.getOrPut(edge.state) { hashSetOf() }.add(Edge(nextState, edge.symbol)) - queue.add(edge.state) - } + /** + * For each state get set of state which contains output edge to it + * and Symbol on this edge + */ + private fun calculateIncomingEdges(state: RSMState): HashMap> { + val used = hashSetOf() + val queue = ArrayDeque() + queue.add(state) + val incomingEdges = hashMapOf>() + while (queue.isNotEmpty()) { + val nextState = queue.removeFirst() + if (!used.contains(nextState)) { + used.add(nextState) + for (edge in getOutgoingEdges(nextState)) { + incomingEdges.getOrPut(edge.state) { hashSetOf() }.add(Edge(nextState, edge.symbol)) + queue.add(edge.state) } } - return incomingEdges } + return incomingEdges } } @@ -244,10 +259,19 @@ fun RSMState.remove(delta: RSMState) { Incremental(this).constructIncremental(delta, true) } -fun RSMState.removeEdge(edge: Edge){ - when(edge.symbol){ +fun RSMState.removeEdge(edge: Edge) { + when (edge.symbol) { is Terminal<*> -> outgoingTerminalEdges[edge.symbol]!!.remove(edge.state) is Nonterminal -> outgoingNonterminalEdges[edge.symbol]!!.remove(edge.state) + else -> throw Exception("Not implemented for ${edge.symbol} instance of Symbol") + } +} + +fun RSMState.getEdges(symbol: Symbol): Set? { + return when (symbol) { + is Terminal<*> -> outgoingTerminalEdges[symbol] + is Nonterminal -> outgoingNonterminalEdges[symbol] + else -> throw Exception("Not implemented for $symbol instance of Symbol") } } diff --git a/src/main/kotlin/org/srcgll/rsm/symbol/Symbol.kt b/src/main/kotlin/org/srcgll/rsm/symbol/Symbol.kt index a34756f89..1815497c2 100644 --- a/src/main/kotlin/org/srcgll/rsm/symbol/Symbol.kt +++ b/src/main/kotlin/org/srcgll/rsm/symbol/Symbol.kt @@ -1,3 +1,3 @@ package org.srcgll.rsm.symbol -interface Symbol \ No newline at end of file +sealed interface Symbol \ No newline at end of file diff --git a/src/test/kotlin/rsm/api/UnionWithRsmTest.kt b/src/test/kotlin/rsm/api/UnionWithRsmTest.kt index 571bdd3c8..8a352cc4e 100644 --- a/src/test/kotlin/rsm/api/UnionWithRsmTest.kt +++ b/src/test/kotlin/rsm/api/UnionWithRsmTest.kt @@ -2,31 +2,31 @@ package rsm.api import org.junit.jupiter.api.Test import org.srcgll.grammar.combinator.Grammar -import org.srcgll.grammar.combinator.regexp.NT -import org.srcgll.grammar.combinator.regexp.Term -import org.srcgll.grammar.combinator.regexp.times +import org.srcgll.grammar.combinator.regexp.* import org.srcgll.rsm.RSMState import org.srcgll.rsm.add +import org.srcgll.rsm.symbol.Nonterminal import org.srcgll.rsm.symbol.Terminal import org.srcgll.rsm.writeRSMToDOT import rsm.RsmTest import kotlin.test.assertEquals +import kotlin.test.assertTrue class UnionWithRsmTest : RsmTest { - class DyckLanguage : Grammar() { - var S by NT() - - init { - setStart(S) - S = Term("(") * S * Term(")") - } - } @Test fun testSimpleAddition() { + class DyckLanguage : Grammar() { + var S by NT() + + init { + setStart(S) + S = Term("(") * S * Term(")") + } + } + val grammar = DyckLanguage() val startOrigin = grammar.getRsm() - val oldOrigin = startOrigin val deltaStart = RSMState(grammar.S.getNonterminal()!!, isStart = true, isFinal = false) val st1 = RSMState(grammar.S.getNonterminal()!!, isStart = false, isFinal = false) val st2 = RSMState(grammar.S.getNonterminal()!!, isStart = false, isFinal = false) @@ -38,6 +38,68 @@ class UnionWithRsmTest : RsmTest { writeRSMToDOT(startOrigin, "source.dot") startOrigin.add(deltaStart) writeRSMToDOT(startOrigin, "result.dot") - assertEquals(oldOrigin, startOrigin) + assertEquals(startOrigin, startOrigin) + } + + @Test + fun `test union {(ba)+, bar} with {bra}`() { + fun getExpected(nonTerm: Nonterminal): RSMState { + //construct expected RSM: Minimal automaton accepting the set (ba)+ ∪ {bar} ∪ {bra}. + val st0 = RSMState(nonTerm, isStart = true) + val st1 = RSMState(nonTerm) + val st2 = RSMState(nonTerm, isFinal = true) + val st3 = RSMState(nonTerm) + val st4 = RSMState(nonTerm) + val st5 = RSMState(nonTerm, isFinal = true) + val st6 = RSMState(nonTerm, isFinal = true) + st0.addEdge(Terminal("b"), st1) + st1.addEdge(Terminal("a"), st2) + st1.addEdge(Terminal("r"), st3) + st2.addEdge(Terminal("b"), st4) + st2.addEdge(Terminal("r"), st5) + st3.addEdge(Terminal("a"), st5) + st4.addEdge(Terminal("a"), st6) + st6.addEdge(Terminal("b"), st4) + return st0 + } + + fun getDelta(nonTerm: Nonterminal): RSMState { + //single-string automaton accepting string bra + val st0 = RSMState(nonTerm, isStart = true) + val st1 = RSMState(nonTerm) + val st2 = RSMState(nonTerm) + val st3 = RSMState(nonTerm, isFinal = true) + st0.addEdge(Terminal("b"), st1) + st1.addEdge(Terminal("r"), st2) + st2.addEdge(Terminal("a"), st3) + return st0 + } + + class BaPlusBar : Grammar() { + var S by NT() + + init { + setStart(S) + S = some(Term("b") * Term("a")) or ( + Term("b") * Term("a") * Term("r") + ) + } +// init { +// setStart(S) +// S = some(Term("b") * Term("a")) or( +// Term("b") * Term("a") * Term("b") +// ) +// } + } + + val s = Nonterminal("S") + val grammar = BaPlusBar() + grammar.getRsm().add(getDelta(s)) + val expected = getExpected(s) + val actual = grammar.getRsm() + writeRSMToDOT(actual, "inc/actual.dot") + writeRSMToDOT(expected, "inc/expected.dot") + + assertTrue { equalsByNtName(expected, actual) } } } \ No newline at end of file From 71f17d5e8ec5ac24add984f869fda21b0f04c490 Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Wed, 13 Dec 2023 15:46:14 +0300 Subject: [PATCH 05/15] add makeConcat for sting --- .../combinator/regexp/Concatenation.kt | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Concatenation.kt b/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Concatenation.kt index 49e2c67f5..23744b3f9 100644 --- a/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Concatenation.kt +++ b/src/main/kotlin/org/srcgll/grammar/combinator/regexp/Concatenation.kt @@ -1,33 +1,38 @@ package org.srcgll.grammar.combinator.regexp data class Concat -( - internal val head : Regexp, - internal val tail : Regexp, -) - : Regexp -{ + ( + internal val head: Regexp, + internal val tail: Regexp, +) : Regexp { /* D[s](h.t) = acceptEps(h).D[s](t) | D[s](h).t */ - override fun derive(symbol : DerivedSymbol) : Regexp - { + override fun derive(symbol: DerivedSymbol): Regexp { val newHead = head.derive(symbol) if (!head.acceptEpsilon()) { return when (newHead) { - Empty -> Empty + Empty -> Empty Epsilon -> tail - else -> Concat(newHead, tail) + else -> Concat(newHead, tail) } } return when (newHead) { - Empty -> tail.derive(symbol) + Empty -> tail.derive(symbol) Epsilon -> Alternative.makeAlternative(tail, tail.derive(symbol)) - else -> Alternative.makeAlternative(Concat(newHead, tail), tail.derive(symbol)) + else -> Alternative.makeAlternative(Concat(newHead, tail), tail.derive(symbol)) } } } -infix operator fun Regexp.times(other : Regexp) : Concat = Concat(head = this, other) \ No newline at end of file +infix operator fun Regexp.times(other: Regexp): Concat = Concat(head = this, other) + +fun makeConcat(vararg literals: T): Regexp { + val terms = literals.map { Term(it) } + val initial: Regexp = Concat(terms[0], terms[1]) + + return terms.subList(2, terms.size) + .fold(initial) { acc: Regexp, i: Term -> Concat(acc, i) } +} \ No newline at end of file From d218070cfd57eec7d7d04960e86365343536b8c7 Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Wed, 13 Dec 2023 15:47:21 +0300 Subject: [PATCH 06/15] fix equivalence by non terminal names --- src/test/kotlin/rsm/RsmTest.kt | 31 +++++++++++++------ .../kotlin/rsm/api/TerminalsEqualsTest.kt | 6 +++- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/test/kotlin/rsm/RsmTest.kt b/src/test/kotlin/rsm/RsmTest.kt index 178719c77..26f58bfd1 100644 --- a/src/test/kotlin/rsm/RsmTest.kt +++ b/src/test/kotlin/rsm/RsmTest.kt @@ -6,49 +6,62 @@ import org.srcgll.rsm.RSMState import org.srcgll.rsm.RSMTerminalEdge import org.srcgll.rsm.symbol.Nonterminal import org.srcgll.rsm.symbol.Terminal +import org.srcgll.rsm.writeRSMToDOT import kotlin.test.assertFalse import kotlin.test.assertTrue interface RsmTest { + fun isDebug() = false + fun writeDotInDebug(startState: RSMState, rsmName: String) { + writeRSMToDOT(startState, "inc/$rsmName.dot") + } /** * Compare two RSM, two state are equal if they have same name - * */ fun equalsByNtName(expected: RSMState, actual: RSMState): Boolean { + return equalsByNtName(expected, actual, hashMapOf()) + } + + private fun equalsByNtName(expected: RSMState, actual: RSMState, equals: HashMap): Boolean { + if (equals[expected] != null) { + return equals[expected] === actual + } if (actual.nonterminal.name == null) { throw IllegalArgumentException("For comparing by name non terminal must have unique not null name") } - if (expected.nonterminal.name != actual.nonterminal.name - || expected.isStart != actual.isStart || expected.isFinal != actual.isFinal) { + if (expected.nonterminal.name != actual.nonterminal.name || expected.isStart != actual.isStart || expected.isFinal != actual.isFinal) { return false } - if (actual.outgoingTerminalEdges.size != expected.outgoingTerminalEdges.size - || actual.outgoingNonterminalEdges.size != expected.outgoingNonterminalEdges.size) { + equals[expected] = actual + if (actual.outgoingTerminalEdges.size != expected.outgoingTerminalEdges.size || actual.outgoingNonterminalEdges.size != expected.outgoingNonterminalEdges.size) { return false } for (tEdge in expected.outgoingTerminalEdges) { val states = actual.outgoingTerminalEdges[tEdge.key] ?: return false - if (!equalsAsSetByName(tEdge.value, states)) { + if (!equalsAsSetByName(tEdge.value, states, equals)) { return false } } for (ntEdge in expected.outgoingNonterminalEdges) { val states = actual.outgoingNonterminalEdges.entries.firstOrNull { it.key.name == ntEdge.key.name } ?: return false - if (!equalsAsSetByName(ntEdge.value, states.value)) { + if (!equalsAsSetByName(ntEdge.value, states.value, equals)) { return false } } + equals[expected] = actual return true } - private fun equalsAsSetByName(expected: HashSet, actual: HashSet): Boolean { + private fun equalsAsSetByName( + expected: HashSet, actual: HashSet, equals: HashMap + ): Boolean { if (expected.size != actual.size) { return false } for (state in expected) { val curState = actual.firstOrNull { it.nonterminal.name == state.nonterminal.name } - if (curState == null || !equalsByNtName(state, curState)) { + if (curState == null || !equalsByNtName(state, curState, equals)) { return false } } diff --git a/src/test/kotlin/rsm/api/TerminalsEqualsTest.kt b/src/test/kotlin/rsm/api/TerminalsEqualsTest.kt index 502f870f6..e7fda24ac 100644 --- a/src/test/kotlin/rsm/api/TerminalsEqualsTest.kt +++ b/src/test/kotlin/rsm/api/TerminalsEqualsTest.kt @@ -6,6 +6,7 @@ import org.srcgll.grammar.combinator.regexp.NT import org.srcgll.grammar.combinator.regexp.Term import org.srcgll.grammar.combinator.regexp.or import org.srcgll.grammar.combinator.regexp.times +import org.srcgll.rsm.writeRSMToDOT import rsm.RsmTest import kotlin.test.assertTrue @@ -21,16 +22,19 @@ class TerminalsEqualsTest : RsmTest { class AStar : Grammar() { var S by NT() - val A = Term("a") + var A by NT() init { setStart(S) S = A or A * S or S * S + A = Term("a") + } } @Test fun testRsm() { + writeRSMToDOT(AStar().getRsm(), "actual.dot") assertTrue { equalsByNtName(AStar().getRsm(), AStarTerms().getRsm()) } } } \ No newline at end of file From e3b05db6d3f2b89330422d38eee7c6a515671d0b Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Wed, 13 Dec 2023 15:47:53 +0300 Subject: [PATCH 07/15] Add incremental removing for linear input --- src/main/kotlin/org/srcgll/rsm/Incremental.kt | 98 ++++---- .../kotlin/rsm/api/UnionWithLinearTest.kt | 214 ++++++++++++++++++ src/test/kotlin/rsm/api/UnionWithRsmTest.kt | 105 --------- 3 files changed, 264 insertions(+), 153 deletions(-) create mode 100644 src/test/kotlin/rsm/api/UnionWithLinearTest.kt delete mode 100644 src/test/kotlin/rsm/api/UnionWithRsmTest.kt diff --git a/src/main/kotlin/org/srcgll/rsm/Incremental.kt b/src/main/kotlin/org/srcgll/rsm/Incremental.kt index 0382efb6a..1357dd824 100644 --- a/src/main/kotlin/org/srcgll/rsm/Incremental.kt +++ b/src/main/kotlin/org/srcgll/rsm/Incremental.kt @@ -12,11 +12,13 @@ class Incremental(private val origin: RSMState) { private val cloneStatesToParents = hashMapOf() private val register = mutableSetOf() private lateinit var commonStart: RSMState + private var isRemoving = false /** * Only linear input */ fun constructIncremental(delta: RSMState, isRemoving: Boolean) { + this.isRemoving = isRemoving registerOriginStates() commonStart = clone(origin, delta) addDeltaStates() @@ -47,7 +49,7 @@ class Incremental(private val origin: RSMState) { fun mergeRecursive(state: RSMState) { if (!used.contains(state)) { used.add(state) - for (outEdge in getOutgoingEdges(state)) { + for (outEdge in state.getOutgoingEdges()) { mergeRecursive(outEdge.state) } val equivState = register.find { @@ -67,7 +69,7 @@ class Incremental(private val origin: RSMState) { */ private fun unregisterUnreachable(): HashSet { val incomingEdges = calculateIncomingEdges(commonStart) - val unreachable = getAllStates(origin).filter { + val unreachable = origin.getAllStates().filter { incomingEdges[it].isNullOrEmpty() }.toHashSet() register.removeAll(unreachable) @@ -84,14 +86,10 @@ class Incremental(private val origin: RSMState) { * Remove all transition of oldState */ fun replace(oldState: RSMState, newState: RSMState) { - getOutgoingEdges(newState).forEach { newState.removeEdge(it) } - getOutgoingEdges(oldState).forEach { (state, symbol) -> newState.addEdge(symbol, state) } + newState.getOutgoingEdges().forEach { newState.removeEdge(it) } + oldState.getOutgoingEdges().forEach { (state, symbol) -> newState.addEdge(symbol, state) } calculateIncomingEdges(commonStart)[oldState]?.forEach { (state, symbol) -> - state.removeEdge( - Edge( - newState, symbol - ) - ) + state.removeEdge(Edge(newState, symbol)) state.addEdge(symbol, newState) } } @@ -114,7 +112,7 @@ class Incremental(private val origin: RSMState) { updatedStart = originState } } - for (edge in getOutgoingEdges(state)) { + for (edge in state.getOutgoingEdges()) { queue.add(edge.state) } } @@ -129,21 +127,30 @@ class Incremental(private val origin: RSMState) { * which will points to the corresponding cloned state */ fun cloneOutgoingEdges(srcState: RSMState, destState: RSMState) { - val srcOutgoingEdges = getOutgoingEdges(srcState) - val destOutgoingEdges = getOutgoingEdges(destState) + val srcOutgoingEdges = srcState.getOutgoingEdges() + val destOutgoingEdges = destState.getOutgoingEdges() for (srcEdge in srcOutgoingEdges) { destState.addEdge(srcEdge.symbol, srcEdge.state) } } - val newState = RSMState(origin.nonterminal, origin.isStart || delta.isStart, origin.isFinal || delta.isFinal) + fun isFinal(): Boolean { + if (isRemoving && delta.isFinal) { + return false + } + return origin.isFinal || delta.isFinal + } + + fun isStart(): Boolean = origin.isStart || delta.isStart + + val newState = RSMState(origin.nonterminal, isStart(), isFinal()) cloneStatesToParents[newState] = CloneState(origin, delta) cloneOutgoingEdges(origin, newState) return newState } private fun registerOriginStates() { - for (state in getAllStates(origin)) { + for (state in origin.getAllStates()) { //optimization: common states are states of original rsm register.add(state) cloneStatesToParents[state] = CloneState(state, botDelta) @@ -161,7 +168,7 @@ class Incremental(private val origin: RSMState) { ): RSMState { val newOrigin = origins[deltaSymbol]?.first() ?: botOrigin val newState = clone(newOrigin, newDelta) - val destEdge = getOutgoingEdges(qLast).find { it.symbol == deltaSymbol } + val destEdge = qLast.getOutgoingEdges().find { it.symbol == deltaSymbol } if (destEdge != null) { qLast.removeEdge(destEdge) } @@ -183,36 +190,6 @@ class Incremental(private val origin: RSMState) { } while (termEdges.isNotEmpty() || nonTermEdges.isNotEmpty()) } - - /** - * - */ - private fun getOutgoingEdges(state: RSMState): HashSet { - val states = hashSetOf() - state.outgoingNonterminalEdges.map { entry -> states.addAll(entry.value.map { Edge(it, entry.key) }) } - state.outgoingTerminalEdges.map { entry -> states.addAll(entry.value.map { Edge(it, entry.key) }) } - return states - } - - /** - * Get all states of RSM reachable from startState - */ - private fun getAllStates(startState: RSMState): HashSet { - val states = hashSetOf() - val queue = ArrayDeque() - queue.add(startState) - while (queue.isNotEmpty()) { - val state = queue.removeFirst() - if (!states.contains(state)) { - states.add(state) - for (edge in getOutgoingEdges(state)) { - queue.add(edge.state) - } - } - } - return states - } - /** * For each state get set of state which contains output edge to it * and Symbol on this edge @@ -226,7 +203,7 @@ class Incremental(private val origin: RSMState) { val nextState = queue.removeFirst() if (!used.contains(nextState)) { used.add(nextState) - for (edge in getOutgoingEdges(nextState)) { + for (edge in nextState.getOutgoingEdges()) { incomingEdges.getOrPut(edge.state) { hashSetOf() }.add(Edge(nextState, edge.symbol)) queue.add(edge.state) } @@ -276,6 +253,31 @@ fun RSMState.getEdges(symbol: Symbol): Set? { } +/** + * Get all states of RSM reachable from startState + */ +fun RSMState.getAllStates(): HashSet { + val states = hashSetOf() + val queue = ArrayDeque() + queue.add(this) + while (queue.isNotEmpty()) { + val state = queue.removeFirst() + if (!states.contains(state)) { + states.add(state) + for (edge in state.getOutgoingEdges()) { + queue.add(edge.state) + } + } + } + return states +} - - +/** + * + */ +fun RSMState.getOutgoingEdges(): HashSet { + val states = hashSetOf() + outgoingNonterminalEdges.map { entry -> states.addAll(entry.value.map { Edge(it, entry.key) }) } + outgoingTerminalEdges.map { entry -> states.addAll(entry.value.map { Edge(it, entry.key) }) } + return states +} diff --git a/src/test/kotlin/rsm/api/UnionWithLinearTest.kt b/src/test/kotlin/rsm/api/UnionWithLinearTest.kt new file mode 100644 index 000000000..9f649fd98 --- /dev/null +++ b/src/test/kotlin/rsm/api/UnionWithLinearTest.kt @@ -0,0 +1,214 @@ +package rsm.api + +import org.junit.jupiter.api.Test +import org.srcgll.grammar.combinator.Grammar +import org.srcgll.grammar.combinator.regexp.* +import org.srcgll.rsm.RSMState +import org.srcgll.rsm.add +import org.srcgll.rsm.getAllStates +import org.srcgll.rsm.remove +import org.srcgll.rsm.symbol.Nonterminal +import org.srcgll.rsm.symbol.Terminal +import rsm.RsmTest +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * Compare incremental union of Grammar Rsm and linear Delta + * Nonterminals in Delta must be the same as in Origin Rsm! + */ +class UnionWithLinearTest : RsmTest { + + private fun testIncremental( + origin: RSMState, + delta: RSMState, + expected: RSMState, + expectedCommonStates: Int = 0, + isRemoving: Boolean = false + ) { + writeDotInDebug(expected, "expected") + writeDotInDebug(origin, "origin") + val originStates = origin.getAllStates() + if (isRemoving) { + origin.remove(delta) + } else { + origin.add(delta) + } + writeDotInDebug(origin, "actual") + assertTrue { equalsByNtName(expected, origin) } + assertEquals(expectedCommonStates, originStates.intersect(origin.getAllStates()).size) + } + + @Test + fun `test Dyck union`() { + /** + * Grammar for language S = ( S ) + */ + class DyckLanguage : Grammar() { + var S by NT() + + init { + setStart(S) + S = Term("(") * S * Term(")") + } + } + + /** + * Rsm for [ S ] + */ + fun getDelta(nonTerm: Nonterminal): RSMState { + val deltaStart = RSMState(nonTerm, isStart = true) + val st1 = RSMState(nonTerm) + val st2 = RSMState(nonTerm) + val st3 = RSMState(nonTerm, isFinal = true) + deltaStart.addEdge(Terminal("["), st1) + st1.addEdge(nonTerm, st2) + st2.addEdge(Terminal("]"), st3) + return deltaStart + } + + /** + * Grammar for language S = ( S ) | [ S ] + */ + class ExpectedLanguage : Grammar() { + var S by NT() + + init { + setStart(S) + S = Term("(") * S * Term(")") or ( + Term("[") * S * Term("]")) + } + } + + val origin = DyckLanguage().getRsm() + val s = origin.nonterminal + testIncremental(origin, getDelta(s), ExpectedLanguage().getRsm(), 4) + } + + @Test + fun `test union {(ba)+} with {bra}`() { + /** + * Grammar for language S = (ba)+ + */ + class BaPlus : Grammar() { + var S by NT() + + init { + setStart(S) + S = some(makeConcat("b", "a")) + } + } + + val origin = BaPlus().getRsm() + val s = origin.nonterminal + testIncremental(origin, getBra(s), BaPlusOrBra().getRsm(), 3) + } + + @Test + fun `test union {(ba)+, bar} with {bra}`() { + val origin = BaPlusOrBra().getRsm() + val s = origin.nonterminal + testIncremental(origin, getBar(s), BaPlusOrBarOrBra().getRsm(), 5) + } + + @Test + fun `test removing {baba} from {(ba)+, bar, bra}`() { + /** + * Grammar for language {(ba)+, bar, bra} \ {baba} + */ + class Expected : Grammar() { + var S by NT() + + init { + setStart(S) + S = makeConcat("b", "a") or ( + makeConcat("b", "a", "r") or ( + makeConcat("b", "r", "a") or ( + makeConcat("b", "a", "b", "a") * some(makeConcat("b", "a")) + ))) + } + } + + fun getBaba(nonTerm: Nonterminal): RSMState { + val st0 = RSMState(nonTerm, isStart = true) + val st1 = RSMState(nonTerm) + val st2 = RSMState(nonTerm) + val st3 = RSMState(nonTerm) + val st4 = RSMState(nonTerm, isFinal = true) + st0.addEdge(Terminal("b"), st1) + st1.addEdge(Terminal("a"), st2) + st2.addEdge(Terminal("b"), st3) + st3.addEdge(Terminal("a"), st4) + return st0 + } + fun printStates(states: Set, prefix: String = ""){ + println(prefix) + for(st in states){ + println("${st.hashCode()}") + } + } + val origin = BaPlusOrBarOrBra().getRsm() + val s = origin.nonterminal + printStates(origin.getAllStates(), "before") + testIncremental(origin, getBaba(s), Expected().getRsm(), 6, true) + printStates(origin.getAllStates(), "after") + + } + + /** + * Single-string automaton accepting string "bra" + */ + private fun getBra(nonTerm: Nonterminal): RSMState { + val st0 = RSMState(nonTerm, isStart = true) + val st1 = RSMState(nonTerm) + val st2 = RSMState(nonTerm) + val st3 = RSMState(nonTerm, isFinal = true) + st0.addEdge(Terminal("b"), st1) + st1.addEdge(Terminal("r"), st2) + st2.addEdge(Terminal("a"), st3) + return st0 + } + + /** + * Single-string automaton accepting string "bra" + */ + private fun getBar(nonTerm: Nonterminal): RSMState { + val st0 = RSMState(nonTerm, isStart = true) + val st1 = RSMState(nonTerm) + val st2 = RSMState(nonTerm) + val st3 = RSMState(nonTerm, isFinal = true) + st0.addEdge(Terminal("b"), st1) + st1.addEdge(Terminal("a"), st2) + st2.addEdge(Terminal("r"), st3) + return st0 + } + + /** + * Grammar for language {(ba)+, bar, bra} + */ + private class BaPlusOrBarOrBra : Grammar() { + var S by NT() + + init { + setStart(S) + S = some(makeConcat("b", "a")) or ( + makeConcat("b", "a", "r") or ( + makeConcat("b", "r", "a"))) + } + } + + /** + * Minimal automaton accepting the language {(ba)+, bra} + */ + private class BaPlusOrBra : Grammar() { + var S by NT() + + init { + setStart(S) + S = some(makeConcat("b", "a")) or + makeConcat("b", "r", "a") + } + } + + +} \ No newline at end of file diff --git a/src/test/kotlin/rsm/api/UnionWithRsmTest.kt b/src/test/kotlin/rsm/api/UnionWithRsmTest.kt deleted file mode 100644 index 8a352cc4e..000000000 --- a/src/test/kotlin/rsm/api/UnionWithRsmTest.kt +++ /dev/null @@ -1,105 +0,0 @@ -package rsm.api - -import org.junit.jupiter.api.Test -import org.srcgll.grammar.combinator.Grammar -import org.srcgll.grammar.combinator.regexp.* -import org.srcgll.rsm.RSMState -import org.srcgll.rsm.add -import org.srcgll.rsm.symbol.Nonterminal -import org.srcgll.rsm.symbol.Terminal -import org.srcgll.rsm.writeRSMToDOT -import rsm.RsmTest -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class UnionWithRsmTest : RsmTest { - - @Test - fun testSimpleAddition() { - class DyckLanguage : Grammar() { - var S by NT() - - init { - setStart(S) - S = Term("(") * S * Term(")") - } - } - - val grammar = DyckLanguage() - val startOrigin = grammar.getRsm() - val deltaStart = RSMState(grammar.S.getNonterminal()!!, isStart = true, isFinal = false) - val st1 = RSMState(grammar.S.getNonterminal()!!, isStart = false, isFinal = false) - val st2 = RSMState(grammar.S.getNonterminal()!!, isStart = false, isFinal = false) - val st3 = RSMState(grammar.S.getNonterminal()!!, isStart = false, isFinal = true) - deltaStart.addEdge(Terminal("["), st1) - st1.addEdge(grammar.S.getNonterminal()!!, st2) - st2.addEdge(Terminal("]"), st3) - writeRSMToDOT(deltaStart, "delta.dot") - writeRSMToDOT(startOrigin, "source.dot") - startOrigin.add(deltaStart) - writeRSMToDOT(startOrigin, "result.dot") - assertEquals(startOrigin, startOrigin) - } - - @Test - fun `test union {(ba)+, bar} with {bra}`() { - fun getExpected(nonTerm: Nonterminal): RSMState { - //construct expected RSM: Minimal automaton accepting the set (ba)+ ∪ {bar} ∪ {bra}. - val st0 = RSMState(nonTerm, isStart = true) - val st1 = RSMState(nonTerm) - val st2 = RSMState(nonTerm, isFinal = true) - val st3 = RSMState(nonTerm) - val st4 = RSMState(nonTerm) - val st5 = RSMState(nonTerm, isFinal = true) - val st6 = RSMState(nonTerm, isFinal = true) - st0.addEdge(Terminal("b"), st1) - st1.addEdge(Terminal("a"), st2) - st1.addEdge(Terminal("r"), st3) - st2.addEdge(Terminal("b"), st4) - st2.addEdge(Terminal("r"), st5) - st3.addEdge(Terminal("a"), st5) - st4.addEdge(Terminal("a"), st6) - st6.addEdge(Terminal("b"), st4) - return st0 - } - - fun getDelta(nonTerm: Nonterminal): RSMState { - //single-string automaton accepting string bra - val st0 = RSMState(nonTerm, isStart = true) - val st1 = RSMState(nonTerm) - val st2 = RSMState(nonTerm) - val st3 = RSMState(nonTerm, isFinal = true) - st0.addEdge(Terminal("b"), st1) - st1.addEdge(Terminal("r"), st2) - st2.addEdge(Terminal("a"), st3) - return st0 - } - - class BaPlusBar : Grammar() { - var S by NT() - - init { - setStart(S) - S = some(Term("b") * Term("a")) or ( - Term("b") * Term("a") * Term("r") - ) - } -// init { -// setStart(S) -// S = some(Term("b") * Term("a")) or( -// Term("b") * Term("a") * Term("b") -// ) -// } - } - - val s = Nonterminal("S") - val grammar = BaPlusBar() - grammar.getRsm().add(getDelta(s)) - val expected = getExpected(s) - val actual = grammar.getRsm() - writeRSMToDOT(actual, "inc/actual.dot") - writeRSMToDOT(expected, "inc/expected.dot") - - assertTrue { equalsByNtName(expected, actual) } - } -} \ No newline at end of file From 5d476dd9cac87d1299022374fcd8bb941e9dab9a Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Thu, 14 Dec 2023 17:44:23 +0300 Subject: [PATCH 08/15] Fix incremental restoring origin states --- src/main/kotlin/org/srcgll/rsm/Incremental.kt | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/org/srcgll/rsm/Incremental.kt b/src/main/kotlin/org/srcgll/rsm/Incremental.kt index 1357dd824..28b22ba15 100644 --- a/src/main/kotlin/org/srcgll/rsm/Incremental.kt +++ b/src/main/kotlin/org/srcgll/rsm/Incremental.kt @@ -22,7 +22,8 @@ class Incremental(private val origin: RSMState) { registerOriginStates() commonStart = clone(origin, delta) addDeltaStates() - restoreUnreachableOrigins(unregisterUnreachable()) + val unreachable = unregisterUnreachable() + restoreUnreachableOrigins(unreachable) mergeOrRegister() } @@ -86,12 +87,16 @@ class Incremental(private val origin: RSMState) { * Remove all transition of oldState */ fun replace(oldState: RSMState, newState: RSMState) { - newState.getOutgoingEdges().forEach { newState.removeEdge(it) } - oldState.getOutgoingEdges().forEach { (state, symbol) -> newState.addEdge(symbol, state) } + newState.getOutgoingEdges().forEach { edge -> + newState.removeEdge(edge) + } calculateIncomingEdges(commonStart)[oldState]?.forEach { (state, symbol) -> - state.removeEdge(Edge(newState, symbol)) + state.removeEdge(oldState, symbol) state.addEdge(symbol, newState) } + oldState.getOutgoingEdges().forEach { (state, symbol) -> + newState.addEdge(symbol, state) + } } val used = mutableSetOf() @@ -99,9 +104,8 @@ class Incremental(private val origin: RSMState) { var updatedStart: RSMState? = null queue.add(commonStart) - while (queue.isNotEmpty()) { - val state = queue.removeFirst() + var state = queue.removeFirst() if (!used.contains(state)) { used.add(state) val originState = cloneStatesToParents[state]!!.origin @@ -110,14 +114,18 @@ class Incremental(private val origin: RSMState) { replace(state, originState) if (state == commonStart) { updatedStart = originState + commonStart = originState } + state = originState } for (edge in state.getOutgoingEdges()) { queue.add(edge.state) } } } - commonStart = updatedStart ?: throw Exception("Start state should be updated!!") + if (updatedStart == null) { + throw Exception("Start state should be updated!!") + } } private fun clone(origin: RSMState, delta: RSMState): RSMState { @@ -128,7 +136,6 @@ class Incremental(private val origin: RSMState) { */ fun cloneOutgoingEdges(srcState: RSMState, destState: RSMState) { val srcOutgoingEdges = srcState.getOutgoingEdges() - val destOutgoingEdges = destState.getOutgoingEdges() for (srcEdge in srcOutgoingEdges) { destState.addEdge(srcEdge.symbol, srcEdge.state) } @@ -151,7 +158,7 @@ class Incremental(private val origin: RSMState) { private fun registerOriginStates() { for (state in origin.getAllStates()) { - //optimization: common states are states of original rsm + //modification: common states are states of original rsm register.add(state) cloneStatesToParents[state] = CloneState(state, botDelta) } @@ -236,22 +243,15 @@ fun RSMState.remove(delta: RSMState) { Incremental(this).constructIncremental(delta, true) } -fun RSMState.removeEdge(edge: Edge) { - when (edge.symbol) { - is Terminal<*> -> outgoingTerminalEdges[edge.symbol]!!.remove(edge.state) - is Nonterminal -> outgoingNonterminalEdges[edge.symbol]!!.remove(edge.state) - else -> throw Exception("Not implemented for ${edge.symbol} instance of Symbol") - } -} - -fun RSMState.getEdges(symbol: Symbol): Set? { - return when (symbol) { - is Terminal<*> -> outgoingTerminalEdges[symbol] - is Nonterminal -> outgoingNonterminalEdges[symbol] +fun RSMState.removeEdge(state: RSMState, symbol: Symbol) { + when (symbol) { + is Terminal<*> -> outgoingTerminalEdges[symbol]!!.remove(state) + is Nonterminal -> outgoingNonterminalEdges[symbol]!!.remove(state) else -> throw Exception("Not implemented for $symbol instance of Symbol") } } +fun RSMState.removeEdge(edge: Edge) = this.removeEdge(edge.state, edge.symbol) /** * Get all states of RSM reachable from startState @@ -272,9 +272,6 @@ fun RSMState.getAllStates(): HashSet { return states } -/** - * - */ fun RSMState.getOutgoingEdges(): HashSet { val states = hashSetOf() outgoingNonterminalEdges.map { entry -> states.addAll(entry.value.map { Edge(it, entry.key) }) } From 44be5c5e7259ad9e3a22274f3624084056e9d80c Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Thu, 14 Dec 2023 18:41:39 +0300 Subject: [PATCH 09/15] Remove deadlocks states --- src/main/kotlin/org/srcgll/rsm/Incremental.kt | 57 +++++++++- src/test/kotlin/rsm/RsmTest.kt | 3 +- .../kotlin/rsm/api/UnionWithLinearTest.kt | 103 +++++++++--------- 3 files changed, 106 insertions(+), 57 deletions(-) diff --git a/src/main/kotlin/org/srcgll/rsm/Incremental.kt b/src/main/kotlin/org/srcgll/rsm/Incremental.kt index 28b22ba15..df3d48f6f 100644 --- a/src/main/kotlin/org/srcgll/rsm/Incremental.kt +++ b/src/main/kotlin/org/srcgll/rsm/Incremental.kt @@ -25,6 +25,7 @@ class Incremental(private val origin: RSMState) { val unreachable = unregisterUnreachable() restoreUnreachableOrigins(unreachable) mergeOrRegister() + removeDeadlocks() } @@ -64,6 +65,41 @@ class Incremental(private val origin: RSMState) { mergeRecursive(commonStart) } + private enum class State { Deadlock, Final, Known } + + /** + * Remove deadlocks -- states with no outgoing edges that are not finite + */ + private fun removeDeadlocks() { + val canGoToFinal = HashMap() + fun removeRecursive(state: RSMState) { + if (!canGoToFinal.contains(state)) { + if (state.isFinal) { + canGoToFinal[state] = State.Final + } else { + canGoToFinal[state] = State.Known + } + if (state.getOutgoingEdges().isEmpty()) { + canGoToFinal[state] = if (state.isFinal) State.Final else State.Deadlock + } + for (outEdge in state.getOutgoingEdges()) { + removeRecursive(outEdge.state) + when (canGoToFinal[outEdge.state]) { + State.Deadlock -> state.removeEdge(outEdge) + //cycle + State.Known -> {} + State.Final -> canGoToFinal[state] = State.Final + else -> throw IllegalArgumentException() + } + } + if (canGoToFinal[state] != State.Final) { + canGoToFinal[state] = State.Deadlock + } + } + } + removeRecursive(commonStart) + } + /** * Find unreachable origin states and remove them from Register @@ -171,7 +207,10 @@ class Incremental(private val origin: RSMState) { * Else (, ) */ fun cloneStep( - qLast: RSMState, deltaSymbol: Symbol, newDelta: RSMState, origins: HashMap> + qLast: RSMState, + deltaSymbol: Symbol, + newDelta: RSMState, + origins: HashMap> ): RSMState { val newOrigin = origins[deltaSymbol]?.first() ?: botOrigin val newState = clone(newOrigin, newDelta) @@ -245,9 +284,19 @@ fun RSMState.remove(delta: RSMState) { fun RSMState.removeEdge(state: RSMState, symbol: Symbol) { when (symbol) { - is Terminal<*> -> outgoingTerminalEdges[symbol]!!.remove(state) - is Nonterminal -> outgoingNonterminalEdges[symbol]!!.remove(state) - else -> throw Exception("Not implemented for $symbol instance of Symbol") + is Terminal<*> -> { + outgoingTerminalEdges[symbol]!!.remove(state) + if (outgoingTerminalEdges[symbol]!!.isEmpty()) { + outgoingTerminalEdges.remove(symbol) + } + } + + is Nonterminal -> { + outgoingNonterminalEdges[symbol]!!.remove(state) + if (outgoingNonterminalEdges[symbol]!!.isEmpty()) { + outgoingNonterminalEdges.remove(symbol) + } + } } } diff --git a/src/test/kotlin/rsm/RsmTest.kt b/src/test/kotlin/rsm/RsmTest.kt index 26f58bfd1..0bc6422fa 100644 --- a/src/test/kotlin/rsm/RsmTest.kt +++ b/src/test/kotlin/rsm/RsmTest.kt @@ -13,8 +13,9 @@ import kotlin.test.assertTrue interface RsmTest { fun isDebug() = false fun writeDotInDebug(startState: RSMState, rsmName: String) { - writeRSMToDOT(startState, "inc/$rsmName.dot") + if (isDebug()) writeRSMToDOT(startState, "inc/$rsmName.dot") } + /** * Compare two RSM, two state are equal if they have same name */ diff --git a/src/test/kotlin/rsm/api/UnionWithLinearTest.kt b/src/test/kotlin/rsm/api/UnionWithLinearTest.kt index 9f649fd98..b4bea127d 100644 --- a/src/test/kotlin/rsm/api/UnionWithLinearTest.kt +++ b/src/test/kotlin/rsm/api/UnionWithLinearTest.kt @@ -23,7 +23,7 @@ class UnionWithLinearTest : RsmTest { origin: RSMState, delta: RSMState, expected: RSMState, - expectedCommonStates: Int = 0, + expectedCommonStates: Int, isRemoving: Boolean = false ) { writeDotInDebug(expected, "expected") @@ -41,48 +41,9 @@ class UnionWithLinearTest : RsmTest { @Test fun `test Dyck union`() { - /** - * Grammar for language S = ( S ) - */ - class DyckLanguage : Grammar() { - var S by NT() - - init { - setStart(S) - S = Term("(") * S * Term(")") - } - } - - /** - * Rsm for [ S ] - */ - fun getDelta(nonTerm: Nonterminal): RSMState { - val deltaStart = RSMState(nonTerm, isStart = true) - val st1 = RSMState(nonTerm) - val st2 = RSMState(nonTerm) - val st3 = RSMState(nonTerm, isFinal = true) - deltaStart.addEdge(Terminal("["), st1) - st1.addEdge(nonTerm, st2) - st2.addEdge(Terminal("]"), st3) - return deltaStart - } - - /** - * Grammar for language S = ( S ) | [ S ] - */ - class ExpectedLanguage : Grammar() { - var S by NT() - - init { - setStart(S) - S = Term("(") * S * Term(")") or ( - Term("[") * S * Term("]")) - } - } - val origin = DyckLanguage().getRsm() val s = origin.nonterminal - testIncremental(origin, getDelta(s), ExpectedLanguage().getRsm(), 4) + testIncremental(origin, getDyckDelta(s), MultiDyck().getRsm(), 4) } @Test @@ -105,10 +66,10 @@ class UnionWithLinearTest : RsmTest { } @Test - fun `test union {(ba)+, bar} with {bra}`() { + fun `test union {(ba)+, bra} with {bar}`() { val origin = BaPlusOrBra().getRsm() val s = origin.nonterminal - testIncremental(origin, getBar(s), BaPlusOrBarOrBra().getRsm(), 5) + testIncremental(origin, getBar(s), BaPlusOrBarOrBra().getRsm(), 6) } @Test @@ -141,18 +102,20 @@ class UnionWithLinearTest : RsmTest { st3.addEdge(Terminal("a"), st4) return st0 } - fun printStates(states: Set, prefix: String = ""){ - println(prefix) - for(st in states){ - println("${st.hashCode()}") - } - } + val origin = BaPlusOrBarOrBra().getRsm() val s = origin.nonterminal - printStates(origin.getAllStates(), "before") - testIncremental(origin, getBaba(s), Expected().getRsm(), 6, true) - printStates(origin.getAllStates(), "after") + testIncremental(origin, getBaba(s), Expected().getRsm(), 7, true) + + } + + + @Test + fun `test removing brace from Dyck language`() { + val origin = MultiDyck().getRsm() + val s = origin.nonterminal + testIncremental(origin, getDyckDelta(s), DyckLanguage().getRsm(), 4, true) } /** @@ -210,5 +173,41 @@ class UnionWithLinearTest : RsmTest { } } + /** + * Rsm for [[ S ]] + */ + private fun getDyckDelta(nonTerm: Nonterminal): RSMState { + val deltaStart = RSMState(nonTerm, isStart = true) + val st1 = RSMState(nonTerm) + val st2 = RSMState(nonTerm) + val st3 = RSMState(nonTerm, isFinal = true) + deltaStart.addEdge(Terminal("["), st1) + st1.addEdge(nonTerm, st2) + st2.addEdge(Terminal("]"), st3) + return deltaStart + } + + /** + * Grammar for language S = ( S ) | [[ S ]] + */ + private class MultiDyck : Grammar() { + var S by NT() + + init { + setStart(S) + S = Term("[") * S * Term("]") or Term("(") * S * Term(")") + } + } + + /** + * Grammar for language S = ( S ) + */ + class DyckLanguage : Grammar() { + var S by NT() + init { + setStart(S) + S = Term("(") * S * Term(")") + } + } } \ No newline at end of file From f2282a1e0d3818d3187581392814d2b7f2a0a7cb Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Thu, 14 Dec 2023 19:11:19 +0300 Subject: [PATCH 10/15] Rename `Incremental` to `DynamicRsm` --- .../org/srcgll/rsm/{Incremental.kt => DynamicRsm.kt} | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) rename src/main/kotlin/org/srcgll/rsm/{Incremental.kt => DynamicRsm.kt} (96%) diff --git a/src/main/kotlin/org/srcgll/rsm/Incremental.kt b/src/main/kotlin/org/srcgll/rsm/DynamicRsm.kt similarity index 96% rename from src/main/kotlin/org/srcgll/rsm/Incremental.kt rename to src/main/kotlin/org/srcgll/rsm/DynamicRsm.kt index df3d48f6f..9a5f5ca04 100644 --- a/src/main/kotlin/org/srcgll/rsm/Incremental.kt +++ b/src/main/kotlin/org/srcgll/rsm/DynamicRsm.kt @@ -4,7 +4,13 @@ import org.srcgll.rsm.symbol.Nonterminal import org.srcgll.rsm.symbol.Symbol import org.srcgll.rsm.symbol.Terminal -class Incremental(private val origin: RSMState) { +/** + * Modified implementation of the Incremental algorithm for dynamic rsm change: + * adding and removing line inputs + * [Incremental Construction and Maintenance of Minimal Finite-State Automata] + * (https://aclanthology.org/J02-2004) (Carrasco & Forcada, CL 2002) + */ +class DynamicRsm(private val origin: RSMState) { data class CloneState(val origin: RSMState, val delta: RSMState) private val botDelta = RSMState(origin.nonterminal) @@ -275,11 +281,11 @@ fun RSMState.equivalent(other: RSMState): Boolean { } fun RSMState.add(delta: RSMState) { - Incremental(this).constructIncremental(delta, false) + DynamicRsm(this).constructIncremental(delta, false) } fun RSMState.remove(delta: RSMState) { - Incremental(this).constructIncremental(delta, true) + DynamicRsm(this).constructIncremental(delta, true) } fun RSMState.removeEdge(state: RSMState, symbol: Symbol) { From 4567685a703b2b176d6db4a2f3c779d60348f904 Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Thu, 14 Dec 2023 19:36:37 +0300 Subject: [PATCH 11/15] Fix tests issues --- src/main/kotlin/org/srcgll/rsm/DynamicRsm.kt | 1 + src/main/kotlin/org/srcgll/rsm/RSMState.kt | 1 + src/test/kotlin/rsm/api/TerminalsEqualsTest.kt | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/srcgll/rsm/DynamicRsm.kt b/src/main/kotlin/org/srcgll/rsm/DynamicRsm.kt index 9a5f5ca04..bcd790aa7 100644 --- a/src/main/kotlin/org/srcgll/rsm/DynamicRsm.kt +++ b/src/main/kotlin/org/srcgll/rsm/DynamicRsm.kt @@ -303,6 +303,7 @@ fun RSMState.removeEdge(state: RSMState, symbol: Symbol) { outgoingNonterminalEdges.remove(symbol) } } + else -> throw IllegalArgumentException("removing not implemented for Symbol implementation $symbol") } } diff --git a/src/main/kotlin/org/srcgll/rsm/RSMState.kt b/src/main/kotlin/org/srcgll/rsm/RSMState.kt index 8d2b5712c..003770205 100644 --- a/src/main/kotlin/org/srcgll/rsm/RSMState.kt +++ b/src/main/kotlin/org/srcgll/rsm/RSMState.kt @@ -47,6 +47,7 @@ class RSMState when (label){ is Terminal<*> -> addTerminalEdge(RSMTerminalEdge(label, head)) is Nonterminal -> addNonterminalEdge(RSMNonterminalEdge(label, head)) + else -> throw IllegalArgumentException("removing not implemented for Symbol implementation $label") } } } diff --git a/src/test/kotlin/rsm/api/TerminalsEqualsTest.kt b/src/test/kotlin/rsm/api/TerminalsEqualsTest.kt index e7fda24ac..1d47eea49 100644 --- a/src/test/kotlin/rsm/api/TerminalsEqualsTest.kt +++ b/src/test/kotlin/rsm/api/TerminalsEqualsTest.kt @@ -8,7 +8,7 @@ import org.srcgll.grammar.combinator.regexp.or import org.srcgll.grammar.combinator.regexp.times import org.srcgll.rsm.writeRSMToDOT import rsm.RsmTest -import kotlin.test.assertTrue +import kotlin.test.assertFalse class TerminalsEqualsTest : RsmTest { class AStarTerms : Grammar() { @@ -35,6 +35,6 @@ class TerminalsEqualsTest : RsmTest { @Test fun testRsm() { writeRSMToDOT(AStar().getRsm(), "actual.dot") - assertTrue { equalsByNtName(AStar().getRsm(), AStarTerms().getRsm()) } + assertFalse { equalsByNtName(AStar().getRsm(), AStarTerms().getRsm()) } } } \ No newline at end of file From 611c470d4486acf19235212b4d5f9cdeaf12a325 Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Thu, 21 Dec 2023 17:17:25 +0300 Subject: [PATCH 12/15] Add `StandAloneNt` -- helper class for construct delta Rsm --- src/test/kotlin/rsm/RsmTest.kt | 58 ++++-- .../kotlin/rsm/api/LinearDynamicDyckTest.kt | 179 ++++++++++++++++++ ...nearTest.kt => LinearDynamicSimpleTest.kt} | 78 +------- src/test/kotlin/rsm/builder/AStarTest.kt | 2 +- src/test/kotlin/rsm/builder/OptionalTest.kt | 6 +- .../rsm/builder/StandAloneBuilderTest.kt | 38 ++++ 6 files changed, 267 insertions(+), 94 deletions(-) create mode 100644 src/test/kotlin/rsm/api/LinearDynamicDyckTest.kt rename src/test/kotlin/rsm/api/{UnionWithLinearTest.kt => LinearDynamicSimpleTest.kt} (65%) create mode 100644 src/test/kotlin/rsm/builder/StandAloneBuilderTest.kt diff --git a/src/test/kotlin/rsm/RsmTest.kt b/src/test/kotlin/rsm/RsmTest.kt index 0bc6422fa..9f463a05c 100644 --- a/src/test/kotlin/rsm/RsmTest.kt +++ b/src/test/kotlin/rsm/RsmTest.kt @@ -1,19 +1,20 @@ package rsm import org.junit.jupiter.api.Test -import org.srcgll.rsm.RSMNonterminalEdge -import org.srcgll.rsm.RSMState -import org.srcgll.rsm.RSMTerminalEdge +import org.srcgll.rsm.* import org.srcgll.rsm.symbol.Nonterminal import org.srcgll.rsm.symbol.Terminal -import org.srcgll.rsm.writeRSMToDOT +import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue interface RsmTest { - fun isDebug() = false + fun isDebug() = true fun writeDotInDebug(startState: RSMState, rsmName: String) { - if (isDebug()) writeRSMToDOT(startState, "inc/$rsmName.dot") + if (isDebug()) { + writeRSMToDOT(startState, "inc/$rsmName.dot") + writeRSMToTXT(startState, "inc/$rsmName.txt") + } } /** @@ -38,7 +39,10 @@ interface RsmTest { return false } for (tEdge in expected.outgoingTerminalEdges) { - val states = actual.outgoingTerminalEdges[tEdge.key] ?: return false + val states = actual.outgoingTerminalEdges[tEdge.key] + if(states == null) { + return false + } if (!equalsAsSetByName(tEdge.value, states, equals)) { return false } @@ -69,7 +73,40 @@ interface RsmTest { return true } - fun getAStar(stateName: String): RSMState { + fun testIncremental( + origin: RSMState, + delta: RSMState, + expected: RSMState, + expectedCommonStates: Int, + isRemoving: Boolean = false + ) { + writeDotInDebug(delta, "delta") + writeDotInDebug(expected, "expected") + writeDotInDebug(origin, "origin") + val originStates = origin.getAllStates() + if (isRemoving) { + origin.remove(delta) + } else { + origin.add(delta) + } + writeDotInDebug(origin, "actual") + assertTrue { equalsByNtName(expected, origin) } + assertEquals(expectedCommonStates, originStates.intersect(origin.getAllStates()).size) + } + + + @Test + fun testEquals() { + assertTrue { equalsByNtName(getAStarRSM("S"), getAStarRSM("S")) } + assertFalse { equalsByNtName(getAStarRSM("S"), getAStarRSM("K")) } + } + + @Test + fun debugTest(){ + assertFalse(isDebug(), "\"Debug\" flag must be set to false before committing.") + } + + fun getAStarRSM(stateName: String): RSMState { val s = Nonterminal(stateName) val a = Terminal("a") val st0 = RSMState(s, isStart = true) @@ -84,9 +121,4 @@ interface RsmTest { return s.startState } - @Test - fun testEquals() { - assertTrue { equalsByNtName(getAStar("S"), getAStar("S")) } - assertFalse { equalsByNtName(getAStar("S"), getAStar("K")) } - } } \ No newline at end of file diff --git a/src/test/kotlin/rsm/api/LinearDynamicDyckTest.kt b/src/test/kotlin/rsm/api/LinearDynamicDyckTest.kt new file mode 100644 index 000000000..dec4540b9 --- /dev/null +++ b/src/test/kotlin/rsm/api/LinearDynamicDyckTest.kt @@ -0,0 +1,179 @@ +package rsm.api + +import org.junit.jupiter.api.Test +import org.srcgll.grammar.combinator.Grammar +import org.srcgll.grammar.combinator.regexp.* +import org.srcgll.rsm.RSMState +import org.srcgll.rsm.symbol.Nonterminal +import org.srcgll.rsm.symbol.Terminal +import rsm.RsmTest + +/** + * Compare incremental union of Grammar Rsm and linear Delta + * Nonterminals in Delta must be the same as in Origin Rsm! + */ +class LinearDynamicDyckTest : RsmTest { + + @Test + fun `test Dyck addition`() { + val origin = DyckLanguage().getRsm() + val delta = getDyckDelta(origin.nonterminal, "[", "]") + testIncremental(origin, delta, Dyck2().getRsm(), 4) + } + + @Test + fun `test removing brace from Dyck language`() { + val origin = Dyck2().getRsm() + val delta = getDyckDelta(origin.nonterminal, "[", "]") + testIncremental(origin, delta, DyckLanguage().getRsm(), 4, true) + } + + @Test + fun `test ExtDyck2 removing`() { + val origin = ExtDyck2().getRsm() + val delta = getExtDyckDelta(origin.nonterminal, "[", "]") + testIncremental(origin, delta, ExtDyck1().getRsm(), 5, true) + } + + @Test + fun `test ExtDyck2 addition`() { + val origin = ExtDyck2().getRsm() + val delta = getExtDyckDelta(origin.nonterminal, "{", "}") + testIncremental(origin, delta, ExtDyck3().getRsm(), 7) + } + + @Test + fun `test DyckStar addition`() { + val origin = DyckStar1().getRsm() + val delta = getStarDyckDelta(origin.nonterminal, "[", "]") + testIncremental(origin, delta, DyckStar2().getRsm(), 3) + } + + + /** + * Rsm for <'openBrace' nonTerm 'closeBrace'> + */ + private fun getDyckDelta(nonTerm: Nonterminal, openBrace: String, closeBrace: String): RSMState { + val nt = StandAloneNt(nonTerm) + return nt.buildRsm(Term(openBrace) * nt * Term(closeBrace)) + } + + /** + * Rsm for <'openBrace' nonTerm 'closeBrace' nonTerm> + */ + private fun getExtDyckDelta(nonTerm: Nonterminal, openBrace: String, closeBrace: String): RSMState { + val nt = StandAloneNt(nonTerm) + return nt.buildRsm(Term(openBrace) * nt * Term(closeBrace) * nt) + } + + /** + * Rsm for <'openBrace' nonTerm 'closeBrace' nonTerm> + */ + private fun getStarDyckDelta(nonTerm: Nonterminal, openBrace: String, closeBrace: String): RSMState { + val nt = StandAloneNt(nonTerm) + return nt.buildRsm(Many(Term(openBrace) * nt * Term(closeBrace))) + } + + /** + * Grammar for language S = '(' S ')' | '[[' S ']]' + */ + private class Dyck2 : Grammar() { + var S by NT() + + init { + setStart(S) + S = Term("[") * S * Term("]") or Term("(") * S * Term(")") + } + } + + /** + * Grammar for language S = eps | '(' S ')' S + */ + private class ExtDyck1 : Grammar() { + var S by NT() + + init { + setStart(S) + S = Epsilon or Term("(") * S * Term(")") * S + } + } + + /** + * Grammar for language S = eps | '(' S ')' S | '[[' S ']]' S + */ + private class ExtDyck2 : Grammar() { + var S by NT() + + init { + setStart(S) + S = Epsilon or Term("[") * S * Term("]") * S or Term("(") * S * Term(")") * S + } + } + + /** + * Grammar for language S = eps | '(' S ')' S | '[[' S ']]' S + */ + private class ExtDyck3 : Grammar() { + var S by NT() + + init { + setStart(S) + S = Epsilon or Term("[") * S * Term("]") * S or ( + Term("(") * S * Term(")") * S) or ( + Term("{") * S * Term("}") * S + ) + } + } + + + /** + * Grammar for language S = ( '(' S ')' )* + */ + private class DyckStar1 : Grammar() { + var S by NT() + + init { + setStart(S) + S = Many(Term("(") * S * Term(")")) + } + } + + /** + * Grammar for language S = ( '(' S ')' )* + */ + private class DyckStar2 : Grammar() { + var S by NT() + + init { + setStart(S) + S = Many(Term("(") * S * Term(")") or ( + Term("[") * S * Term("]")) + ) + } + } + + /** + * Get RSM for S = Many('{' S '}') with nonTerminal S + */ + private fun getDyckStar1Delta(nonTerm: Nonterminal): RSMState { + val deltaStart = RSMState(nonTerm, isStart = true, isFinal = true) + val st1 = RSMState(nonTerm) + val st2 = RSMState(nonTerm) + deltaStart.addEdge(Terminal("{"), st1) + st1.addEdge(nonTerm, st2) + st2.addEdge(Terminal("}"), deltaStart) + return deltaStart + } + + /** + * Grammar for language S = ( S ) + */ + class DyckLanguage : Grammar() { + var S by NT() + + init { + setStart(S) + S = Term("(") * S * Term(")") + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/rsm/api/UnionWithLinearTest.kt b/src/test/kotlin/rsm/api/LinearDynamicSimpleTest.kt similarity index 65% rename from src/test/kotlin/rsm/api/UnionWithLinearTest.kt rename to src/test/kotlin/rsm/api/LinearDynamicSimpleTest.kt index b4bea127d..cd556a9f8 100644 --- a/src/test/kotlin/rsm/api/UnionWithLinearTest.kt +++ b/src/test/kotlin/rsm/api/LinearDynamicSimpleTest.kt @@ -4,9 +4,7 @@ import org.junit.jupiter.api.Test import org.srcgll.grammar.combinator.Grammar import org.srcgll.grammar.combinator.regexp.* import org.srcgll.rsm.RSMState -import org.srcgll.rsm.add import org.srcgll.rsm.getAllStates -import org.srcgll.rsm.remove import org.srcgll.rsm.symbol.Nonterminal import org.srcgll.rsm.symbol.Terminal import rsm.RsmTest @@ -17,34 +15,7 @@ import kotlin.test.assertTrue * Compare incremental union of Grammar Rsm and linear Delta * Nonterminals in Delta must be the same as in Origin Rsm! */ -class UnionWithLinearTest : RsmTest { - - private fun testIncremental( - origin: RSMState, - delta: RSMState, - expected: RSMState, - expectedCommonStates: Int, - isRemoving: Boolean = false - ) { - writeDotInDebug(expected, "expected") - writeDotInDebug(origin, "origin") - val originStates = origin.getAllStates() - if (isRemoving) { - origin.remove(delta) - } else { - origin.add(delta) - } - writeDotInDebug(origin, "actual") - assertTrue { equalsByNtName(expected, origin) } - assertEquals(expectedCommonStates, originStates.intersect(origin.getAllStates()).size) - } - - @Test - fun `test Dyck union`() { - val origin = DyckLanguage().getRsm() - val s = origin.nonterminal - testIncremental(origin, getDyckDelta(s), MultiDyck().getRsm(), 4) - } +class LinearDynamicSimpleTest : RsmTest { @Test fun `test union {(ba)+} with {bra}`() { @@ -109,15 +80,6 @@ class UnionWithLinearTest : RsmTest { } - - @Test - fun `test removing brace from Dyck language`() { - val origin = MultiDyck().getRsm() - val s = origin.nonterminal - - testIncremental(origin, getDyckDelta(s), DyckLanguage().getRsm(), 4, true) - } - /** * Single-string automaton accepting string "bra" */ @@ -172,42 +134,4 @@ class UnionWithLinearTest : RsmTest { makeConcat("b", "r", "a") } } - - /** - * Rsm for [[ S ]] - */ - private fun getDyckDelta(nonTerm: Nonterminal): RSMState { - val deltaStart = RSMState(nonTerm, isStart = true) - val st1 = RSMState(nonTerm) - val st2 = RSMState(nonTerm) - val st3 = RSMState(nonTerm, isFinal = true) - deltaStart.addEdge(Terminal("["), st1) - st1.addEdge(nonTerm, st2) - st2.addEdge(Terminal("]"), st3) - return deltaStart - } - - /** - * Grammar for language S = ( S ) | [[ S ]] - */ - private class MultiDyck : Grammar() { - var S by NT() - - init { - setStart(S) - S = Term("[") * S * Term("]") or Term("(") * S * Term(")") - } - } - - /** - * Grammar for language S = ( S ) - */ - class DyckLanguage : Grammar() { - var S by NT() - - init { - setStart(S) - S = Term("(") * S * Term(")") - } - } } \ No newline at end of file diff --git a/src/test/kotlin/rsm/builder/AStarTest.kt b/src/test/kotlin/rsm/builder/AStarTest.kt index a972f82f5..70684595f 100644 --- a/src/test/kotlin/rsm/builder/AStarTest.kt +++ b/src/test/kotlin/rsm/builder/AStarTest.kt @@ -25,6 +25,6 @@ class AStarTest : RsmTest { fun testRsm() { val aStar = AStar() assertNotNull(aStar.S.getNonterminal()) - assertTrue { equalsByNtName(getAStar("S"), aStar.getRsm()) } + assertTrue { equalsByNtName(getAStarRSM("S"), aStar.getRsm()) } } } \ No newline at end of file diff --git a/src/test/kotlin/rsm/builder/OptionalTest.kt b/src/test/kotlin/rsm/builder/OptionalTest.kt index 455a37a33..1e2fa2b35 100644 --- a/src/test/kotlin/rsm/builder/OptionalTest.kt +++ b/src/test/kotlin/rsm/builder/OptionalTest.kt @@ -25,7 +25,7 @@ class OptionalTest : RsmTest { S = opt(Term("a")) * S } } - override fun getAStar(stateName: String): RSMState { + override fun getAStarRSM(stateName: String): RSMState { val s = Nonterminal(stateName) val a = Terminal("a") val st0 = RSMState(s, isStart = true) @@ -43,7 +43,7 @@ class OptionalTest : RsmTest { val aStar = AStar() assertNotNull(aStar.S.getNonterminal()) writeRSMToTXT(aStar.getRsm(), "actual.txt") - writeRSMToTXT(getAStar("S"), "expected.txt") - assertTrue { equalsByNtName(getAStar("S"), aStar.getRsm()) } + writeRSMToTXT(getAStarRSM("S"), "expected.txt") + assertTrue { equalsByNtName(getAStarRSM("S"), aStar.getRsm()) } } } \ No newline at end of file diff --git a/src/test/kotlin/rsm/builder/StandAloneBuilderTest.kt b/src/test/kotlin/rsm/builder/StandAloneBuilderTest.kt new file mode 100644 index 000000000..3ce933401 --- /dev/null +++ b/src/test/kotlin/rsm/builder/StandAloneBuilderTest.kt @@ -0,0 +1,38 @@ +package rsm.builder + +import org.srcgll.grammar.combinator.regexp.StandAloneNt +import org.srcgll.grammar.combinator.regexp.Term +import org.srcgll.grammar.combinator.regexp.times +import org.srcgll.rsm.RSMState +import org.srcgll.rsm.symbol.Nonterminal +import org.srcgll.rsm.symbol.Terminal +import rsm.RsmTest +import kotlin.test.Test + +class StandAloneBuilderTest : RsmTest { + @Test + fun testDyckDelta() { + fun getExpected(nonTerm: Nonterminal): RSMState { + val deltaStart = RSMState(nonTerm, isStart = true) + val st1 = RSMState(nonTerm) + val st2 = RSMState(nonTerm) + val st3 = RSMState(nonTerm) + val st4 = RSMState(nonTerm, isFinal = true) + deltaStart.addEdge(Terminal("["), st1) + st1.addEdge(nonTerm, st2) + st2.addEdge(Terminal("]"), st3) + st3.addEdge(nonTerm, st4) + return deltaStart + } + + fun getActual(nonTerm: Nonterminal): RSMState { + val s = StandAloneNt(nonTerm) + s.setDescription(Term("[") * s * Term("]") * s) + return s.buildRsmBox() + } + + val nonTerm = Nonterminal("S") + equalsByNtName(getExpected(nonTerm), getActual(nonTerm)) + } + +} \ No newline at end of file From fed813b5b4a06a966a8b765fb1127c898807cdea Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Thu, 21 Dec 2023 17:34:44 +0300 Subject: [PATCH 13/15] Add complex DyckLanguage tests --- .../srcgll/grammar/combinator/regexp/NT.kt | 48 ++++++++++-- src/main/kotlin/org/srcgll/rsm/DynamicRsm.kt | 8 -- src/main/kotlin/org/srcgll/rsm/RSMState.kt | 8 ++ src/test/kotlin/rsm/RsmTest.kt | 2 +- .../kotlin/rsm/api/LinearDynamicDyckTest.kt | 78 +++---------------- .../rsm/api/LinearDynamicStarDyckTest.kt | 62 +++++++++++++++ 6 files changed, 123 insertions(+), 83 deletions(-) create mode 100644 src/test/kotlin/rsm/api/LinearDynamicStarDyckTest.kt diff --git a/src/main/kotlin/org/srcgll/grammar/combinator/regexp/NT.kt b/src/main/kotlin/org/srcgll/grammar/combinator/regexp/NT.kt index fa1d58fba..9cc620254 100644 --- a/src/main/kotlin/org/srcgll/grammar/combinator/regexp/NT.kt +++ b/src/main/kotlin/org/srcgll/grammar/combinator/regexp/NT.kt @@ -10,17 +10,19 @@ import java.util.* import kotlin.reflect.KProperty open class NT : DerivedSymbol { - private lateinit var nonTerm: Nonterminal - private lateinit var rsmDescription: Regexp + protected open lateinit var nonTerm: Nonterminal + protected lateinit var rsmDescription: Regexp - private fun getNewState(regex: Regexp): RSMState { - return RSMState(nonTerm, isStart = false, regex.acceptEpsilon()) + protected fun getNewState(regex: Regexp, isStart: Boolean = false): RSMState { + return RSMState(nonTerm, isStart, regex.acceptEpsilon()) } - fun buildRsmBox(): RSMState { + open fun buildRsmBox(): RSMState = buildRsmBox(nonTerm.startState) + + protected fun buildRsmBox(startState: RSMState): RSMState { val regexpToProcess = Stack() val regexpToRsmState = HashMap() - regexpToRsmState[rsmDescription] = nonTerm.startState + regexpToRsmState[rsmDescription] = startState val alphabet = rsmDescription.getAlphabet() @@ -53,7 +55,7 @@ open class NT : DerivedSymbol { } } } - return nonTerm.startState + return startState } override fun getNonterminal(): Nonterminal? { @@ -73,4 +75,36 @@ open class NT : DerivedSymbol { } operator fun getValue(grammar: Grammar, property: KProperty<*>): Regexp = this + +} + +/** + * Helper class for building rsm delta when deleting/adding rules to the grammar. + * Uses existing grammar nonterminal + */ +class StandAloneNt(nonterminal: Nonterminal) : NT() { + init { + nonTerm = nonterminal + } + + /** + * Set description of Rsm, may be recursive + */ + fun setDescription(description: Regexp){ + rsmDescription = description + } + + /** + * Create new start state for RsmBox + * Otherwise the origin of the Rsm will be ruined. + */ + override fun buildRsmBox(): RSMState = buildRsmBox(getNewState(rsmDescription, true)) + + /** + * Build rsm from given description in regexp + */ + fun buildRsm(description: Regexp): RSMState{ + rsmDescription = description + return buildRsmBox() + } } \ No newline at end of file diff --git a/src/main/kotlin/org/srcgll/rsm/DynamicRsm.kt b/src/main/kotlin/org/srcgll/rsm/DynamicRsm.kt index bcd790aa7..87fb6cb95 100644 --- a/src/main/kotlin/org/srcgll/rsm/DynamicRsm.kt +++ b/src/main/kotlin/org/srcgll/rsm/DynamicRsm.kt @@ -280,14 +280,6 @@ fun RSMState.equivalent(other: RSMState): Boolean { return outgoingNonterminalEdges == other.outgoingNonterminalEdges } -fun RSMState.add(delta: RSMState) { - DynamicRsm(this).constructIncremental(delta, false) -} - -fun RSMState.remove(delta: RSMState) { - DynamicRsm(this).constructIncremental(delta, true) -} - fun RSMState.removeEdge(state: RSMState, symbol: Symbol) { when (symbol) { is Terminal<*> -> { diff --git a/src/main/kotlin/org/srcgll/rsm/RSMState.kt b/src/main/kotlin/org/srcgll/rsm/RSMState.kt index 003770205..12613bf49 100644 --- a/src/main/kotlin/org/srcgll/rsm/RSMState.kt +++ b/src/main/kotlin/org/srcgll/rsm/RSMState.kt @@ -50,4 +50,12 @@ class RSMState else -> throw IllegalArgumentException("removing not implemented for Symbol implementation $label") } } + + fun add(delta: RSMState) { + DynamicRsm(this).constructIncremental(delta, false) + } + + fun remove(delta: RSMState) { + DynamicRsm(this).constructIncremental(delta, true) + } } diff --git a/src/test/kotlin/rsm/RsmTest.kt b/src/test/kotlin/rsm/RsmTest.kt index 9f463a05c..f3d0081d3 100644 --- a/src/test/kotlin/rsm/RsmTest.kt +++ b/src/test/kotlin/rsm/RsmTest.kt @@ -9,7 +9,7 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue interface RsmTest { - fun isDebug() = true + fun isDebug() = false fun writeDotInDebug(startState: RSMState, rsmName: String) { if (isDebug()) { writeRSMToDOT(startState, "inc/$rsmName.dot") diff --git a/src/test/kotlin/rsm/api/LinearDynamicDyckTest.kt b/src/test/kotlin/rsm/api/LinearDynamicDyckTest.kt index dec4540b9..96c149100 100644 --- a/src/test/kotlin/rsm/api/LinearDynamicDyckTest.kt +++ b/src/test/kotlin/rsm/api/LinearDynamicDyckTest.kt @@ -5,14 +5,13 @@ import org.srcgll.grammar.combinator.Grammar import org.srcgll.grammar.combinator.regexp.* import org.srcgll.rsm.RSMState import org.srcgll.rsm.symbol.Nonterminal -import org.srcgll.rsm.symbol.Terminal import rsm.RsmTest /** * Compare incremental union of Grammar Rsm and linear Delta * Nonterminals in Delta must be the same as in Origin Rsm! */ -class LinearDynamicDyckTest : RsmTest { +class LinearDynamicStarDyckTest : RsmTest { @Test fun `test Dyck addition`() { @@ -42,13 +41,6 @@ class LinearDynamicDyckTest : RsmTest { testIncremental(origin, delta, ExtDyck3().getRsm(), 7) } - @Test - fun `test DyckStar addition`() { - val origin = DyckStar1().getRsm() - val delta = getStarDyckDelta(origin.nonterminal, "[", "]") - testIncremental(origin, delta, DyckStar2().getRsm(), 3) - } - /** * Rsm for <'openBrace' nonTerm 'closeBrace'> @@ -66,14 +58,6 @@ class LinearDynamicDyckTest : RsmTest { return nt.buildRsm(Term(openBrace) * nt * Term(closeBrace) * nt) } - /** - * Rsm for <'openBrace' nonTerm 'closeBrace' nonTerm> - */ - private fun getStarDyckDelta(nonTerm: Nonterminal, openBrace: String, closeBrace: String): RSMState { - val nt = StandAloneNt(nonTerm) - return nt.buildRsm(Many(Term(openBrace) * nt * Term(closeBrace))) - } - /** * Grammar for language S = '(' S ')' | '[[' S ']]' */ @@ -111,69 +95,29 @@ class LinearDynamicDyckTest : RsmTest { } /** - * Grammar for language S = eps | '(' S ')' S | '[[' S ']]' S - */ - private class ExtDyck3 : Grammar() { - var S by NT() - - init { - setStart(S) - S = Epsilon or Term("[") * S * Term("]") * S or ( - Term("(") * S * Term(")") * S) or ( - Term("{") * S * Term("}") * S - ) - } - } - - - /** - * Grammar for language S = ( '(' S ')' )* - */ - private class DyckStar1 : Grammar() { - var S by NT() - - init { - setStart(S) - S = Many(Term("(") * S * Term(")")) - } - } - - /** - * Grammar for language S = ( '(' S ')' )* + * Grammar for language S = ( S ) */ - private class DyckStar2 : Grammar() { + class DyckLanguage : Grammar() { var S by NT() init { setStart(S) - S = Many(Term("(") * S * Term(")") or ( - Term("[") * S * Term("]")) - ) + S = Term("(") * S * Term(")") } } /** - * Get RSM for S = Many('{' S '}') with nonTerminal S - */ - private fun getDyckStar1Delta(nonTerm: Nonterminal): RSMState { - val deltaStart = RSMState(nonTerm, isStart = true, isFinal = true) - val st1 = RSMState(nonTerm) - val st2 = RSMState(nonTerm) - deltaStart.addEdge(Terminal("{"), st1) - st1.addEdge(nonTerm, st2) - st2.addEdge(Terminal("}"), deltaStart) - return deltaStart - } - - /** - * Grammar for language S = ( S ) + * Grammar for language S = eps | '(' S ')' S | '[[' S ']]' S */ - class DyckLanguage : Grammar() { + private class ExtDyck3 : Grammar() { var S by NT() init { setStart(S) - S = Term("(") * S * Term(")") + S = Epsilon or Term("[") * S * Term("]") * S or ( + Term("(") * S * Term(")") * S) or ( + Term("{") * S * Term("}") * S + ) } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/rsm/api/LinearDynamicStarDyckTest.kt b/src/test/kotlin/rsm/api/LinearDynamicStarDyckTest.kt new file mode 100644 index 000000000..43f5339ad --- /dev/null +++ b/src/test/kotlin/rsm/api/LinearDynamicStarDyckTest.kt @@ -0,0 +1,62 @@ +package rsm.api + +import org.junit.jupiter.api.Test +import org.srcgll.grammar.combinator.Grammar +import org.srcgll.grammar.combinator.regexp.* +import org.srcgll.rsm.RSMState +import org.srcgll.rsm.symbol.Nonterminal +import rsm.RsmTest +import kotlin.test.Ignore + +/** + * Compare incremental union of Grammar Rsm and linear Delta + * Nonterminals in Delta must be the same as in Origin Rsm! + */ +class LinearDynamicDyckTest : RsmTest { + + @Test + @Ignore("not implemented yet") + fun `test DyckStar addition`() { + val origin = DyckStar1().getRsm() + val delta = getStarDyckDelta(origin.nonterminal, "[", "]") + testIncremental(origin, delta, DyckStar2().getRsm(), 3) + } + + /** + * Rsm for <'openBrace' nonTerm 'closeBrace' nonTerm> + */ + @Test + @Ignore("not implemented yet") + private fun getStarDyckDelta(nonTerm: Nonterminal, openBrace: String, closeBrace: String): RSMState { + val nt = StandAloneNt(nonTerm) + return nt.buildRsm(Many(Term(openBrace) * nt * Term(closeBrace))) + } + + /** + * Grammar for language S = ( '(' S ')' )* + */ + private class DyckStar1 : Grammar() { + var S by NT() + + init { + setStart(S) + S = Many(Term("(") * S * Term(")")) + } + } + + /** + * Grammar for language S = ( '(' S ')' | '[[' S ']]' )* + */ + private class DyckStar2 : Grammar() { + var S by NT() + + init { + setStart(S) + S = Many( + Term("(") * S * Term(")") or ( + Term("[") * S * Term("]")) + ) + } + } + +} \ No newline at end of file From 5d916393fc087e8068e6618215ccb549b4ebb9df Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Thu, 21 Dec 2023 17:43:54 +0300 Subject: [PATCH 14/15] Add complex DyckLanguage tests --- .../kotlin/rsm/api/LinearDynamicSimpleTest.kt | 36 +++----------- .../rsm/api/LinearDynamicStarDyckTest.kt | 2 - .../rsm/builder/StandAloneBuilderTest.kt | 48 ++++++++++++++++++- 3 files changed, 53 insertions(+), 33 deletions(-) diff --git a/src/test/kotlin/rsm/api/LinearDynamicSimpleTest.kt b/src/test/kotlin/rsm/api/LinearDynamicSimpleTest.kt index cd556a9f8..fc8c1fffe 100644 --- a/src/test/kotlin/rsm/api/LinearDynamicSimpleTest.kt +++ b/src/test/kotlin/rsm/api/LinearDynamicSimpleTest.kt @@ -4,12 +4,8 @@ import org.junit.jupiter.api.Test import org.srcgll.grammar.combinator.Grammar import org.srcgll.grammar.combinator.regexp.* import org.srcgll.rsm.RSMState -import org.srcgll.rsm.getAllStates import org.srcgll.rsm.symbol.Nonterminal -import org.srcgll.rsm.symbol.Terminal import rsm.RsmTest -import kotlin.test.assertEquals -import kotlin.test.assertTrue /** * Compare incremental union of Grammar Rsm and linear Delta @@ -62,16 +58,8 @@ class LinearDynamicSimpleTest : RsmTest { } fun getBaba(nonTerm: Nonterminal): RSMState { - val st0 = RSMState(nonTerm, isStart = true) - val st1 = RSMState(nonTerm) - val st2 = RSMState(nonTerm) - val st3 = RSMState(nonTerm) - val st4 = RSMState(nonTerm, isFinal = true) - st0.addEdge(Terminal("b"), st1) - st1.addEdge(Terminal("a"), st2) - st2.addEdge(Terminal("b"), st3) - st3.addEdge(Terminal("a"), st4) - return st0 + val s = StandAloneNt(nonTerm) + return s.buildRsm(makeConcat("b", "a", "b", "a")) } val origin = BaPlusOrBarOrBra().getRsm() @@ -84,28 +72,16 @@ class LinearDynamicSimpleTest : RsmTest { * Single-string automaton accepting string "bra" */ private fun getBra(nonTerm: Nonterminal): RSMState { - val st0 = RSMState(nonTerm, isStart = true) - val st1 = RSMState(nonTerm) - val st2 = RSMState(nonTerm) - val st3 = RSMState(nonTerm, isFinal = true) - st0.addEdge(Terminal("b"), st1) - st1.addEdge(Terminal("r"), st2) - st2.addEdge(Terminal("a"), st3) - return st0 + val s = StandAloneNt(nonTerm) + return s.buildRsm(makeConcat("b", "r", "a")) } /** * Single-string automaton accepting string "bra" */ private fun getBar(nonTerm: Nonterminal): RSMState { - val st0 = RSMState(nonTerm, isStart = true) - val st1 = RSMState(nonTerm) - val st2 = RSMState(nonTerm) - val st3 = RSMState(nonTerm, isFinal = true) - st0.addEdge(Terminal("b"), st1) - st1.addEdge(Terminal("a"), st2) - st2.addEdge(Terminal("r"), st3) - return st0 + val s = StandAloneNt(nonTerm) + return s.buildRsm(makeConcat("b", "a", "r")) } /** diff --git a/src/test/kotlin/rsm/api/LinearDynamicStarDyckTest.kt b/src/test/kotlin/rsm/api/LinearDynamicStarDyckTest.kt index 43f5339ad..2cbadeb0e 100644 --- a/src/test/kotlin/rsm/api/LinearDynamicStarDyckTest.kt +++ b/src/test/kotlin/rsm/api/LinearDynamicStarDyckTest.kt @@ -25,8 +25,6 @@ class LinearDynamicDyckTest : RsmTest { /** * Rsm for <'openBrace' nonTerm 'closeBrace' nonTerm> */ - @Test - @Ignore("not implemented yet") private fun getStarDyckDelta(nonTerm: Nonterminal, openBrace: String, closeBrace: String): RSMState { val nt = StandAloneNt(nonTerm) return nt.buildRsm(Many(Term(openBrace) * nt * Term(closeBrace))) diff --git a/src/test/kotlin/rsm/builder/StandAloneBuilderTest.kt b/src/test/kotlin/rsm/builder/StandAloneBuilderTest.kt index 3ce933401..064c55996 100644 --- a/src/test/kotlin/rsm/builder/StandAloneBuilderTest.kt +++ b/src/test/kotlin/rsm/builder/StandAloneBuilderTest.kt @@ -2,6 +2,7 @@ package rsm.builder import org.srcgll.grammar.combinator.regexp.StandAloneNt import org.srcgll.grammar.combinator.regexp.Term +import org.srcgll.grammar.combinator.regexp.makeConcat import org.srcgll.grammar.combinator.regexp.times import org.srcgll.rsm.RSMState import org.srcgll.rsm.symbol.Nonterminal @@ -35,4 +36,49 @@ class StandAloneBuilderTest : RsmTest { equalsByNtName(getExpected(nonTerm), getActual(nonTerm)) } -} \ No newline at end of file + @Test + fun testBabaDelta() { + fun getExpectedBaba(nonTerm: Nonterminal): RSMState { + val st0 = RSMState(nonTerm, isStart = true) + val st1 = RSMState(nonTerm) + val st2 = RSMState(nonTerm) + val st3 = RSMState(nonTerm) + val st4 = RSMState(nonTerm, isFinal = true) + st0.addEdge(Terminal("b"), st1) + st1.addEdge(Terminal("a"), st2) + st2.addEdge(Terminal("b"), st3) + st3.addEdge(Terminal("a"), st4) + return st0 + } + + fun getActualBaba(nonTerm: Nonterminal): RSMState { + val s = StandAloneNt(nonTerm) + return s.buildRsm(makeConcat("b", "a", "b", "a")) + } + + val nonTerm = Nonterminal("S") + equalsByNtName(getExpectedBaba(nonTerm), getActualBaba(nonTerm)) + } + + @Test + fun testBra(){ + fun getExpectedBra(nonTerm: Nonterminal): RSMState { + val st0 = RSMState(nonTerm, isStart = true) + val st1 = RSMState(nonTerm) + val st2 = RSMState(nonTerm) + val st3 = RSMState(nonTerm, isFinal = true) + st0.addEdge(Terminal("b"), st1) + st1.addEdge(Terminal("r"), st2) + st2.addEdge(Terminal("a"), st3) + return st0 + } + + fun getActualBra(nonTerm: Nonterminal): RSMState { + val s = StandAloneNt(nonTerm) + return s.buildRsm(makeConcat("b", "r", "a")) + } + val nonTerm = Nonterminal("S") + equalsByNtName(getExpectedBra(nonTerm), getActualBra(nonTerm)) + } +} + From a66921c05b81889808a62fc2cc258e7b62fb0fa1 Mon Sep 17 00:00:00 2001 From: Olga Bachishe Date: Thu, 21 Dec 2023 18:32:39 +0300 Subject: [PATCH 15/15] Add complex DyckLanguage tests --- src/test/kotlin/rsm/RsmTest.kt | 5 +---- src/test/kotlin/rsm/api/LinearDynamicStarDyckTest.kt | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/test/kotlin/rsm/RsmTest.kt b/src/test/kotlin/rsm/RsmTest.kt index f3d0081d3..ff619d4c7 100644 --- a/src/test/kotlin/rsm/RsmTest.kt +++ b/src/test/kotlin/rsm/RsmTest.kt @@ -39,10 +39,7 @@ interface RsmTest { return false } for (tEdge in expected.outgoingTerminalEdges) { - val states = actual.outgoingTerminalEdges[tEdge.key] - if(states == null) { - return false - } + val states = actual.outgoingTerminalEdges[tEdge.key] ?: return false if (!equalsAsSetByName(tEdge.value, states, equals)) { return false } diff --git a/src/test/kotlin/rsm/api/LinearDynamicStarDyckTest.kt b/src/test/kotlin/rsm/api/LinearDynamicStarDyckTest.kt index 2cbadeb0e..8c9a2ed68 100644 --- a/src/test/kotlin/rsm/api/LinearDynamicStarDyckTest.kt +++ b/src/test/kotlin/rsm/api/LinearDynamicStarDyckTest.kt @@ -15,7 +15,7 @@ import kotlin.test.Ignore class LinearDynamicDyckTest : RsmTest { @Test - @Ignore("not implemented yet") + @Ignore("not implemented yet: not linear input") fun `test DyckStar addition`() { val origin = DyckStar1().getRsm() val delta = getStarDyckDelta(origin.nonterminal, "[", "]")