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 token_specificity(b).cmp(&token_specificity(a)).then_with(|| {
184 b.re.as_str().len().cmp(&a.re.as_str().len())
185 })
186 });
187
188 Ok(Self {
189 rules,
190 start,
191 tokens,
192 })
193 }
194
195 fn parse(&self, input: &str) -> Result<ParseTree, RantlrError> {
196 let lexed = self.tokenize(input)?;
197 let mut parser = Parser {
198 tokens: &lexed,
199 pos: 0,
200 rules: &self.rules,
201 };
202 let tree = parser.parse_rule(&self.start)?;
203 if !parser.at_end() {
204 let t = parser.peek();
205 return Err(RantlrError::parse(
206 input,
207 format!("unexpected trailing input (`{}`)", t.name),
208 Span::from_offsets(0, 0),
209 ));
210 }
211 Ok(tree)
212 }
213
214 fn tokenize(&self, input: &str) -> Result<Vec<LexTok>, RantlrError> {
215 let mut out = Vec::new();
216 let mut i = 0;
217 let bytes = input.as_bytes();
218 while i < input.len() {
219 let rest = &input[i..];
220 let mut best: Option<(usize, i32, &TokDef)> = None;
221 for tok in &self.tokens {
222 if let Some(m) = tok.re.find(rest) {
223 if m.start() == 0 {
224 let len = m.end();
225 let spec = token_specificity(tok);
226 let better = match best {
227 None => true,
228 Some((bl, bs, _)) => len > bl || (len == bl && spec > bs),
229 };
230 if better {
231 best = Some((len, spec, tok));
232 }
233 }
234 }
235 }
236 let Some((len, _, tok)) = best else {
237 let (line, col) = Span::from_offsets(i, i).line_col(input);
238 return Err(RantlrError {
239 kind: crate::error::ErrorKind::Parse,
240 message: format!(
241 "unexpected character `{}`",
242 input[i..].chars().next().unwrap_or('?')
243 ),
244 line,
245 column: col,
246 snippet: crate::error::context_snippet(input, Span::from_offsets(i, i + 1)),
247 help: None,
248 code: None,
249 fix_hint: None,
250 });
251 };
252 if !tok.skip {
253 out.push(LexTok {
254 name: tok.name.clone(),
255 text: input[i..i + len].to_string(),
256 });
257 }
258 i += len;
259 let _ = bytes; }
261 out.push(LexTok {
262 name: "EOF".into(),
263 text: String::new(),
264 });
265 Ok(out)
266 }
267}
268
269impl<'a> Parser<'a> {
270 fn at_end(&self) -> bool {
271 self.peek().name == "EOF"
272 }
273
274 fn peek(&self) -> &LexTok {
275 &self.tokens[self.pos.min(self.tokens.len() - 1)]
276 }
277
278 fn mark(&self) -> usize {
279 self.pos
280 }
281
282 fn reset(&mut self, m: usize) {
283 self.pos = m;
284 }
285
286 fn expect_token(&mut self, name: &str) -> Result<ParseTree, RantlrError> {
287 let tok = self.peek().clone();
288 if tok.name == name {
289 if tok.name != "EOF" {
290 self.pos += 1;
291 }
292 return Ok(ParseTree {
293 kind: name.into(),
294 text: tok.text,
295 children: vec![],
296 });
297 }
298 Err(self.err(format!("expected `{name}`, found `{}`", tok.name)))
299 }
300
301 fn parse_rule(&mut self, name: &str) -> Result<ParseTree, RantlrError> {
302 let rule = self
303 .rules
304 .get(name)
305 .copied()
306 .ok_or_else(|| self.err(format!("unknown rule `{name}`")))?;
307 let children = self.parse_expr(&rule.body)?;
308 let text = children.iter().map(|c| c.text.as_str()).collect::<String>();
309 Ok(ParseTree {
310 kind: name.into(),
311 text,
312 children,
313 })
314 }
315
316 fn parse_expr(&mut self, expr: &Expr) -> Result<Vec<ParseTree>, RantlrError> {
317 match expr {
318 Expr::Seq { items } => {
319 let mut out = Vec::new();
320 for item in items {
321 out.extend(self.parse_expr(item)?);
322 }
323 Ok(out)
324 }
325 Expr::Group { body } => self.parse_expr(body),
326 Expr::Ref { name, .. } => {
327 if self.rules.contains_key(name) {
328 Ok(vec![self.parse_rule(name)?])
329 } else {
330 Ok(vec![self.expect_token(name)?])
331 }
332 }
333 Expr::Literal { value, .. } => {
334 let tok_name = literal_token_name(value);
335 Ok(vec![self.expect_token(&tok_name)?])
336 }
337 Expr::Optional { body } => {
338 let m = self.mark();
339 match self.parse_expr(body) {
340 Ok(nodes) => Ok(nodes),
341 Err(_) => {
342 self.reset(m);
343 Ok(vec![])
344 }
345 }
346 }
347 Expr::Repeat { min, max, body } => {
348 let mut out = Vec::new();
349 let mut n = 0u64;
350 loop {
351 if let Some(max) = *max {
352 if n >= max {
353 break;
354 }
355 }
356 let m = self.mark();
357 match self.parse_expr(body) {
358 Ok(nodes) => {
359 out.extend(nodes);
360 n += 1;
361 }
362 Err(_) => {
363 self.reset(m);
364 break;
365 }
366 }
367 }
368 if let Some(min) = *min {
369 if n < min {
370 return Err(self.err(format!("expected at least {min} repetitions")));
371 }
372 }
373 Ok(out)
374 }
375 Expr::Match { arms } | Expr::Alt { alts: arms } => {
376 let mut last_err = None;
377 for arm in arms {
378 let m = self.mark();
379 match self.parse_expr(arm) {
380 Ok(nodes) => return Ok(nodes),
381 Err(e) => {
382 self.reset(m);
383 last_err = Some(e);
384 }
385 }
386 }
387 Err(last_err.unwrap_or_else(|| self.err("no alternative matched")))
388 }
389 }
390 }
391
392 fn err(&self, message: impl Into<String>) -> RantlrError {
393 RantlrError {
394 kind: crate::error::ErrorKind::Parse,
395 message: message.into(),
396 line: 1,
397 column: 1,
398 snippet: String::new(),
399 help: Some(format!("at token `{}`", self.peek().name)),
400 code: None,
401 fix_hint: None,
402 }
403 }
404}
405
406fn pattern_for_body(body: &TokenBody) -> Result<String, String> {
407 Ok(match body {
408 TokenBody::Literal(s) => regex_escape(s),
409 TokenBody::Builtin(b) => builtin_regex(*b).to_string(),
410 })
411}
412
413fn token_specificity(tok: &TokDef) -> i32 {
415 if tok.name.starts_with("Lit_") {
416 return 100;
417 }
418 let pat = tok.re.as_str();
419 if pat.contains("[A-Za-z_") || pat.contains("[0-9]") {
421 return 10;
422 }
423 50
424}
425
426fn builtin_regex(b: BuiltinType) -> &'static str {
427 match b {
428 BuiltinType::Number => r"[0-9]+(?:\.[0-9]+)?",
429 BuiltinType::QuotedString => r#"(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')"#,
430 BuiltinType::Email => r"[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}",
431 BuiltinType::Url => r"https?://[^\s]+",
432 BuiltinType::DateTime => r"\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}:\d{2})?",
433 BuiltinType::Identifier => r"[A-Za-z_][A-Za-z0-9_]*",
434 }
435}
436
437fn regex_escape(s: &str) -> String {
438 let mut out = String::new();
439 for c in s.chars() {
440 if matches!(
441 c,
442 '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '^' | '$' | '|'
443 ) {
444 out.push('\\');
445 }
446 out.push(c);
447 }
448 out
449}
450
451fn collect_literals(expr: &Expr, out: &mut BTreeMap<String, String>) {
452 match expr {
453 Expr::Alt { alts } | Expr::Seq { items: alts } | Expr::Match { arms: alts } => {
454 for e in alts {
455 collect_literals(e, out);
456 }
457 }
458 Expr::Repeat { body, .. } | Expr::Optional { body } | Expr::Group { body } => {
459 collect_literals(body, out);
460 }
461 Expr::Literal { value, .. } => {
462 out.entry(value.clone())
463 .or_insert_with(|| literal_token_name(value));
464 }
465 Expr::Ref { .. } => {}
466 }
467}
468
469fn literal_token_name(lit: &str) -> String {
470 let mapped: String = lit
471 .chars()
472 .map(|c| match c {
473 '+' => "Plus".into(),
474 '-' => "Minus".into(),
475 '*' => "Star".into(),
476 '/' => "Slash".into(),
477 '(' => "LParen".into(),
478 ')' => "RParen".into(),
479 c if c.is_ascii_alphanumeric() => c.to_string(),
480 _ => format!("U{:04X}", c as u32),
481 })
482 .collect();
483 format!("Lit_{mapped}")
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489
490 #[test]
491 fn interprets_calculator() {
492 let src = include_str!("../testdata/calculator.gr");
493 let result = analyze_and_parse(src, "1+2*3").unwrap();
494 assert_eq!(result.grammar_name, "Calculator");
495 assert_eq!(result.tree.kind, "prog");
496 assert_eq!(result.tree.text, "1+2*3");
497 }
498
499 #[test]
500 fn diagnose_left_recursion() {
501 let src = include_str!("../testdata/left_recursive.gr");
502 let diags = diagnose(src);
503 assert_eq!(diags.len(), 1);
504 assert_eq!(diags[0].code.as_deref(), Some("left-recursion"));
505 }
506
507 #[test]
508 fn interprets_api_gateway() {
509 let src = include_str!("../testdata/api_gateway.gr");
510 let input = r#"gateway api_v1 { route /users/:id { methods GET POST limit 10000 per ip balance RoundRobin auth jwt oauth nested posts { constraint "^[a-z0-9-]{3,40}$" methods GET limit 5000 per ip balance LeastConn } } }"#;
511 let result = analyze_and_parse(src, input).expect("api gateway");
512 assert_eq!(result.grammar_name, "ApiGateway");
513 assert_eq!(result.tree.kind, "gateway");
514 }
515}