mediawiki_parser/
error.rs

1//! Error structures
2
3use crate::ast::{Element, Position, Span};
4use crate::grammar;
5use crate::util::{get_source_lines, is_whitespace, shorten_str};
6use colored::*;
7use serde_derive::{Deserialize, Serialize};
8use std::error;
9use std::fmt;
10
11/// The number of lines to display as error context.
12const ERROR_CONTEXT_LINES: usize = 5;
13
14/// Generic error type for high-level errors of this libaray.
15#[derive(Debug, Serialize, Deserialize, PartialEq)]
16#[serde(rename_all = "lowercase", deny_unknown_fields)]
17pub enum MWError {
18    ParseError(ParseError),
19    TransformationError(TransformationError),
20}
21
22/// The parser error with source code context.
23#[derive(Debug, Serialize, Deserialize, PartialEq)]
24#[serde(rename_all = "lowercase", deny_unknown_fields)]
25pub struct ParseError {
26    pub position: Position,
27    pub expected: Vec<String>,
28    pub context: Vec<String>,
29    pub context_start: usize,
30    pub context_end: usize,
31}
32
33/// Error structure for syntax tree transformations.
34#[derive(Debug, Serialize, Deserialize, PartialEq)]
35#[serde(rename_all = "lowercase", deny_unknown_fields)]
36pub struct TransformationError {
37    pub cause: String,
38    pub position: Span,
39    pub transformation_name: String,
40    pub tree: Element,
41}
42
43impl ParseError {
44    pub fn from(err: &grammar::ParseError, input: &str) -> Self {
45        let source_lines = get_source_lines(input);
46        let line_count = source_lines.len();
47
48        let line = if err.line <= line_count {
49            err.line
50        } else {
51            source_lines.len()
52        } - 1;
53
54        let start = if line < ERROR_CONTEXT_LINES {
55            0
56        } else {
57            line - ERROR_CONTEXT_LINES
58        };
59
60        let end = if line + ERROR_CONTEXT_LINES >= line_count {
61            line_count - 1
62        } else {
63            line + ERROR_CONTEXT_LINES
64        };
65
66        let mut token_str = vec![];
67        for token in &err.expected {
68            token_str.push(String::from(*token));
69        }
70
71        let mut context = vec![];
72        for sloc in source_lines[start..=end].iter() {
73            context.push(String::from(sloc.content));
74        }
75
76        ParseError {
77            position: Position::new(err.offset, &source_lines),
78            context,
79            expected: token_str,
80            context_start: start,
81            context_end: end,
82        }
83    }
84}
85
86impl error::Error for ParseError {
87    fn description(&self) -> &str {
88        "Could not continue to parse, because no rules could be matched."
89    }
90}
91
92impl fmt::Display for ParseError {
93    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
94        let error_message = format!(
95            "ERROR in line {} at column {}: Could not continue to parse, expected one of: ",
96            self.position.line, self.position.col
97        )
98        .red()
99        .bold();
100
101        let mut token_str = vec![];
102        for token in &self.expected {
103            if is_whitespace(token) {
104                token_str.push(format!("{:?}", token));
105            } else {
106                token_str.push(token.to_string());
107            }
108        }
109
110        write!(f, "{}", error_message)?;
111        writeln!(f, "{}", token_str.join(", ").blue().bold())?;
112
113        for (i, content) in self.context.iter().enumerate() {
114            let lineno = format!("{} |", self.context_start + i + 1);
115            let lineno_col;
116
117            let formatted_content;
118            // the erroneous line
119            if self.context_start + i + 1 == self.position.line {
120                formatted_content = content.red();
121                lineno_col = lineno.red().bold();
122            } else {
123                formatted_content = shorten_str(content).normal();
124                lineno_col = lineno.blue().bold()
125            }
126
127            writeln!(f, "{} {}", lineno_col, formatted_content)?;
128        }
129
130        Ok(())
131    }
132}
133
134impl error::Error for TransformationError {
135    fn description(&self) -> &str {
136        &self.cause
137    }
138}
139
140impl fmt::Display for TransformationError {
141    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
142        let message = format!(
143            "ERROR applying transformation \"{}\" to Elemtn at {}:{} to {}:{}: {}",
144            self.transformation_name,
145            self.position.start.line,
146            self.position.start.col,
147            self.position.end.line,
148            self.position.end.col,
149            self.cause
150        );
151        writeln!(f, "{}", message.red().bold())
152    }
153}
154
155impl error::Error for MWError {
156    fn description(&self) -> &str {
157        match *self {
158            MWError::ParseError(ref e) => e.description(),
159            MWError::TransformationError(ref e) => e.description(),
160        }
161    }
162}
163
164impl fmt::Display for MWError {
165    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
166        match *self {
167            MWError::ParseError(ref e) => write!(f, "{}", e),
168            MWError::TransformationError(ref e) => write!(f, "{}", e),
169        }
170    }
171}