1use std::collections::BTreeMap;
5
6use regex::Regex;
7use serde::Serialize;
8
9use crate::ast::{Expr, Grammar, GrammarItem, RuleDecl, TokenBody};
10use crate::error::RantlrError;
11use crate::span::Span;
12use crate::token::BuiltinType;
13use crate::{analyze, Diagnostic};
14
15#[derive(Debug, Clone, Serialize)]
16pub struct ParseTree {
17 pub kind: String,
18 pub text: String,
19 pub children: Vec<ParseTree>,
20}
21
22#[derive(Debug, Clone, Serialize)]
23pub struct AnalyzeResult {
24 pub grammar_name: String,
25 pub tree: ParseTree,
26}
27
28#[derive(Debug, Clone, Serialize)]
29#[serde(rename_all = "camelCase")]
30pub struct DiagnosticJson {
31 pub severity: String,
32 pub kind: String,
33 pub line: usize,
34 pub column: usize,
35 pub end_line: usize,
36 pub end_column: usize,
37 pub message: String,
38 pub help: Option<String>,
39 pub code: Option<String>,
41 pub fix_hint: Option<crate::FixHint>,
42}
43
44pub fn diagnose(source: &str) -> Vec<DiagnosticJson> {
46 match crate::parser::parse(source) {
47 Err(err) => vec![diagnostic_from_error(source, &err)],
48 Ok(grammar) => {
49 let mut out = Vec::new();
50 for diag in crate::linter::lint_all(source, &grammar) {
51 out.push(diagnostic_from_diag(source, &diag));
52 }
53 out
54 }
55 }
56}
57
58pub fn analyze_and_parse(source: &str, test_input: &str) -> Result<AnalyzeResult, RantlrError> {
60 let grammar = analyze(source)?;
61 let tree = parse_input(&grammar, test_input)?;
62 Ok(AnalyzeResult {
63 grammar_name: grammar.name,
64 tree,
65 })
66}
67
68pub fn parse_input(grammar: &Grammar, input: &str) -> Result<ParseTree, RantlrError> {
69 let engine = Engine::from_grammar(grammar).map_err(|msg| {
70 RantlrError::parse(input, msg, Span::from_offsets(0, 0))
71 })?;
72 engine.parse(input)
73}
74
75fn diagnostic_from_error(_source: &str, err: &RantlrError) -> DiagnosticJson {
76 let end_col = err.column.saturating_add(1);
77 DiagnosticJson {
78 severity: "Error".into(),
79 kind: format!("{:?}", err.kind),
80 line: err.line,
81 column: err.column,
82 end_line: err.line,
83 end_column: end_col,
84 message: err.message.clone(),
85 help: err.help.clone(),
86 code: err.code.clone(),
87 fix_hint: err.fix_hint.clone(),
88 }
89}
90
91fn diagnostic_from_diag(source: &str, diag: &Diagnostic) -> DiagnosticJson {
92 let (line, column) = diag.span.line_col(source);
93 let end = diag.span.end.as_usize().min(source.len());
94 let (end_line, end_column) = Span::from_offsets(end, end).line_col(source);
95 DiagnosticJson {
96 severity: format!("{:?}", diag.severity),
97 kind: format!("{:?}", diag.kind),
98 line,
99 column,
100 end_line,
101 end_column: end_column.max(column + 1),
102 message: diag.message.clone(),
103 help: diag.help.clone(),
104 code: diag.code.clone(),
105 fix_hint: diag.fix.clone(),
106 }
107}
108
109struct TokDef {
110 name: String,
111 re: Regex,
112 skip: bool,
113}
114
115struct Engine<'g> {
116 rules: BTreeMap<String, &'g RuleDecl>,
117 start: String,
118 tokens: Vec<TokDef>,
119}
120
121#[derive(Clone)]
122struct LexTok {
123 name: String,
124 text: String,
125}
126
127struct Parser<'a> {
128 tokens: &'a [LexTok],
129 pos: usize,
130 rules: &'a BTreeMap<String, &'a RuleDecl>,
131}
132
133impl<'g> Engine<'g> {
134 fn from_grammar(grammar: &'g Grammar) -> Result<Self, String> {
135 let mut rules = BTreeMap::new();
136 let mut start = None;
137 let mut declared = Vec::new();
138
139 for item in &grammar.items {
140 match item {
141 GrammarItem::Rule(r) => {
142 if start.is_none() {
143 start = Some(r.name.clone());
144 }
145 rules.insert(r.name.clone(), r);
146 }
147 GrammarItem::Token(t) => {
148 declared.push((t.name.clone(), pattern_for_body(&t.body)?, t.skip));
149 }
150 _ => {}
151 }
152 }
153
154 let start = start.ok_or_else(|| "grammar has no rules".to_string())?;
155
156 let mut inferred: BTreeMap<String, String> = BTreeMap::new();
157 for rule in rules.values() {
158 collect_literals(&rule.body, &mut inferred);
159 }
160
161 let mut tokens = Vec::new();
162 for (name, pat, skip) in declared {
163 tokens.push(TokDef {
164 name,
165 re: Regex::new(&format!("^{pat}")).map_err(|e| e.to_string())?,
166 skip,
167 });
168 }
169 for (lit, name) in inferred {
170 if tokens.iter().any(|t| t.name == name) {
171 continue;
172 }
173 let pat = regex_escape(&lit);
174 tokens.push(TokDef {
175 name,
176 re: Regex::new(&format!("^{pat}")).map_err(|e| e.to_string())?,
177 skip: false,
178 });
179 }
180
181 tokens.sort_by(|a, b| {
182 b.re.as_str().len().cmp(&a.re.as_str().len())
184 });
185
186 Ok(Self {
187 rules,
188 start,
189 tokens,
190 })
191 }
192
193 fn parse(&self, input: &str) -> Result<ParseTree, RantlrError> {
194 let lexed = self.tokenize(input)?;
195 let mut parser = Parser {
196 tokens: &lexed,
197 pos: 0,
198 rules: &self.rules,
199 };
200 let tree = parser.parse_rule(&self.start)?;
201 if !parser.at_end() {
202 let t = parser.peek();
203 return Err(RantlrError::parse(
204 input,
205 format!("unexpected trailing input (`{}`)", t.name),
206 Span::from_offsets(0, 0),
207 ));
208 }
209 Ok(tree)
210 }
211
212 fn tokenize(&self, input: &str) -> Result<Vec<LexTok>, RantlrError> {
213 let mut out = Vec::new();
214 let mut i = 0;
215 let bytes = input.as_bytes();
216 while i < input.len() {
217 let rest = &input[i..];
218 let mut best: Option<(usize, &TokDef)> = None;
219 for tok in &self.tokens {
220 if let Some(m) = tok.re.find(rest) {
221 if m.start() == 0 {
222 let len = m.end();
223 if best.map(|(l, _)| len > l).unwrap_or(true) {
224 best = Some((len, tok));
225 }
226 }
227 }
228 }
229 let Some((len, tok)) = best else {
230 let (line, col) = Span::from_offsets(i, i).line_col(input);
231 return Err(RantlrError {
232 kind: crate::error::ErrorKind::Parse,
233 message: format!(
234 "unexpected character `{}`",
235 input[i..].chars().next().unwrap_or('?')
236 ),
237 line,
238 column: col,
239 snippet: crate::error::context_snippet(input, Span::from_offsets(i, i + 1)),
240 help: None,
241 code: None,
242 fix_hint: None,
243 });
244 };
245 if !tok.skip {
246 out.push(LexTok {
247 name: tok.name.clone(),
248 text: input[i..i + len].to_string(),
249 });
250 }
251 i += len;
252 let _ = bytes; }
254 out.push(LexTok {
255 name: "EOF".into(),
256 text: String::new(),
257 });
258 Ok(out)
259 }
260}
261
262impl<'a> Parser<'a> {
263 fn at_end(&self) -> bool {
264 self.peek().name == "EOF"
265 }
266
267 fn peek(&self) -> &LexTok {
268 &self.tokens[self.pos.min(self.tokens.len() - 1)]
269 }
270
271 fn mark(&self) -> usize {
272 self.pos
273 }
274
275 fn reset(&mut self, m: usize) {
276 self.pos = m;
277 }
278
279 fn expect_token(&mut self, name: &str) -> Result<ParseTree, RantlrError> {
280 let tok = self.peek().clone();
281 if tok.name == name {
282 if tok.name != "EOF" {
283 self.pos += 1;
284 }
285 return Ok(ParseTree {
286 kind: name.into(),
287 text: tok.text,
288 children: vec![],
289 });
290 }
291 Err(self.err(format!("expected `{name}`, found `{}`", tok.name)))
292 }
293
294 fn parse_rule(&mut self, name: &str) -> Result<ParseTree, RantlrError> {
295 let rule = self
296 .rules
297 .get(name)
298 .copied()
299 .ok_or_else(|| self.err(format!("unknown rule `{name}`")))?;
300 let children = self.parse_expr(&rule.body)?;
301 let text = children.iter().map(|c| c.text.as_str()).collect::<String>();
302 Ok(ParseTree {
303 kind: name.into(),
304 text,
305 children,
306 })
307 }
308
309 fn parse_expr(&mut self, expr: &Expr) -> Result<Vec<ParseTree>, RantlrError> {
310 match expr {
311 Expr::Seq { items } => {
312 let mut out = Vec::new();
313 for item in items {
314 out.extend(self.parse_expr(item)?);
315 }
316 Ok(out)
317 }
318 Expr::Group { body } => self.parse_expr(body),
319 Expr::Ref { name, .. } => {
320 if self.rules.contains_key(name) {
321 Ok(vec![self.parse_rule(name)?])
322 } else {
323 Ok(vec![self.expect_token(name)?])
324 }
325 }
326 Expr::Literal { value, .. } => {
327 let tok_name = literal_token_name(value);
328 Ok(vec![self.expect_token(&tok_name)?])
329 }
330 Expr::Optional { body } => {
331 let m = self.mark();
332 match self.parse_expr(body) {
333 Ok(nodes) => Ok(nodes),
334 Err(_) => {
335 self.reset(m);
336 Ok(vec![])
337 }
338 }
339 }
340 Expr::Repeat { min, max, body } => {
341 let mut out = Vec::new();
342 let mut n = 0u64;
343 loop {
344 if let Some(max) = *max {
345 if n >= max {
346 break;
347 }
348 }
349 let m = self.mark();
350 match self.parse_expr(body) {
351 Ok(nodes) => {
352 out.extend(nodes);
353 n += 1;
354 }
355 Err(_) => {
356 self.reset(m);
357 break;
358 }
359 }
360 }
361 if let Some(min) = *min {
362 if n < min {
363 return Err(self.err(format!("expected at least {min} repetitions")));
364 }
365 }
366 Ok(out)
367 }
368 Expr::Match { arms } | Expr::Alt { alts: arms } => {
369 let mut last_err = None;
370 for arm in arms {
371 let m = self.mark();
372 match self.parse_expr(arm) {
373 Ok(nodes) => return Ok(nodes),
374 Err(e) => {
375 self.reset(m);
376 last_err = Some(e);
377 }
378 }
379 }
380 Err(last_err.unwrap_or_else(|| self.err("no alternative matched")))
381 }
382 }
383 }
384
385 fn err(&self, message: impl Into<String>) -> RantlrError {
386 RantlrError {
387 kind: crate::error::ErrorKind::Parse,
388 message: message.into(),
389 line: 1,
390 column: 1,
391 snippet: String::new(),
392 help: Some(format!("at token `{}`", self.peek().name)),
393 code: None,
394 fix_hint: None,
395 }
396 }
397}
398
399fn pattern_for_body(body: &TokenBody) -> Result<String, String> {
400 Ok(match body {
401 TokenBody::Literal(s) => regex_escape(s),
402 TokenBody::Builtin(b) => builtin_regex(*b).to_string(),
403 })
404}
405
406fn builtin_regex(b: BuiltinType) -> &'static str {
407 match b {
408 BuiltinType::Number => r"[0-9]+(?:\.[0-9]+)?",
409 BuiltinType::QuotedString => r#"(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')"#,
410 BuiltinType::Email => r"[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}",
411 BuiltinType::Url => r"https?://[^\s]+",
412 BuiltinType::DateTime => r"\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}:\d{2})?",
413 }
414}
415
416fn regex_escape(s: &str) -> String {
417 let mut out = String::new();
418 for c in s.chars() {
419 if matches!(
420 c,
421 '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '^' | '$' | '|'
422 ) {
423 out.push('\\');
424 }
425 out.push(c);
426 }
427 out
428}
429
430fn collect_literals(expr: &Expr, out: &mut BTreeMap<String, String>) {
431 match expr {
432 Expr::Alt { alts } | Expr::Seq { items: alts } | Expr::Match { arms: alts } => {
433 for e in alts {
434 collect_literals(e, out);
435 }
436 }
437 Expr::Repeat { body, .. } | Expr::Optional { body } | Expr::Group { body } => {
438 collect_literals(body, out);
439 }
440 Expr::Literal { value, .. } => {
441 out.entry(value.clone())
442 .or_insert_with(|| literal_token_name(value));
443 }
444 Expr::Ref { .. } => {}
445 }
446}
447
448fn literal_token_name(lit: &str) -> String {
449 let mapped: String = lit
450 .chars()
451 .map(|c| match c {
452 '+' => "Plus".into(),
453 '-' => "Minus".into(),
454 '*' => "Star".into(),
455 '/' => "Slash".into(),
456 '(' => "LParen".into(),
457 ')' => "RParen".into(),
458 c if c.is_ascii_alphanumeric() => c.to_string(),
459 _ => format!("U{:04X}", c as u32),
460 })
461 .collect();
462 format!("Lit_{mapped}")
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 #[test]
470 fn interprets_calculator() {
471 let src = include_str!("../testdata/calculator.gr");
472 let result = analyze_and_parse(src, "1+2*3").unwrap();
473 assert_eq!(result.grammar_name, "Calculator");
474 assert_eq!(result.tree.kind, "prog");
475 assert_eq!(result.tree.text, "1+2*3");
476 }
477
478 #[test]
479 fn diagnose_left_recursion() {
480 let src = include_str!("../testdata/left_recursive.gr");
481 let diags = diagnose(src);
482 assert_eq!(diags.len(), 1);
483 assert_eq!(diags[0].code.as_deref(), Some("left-recursion"));
484 }
485}