@@ -20,17 +20,20 @@ pub fn parse_with_recovery<'i>(
2020 input. parse_collecting_errors ( )
2121}
2222
23- #[ derive( Debug , Clone , PartialEq , Eq ) ]
23+ // Most general errors first, most specific last (for deduplication, we prefer
24+ // being able to give a more specific error message to the user)
25+ #[ derive( Debug , Clone , PartialEq , Eq , PartialOrd , Ord ) ]
2426pub enum ParsingError {
27+ // lowest priority
2528 IllegalParserState ( usize ) ,
2629 Unimplemented ( usize ) ,
2730 Unrecognized ( usize ) , // improve this
31+ UnexpectedEndOfInput ( usize ) ,
2832 Expected ( usize , & ' static str ) ,
2933 ExpectedMatchingChar ( usize , & ' static str , char , char ) ,
30- UnclosedInterpolation ( usize ) ,
31- InvalidHeader ( usize ) ,
34+ // more specific errors
3235 InvalidCharacter ( usize , char ) ,
33- UnexpectedEndOfInput ( usize ) ,
36+ InvalidHeader ( usize ) ,
3437 InvalidIdentifier ( usize , String ) ,
3538 InvalidForma ( usize ) ,
3639 InvalidGenus ( usize ) ,
@@ -40,17 +43,19 @@ pub enum ParsingError {
4043 InvalidInvocation ( usize ) ,
4144 InvalidFunction ( usize ) ,
4245 InvalidCodeBlock ( usize ) ,
43- InvalidMultiline ( usize ) ,
4446 InvalidStep ( usize ) ,
4547 InvalidSubstep ( usize ) ,
46- InvalidForeach ( usize ) ,
4748 InvalidResponse ( usize ) ,
49+ InvalidMultiline ( usize ) ,
50+ InvalidForeach ( usize ) ,
4851 InvalidIntegral ( usize ) ,
4952 InvalidQuantity ( usize ) ,
5053 InvalidQuantityDecimal ( usize ) ,
5154 InvalidQuantityUncertainty ( usize ) ,
5255 InvalidQuantityMagnitude ( usize ) ,
5356 InvalidQuantitySymbol ( usize ) ,
57+ // highest priority when deduplicating
58+ UnclosedInterpolation ( usize ) ,
5459}
5560
5661impl ParsingError {
@@ -89,6 +94,35 @@ impl ParsingError {
8994 }
9095}
9196
97+ /// Deduplicate errors, preferring more specific errors over generic ones at the same offset.
98+ /// When multiple errors occur at the same offset, keep only the most specific one.
99+ /// Since ParsingError derives Ord with general errors first and specific errors last,
100+ /// we use > to prefer the higher Ord value (more specific) errors.
101+ fn deduplicate_errors ( errors : Vec < ParsingError > ) -> Vec < ParsingError > {
102+ let mut deduped = Vec :: new ( ) ;
103+
104+ for error in errors {
105+ let error_offset = error. offset ( ) ;
106+
107+ // Check if we have an existing error at this offset
108+ if let Some ( existing_idx) = deduped
109+ . iter ( )
110+ . position ( |e : & ParsingError | e. offset ( ) == error_offset)
111+ {
112+ // Keep the more specific error (higher Ord value, since specific errors come last)
113+ if error > deduped[ existing_idx] {
114+ deduped[ existing_idx] = error;
115+ }
116+ // Otherwise, keep the existing more specific error
117+ } else {
118+ // No error at this offset yet, add it
119+ deduped. push ( error) ;
120+ }
121+ }
122+
123+ deduped
124+ }
125+
92126#[ derive( Debug ) ]
93127pub struct Parser < ' i > {
94128 filename : & ' i Path ,
@@ -284,6 +318,10 @@ impl<'i> Parser<'i> {
284318 let document = Document { header, body } ;
285319 let errors = std:: mem:: take ( & mut self . problems ) ;
286320
321+ // Deduplicate errors, preferring more specific errors over generic
322+ // ones at the same offset
323+ let errors = deduplicate_errors ( errors) ;
324+
287325 if errors. is_empty ( ) {
288326 Ok ( document)
289327 } else {
@@ -4790,6 +4828,30 @@ broken_proc2 : -> B
47904828 } ;
47914829 }
47924830
4831+ #[ test]
4832+ fn test_error_deduplication_unclosed_interpolation ( ) {
4833+ let mut input = Parser :: new ( ) ;
4834+
4835+ // Test that UnclosedInterpolation error takes precedence over generic
4836+ // ExpectedMatchingChar
4837+ input. initialize ( r#"{ "string with {unclosed interpolation" }"# ) ;
4838+ let result = input. read_code_block ( ) ;
4839+
4840+ // Should get the specific UnclosedInterpolation error, not a generic
4841+ // one
4842+ match result {
4843+ Err ( ParsingError :: UnclosedInterpolation ( _) ) => {
4844+ // Good - we got the specific error
4845+ }
4846+ Err ( other) => {
4847+ panic ! ( "Expected UnclosedInterpolation error, got: {:?}" , other) ;
4848+ }
4849+ Ok ( _) => {
4850+ panic ! ( "Expected error for unclosed interpolation, but parsing succeeded" ) ;
4851+ }
4852+ }
4853+ }
4854+
47934855 #[ test]
47944856 fn multiline_code_inline ( ) {
47954857 let mut input = Parser :: new ( ) ;
0 commit comments