From 97edb2683ec017c7fbf0998a2b7bd8fc97610cff Mon Sep 17 00:00:00 2001 From: Aliaksandr Kukrash Date: Tue, 21 Jul 2026 22:25:49 +0200 Subject: [PATCH 1/3] Implement cross-stack benchmarks program (phases 1-6, 8 functional) Phase 1: five new workloads + 20 twins, parity contract v2 gates (N3b), golden corpus with export-corpus/verify-corpus verbs, five new benchmark suites. Phases 2-6: Rust (Askama/Tera + Criterion), JVM (JTE/Thymeleaf + JMH), JS (Handlebars/Eta + mitata), Python (Jinja2/Mako + pyperf), Go (text/html/template + templ) harnesses under benchmarks// - each with all 32 controlled+idiomatic cells passing the byte gate/verifier. Phase 8: linux-crosscheck tooling (validate.py, launchers, env capture); full gate sweep green on WSL Ubuntu 24.04 at exact toolchain pins. Measurement/publication runs deferred to a dedicated session; Phase 7 awaits published runs per its go/no-go gate. Errata E1-E3 recorded in docs/spec/records.md. Bug fixes related to type resolution Signed-off-by: Aliaksandr Kukrash --- .gitattributes | 19 + .github/workflows/docs.yml | 4 +- .github/workflows/dotnet.yml | 6 +- .github/workflows/lsp.yml | 8 +- .github/workflows/npm.yml | 7 +- .github/workflows/samples.yml | 2 +- .gitignore | 5 + benchmarks/go/.gitattributes | 5 + benchmarks/go/README.md | 113 ++ benchmarks/go/go.mod | 31 + benchmarks/go/go.sum | 47 + benchmarks/go/internal/corpus/gate.go | 235 ++++ benchmarks/go/internal/corpus/gate_test.go | 149 +++ benchmarks/go/internal/corpus/normalize.go | 181 +++ .../go/internal/corpus/normalize_test.go | 172 +++ benchmarks/go/internal/corpus/verify.go | 160 +++ benchmarks/go/internal/corpus/verify_test.go | 256 ++++ benchmarks/go/internal/model/composed.go | 97 ++ .../model/data/composed-page/area-1.html | 1 + .../model/data/composed-page/area-2.html | 138 ++ .../model/data/composed-page/area-3.html | 116 ++ .../model/data/composed-page/area-4.html | 409 ++++++ .../model/data/composed-page/area-5.html | 413 ++++++ .../model/data/composed-page/area-6.html | 0 .../model/data/composed-page/area-7.html | 45 + .../composed-page/comp-assets-scripts.html | 1 + .../composed-page/comp-assets-styles.html | 1 + .../composed-page/comp-body-end-scripts.html | 1 + .../data/composed-page/comp-body-scripts.html | 1 + .../composed-page/comp-custom-styles.html | 1 + .../data/composed-page/comp-head-scripts.html | 1 + .../data/composed-page/section-meta.html | 1 + .../data/composed-page/section-social.html | 1 + benchmarks/go/internal/model/model_test.go | 196 +++ benchmarks/go/internal/model/models.go | 262 ++++ benchmarks/go/internal/spike/spike.templ | 48 + benchmarks/go/internal/spike/spike_templ.go | 453 +++++++ benchmarks/go/internal/spike/spike_test.go | 220 ++++ benchmarks/go/internal/stdlibtpl/coldparse.go | 40 + benchmarks/go/internal/stdlibtpl/idiomatic.go | 266 ++++ benchmarks/go/internal/stdlibtpl/render.go | 88 ++ benchmarks/go/internal/stdlibtpl/templates.go | 112 ++ .../go/internal/templeng/composed_page.templ | 25 + .../internal/templeng/composed_page_templ.go | 96 ++ .../internal/templeng/conditional_heavy.templ | 32 + .../templeng/conditional_heavy_templ.go | 127 ++ .../go/internal/templeng/encoded_loop.templ | 15 + .../internal/templeng/encoded_loop_templ.go | 99 ++ .../internal/templeng/fortunes_encoded.templ | 31 + .../templeng/fortunes_encoded_templ.go | 104 ++ .../go/internal/templeng/fragment_heavy.templ | 19 + .../internal/templeng/fragment_heavy_templ.go | 128 ++ .../templeng/idiomatic/composed_page.templ | 26 + .../templeng/idiomatic/composed_page_templ.go | 98 ++ .../idiomatic/conditional_heavy.templ | 40 + .../idiomatic/conditional_heavy_templ.go | 164 +++ .../templeng/idiomatic/encoded_loop.templ | 27 + .../templeng/idiomatic/encoded_loop_templ.go | 138 ++ .../templeng/idiomatic/fortunes_encoded.templ | 45 + .../idiomatic/fortunes_encoded_templ.go | 141 ++ .../templeng/idiomatic/fragment_heavy.templ | 27 + .../idiomatic/fragment_heavy_templ.go | 136 ++ .../templeng/idiomatic/large_loop.templ | 23 + .../templeng/idiomatic/large_loop_templ.go | 113 ++ .../templeng/idiomatic/mixed_page.templ | 97 ++ .../templeng/idiomatic/mixed_page_templ.go | 493 +++++++ .../go/internal/templeng/idiomatic/render.go | 79 ++ .../idiomatic/trivial_substitution.templ | 25 + .../idiomatic/trivial_substitution_templ.go | 184 +++ .../go/internal/templeng/large_loop.templ | 12 + .../go/internal/templeng/large_loop_templ.go | 76 ++ .../go/internal/templeng/mixed_page.templ | 61 + .../go/internal/templeng/mixed_page_templ.go | 279 ++++ benchmarks/go/internal/templeng/render.go | 78 ++ .../templeng/trivial_substitution.templ | 11 + .../templeng/trivial_substitution_templ.go | 180 +++ benchmarks/go/run-benchmarks.ps1 | 103 ++ benchmarks/go/suites/bench_test.go | 52 + benchmarks/go/suites/coldparse_test.go | 37 + benchmarks/go/suites/gate_test.go | 170 +++ benchmarks/js/.gitattributes | 1 + benchmarks/js/.gitignore | 2 + benchmarks/js/.npmrc | 2 + benchmarks/js/README.md | 31 + benchmarks/js/bench/_shared.mjs | 185 +++ benchmarks/js/bench/cold-compile.mjs | 127 ++ benchmarks/js/bench/controlled.mjs | 26 + benchmarks/js/bench/idiomatic.mjs | 26 + benchmarks/js/package-lock.json | 100 ++ benchmarks/js/package.json | 18 + benchmarks/js/run.ps1 | 94 ++ benchmarks/js/src/engines/eta.mjs | 88 ++ benchmarks/js/src/engines/handlebars.mjs | 112 ++ benchmarks/js/src/engines/index.mjs | 29 + benchmarks/js/src/gate/controlled.mjs | 72 + benchmarks/js/src/gate/corpus.mjs | 94 ++ benchmarks/js/src/gate/normalize.mjs | 107 ++ benchmarks/js/src/gate/run-all.mjs | 71 + benchmarks/js/src/gate/verifier.mjs | 74 ++ benchmarks/js/src/models/_deep-freeze.mjs | 12 + benchmarks/js/src/models/composed-page.mjs | 1169 +++++++++++++++++ .../js/src/models/conditional-heavy.mjs | 19 + benchmarks/js/src/models/encoded-loop.mjs | 16 + benchmarks/js/src/models/fortunes-encoded.mjs | 22 + benchmarks/js/src/models/fragment-heavy.mjs | 14 + benchmarks/js/src/models/large-loop.mjs | 11 + benchmarks/js/src/models/mixed-page.mjs | 29 + .../js/src/models/trivial-substitution.mjs | 18 + .../eta/controlled/composed-page.eta | 1 + .../eta/controlled/conditional-heavy.eta | 1 + .../templates/eta/controlled/encoded-loop.eta | 1 + .../eta/controlled/fortunes-encoded.eta | 1 + .../eta/controlled/fragment-heavy.eta | 1 + .../templates/eta/controlled/large-loop.eta | 1 + .../eta/controlled/layout.partial.eta | 1 + .../templates/eta/controlled/mixed-page.eta | 29 + .../templates/eta/controlled/tile.partial.eta | 1 + .../eta/controlled/trivial-substitution.eta | 1 + .../templates/eta/idiomatic/composed-page.eta | 6 + .../eta/idiomatic/conditional-heavy.eta | 22 + .../templates/eta/idiomatic/encoded-loop.eta | 9 + .../eta/idiomatic/fortunes-encoded.eta | 16 + .../eta/idiomatic/fragment-heavy.eta | 9 + .../templates/eta/idiomatic/large-loop.eta | 7 + .../templates/eta/idiomatic/mixed-page.eta | 24 + .../templates/eta/idiomatic/page.layout.eta | 25 + .../templates/eta/idiomatic/shell.layout.eta | 8 + .../templates/eta/idiomatic/tile.partial.eta | 9 + .../eta/idiomatic/trivial-substitution.eta | 15 + .../handlebars/controlled/composed-page.hbs | 1 + .../controlled/conditional-heavy.hbs | 1 + .../handlebars/controlled/encoded-loop.hbs | 1 + .../controlled/fortunes-encoded.hbs | 1 + .../handlebars/controlled/fragment-heavy.hbs | 1 + .../handlebars/controlled/large-loop.hbs | 1 + .../handlebars/controlled/layout.partial.hbs | 1 + .../handlebars/controlled/mixed-page.hbs | 29 + .../handlebars/controlled/tile.partial.hbs | 1 + .../controlled/trivial-substitution.hbs | 1 + .../handlebars/idiomatic/composed-page.hbs | 6 + .../idiomatic/conditional-heavy.hbs | 6 + .../handlebars/idiomatic/encoded-loop.hbs | 6 + .../handlebars/idiomatic/fortunes-encoded.hbs | 6 + .../handlebars/idiomatic/fragment-heavy.hbs | 6 + .../handlebars/idiomatic/large-loop.hbs | 6 + .../handlebars/idiomatic/layout.partial.hbs | 6 + .../handlebars/idiomatic/mixed-page.hbs | 34 + .../handlebars/idiomatic/tile.partial.hbs | 6 + .../idiomatic/trivial-substitution.hbs | 6 + benchmarks/js/test/gate-selftest.mjs | 442 +++++++ benchmarks/jvm/.gitignore | 1 + .../jvm/.mvn/wrapper/maven-wrapper.properties | 3 + benchmarks/jvm/README.md | 30 + benchmarks/jvm/mvnw | 295 +++++ benchmarks/jvm/mvnw.cmd | 189 +++ benchmarks/jvm/pom.xml | 179 +++ .../jvm/bench/ComposedPageBench.java | 89 ++ .../jvm/bench/ConditionalHeavyBench.java | 89 ++ .../jvm/bench/EncodedLoopBench.java | 89 ++ .../jvm/bench/FortunesEncodedBench.java | 89 ++ .../jvm/bench/FragmentHeavyBench.java | 89 ++ .../benchmarks/jvm/bench/LargeLoopBench.java | 89 ++ .../benchmarks/jvm/bench/MixedPageBench.java | 89 ++ .../jvm/bench/TrivialSubstitutionBench.java | 89 ++ .../benchmarks/jvm/engines/JteEngines.java | 115 ++ .../jvm/engines/ThymeleafEngines.java | 167 +++ .../heddle/benchmarks/jvm/gate/Corpus.java | 198 +++ .../heddle/benchmarks/jvm/gate/GateCli.java | 519 ++++++++ .../heddle/benchmarks/jvm/gate/Gates.java | 52 + .../benchmarks/jvm/gate/Normalizer.java | 217 +++ .../heddle/benchmarks/jvm/gate/Verifier.java | 144 ++ .../jvm/jte/FiveEntityHtmlOutput.java | 82 ++ .../heddle/benchmarks/jvm/model/Models.java | 427 ++++++ benchmarks/jvm/src/main/jte-html/.gitkeep | 0 .../main/jte-html/controlled/encoded-loop.jte | 2 + .../jte-html/controlled/fortunes-encoded.jte | 2 + .../main/jte-html/idiomatic/encoded-loop.jte | 16 + .../jte-html/idiomatic/fortunes-encoded.jte | 27 + benchmarks/jvm/src/main/jte-plain/.gitkeep | 0 .../jte-plain/controlled/composed-page.jte | 2 + .../controlled/conditional-heavy.jte | 2 + .../jte-plain/controlled/fragment-heavy.jte | 2 + .../main/jte-plain/controlled/large-loop.jte | 2 + .../src/main/jte-plain/controlled/layout.jte | 2 + .../main/jte-plain/controlled/mixed-page.jte | 30 + .../src/main/jte-plain/controlled/tile.jte | 2 + .../controlled/trivial-substitution.jte | 2 + .../jte-plain/idiomatic/composed-page.jte | 7 + .../jte-plain/idiomatic/conditional-heavy.jte | 28 + .../jte-plain/idiomatic/fragment-heavy.jte | 11 + .../main/jte-plain/idiomatic/large-loop.jte | 12 + .../src/main/jte-plain/idiomatic/layout.jte | 19 + .../main/jte-plain/idiomatic/mixed-page.jte | 49 + .../jvm/src/main/jte-plain/idiomatic/tile.jte | 11 + .../idiomatic/trivial-substitution.jte | 19 + .../resources/composed-page/area-order.txt | 7 + .../area.1.alert-top-section-above-nav.txt | 1 + .../area.2.secondary-wholesale-menu.txt | 138 ++ .../area.3.secondary-retail-menu.txt | 116 ++ .../area.4.wholesale-top-mega-menu.txt | 409 ++++++ .../area.5.retail-top-mega-menu.txt | 413 ++++++ .../area.6.alert-top-section-below-nav.txt | 0 .../composed-page/area.7.footer-links.txt | 45 + .../composed-page/comp.assets_scripts.txt | 1 + .../composed-page/comp.assets_styles.txt | 1 + .../composed-page/comp.body_end_scripts.txt | 1 + .../composed-page/comp.body_scripts.txt | 1 + .../composed-page/comp.custom_styles.txt | 1 + .../composed-page/comp.head_scripts.txt | 1 + .../resources/composed-page/fragments.txt | 17 + .../composed-page/section.endpage_scripts.txt | 0 .../resources/composed-page/section.meta.txt | 1 + .../composed-page/section.page_scripts.txt | 0 .../composed-page/section.social.txt | 1 + .../thymeleaf/controlled/composed-page.html | 1 + .../controlled/conditional-heavy.html | 1 + .../thymeleaf/controlled/encoded-loop.html | 1 + .../controlled/fortunes-encoded.html | 1 + .../thymeleaf/controlled/fragment-heavy.html | 1 + .../thymeleaf/controlled/large-loop.html | 1 + .../thymeleaf/controlled/layout.html | 1 + .../thymeleaf/controlled/mixed-page.html | 29 + .../resources/thymeleaf/controlled/tile.html | 1 + .../controlled/trivial-substitution.html | 1 + .../thymeleaf/idiomatic/composed-page.html | 6 + .../idiomatic/conditional-heavy.html | 18 + .../thymeleaf/idiomatic/encoded-loop.html | 12 + .../thymeleaf/idiomatic/fortunes-encoded.html | 24 + .../thymeleaf/idiomatic/fragment-heavy.html | 11 + .../thymeleaf/idiomatic/large-loop.html | 9 + .../resources/thymeleaf/idiomatic/layout.html | 19 + .../thymeleaf/idiomatic/mixed-page.html | 42 + .../resources/thymeleaf/idiomatic/tile.html | 11 + .../idiomatic/trivial-substitution.html | 19 + benchmarks/linux-crosscheck/README.md | 113 ++ .../__pycache__/validate.cpython-312.pyc | Bin 0 -> 18883 bytes .../__pycache__/validate.cpython-313.pyc | Bin 0 -> 19175 bytes .../linux-crosscheck/capture-environment.sh | 221 ++++ benchmarks/linux-crosscheck/common.sh | 111 ++ .../grub-isolation.cfg.example | 50 + benchmarks/linux-crosscheck/run-all.sh | 51 + benchmarks/linux-crosscheck/run-dotnet.sh | 35 + benchmarks/linux-crosscheck/run-go.sh | 69 + benchmarks/linux-crosscheck/run-js.sh | 94 ++ benchmarks/linux-crosscheck/run-jvm.sh | 36 + benchmarks/linux-crosscheck/run-python.sh | 72 + benchmarks/linux-crosscheck/run-rust.sh | 39 + .../linux-crosscheck/setup-toolchains.sh | 213 +++ benchmarks/linux-crosscheck/test_validate.py | 270 ++++ benchmarks/linux-crosscheck/tune.sh | 101 ++ benchmarks/linux-crosscheck/untune.sh | 52 + benchmarks/linux-crosscheck/validate.py | 349 +++++ .../windows-source.schema.json | 117 ++ benchmarks/python/README.md | 62 + benchmarks/python/bench_cold_compile.py | 72 + benchmarks/python/bench_jinja2_controlled.py | 37 + benchmarks/python/bench_jinja2_idiomatic.py | 37 + benchmarks/python/bench_mako_controlled.py | 37 + benchmarks/python/bench_mako_idiomatic.py | 37 + benchmarks/python/mem_tracemalloc.py | 177 +++ benchmarks/python/requirements.txt | 5 + benchmarks/python/runner/__init__.py | 5 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 379 bytes .../runner/__pycache__/data.cpython-313.pyc | Bin 0 -> 62422 bytes .../__pycache__/engines.cpython-313.pyc | Bin 0 -> 5556 bytes .../__pycache__/gate_all.cpython-313.pyc | Bin 0 -> 5601 bytes .../runner/__pycache__/gates.cpython-313.pyc | Bin 0 -> 12143 bytes .../__pycache__/selftest.cpython-313.pyc | Bin 0 -> 14829 bytes .../runner/__pycache__/verify.cpython-313.pyc | Bin 0 -> 5979 bytes benchmarks/python/runner/data.py | 223 ++++ benchmarks/python/runner/engines.py | 116 ++ benchmarks/python/runner/gate_all.py | 105 ++ benchmarks/python/runner/gates.py | 258 ++++ benchmarks/python/runner/selftest.py | 307 +++++ benchmarks/python/runner/verify.py | 123 ++ .../jinja2/controlled/composed-page.jinja | 1 + .../jinja2/controlled/conditional-heavy.jinja | 1 + .../jinja2/controlled/encoded-loop.jinja | 1 + .../jinja2/controlled/fortunes-encoded.jinja | 1 + .../jinja2/controlled/fragment-heavy.jinja | 1 + .../jinja2/controlled/large-loop.jinja | 1 + .../templates/jinja2/controlled/layout.jinja | 1 + .../jinja2/controlled/mixed-page.jinja | 29 + .../templates/jinja2/controlled/tile.jinja | 1 + .../controlled/trivial-substitution.jinja | 1 + .../templates/jinja2/idiomatic/base.jinja | 24 + .../jinja2/idiomatic/composed-page.jinja | 4 + .../jinja2/idiomatic/conditional-heavy.jinja | 26 + .../jinja2/idiomatic/encoded-loop.html | 9 + .../jinja2/idiomatic/fortunes-encoded.html | 18 + .../jinja2/idiomatic/fragment-heavy.jinja | 10 + .../jinja2/idiomatic/large-loop.jinja | 6 + .../templates/jinja2/idiomatic/layout.jinja | 19 + .../jinja2/idiomatic/mixed-page.jinja | 31 + .../templates/jinja2/idiomatic/tile.jinja | 10 + .../idiomatic/trivial-substitution.jinja | 14 + .../mako/controlled/composed-page.mako | 1 + .../mako/controlled/conditional-heavy.mako | 22 + .../mako/controlled/encoded-loop.mako | 5 + .../mako/controlled/fortunes-encoded.mako | 5 + .../mako/controlled/fragment-heavy.mako | 5 + .../templates/mako/controlled/large-loop.mako | 3 + .../templates/mako/controlled/layout.mako | 5 + .../templates/mako/controlled/mixed-page.mako | 39 + .../templates/mako/controlled/tile.mako | 1 + .../mako/controlled/trivial-substitution.mako | 1 + .../python/templates/mako/idiomatic/base.mako | 27 + .../mako/idiomatic/composed-page.mako | 6 + .../mako/idiomatic/conditional-heavy.mako | 27 + .../mako/idiomatic/encoded-loop.mako | 12 + .../mako/idiomatic/fortunes-encoded.mako | 21 + .../mako/idiomatic/fragment-heavy.mako | 12 + .../templates/mako/idiomatic/large-loop.mako | 8 + .../templates/mako/idiomatic/layout.mako | 21 + .../templates/mako/idiomatic/mixed-page.mako | 32 + .../python/templates/mako/idiomatic/tile.mako | 12 + .../mako/idiomatic/trivial-substitution.mako | 16 + benchmarks/rust/.gitignore | 2 + benchmarks/rust/Cargo.lock | 763 +++++++++++ benchmarks/rust/Cargo.toml | 50 + benchmarks/rust/askama.toml | 2 + benchmarks/rust/benches/cold.rs | 42 + benchmarks/rust/benches/controlled.rs | 43 + benchmarks/rust/benches/idiomatic.rs | 38 + .../rust/data/composed-page/area-1.html | 1 + .../rust/data/composed-page/area-2.html | 138 ++ .../rust/data/composed-page/area-3.html | 116 ++ .../rust/data/composed-page/area-4.html | 409 ++++++ .../rust/data/composed-page/area-5.html | 413 ++++++ .../rust/data/composed-page/area-6.html | 0 .../rust/data/composed-page/area-7.html | 45 + .../composed-page/comp-assets-scripts.html | 1 + .../composed-page/comp-assets-styles.html | 1 + .../composed-page/comp-body-end-scripts.html | 1 + .../data/composed-page/comp-body-scripts.html | 1 + .../composed-page/comp-custom-styles.html | 1 + .../data/composed-page/comp-head-scripts.html | 1 + .../rust/data/composed-page/section-meta.html | 1 + .../data/composed-page/section-social.html | 1 + benchmarks/rust/heddle-reference.toml | 27 + benchmarks/rust/rust-toolchain.toml | 2 + benchmarks/rust/src/corpus.rs | 150 +++ .../rust/src/engines/askama_controlled.rs | 262 ++++ .../rust/src/engines/askama_idiomatic.rs | 273 ++++ benchmarks/rust/src/engines/mod.rs | 6 + .../rust/src/engines/tera_controlled.rs | 187 +++ benchmarks/rust/src/engines/tera_idiomatic.rs | 166 +++ benchmarks/rust/src/gates.rs | 463 +++++++ benchmarks/rust/src/lib.rs | 12 + benchmarks/rust/src/models.rs | 433 ++++++ benchmarks/rust/src/normalize.rs | 269 ++++ benchmarks/rust/src/verifier.rs | 379 ++++++ .../askama/composed-page-layout.html | 1 + .../controlled/askama/composed-page.html | 1 + .../controlled/askama/conditional-heavy.html | 1 + .../controlled/askama/encoded-loop.html | 1 + .../controlled/askama/fortunes-encoded.html | 1 + .../askama/fragment-heavy-tile.html | 1 + .../controlled/askama/fragment-heavy.html | 1 + .../controlled/askama/large-loop.html | 1 + .../controlled/askama/mixed-page.html | 29 + .../askama/trivial-substitution.html | 1 + .../controlled/tera/composed-page-layout.html | 1 + .../controlled/tera/composed-page.html | 1 + .../controlled/tera/conditional-heavy.html | 1 + .../controlled/tera/encoded-loop.html | 1 + .../controlled/tera/fortunes-encoded.html | 1 + .../controlled/tera/fragment-heavy-tile.html | 1 + .../controlled/tera/fragment-heavy.html | 1 + .../templates/controlled/tera/large-loop.html | 1 + .../templates/controlled/tera/mixed-page.html | 29 + .../controlled/tera/trivial-substitution.html | 1 + .../idiomatic/askama/composed-page-base.html | 16 + .../idiomatic/askama/composed-page.html | 4 + .../idiomatic/askama/conditional-heavy.html | 25 + .../idiomatic/askama/encoded-loop.html | 11 + .../idiomatic/askama/fortunes-encoded.html | 17 + .../idiomatic/askama/fragment-heavy-tile.html | 8 + .../idiomatic/askama/fragment-heavy.html | 9 + .../idiomatic/askama/large-loop.html | 8 + .../idiomatic/askama/mixed-page-base.html | 24 + .../idiomatic/askama/mixed-page.html | 29 + .../askama/trivial-substitution.html | 16 + .../idiomatic/tera/composed-page-base.html | 16 + .../idiomatic/tera/composed-page.html | 4 + .../idiomatic/tera/conditional-heavy.html | 25 + .../idiomatic/tera/encoded-loop.html | 11 + .../idiomatic/tera/fortunes-encoded.html | 17 + .../idiomatic/tera/fragment-heavy-tile.html | 8 + .../idiomatic/tera/fragment-heavy.html | 9 + .../templates/idiomatic/tera/large-loop.html | 8 + .../idiomatic/tera/mixed-page-base.html | 24 + .../templates/idiomatic/tera/mixed-page.html | 29 + .../idiomatic/tera/trivial-substitution.html | 16 + docs/demo-smoke/demo.spec.ts | 24 + docs/plan/README.md | 75 ++ docs/plan/open-questions.md | 394 ++++++ docs/plan/phase-1-cross-stack-foundation.md | 325 +++++ docs/plan/phase-2-rust.md | 256 ++++ docs/plan/phase-3-jvm.md | 228 ++++ docs/plan/phase-4-js.md | 234 ++++ docs/plan/phase-5-python.md | 247 ++++ docs/plan/phase-6-go.md | 262 ++++ docs/plan/phase-7-consolidated-report.md | 332 +++++ docs/plan/phase-8-linux-crosscheck.md | 294 +++++ docs/public/demo.html | 47 +- docs/spec/README.md | 10 +- docs/spec/cross-stack-benchmarks/README.md | 86 ++ .../phase-1-cross-stack-foundation/README.md | 652 +++++++++ .../golden-corpus.md | 240 ++++ .../metrics-protocol.md | 151 +++ .../parity-contract-v2.md | 281 ++++ .../workloads.md | 449 +++++++ .../phase-2-rust/README.md | 741 +++++++++++ .../phase-2-rust/workload-ports.md | 485 +++++++ .../phase-3-jvm/README.md | 815 ++++++++++++ .../phase-3-jvm/construct-mapping.md | 301 +++++ .../phase-3-jvm/harness-and-jmh.md | 256 ++++ .../thymeleaf-controlled-feasibility.md | 180 +++ .../phase-4-js/README.md | 700 ++++++++++ .../phase-4-js/harness-and-run.md | 303 +++++ .../phase-4-js/templates-and-models.md | 454 +++++++ .../phase-5-python/README.md | 689 ++++++++++ .../phase-5-python/harness.md | 294 +++++ .../phase-5-python/templates.md | 332 +++++ .../phase-6-go/README.md | 545 ++++++++ .../phase-6-go/harness-and-measurement.md | 231 ++++ .../phase-6-go/port-mapping.md | 224 ++++ .../phase-6-go/templ-feasibility.md | 170 +++ .../phase-7-consolidated-report/README.md | 746 +++++++++++ .../report-assembly.md | 517 ++++++++ .../phase-8-linux-crosscheck/README.md | 574 ++++++++ .../environment-and-toolchains.md | 229 ++++ .../harness-settings-and-validation.md | 396 ++++++ docs/spec/records.md | 4 +- samples/component-props-slots/Program.cs | 36 +- .../golden/components-page.html | 24 +- samples/dynamic-models/Program.cs | 15 +- samples/ssr-aspnetcore/golden/index.html | 7 +- samples/ssr-aspnetcore/templates/index.heddle | 6 +- samples/streaming-ssr/Program.cs | 10 +- samples/streaming-ssr/golden/streamed.html | 12 +- samples/streaming-ssr/golden/textwriter.html | 12 +- src/Heddle.Demo.Models/DemoCatalog.cs | 51 +- .../Heddle.Generator.IntegrationTests.csproj | 7 +- .../Heddle.Generator.Tests.csproj | 9 +- .../js/src/mode/heddle_worker.js | 10 +- src/Heddle.Language/js/test/worker.test.js | 29 + src/Heddle.Language/package-lock.json | 4 +- src/Heddle.Language/package.json | 2 +- .../Heddle.LanguageServices.Tests.csproj | 2 +- .../ConditionalRenderBenchmarks.cs | 61 + .../EncodedLoopRenderBenchmarks.cs | 62 + .../FortunesRenderBenchmarks.cs | 61 + .../FragmentRenderBenchmarks.cs | 60 + src/Heddle.Performance/GoldenCorpus/README.md | 76 ++ .../GoldenCorpus/composed-page.golden.html | 1 + .../GoldenCorpus/composed-page.verify.json | 22 + .../conditional-heavy.golden.html | 1 + .../conditional-heavy.verify.json | 22 + .../GoldenCorpus/encoded-loop.golden.html | 1 + .../GoldenCorpus/encoded-loop.verify.json | 17 + .../GoldenCorpus/fortunes-encoded.golden.html | 1 + .../GoldenCorpus/fortunes-encoded.verify.json | 29 + .../GoldenCorpus/fragment-heavy.golden.html | 1 + .../GoldenCorpus/fragment-heavy.verify.json | 19 + .../GoldenCorpus/large-loop.golden.html | 1 + .../GoldenCorpus/large-loop.verify.json | 16 + .../GoldenCorpus/manifest.json | 78 ++ .../GoldenCorpus/mixed-page.golden.html | 1 + .../GoldenCorpus/mixed-page.verify.json | 24 + .../trivial-substitution.golden.html | 1 + .../trivial-substitution.verify.json | 19 + .../Heddle.Performance.csproj | 30 +- .../LoopRenderBenchmarks.cs | 1 + .../MixedRenderBenchmarks.cs | 60 + src/Heddle.Performance/Program.cs | 44 +- .../PropsRenderBenchmarks.cs | 10 +- .../Runners/ConditionalContent.cs | 99 ++ .../Runners/ConditionalDotLiquidTest.cs | 32 + .../Runners/ConditionalFluidTest.cs | 42 + .../Runners/ConditionalHandlebarsTest.cs | 44 + .../Runners/ConditionalHeddleTest.cs | 46 + .../Runners/ConditionalLiquidTemplates.cs | 22 + .../Runners/ConditionalScribanTest.cs | 78 ++ .../Runners/EncodedLoopContent.cs | 84 ++ .../Runners/EncodedLoopDotLiquidTest.cs | 31 + .../Runners/EncodedLoopFluidTest.cs | 41 + .../Runners/EncodedLoopHandlebarsTest.cs | 41 + .../Runners/EncodedLoopHeddleTest.cs | 46 + .../Runners/EncodedLoopLiquidTemplates.cs | 17 + .../Runners/EncodedLoopScribanTest.cs | 69 + .../Runners/FiveEntityTextEncoder.cs | 64 + .../Runners/FortunesContent.cs | 82 ++ .../Runners/FortunesDotLiquidTest.cs | 31 + .../Runners/FortunesFluidTest.cs | 41 + .../Runners/FortunesHandlebarsTest.cs | 41 + .../Runners/FortunesHeddleTest.cs | 47 + .../Runners/FortunesLiquidTemplates.cs | 16 + .../Runners/FortunesScribanTest.cs | 66 + .../Runners/FragmentContent.cs | 85 ++ .../Runners/FragmentDotLiquidTest.cs | 47 + .../Runners/FragmentFluidTest.cs | 71 + .../Runners/FragmentHandlebarsTest.cs | 40 + .../Runners/FragmentHeddleTest.cs | 46 + .../Runners/FragmentLiquidTemplates.cs | 27 + .../Runners/FragmentScribanTest.cs | 83 ++ .../Runners/GoldenCorpus.cs | 377 ++++++ .../Runners/IdiomaticChecks.cs | 489 +++++++ .../Runners/MixedContent.cs | 141 ++ .../Runners/MixedDotLiquidTest.cs | 31 + .../Runners/MixedFluidTest.cs | 41 + .../Runners/MixedHandlebarsTest.cs | 47 + .../Runners/MixedHeddleTest.cs | 46 + .../Runners/MixedLiquidTemplates.cs | 28 + .../Runners/MixedScribanTest.cs | 93 ++ src/Heddle.Performance/Runners/ParityCheck.cs | 143 +- src/Heddle.Performance/Runners/README.md | 11 + .../SubstitutionRenderBenchmarks.cs | 1 + .../TestTemplates/conditional-heavy.heddle | 1 + .../TestTemplates/encoded-loop.heddle | 1 + .../TestTemplates/fortunes-encoded.heddle | 1 + .../TestTemplates/fragment-heavy.heddle | 5 + .../TestTemplates/mixed-page.heddle | 29 + .../TextRenderBenchmarks.cs | 1 + .../FileWatcherByteIdentityTests.cs | 7 +- .../FileWatcherFailedRecompileTests.cs | 11 +- .../FileWatcherFilterAndArmingTests.cs | 18 +- src/Heddle.Tests/FileWatcherReloadTests.cs | 78 +- .../FileWatcherSwapConcurrencyTests.cs | 15 +- .../GenericTypeResolutionTests.cs | 214 +++ src/Heddle.Tests/Heddle.Tests.csproj | 16 +- src/Heddle.Tests/NestedTypeResolutionTests.cs | 174 +++ .../Heddle.Tool.Tests.csproj | 5 +- src/Heddle/Heddle.csproj | 2 +- src/Heddle/HeddleTemplate.cs | 29 +- src/Heddle/Helpers/ReflectionHelper.cs | 147 ++- 537 files changed, 43542 insertions(+), 228 deletions(-) create mode 100644 benchmarks/go/.gitattributes create mode 100644 benchmarks/go/README.md create mode 100644 benchmarks/go/go.mod create mode 100644 benchmarks/go/go.sum create mode 100644 benchmarks/go/internal/corpus/gate.go create mode 100644 benchmarks/go/internal/corpus/gate_test.go create mode 100644 benchmarks/go/internal/corpus/normalize.go create mode 100644 benchmarks/go/internal/corpus/normalize_test.go create mode 100644 benchmarks/go/internal/corpus/verify.go create mode 100644 benchmarks/go/internal/corpus/verify_test.go create mode 100644 benchmarks/go/internal/model/composed.go create mode 100644 benchmarks/go/internal/model/data/composed-page/area-1.html create mode 100644 benchmarks/go/internal/model/data/composed-page/area-2.html create mode 100644 benchmarks/go/internal/model/data/composed-page/area-3.html create mode 100644 benchmarks/go/internal/model/data/composed-page/area-4.html create mode 100644 benchmarks/go/internal/model/data/composed-page/area-5.html create mode 100644 benchmarks/go/internal/model/data/composed-page/area-6.html create mode 100644 benchmarks/go/internal/model/data/composed-page/area-7.html create mode 100644 benchmarks/go/internal/model/data/composed-page/comp-assets-scripts.html create mode 100644 benchmarks/go/internal/model/data/composed-page/comp-assets-styles.html create mode 100644 benchmarks/go/internal/model/data/composed-page/comp-body-end-scripts.html create mode 100644 benchmarks/go/internal/model/data/composed-page/comp-body-scripts.html create mode 100644 benchmarks/go/internal/model/data/composed-page/comp-custom-styles.html create mode 100644 benchmarks/go/internal/model/data/composed-page/comp-head-scripts.html create mode 100644 benchmarks/go/internal/model/data/composed-page/section-meta.html create mode 100644 benchmarks/go/internal/model/data/composed-page/section-social.html create mode 100644 benchmarks/go/internal/model/model_test.go create mode 100644 benchmarks/go/internal/model/models.go create mode 100644 benchmarks/go/internal/spike/spike.templ create mode 100644 benchmarks/go/internal/spike/spike_templ.go create mode 100644 benchmarks/go/internal/spike/spike_test.go create mode 100644 benchmarks/go/internal/stdlibtpl/coldparse.go create mode 100644 benchmarks/go/internal/stdlibtpl/idiomatic.go create mode 100644 benchmarks/go/internal/stdlibtpl/render.go create mode 100644 benchmarks/go/internal/stdlibtpl/templates.go create mode 100644 benchmarks/go/internal/templeng/composed_page.templ create mode 100644 benchmarks/go/internal/templeng/composed_page_templ.go create mode 100644 benchmarks/go/internal/templeng/conditional_heavy.templ create mode 100644 benchmarks/go/internal/templeng/conditional_heavy_templ.go create mode 100644 benchmarks/go/internal/templeng/encoded_loop.templ create mode 100644 benchmarks/go/internal/templeng/encoded_loop_templ.go create mode 100644 benchmarks/go/internal/templeng/fortunes_encoded.templ create mode 100644 benchmarks/go/internal/templeng/fortunes_encoded_templ.go create mode 100644 benchmarks/go/internal/templeng/fragment_heavy.templ create mode 100644 benchmarks/go/internal/templeng/fragment_heavy_templ.go create mode 100644 benchmarks/go/internal/templeng/idiomatic/composed_page.templ create mode 100644 benchmarks/go/internal/templeng/idiomatic/composed_page_templ.go create mode 100644 benchmarks/go/internal/templeng/idiomatic/conditional_heavy.templ create mode 100644 benchmarks/go/internal/templeng/idiomatic/conditional_heavy_templ.go create mode 100644 benchmarks/go/internal/templeng/idiomatic/encoded_loop.templ create mode 100644 benchmarks/go/internal/templeng/idiomatic/encoded_loop_templ.go create mode 100644 benchmarks/go/internal/templeng/idiomatic/fortunes_encoded.templ create mode 100644 benchmarks/go/internal/templeng/idiomatic/fortunes_encoded_templ.go create mode 100644 benchmarks/go/internal/templeng/idiomatic/fragment_heavy.templ create mode 100644 benchmarks/go/internal/templeng/idiomatic/fragment_heavy_templ.go create mode 100644 benchmarks/go/internal/templeng/idiomatic/large_loop.templ create mode 100644 benchmarks/go/internal/templeng/idiomatic/large_loop_templ.go create mode 100644 benchmarks/go/internal/templeng/idiomatic/mixed_page.templ create mode 100644 benchmarks/go/internal/templeng/idiomatic/mixed_page_templ.go create mode 100644 benchmarks/go/internal/templeng/idiomatic/render.go create mode 100644 benchmarks/go/internal/templeng/idiomatic/trivial_substitution.templ create mode 100644 benchmarks/go/internal/templeng/idiomatic/trivial_substitution_templ.go create mode 100644 benchmarks/go/internal/templeng/large_loop.templ create mode 100644 benchmarks/go/internal/templeng/large_loop_templ.go create mode 100644 benchmarks/go/internal/templeng/mixed_page.templ create mode 100644 benchmarks/go/internal/templeng/mixed_page_templ.go create mode 100644 benchmarks/go/internal/templeng/render.go create mode 100644 benchmarks/go/internal/templeng/trivial_substitution.templ create mode 100644 benchmarks/go/internal/templeng/trivial_substitution_templ.go create mode 100644 benchmarks/go/run-benchmarks.ps1 create mode 100644 benchmarks/go/suites/bench_test.go create mode 100644 benchmarks/go/suites/coldparse_test.go create mode 100644 benchmarks/go/suites/gate_test.go create mode 100644 benchmarks/js/.gitattributes create mode 100644 benchmarks/js/.gitignore create mode 100644 benchmarks/js/.npmrc create mode 100644 benchmarks/js/README.md create mode 100644 benchmarks/js/bench/_shared.mjs create mode 100644 benchmarks/js/bench/cold-compile.mjs create mode 100644 benchmarks/js/bench/controlled.mjs create mode 100644 benchmarks/js/bench/idiomatic.mjs create mode 100644 benchmarks/js/package-lock.json create mode 100644 benchmarks/js/package.json create mode 100644 benchmarks/js/run.ps1 create mode 100644 benchmarks/js/src/engines/eta.mjs create mode 100644 benchmarks/js/src/engines/handlebars.mjs create mode 100644 benchmarks/js/src/engines/index.mjs create mode 100644 benchmarks/js/src/gate/controlled.mjs create mode 100644 benchmarks/js/src/gate/corpus.mjs create mode 100644 benchmarks/js/src/gate/normalize.mjs create mode 100644 benchmarks/js/src/gate/run-all.mjs create mode 100644 benchmarks/js/src/gate/verifier.mjs create mode 100644 benchmarks/js/src/models/_deep-freeze.mjs create mode 100644 benchmarks/js/src/models/composed-page.mjs create mode 100644 benchmarks/js/src/models/conditional-heavy.mjs create mode 100644 benchmarks/js/src/models/encoded-loop.mjs create mode 100644 benchmarks/js/src/models/fortunes-encoded.mjs create mode 100644 benchmarks/js/src/models/fragment-heavy.mjs create mode 100644 benchmarks/js/src/models/large-loop.mjs create mode 100644 benchmarks/js/src/models/mixed-page.mjs create mode 100644 benchmarks/js/src/models/trivial-substitution.mjs create mode 100644 benchmarks/js/src/templates/eta/controlled/composed-page.eta create mode 100644 benchmarks/js/src/templates/eta/controlled/conditional-heavy.eta create mode 100644 benchmarks/js/src/templates/eta/controlled/encoded-loop.eta create mode 100644 benchmarks/js/src/templates/eta/controlled/fortunes-encoded.eta create mode 100644 benchmarks/js/src/templates/eta/controlled/fragment-heavy.eta create mode 100644 benchmarks/js/src/templates/eta/controlled/large-loop.eta create mode 100644 benchmarks/js/src/templates/eta/controlled/layout.partial.eta create mode 100644 benchmarks/js/src/templates/eta/controlled/mixed-page.eta create mode 100644 benchmarks/js/src/templates/eta/controlled/tile.partial.eta create mode 100644 benchmarks/js/src/templates/eta/controlled/trivial-substitution.eta create mode 100644 benchmarks/js/src/templates/eta/idiomatic/composed-page.eta create mode 100644 benchmarks/js/src/templates/eta/idiomatic/conditional-heavy.eta create mode 100644 benchmarks/js/src/templates/eta/idiomatic/encoded-loop.eta create mode 100644 benchmarks/js/src/templates/eta/idiomatic/fortunes-encoded.eta create mode 100644 benchmarks/js/src/templates/eta/idiomatic/fragment-heavy.eta create mode 100644 benchmarks/js/src/templates/eta/idiomatic/large-loop.eta create mode 100644 benchmarks/js/src/templates/eta/idiomatic/mixed-page.eta create mode 100644 benchmarks/js/src/templates/eta/idiomatic/page.layout.eta create mode 100644 benchmarks/js/src/templates/eta/idiomatic/shell.layout.eta create mode 100644 benchmarks/js/src/templates/eta/idiomatic/tile.partial.eta create mode 100644 benchmarks/js/src/templates/eta/idiomatic/trivial-substitution.eta create mode 100644 benchmarks/js/src/templates/handlebars/controlled/composed-page.hbs create mode 100644 benchmarks/js/src/templates/handlebars/controlled/conditional-heavy.hbs create mode 100644 benchmarks/js/src/templates/handlebars/controlled/encoded-loop.hbs create mode 100644 benchmarks/js/src/templates/handlebars/controlled/fortunes-encoded.hbs create mode 100644 benchmarks/js/src/templates/handlebars/controlled/fragment-heavy.hbs create mode 100644 benchmarks/js/src/templates/handlebars/controlled/large-loop.hbs create mode 100644 benchmarks/js/src/templates/handlebars/controlled/layout.partial.hbs create mode 100644 benchmarks/js/src/templates/handlebars/controlled/mixed-page.hbs create mode 100644 benchmarks/js/src/templates/handlebars/controlled/tile.partial.hbs create mode 100644 benchmarks/js/src/templates/handlebars/controlled/trivial-substitution.hbs create mode 100644 benchmarks/js/src/templates/handlebars/idiomatic/composed-page.hbs create mode 100644 benchmarks/js/src/templates/handlebars/idiomatic/conditional-heavy.hbs create mode 100644 benchmarks/js/src/templates/handlebars/idiomatic/encoded-loop.hbs create mode 100644 benchmarks/js/src/templates/handlebars/idiomatic/fortunes-encoded.hbs create mode 100644 benchmarks/js/src/templates/handlebars/idiomatic/fragment-heavy.hbs create mode 100644 benchmarks/js/src/templates/handlebars/idiomatic/large-loop.hbs create mode 100644 benchmarks/js/src/templates/handlebars/idiomatic/layout.partial.hbs create mode 100644 benchmarks/js/src/templates/handlebars/idiomatic/mixed-page.hbs create mode 100644 benchmarks/js/src/templates/handlebars/idiomatic/tile.partial.hbs create mode 100644 benchmarks/js/src/templates/handlebars/idiomatic/trivial-substitution.hbs create mode 100644 benchmarks/js/test/gate-selftest.mjs create mode 100644 benchmarks/jvm/.gitignore create mode 100644 benchmarks/jvm/.mvn/wrapper/maven-wrapper.properties create mode 100644 benchmarks/jvm/README.md create mode 100644 benchmarks/jvm/mvnw create mode 100644 benchmarks/jvm/mvnw.cmd create mode 100644 benchmarks/jvm/pom.xml create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/bench/ComposedPageBench.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/bench/ConditionalHeavyBench.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/bench/EncodedLoopBench.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/bench/FortunesEncodedBench.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/bench/FragmentHeavyBench.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/bench/LargeLoopBench.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/bench/MixedPageBench.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/bench/TrivialSubstitutionBench.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/engines/JteEngines.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/engines/ThymeleafEngines.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/gate/Corpus.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/gate/GateCli.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/gate/Gates.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/gate/Normalizer.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/gate/Verifier.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/jte/FiveEntityHtmlOutput.java create mode 100644 benchmarks/jvm/src/main/java/heddle/benchmarks/jvm/model/Models.java create mode 100644 benchmarks/jvm/src/main/jte-html/.gitkeep create mode 100644 benchmarks/jvm/src/main/jte-html/controlled/encoded-loop.jte create mode 100644 benchmarks/jvm/src/main/jte-html/controlled/fortunes-encoded.jte create mode 100644 benchmarks/jvm/src/main/jte-html/idiomatic/encoded-loop.jte create mode 100644 benchmarks/jvm/src/main/jte-html/idiomatic/fortunes-encoded.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/.gitkeep create mode 100644 benchmarks/jvm/src/main/jte-plain/controlled/composed-page.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/controlled/conditional-heavy.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/controlled/fragment-heavy.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/controlled/large-loop.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/controlled/layout.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/controlled/mixed-page.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/controlled/tile.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/controlled/trivial-substitution.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/idiomatic/composed-page.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/idiomatic/conditional-heavy.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/idiomatic/fragment-heavy.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/idiomatic/large-loop.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/idiomatic/layout.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/idiomatic/mixed-page.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/idiomatic/tile.jte create mode 100644 benchmarks/jvm/src/main/jte-plain/idiomatic/trivial-substitution.jte create mode 100644 benchmarks/jvm/src/main/resources/composed-page/area-order.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/area.1.alert-top-section-above-nav.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/area.2.secondary-wholesale-menu.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/area.3.secondary-retail-menu.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/area.4.wholesale-top-mega-menu.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/area.5.retail-top-mega-menu.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/area.6.alert-top-section-below-nav.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/area.7.footer-links.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/comp.assets_scripts.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/comp.assets_styles.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/comp.body_end_scripts.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/comp.body_scripts.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/comp.custom_styles.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/comp.head_scripts.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/fragments.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/section.endpage_scripts.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/section.meta.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/section.page_scripts.txt create mode 100644 benchmarks/jvm/src/main/resources/composed-page/section.social.txt create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/controlled/composed-page.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/controlled/conditional-heavy.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/controlled/encoded-loop.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/controlled/fortunes-encoded.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/controlled/fragment-heavy.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/controlled/large-loop.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/controlled/layout.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/controlled/mixed-page.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/controlled/tile.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/controlled/trivial-substitution.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/idiomatic/composed-page.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/idiomatic/conditional-heavy.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/idiomatic/encoded-loop.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/idiomatic/fortunes-encoded.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/idiomatic/fragment-heavy.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/idiomatic/large-loop.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/idiomatic/layout.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/idiomatic/mixed-page.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/idiomatic/tile.html create mode 100644 benchmarks/jvm/src/main/resources/thymeleaf/idiomatic/trivial-substitution.html create mode 100644 benchmarks/linux-crosscheck/README.md create mode 100644 benchmarks/linux-crosscheck/__pycache__/validate.cpython-312.pyc create mode 100644 benchmarks/linux-crosscheck/__pycache__/validate.cpython-313.pyc create mode 100644 benchmarks/linux-crosscheck/capture-environment.sh create mode 100644 benchmarks/linux-crosscheck/common.sh create mode 100644 benchmarks/linux-crosscheck/grub-isolation.cfg.example create mode 100644 benchmarks/linux-crosscheck/run-all.sh create mode 100644 benchmarks/linux-crosscheck/run-dotnet.sh create mode 100644 benchmarks/linux-crosscheck/run-go.sh create mode 100644 benchmarks/linux-crosscheck/run-js.sh create mode 100644 benchmarks/linux-crosscheck/run-jvm.sh create mode 100644 benchmarks/linux-crosscheck/run-python.sh create mode 100644 benchmarks/linux-crosscheck/run-rust.sh create mode 100644 benchmarks/linux-crosscheck/setup-toolchains.sh create mode 100644 benchmarks/linux-crosscheck/test_validate.py create mode 100644 benchmarks/linux-crosscheck/tune.sh create mode 100644 benchmarks/linux-crosscheck/untune.sh create mode 100644 benchmarks/linux-crosscheck/validate.py create mode 100644 benchmarks/linux-crosscheck/windows-source.schema.json create mode 100644 benchmarks/python/README.md create mode 100644 benchmarks/python/bench_cold_compile.py create mode 100644 benchmarks/python/bench_jinja2_controlled.py create mode 100644 benchmarks/python/bench_jinja2_idiomatic.py create mode 100644 benchmarks/python/bench_mako_controlled.py create mode 100644 benchmarks/python/bench_mako_idiomatic.py create mode 100644 benchmarks/python/mem_tracemalloc.py create mode 100644 benchmarks/python/requirements.txt create mode 100644 benchmarks/python/runner/__init__.py create mode 100644 benchmarks/python/runner/__pycache__/__init__.cpython-313.pyc create mode 100644 benchmarks/python/runner/__pycache__/data.cpython-313.pyc create mode 100644 benchmarks/python/runner/__pycache__/engines.cpython-313.pyc create mode 100644 benchmarks/python/runner/__pycache__/gate_all.cpython-313.pyc create mode 100644 benchmarks/python/runner/__pycache__/gates.cpython-313.pyc create mode 100644 benchmarks/python/runner/__pycache__/selftest.cpython-313.pyc create mode 100644 benchmarks/python/runner/__pycache__/verify.cpython-313.pyc create mode 100644 benchmarks/python/runner/data.py create mode 100644 benchmarks/python/runner/engines.py create mode 100644 benchmarks/python/runner/gate_all.py create mode 100644 benchmarks/python/runner/gates.py create mode 100644 benchmarks/python/runner/selftest.py create mode 100644 benchmarks/python/runner/verify.py create mode 100644 benchmarks/python/templates/jinja2/controlled/composed-page.jinja create mode 100644 benchmarks/python/templates/jinja2/controlled/conditional-heavy.jinja create mode 100644 benchmarks/python/templates/jinja2/controlled/encoded-loop.jinja create mode 100644 benchmarks/python/templates/jinja2/controlled/fortunes-encoded.jinja create mode 100644 benchmarks/python/templates/jinja2/controlled/fragment-heavy.jinja create mode 100644 benchmarks/python/templates/jinja2/controlled/large-loop.jinja create mode 100644 benchmarks/python/templates/jinja2/controlled/layout.jinja create mode 100644 benchmarks/python/templates/jinja2/controlled/mixed-page.jinja create mode 100644 benchmarks/python/templates/jinja2/controlled/tile.jinja create mode 100644 benchmarks/python/templates/jinja2/controlled/trivial-substitution.jinja create mode 100644 benchmarks/python/templates/jinja2/idiomatic/base.jinja create mode 100644 benchmarks/python/templates/jinja2/idiomatic/composed-page.jinja create mode 100644 benchmarks/python/templates/jinja2/idiomatic/conditional-heavy.jinja create mode 100644 benchmarks/python/templates/jinja2/idiomatic/encoded-loop.html create mode 100644 benchmarks/python/templates/jinja2/idiomatic/fortunes-encoded.html create mode 100644 benchmarks/python/templates/jinja2/idiomatic/fragment-heavy.jinja create mode 100644 benchmarks/python/templates/jinja2/idiomatic/large-loop.jinja create mode 100644 benchmarks/python/templates/jinja2/idiomatic/layout.jinja create mode 100644 benchmarks/python/templates/jinja2/idiomatic/mixed-page.jinja create mode 100644 benchmarks/python/templates/jinja2/idiomatic/tile.jinja create mode 100644 benchmarks/python/templates/jinja2/idiomatic/trivial-substitution.jinja create mode 100644 benchmarks/python/templates/mako/controlled/composed-page.mako create mode 100644 benchmarks/python/templates/mako/controlled/conditional-heavy.mako create mode 100644 benchmarks/python/templates/mako/controlled/encoded-loop.mako create mode 100644 benchmarks/python/templates/mako/controlled/fortunes-encoded.mako create mode 100644 benchmarks/python/templates/mako/controlled/fragment-heavy.mako create mode 100644 benchmarks/python/templates/mako/controlled/large-loop.mako create mode 100644 benchmarks/python/templates/mako/controlled/layout.mako create mode 100644 benchmarks/python/templates/mako/controlled/mixed-page.mako create mode 100644 benchmarks/python/templates/mako/controlled/tile.mako create mode 100644 benchmarks/python/templates/mako/controlled/trivial-substitution.mako create mode 100644 benchmarks/python/templates/mako/idiomatic/base.mako create mode 100644 benchmarks/python/templates/mako/idiomatic/composed-page.mako create mode 100644 benchmarks/python/templates/mako/idiomatic/conditional-heavy.mako create mode 100644 benchmarks/python/templates/mako/idiomatic/encoded-loop.mako create mode 100644 benchmarks/python/templates/mako/idiomatic/fortunes-encoded.mako create mode 100644 benchmarks/python/templates/mako/idiomatic/fragment-heavy.mako create mode 100644 benchmarks/python/templates/mako/idiomatic/large-loop.mako create mode 100644 benchmarks/python/templates/mako/idiomatic/layout.mako create mode 100644 benchmarks/python/templates/mako/idiomatic/mixed-page.mako create mode 100644 benchmarks/python/templates/mako/idiomatic/tile.mako create mode 100644 benchmarks/python/templates/mako/idiomatic/trivial-substitution.mako create mode 100644 benchmarks/rust/.gitignore create mode 100644 benchmarks/rust/Cargo.lock create mode 100644 benchmarks/rust/Cargo.toml create mode 100644 benchmarks/rust/askama.toml create mode 100644 benchmarks/rust/benches/cold.rs create mode 100644 benchmarks/rust/benches/controlled.rs create mode 100644 benchmarks/rust/benches/idiomatic.rs create mode 100644 benchmarks/rust/data/composed-page/area-1.html create mode 100644 benchmarks/rust/data/composed-page/area-2.html create mode 100644 benchmarks/rust/data/composed-page/area-3.html create mode 100644 benchmarks/rust/data/composed-page/area-4.html create mode 100644 benchmarks/rust/data/composed-page/area-5.html create mode 100644 benchmarks/rust/data/composed-page/area-6.html create mode 100644 benchmarks/rust/data/composed-page/area-7.html create mode 100644 benchmarks/rust/data/composed-page/comp-assets-scripts.html create mode 100644 benchmarks/rust/data/composed-page/comp-assets-styles.html create mode 100644 benchmarks/rust/data/composed-page/comp-body-end-scripts.html create mode 100644 benchmarks/rust/data/composed-page/comp-body-scripts.html create mode 100644 benchmarks/rust/data/composed-page/comp-custom-styles.html create mode 100644 benchmarks/rust/data/composed-page/comp-head-scripts.html create mode 100644 benchmarks/rust/data/composed-page/section-meta.html create mode 100644 benchmarks/rust/data/composed-page/section-social.html create mode 100644 benchmarks/rust/heddle-reference.toml create mode 100644 benchmarks/rust/rust-toolchain.toml create mode 100644 benchmarks/rust/src/corpus.rs create mode 100644 benchmarks/rust/src/engines/askama_controlled.rs create mode 100644 benchmarks/rust/src/engines/askama_idiomatic.rs create mode 100644 benchmarks/rust/src/engines/mod.rs create mode 100644 benchmarks/rust/src/engines/tera_controlled.rs create mode 100644 benchmarks/rust/src/engines/tera_idiomatic.rs create mode 100644 benchmarks/rust/src/gates.rs create mode 100644 benchmarks/rust/src/lib.rs create mode 100644 benchmarks/rust/src/models.rs create mode 100644 benchmarks/rust/src/normalize.rs create mode 100644 benchmarks/rust/src/verifier.rs create mode 100644 benchmarks/rust/templates/controlled/askama/composed-page-layout.html create mode 100644 benchmarks/rust/templates/controlled/askama/composed-page.html create mode 100644 benchmarks/rust/templates/controlled/askama/conditional-heavy.html create mode 100644 benchmarks/rust/templates/controlled/askama/encoded-loop.html create mode 100644 benchmarks/rust/templates/controlled/askama/fortunes-encoded.html create mode 100644 benchmarks/rust/templates/controlled/askama/fragment-heavy-tile.html create mode 100644 benchmarks/rust/templates/controlled/askama/fragment-heavy.html create mode 100644 benchmarks/rust/templates/controlled/askama/large-loop.html create mode 100644 benchmarks/rust/templates/controlled/askama/mixed-page.html create mode 100644 benchmarks/rust/templates/controlled/askama/trivial-substitution.html create mode 100644 benchmarks/rust/templates/controlled/tera/composed-page-layout.html create mode 100644 benchmarks/rust/templates/controlled/tera/composed-page.html create mode 100644 benchmarks/rust/templates/controlled/tera/conditional-heavy.html create mode 100644 benchmarks/rust/templates/controlled/tera/encoded-loop.html create mode 100644 benchmarks/rust/templates/controlled/tera/fortunes-encoded.html create mode 100644 benchmarks/rust/templates/controlled/tera/fragment-heavy-tile.html create mode 100644 benchmarks/rust/templates/controlled/tera/fragment-heavy.html create mode 100644 benchmarks/rust/templates/controlled/tera/large-loop.html create mode 100644 benchmarks/rust/templates/controlled/tera/mixed-page.html create mode 100644 benchmarks/rust/templates/controlled/tera/trivial-substitution.html create mode 100644 benchmarks/rust/templates/idiomatic/askama/composed-page-base.html create mode 100644 benchmarks/rust/templates/idiomatic/askama/composed-page.html create mode 100644 benchmarks/rust/templates/idiomatic/askama/conditional-heavy.html create mode 100644 benchmarks/rust/templates/idiomatic/askama/encoded-loop.html create mode 100644 benchmarks/rust/templates/idiomatic/askama/fortunes-encoded.html create mode 100644 benchmarks/rust/templates/idiomatic/askama/fragment-heavy-tile.html create mode 100644 benchmarks/rust/templates/idiomatic/askama/fragment-heavy.html create mode 100644 benchmarks/rust/templates/idiomatic/askama/large-loop.html create mode 100644 benchmarks/rust/templates/idiomatic/askama/mixed-page-base.html create mode 100644 benchmarks/rust/templates/idiomatic/askama/mixed-page.html create mode 100644 benchmarks/rust/templates/idiomatic/askama/trivial-substitution.html create mode 100644 benchmarks/rust/templates/idiomatic/tera/composed-page-base.html create mode 100644 benchmarks/rust/templates/idiomatic/tera/composed-page.html create mode 100644 benchmarks/rust/templates/idiomatic/tera/conditional-heavy.html create mode 100644 benchmarks/rust/templates/idiomatic/tera/encoded-loop.html create mode 100644 benchmarks/rust/templates/idiomatic/tera/fortunes-encoded.html create mode 100644 benchmarks/rust/templates/idiomatic/tera/fragment-heavy-tile.html create mode 100644 benchmarks/rust/templates/idiomatic/tera/fragment-heavy.html create mode 100644 benchmarks/rust/templates/idiomatic/tera/large-loop.html create mode 100644 benchmarks/rust/templates/idiomatic/tera/mixed-page-base.html create mode 100644 benchmarks/rust/templates/idiomatic/tera/mixed-page.html create mode 100644 benchmarks/rust/templates/idiomatic/tera/trivial-substitution.html create mode 100644 docs/plan/README.md create mode 100644 docs/plan/open-questions.md create mode 100644 docs/plan/phase-1-cross-stack-foundation.md create mode 100644 docs/plan/phase-2-rust.md create mode 100644 docs/plan/phase-3-jvm.md create mode 100644 docs/plan/phase-4-js.md create mode 100644 docs/plan/phase-5-python.md create mode 100644 docs/plan/phase-6-go.md create mode 100644 docs/plan/phase-7-consolidated-report.md create mode 100644 docs/plan/phase-8-linux-crosscheck.md create mode 100644 docs/spec/cross-stack-benchmarks/README.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-1-cross-stack-foundation/README.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-1-cross-stack-foundation/golden-corpus.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-1-cross-stack-foundation/metrics-protocol.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-1-cross-stack-foundation/parity-contract-v2.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-1-cross-stack-foundation/workloads.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-2-rust/README.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-2-rust/workload-ports.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-3-jvm/README.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-3-jvm/construct-mapping.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-3-jvm/harness-and-jmh.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-3-jvm/thymeleaf-controlled-feasibility.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-4-js/README.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-4-js/harness-and-run.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-4-js/templates-and-models.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-5-python/README.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-5-python/harness.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-5-python/templates.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-6-go/README.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-6-go/harness-and-measurement.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-6-go/port-mapping.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-6-go/templ-feasibility.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-7-consolidated-report/README.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-7-consolidated-report/report-assembly.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-8-linux-crosscheck/README.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-8-linux-crosscheck/environment-and-toolchains.md create mode 100644 docs/spec/cross-stack-benchmarks/phase-8-linux-crosscheck/harness-settings-and-validation.md create mode 100644 src/Heddle.Performance/ConditionalRenderBenchmarks.cs create mode 100644 src/Heddle.Performance/EncodedLoopRenderBenchmarks.cs create mode 100644 src/Heddle.Performance/FortunesRenderBenchmarks.cs create mode 100644 src/Heddle.Performance/FragmentRenderBenchmarks.cs create mode 100644 src/Heddle.Performance/GoldenCorpus/README.md create mode 100644 src/Heddle.Performance/GoldenCorpus/composed-page.golden.html create mode 100644 src/Heddle.Performance/GoldenCorpus/composed-page.verify.json create mode 100644 src/Heddle.Performance/GoldenCorpus/conditional-heavy.golden.html create mode 100644 src/Heddle.Performance/GoldenCorpus/conditional-heavy.verify.json create mode 100644 src/Heddle.Performance/GoldenCorpus/encoded-loop.golden.html create mode 100644 src/Heddle.Performance/GoldenCorpus/encoded-loop.verify.json create mode 100644 src/Heddle.Performance/GoldenCorpus/fortunes-encoded.golden.html create mode 100644 src/Heddle.Performance/GoldenCorpus/fortunes-encoded.verify.json create mode 100644 src/Heddle.Performance/GoldenCorpus/fragment-heavy.golden.html create mode 100644 src/Heddle.Performance/GoldenCorpus/fragment-heavy.verify.json create mode 100644 src/Heddle.Performance/GoldenCorpus/large-loop.golden.html create mode 100644 src/Heddle.Performance/GoldenCorpus/large-loop.verify.json create mode 100644 src/Heddle.Performance/GoldenCorpus/manifest.json create mode 100644 src/Heddle.Performance/GoldenCorpus/mixed-page.golden.html create mode 100644 src/Heddle.Performance/GoldenCorpus/mixed-page.verify.json create mode 100644 src/Heddle.Performance/GoldenCorpus/trivial-substitution.golden.html create mode 100644 src/Heddle.Performance/GoldenCorpus/trivial-substitution.verify.json create mode 100644 src/Heddle.Performance/MixedRenderBenchmarks.cs create mode 100644 src/Heddle.Performance/Runners/ConditionalContent.cs create mode 100644 src/Heddle.Performance/Runners/ConditionalDotLiquidTest.cs create mode 100644 src/Heddle.Performance/Runners/ConditionalFluidTest.cs create mode 100644 src/Heddle.Performance/Runners/ConditionalHandlebarsTest.cs create mode 100644 src/Heddle.Performance/Runners/ConditionalHeddleTest.cs create mode 100644 src/Heddle.Performance/Runners/ConditionalLiquidTemplates.cs create mode 100644 src/Heddle.Performance/Runners/ConditionalScribanTest.cs create mode 100644 src/Heddle.Performance/Runners/EncodedLoopContent.cs create mode 100644 src/Heddle.Performance/Runners/EncodedLoopDotLiquidTest.cs create mode 100644 src/Heddle.Performance/Runners/EncodedLoopFluidTest.cs create mode 100644 src/Heddle.Performance/Runners/EncodedLoopHandlebarsTest.cs create mode 100644 src/Heddle.Performance/Runners/EncodedLoopHeddleTest.cs create mode 100644 src/Heddle.Performance/Runners/EncodedLoopLiquidTemplates.cs create mode 100644 src/Heddle.Performance/Runners/EncodedLoopScribanTest.cs create mode 100644 src/Heddle.Performance/Runners/FiveEntityTextEncoder.cs create mode 100644 src/Heddle.Performance/Runners/FortunesContent.cs create mode 100644 src/Heddle.Performance/Runners/FortunesDotLiquidTest.cs create mode 100644 src/Heddle.Performance/Runners/FortunesFluidTest.cs create mode 100644 src/Heddle.Performance/Runners/FortunesHandlebarsTest.cs create mode 100644 src/Heddle.Performance/Runners/FortunesHeddleTest.cs create mode 100644 src/Heddle.Performance/Runners/FortunesLiquidTemplates.cs create mode 100644 src/Heddle.Performance/Runners/FortunesScribanTest.cs create mode 100644 src/Heddle.Performance/Runners/FragmentContent.cs create mode 100644 src/Heddle.Performance/Runners/FragmentDotLiquidTest.cs create mode 100644 src/Heddle.Performance/Runners/FragmentFluidTest.cs create mode 100644 src/Heddle.Performance/Runners/FragmentHandlebarsTest.cs create mode 100644 src/Heddle.Performance/Runners/FragmentHeddleTest.cs create mode 100644 src/Heddle.Performance/Runners/FragmentLiquidTemplates.cs create mode 100644 src/Heddle.Performance/Runners/FragmentScribanTest.cs create mode 100644 src/Heddle.Performance/Runners/GoldenCorpus.cs create mode 100644 src/Heddle.Performance/Runners/IdiomaticChecks.cs create mode 100644 src/Heddle.Performance/Runners/MixedContent.cs create mode 100644 src/Heddle.Performance/Runners/MixedDotLiquidTest.cs create mode 100644 src/Heddle.Performance/Runners/MixedFluidTest.cs create mode 100644 src/Heddle.Performance/Runners/MixedHandlebarsTest.cs create mode 100644 src/Heddle.Performance/Runners/MixedHeddleTest.cs create mode 100644 src/Heddle.Performance/Runners/MixedLiquidTemplates.cs create mode 100644 src/Heddle.Performance/Runners/MixedScribanTest.cs create mode 100644 src/Heddle.Performance/TestTemplates/conditional-heavy.heddle create mode 100644 src/Heddle.Performance/TestTemplates/encoded-loop.heddle create mode 100644 src/Heddle.Performance/TestTemplates/fortunes-encoded.heddle create mode 100644 src/Heddle.Performance/TestTemplates/fragment-heavy.heddle create mode 100644 src/Heddle.Performance/TestTemplates/mixed-page.heddle create mode 100644 src/Heddle.Tests/GenericTypeResolutionTests.cs create mode 100644 src/Heddle.Tests/NestedTypeResolutionTests.cs diff --git a/.gitattributes b/.gitattributes index 0368cf01..ccbb4f6c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -22,6 +22,25 @@ src/Heddle.Generator.IntegrationTests/Fixtures/** text eol=lf src/Heddle.LanguageServices.Tests/Corpus/** text eol=lf src/Heddle.Demo.Wasm/contract-fixtures/** text eol=lf +# Golden oracle corpus (docs/spec/cross-stack-benchmarks/phase-1-cross-stack-foundation/ +# golden-corpus.md): the *.golden.html files intentionally end WITHOUT a trailing newline and +# must round-trip byte-exact on every platform, so they are pinned -text (no smudging at all) +# rather than eol=lf. The JSON sidecars are ordinary LF text. +src/Heddle.Performance/GoldenCorpus/*.golden.html -text +src/Heddle.Performance/GoldenCorpus/*.verify.json text eol=lf +src/Heddle.Performance/GoldenCorpus/manifest.json text eol=lf + +# Rust benchmark harness (docs/spec/cross-stack-benchmarks/phase-2-rust/README.md D2): +# composed-page fragment data must round-trip byte-exact (-text); template files are ordinary +# LF text (their line endings are erased by contract N2, but LF keeps diffs clean). +benchmarks/rust/data/** -text +benchmarks/rust/templates/** text eol=lf + +# Python benchmark harness (docs/spec/cross-stack-benchmarks/phase-5-python/README.md D3): +# template files are committed LF on every platform — Mako passes a literal \r\n in a template +# file through into rendered output, so LF-pinning keeps checked-in bytes equal to authored bytes. +benchmarks/python/templates/** text eol=lf + # Custom for Visual Studio *.cs diff=csharp *.sln merge=union diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 642ca429..2f8ae7af 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -48,7 +48,7 @@ jobs: # Full history so VitePress lastUpdated timestamps work. fetch-depth: 0 - name: Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: 24 cache: npm @@ -85,7 +85,7 @@ jobs: cp -R src/Heddle.Language/ace_build/ace/build/src-noconflict/. docs/public/ace/ # ---- Phase 9: the typed WASM demo bundle (module worker + _framework), Roslyn-free ---- - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: 10.0.x - name: Install wasm-tools workload diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 10c72144..b60e5055 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -28,7 +28,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: | 6.0.x @@ -52,7 +52,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: | 6.0.x @@ -86,7 +86,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: | 6.0.x diff --git a/.github/workflows/lsp.yml b/.github/workflows/lsp.yml index cea8c674..bdd28ea6 100644 --- a/.github/workflows/lsp.yml +++ b/.github/workflows/lsp.yml @@ -17,7 +17,7 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-dotnet@v5 + - uses: actions/setup-dotnet@v6 with: # The multi-targeted test suite runs net6.0/net8.0/net10.0/net48; the runner image # no longer ships the EOL .NET 6 (and may drop 8) runtime, so install them explicitly @@ -32,7 +32,7 @@ jobs: run: dotnet test src/Heddle.Tests/Heddle.Tests.csproj -c Release --no-build - name: Test language services run: dotnet test src/Heddle.LanguageServices.Tests/Heddle.LanguageServices.Tests.csproj -c Release --no-build - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version: 24 - name: JS editor-artifact harness (highlight / beautify / completions) @@ -45,7 +45,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-dotnet@v5 + - uses: actions/setup-dotnet@v6 with: global-json-file: global.json # Framework-dependent + R2R per-RID packs cross-pack from one runner (phase 6 D19). @@ -74,7 +74,7 @@ jobs: - uses: actions/setup-dotnet@v4 with: global-json-file: global.json - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version: 24 - name: Map VS Code target to .NET RID diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index 3c7400cb..b0b0b917 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -1,6 +1,6 @@ # Builds the custom Ace editor bundle (@multiarc/ace_heddle) and publishes it to the # public npm registry (npmjs.org) using npm Trusted Publishing (OIDC) — no NPM_TOKEN. -# - internal PRs -> `npm stage publish` 1.0.0-beta. (staged for review, +# - internal PRs -> `npm stage publish` -beta. (staged for review, # NOT publicly installable until a maintainer approves it) # - tag vX.Y.Z -> `npm publish` X.Y.Z (live release, dist-tag "latest") # @@ -29,7 +29,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Setup Node - uses: actions/setup-node@v6 + uses: actions/setup-node@v7 with: node-version: 24 registry-url: https://registry.npmjs.org @@ -48,7 +48,8 @@ jobs: if: ${{ github.event_name == 'pull_request' }} working-directory: src/Heddle.Language/ace_build/ace/build run: | - npm version --no-git-tag-version 1.0.0-beta.${{ github.run_number }} + BASE_VERSION="$(node -p "require('./package.json').version")" + npm version --no-git-tag-version "${BASE_VERSION}-beta.${{ github.run_number }}" npm stage publish --tag beta - name: Publish release package (tag) if: ${{ startsWith(github.ref, 'refs/tags/v') }} diff --git a/.github/workflows/samples.yml b/.github/workflows/samples.yml index cd5dca1d..bd2b8cf6 100644 --- a/.github/workflows/samples.yml +++ b/.github/workflows/samples.yml @@ -39,7 +39,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 with: dotnet-version: 10.0.x - name: Comparer self-test diff --git a/.gitignore b/.gitignore index 34604d4e..24841813 100644 --- a/.gitignore +++ b/.gitignore @@ -146,3 +146,8 @@ samples/**/generated/ docs/test-results/ docs/playwright-report/ /docs/logs + +# Python benchmark harness (docs/spec/cross-stack-benchmarks/phase-5-python/ WI1): +# venv and raw measurement outputs are never committed; published copies go to docs/benchmarks// +benchmarks/python/.venv/ +benchmarks/python/results/ diff --git a/benchmarks/go/.gitattributes b/benchmarks/go/.gitattributes new file mode 100644 index 00000000..7070ddc5 --- /dev/null +++ b/benchmarks/go/.gitattributes @@ -0,0 +1,5 @@ +# Go benchmark harness (docs/spec/cross-stack-benchmarks/phase-6-go/README.md D2/D3): +# composed-page fragment data must round-trip byte-exact (-text) — the files are embedded +# (go:embed) as RUNTIME DATA and byte-checked against the golden corpus by the controlled +# gate; any smudging would fail the gate. Sources and generated *_templ.go are ordinary text. +internal/model/data/** -text diff --git a/benchmarks/go/README.md b/benchmarks/go/README.md new file mode 100644 index 00000000..8f04449c --- /dev/null +++ b/benchmarks/go/README.md @@ -0,0 +1,113 @@ +# Go benchmark harness (Phase 6 — go) + +The Go module for the cross-stack benchmarks Phase 6 +([spec](../../docs/spec/cross-stack-benchmarks/phase-6-go/README.md)): the stdlib engine +(text/template on the raw suites, html/template on the encoded suite — Q6.1) and templ, both +fairness tracks, gated against the Phase 1 golden corpus at +`src/Heddle.Performance/GoldenCorpus/` (read repo-relative; the corpus does not move). + +Module: `heddle.dev/benchmarks/go` (repo-local, never published). Pins (spec D3): `go 1.26` / +`toolchain go1.26.5`; `github.com/a-h/templ v0.3.1020` (runtime require + CLI `tool` +directive); benchstat via `tool golang.org/x/perf/cmd/benchstat` at +`golang.org/x/perf v0.0.0-20260709024250-82a0b07e230d`. Generated `*_templ.go` files are +committed; regenerate with the pinned CLI only: + +``` +go tool templ generate +``` + +## How to run + +From `benchmarks/go/`: + +``` +go vet ./... # static checks +go test ./internal/... # unit tests: N1–N5 pipeline, verifier calibration, alphabet assert, model pins, S1 probes +go test ./suites # the phase's parity command: TestMain runs every registered gate before anything times +``` + +Layout (harness-and-measurement.md §Module layout): + +- `internal/corpus/` — corpus loader + manifest SHA-256 check, N1–N5 pipeline (N3b comparison + strip included), controlled byte gate + encoded security floor, idiomatic verifier, + untrusted-data alphabet assert (D5). +- `internal/model/` — the eight pinned models (C# field spellings, `strconv`/`%d` numerics); + composed-page fragments embedded byte-exact as runtime data under + `internal/model/data/composed-page/` (pinned `-text` in `.gitattributes`) and byte-checked + against the oracle by `TestComposedAssemblyMatchesTheCorpusOracle`. +- `internal/spike/` — the S1 templ feasibility probes (evidence, excluded from timing). +- `suites/` — `gate_test.go` (TestMain + gate registry; engine cells register here as WI3–WI5 + land), later `bench_test.go` / `coldparse_test.go`. + +Benchmarks, the runner script (`run-benchmarks.ps1`), and the engine ports land with WI3–WI9. + +## S1 results (templ feasibility spike — WI2) + +Executed 2026-07-21 at the pinned toolchain (`go1.26.5`, templ `v0.3.1020`, Windows 11). +Command: `go test ./internal/spike/ -v` (probes are the committed tests in +`internal/spike/spike_test.go`; templ sources in `internal/spike/spike.templ`, generated code +committed alongside). **Verdict: clean pass on all four probes — the expected outcome of +[templ-feasibility.md](../../docs/spec/cross-stack-benchmarks/phase-6-go/templ-feasibility.md). +No non-whitespace divergence surfaced, so no `EXCLUSIONS.md` and no `internal/spike/attempts/` +exist; the implementation plan proceeds unchanged (WI3+).** + +### P1 — smallest-workload byte gate: PASS (byte-identical, pre-N3b) + +`trivial-substitution.templ` authored densely renders, after N1–N4, **byte-identical to the +committed oracle even before the N3b comparison strip** — templ's collapser found nothing to +change in the dense form, and templ emitted the void element as `` +(no self-closing slash), matching the oracle. Observed normalized output (338 bytes = the +oracle's byte length): + +``` +

Heddle Handbook

HB-2001

+``` + +Fixed-point check: `

{ a } { b } { c }

` with single literal spaces renders +`

Mercantile 2026 support at mercantile.example

` — **the single spaces survive +verbatim** (F3 fixed point confirmed; the mixed-page footer construct is safe). + +### P2 — escaper bytes: PASS (every spelling inside the N5 table) + +Text path (fortunes-shaped row over `& < > " '` + Japanese) — observed bytes: + +``` +1& < > " ' こんにちは +``` + +Attribute path (encoded-loop-shaped row, `data-tag={ tag }`, pinned row-0 data) — observed +bytes (templ auto-quotes the attribute with `"` and uses the same escaper on both paths): + +``` +item <0> & "co"'q' & <angle> "d" こんにちは 0 +``` + +Emitted spellings: `& < > " '` on **both** paths (templ.EscapeString → +stdlib html.EscapeString; the attribute path did *not* emit `"`) — **every spelling is +inside the N5 table**; the only non-canonical spelling is `"`, which N5 maps to `"`, +after which both outputs equal the canonical oracle bytes exactly. The Japanese strings pass +through byte-intact, and escape-free data round-trips unescaped (raw-suite no-op premise +confirmed). The non-whitespace contingency (a spelling outside N5) did **not** occur. + +### P3 — style passthrough: PASS (byte-identical) + +The exact mixed-page ``) — F4 executed and confirmed; the `@templ.Raw` fallback of port-mapping rule 6 is +**not** needed; mixed-page will be authored with a literal ` +} + +// P4 — inter-statement whitespace (evidence only): two consecutive @templ.Raw calls on +// separate lines whose payloads reproduce the composed-page CSS-comment neighborhood +// (comp-assets-styles → comp-custom-styles → comp-head-scripts, the one non-tag-flanked +// fragment boundary of the oracle). +templ boundary(link string, css string, script string) { + @templ.Raw(link) + @templ.Raw(css) + @templ.Raw(script) +} diff --git a/benchmarks/go/internal/spike/spike_templ.go b/benchmarks/go/internal/spike/spike_templ.go new file mode 100644 index 00000000..98fd39a0 --- /dev/null +++ b/benchmarks/go/internal/spike/spike_templ.go @@ -0,0 +1,453 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +// S1 — templ controlled-track feasibility spike (WI2). + +// Probes P1–P4 of docs/spec/cross-stack-benchmarks/phase-6-go/templ-feasibility.md, + +// executed at the pinned toolchain (Go 1.26.5, templ v0.3.1020). Throwaway-quality but + +// committed in-tree as evidence; excluded from benchmark runs. Generated via + +// `go tool templ generate` (the pinned CLI through the go.mod tool directive — D3). + +package spike + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "heddle.dev/benchmarks/go/internal/model" + +// P1 — smallest-workload byte gate: trivial-substitution authored densely per +// port-mapping.md §Workload 2 (the pinned one-line
card, ten substitutions in +// pinned order including the two attribute positions). +func trivialSubstitution(m model.SubstitutionModel) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(m.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 14, Col: 23} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(m.Sku) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 14, Col: 52} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(m.Price) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 14, Col: 84} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(m.Brand) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 14, Col: 116} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(m.Category) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 14, Col: 149} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(m.Availability) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 14, Col: 188} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(m.Summary) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 14, Col: 279} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(m.Rating) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 14, Col: 313} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// P1 (second half) — the mixed-page footer construct: string expressions separated by +// single literal spaces; F3's fixed point (a lone space maps to itself). +func spacedExpressions(a string, b string, c string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var12 := templ.GetChildren(ctx) + if templ_7745c5c3_Var12 == nil { + templ_7745c5c3_Var12 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(a) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 20, Col: 7} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(b) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 20, Col: 13} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(c) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 20, Col: 19} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// P2 — escaper bytes, text path: a one-row fortunes-shaped fragment. +func fortunesRow(id string, message string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var16 := templ.GetChildren(ctx) + if templ_7745c5c3_Var16 == nil { + templ_7745c5c3_Var16 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(id) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 25, Col: 13} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(message) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 25, Col: 33} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// P2 — escaper bytes, attribute path: an encoded-loop-shaped row (data-tag={ tag } plus +// two text expressions). +func encodedRow(tag string, name string, comment string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var19 := templ.GetChildren(ctx) + if templ_7745c5c3_Var19 == nil { + templ_7745c5c3_Var19 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 31, Col: 32} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(comment) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/spike/spike.templ`, Line: 31, Col: 52} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// P3 — style passthrough: the exact mixed-page ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// P4 — inter-statement whitespace (evidence only): two consecutive @templ.Raw calls on +// separate lines whose payloads reproduce the composed-page CSS-comment neighborhood +// (comp-assets-styles → comp-custom-styles → comp-head-scripts, the one non-tag-flanked +// fragment boundary of the oracle). +func boundary(link string, css string, script string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var24 := templ.GetChildren(ctx) + if templ_7745c5c3_Var24 == nil { + templ_7745c5c3_Var24 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templ.Raw(link).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(css).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(script).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/benchmarks/go/internal/spike/spike_test.go b/benchmarks/go/internal/spike/spike_test.go new file mode 100644 index 00000000..fa4996ac --- /dev/null +++ b/benchmarks/go/internal/spike/spike_test.go @@ -0,0 +1,220 @@ +// S1 probe execution (WI2). Each test is one probe of templ-feasibility.md §S1; the +// observed bytes are additionally recorded in benchmarks/go/README.md §S1 results. +package spike + +import ( + "context" + "strings" + "testing" + + "github.com/a-h/templ" + + "heddle.dev/benchmarks/go/internal/corpus" + "heddle.dev/benchmarks/go/internal/model" +) + +func render(t *testing.T, c templ.Component) string { + t.Helper() + var b strings.Builder + if err := c.Render(context.Background(), &b); err != nil { + t.Fatalf("render failed: %v", err) + } + return b.String() +} + +// ---- P1 — smallest-workload byte gate -------------------------------------------------------- + +func TestP1TrivialSubstitutionPassesTheByteGate(t *testing.T) { + output := render(t, trivialSubstitution(model.Substitution)) + + // The full controlled gate: N1–N4 pipeline, N3b strip on both sides, byte compare + // against the committed corpus entry. + if err := corpus.CheckControlled("trivial-substitution", "raw", "templ-spike", + func() string { return output }); err != nil { + t.Fatal(err) + } + + // Evidence: is the normalized candidate byte-identical to the stored oracle even + // BEFORE the N3b comparison strip? + golden, err := corpus.LoadGolden("trivial-substitution") + if err != nil { + t.Fatal(err) + } + normalized := corpus.NormalizeRaw(output) + if normalized == golden { + t.Log("P1: normalized output is byte-identical to the oracle pre-N3b (no whitespace divergence at all)") + } else { + t.Logf("P1: whitespace-only divergence reconciled by N3/N3b (normalized %d bytes vs oracle %d)", + len(normalized), len(golden)) + } +} + +func TestP1SingleSpacesBetweenExpressionsSurviveVerbatim(t *testing.T) { + // The mixed-page footer construct: `{ a } { b } { c }` separated by single literal + // spaces. F3: collapsing maps a lone space to itself (fixed point) — the spaces must + // survive verbatim in the rendered output. + output := render(t, spacedExpressions("Mercantile", "2026", "support at mercantile.example")) + const want = "

Mercantile 2026 support at mercantile.example

" + if output != want { + t.Fatalf("single spaces did not survive verbatim:\n want %q\n got %q", want, output) + } +} + +// ---- P2 — escaper bytes ---------------------------------------------------------------------- + +// n5Table lists every spelling the contract's N5 table recognizes for the five +// markup-significant characters; any escaper output must be composed of literal safe text +// plus spellings from this set. +var n5Table = []string{ + "&", "&", "&", + "<", "<", "<", + ">", ">", ">", + """, """, """, + "'", "'", "'", +} + +// assertSpellingsInsideN5 walks every entity-shaped span ("&…;") of the escaped output and +// asserts it is one of the N5 table's recognized spellings. +func assertSpellingsInsideN5(t *testing.T, label, escaped string) { + t.Helper() + for i := 0; i < len(escaped); i++ { + if escaped[i] != '&' { + continue + } + end := strings.IndexByte(escaped[i:], ';') + if end < 0 { + t.Fatalf("%s: unterminated entity at %d in %q", label, i, escaped) + } + entity := escaped[i : i+end+1] + found := false + for _, spelling := range n5Table { + if entity == spelling { + found = true + break + } + } + if !found { + t.Fatalf("%s: escaper spelling %q is OUTSIDE the N5 table — non-whitespace divergence (Q1.2 exclusion contingency)", label, entity) + } + i += end + } +} + +func TestP2TextPathEscaperBytes(t *testing.T) { + // A fortunes-shaped row whose message covers all five specials plus Japanese. + const message = `& < > " ' こんにちは` + output := render(t, fortunesRow("1", message)) + t.Logf("P2 text path: %q", output) + + // Every emitted spelling must be inside the N5 table. + assertSpellingsInsideN5(t, "text path", output) + + // Pin the exact observed spellings (templ.EscapeString → html.EscapeString: five + // characters, decimal quotes — F5). + const wantRow = `1& < > " ' こんにちは` + if output != wantRow { + t.Fatalf("text-path bytes changed:\n want %q\n got %q", wantRow, output) + } + + // After N5 the output must carry the canonical spellings, byte-for-byte. + canonical := corpus.N5Canonicalize(output) + const wantCanonical = `1& < > " ' こんにちは` + if canonical != wantCanonical { + t.Fatalf("N5 canonicalization mismatch:\n want %q\n got %q", wantCanonical, canonical) + } + + // The Japanese string is byte-intact (the stdlib escaper touches only the five ASCII). + if !strings.Contains(output, "こんにちは") { + t.Fatal("Japanese string must pass through byte-intact") + } +} + +func TestP2AttributePathEscaperBytes(t *testing.T) { + // An encoded-loop-shaped row: data-tag={ tag } attribute plus two text expressions, + // using the pinned row-0 values (tag covers & and '; name covers < > and "). + row := model.EncodedLoop.Items[0] + output := render(t, encodedRow(row.Tag, row.Name, row.Comment)) + t.Logf("P2 attribute path: %q", output) + + assertSpellingsInsideN5(t, "attribute path", output) + + // Pin the exact observed bytes: templ auto-quotes the attribute and escapes both paths + // with the same five-character decimal-quote table. + const wantRow = `item <0> & "co"'q' & <angle> "d" こんにちは 0` + if output != wantRow { + t.Fatalf("attribute-path bytes changed:\n want %q\n got %q", wantRow, output) + } + + // After N5, the bytes equal the oracle's canonical row 0 exactly. + const wantCanonical = `item <0> & "co"'q' & <angle> "d" こんにちは 0` + if canonical := corpus.N5Canonicalize(output); canonical != wantCanonical { + t.Fatalf("N5 canonicalization mismatch:\n want %q\n got %q", wantCanonical, canonical) + } +} + +func TestP2EscapeFreeDataRoundTripsUnescaped(t *testing.T) { + // '+'-free (and escapable-free) data round-trips byte-identical — the raw suites' + // no-op-escaper premise (authoring rule 4). + output := render(t, fortunesRow("7", "Computers make very fast, very accurate mistakes.")) + const want = "7Computers make very fast, very accurate mistakes." + if output != want { + t.Fatalf("escape-free data must round-trip unescaped:\n want %q\n got %q", want, output) + } +} + +// ---- P3 — style passthrough ------------------------------------------------------------------ + +// styleCSS is the exact mixed-page " + if !strings.HasPrefix(output, prefix) || !strings.HasSuffix(output, suffix) { + t.Fatalf("style element shape changed: %q", output) + } + content := output[len(prefix) : len(output)-len(suffix)] + if content == styleCSS { + return // F4 holds: rendered without any changes — no fallback needed + } + // Only a confirmed NON-whitespace change routes to the @templ.Raw fallback + // (port-mapping rule 6); a whitespace-only difference passes via N3b. + if corpus.N3bStrip(content) == corpus.N3bStrip(styleCSS) { + t.Logf("P3: whitespace-only difference in style content (reconciled by N3b): %q", content) + return + } + t.Fatalf("P3: NON-whitespace change to style content — the @templ.Raw fallback branch applies:\n want %q\n got %q", styleCSS, content) +} + +// ---- P4 — inter-statement whitespace (evidence only) ----------------------------------------- + +func TestP4ComposedPageBoundaryBytes(t *testing.T) { + // The composed-page CSS-comment neighborhood: comp-assets-styles → + // comp-custom-styles → comp-head-scripts, issued as consecutive @templ.Raw calls on + // separate lines — the fragments verbatim from the pinned model data. + link := model.Composed.Comp["assets_styles"] + css := model.Composed.Comp["custom_styles"] + script := model.Composed.Comp["head_scripts"] + output := render(t, boundary(link, css, script)) + + // Evidence: the exact bytes templ emits between consecutive statement nodes. + zeroSeparator := link + css + script + switch { + case output == zeroSeparator: + t.Log("P4: templ emits ZERO separator bytes between consecutive @templ.Raw nodes — boundary already byte-exact vs the oracle") + case corpus.N3bStrip(output) == corpus.N3bStrip(zeroSeparator): + t.Logf("P4: whitespace-only separator between @templ.Raw nodes (passes via N3b). Observed output: %q", output) + default: + t.Fatalf("P4: NON-whitespace divergence at the inter-statement boundary:\n want (stripped) %q\n got %q", + zeroSeparator, output) + } + + // The oracle's non-tag-flanked boundary must survive the comparison projection: the + // stripped candidate must contain the oracle's `/>/*CSSCommentTest*//*CSSCommentTest*/.verify.json semantics), not the byte gate. +// +// Doc citations (Q1.7 — official documentation patterns followed here): +// - https://pkg.go.dev/text/template (Actions, Text and spaces / trim markers, +// Nested template definitions, Examples) +// - https://pkg.go.dev/html/template (same template API; contextual auto-escaping) +// +// Spec: docs/spec/cross-stack-benchmarks/phase-6-go/port-mapping.md +// §Idiomatic track — both engines, all eight workloads. +package stdlibtpl + +import ( + htmltemplate "html/template" + texttemplate "text/template" + + "heddle.dev/benchmarks/go/internal/model" +) + +// ---- workload 1 — composed-page (text/template) ---------------------------------------------- + +// Layout composition via nested template definitions ({{define "layout"}} + +// {{template "layout" .}}), one fragment per line; the HTML fragments are runtime data and +// text/template passes them through unescaped. +const idiomaticComposedSrc = `{{define "layout"}} +{{index .Section "meta"}} +{{index .Section "social"}} +{{index .Comp "assets_styles"}} +{{index .Comp "custom_styles"}} +{{index .Comp "head_scripts"}} +{{index .Comp "body_scripts"}} +{{range .AreaNames -}} +{{index $.Areas .}} +{{end -}} +{{index .Comp "assets_scripts"}} +{{index .Section "page_scripts"}} +{{index .Section "endpage_scripts"}} +{{index .Comp "body_end_scripts"}} +{{end}} +{{- template "layout" .}}` + +// ---- workload 2 — trivial-substitution (text/template) --------------------------------------- + +const idiomaticTrivialSubstitutionSrc = `
+

{{.Title}}

+

{{.Sku}}

+

{{.Price}}

+

{{.Brand}}

+

{{.Category}}

+

{{.Availability}}

+ +

{{.Summary}}

+

{{.Rating}}

+
+` + +// ---- workload 3 — large-loop (text/template) ------------------------------------------------- + +// Trim markers around the range body, the shape the text/template docs' "Text and spaces" +// section teaches for loop output. +const idiomaticLargeLoopSrc = `{{range .Items -}} + + {{.Name}} + {{.Value}} + +{{end -}} +` + +// ---- workload 4 — mixed-page (text/template) ------------------------------------------------- + +const idiomaticMixedPageSrc = ` + + + + {{.PageTitle}} + + + +
+

{{.StoreName}}

+ +
+
+ {{if .ShowBanner}}{{end}} +
+

{{.HeroHeading}}

+

{{.HeroTagline}}

+
+
+ {{range .Products -}} +
+

{{.Name}}

+

{{.Sku}}

+

{{.Price}}

+ {{if .OnSale}}

On sale

{{end}} +

{{.Blurb}}

+
+ {{end -}} +
+ {{if .ShowDebugPanel}}
debug
{{end}} +
+
+

{{.FooterNote}}

+

{{.StoreName}} {{.Year}} {{.SupportEmail}}

+
+ + +` + +// ---- workload 5 — conditional-heavy (text/template) ------------------------------------------ + +const idiomaticConditionalHeavySrc = `
    + {{range .Rows -}} +
  • + {{if .IsBronze -}} + bronze + {{- else if .IsSilver -}} + silver + {{- else if .IsGold -}} + gold + {{- else -}} + platinum + {{- end}} + {{.Name}} + {{if .HasNote}}{{.Note}}{{end}} + {{if .IsActive}}active{{end}} +
  • + {{end -}} +
+` + +// ---- workload 6 — fragment-heavy (text/template) --------------------------------------------- + +// The partial-with-current-row construct exactly as the docs' nested-template-definition +// examples show it: a named {{define}} invoked with {{template "tile" .}} per row. +const idiomaticFragmentHeavySrc = `{{define "tile"}} +
+

{{.Name}}

+

{{.Value}}

+ {{.Badge}} +
+{{end}} +{{- define "panel"}} +
+ {{range .Items}}{{template "tile" .}}{{end}} +
+{{end}} +{{- template "panel" .}}` + +// ---- workload 7 — fortunes-encoded (html/template) ------------------------------------------- + +// html/template's contextual auto-escaper fires on {{.Id}}/{{.Message}}; the authoring is +// otherwise identical to how the html/template docs teach a data-driven page. +const idiomaticFortunesEncodedSrc = ` + + + Fortunes + + + + + {{range .Rows -}} + + + + + {{end -}} +
idmessage
{{.Id}}{{.Message}}
+ + +` + +// ---- workload 8 — encoded-loop (html/template) ----------------------------------------------- + +// {{.Tag}} in quoted-attribute context, the other two substitutions in text context — the +// escaper picks the context per the html/template package docs. +const idiomaticEncodedLoopSrc = ` + {{range .Items -}} + + + + + {{end -}} +
{{.Name}}{{.Comment}}
+` + +// ---- parsed templates (once, at package init — the cached-template render path) -------------- + +var ( + idiomaticComposedTpl = texttemplate.Must(texttemplate.New("composed-page").Parse(idiomaticComposedSrc)) + idiomaticTrivialSubstitutionTpl = texttemplate.Must(texttemplate.New("trivial-substitution").Parse(idiomaticTrivialSubstitutionSrc)) + idiomaticLargeLoopTpl = texttemplate.Must(texttemplate.New("large-loop").Parse(idiomaticLargeLoopSrc)) + idiomaticMixedPageTpl = texttemplate.Must(texttemplate.New("mixed-page").Parse(idiomaticMixedPageSrc)) + idiomaticConditionalHeavyTpl = texttemplate.Must(texttemplate.New("conditional-heavy").Parse(idiomaticConditionalHeavySrc)) + idiomaticFragmentHeavyTpl = texttemplate.Must(texttemplate.New("fragment-heavy").Parse(idiomaticFragmentHeavySrc)) + idiomaticFortunesEncodedTpl = htmltemplate.Must(htmltemplate.New("fortunes-encoded").Parse(idiomaticFortunesEncodedSrc)) + idiomaticEncodedLoopTpl = htmltemplate.Must(htmltemplate.New("encoded-loop").Parse(idiomaticEncodedLoopSrc)) +) + +// ---- per-cell reused buffers (render.go's newBuf; same sizing cases as controlled) ----------- + +var ( + idComposedBuf = newBuf(64 << 10) + idTrivialBuf = newBuf(1 << 10) + idLargeLoopBuf = newBuf(256 << 10) + idMixedBuf = newBuf(32 << 10) + idConditionalBuf = newBuf(32 << 10) + idFragmentBuf = newBuf(16 << 10) + idFortunesBuf = newBuf(4 << 10) + idEncodedLoopBuf = newBuf(2 << 20) +) + +// ---- the eight idiomatic cells (raw = stdlib-text, encoded = stdlib-html) -------------------- + +// RenderIdiomaticComposedPage renders workload 1 idiomatically via text/template. +func RenderIdiomaticComposedPage() string { + return render(idiomaticComposedTpl, idComposedBuf, model.Composed) +} + +// RenderIdiomaticTrivialSubstitution renders workload 2 idiomatically via text/template. +func RenderIdiomaticTrivialSubstitution() string { + return render(idiomaticTrivialSubstitutionTpl, idTrivialBuf, model.Substitution) +} + +// RenderIdiomaticLargeLoop renders workload 3 idiomatically via text/template. +func RenderIdiomaticLargeLoop() string { + return render(idiomaticLargeLoopTpl, idLargeLoopBuf, model.LargeLoop) +} + +// RenderIdiomaticMixedPage renders workload 4 idiomatically via text/template. +func RenderIdiomaticMixedPage() string { + return render(idiomaticMixedPageTpl, idMixedBuf, model.Mixed) +} + +// RenderIdiomaticConditionalHeavy renders workload 5 idiomatically via text/template. +func RenderIdiomaticConditionalHeavy() string { + return render(idiomaticConditionalHeavyTpl, idConditionalBuf, model.Conditional) +} + +// RenderIdiomaticFragmentHeavy renders workload 6 idiomatically via text/template. +func RenderIdiomaticFragmentHeavy() string { + return render(idiomaticFragmentHeavyTpl, idFragmentBuf, model.Fragment) +} + +// RenderIdiomaticFortunesEncoded renders workload 7 idiomatically via html/template. +func RenderIdiomaticFortunesEncoded() string { + return render(idiomaticFortunesEncodedTpl, idFortunesBuf, model.Fortunes) +} + +// RenderIdiomaticEncodedLoop renders workload 8 idiomatically via html/template. +func RenderIdiomaticEncodedLoop() string { + return render(idiomaticEncodedLoopTpl, idEncodedLoopBuf, model.EncodedLoop) +} diff --git a/benchmarks/go/internal/stdlibtpl/render.go b/benchmarks/go/internal/stdlibtpl/render.go new file mode 100644 index 00000000..7127f82e --- /dev/null +++ b/benchmarks/go/internal/stdlibtpl/render.go @@ -0,0 +1,88 @@ +// Controlled-track render path for the stdlib surfaces: every render executes the parsed +// template into a reused per-cell bytes.Buffer, Reset() at the top of each call and +// pre-grown once to the workload's output size so neither growth policy nor the reset is a +// per-engine variable (harness-and-measurement.md §Benchmark shape), returning buf.String(). +package stdlibtpl + +import ( + "bytes" + "io" + + "heddle.dev/benchmarks/go/internal/model" +) + +// executer is the shared surface of *text/template.Template and *html/template.Template. +type executer interface { + Execute(wr io.Writer, data any) error +} + +// newBuf returns a buffer pre-grown to the workload's output size (encoded-loop's ~1.1 MB +// escaped output is the sizing case), so growth never lands in a timed region. +func newBuf(capacity int) *bytes.Buffer { + buf := new(bytes.Buffer) + buf.Grow(capacity) + return buf +} + +var ( + composedBuf = newBuf(64 << 10) + trivialBuf = newBuf(1 << 10) + largeLoopBuf = newBuf(256 << 10) + mixedBuf = newBuf(32 << 10) + conditionalBuf = newBuf(32 << 10) + fragmentBuf = newBuf(16 << 10) + fortunesBuf = newBuf(4 << 10) + encodedLoopBuf = newBuf(2 << 20) +) + +// render resets the cell's buffer, executes the cached template, and returns the output. +// A template execution error is a harness defect, never a gate outcome — panic loudly. +func render(t executer, buf *bytes.Buffer, data any) string { + buf.Reset() + if err := t.Execute(buf, data); err != nil { + panic("stdlibtpl: render failed: " + err.Error()) + } + return buf.String() +} + +// ---- the eight controlled cells (raw = stdlib-text, encoded = stdlib-html) ------------------- + +// RenderComposedPage renders workload 1 via text/template (stdlib-text). +func RenderComposedPage() string { + return render(composedTpl, composedBuf, model.Composed) +} + +// RenderTrivialSubstitution renders workload 2 via text/template (stdlib-text). +func RenderTrivialSubstitution() string { + return render(trivialSubstitutionTpl, trivialBuf, model.Substitution) +} + +// RenderLargeLoop renders workload 3 via text/template (stdlib-text). +func RenderLargeLoop() string { + return render(largeLoopTpl, largeLoopBuf, model.LargeLoop) +} + +// RenderMixedPage renders workload 4 via text/template (stdlib-text). +func RenderMixedPage() string { + return render(mixedPageTpl, mixedBuf, model.Mixed) +} + +// RenderConditionalHeavy renders workload 5 via text/template (stdlib-text). +func RenderConditionalHeavy() string { + return render(conditionalHeavyTpl, conditionalBuf, model.Conditional) +} + +// RenderFragmentHeavy renders workload 6 via text/template (stdlib-text). +func RenderFragmentHeavy() string { + return render(fragmentHeavyTpl, fragmentBuf, model.Fragment) +} + +// RenderFortunesEncoded renders workload 7 via html/template (stdlib-html). +func RenderFortunesEncoded() string { + return render(fortunesEncodedTpl, fortunesBuf, model.Fortunes) +} + +// RenderEncodedLoop renders workload 8 via html/template (stdlib-html). +func RenderEncodedLoop() string { + return render(encodedLoopTpl, encodedLoopBuf, model.EncodedLoop) +} diff --git a/benchmarks/go/internal/stdlibtpl/templates.go b/benchmarks/go/internal/stdlibtpl/templates.go new file mode 100644 index 00000000..0a9f45e8 --- /dev/null +++ b/benchmarks/go/internal/stdlibtpl/templates.go @@ -0,0 +1,112 @@ +// Package stdlibtpl hosts the stdlib-engine controlled ports: text/template for the six +// raw workloads and html/template for the two encoded workloads (README D1 / Q6.1 — both +// surfaces of the one credibility-pick stdlib engine; benchmark ids stdlib-text / +// stdlib-html). Template sources are Go raw string literals authored densely — byte-for-byte +// the pinned Heddle/twin shape with {{…}} actions in place of @(…) substitutions, no +// whitespace beyond what the pinned shape carries. Every template is parsed once in a +// package var block (the cached-template render path); renders execute into a reused +// pre-grown buffer (render.go). +// +// Normative texts: docs/spec/cross-stack-benchmarks/phase-6-go/port-mapping.md +// §Controlled track — stdlib surfaces (workload shapes from Phase 1 workloads.md). +package stdlibtpl + +import ( + htmltemplate "html/template" + texttemplate "text/template" +) + +// ---- workload 1 — composed-page (text/template) ---------------------------------------------- + +// composedHomeSrc is the home template (the include mapping of the twin table). +const composedHomeSrc = `{{template "layout" .}}` + +// composedLayoutSrc is the layout template — one line, no whitespace between actions, +// mirroring LiquidTemplates.LayoutTemplate's zero-separator concatenation. +const composedLayoutSrc = `{{define "layout"}}{{index .Section "meta"}}{{index .Section "social"}}{{index .Comp "assets_styles"}}{{index .Comp "custom_styles"}}{{index .Comp "head_scripts"}}{{index .Comp "body_scripts"}}{{range .AreaNames}}{{index $.Areas .}}{{end}}{{index .Comp "assets_scripts"}}{{index .Section "page_scripts"}}{{index .Section "endpage_scripts"}}{{index .Comp "body_end_scripts"}}{{end}}` + +// ---- workload 2 — trivial-substitution (text/template) --------------------------------------- + +// Dense one-line
card, ten {{.Member}} substitutions in pinned order, including +// the two attribute positions. +const trivialSubstitutionSrc = `

{{.Title}}

{{.Sku}}

{{.Price}}

{{.Brand}}

{{.Category}}

{{.Availability}}

{{.Summary}}

{{.Rating}}

` + +// ---- workload 3 — large-loop (text/template) ------------------------------------------------- + +// Value is an int; text/template renders ints via fmt (%v), identical bytes to strconv.Itoa. +const largeLoopSrc = `{{range .Items}}{{.Name}}{{.Value}}{{end}}` + +// ---- workload 4 — mixed-page (text/template) ------------------------------------------------- + +// The pinned skeleton transcribed line-for-line (line breaks between sibling elements are +// N2/N3-erased; the + + +
+

{{.StoreName}}

+ +
+
+{{if .ShowBanner}}{{end}} +
+

{{.HeroHeading}}

+

{{.HeroTagline}}

+
+
+{{range .Products}}

{{.Name}}

{{.Sku}}

{{.Price}}

{{if .OnSale}}

On sale

{{end}}

{{.Blurb}}

{{end}} +
+{{if .ShowDebugPanel}}
debug
{{end}} +
+
+

{{.FooterNote}}

+

{{.StoreName}} {{.Year}} {{.SupportEmail}}

+
+ +` + +// ---- workload 5 — conditional-heavy (text/template) ------------------------------------------ + +// The pinned single-line