1use std::collections::BTreeSet;
4use std::fmt::Write as _;
5
6use super::structural::{
7 NamePlan, Precedence, Rendered, ascii_identifier, case_variants, collect_references,
8 join_choice, join_sequence, lower_repeat, negated_lookahead_set, repeat_bounds, wrap,
9};
10use super::{EmitReport, GrammarEmitError, finish_lines, unsupported_error};
11use crate::grammar::{
12 CharClassItem, Grammar, GrammarExpr, GrammarFormat, GrammarRule, RuleKind, import_antlr,
13};
14
15const FORMAT: GrammarFormat = GrammarFormat::Antlr;
16const LABEL: &str = "ANTLR";
17
18const RESERVED_WORDS: &[&str] = &[
20 "catch", "channels", "finally", "fragment", "grammar", "import", "lexer", "locals", "mode",
21 "options", "parser", "returns", "throws", "tokens",
22];
23
24const RESERVED_TOKENS: &[&str] = &["EOF"];
26
27pub fn emit_antlr(grammar: &Grammar) -> Result<(String, EmitReport), GrammarEmitError> {
55 AntlrEmitter::new(grammar).emit()
56}
57
58#[derive(Debug)]
59struct AntlrEmitter<'grammar> {
60 grammar: &'grammar Grammar,
61 names: NamePlan,
62 report: EmitReport,
63 foreign: bool,
64 rule: String,
65}
66
67impl<'grammar> AntlrEmitter<'grammar> {
68 fn new(grammar: &'grammar Grammar) -> Self {
69 let mut report = EmitReport::default();
70 let candidates = grammar
71 .rules()
72 .iter()
73 .map(|rule| rule_candidate(&rule.name, is_lexer_kind(rule.kind)))
74 .collect::<Vec<_>>();
75 let names = NamePlan::new(
76 grammar,
77 LABEL,
78 &candidates,
79 &BTreeSet::new(),
80 reference_candidate,
81 &mut report,
82 );
83 Self {
84 grammar,
85 names,
86 report,
87 foreign: grammar.source_format() != Some(FORMAT),
88 rule: String::new(),
89 }
90 }
91
92 fn emit(mut self) -> Result<(String, EmitReport), GrammarEmitError> {
93 let rules = self.grammar.rules();
94 let order = self.emission_order();
95 self.note_start(&order);
96
97 let has_parser_rules = rules.iter().any(|rule| rule.kind == RuleKind::Normal);
98 let grammar_name = order.first().map_or_else(String::new, |&index| {
99 pascal_case(self.names.rule_name(index))
100 });
101 let grammar_name = if grammar_name.starts_with(|c: char| c.is_ascii_alphabetic()) {
102 grammar_name
103 } else {
104 "Grammar".to_string()
105 };
106 let mut lines = vec![if has_parser_rules {
107 format!("grammar {grammar_name};")
108 } else {
109 format!("lexer grammar {grammar_name};")
110 }];
111 if !order.is_empty() {
112 lines.push(String::new());
113 }
114 for index in order {
115 self.emit_rule(index, &mut lines)?;
116 }
117 Ok((finish_lines(&lines), self.report))
118 }
119
120 fn emission_order(&self) -> Vec<usize> {
124 let rules = self.grammar.rules();
125 let start_index = self
126 .grammar
127 .start()
128 .and_then(|start| rules.iter().position(|rule| rule.name == start));
129 let effective = match start_index {
130 Some(index) if rules[index].kind == RuleKind::Normal => Some(index),
131 _ => rules
132 .iter()
133 .position(|rule| rule.kind == RuleKind::Normal)
134 .or(start_index),
135 };
136 let mut order = effective.into_iter().collect::<Vec<_>>();
137 order.extend((0..rules.len()).filter(|&index| Some(index) != effective));
138 order
139 }
140
141 fn note_start(&mut self, order: &[usize]) {
142 let Some(start) = self.grammar.start() else {
143 return;
144 };
145 let Some(&first) = order.first() else {
146 return;
147 };
148 let expected = self.names.resolve(start).to_string();
149 let actual = self.names.rule_name(first).to_string();
150 if expected != actual {
151 self.report.add_lossy(format!(
152 "ANTLR re-imports start rule as {actual:?} instead of {expected:?}, \
153 because ANTLR starts at the first parser rule"
154 ));
155 }
156 }
157
158 fn emit_rule(&mut self, index: usize, lines: &mut Vec<String>) -> Result<(), GrammarEmitError> {
159 let rule = &self.grammar.rules()[index];
160 let name = self.names.rule_name(index).to_string();
161 self.rule.clone_from(&name);
162 let lexer = is_lexer_kind(rule.kind);
163 self.note_kind(rule, &name);
164
165 let doc = plan_doc(rule.doc(), lexer);
166 if doc.reimported.as_deref() != rule.doc() {
167 self.report.add_lossy(format!(
168 "ANTLR re-imports the documentation of rule {name:?} as {:?}",
169 doc.reimported
170 ));
171 }
172 lines.extend(doc.comments);
173
174 let body = self.render(rule.expr())?.0;
175 let mut line = if rule.kind == RuleKind::Silent {
176 format!("fragment {name} :")
177 } else {
178 format!("{name} :")
179 };
180 if !body.is_empty() {
181 line.push(' ');
182 line.push_str(&body);
183 }
184 if let Some(command) = doc.command {
185 line.push(' ');
186 line.push_str(&command);
187 }
188 line.push_str(" ;");
189 lines.push(line);
190
191 if self.foreign {
192 self.note_rule_placement(rule, &name, lexer);
193 }
194 Ok(())
195 }
196
197 fn note_kind(&mut self, rule: &GrammarRule, name: &str) {
198 match rule.kind {
199 RuleKind::Atomic => self.report.add_lossy(format!(
200 "ANTLR emitted atomic rule {:?} as lexer rule {name:?}, which re-imports as a token rule",
201 rule.name
202 )),
203 RuleKind::Silent if self.foreign => self.report.add_lossy(format!(
204 "ANTLR emitted silent rule {:?} as lexer fragment {name:?}",
205 rule.name
206 )),
207 RuleKind::Normal | RuleKind::Silent | RuleKind::Token => {}
208 }
209 }
210
211 fn note_rule_placement(&mut self, rule: &GrammarRule, name: &str, lexer: bool) {
215 let mut references = Vec::new();
216 collect_references(rule.expr(), &mut references);
217 for reference in references {
218 let Some(target) = self.grammar.rule(reference) else {
219 continue;
220 };
221 let emitted = self.names.resolve(reference).to_string();
222 if lexer && target.kind == RuleKind::Normal {
223 self.report.add_lossy(format!(
224 "ANTLR lexer rule {name:?} references parser rule {emitted:?}"
225 ));
226 } else if !lexer && target.kind == RuleKind::Silent {
227 self.report.add_lossy(format!(
228 "ANTLR parser rule {name:?} references lexer fragment {emitted:?}"
229 ));
230 }
231 }
232 if !lexer && uses_character_constructs(rule.expr()) {
233 self.report.add_lossy(format!(
234 "ANTLR parser rule {name:?} uses character-level constructs that ANTLR matches only in lexer rules"
235 ));
236 }
237 }
238
239 fn note(&mut self, message: &str) {
240 let note = format!("ANTLR {message} in rule {:?}", self.rule);
241 self.report.add_lossy(note);
242 }
243
244 fn render(&mut self, expr: &GrammarExpr) -> Result<Rendered, GrammarEmitError> {
245 Ok(match expr {
246 GrammarExpr::Empty => (String::new(), Precedence::Sequence),
247 GrammarExpr::Terminal(value) => {
248 if value.is_empty() {
249 self.note("keeps an empty literal, which the ANTLR tool rejects,");
250 }
251 (quote(value), Precedence::Atom)
252 }
253 GrammarExpr::TerminalInsensitive(value) => self.render_insensitive(value)?,
254 GrammarExpr::CharRange(start, end) => {
255 if start > end {
256 return Err(unsupported_error(
257 FORMAT,
258 format!("descending character range {start:?}..{end:?}"),
259 ));
260 }
261 (
262 format!("{}..{}", quote(&start.to_string()), quote(&end.to_string())),
263 Precedence::Atom,
264 )
265 }
266 GrammarExpr::CharClass { negated, items } => self.render_class(*negated, items)?,
267 GrammarExpr::AnyChar => (".".to_string(), Precedence::Atom),
268 GrammarExpr::NonTerminal(name) => {
269 (self.names.resolve(name).to_string(), Precedence::Atom)
270 }
271 GrammarExpr::Choice {
272 ordered,
273 alternatives,
274 } => {
275 if *ordered && alternatives.len() > 1 {
276 self.note("treats ordered choice as unordered choice");
277 }
278 let rendered = alternatives
279 .iter()
280 .map(|alternative| self.render(alternative))
281 .collect::<Result<Vec<_>, _>>()?;
282 join_choice(rendered)
283 }
284 GrammarExpr::Sequence(items) => self.render_sequence(items)?,
285 GrammarExpr::Optional(inner) => self.render_postfix(inner, "?")?,
286 GrammarExpr::ZeroOrMore(inner) => self.render_postfix(inner, "*")?,
287 GrammarExpr::OneOrMore(inner) => self.render_postfix(inner, "+")?,
288 GrammarExpr::Repeat { expr, min, max } => {
289 let lowered = lower_repeat(FORMAT, expr, *min, *max)?;
290 self.note(&format!(
291 "expanded counted repetition {}",
292 repeat_bounds(*min, *max)
293 ));
294 self.render(&lowered)?
295 }
296 GrammarExpr::And(_) => {
297 self.note("dropped positive lookahead");
298 (String::new(), Precedence::Sequence)
299 }
300 GrammarExpr::Not(inner) => self.render_not(inner)?,
301 GrammarExpr::Capture { label, expr } => self.render_capture(label.as_deref(), expr)?,
302 GrammarExpr::Feature(feature) => {
303 return Err(unsupported_error(GrammarFormat::Antlr, feature.head()));
304 }
305 })
306 }
307
308 fn render_sequence(&mut self, items: &[GrammarExpr]) -> Result<Rendered, GrammarEmitError> {
309 let mut rendered = Vec::with_capacity(items.len());
310 let mut index = 0;
311 while index < items.len() {
312 if self.foreign
313 && let Some((negated, class)) = negated_lookahead_set(items, index)
314 {
315 self.note(
316 "lowered negative lookahead followed by any character to a complemented set",
317 );
318 rendered.push(render_set(negated, &class));
319 index += 2;
320 continue;
321 }
322 rendered.push(self.render(&items[index])?);
323 index += 1;
324 }
325 Ok(join_sequence(rendered))
326 }
327
328 fn render_postfix(
329 &mut self,
330 inner: &GrammarExpr,
331 operator: &str,
332 ) -> Result<Rendered, GrammarEmitError> {
333 let operand = self.render(inner)?;
334 Ok((
335 format!("{}{operator}", wrap(operand, Precedence::Prefix)),
336 Precedence::Postfix,
337 ))
338 }
339
340 fn render_not(&mut self, inner: &GrammarExpr) -> Result<Rendered, GrammarEmitError> {
341 if matches!(inner, GrammarExpr::CharClass { negated: false, .. }) {
342 self.note("emitted negative lookahead over a character set as a complemented set");
343 } else if self.foreign {
344 self.note(
345 "emitted negative lookahead as the `~` complement, which consumes one symbol",
346 );
347 }
348 let operand = self.render(inner)?;
349 Ok((
350 format!("~{}", wrap(operand, Precedence::Atom)),
351 Precedence::Prefix,
352 ))
353 }
354
355 fn render_capture(
356 &mut self,
357 label: Option<&str>,
358 inner: &GrammarExpr,
359 ) -> Result<Rendered, GrammarEmitError> {
360 let Some(label) = label else {
361 self.note("dropped anonymous capture");
362 return self.render(inner);
363 };
364 if label == "non_greedy"
365 && matches!(
366 inner,
367 GrammarExpr::Optional(_) | GrammarExpr::ZeroOrMore(_) | GrammarExpr::OneOrMore(_)
368 )
369 {
370 let (text, _precedence) = self.render(inner)?;
371 return Ok((format!("{text}?"), Precedence::Postfix));
372 }
373 let emitted = label_candidate(label);
374 if emitted != label {
375 self.note(&format!("renamed capture label {label:?} to {emitted:?}"));
376 }
377 if label == "regex"
378 && self.grammar.source_format() == Some(GrammarFormat::Lark)
379 && let GrammarExpr::Terminal(pattern) = inner
380 {
381 self.note(&format!(
382 "emitted regex /{pattern}/ as a literal labelled `regex`; regex semantics are not translated"
383 ));
384 }
385 let operand = self.render(inner)?;
386 Ok((
387 format!("{emitted}={}", wrap(operand, Precedence::Postfix)),
388 Precedence::Labeled,
389 ))
390 }
391
392 fn render_insensitive(&mut self, value: &str) -> Result<Rendered, GrammarEmitError> {
393 if value.is_empty() {
394 return self.render(&GrammarExpr::Terminal(String::new()));
395 }
396 let mut items = Vec::new();
397 let mut literal = String::new();
398 for character in value.chars() {
399 if let Some(variants) = case_variants(character) {
400 if !literal.is_empty() {
401 items.push(GrammarExpr::Terminal(std::mem::take(&mut literal)));
402 }
403 items.push(GrammarExpr::CharClass {
404 negated: false,
405 items: variants.into_iter().map(CharClassItem::Char).collect(),
406 });
407 } else {
408 literal.push(character);
409 }
410 }
411 if !literal.is_empty() {
412 items.push(GrammarExpr::Terminal(literal));
413 }
414 self.note(&format!(
415 "expanded case-insensitive literal {value:?} to per-letter character sets"
416 ));
417 let lowered = if items.len() == 1 {
418 items.remove(0)
419 } else {
420 GrammarExpr::Sequence(items)
421 };
422 self.render(&lowered)
423 }
424
425 fn render_class(
426 &mut self,
427 negated: bool,
428 items: &[CharClassItem],
429 ) -> Result<Rendered, GrammarEmitError> {
430 if items.is_empty() {
431 if negated {
432 self.note("emitted an empty negated character class as `.`");
433 return Ok((".".to_string(), Precedence::Atom));
434 }
435 return Err(unsupported_error(FORMAT, "empty character class"));
436 }
437 if let Some(CharClassItem::Range(start, end)) = items
438 .iter()
439 .find(|item| matches!(item, CharClassItem::Range(start, end) if start > end))
440 {
441 return Err(unsupported_error(
442 FORMAT,
443 format!("descending character class range {start:?}-{end:?}"),
444 ));
445 }
446 Ok(render_set(negated, items))
447 }
448}
449
450const fn is_lexer_kind(kind: RuleKind) -> bool {
451 !matches!(kind, RuleKind::Normal)
452}
453
454fn rule_candidate(name: &str, lexer: bool) -> String {
456 let mut output = ascii_identifier(name);
457 match output.chars().next() {
458 Some(first) if first.is_ascii_alphabetic() => {
459 let fixed = if lexer {
460 first.to_ascii_uppercase()
461 } else {
462 first.to_ascii_lowercase()
463 };
464 output.replace_range(..1, fixed.encode_utf8(&mut [0; 4]));
465 }
466 _ => output.insert(0, if lexer { 'T' } else { 'r' }),
467 }
468 let reserved = if lexer {
469 RESERVED_TOKENS
470 } else {
471 RESERVED_WORDS
472 };
473 if reserved.contains(&output.as_str()) {
474 output.push('_');
475 }
476 output
477}
478
479fn reference_candidate(name: &str) -> String {
481 let lexer = name.starts_with(|c: char| c.is_ascii_uppercase());
482 let mut output = ascii_identifier(name);
483 if !output.starts_with(|c: char| c.is_ascii_alphabetic()) {
484 output.insert(0, if lexer { 'T' } else { 'r' });
485 }
486 if RESERVED_WORDS.contains(&output.as_str()) {
487 output.push('_');
488 }
489 output
490}
491
492fn label_candidate(label: &str) -> String {
493 let mut output = ascii_identifier(label);
494 if !output.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_') {
495 output.insert(0, 'l');
496 }
497 if RESERVED_WORDS.contains(&output.as_str()) {
498 output.push('_');
499 }
500 output
501}
502
503fn pascal_case(name: &str) -> String {
504 name.split('_')
505 .filter(|part| !part.is_empty())
506 .map(|part| {
507 let mut chars = part.chars();
508 chars.next().map_or_else(String::new, |first| {
509 first.to_ascii_uppercase().to_string() + chars.as_str()
510 })
511 })
512 .collect()
513}
514
515fn uses_character_constructs(expr: &GrammarExpr) -> bool {
516 match expr {
517 GrammarExpr::TerminalInsensitive(_)
518 | GrammarExpr::CharRange(_, _)
519 | GrammarExpr::CharClass { .. }
520 | GrammarExpr::AnyChar => true,
521 GrammarExpr::Choice { alternatives, .. } => {
522 alternatives.iter().any(uses_character_constructs)
523 }
524 GrammarExpr::Sequence(items) => items.iter().any(uses_character_constructs),
525 GrammarExpr::Optional(inner)
526 | GrammarExpr::ZeroOrMore(inner)
527 | GrammarExpr::OneOrMore(inner)
528 | GrammarExpr::And(inner)
529 | GrammarExpr::Not(inner)
530 | GrammarExpr::Repeat { expr: inner, .. }
531 | GrammarExpr::Capture { expr: inner, .. } => uses_character_constructs(inner),
532 GrammarExpr::Empty
533 | GrammarExpr::Terminal(_)
534 | GrammarExpr::NonTerminal(_)
535 | GrammarExpr::Feature(_) => false,
536 }
537}
538
539fn quote(value: &str) -> String {
541 let mut output = String::from("'");
542 for character in value.chars() {
543 match character {
544 '\'' => output.push_str("\\'"),
545 '\\' => output.push_str("\\\\"),
546 '\n' => output.push_str("\\n"),
547 '\r' => output.push_str("\\r"),
548 '\t' => output.push_str("\\t"),
549 '\u{08}' => output.push_str("\\b"),
550 '\u{0c}' => output.push_str("\\f"),
551 character if character.is_control() => {
552 let _ = write!(output, "\\u{:04X}", u32::from(character));
553 }
554 character => output.push(character),
555 }
556 }
557 output.push('\'');
558 output
559}
560
561fn render_set(negated: bool, items: &[CharClassItem]) -> Rendered {
563 let mut body = String::new();
564 for item in items {
565 match item {
566 CharClassItem::Char(character) => push_set_char(&mut body, *character),
567 CharClassItem::Range(start, end) => {
568 push_set_char(&mut body, *start);
569 body.push('-');
570 push_set_char(&mut body, *end);
571 }
572 }
573 }
574 if negated {
575 (format!("~[{body}]"), Precedence::Prefix)
576 } else {
577 (format!("[{body}]"), Precedence::Atom)
578 }
579}
580
581fn push_set_char(body: &mut String, character: char) {
582 match character {
583 '\\' | ']' | '-' => {
584 body.push('\\');
585 body.push(character);
586 }
587 '^' if body.is_empty() => body.push_str("\\^"),
588 '\n' => body.push_str("\\n"),
589 '\r' => body.push_str("\\r"),
590 '\t' => body.push_str("\\t"),
591 '\u{08}' => body.push_str("\\b"),
592 '\u{0c}' => body.push_str("\\f"),
593 character => body.push(character),
594 }
595}
596
597#[derive(Debug)]
599struct DocPlan {
600 comments: Vec<String>,
601 command: Option<String>,
602 reimported: Option<String>,
603}
604
605fn plan_doc(doc: Option<&str>, lexer: bool) -> DocPlan {
609 let Some(doc) = doc else {
610 return DocPlan {
611 comments: Vec::new(),
612 command: None,
613 reimported: None,
614 };
615 };
616 let (text, command) = if lexer {
617 split_command(doc)
618 } else {
619 (doc, None)
620 };
621 let comments = comment_lines(text);
622 let mut parts = comments
623 .iter()
624 .map(|comment| comment.trim().to_string())
625 .collect::<Vec<_>>();
626 parts.extend(command.clone());
627 DocPlan {
628 comments,
629 command,
630 reimported: (!parts.is_empty()).then(|| parts.join("; ")),
631 }
632}
633
634fn split_command(doc: &str) -> (&str, Option<String>) {
637 let (text, command) = if doc.starts_with("->") {
638 ("", doc)
639 } else if let Some(position) = doc.rfind("; ->") {
640 (&doc[..position], &doc[position + 2..])
641 } else {
642 return (doc, None);
643 };
644 let probe = format!("A : 'a' {command} ;");
645 let round_trips = !command.contains(['\n', '\r'])
646 && import_antlr(&probe)
647 .ok()
648 .and_then(|grammar| {
649 grammar
650 .rule("A")
651 .and_then(GrammarRule::doc)
652 .map(str::to_string)
653 })
654 .is_some_and(|reimported| reimported == command);
655 if round_trips {
656 (text, Some(command.to_string()))
657 } else {
658 (doc, None)
659 }
660}
661
662fn comment_lines(text: &str) -> Vec<String> {
665 let mut lines = Vec::new();
666 for part in split_comment_parts(text) {
667 let block = part.len() >= 4
668 && part.starts_with("/*")
669 && part[2..].find("*/") == Some(part.len() - 4);
670 let line = part.starts_with("//") && !part.contains(['\n', '\r']);
671 if block || line {
672 lines.push(part.to_string());
673 } else {
674 lines.extend(super::structural::doc_lines(part).map(|line| {
675 if line.starts_with("//") {
676 line.to_string()
677 } else {
678 format!("// {line}")
679 }
680 }));
681 }
682 }
683 lines
684}
685
686fn split_comment_parts(text: &str) -> Vec<&str> {
688 let mut parts = Vec::new();
689 let mut rest = text;
690 loop {
691 let split = ["; //", "; /*"]
692 .iter()
693 .filter_map(|separator| rest.find(separator))
694 .min();
695 let Some(position) = split else {
696 parts.push(rest);
697 break;
698 };
699 parts.push(&rest[..position]);
700 rest = &rest[position + 2..];
701 }
702 parts.retain(|part| !part.trim().is_empty());
703 parts
704}