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
542 changes: 431 additions & 111 deletions src/parser.rs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
source: src/parser.rs
expression: contexts
---
[
(
0,
0,
6,
"1+2",
),
(
0,
0,
1,
"1",
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
source: src/parser.rs
expression: contexts
---
[
(
true,
2,
"a+b",
),
(
false,
1,
"a",
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
source: src/parser.rs
expression: "(root.alt_number(), root.context_alt_number(), root.text())"
---
(
0,
2,
"x<EOF>",
)
13 changes: 13 additions & 0 deletions tools/parse-bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ The script regenerates parsers into `target/parse-bench`, builds:
- a Go ANTLR runner using `github.com/antlr4-go/antlr/v4`,
- a tree-sitter runner using `tree-sitter-language-pack`.

Rust generation keeps the pinned `JavaParser.g4` unchanged, including its
`JavaParserBase` option and both semantic predicates. The generated benchmark
parser uses the source compiler's historical `assume-true` policy for those
target-language helpers, matching downstream generation while retaining the
predicate metadata that controls runtime routing. Python and Go still use the
equivalent portable rewrite because that grammars-v4 revision does not provide
`JavaParserBase` implementations for those targets. The
`issue-174-return-expression.java` fixture guards the resulting Java
method-body performance path. JSON rows include a benchmark-variant tag, so
the comparator skips only method changes such as this legacy-to-predicate
transition and resumes Java regression checks once both reports use the same
variant.

The output table reports `min` and `avg` parse time per fixture and a relative
ratio against `rust-antlr` for the same fixture.
Use `--rust-generated-only` for Adaptive LL delivery evidence so the Rust
Expand Down
37 changes: 29 additions & 8 deletions tools/parse-bench/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
from pathlib import Path


DEFAULT_BENCHMARK_VARIANT = "default"


def result_key(result: dict) -> tuple[str, str, str]:
return (
str(result["language"]),
Expand All @@ -28,6 +31,10 @@ def load_results(path: Path) -> dict[tuple[str, str, str], dict]:
return indexed


def result_variant(result: dict) -> str:
return str(result.get("benchmark_variant", DEFAULT_BENCHMARK_VARIANT))


def parse_speedup_requirement(value: str) -> tuple[str, str, str, float]:
parts = value.split(":")
if len(parts) != 4:
Expand Down Expand Up @@ -131,11 +138,20 @@ def main() -> int:
runtimes = set(args.runtime or ["rust-antlr"])

regression_failures: list[str] = []
variant_mismatches: list[tuple[tuple[str, str, str], str, str]] = []
compared = 0
for key, head in sorted(current.items()):
language, fixture, runtime = key
if runtime not in runtimes or key not in baseline:
base = baseline.get(key)
if runtime not in runtimes or base is None:
continue
base_variant = result_variant(base)
head_variant = result_variant(head)
if base_variant != head_variant:
variant_mismatches.append((key, base_variant, head_variant))
continue
Comment thread
tinovyatkin marked this conversation as resolved.
base_avg = float(baseline[key]["avg_ns"])
compared += 1
base_avg = float(base["avg_ns"])
head_avg = float(head["avg_ns"])
if base_avg <= 0:
continue
Expand All @@ -148,17 +164,22 @@ def main() -> int:
)
speedup_failures = check_speedup_requirements(current, args.require_speedup)

compared = sum(
1
for key in current
if key in baseline and key[2] in runtimes
)
if variant_mismatches:
print(
"parse benchmark compare skipped "
f"{len(variant_mismatches)} result(s) with changed benchmark variants:"
)
for (language, fixture, runtime), base_variant, head_variant in variant_mismatches:
print(
f" {language}/{fixture} {runtime}: "
f"{base_variant} -> {head_variant}"
)
if compared == 0:
message = (
"parse benchmark compare found no matching baseline/current "
f"result pairs for runtime(s): {', '.join(sorted(runtimes))}"
)
if args.allow_empty:
if args.allow_empty or variant_mismatches:
print(f"{message}; skipping regression comparison")
else:
print(message, file=sys.stderr)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class C {
int m() {
return 1;
}
}
10 changes: 10 additions & 0 deletions tools/parse-bench/fixtures/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@
"lex"
]
},
{
"language": "java",
"path": "java/issue-174-return-expression.java",
"source": "repository regression benchmark for issue #174",
"license": "BSD-3-Clause",
"description": "Minimal Java method-body return expression that exposes predicate-aware interpreted-rule routing regressions.",
"phases": [
"parse"
]
},
{
"language": "java",
"path": "java/mojang-data-result.java",
Expand Down
47 changes: 33 additions & 14 deletions tools/parse-bench/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
# ANTLR 4.13.2 still generates Go imports for github.com/antlr4-go/antlr/v4,
# whose latest published module tag is v4.13.1.
GO_ANTLR_RUNTIME = "v4.13.1"
DEFAULT_BENCHMARK_VARIANT = "default"
JAVA_RUST_PREDICATE_VARIANT = "java-upstream-parser-predicates-v1"


@dataclasses.dataclass(frozen=True)
Expand Down Expand Up @@ -144,6 +146,7 @@ class Measurement:
language: str
fixture: str
runtime: str
benchmark_variant: str
min_ns: int
avg_ns: int
bytes: int
Expand Down Expand Up @@ -257,7 +260,7 @@ def predicate_replacement(match: re.Match[str]) -> str:
return grammar.replace("this.", "l.")


def transform_java(grammar_dir: Path) -> None:
def transform_java_for_portable_target(grammar_dir: Path) -> None:
parser = grammar_dir / "JavaParser.g4"
text = parser.read_text()
text = text.replace(" superClass = JavaParserBase;\n", "")
Expand All @@ -274,6 +277,31 @@ def transform_java(grammar_dir: Path) -> None:
parser.write_text(text)


def prepare_runtime_grammar(
spec: LanguageSpec,
grammars_v4: Path,
target: Path,
runtime: str,
) -> None:
copy_grammar(spec, grammars_v4, target)
if spec.name == "csharp":
if runtime == "python-antlr":
transform_csharp_python(target)
elif runtime == "go-antlr":
transform_csharp_go(target)
elif spec.name == "java" and runtime != "rust-antlr":
# The pinned grammars-v4 revision has no Python or Go JavaParserBase.
# Keep their equivalent portable rewrite, but retain the untouched
# predicates for Rust so this benchmark covers runtime semantic routing.
transform_java_for_portable_target(target)


def benchmark_variant(language: str, runtime: str, phase: str) -> str:
if phase == "parse" and language == "java" and runtime == "rust-antlr":
return JAVA_RUST_PREDICATE_VARIANT
return DEFAULT_BENCHMARK_VARIANT


def generate_antlr(
antlr_jar: Path,
spec: LanguageSpec,
Expand Down Expand Up @@ -1217,9 +1245,7 @@ def prepare_work(
for spec in specs:
if "rust-antlr" in runtimes:
base_grammar = work_dir / "grammars" / spec.name / "base"
copy_grammar(spec, args.grammars_v4, base_grammar)
if spec.name == "java":
transform_java(base_grammar)
prepare_runtime_grammar(spec, args.grammars_v4, base_grammar, "rust-antlr")
generate_rust_modules(
spec,
base_grammar,
Expand All @@ -1230,22 +1256,14 @@ def prepare_work(

if "python-antlr" in runtimes:
py_grammar = work_dir / "grammars" / spec.name / "python"
copy_grammar(spec, args.grammars_v4, py_grammar)
if spec.name == "csharp":
transform_csharp_python(py_grammar)
if spec.name == "java":
transform_java(py_grammar)
prepare_runtime_grammar(spec, args.grammars_v4, py_grammar, "python-antlr")
py_lang_gen = py_gen
generate_antlr(args.antlr_jar, spec, py_grammar, py_lang_gen, "Python3")
prepare_python_support(spec, args.grammars_v4, py_lang_gen)

if "go-antlr" in runtimes:
go_grammar = work_dir / "grammars" / spec.name / "go"
copy_grammar(spec, args.grammars_v4, go_grammar)
if spec.name == "csharp":
transform_csharp_go(go_grammar)
if spec.name == "java":
transform_java(go_grammar)
prepare_runtime_grammar(spec, args.grammars_v4, go_grammar, "go-antlr")
go_gen = work_dir / "generated" / spec.name / "go"
package_name = go_package_name(spec)
generate_antlr(args.antlr_jar, spec, go_grammar, go_gen, "Go", package_name)
Expand Down Expand Up @@ -1361,6 +1379,7 @@ def measure_fixture(
language=fixture.language,
fixture=fixture.name,
runtime=runtime,
benchmark_variant=benchmark_variant(fixture.language, runtime, args.phase),
min_ns=int(match.group("min")),
avg_ns=int(match.group("avg")),
bytes=fixture.abs_path.stat().st_size,
Expand Down
Loading
Loading