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 {
66        Some(def.loc)
67      } else {
68        None
69      }
70    });
71    let Some(loc) = loc else {
72      debug_assert!(false, "doc attr not found");
73      return Ok(());
74    };
75    self.err_line_of(message, loc)
76  }
77
78  pub(crate) fn err_at_pattern(
79    &self,
80    message: impl Into<String>,
81    line_start: SourceLocation,
82    pattern: &str,
83  ) -> Result<()> {
84    let (line_num, _) = self.lexer.line_number_with_offset(line_start);
85    let line = self.lexer.line_of(line_start);
86    if let Some(idx) = line.find(pattern) {
87      return self.handle_err(Diagnostic {
88        line_num,
89        line: line.to_string(),
90        message: message.into(),
91        underline_start: idx as u32,
92        underline_width: pattern.len() as u32,
93        source_file: self.lexer.source_file_at(line_start.include_depth).clone(),
94      });
95    }
96    self.handle_err(Diagnostic {
97      line_num,
98      line: line.to_string(),
99      message: message.into(),
100      underline_start: 0,
101      underline_width: line.len() as u32,
102      source_file: self.lexer.source_file_at(line_start.include_depth).clone(),
103    })
104  }
105
106  pub(crate) fn err_at(&self, message: impl Into<String>, loc: SourceLocation) -> Result<()> {
107    let (line_num, offset) = self.lexer.line_number_with_offset(loc);
108    self.handle_err(Diagnostic {
109      line_num,
110      line: self.lexer.line_of(loc).to_string(),
111      message: message.into(),
112      underline_start: offset,
113      underline_width: loc.end - loc.start,
114      source_file: self.lexer.source_file_at(loc.include_depth).clone(),
115    })
116  }
117
118  pub(crate) fn err_token_full(&self, message: impl Into<String>, token: &Token) -> Result<()> {
119    let (line_num, offset) = self.lexer.line_number_with_offset(token.loc);
120    self.handle_err(Diagnostic {
121      line_num,
122      line: self.lexer.line_of(token.loc).to_string(),
123      message: message.into(),
124      underline_start: offset,
125      underline_width: token.lexeme.len() as u32,
126      source_file: self.lexer.source_file_at(token.loc.include_depth).clone(),
127    })
128  }
129
130  pub(crate) fn err_token_start(&self, message: impl Into<String>, token: &Token) -> Result<()> {
131    let (line_num, offset) = self.lexer.line_number_with_offset(token.loc);
132    self.handle_err(Diagnostic {
133      line_num,
134      line: self.lexer.line_of(token.loc).to_string(),
135      message: message.into(),
136      underline_start: offset,
137      underline_width: 1,
138      source_file: self.lexer.source_file_at(token.loc.include_depth).clone(),
139    })
140  }
141
142  pub(crate) fn err_token(&self, message: impl Into<String>, token: Option<&Token>) -> Result<()> {
143    let location = token.map_or_else(|| self.lexer.loc(), |t| t.loc);
144    let (line_num, offset) = self.lexer.line_number_with_offset(location);
145    self.handle_err(Diagnostic {
146      line_num,
147      line: self.lexer.line_of(location).to_string(),
148      message: message.into(),
149      underline_start: offset,
150      underline_width: 1,
151      source_file: self.lexer.source_file_at(location.include_depth).clone(),
152    })
153  }
154
155  pub(crate) fn err(&self, diagnostic: Diagnostic) -> Result<()> {
156    self.handle_err(diagnostic)
157  }
158
159  fn handle_err(&self, err: Diagnostic) -> Result<()> {
160    if self.strict {
161      Err(err)
162    } else {
163      self.errors.borrow_mut().push(err);
164      Ok(())
165    }
166  }
167}
168
169pub trait DiagnosticColor {
170  fn line_num(&self, s: impl Into<String>) -> String {
171    s.into()
172  }
173  fn line(&self, s: impl Into<String>) -> String {
174    s.into()
175  }
176  fn location(&self, s: impl Into<String>) -> String {
177    s.into()
178  }
179  fn message(&self, s: impl Into<String>) -> String {
180    s.into()
181  }
182}
183
184impl Diagnostic {
185  pub fn plain_text(&self) -> String {
186    struct NoColor;
187    impl DiagnosticColor for NoColor {}
188    self.plain_text_with(NoColor)
189  }
190
191  pub fn plain_text_with<C: DiagnosticColor>(&self, colorizer: C) -> String {
192    let line_num_pad = match self.line_num {
193      n if n < 10 => 4,
194      n if n < 100 => 5,
195      n if n < 1000 => 6,
196      n if n < 10000 => 7,
197      _ => 8,
198    };
199    format!(
200      "{}{}{}{}{}{}{}\n{}{}\n{} {} {}\n{}{} {}{} {}\n",
201      " ".repeat((line_num_pad - 3) as usize),
202      colorizer.line_num("--> "),
203      colorizer.line_num(self.source_file.file_name()),
204      colorizer.line_num(":"),
205      colorizer.line_num(self.line_num.to_string()),
206      colorizer.line_num(":"),
207      colorizer.line_num((self.underline_start + 1).to_string()),
208      " ".repeat((line_num_pad - 2) as usize),
209      colorizer.line_num("|"),
210      colorizer.line_num(self.line_num.to_string()),
211      colorizer.line_num("|"),
212      colorizer.line(&self.line),
213      " ".repeat((line_num_pad - 2) as usize),
214      colorizer.line_num("|"),
215      " ".repeat(self.underline_start as usize),
216      colorizer.location("^".repeat(self.underline_width as usize)),
217      colorizer.message(&self.message),
218    )
219  }
220}