1use std::ops::Range;
2use 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)]
97pub 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
107macro_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)]
126pub 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