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