@@ -42,29 +42,11 @@ internal class ParserStructure<in Output>(
4242}
4343
4444/* *
45- * Concatenates a list of parser structures into a single structure.
46- *
47- * Processes parsers in reverse order, batching operations from parsers without alternatives
48- * and simplifying the resulting structure (merging number spans, handling unconditional modifications).
49- *
50- * @return A single parser structure representing the composition of all parsers in the list
45+ * Concatenates a list of parser structures into a single structure, processing them in reverse order.
46+ * Simplifies the result by merging number spans and handling unconditional modifications.
5147 */
5248internal fun <T > List<ParserStructure<T>>.concat (): ParserStructure <T > {
53- /* *
54- * Merges pending operations (base operations, number span, and unconditional modifications)
55- * with a simplified parser structure.
56- *
57- * Invariant: this function should only be called when [simplifiedParserStructure.operations]
58- * is non-empty. If the structure consists only of alternatives (empty operations with
59- * non-empty followedBy), this function should NOT be called.
60- *
61- * @param baseOperations Operations to prepend (already processed operations from this parser)
62- * @param numberSpan Pending number consumers that need to be merged or added (`null` if none pending)
63- * @param unconditionalModifications Operations that must execute after all others
64- * @param simplifiedParserStructure The simplified parser structure to merge with (must have operations)
65- *
66- * @return A new parser structure with all operations merged and the alternatives from [simplifiedParserStructure]
67- */
49+ // Invariant: only called when simplifiedParserStructure.operations is non-empty
6850 fun mergeOperations (
6951 baseOperations : List <ParserOperation <T >>,
7052 numberSpan : List <NumberConsumer <T >>? ,
@@ -77,55 +59,32 @@ internal fun <T> List<ParserStructure<T>>.concat(): ParserStructure<T> {
7759 val mergedOperations = buildList {
7860 addAll(baseOperations)
7961 when {
80- // No pending number span: just append all operations
8162 numberSpan == null -> {
8263 addAll(operationsToMerge)
8364 }
84- // Merge the pending number span with the first operation if it's also a number span
8565 firstOperation is NumberSpanParserOperation -> {
8666 add(NumberSpanParserOperation (numberSpan + firstOperation.consumers))
8767 for (i in 1 .. operationsToMerge.lastIndex) {
8868 add(operationsToMerge[i])
8969 }
9070 }
91- // Add the pending number span as a separate operation before the others
9271 else -> {
9372 add(NumberSpanParserOperation (numberSpan))
9473 addAll(operationsToMerge)
9574 }
9675 }
97- // Unconditional modifications always go at the end of the branch
9876 addAll(unconditionalModifications)
9977 }
10078 return ParserStructure (mergedOperations, simplifiedParserStructure.followedBy)
10179 }
10280
103- /* *
104- * Simplifies this parser structure and appends [other] to all execution paths.
105- *
106- * Simplification includes:
107- * - Merging consecutive number spans into single operations
108- * - Collecting unconditional modifications and applying them before regular operations or at branch ends
109- * - Flattening nested alternatives
110- *
111- * Number span handling at branch ends:
112- * - If no alternative starts with a number span, the pending number span is added as a separate operation
113- * - If any alternative starts with a number span, the pending number span is distributed to all alternatives
114- * via [mergeOperations] for proper merging
115- *
116- * Invariant: [mergeOperations] is only called when the target structure has non-empty
117- * operations, ensuring correct merging and unconditional modification placement.
118- *
119- * @param other The simplified parser structure to append
120- * @return A new parser structure representing the simplified concatenation
121- */
81+ // Simplifies this parser and appends [other] to all execution paths.
82+ // Merges number spans, collects unconditional modifications, and flattens alternatives.
12283 fun ParserStructure<T>.simplifyAndAppend (other : ParserStructure <T >): ParserStructure <T > {
12384 val newOperations = mutableListOf<ParserOperation <T >>()
12485 var currentNumberSpan: MutableList <NumberConsumer <T >>? = null
12586 val unconditionalModifications = mutableListOf<UnconditionalModification <T >>()
12687
127- // Joining together the number consumers in this parser before the first alternative.
128- // Collecting the unconditional modifications.
12988 for (op in operations) {
13089 if (op is NumberSpanParserOperation ) {
13190 if (currentNumberSpan != null ) {
@@ -136,7 +95,6 @@ internal fun <T> List<ParserStructure<T>>.concat(): ParserStructure<T> {
13695 } else if (op is UnconditionalModification ) {
13796 unconditionalModifications.add(op)
13897 } else {
139- // Flush pending number span and unconditional modifications before regular operations
14098 if (currentNumberSpan != null ) {
14199 newOperations.add(NumberSpanParserOperation (currentNumberSpan))
142100 currentNumberSpan = null
@@ -147,7 +105,6 @@ internal fun <T> List<ParserStructure<T>>.concat(): ParserStructure<T> {
147105 }
148106 }
149107
150- // Recursively process alternatives, appending [other] and flattening nested structures
151108 val mergedTails = followedBy.flatMap {
152109 val simplified = it.simplifyAndAppend(other)
153110 // Parser `ParserStructure(emptyList(), p)` is equivalent to `p`,
@@ -160,40 +117,35 @@ internal fun <T> List<ParserStructure<T>>.concat(): ParserStructure<T> {
160117 listOf (simplified)
161118 }.ifEmpty {
162119 if (other.operations.isNotEmpty()) {
163- // Safe to call mergeOperations: target has operations
120+ // The invariant is preserved: other.operations is non-empty
164121 return mergeOperations(newOperations, currentNumberSpan, unconditionalModifications, other)
165122 }
166123 // [other] has no operations, just alternatives; use them as our tails
167124 other.followedBy
168125 }
169126
170127 return if (currentNumberSpan == null ) {
171- // The last operation was not a number span, or it was a number span that we are allowed to interrupt
172128 newOperations.addAll(unconditionalModifications)
173129 ParserStructure (newOperations, mergedTails)
174130 } else if (mergedTails.none { it.operations.firstOrNull() is NumberSpanParserOperation }) {
175- // The last operation was a number span, but there are no alternatives that start with a number span.
176131 newOperations.add(NumberSpanParserOperation (currentNumberSpan))
177132 newOperations.addAll(unconditionalModifications)
178133 ParserStructure (newOperations, mergedTails)
179134 } else {
180- // The last operation was a number span, and some alternatives start with one: distribute for merging.
181- // [mergeOperations] is safe here because each alternative in [mergedTails] has operations
182- // (verified by the structure coming from the recursive [simplifyAndAppend] calls).
183- val newTails = mergedTails.map {
184- mergeOperations(emptyList(), currentNumberSpan, unconditionalModifications, it)
135+ // Distribute number span across alternatives that start with number spans
136+ // The invariant is preserved: the structure coming from the recursive [simplifyAndAppend] calls
137+ val newTails = mergedTails.map { structure ->
138+ mergeOperations(emptyList(), currentNumberSpan, unconditionalModifications, structure)
185139 }
186140 ParserStructure (newOperations, newTails)
187141 }
188142 }
189143
190- // Combine parsers in reverse order, batching operations from parsers without followedBy.
191144 var result = ParserStructure <T >(emptyList(), emptyList())
192145 val accumulatedOperations = mutableListOf<List <ParserOperation <T >>>()
193146
194147 fun flushAccumulatedOperations () {
195148 if (accumulatedOperations.isNotEmpty()) {
196- // Reverse to restore the original order (since parsers are processed in reverse).
197149 val operations = buildList {
198150 for (parserOperations in accumulatedOperations.asReversed()) {
199151 addAll(parserOperations)
@@ -206,16 +158,13 @@ internal fun <T> List<ParserStructure<T>>.concat(): ParserStructure<T> {
206158
207159 for (parser in this .asReversed()) {
208160 if (parser.followedBy.isEmpty()) {
209- // No followedBy: accumulate for batch processing.
210161 accumulatedOperations.add(parser.operations)
211162 } else {
212- // Has followedBy: flush accumulated operations, then process individually.
213163 flushAccumulatedOperations()
214164 result = parser.simplifyAndAppend(result)
215165 }
216166 }
217167
218- // Flush remaining accumulated operations.
219168 flushAccumulatedOperations()
220169 return result
221170}
0 commit comments