@@ -23,7 +23,8 @@ use std::rc::Rc;
2323
2424use puml_lobster:: { write_lobster_to_file, LobsterModel } ;
2525use puml_parser:: {
26- DiagramParser , Preprocessor , PumlClassParser , PumlComponentParser , PumlSequenceParser ,
26+ DiagramParser , ErrorLocation , Preprocessor , PumlClassParser , PumlComponentParser ,
27+ PumlSequenceParser ,
2728} ;
2829use puml_resolver:: {
2930 ClassResolver , ComponentResolver , DiagramResolver , SequenceResolver , SequenceTree ,
@@ -111,7 +112,14 @@ enum ParsedDiagram {
111112 Sequence ( puml_parser:: SeqPumlDocument ) ,
112113}
113114
114- fn main ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
115+ fn main ( ) {
116+ if let Err ( e) = run ( ) {
117+ eprintln ! ( "{e}" ) ;
118+ std:: process:: exit ( 1 ) ;
119+ }
120+ }
121+
122+ fn run ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
115123 let args = Args :: parse ( ) ;
116124 let log_level: LogLevel = args. log_level . into ( ) ;
117125 Builder :: new ( )
@@ -149,13 +157,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
149157
150158 debug ! ( "Parsing started" ) ;
151159 for ( path, content) in & preprocessed_files {
152- let parsed_content =
153- parse_puml_file ( path, content, log_level, args. diagram_type ) . map_err ( |e| {
154- std:: io:: Error :: new (
155- std:: io:: ErrorKind :: Other ,
156- format ! ( "Parse error in {}: {}" , path. display( ) , e) ,
157- )
158- } ) ?;
160+ let parsed_content = parse_puml_file ( path, content, log_level, args. diagram_type )
161+ . map_err ( |e| std:: io:: Error :: new ( std:: io:: ErrorKind :: Other , e. to_string ( ) ) ) ?;
159162 if emit_debug_json {
160163 if let Some ( ref dir) = fbs_output_dir {
161164 write_json_to_file ( & parsed_content, path, dir, "raw.ast" ) ?;
@@ -304,36 +307,91 @@ fn parse_puml_file(
304307 }
305308}
306309
307- type ParserFn =
308- fn ( & Rc < PathBuf > , & str , LogLevel ) -> Result < ParsedDiagram , Box < dyn std:: error:: Error > > ;
310+ type ParseAttempt < ' a > = ( & ' a str , Box < dyn std:: error:: Error > , Option < ( usize , usize ) > ) ;
309311
310312fn parse_in_order (
311313 path : & Rc < PathBuf > ,
312314 content : & str ,
313315 log_level : LogLevel ,
314316) -> Result < ParsedDiagram , Box < dyn std:: error:: Error > > {
315- let parsers: & [ ( & str , ParserFn ) ] = & [
316- ( "Component" , |p, c, l| {
317- parse_with_parser ( & mut PumlComponentParser , p, c, l) . map ( ParsedDiagram :: Component )
318- } ) ,
319- ( "Class" , |p, c, l| {
320- parse_with_parser ( & mut PumlClassParser , p, c, l) . map ( ParsedDiagram :: Class )
321- } ) ,
322- ( "Sequence" , |p, c, l| {
323- parse_with_parser ( & mut PumlSequenceParser , p, c, l) . map ( ParsedDiagram :: Sequence )
324- } ) ,
325- ] ;
326-
327- for ( parser_name, parser) in parsers {
328- if let Ok ( ast) = parser ( path, content, log_level) {
329- debug ! ( "Successfully detected as {} diagram" , parser_name) ;
330- return Ok ( ast) ;
317+ // Each attempt records the parser name, the boxed error, and the source
318+ // location extracted from the concrete type before boxing.
319+ let mut attempts: Vec < ParseAttempt < ' _ > > = Vec :: new ( ) ;
320+
321+ match PumlComponentParser . parse_file ( path, content, log_level) {
322+ Ok ( doc) => {
323+ debug ! ( "Successfully detected as Component diagram" ) ;
324+ return Ok ( ParsedDiagram :: Component ( doc) ) ;
325+ }
326+ Err ( e) => {
327+ let loc = e. error_location ( ) ;
328+ debug ! ( "Component parser failed at {:?}: {}" , loc, e) ;
329+ attempts. push ( ( "Component" , Box :: new ( e) , loc) ) ;
330+ }
331+ }
332+
333+ match PumlClassParser . parse_file ( path, content, log_level) {
334+ Ok ( doc) => {
335+ debug ! ( "Successfully detected as Class diagram" ) ;
336+ return Ok ( ParsedDiagram :: Class ( doc) ) ;
337+ }
338+ Err ( e) => {
339+ let loc = e. error_location ( ) ;
340+ debug ! ( "Class parser failed at {:?}: {}" , loc, e) ;
341+ attempts. push ( ( "Class" , Box :: new ( e) , loc) ) ;
331342 }
332343 }
333344
345+ match PumlSequenceParser . parse_file ( path, content, log_level) {
346+ Ok ( doc) => {
347+ debug ! ( "Successfully detected as Sequence diagram" ) ;
348+ return Ok ( ParsedDiagram :: Sequence ( doc) ) ;
349+ }
350+ Err ( e) => {
351+ let loc = e. error_location ( ) ;
352+ debug ! ( "Sequence parser failed at {:?}: {}" , loc, e) ;
353+ attempts. push ( ( "Sequence" , Box :: new ( e) , loc) ) ;
354+ }
355+ }
356+
357+ // The parser that reached the furthest line is the most informative one.
358+ let best = attempts
359+ . iter ( )
360+ . max_by_key ( |( _, _, loc) | loc. map_or ( 0 , |( line, _) | line) ) ;
361+
362+ let tried_names: Vec < & str > = attempts. iter ( ) . map ( |( n, _, _) | * n) . collect ( ) ;
363+
364+ let detail = match best {
365+ Some ( ( best_name, best_err, Some ( ( line_no, _col) ) ) ) => {
366+ let source_line = content
367+ . lines ( )
368+ . nth ( line_no - 1 )
369+ . unwrap_or ( "<unknown>" )
370+ . trim ( ) ;
371+ format ! (
372+ "\n Parsers tried: {}\n Parser with longest match: {}\n Failed at line {}: {}\n Error: {}" ,
373+ tried_names. join( ", " ) ,
374+ best_name,
375+ line_no,
376+ source_line,
377+ best_err,
378+ )
379+ }
380+ Some ( ( best_name, best_err, None ) ) => {
381+ format ! (
382+ "\n Parsers tried: {}\n Parser with longest match: {}\n Error: {}" ,
383+ tried_names. join( ", " ) ,
384+ best_name,
385+ best_err,
386+ )
387+ }
388+ None => String :: new ( ) ,
389+ } ;
390+
334391 Err ( format ! (
335- "Failed to parse {} with any available parser" ,
336- path. display( )
392+ "Failed to parse {} with any available parser{}" ,
393+ path. display( ) ,
394+ detail,
337395 )
338396 . into ( ) )
339397}
0 commit comments