From c6cd7d7e0084d4f6d97bffd3932158331ca5c45e Mon Sep 17 00:00:00 2001 From: Maxim Rodionov Date: Sun, 1 Mar 2026 17:04:39 +0300 Subject: [PATCH 1/3] refactor: replace full object checking with link checking --- .../main/kotlin/org/ucfs/sppf/SppfStorage.kt | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/solver/src/main/kotlin/org/ucfs/sppf/SppfStorage.kt b/solver/src/main/kotlin/org/ucfs/sppf/SppfStorage.kt index 253b654ce..f476f24e9 100644 --- a/solver/src/main/kotlin/org/ucfs/sppf/SppfStorage.kt +++ b/solver/src/main/kotlin/org/ucfs/sppf/SppfStorage.kt @@ -77,14 +77,25 @@ open class SppfStorage { val rangeNode = addNode(RangeSppfNode(input, rsm, Range)) val valueRsm = if (rangeType is TerminalType<*>) null else rsm val valueNode = addNode(RangeSppfNode(input, valueRsm, rangeType)) - if (!rangeNode.children.contains(valueNode)) { + + if (!rangeNode.hasChild(valueNode)) { rangeNode.children.add(valueNode) } for (child in children) { - if (!valueNode.children.contains(child)) { + if (!valueNode.hasChild(child)) { valueNode.children.add(child) } } return rangeNode } -} \ No newline at end of file + + private fun RangeSppfNode.hasChild(target: RangeSppfNode): Boolean { + if (children.isEmpty()) return false + + for (child in children) { + if (child === target) return true + } + + return false + } +} From a5d2eda345bb7ea78831e00a0c8ac3f9ed0c1951 Mon Sep 17 00:00:00 2001 From: Maxim Rodionov Date: Sun, 1 Mar 2026 17:05:46 +0300 Subject: [PATCH 2/3] refactor: replace the ArrayList with an object with two fields and a reference to list to reduce memory consumption --- .../org/ucfs/sppf/node/RangeSppfNode.kt | 2 +- .../kotlin/org/ucfs/sppf/node/SmallList.kt | 55 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 solver/src/main/kotlin/org/ucfs/sppf/node/SmallList.kt diff --git a/solver/src/main/kotlin/org/ucfs/sppf/node/RangeSppfNode.kt b/solver/src/main/kotlin/org/ucfs/sppf/node/RangeSppfNode.kt index 1ea12899c..193e5e0f7 100644 --- a/solver/src/main/kotlin/org/ucfs/sppf/node/RangeSppfNode.kt +++ b/solver/src/main/kotlin/org/ucfs/sppf/node/RangeSppfNode.kt @@ -23,7 +23,7 @@ data class RangeSppfNode( val rsmRange: RsmRange?, val type: RangeType, ) { - val children = ArrayList>() + val children = SmallList>() } fun getEmptyRange( isStart: Boolean = false): RangeSppfNode { diff --git a/solver/src/main/kotlin/org/ucfs/sppf/node/SmallList.kt b/solver/src/main/kotlin/org/ucfs/sppf/node/SmallList.kt new file mode 100644 index 000000000..80fe000ac --- /dev/null +++ b/solver/src/main/kotlin/org/ucfs/sppf/node/SmallList.kt @@ -0,0 +1,55 @@ +package org.ucfs.sppf.node + +class SmallList : Iterable { + private var first: T? = null + private var second: T? = null + private var rest: ArrayList? = null + private var _size: Int = 0 + + fun add(element: T) { + when (_size) { + 0 -> first = element + 1 -> second = element + else -> { + if (rest == null) rest = ArrayList(4) + rest!!.add(element) + } + } + _size++ + } + + @Suppress("UNCHECKED_CAST") + operator fun get(index: Int): T = when { + index == 0 && _size > 0 -> first as T + index == 1 && _size > 1 -> second as T + index >= 2 -> rest!![index - 2] + else -> throw IndexOutOfBoundsException("Index $index, size $_size") + } + + val size: Int get() = _size + + fun isEmpty(): Boolean = _size == 0 + + fun isNotEmpty(): Boolean = _size > 0 + + fun findLast(predicate: (T) -> Boolean): T? { + var result: T? = null + for (item in this) { + if (predicate(item)) result = item + } + return result + } + + fun contains(element: T): Boolean { + for (item in this) { + if (item == element) return true + } + return false + } + + override fun iterator(): Iterator = object : Iterator { + var index = 0 + override fun hasNext() = index < _size + override fun next(): T = get(index++) + } +} From 75b515f0a50e8be0c3fffa9055a8f80c6aaa3c71 Mon Sep 17 00:00:00 2001 From: Maxim Rodionov Date: Wed, 11 Mar 2026 14:25:23 +0300 Subject: [PATCH 3/3] refactor: replace RangeSppfNode with sealed class hierarchy --- solver/src/main/kotlin/org/ucfs/parser/Gll.kt | 2 +- .../src/main/kotlin/org/ucfs/parser/IGll.kt | 2 +- .../main/kotlin/org/ucfs/sppf/SppfStorage.kt | 30 ++--- .../org/ucfs/sppf/buildStringFromSppf.kt | 4 +- .../org/ucfs/sppf/node/RangeSppfNode.kt | 109 +++++++++++++++--- .../kotlin/org/ucfs/sppf/node/SmallList.kt | 55 --------- .../kotlin/org/ucfs/sppf/writeSppfToDot.kt | 2 +- 7 files changed, 114 insertions(+), 90 deletions(-) delete mode 100644 solver/src/main/kotlin/org/ucfs/sppf/node/SmallList.kt diff --git a/solver/src/main/kotlin/org/ucfs/parser/Gll.kt b/solver/src/main/kotlin/org/ucfs/parser/Gll.kt index 5495d2b2a..c70c9b5fa 100644 --- a/solver/src/main/kotlin/org/ucfs/parser/Gll.kt +++ b/solver/src/main/kotlin/org/ucfs/parser/Gll.kt @@ -70,7 +70,7 @@ class Gll private constructor( private fun isParseResult(matchedRange: RangeSppfNode): Boolean { return matchedRange.inputRange!!.from in ctx.input.getInputStartVertices() && matchedRange.rsmRange!!.from == ctx.fictiveStartState - && matchedRange.rsmRange.to == ctx.fictiveFinalState + && matchedRange.rsmRange!!.to == ctx.fictiveFinalState } /** diff --git a/solver/src/main/kotlin/org/ucfs/parser/IGll.kt b/solver/src/main/kotlin/org/ucfs/parser/IGll.kt index 0796d5a52..cb0ad1384 100644 --- a/solver/src/main/kotlin/org/ucfs/parser/IGll.kt +++ b/solver/src/main/kotlin/org/ucfs/parser/IGll.kt @@ -86,7 +86,7 @@ interface IGll { //TODO why these parameters??? newDescriptor = Descriptor( - rangeToPop.inputRange.to, descriptor.gssNode, destinationRsmState, newSppfNode + rangeToPop.inputRange!!.to, descriptor.gssNode, destinationRsmState, newSppfNode ) ctx.descriptors.add(newDescriptor) } diff --git a/solver/src/main/kotlin/org/ucfs/sppf/SppfStorage.kt b/solver/src/main/kotlin/org/ucfs/sppf/SppfStorage.kt index f476f24e9..1a5c6749d 100644 --- a/solver/src/main/kotlin/org/ucfs/sppf/SppfStorage.kt +++ b/solver/src/main/kotlin/org/ucfs/sppf/SppfStorage.kt @@ -18,6 +18,16 @@ open class SppfStorage { return createdSppfNodes.getOrPut(node, { node }) } + private fun makeNode( + input: InputRange?, + rsm: RsmRange?, + type: RangeType + ): RangeSppfNode = when (type) { + is TerminalType<*>, is EpsilonNonterminalType, is EmptyType -> LeafSppfNode(input, rsm, type) + is IntermediateType<*> -> BinarySppfNode(input, rsm, type) + else -> VariadicSppfNode(input, rsm, type) // Range, NonterminalType + } + /** * Add nonterminal node after pop */ @@ -63,7 +73,7 @@ open class SppfStorage { ), RsmRange( leftSubtree.rsmRange!!.from, rightSubtree.rsmRange!!.to ), IntermediateType( - leftSubtree.rsmRange.to, leftSubtree.inputRange.to + leftSubtree.rsmRange!!.to, leftSubtree.inputRange!!.to ), listOf(leftSubtree, rightSubtree) ) } @@ -74,28 +84,18 @@ open class SppfStorage { rangeType: RangeType, children: List> = listOf() ): RangeSppfNode { - val rangeNode = addNode(RangeSppfNode(input, rsm, Range)) + val rangeNode = addNode(makeNode(input, rsm, Range)) val valueRsm = if (rangeType is TerminalType<*>) null else rsm - val valueNode = addNode(RangeSppfNode(input, valueRsm, rangeType)) + val valueNode = addNode(makeNode(input, valueRsm, rangeType)) if (!rangeNode.hasChild(valueNode)) { - rangeNode.children.add(valueNode) + rangeNode.addChild(valueNode) } for (child in children) { if (!valueNode.hasChild(child)) { - valueNode.children.add(child) + valueNode.addChild(child) } } return rangeNode } - - private fun RangeSppfNode.hasChild(target: RangeSppfNode): Boolean { - if (children.isEmpty()) return false - - for (child in children) { - if (child === target) return true - } - - return false - } } diff --git a/solver/src/main/kotlin/org/ucfs/sppf/buildStringFromSppf.kt b/solver/src/main/kotlin/org/ucfs/sppf/buildStringFromSppf.kt index 243923193..c25478eb2 100644 --- a/solver/src/main/kotlin/org/ucfs/sppf/buildStringFromSppf.kt +++ b/solver/src/main/kotlin/org/ucfs/sppf/buildStringFromSppf.kt @@ -29,7 +29,7 @@ fun buildTokenStreamFromSppf(sppfNode: RangeSppfNode): Mut } is NonterminalType -> { - if (curNode.children.isNotEmpty()) { + if (curNode.children.toList().isNotEmpty()) { curNode.children.findLast { !visited.contains( it @@ -50,4 +50,4 @@ fun buildTokenStreamFromSppf(sppfNode: RangeSppfNode): Mut */ fun buildStringFromSppf(sppfNode: RangeSppfNode): String { return buildTokenStreamFromSppf(sppfNode).joinToString(separator = "") -} \ No newline at end of file +} diff --git a/solver/src/main/kotlin/org/ucfs/sppf/node/RangeSppfNode.kt b/solver/src/main/kotlin/org/ucfs/sppf/node/RangeSppfNode.kt index 193e5e0f7..260f02ffd 100644 --- a/solver/src/main/kotlin/org/ucfs/sppf/node/RangeSppfNode.kt +++ b/solver/src/main/kotlin/org/ucfs/sppf/node/RangeSppfNode.kt @@ -6,24 +6,103 @@ import org.ucfs.rsm.RsmState import org.ucfs.rsm.symbol.ITerminal /** + * Base class for SPPF range nodes. + * Represents all possible ways to derive a specific range. + * Contains two ranges: one in the RSM and one in the input graph. + * Nodes are deduplicated via [SppfStorage] and can be reused. + * + * Specialized into three subclasses based on the number of children: + * - [LeafSppfNode] — no children (terminal, epsilon, empty nodes) + * - [BinarySppfNode] — exactly two children (intermediate nodes) + * - [VariadicSppfNode] — variable number of children (range, nonterminal nodes) * - * A Range node which corresponds to a matched range. It - * represents all possible ways to get a specific range and can - * have arbitrary many children. A child node can be of any - * type, besides a Range node. Nodes of this type can be reused. - *

- * Contains two range: in RSM and in Input graph - *

- * May be used as extended packed sppfNode. * @param VertexType - type of vertex in input graph */ +sealed class RangeSppfNode { + abstract val inputRange: InputRange? + abstract val rsmRange: RsmRange? + abstract val type: RangeType + abstract fun hasChild(target: RangeSppfNode): Boolean + abstract fun addChild(child: RangeSppfNode) + abstract val children: Iterable> +} -data class RangeSppfNode( - val inputRange: InputRange?, - val rsmRange: RsmRange?, - val type: RangeType, -) { - val children = SmallList>() +data class LeafSppfNode( + override val inputRange: InputRange?, + override val rsmRange: RsmRange?, + override val type: RangeType, +) : RangeSppfNode() { + override val children = emptyList>() + + override fun hasChild(target: RangeSppfNode) = false + + override fun addChild(child: RangeSppfNode) = throw UnsupportedOperationException() +} + +data class BinarySppfNode( + override val inputRange: InputRange?, + override val rsmRange: RsmRange?, + override val type: RangeType, +) : RangeSppfNode() { + var child0: RangeSppfNode? = null + var child1: RangeSppfNode? = null + + override val children: Iterable> get() = listOfNotNull(child0, child1) + + override fun hasChild(target: RangeSppfNode) = + child0 === target || child1 === target + + override fun addChild(child: RangeSppfNode) { + when { + child0 == null -> child0 = child + child1 == null -> child1 = child + else -> throw IllegalStateException("BinarySppfNode already has 2 children") + } + } +} + +data class VariadicSppfNode( + override val inputRange: InputRange?, + override val rsmRange: RsmRange?, + override val type: RangeType, +) : RangeSppfNode() { + private var _child0: RangeSppfNode? = null + private var _child1: RangeSppfNode? = null + private var _rest: ArrayList>? = null + private var _size = 0 + + override val children: Iterable> get() = object : Iterable> { + override fun iterator() = object : Iterator> { + var i = 0 + + override fun hasNext() = i < _size + + override fun next(): RangeSppfNode = when (i++) { + 0 -> _child0!! + 1 -> _child1!! + else -> _rest!![i - 3] + } + } + } + + override fun hasChild(target: RangeSppfNode): Boolean { + if (_child0 === target) return true + if (_child1 === target) return true + _rest?.forEach { if (it === target) return true } + return false + } + + override fun addChild(child: RangeSppfNode) { + when (_size) { + 0 -> _child0 = child + 1 -> _child1 = child + else -> { + if (_rest == null) _rest = ArrayList(2) + _rest!!.add(child) + } + } + _size++ + } } fun getEmptyRange( isStart: Boolean = false): RangeSppfNode { @@ -31,7 +110,7 @@ fun getEmptyRange( isStart: Boolean = false): RangeSppfNode( diff --git a/solver/src/main/kotlin/org/ucfs/sppf/node/SmallList.kt b/solver/src/main/kotlin/org/ucfs/sppf/node/SmallList.kt deleted file mode 100644 index 80fe000ac..000000000 --- a/solver/src/main/kotlin/org/ucfs/sppf/node/SmallList.kt +++ /dev/null @@ -1,55 +0,0 @@ -package org.ucfs.sppf.node - -class SmallList : Iterable { - private var first: T? = null - private var second: T? = null - private var rest: ArrayList? = null - private var _size: Int = 0 - - fun add(element: T) { - when (_size) { - 0 -> first = element - 1 -> second = element - else -> { - if (rest == null) rest = ArrayList(4) - rest!!.add(element) - } - } - _size++ - } - - @Suppress("UNCHECKED_CAST") - operator fun get(index: Int): T = when { - index == 0 && _size > 0 -> first as T - index == 1 && _size > 1 -> second as T - index >= 2 -> rest!![index - 2] - else -> throw IndexOutOfBoundsException("Index $index, size $_size") - } - - val size: Int get() = _size - - fun isEmpty(): Boolean = _size == 0 - - fun isNotEmpty(): Boolean = _size > 0 - - fun findLast(predicate: (T) -> Boolean): T? { - var result: T? = null - for (item in this) { - if (predicate(item)) result = item - } - return result - } - - fun contains(element: T): Boolean { - for (item in this) { - if (item == element) return true - } - return false - } - - override fun iterator(): Iterator = object : Iterator { - var index = 0 - override fun hasNext() = index < _size - override fun next(): T = get(index++) - } -} diff --git a/solver/src/main/kotlin/org/ucfs/sppf/writeSppfToDot.kt b/solver/src/main/kotlin/org/ucfs/sppf/writeSppfToDot.kt index d9391295e..89922051b 100644 --- a/solver/src/main/kotlin/org/ucfs/sppf/writeSppfToDot.kt +++ b/solver/src/main/kotlin/org/ucfs/sppf/writeSppfToDot.kt @@ -21,7 +21,7 @@ fun getSppfDot(sppfNodes: Set>, label: Stri sb.appendLine("labelloc=\"t\"") sb.appendLine("label=\"$label\"") var idx = 0 - val results = sppfNodes.sortedWith(compareBy { it.toString() }).map { sppf -> getSppfDot(sppf.children[0], idx++.toString()) } + val results = sppfNodes.sortedWith(compareBy { it.toString() }).map { sppf -> getSppfDot(sppf.children.first(), idx++.toString()) } for (sppf in results.sorted()) { sb.appendLine(sppf) }