|
1 |
| -use std::error::Error; |
2 | 1 | use std::fmt;
|
3 | 2 |
|
| 3 | +use crate::ParseStringErrorKind; |
| 4 | + |
4 | 5 | use super::common::Range;
|
5 | 6 |
|
6 |
| -/// Error that could occur while parsing or tokenizing. |
7 |
| -#[derive(Debug, PartialEq)] |
8 |
| -pub struct ParseError { |
9 |
| - /// Start and end position of the error. |
10 |
| - pub range: Range, |
11 |
| - /// Error message. |
12 |
| - pub message: String, |
13 |
| - /// Message with the range text. |
14 |
| - display_message: String, |
| 7 | +#[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| 8 | +pub enum ParseErrorKind { |
| 9 | + CommentsNotAllowed, |
| 10 | + ExpectedColonAfterObjectKey, |
| 11 | + ExpectedObjectValue, |
| 12 | + ExpectedDigit, |
| 13 | + ExpectedDigitFollowingNegativeSign, |
| 14 | + ExpectedPlusMinusOrDigitInNumberLiteral, |
| 15 | + ExpectedStringObjectProperty, |
| 16 | + MultipleRootJsonValues, |
| 17 | + String(ParseStringErrorKind), |
| 18 | + TrailingCommasNotAllowed, |
| 19 | + UnexpectedCloseBrace, |
| 20 | + UnexpectedCloseBracket, |
| 21 | + UnexpectedColon, |
| 22 | + UnexpectedComma, |
| 23 | + UnexpectedToken, |
| 24 | + UnexpectedTokenInObject, |
| 25 | + UnexpectedWord, |
| 26 | + UnterminatedArray, |
| 27 | + UnterminatedCommentBlock, |
| 28 | + UnterminatedObject, |
15 | 29 | }
|
16 | 30 |
|
17 |
| -impl ParseError { |
18 |
| - pub(crate) fn new(range: Range, message: &str, file_text: &str) -> ParseError { |
19 |
| - let display_message = get_message_with_range(range, message, file_text); |
20 |
| - ParseError { |
21 |
| - message: message.to_string(), |
22 |
| - range, |
23 |
| - display_message, |
| 31 | +impl std::fmt::Display for ParseErrorKind { |
| 32 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 33 | + use ParseErrorKind::*; |
| 34 | + match self { |
| 35 | + CommentsNotAllowed => { |
| 36 | + write!(f, "Comments are not allowed") |
| 37 | + } |
| 38 | + ExpectedColonAfterObjectKey => { |
| 39 | + write!(f, "Expected colon after the string or word in object property") |
| 40 | + } |
| 41 | + ExpectedDigit => { |
| 42 | + write!(f, "Expected digit") |
| 43 | + } |
| 44 | + ExpectedDigitFollowingNegativeSign => { |
| 45 | + write!(f, "Expected digit following negative sign") |
| 46 | + } |
| 47 | + ExpectedPlusMinusOrDigitInNumberLiteral => { |
| 48 | + write!(f, "Expected plus, minus, or digit in number literal") |
| 49 | + } |
| 50 | + ExpectedObjectValue => { |
| 51 | + write!(f, "Expected value after colon in object property") |
| 52 | + } |
| 53 | + ExpectedStringObjectProperty => { |
| 54 | + write!(f, "Expected string for object property") |
| 55 | + } |
| 56 | + MultipleRootJsonValues => { |
| 57 | + write!(f, "Text cannot contain more than one JSON value") |
| 58 | + } |
| 59 | + String(kind) => kind.fmt(f), |
| 60 | + TrailingCommasNotAllowed => { |
| 61 | + write!(f, "Trailing commas are not allowed") |
| 62 | + } |
| 63 | + UnexpectedCloseBrace => { |
| 64 | + write!(f, "Unexpected close brace") |
| 65 | + } |
| 66 | + UnexpectedCloseBracket => { |
| 67 | + write!(f, "Unexpected close bracket") |
| 68 | + } |
| 69 | + UnexpectedColon => { |
| 70 | + write!(f, "Unexpected colon") |
| 71 | + } |
| 72 | + UnexpectedComma => { |
| 73 | + write!(f, "Unexpected comma") |
| 74 | + } |
| 75 | + UnexpectedWord => { |
| 76 | + write!(f, "Unexpected word") |
| 77 | + } |
| 78 | + UnexpectedToken => { |
| 79 | + write!(f, "Unexpected token") |
| 80 | + } |
| 81 | + UnexpectedTokenInObject => { |
| 82 | + write!(f, "Unexpected token in object") |
| 83 | + } |
| 84 | + UnterminatedArray => { |
| 85 | + write!(f, "Unterminated array") |
| 86 | + } |
| 87 | + UnterminatedCommentBlock => { |
| 88 | + write!(f, "Unterminated comment block") |
| 89 | + } |
| 90 | + UnterminatedObject => { |
| 91 | + write!(f, "Unterminated object") |
| 92 | + } |
24 | 93 | }
|
25 | 94 | }
|
26 | 95 | }
|
27 | 96 |
|
28 |
| -impl fmt::Display for ParseError { |
29 |
| - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
30 |
| - write!(f, "{}", self.display_message) |
| 97 | +#[derive(Debug, Clone, PartialEq)] |
| 98 | +struct ParseErrorInner { |
| 99 | + range: Range, |
| 100 | + line_display: usize, |
| 101 | + column_display: usize, |
| 102 | + kind: ParseErrorKind, |
| 103 | +} |
| 104 | + |
| 105 | +/// Error that could occur while parsing or tokenizing. |
| 106 | +#[derive(Debug, Clone, PartialEq)] |
| 107 | +pub struct ParseError(Box<ParseErrorInner>); |
| 108 | + |
| 109 | +impl std::error::Error for ParseError {} |
| 110 | + |
| 111 | +impl ParseError { |
| 112 | + pub(crate) fn new(range: Range, kind: ParseErrorKind, file_text: &str) -> ParseError { |
| 113 | + let (line_display, column_display) = get_line_and_column_display(range, file_text); |
| 114 | + ParseError(Box::new(ParseErrorInner { |
| 115 | + range, |
| 116 | + line_display, |
| 117 | + column_display, |
| 118 | + kind, |
| 119 | + })) |
| 120 | + } |
| 121 | + |
| 122 | + /// Start and end position of the error. |
| 123 | + pub fn range(&self) -> Range { |
| 124 | + self.0.range |
| 125 | + } |
| 126 | + |
| 127 | + /// 1-indexed line number the error occurred on. |
| 128 | + pub fn line_display(&self) -> usize { |
| 129 | + self.0.line_display |
| 130 | + } |
| 131 | + |
| 132 | + /// 1-indexed column number the error occurred on. |
| 133 | + /// |
| 134 | + /// Note: Use the `error_unicode_width` feature to get the correct column |
| 135 | + /// number for Unicode characters on the line, otherwise this is just the |
| 136 | + /// number of characters by default. |
| 137 | + pub fn column_display(&self) -> usize { |
| 138 | + self.0.column_display |
| 139 | + } |
| 140 | + |
| 141 | + /// Error message. |
| 142 | + pub fn kind(&self) -> &ParseErrorKind { |
| 143 | + &self.0.kind |
31 | 144 | }
|
32 | 145 | }
|
33 | 146 |
|
34 |
| -impl Error for ParseError { |
35 |
| - fn description(&self) -> &str { |
36 |
| - &self.display_message |
| 147 | +impl fmt::Display for ParseError { |
| 148 | + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 149 | + let inner = &*self.0; |
| 150 | + write!( |
| 151 | + f, |
| 152 | + "{} on line {} column {}", |
| 153 | + inner.kind, inner.line_display, inner.column_display |
| 154 | + ) |
37 | 155 | }
|
38 | 156 | }
|
39 | 157 |
|
40 |
| -fn get_message_with_range(range: Range, message: &str, file_text: &str) -> String { |
| 158 | +fn get_line_and_column_display(range: Range, file_text: &str) -> (usize, usize) { |
41 | 159 | let mut line_index = 0;
|
42 | 160 | let mut column_index = 0;
|
43 | 161 | for c in file_text[..range.start].chars() {
|
44 | 162 | if c == '\n' {
|
45 | 163 | line_index += 1;
|
46 | 164 | column_index = 0;
|
47 | 165 | } else {
|
48 |
| - column_index += 1; |
| 166 | + #[cfg(feature = "error_unicode_width")] |
| 167 | + { |
| 168 | + if let Some(width) = unicode_width::UnicodeWidthChar::width_cjk(c) { |
| 169 | + column_index += width; |
| 170 | + } |
| 171 | + } |
| 172 | + #[cfg(not(feature = "error_unicode_width"))] |
| 173 | + { |
| 174 | + column_index += 1; |
| 175 | + } |
49 | 176 | }
|
50 | 177 | }
|
51 |
| - format!("{} on line {} column {}.", message, line_index + 1, column_index + 1,) |
| 178 | + (line_index + 1, column_index + 1) |
52 | 179 | }
|
0 commit comments