1 | use std::ops::Range; |
2 | use thiserror::Error; |
3 | |
4 | /// Error containing information about an error encountered by the Fluent Parser. |
5 | /// |
6 | /// Errors in Fluent Parser are non-fatal, and the syntax has been |
7 | /// designed to allow for strong recovery. |
8 | /// |
9 | /// In result [`ParserError`] is designed to point at the slice of |
10 | /// the input that is most likely to be a complete fragment from after |
11 | /// the end of a valid entry, to the start of the next valid entry, with |
12 | /// the invalid syntax in the middle. |
13 | /// |
14 | /// |
15 | /// # Example |
16 | /// |
17 | /// ``` |
18 | /// use fluent_syntax::parser; |
19 | /// use fluent_syntax::ast; |
20 | /// |
21 | /// let ftl = r#" |
22 | /// key1 = Value 1 |
23 | /// |
24 | /// g@Rb@ge = #2y ds |
25 | /// |
26 | /// key2 = Value 2 |
27 | /// |
28 | /// "# ; |
29 | /// |
30 | /// let (resource, errors) = parser::parse_runtime(ftl) |
31 | /// .expect_err("Resource should contain errors." ); |
32 | /// |
33 | /// assert_eq!( |
34 | /// errors, |
35 | /// vec![ |
36 | /// parser::ParserError { |
37 | /// pos: 18..19, |
38 | /// slice: Some(17..35), |
39 | /// kind: parser::ErrorKind::ExpectedToken('=' ) |
40 | /// } |
41 | /// ] |
42 | /// ); |
43 | /// |
44 | /// assert_eq!( |
45 | /// resource.body[0], |
46 | /// ast::Entry::Message( |
47 | /// ast::Message { |
48 | /// id: ast::Identifier { |
49 | /// name: "key1" |
50 | /// }, |
51 | /// value: Some(ast::Pattern { |
52 | /// elements: vec![ |
53 | /// ast::PatternElement::TextElement { |
54 | /// value: "Value 1" |
55 | /// }, |
56 | /// ] |
57 | /// }), |
58 | /// attributes: vec![], |
59 | /// comment: None, |
60 | /// } |
61 | /// ), |
62 | /// ); |
63 | /// |
64 | /// assert_eq!( |
65 | /// resource.body[1], |
66 | /// ast::Entry::Junk { |
67 | /// content: "g@Rb@ge = #2y ds \n\n" |
68 | /// } |
69 | /// ); |
70 | /// |
71 | /// assert_eq!( |
72 | /// resource.body[2], |
73 | /// ast::Entry::Message( |
74 | /// ast::Message { |
75 | /// id: ast::Identifier { |
76 | /// name: "key2" |
77 | /// }, |
78 | /// value: Some(ast::Pattern { |
79 | /// elements: vec![ |
80 | /// ast::PatternElement::TextElement { |
81 | /// value: "Value 2" |
82 | /// }, |
83 | /// ] |
84 | /// }), |
85 | /// attributes: vec![], |
86 | /// comment: None, |
87 | /// } |
88 | /// ), |
89 | /// ); |
90 | /// ``` |
91 | /// |
92 | /// The information contained in the `ParserError` should allow the tooling |
93 | /// to display rich contextual annotations of the error slice, using |
94 | /// crates such as `annotate-snippers`. |
95 | #[derive (Error, Debug, PartialEq, Clone)] |
96 | #[error("{}" , self.kind)] |
97 | pub struct ParserError { |
98 | /// Precise location of where the parser encountered the error. |
99 | pub pos: Range<usize>, |
100 | /// Slice of the input from the end of the last valid entry to the beginning |
101 | /// of the next valid entry with the invalid syntax in the middle. |
102 | pub slice: Option<Range<usize>>, |
103 | /// The type of the error that the parser encountered. |
104 | pub kind: ErrorKind, |
105 | } |
106 | |
107 | macro_rules! error { |
108 | ($kind:expr, $start:expr) => {{ |
109 | Err(ParserError { |
110 | pos: $start..$start + 1, |
111 | slice: None, |
112 | kind: $kind, |
113 | }) |
114 | }}; |
115 | ($kind:expr, $start:expr, $end:expr) => {{ |
116 | Err(ParserError { |
117 | pos: $start..$end, |
118 | slice: None, |
119 | kind: $kind, |
120 | }) |
121 | }}; |
122 | } |
123 | |
124 | /// Kind of an error associated with the [`ParserError`]. |
125 | #[derive (Error, Debug, PartialEq, Clone)] |
126 | pub enum ErrorKind { |
127 | #[error("Expected a token starting with \"{0} \"" )] |
128 | ExpectedToken(char), |
129 | #[error("Expected one of \"{range} \"" )] |
130 | ExpectedCharRange { range: String }, |
131 | #[error("Expected a message field for \"{entry_id} \"" )] |
132 | ExpectedMessageField { entry_id: String }, |
133 | #[error("Expected a term field for \"{entry_id} \"" )] |
134 | ExpectedTermField { entry_id: String }, |
135 | #[error("Callee is not allowed here" )] |
136 | ForbiddenCallee, |
137 | #[error("The select expression must have a default variant" )] |
138 | MissingDefaultVariant, |
139 | #[error("Expected a value" )] |
140 | MissingValue, |
141 | #[error("A select expression can only have one default variant" )] |
142 | MultipleDefaultVariants, |
143 | #[error("Message references can't be used as a selector" )] |
144 | MessageReferenceAsSelector, |
145 | #[error("Term references can't be used as a selector" )] |
146 | TermReferenceAsSelector, |
147 | #[error("Message attributes can't be used as a selector" )] |
148 | MessageAttributeAsSelector, |
149 | #[error("Term attributes can't be used as a selector" )] |
150 | TermAttributeAsPlaceable, |
151 | #[error("Unterminated string literal" )] |
152 | UnterminatedStringLiteral, |
153 | #[error("Positional arguments must come before named arguments" )] |
154 | PositionalArgumentFollowsNamed, |
155 | #[error("The \"{0} \" argument appears twice" )] |
156 | DuplicatedNamedArgument(String), |
157 | #[error("Unknown escape sequence" )] |
158 | UnknownEscapeSequence(String), |
159 | #[error("Invalid unicode escape sequence, \"{0} \"" )] |
160 | InvalidUnicodeEscapeSequence(String), |
161 | #[error("Unbalanced closing brace" )] |
162 | UnbalancedClosingBrace, |
163 | #[error("Expected an inline expression" )] |
164 | ExpectedInlineExpression, |
165 | #[error("Expected a simple expression as selector" )] |
166 | ExpectedSimpleExpressionAsSelector, |
167 | #[error("Expected a string or number literal" )] |
168 | ExpectedLiteral, |
169 | } |
170 | |