asciidork_parser/
diagnostic.rs

1use crate::internal::*;
2
3#[derive(Debug, Eq, PartialEq, Clone)]
4pub struct Diagnostic {
5  pub line_num: u32,
6  pub line: String,
7  pub message: String,
8  pub underline_start: u32,
9  pub underline_width: u32,
10  pub source_file: SourceFile,
11}
12
13impl Parser<'_> {
14  pub(crate) fn err_line(&self, message: impl Into<String>, line: &Line) -> Result<()> {
15    let loc = line.first_loc().expect("non empty line for `err_line`");
16    let (line_num, offset) = self.lexer.line_number_with_offset(loc);
17    let line = line.reassemble_src().to_string();
18    self.handle_err(Diagnostic {
19      line_num,
20      message: message.into(),
21      underline_start: offset,
22      underline_width: line.len() as u32,
23      line,
24      source_file: self.lexer.source_file_at(loc.include_depth).clone(),
25    })
26  }
27
28  pub(crate) fn err_line_starting(
29    &self,
30    message: impl Into<String>,
31    loc: SourceLocation,
32  ) -> Result<()> {
33    let (line_num, offset) = self.lexer.line_number_with_offset(loc);
34    let line = self.lexer.line_of(loc);
35    self.handle_err(Diagnostic {
36      line_num,
37      message: message.into(),
38      underline_start: offset,
39      underline_width: line.len() as u32,
40      line: String::from(line.as_str()),
41      source_file: self.lexer.source_file_at(loc.include_depth).clone(),
42    })
43  }
44
45  pub(crate) fn err_line_of(&self, message: impl Into<String>, loc: SourceLocation) -> Result<()> {
46    let (line_num, _) = self.lexer.line_number_with_offset(loc);
47    let line = self.lexer.line_of(loc);
48    self.handle_err(Diagnostic {
49      line_num,
50      message: message.into(),
51      underline_start: 0,
52      underline_width: line.len() as u32,
53      line: String::from(line.as_str()),
54      source_file: self.lexer.source_file_at(loc.include_depth).clone(),
55    })
56  }
57
58  pub(crate) fn err_doc_attr(
59    &self,
60    key: impl Into<String>,
61    message: impl Into<String>,
62  ) -> Result<()> {
63    let key: &String = &key.into();
64    let loc = self.ctx.attr_defs.iter().find_map(|def| {
65      if def.in_header && def.name == *key { Some(def.loc) } else { None }
66    });
67    let Some(loc) = loc else {
68      debug_assert!(false, "doc attr not found");
69      return Ok(());
70    };
71    self.err_line_of(message, loc)
72  }
73
74  pub(crate) fn err_at_pattern(
75    &self,
76    message: impl Into<String>,
77    line_start: SourceLocation,
78    pattern: &str,
79  ) -> Result<()> {
80    let (line_num, _) = self.lexer.line_number_with_offset(line_start);
81    let line = self.lexer.line_of(line_start);
82    if let Some(idx) = line.find(pattern) {
83      return self.handle_err(Diagnostic {
84        line_num,
85        line: line.to_string(),
86        message: message.into(),
87        underline_start: idx as u32,
88        underline_width: pattern.len() as u32,
89        source_file: self.lexer.source_file_at(line_start.include_depth).clone(),
90      });
91    }
92    self.handle_err(Diagnostic {
93      line_num,
94      line: line.to_string(),
95      message: message.into(),
96      underline_start: 0,
97      underline_width: line.len() as u32,
98      source_file: self.lexer.source_file_at(line_start.include_depth).clone(),
99    })
100  }
101
102  pub(crate) fn err_at(&self, message: impl Into<String>, loc: SourceLocation) -> Result<()> {
103    let (line_num, offset) = self.lexer.line_number_with_offset(loc);
104    self.handle_err(Diagnostic {
105      line_num,
106      line: self.lexer.line_of(loc).to_string(),
107      message: message.into(),
108      underline_start: offset,
109      underline_width: loc.end - loc.start,
110      source_file: self.lexer.source_file_at(loc.include_depth).clone(),
111    })
112  }
113
114  pub(crate) fn err_token_full(&self, message: impl Into<String>, token: &Token) -> Result<()> {
115    let (line_num, offset) = self.lexer.line_number_with_offset(token.loc);
116    self.handle_err(Diagnostic {
117      line_num,
118      line: self.lexer.line_of(token.loc).to_string(),
119      message: message.into(),
120      underline_start: offset,
121      underline_width: token.lexeme.len() as u32,
122      source_file: self.lexer.source_file_at(token.loc.include_depth).clone(),
123    })
124  }
125
126  pub(crate) fn err_token_start(&self, message: impl Into<String>, token: &Token) -> Result<()> {
127    let (line_num, offset) = self.lexer.line_number_with_offset(token.loc);
128    self.handle_err(Diagnostic {
129      line_num,
130      line: self.lexer.line_of(token.loc).to_string(),
131      message: message.into(),
132      underline_start: offset,
133      underline_width: 1,
134      source_file: self.lexer.source_file_at(token.loc.include_depth).clone(),
135    })
136  }
137
138  pub(crate) fn err_token(&self, message: impl Into<String>, token: Option<&Token>) -> Result<()> {
139    let location = token.map_or_else(|| self.lexer.loc(), |t| t.loc);
140    let (line_num, offset) = self.lexer.line_number_with_offset(location);
141    self.handle_err(Diagnostic {
142      line_num,
143      line: self.lexer.line_of(location).to_string(),
144      message: message.into(),
145      underline_start: offset,
146      underline_width: 1,
147      source_file: self.lexer.source_file_at(location.include_depth).clone(),
148    })
149  }
150
151  pub(crate) fn err(&self, diagnostic: Diagnostic) -> Result<()> {
152    self.handle_err(diagnostic)
153  }
154
155  fn handle_err(&self, err: Diagnostic) -> Result<()> {
156    if self.strict {
157      Err(err)
158    } else {
159      self.errors.borrow_mut().push(err);
160      Ok(())
161    }
162  }
163}
164
165pub trait DiagnosticColor {
166  fn line_num(&self, s: impl Into<String>) -> String {
167    s.into()
168  }
169  fn line(&self, s: impl Into<String>) -> String {
170    s.into()
171  }
172  fn location(&self, s: impl Into<String>) -> String {
173    s.into()
174  }
175  fn message(&self, s: impl Into<String>) -> String {
176    s.into()
177  }
178}
179
180impl Diagnostic {
181  pub fn plain_text(&self) -> String {
182    struct NoColor;
183    impl DiagnosticColor for NoColor {}
184    self.plain_text_with(NoColor)
185  }
186
187  pub fn plain_text_with<C: DiagnosticColor>(&self, colorizer: C) -> String {
188    let line_num_pad = match self.line_num {
189      n if n < 10 => 4,
190      n if n < 100 => 5,
191      n if n < 1000 => 6,
192      n if n < 10000 => 7,
193      _ => 8,
194    };
195    format!(
196      "{}{}{}{}{}{}{}\n{}{}\n{} {} {}\n{}{} {}{} {}\n",
197      " ".repeat((line_num_pad - 3) as usize),
198      colorizer.line_num("--> "),
199      colorizer.line_num(self.source_file.file_name()),
200      colorizer.line_num(":"),
201      colorizer.line_num(self.line_num.to_string()),
202      colorizer.line_num(":"),
203      colorizer.line_num((self.underline_start + 1).to_string()),
204      " ".repeat((line_num_pad - 2) as usize),
205      colorizer.line_num("|"),
206      colorizer.line_num(self.line_num.to_string()),
207      colorizer.line_num("|"),
208      colorizer.line(&self.line),
209      " ".repeat((line_num_pad - 2) as usize),
210      colorizer.line_num("|"),
211      " ".repeat(self.underline_start as usize),
212      colorizer.location("^".repeat(self.underline_width as usize)),
213      colorizer.message(&self.message),
214    )
215  }
216}