| 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, Eq, 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, Eq, 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 | |