Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
32 changes: 29 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,28 @@ fn main() -> Result<(), antlr4_runtime::AntlrError> {
}
```

Or construct each layer explicitly when you need to set source names, parser
options, or custom error handling before invoking the entry rule:
Use `parse_stream` with a preconstructed `CharStream` when parsing a file,
preserving its source name, or supplying a custom stream implementation:

```rust
use std::fs::File;
use antlr4_runtime::InputStream;
use generated::json_lexer::JsonLexer;
use generated::json_parser::{self, JsonParser};

let input = InputStream::from_reader_with_source_name(
File::open(path)?,
path.display().to_string(),
)?;
let parsed = json_parser::parse_stream(input, JsonLexer::new, JsonParser::json)?;
```

`InputStream::from_reader` accepts any `std::io::Read` and validates UTF-8.
`parse_stream_with_parser` is the corresponding stream-based helper when the
caller also needs the parser afterward.

Construct each layer explicitly when you need parser options or custom error
handling before invoking the entry rule:

```rust
use antlr4_runtime::{CommonTokenStream, InputStream};
Expand Down Expand Up @@ -593,7 +613,13 @@ byte value (`0..=255`). It is generic over the
backing store — `ByteStream::new(vec)` owns, `ByteStream::new(&buf[..])` borrows
a network buffer zero-copy, and `ByteStream::from_reader(file)?` drains any
`std::io::Read`. Because the bytes are not text, `text()` renders a matched span
as lowercase hex.
as lowercase hex. Generated parser modules accept it through `parse_stream`, so
binary inputs retain the same compact lexer/token-stream/parser setup as text:

```rust
let input = ByteStream::from_reader(file)?;
let parsed = midi_parser::parse_stream(input, MidiLexer::new, MidiParser::file)?;
```

Length-prefixed formats ("read N, then consume N bytes") are data-dependent, so
a pure grammar cannot frame them alone — the same constraint ANTLR's `bencoding`
Expand Down
32 changes: 28 additions & 4 deletions docs/kotlin-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,32 @@ assert!(tree.text().contains("fun"));
assert!(!tokens.tokens().is_empty());
```

The generated helper is additive. The explicit path is still available when the
caller needs to name the input source, adjust parser options, or attach custom
error handling before the entry rule:
Use `parse_stream` to parse directly from a Rust reader while retaining the
input's source name:

```rust
use std::fs::File;
use antlr4_runtime::InputStream;
use generated::kotlin_lexer::KotlinLexer;
use generated::kotlin_parser::{self, KotlinParser};

let path = std::path::Path::new("Main.kt");
let input = InputStream::from_reader_with_source_name(
File::open(path).expect("Kotlin source should open"),
path.display().to_string(),
)
.expect("Kotlin source should be UTF-8");
let parsed = kotlin_parser::parse_stream(input, KotlinLexer::new, KotlinParser::kotlin_file)
.expect("entry rule parses");
assert!(parsed.tree().text().contains("fun"));
```

`InputStream::from_reader` also accepts stdin, sockets, cursors, and other
`std::io::Read` implementations when no explicit source name is needed.

The generated helpers are additive. The explicit path remains available when
the caller needs to adjust parser options or attach custom error handling before
the entry rule:

```rust
use antlr4_runtime::{CommonTokenStream, InputStream};
Expand All @@ -105,4 +128,5 @@ let tree = parser.kotlin_file().expect("entry rule parses");
assert!(tree.text().contains("fun"));
```

Validated locally: the generated Kotlin lexer emits real tokens and the generated parser recognizes the `parser.kotlin_file()` entry rule for `fun main() {}`.
Validated locally: the generated Kotlin lexer emits real tokens and the generated parser recognizes the `parser.kotlin_file()` entry rule for
`fun main() {}`.
61 changes: 53 additions & 8 deletions src/bin/antlr4-rust-gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13512,12 +13512,12 @@ fn render_parser_base_initialization(
out
}

/// Renders the parser-module convenience that wires text input through the
/// caller-selected lexer, token stream, parser, and entry rule in one call.
/// Renders parser-module conveniences that wire text or a caller-provided
/// character stream through the lexer, token stream, parser, and entry rule.
fn render_parser_parse_convenience(type_name: &str) -> String {
let output_type_name = format!("{type_name}ParseOutput");
format!(
r#"/// Result from [`parse_with_parser`].
r#"/// Result from [`parse_with_parser`] or [`parse_stream_with_parser`].
///
/// Keeps the generated parser available after the entry rule runs so callers
/// can inspect diagnostics or recover the parser-owned token stream.
Expand Down Expand Up @@ -13546,8 +13546,7 @@ pub fn parse<L: TokenSource>(
entry: impl FnOnce(&mut {type_name}<L>) -> Result<antlr4_runtime::NodeId, antlr4_runtime::AntlrError>,
) -> Result<antlr4_runtime::ParsedFile, antlr4_runtime::AntlrError>
{{
let {output_type_name} {{ result, parser }} = parse_with_parser(input, lexer, entry)?;
Ok(parser.into_parsed_file(result))
parse_stream(antlr4_runtime::InputStream::new(input.as_ref()), lexer, entry)
}}

/// Parses UTF-8 text like [`parse`] while returning the parser after the entry
Expand All @@ -13561,7 +13560,39 @@ pub fn parse_with_parser<L: TokenSource, R>(
entry: impl FnOnce(&mut {type_name}<L>) -> Result<R, antlr4_runtime::AntlrError>,
) -> Result<{output_type_name}<R, L>, antlr4_runtime::AntlrError>
{{
let lexer = lexer(antlr4_runtime::InputStream::new(input.as_ref()));
parse_stream_with_parser(
antlr4_runtime::InputStream::new(input.as_ref()),
lexer,
entry,
)
}}

/// Parses a caller-provided character stream by constructing the lexer, token
/// stream, parser, and caller-selected entry rule in one call.
///
/// Unlike [`parse`], this accepts any [`antlr4_runtime::CharStream`], including
/// a named [`antlr4_runtime::InputStream`] or a byte-oriented
/// [`antlr4_runtime::ByteStream`].
pub fn parse_stream<I: antlr4_runtime::CharStream, L: TokenSource>(
Comment thread
tinovyatkin marked this conversation as resolved.
input: I,
lexer: impl FnOnce(I) -> L,
entry: impl FnOnce(&mut {type_name}<L>) -> Result<antlr4_runtime::NodeId, antlr4_runtime::AntlrError>,
) -> Result<antlr4_runtime::ParsedFile, antlr4_runtime::AntlrError>
{{
let {output_type_name} {{ result, parser }} =
parse_stream_with_parser(input, lexer, entry)?;
Ok(parser.into_parsed_file(result))
}}

/// Parses a caller-provided character stream like [`parse_stream`] while
/// returning the parser after the entry rule has run.
pub fn parse_stream_with_parser<I: antlr4_runtime::CharStream, L: TokenSource, R>(
input: I,
lexer: impl FnOnce(I) -> L,
entry: impl FnOnce(&mut {type_name}<L>) -> Result<R, antlr4_runtime::AntlrError>,
) -> Result<{output_type_name}<R, L>, antlr4_runtime::AntlrError>
{{
let lexer = lexer(input);
let tokens = CommonTokenStream::new(lexer);
let mut parser = {type_name}::new(tokens);
let result = entry(&mut parser)?;
Expand Down Expand Up @@ -16156,11 +16187,22 @@ mod tests {
let rendered =
render_parser("TParser", &minimal_parser_data()).expect("parser should render");

insta::assert_snapshot!(
"parser_parse_convenience",
render_parser_parse_convenience("TParser")
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
assert!(rendered.contains("pub struct TParserParseOutput<R, L>"));
assert!(rendered.contains("pub result: R,"));
assert!(rendered.contains("pub parser: TParser<L>,"));
assert!(rendered.contains("pub fn parse<L: TokenSource>("));
assert!(rendered.contains("pub fn parse_with_parser<L: TokenSource, R>("));
assert!(
rendered
.contains("pub fn parse_stream<I: antlr4_runtime::CharStream, L: TokenSource>(")
);
assert!(rendered.contains(
"pub fn parse_stream_with_parser<I: antlr4_runtime::CharStream, L: TokenSource, R>("
));
assert!(
!rendered
.contains(") -> Result<R, antlr4_runtime::AntlrError>\nwhere\n L: TokenSource,")
Expand All @@ -16169,12 +16211,15 @@ mod tests {
") -> Result<TParserParseOutput<R, L>, antlr4_runtime::AntlrError>\nwhere\n L: TokenSource,"
));
assert!(rendered.contains("lexer: impl FnOnce(antlr4_runtime::InputStream) -> L"));
assert!(rendered.contains("antlr4_runtime::InputStream::new(input.as_ref())"));
assert!(rendered.contains(
"parse_stream(antlr4_runtime::InputStream::new(input.as_ref()), lexer, entry)"
));
assert!(rendered.contains("let lexer = lexer(input);"));
assert!(rendered.contains("let tokens = CommonTokenStream::new(lexer);"));
assert!(rendered.contains("let result = entry(&mut parser)?;"));
assert!(rendered.contains("Ok(TParserParseOutput { result, parser })"));
assert!(rendered.contains(
"let TParserParseOutput { result, parser } = parse_with_parser(input, lexer, entry)?;"
"parse_stream_with_parser(\n antlr4_runtime::InputStream::new(input.as_ref()),"
));
assert!(rendered.contains("Ok(parser.into_parsed_file(result))"));
assert!(rendered.contains("pub fn new(input: CommonTokenStream<L>) -> Self"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
source: src/bin/antlr4-rust-gen.rs
expression: "render_parser_parse_convenience(\"TParser\")"
---
/// Result from [`parse_with_parser`] or [`parse_stream_with_parser`].
///
/// Keeps the generated parser available after the entry rule runs so callers
/// can inspect diagnostics or recover the parser-owned token stream.
#[derive(Debug)]
pub struct TParserParseOutput<R, L>
where
L: TokenSource,
{
pub result: R,
pub parser: TParser<L>,
}

/// Parses UTF-8 text by constructing the lexer, token stream, parser, and
/// caller-selected entry rule in one call.
///
/// Pass the generated lexer constructor and a parser entry rule, for example
/// `parse(src, MyGrammarLexer::new, TParser::file)`.
///
/// The returned [`antlr4_runtime::ParsedFile`] owns the canonical token store,
/// flat CST storage, and entry-rule root.
/// Use [`parse_with_parser`] instead when the caller also needs parser
/// diagnostics after the entry rule runs.
pub fn parse<L: TokenSource>(
input: impl AsRef<str>,
lexer: impl FnOnce(antlr4_runtime::InputStream) -> L,
entry: impl FnOnce(&mut TParser<L>) -> Result<antlr4_runtime::NodeId, antlr4_runtime::AntlrError>,
) -> Result<antlr4_runtime::ParsedFile, antlr4_runtime::AntlrError>
{
parse_stream(antlr4_runtime::InputStream::new(input.as_ref()), lexer, entry)
}

/// Parses UTF-8 text like [`parse`] while returning the parser after the entry
/// rule has run.
///
/// This keeps the compact generated setup path available for callers that also
/// need `Parser::number_of_syntax_errors()` or `TParser::into_token_stream()`.
pub fn parse_with_parser<L: TokenSource, R>(
input: impl AsRef<str>,
lexer: impl FnOnce(antlr4_runtime::InputStream) -> L,
entry: impl FnOnce(&mut TParser<L>) -> Result<R, antlr4_runtime::AntlrError>,
) -> Result<TParserParseOutput<R, L>, antlr4_runtime::AntlrError>
{
parse_stream_with_parser(
antlr4_runtime::InputStream::new(input.as_ref()),
lexer,
entry,
)
}

/// Parses a caller-provided character stream by constructing the lexer, token
/// stream, parser, and caller-selected entry rule in one call.
///
/// Unlike [`parse`], this accepts any [`antlr4_runtime::CharStream`], including
/// a named [`antlr4_runtime::InputStream`] or a byte-oriented
/// [`antlr4_runtime::ByteStream`].
pub fn parse_stream<I: antlr4_runtime::CharStream, L: TokenSource>(
input: I,
lexer: impl FnOnce(I) -> L,
entry: impl FnOnce(&mut TParser<L>) -> Result<antlr4_runtime::NodeId, antlr4_runtime::AntlrError>,
) -> Result<antlr4_runtime::ParsedFile, antlr4_runtime::AntlrError>
{
let TParserParseOutput { result, parser } =
parse_stream_with_parser(input, lexer, entry)?;
Ok(parser.into_parsed_file(result))
}

/// Parses a caller-provided character stream like [`parse_stream`] while
/// returning the parser after the entry rule has run.
pub fn parse_stream_with_parser<I: antlr4_runtime::CharStream, L: TokenSource, R>(
input: I,
lexer: impl FnOnce(I) -> L,
entry: impl FnOnce(&mut TParser<L>) -> Result<R, antlr4_runtime::AntlrError>,
) -> Result<TParserParseOutput<R, L>, antlr4_runtime::AntlrError>
{
let lexer = lexer(input);
let tokens = CommonTokenStream::new(lexer);
let mut parser = TParser::new(tokens);
let result = entry(&mut parser)?;
Ok(TParserParseOutput { result, parser })
}
34 changes: 29 additions & 5 deletions src/bin_support/grammar/frontend.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use std::fmt;
use std::ops::Range;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::{Arc, Mutex};

use antlr4_runtime::{
AsRuleNode, CommonTokenStream, ErrorListener, InputStream, Node, NodeId, NodeKind, Parser,
Recognizer, TOKEN_EOF as RUNTIME_TOKEN_EOF, Token,
AsRuleNode, CharStream as _, CommonTokenStream, ErrorListener, InputStream, Node, NodeId,
NodeKind, Parser, Recognizer, TOKEN_EOF as RUNTIME_TOKEN_EOF, Token,
};

use super::generated::antlr_v4_lexer::{
Expand Down Expand Up @@ -143,7 +144,7 @@ impl Iterator for CstDescendants<'_> {
pub(crate) struct SourceFile {
id: SourceId,
logical_path: PathBuf,
text: Box<str>,
text: Rc<str>,
line_starts: Box<[u32]>,
tokens: Box<[SyntaxToken]>,
trivia: Box<[u32]>,
Expand Down Expand Up @@ -265,7 +266,18 @@ pub(crate) fn parse_source(
logical_path: impl Into<PathBuf>,
text: impl Into<Box<str>>,
) -> Result<SourceFile, FrontendError> {
let recovered = parse_source_recovering(source, logical_path, text)?;
let logical_path = logical_path.into();
let text = text.into();
let input = InputStream::with_source_name(&text, logical_path.to_string_lossy());
parse_input_stream(source, logical_path, input)
}

pub(crate) fn parse_input_stream(
source: SourceId,
logical_path: impl Into<PathBuf>,
input: InputStream,
) -> Result<SourceFile, FrontendError> {
let recovered = parse_input_stream_recovering(source, logical_path, input)?;
if recovered.diagnostics.is_empty() {
Ok(recovered.file)
} else {
Expand All @@ -282,8 +294,20 @@ pub(crate) fn parse_source_recovering(
) -> Result<RecoveredSource, FrontendError> {
let logical_path = logical_path.into();
let text = text.into();
let line_starts = line_starts(source, &text)?;
let input = InputStream::with_source_name(&text, logical_path.to_string_lossy());
parse_input_stream_recovering(source, logical_path, input)
}

pub(crate) fn parse_input_stream_recovering(
source: SourceId,
logical_path: impl Into<PathBuf>,
input: InputStream,
) -> Result<RecoveredSource, FrontendError> {
let logical_path = logical_path.into();
let text = input
.source_text()
.expect("InputStream always exposes its complete source text");
let line_starts = line_starts(source, &text)?;
let mut lexer = AntlRv4Lexer::with_hooks(input, LexerAdaptor::default());
lexer.remove_error_listeners();
let mut token_stream = CommonTokenStream::try_new(lexer).map_err(|error| FrontendError {
Expand Down
2 changes: 1 addition & 1 deletion src/bin_support/grammar/generated/antlr_v4_lexer.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading