@@ -91,12 +91,32 @@ internal fun <T> List<ParserStructure<T>>.concat(): ParserStructure<T> {
9191 return ParserStructure (mergedOperations, simplifiedParserStructure.followedBy)
9292 }
9393
94+ /* *
95+ * Simplifies this parser structure and appends [other] to all execution paths.
96+ *
97+ * Simplification includes:
98+ * - Merging consecutive number spans into single operations
99+ * - Collecting unconditional modifications and applying them before regular operations or at branch ends
100+ * - Flattening nested alternatives
101+ *
102+ * Number span handling at branch ends:
103+ * - If no alternative starts with a number span, the pending number span is added as a separate operation
104+ * - If any alternative starts with a number span, the pending number span is distributed to all alternatives
105+ * via [mergeOperations] for proper merging
106+ *
107+ * Invariant: [mergeOperations] is only called when the target structure has non-empty
108+ * operations, ensuring correct merging and unconditional modification placement.
109+ *
110+ * @param other The simplified parser structure to append
111+ * @return A new parser structure representing the simplified concatenation
112+ */
94113 fun ParserStructure<T>.simplifyAndAppend (other : ParserStructure <T >): ParserStructure <T > {
95114 val newOperations = mutableListOf<ParserOperation <T >>()
96115 var currentNumberSpan: MutableList <NumberConsumer <T >>? = null
97116 val unconditionalModifications = mutableListOf<UnconditionalModification <T >>()
98- // joining together the number consumers in this parser before the first alternative;
99- // collecting the unconditional modifications.
117+
118+ // Joining together the number consumers in this parser before the first alternative.
119+ // Collecting the unconditional modifications.
100120 for (op in operations) {
101121 if (op is NumberSpanParserOperation ) {
102122 if (currentNumberSpan != null ) {
@@ -107,6 +127,7 @@ internal fun <T> List<ParserStructure<T>>.concat(): ParserStructure<T> {
107127 } else if (op is UnconditionalModification ) {
108128 unconditionalModifications.add(op)
109129 } else {
130+ // Flush pending number span and unconditional modifications before regular operations
110131 if (currentNumberSpan != null ) {
111132 newOperations.add(NumberSpanParserOperation (currentNumberSpan))
112133 currentNumberSpan = null
@@ -117,6 +138,7 @@ internal fun <T> List<ParserStructure<T>>.concat(): ParserStructure<T> {
117138 }
118139 }
119140
141+ // Recursively process alternatives, appending [other] and flattening nested structures
120142 val mergedTails = followedBy.flatMap {
121143 val simplified = it.simplifyAndAppend(other)
122144 // parser `ParserStructure(emptyList(), p)` is equivalent to `p`,
@@ -129,21 +151,26 @@ internal fun <T> List<ParserStructure<T>>.concat(): ParserStructure<T> {
129151 listOf (simplified)
130152 }.ifEmpty {
131153 if (other.operations.isNotEmpty()) {
154+ // Safe to call mergeOperations: target has operations
132155 return mergeOperations(newOperations, currentNumberSpan, unconditionalModifications, other)
133156 }
157+ // [other] has no operations, just alternatives; use them as our tails
134158 other.followedBy
135159 }
136160
137161 return if (currentNumberSpan == null ) {
138- // the last operation was not a number span, or it was a number span that we are allowed to interrupt
162+ // The last operation was not a number span, or it was a number span that we are allowed to interrupt
139163 newOperations.addAll(unconditionalModifications)
140164 ParserStructure (newOperations, mergedTails)
141165 } else if (mergedTails.none { it.operations.firstOrNull() is NumberSpanParserOperation }) {
142- // the last operation was a number span, but there are no alternatives that start with a number span.
166+ // The last operation was a number span, but there are no alternatives that start with a number span.
143167 newOperations.add(NumberSpanParserOperation (currentNumberSpan))
144168 newOperations.addAll(unconditionalModifications)
145169 ParserStructure (newOperations, mergedTails)
146170 } else {
171+ // The last operation was a number span, and some alternatives start with one: distribute for merging.
172+ // [mergeOperations] is safe here because each alternative in [mergedTails] has operations
173+ // (verified by the structure coming from the recursive [simplifyAndAppend] calls).
147174 val newTails = mergedTails.map {
148175 mergeOperations(emptyList(), currentNumberSpan, unconditionalModifications, it)
149176 }
0 commit comments