Skip to content

Commit 18f55fe

Browse files
committed
[plantuml parser] enhance error reporting
1 parent 812cd5a commit 18f55fe

7 files changed

Lines changed: 139 additions & 35 deletions

File tree

plantuml/parser/puml_cli/src/main.rs

Lines changed: 86 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ use std::rc::Rc;
2323

2424
use puml_lobster::{write_lobster_to_file, LobsterModel};
2525
use puml_parser::{
26-
DiagramParser, Preprocessor, PumlClassParser, PumlComponentParser, PumlSequenceParser,
26+
DiagramParser, ErrorLocation, Preprocessor, PumlClassParser, PumlComponentParser,
27+
PumlSequenceParser,
2728
};
2829
use 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,89 @@ fn parse_puml_file(
304307
}
305308
}
306309

307-
type ParserFn =
308-
fn(&Rc<PathBuf>, &str, LogLevel) -> Result<ParsedDiagram, Box<dyn std::error::Error>>;
309-
310310
fn parse_in_order(
311311
path: &Rc<PathBuf>,
312312
content: &str,
313313
log_level: LogLevel,
314314
) -> 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);
315+
// Each attempt records the parser name, the boxed error, and the source
316+
// location extracted from the concrete type before boxing.
317+
let mut attempts: Vec<(&str, Box<dyn std::error::Error>, Option<(usize, usize)>)> = Vec::new();
318+
319+
match PumlComponentParser.parse_file(path, content, log_level) {
320+
Ok(doc) => {
321+
debug!("Successfully detected as Component diagram");
322+
return Ok(ParsedDiagram::Component(doc));
323+
}
324+
Err(e) => {
325+
let loc = e.error_location();
326+
debug!("Component parser failed at {:?}: {}", loc, e);
327+
attempts.push(("Component", Box::new(e), loc));
328+
}
329+
}
330+
331+
match PumlClassParser.parse_file(path, content, log_level) {
332+
Ok(doc) => {
333+
debug!("Successfully detected as Class diagram");
334+
return Ok(ParsedDiagram::Class(doc));
335+
}
336+
Err(e) => {
337+
let loc = e.error_location();
338+
debug!("Class parser failed at {:?}: {}", loc, e);
339+
attempts.push(("Class", Box::new(e), loc));
331340
}
332341
}
333342

343+
match PumlSequenceParser.parse_file(path, content, log_level) {
344+
Ok(doc) => {
345+
debug!("Successfully detected as Sequence diagram");
346+
return Ok(ParsedDiagram::Sequence(doc));
347+
}
348+
Err(e) => {
349+
let loc = e.error_location();
350+
debug!("Sequence parser failed at {:?}: {}", loc, e);
351+
attempts.push(("Sequence", Box::new(e), loc));
352+
}
353+
}
354+
355+
// The parser that reached the furthest line is the most informative one.
356+
let best = attempts
357+
.iter()
358+
.max_by_key(|(_, _, loc)| loc.map_or(0, |(line, _)| line));
359+
360+
let tried_names: Vec<&str> = attempts.iter().map(|(n, _, _)| *n).collect();
361+
362+
let detail = match best {
363+
Some((best_name, best_err, Some((line_no, _col)))) => {
364+
let source_line = content
365+
.lines()
366+
.nth(line_no - 1)
367+
.unwrap_or("<unknown>")
368+
.trim();
369+
format!(
370+
"\n Parsers tried: {}\n Parser with longest match: {}\n Failed at line {}: {}\n Error: {}",
371+
tried_names.join(", "),
372+
best_name,
373+
line_no,
374+
source_line,
375+
best_err,
376+
)
377+
}
378+
Some((best_name, best_err, None)) => {
379+
format!(
380+
"\n Parsers tried: {}\n Parser with longest match: {}\n Error: {}",
381+
tried_names.join(", "),
382+
best_name,
383+
best_err,
384+
)
385+
}
386+
None => String::new(),
387+
};
388+
334389
Err(format!(
335-
"Failed to parse {} with any available parser",
336-
path.display()
390+
"Failed to parse {} with any available parser{}",
391+
path.display(),
392+
detail,
337393
)
338394
.into())
339395
}

plantuml/parser/puml_parser/src/class_diagram/src/class_parser.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ use crate::source_map::{
2121
};
2222
use log::{debug, trace};
2323
use parser_core::common_parser::{parse_arrow, PlantUmlCommonParser, Rule};
24-
use parser_core::{format_parse_tree, pest_to_syntax_error, BaseParseError, DiagramParser};
24+
use parser_core::{
25+
format_parse_tree, pest_to_syntax_error, BaseParseError, DiagramParser, ErrorLocation,
26+
};
2527
use pest::Parser;
2628
use puml_utils::LogLevel;
2729
use std::collections::HashSet;
@@ -39,6 +41,15 @@ pub enum ClassError {
3941
UnexpectedClassMember(String),
4042
}
4143

44+
impl ErrorLocation for ClassError {
45+
fn error_location(&self) -> Option<(usize, usize)> {
46+
match self {
47+
Self::Base(b) => b.error_location(),
48+
_ => None,
49+
}
50+
}
51+
}
52+
4253
// Object definitions are ignored by the class parser, but their names must be
4354
// tracked long enough to drop relationships that reference those ignored objects.
4455
#[derive(Debug, Default)]

plantuml/parser/puml_parser/src/component_diagram/src/component_parser.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ use thiserror::Error;
1818
use crate::{
1919
Arrow, CompPumlDocument, Component, ComponentStyle, Port, PortType, Relation, Statement,
2020
};
21-
use parser_core::{format_parse_tree, pest_to_syntax_error, BaseParseError, DiagramParser};
21+
use parser_core::{
22+
format_parse_tree, pest_to_syntax_error, BaseParseError, DiagramParser, ErrorLocation,
23+
};
2224
use puml_utils::LogLevel;
2325

2426
use parser_core::common_parser::parse_arrow as common_parse_arrow;
@@ -32,6 +34,15 @@ pub enum ComponentError {
3234
InvalidStatement(String),
3335
}
3436

37+
impl ErrorLocation for ComponentError {
38+
fn error_location(&self) -> Option<(usize, usize)> {
39+
match self {
40+
Self::Base(b) => b.error_location(),
41+
_ => None,
42+
}
43+
}
44+
}
45+
3546
pub struct PumlComponentParser;
3647

3748
// lobster-trace: Tools.ArchitectureModelingSyntax

plantuml/parser/puml_parser/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
// Re-export commonly used items that don't have name conflicts
1515
pub use class_parser::{ClassError, ClassUmlFile, PumlClassParser};
1616
pub use component_parser::{CompPumlDocument, ComponentError, PumlComponentParser};
17-
pub use parser_core::{common_ast, common_parser, Arrow, BaseParseError, DiagramParser};
17+
pub use parser_core::{
18+
common_ast, common_parser, Arrow, BaseParseError, DiagramParser, ErrorLocation,
19+
};
1820
pub use preprocessor::{
1921
IncludeExpandError, IncludeParseError, PreprocessError, Preprocessor, ProcedureExpandError,
2022
ProcedureParseError,

plantuml/parser/puml_parser/src/parser_core/src/error.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ pub enum BaseParseError<Rule> {
3535
},
3636
}
3737

38+
pub trait ErrorLocation {
39+
fn error_location(&self) -> Option<(usize, usize)>;
40+
}
41+
42+
impl<Rule> ErrorLocation for BaseParseError<Rule> {
43+
fn error_location(&self) -> Option<(usize, usize)> {
44+
match self {
45+
Self::SyntaxError { line, column, .. } => Some((*line, *column)),
46+
_ => None,
47+
}
48+
}
49+
}
50+
3851
pub fn pest_to_syntax_error<Rule>(
3952
err: PestError<Rule>,
4053
file: PathBuf,

plantuml/parser/puml_parser/src/parser_core/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub mod error;
1616

1717
pub use common_ast::*;
1818
pub use common_parser::*;
19-
pub use error::{pest_to_syntax_error, BaseParseError};
19+
pub use error::{pest_to_syntax_error, BaseParseError, ErrorLocation};
2020

2121
/// Recursively format a Pest parse tree into an indented string for diagnostic output.
2222
///

plantuml/parser/puml_parser/src/sequence_diagram/src/syntax_parser.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
use log::{debug, trace};
1414
use parser_core::common_parser::parse_arrow as common_parse_arrow;
1515
use parser_core::common_parser::{PlantUmlCommonParser, Rule};
16-
use parser_core::{format_parse_tree, pest_to_syntax_error, BaseParseError, DiagramParser};
16+
use parser_core::{
17+
format_parse_tree, pest_to_syntax_error, BaseParseError, DiagramParser, ErrorLocation,
18+
};
1719
use puml_utils::LogLevel;
1820
use std::path::PathBuf;
1921
use std::rc::Rc;
@@ -29,6 +31,15 @@ pub enum SequenceError {
2931
InvalidStatement(String),
3032
}
3133

34+
impl ErrorLocation for SequenceError {
35+
fn error_location(&self) -> Option<(usize, usize)> {
36+
match self {
37+
Self::Base(b) => b.error_location(),
38+
_ => None,
39+
}
40+
}
41+
}
42+
3243
pub struct PumlSequenceParser;
3344

3445
// lobster-trace: Tools.ArchitectureModelingSyntax

0 commit comments

Comments
 (0)