1use std::fmt;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum GrammarError {
18 Syntax {
23 expected: String,
24 offset: usize,
25 rest: String,
26 },
27 UndefinedRule { name: String, rule_id: u32 },
29 MissingRoot { name: String },
31 LeftRecursion { rule_id: u32, name: Option<String> },
34 RepetitionTooLarge {
37 requested: u64,
38 limit: u64,
39 offset: usize,
40 },
41 TokenNeedsVocabulary { token: String, offset: usize },
44 TokenNotSingle { token: String, n_tokens: usize },
46 NoViableStack { piece: String },
49 TriggerPatternInvalid { pattern: String, reason: String },
52 TriggerPatternFailed { pattern: String, reason: String },
56 LazyWithoutTriggers,
60 AwaitingTrigger,
67 Internal(&'static str),
70}
71
72const REST_CLIP: usize = 40;
74
75impl GrammarError {
76 pub(crate) fn syntax(expected: impl Into<String>, src: &[u8], offset: usize) -> Self {
78 let start = offset.min(src.len());
79 let end = (start + REST_CLIP).min(src.len());
80 let rest = match std::str::from_utf8(&src[start..end]) {
83 Ok(s) => s.to_string(),
84 Err(e) => String::from_utf8_lossy(&src[start..start + e.valid_up_to()]).into_owned(),
85 };
86 GrammarError::Syntax {
87 expected: expected.into(),
88 offset: start,
89 rest,
90 }
91 }
92}
93
94impl fmt::Display for GrammarError {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 match self {
97 GrammarError::Syntax {
98 expected,
99 offset,
100 rest,
101 } => {
102 write!(
103 f,
104 "grammar syntax error at byte {offset}: {expected}, at {rest:?}"
105 )
106 }
107 GrammarError::UndefinedRule { name, rule_id } => write!(
108 f,
109 "grammar references rule {name:?} (id {rule_id}) which is never defined with ::="
110 ),
111 GrammarError::MissingRoot { name } => {
112 write!(f, "grammar does not contain a {name:?} rule to start from")
113 }
114 GrammarError::LeftRecursion { rule_id, name } => match name {
115 Some(n) => write!(
116 f,
117 "unsupported grammar: rule {n:?} (id {rule_id}) is left-recursive"
118 ),
119 None => write!(
120 f,
121 "unsupported grammar: rule id {rule_id} is left-recursive"
122 ),
123 },
124 GrammarError::RepetitionTooLarge {
125 requested,
126 limit,
127 offset,
128 } => write!(
129 f,
130 "grammar repetition at byte {offset} would expand to {requested} rules, over the \
131 limit of {limit}; reduce the repetition count or the rule complexity"
132 ),
133 GrammarError::TokenNeedsVocabulary { token, offset } => write!(
134 f,
135 "grammar token {token:?} at byte {offset} names a token but no vocabulary was \
136 supplied; use the <[id]> form or pass a vocabulary"
137 ),
138 GrammarError::TokenNotSingle { token, n_tokens } => write!(
139 f,
140 "grammar token {token:?} tokenizes to {n_tokens} tokens, but must be exactly 1"
141 ),
142 GrammarError::NoViableStack { piece } => write!(
143 f,
144 "no grammar parse survives the piece {piece:?}; it should have been masked out \
145 before it was sampled"
146 ),
147 GrammarError::TriggerPatternInvalid { pattern, reason } => write!(
148 f,
149 "lazy grammar trigger pattern {pattern:?} does not compile: {reason}"
150 ),
151 GrammarError::TriggerPatternFailed { pattern, reason } => write!(
152 f,
153 "lazy grammar trigger pattern {pattern:?} failed while matching the output so \
154 far: {reason}"
155 ),
156 GrammarError::LazyWithoutTriggers => write!(
157 f,
158 "a lazy grammar needs at least one trigger token or trigger pattern; with none \
159 it can never switch on, and nothing would be constrained"
160 ),
161 GrammarError::AwaitingTrigger => write!(
162 f,
163 "this lazy grammar has not been triggered yet and constrains nothing; check \
164 is_awaiting_trigger before asking which tokens it rejects"
165 ),
166 GrammarError::Internal(what) => {
167 write!(f, "internal grammar engine invariant violated: {what}")
168 }
169 }
170 }
171}
172
173impl std::error::Error for GrammarError {}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn syntax_error_clips_and_quotes_the_rest_of_the_input() {
181 let src =
182 b"root ::= \"a\" @@@ trailing garbage that runs on and on and on and on past the clip";
183 let e = GrammarError::syntax("expecting newline or end", src, 13);
184 match &e {
185 GrammarError::Syntax {
186 expected,
187 offset,
188 rest,
189 } => {
190 assert_eq!(expected, "expecting newline or end");
191 assert_eq!(*offset, 13);
192 assert!(rest.starts_with("@@@ trailing"));
193 assert_eq!(rest.len(), REST_CLIP);
194 }
195 other => panic!("wrong variant: {other:?}"),
196 }
197 assert!(e.to_string().contains("byte 13"));
198 }
199
200 #[test]
201 fn syntax_error_offset_past_the_end_is_clamped() {
202 let src = b"root";
203 let e = GrammarError::syntax("expecting ::=", src, 999);
204 match e {
205 GrammarError::Syntax { offset, rest, .. } => {
206 assert_eq!(offset, 4);
207 assert_eq!(rest, "");
208 }
209 other => panic!("wrong variant: {other:?}"),
210 }
211 }
212
213 #[test]
214 fn syntax_error_does_not_panic_on_a_split_codepoint() {
215 let src = "aé".as_bytes();
217 let e = GrammarError::syntax("expecting name", src, 2);
218 match e {
219 GrammarError::Syntax { rest, .. } => assert_eq!(rest, ""),
220 other => panic!("wrong variant: {other:?}"),
221 }
222 }
223
224 #[test]
225 fn every_variant_says_what_is_missing() {
226 let cases: Vec<GrammarError> = vec![
227 GrammarError::UndefinedRule {
228 name: "ws".into(),
229 rule_id: 4,
230 },
231 GrammarError::MissingRoot {
232 name: "root".into(),
233 },
234 GrammarError::LeftRecursion {
235 rule_id: 1,
236 name: Some("expr".into()),
237 },
238 GrammarError::LeftRecursion {
239 rule_id: 1,
240 name: None,
241 },
242 GrammarError::RepetitionTooLarge {
243 requested: 10_000,
244 limit: 2000,
245 offset: 7,
246 },
247 GrammarError::TokenNeedsVocabulary {
248 token: "<think>".into(),
249 offset: 9,
250 },
251 GrammarError::TokenNotSingle {
252 token: "<think>".into(),
253 n_tokens: 3,
254 },
255 GrammarError::NoViableStack { piece: "}".into() },
256 GrammarError::TriggerPatternInvalid {
257 pattern: "(unclosed".into(),
258 reason: "unclosed group".into(),
259 },
260 GrammarError::TriggerPatternFailed {
261 pattern: "(a+)+b".into(),
262 reason: "backtrack limit exceeded".into(),
263 },
264 GrammarError::LazyWithoutTriggers,
265 GrammarError::AwaitingTrigger,
266 GrammarError::Internal("stack rested on CHAR_ALT"),
267 ];
268 for c in cases {
269 let msg = c.to_string();
270 assert!(msg.len() > 20, "message too thin for {c:?}: {msg}");
271 }
272 }
273}