Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -423,10 +423,12 @@ parser action events are offered to `SemanticHooks::action` after the committed
parse path is selected. Predicate hooks may run speculatively during
prediction, so they must be replay-safe.

For bare helper-call predicates, generated parsers also emit a typed hook
adapter (`MyParserHooks` plus `MyParserTypedHooks<T>`) that maps stable
manifest coordinates to named Rust methods. Lexer callers can use
`LexerSemCtx` with `atn::lexer::next_token_with_semantic_hooks` or the
For helper-call predicates written as `helper()`, `this.helper()`,
`self.helper()`, or the antlr4rust convention `recog.helper()`, generated
parsers also emit a typed hook adapter (`MyParserHooks` plus
`MyParserTypedHooks<T>`) that maps stable manifest coordinates to named Rust
methods. Lexer callers can use `LexerSemCtx` with
`atn::lexer::next_token_with_semantic_hooks` or the
compiled-DFA variant to route lexer predicates/actions through the same
`SemanticHooks` trait. On the committed action path, `LexerSemCtx` exposes the
pending token type/channel, character lookahead and consumption, and mode
Expand Down
18 changes: 18 additions & 0 deletions src/bin/antlr4-rust-gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@ fn parse_semantic_helper_call(body: &str, kind: SemanticsKind) -> Option<Semanti
body = body
.strip_prefix("this.")
.or_else(|| body.strip_prefix("self."))
.or_else(|| body.strip_prefix("recog."))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move antlr4rust receiver handling out of generic codegen

For any non-antlr4rust grammar containing recog.foo() and a matching [[helper]] declaration, this unconditional normalization now changes the expression from unsupported semantics into a typed hook. That embeds an antlr4rust-specific source-language convention in the generic helper parser; make accepted receiver aliases pattern- or metadata-driven instead, leaving the recog convention in language-specific configuration, documentation, and tests.

AGENTS.md reference: AGENTS.md:L5-L9

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 754b2c60c. The generic parser no longer contains recog; [[helper]] now has an optional metadata-driven receiver alias, and only the antlr4rust migration pattern declares receiver = "recog". Unit coverage confirms an unconfigured dotted receiver remains unmatched. Full generator tests, CLI integration tests, and strict clippy pass.

.unwrap_or(body);
let open = body.find('(')?;
let name = body[..open].trim();
Expand Down Expand Up @@ -19431,6 +19432,23 @@ lower = "pop_member(depths)"

#[test]
fn semantic_helper_calls_capture_kind_negation_and_literals() {
let expected = Some(SemanticHelperCall {
name: "isTypeName".to_owned(),
arguments: Vec::new(),
negated: false,
});
for body in [
"isTypeName()",
"this.isTypeName()",
"self.isTypeName()",
"recog.isTypeName()",
] {
assert_eq!(
parse_semantic_helper_call(body, SemanticsKind::ParserPredicate),
expected,
"receiver form {body:?} should parse"
);
}
assert_eq!(
parse_semantic_helper_call(
r#"!this.matches("marker", true, -2)"#,
Expand Down
125 changes: 125 additions & 0 deletions tests/antlr4_rust_gen_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2022,6 +2022,131 @@ fn imported_parser_predicate_generates_typed_hook_from_structural_body() {
);
}

/// Issue #241: antlr4rust grammars use `recog` as the parser-predicate receiver.
/// Supporting that convention keeps migrations source-compatible by routing it
/// through the same typed helper hook as bare, `this.`, and `self.` calls.
#[test]
fn recog_receiver_parser_predicate_routes_to_typed_hook() {
let temp = temporary_directory("recog-parser-hook");
let grammar = temp.path().join("RecogPredicate.g4");
let patterns = temp.path().join("patterns.toml");
let out = temp.path().join("generated");
fs::write(
&grammar,
r#"grammar RecogPredicate;

options {
superClass = ParserBase;
}

shl
: {recog.IsOk()}? LT LT EOF
;

LT: '<';
WS: [ \t\r\n]+ -> skip;
"#,
)
.expect("grammar should be writable");
fs::write(
&patterns,
r#"version = 1

[[helper]]
kind = "parser-predicate"
name = "IsOk"
returns = "bool"
lower = "hook"
"#,
)
.expect("semantic patterns should be writable");

let output = run_antlr4_rust_gen(&[
grammar.as_os_str(),
OsStr::new("--sem-patterns"),
patterns.as_os_str(),
OsStr::new("--option-hook"),
OsStr::new("superClass=ParserBase"),
OsStr::new("--sem-unknown"),
OsStr::new("error"),
OsStr::new("--require-full-semantics"),
OsStr::new("--out-dir"),
out.as_os_str(),
]);
assert!(
output.status.success(),
"stdout: {}\nstderr: {}",
utf8(&output.stdout),
utf8(&output.stderr)
);

let manifest =
fs::read_to_string(out.join("semantics.json")).expect("manifest should be emitted");
let predicate = manifest
.lines()
.find(|line| line.contains("\"kind\": \"parser-predicate\""))
.expect("parser predicate should be inventoried");
for expected in [
"\"body\": \"recog.IsOk()\"",
"\"disposition\": \"hooked\"",
"\"template\": \"Hook\"",
Comment thread
tinovyatkin marked this conversation as resolved.
Resolved
] {
assert!(
predicate.contains(expected),
"missing {expected:?}: {predicate}"
);
}

let parser = fs::read_to_string(out.join("recog_predicate_parser.rs"))
.expect("parser should be emitted");
for expected in [
"pub trait RecogPredicateParserHooks",
"fn is_ok",
"(0, 0) => Some(self.0.is_ok(ctx))",
] {
assert!(parser.contains(expected), "missing {expected:?}\n{parser}");
}

let test_source = r####"
#[cfg(test)]
mod recog_receiver_tests {
use super::recog_predicate_lexer::RecogPredicateLexer;
use super::recog_predicate_parser::{
RecogPredicateParser, RecogPredicateParserHooks,
};
use antlr4_runtime::{
CommonTokenStream, InputStream, Parser as _, ParserSemCtx, TokenSource,
};

struct Hooks;

impl RecogPredicateParserHooks for Hooks {
fn is_ok<L>(&mut self, _ctx: &mut ParserSemCtx<'_, L>) -> bool
where
L: TokenSource,
{
true
Comment thread
tinovyatkin marked this conversation as resolved.
Resolved
}
}

#[test]
fn typed_hook_accepts_the_predicated_alternative() {
let lexer = RecogPredicateLexer::new(InputStream::new("<<"));
let mut parser =
RecogPredicateParser::with_typed_hooks(CommonTokenStream::new(lexer), Hooks);
let _ = parser.shl().expect("predicated rule should parse");
assert_eq!(parser.number_of_syntax_errors(), 0);
}
}
"####;

assert_generated_project(
temp.path(),
&["recog_predicate_lexer.rs", "recog_predicate_parser.rs"],
test_source,
);
}

#[test]
fn imported_lexer_action_generates_typed_hook_from_structural_body() {
let temp = temporary_directory("imported-lexer-hook");
Expand Down
Loading