@@ -48,6 +48,7 @@ internal fun <T> List<ParserStructure<T>>.concat(): ParserStructure<T> {
48
48
} else {
49
49
ParserStructure (operations, followedBy.map { it.append(other) })
50
50
}
51
+
51
52
fun <T > ParserStructure<T>.simplify (): ParserStructure <T > {
52
53
val newOperations = mutableListOf<ParserOperation <T >>()
53
54
var currentNumberSpan: MutableList <NumberConsumer <T >>? = null
@@ -122,7 +123,7 @@ internal interface Copyable<Self> {
122
123
}
123
124
124
125
@JvmInline
125
- internal value class Parser <Output : Copyable <Output >>(
126
+ internal value class Parser <Output : Copyable <Output >>(
126
127
private val commands : ParserStructure <Output >
127
128
) {
128
129
/* *
@@ -145,7 +146,7 @@ internal value class Parser<Output: Copyable<Output>>(
145
146
onSuccess : (Int , Output ) -> Unit
146
147
) {
147
148
val parseOptions = mutableListOf (ParserState (initialContainer, commands, startIndex))
148
- iterate_over_alternatives@while (true ) {
149
+ iterate_over_alternatives@ while (true ) {
149
150
val state = parseOptions.removeLastOrNull() ? : break
150
151
val output = state.output.copy()
151
152
var inputPosition = state.inputPosition
@@ -178,14 +179,15 @@ internal value class Parser<Output: Copyable<Output>>(
178
179
fun match (input : CharSequence , initialContainer : Output , startIndex : Int = 0): Output {
179
180
val errors = mutableListOf<ParseError >()
180
181
parse(input, startIndex, initialContainer, allowDanglingInput = false , { errors.add(it) }, { _, out -> return @match out })
182
+ /*
183
+ * We do care about **all** parser errors and provide diagnostic information to make the error message approacheable
184
+ * for authors of non-trivial formatters with a multitude of potential parsing paths.
185
+ * For that, we sort errors so that the most successful parsing paths are at the top, and
186
+ * add them all to the parse exception message.
187
+ */
181
188
errors.sortByDescending { it.position }
182
189
// `errors` can not be empty because each parser will have (successes + failures) >= 1, and here, successes == 0
183
- ParseException (errors.first()).let {
184
- for (error in errors.drop(1 )) {
185
- it.addSuppressed(ParseException (error))
186
- }
187
- throw it
188
- }
190
+ throw ParseException (errors)
189
191
}
190
192
191
193
fun matchOrNull (input : CharSequence , initialContainer : Output , startIndex : Int = 0): Output ? {
@@ -200,4 +202,18 @@ internal value class Parser<Output: Copyable<Output>>(
200
202
)
201
203
}
202
204
203
- internal class ParseException (error : ParseError ) : Exception(" Position ${error.position} : ${error.message()} " )
205
+ internal class ParseException (errors : List <ParseError >) : Exception(formatError(errors))
206
+
207
+ private fun formatError (errors : List <ParseError >): String {
208
+ if (errors.size == 1 ) {
209
+ return " Position ${errors[0 ].position} : ${errors[0 ].message()} "
210
+ }
211
+ // 20 For average error string: "Expected X but got Y"
212
+ // 13 for static part "Position :,"
213
+ val averageMessageLength = 20 + 13
214
+ return errors.joinTo(
215
+ StringBuilder (averageMessageLength * errors.size),
216
+ prefix = " Errors: " ,
217
+ separator = " , "
218
+ ) { " position ${it.position} : '${it.message()} '" }.toString()
219
+ }
0 commit comments