Skip to content

Comments

Replace regex-based {{}} expansion with lvalue/Proxy approach#97

Merged
benbernard merged 1 commit intomasterfrom
feature/lvalue-template-expansion
Feb 25, 2026
Merged

Replace regex-based {{}} expansion with lvalue/Proxy approach#97
benbernard merged 1 commit intomasterfrom
feature/lvalue-template-expansion

Conversation

@benbernard
Copy link
Owner

Summary

  • Replace 3-step regex pipeline in transformCode() with a single {{(.*?)}} regex
  • JS: expand {{x}} to __F["x"] backed by a Proxy with get/set traps
  • Python: expand {{x}} to __F["x"] backed by __getitem__/__setitem__
  • Perl: expand {{x}} to _f("x") using a lvalue sub that returns the hash element directly
  • Fix bugs where old [^;,\n]+ RHS capture broke on commas in function calls, array/object literals, and ternaries
  • Add regression tests for the fixed bugs plus ++/-- support

Test plan

  • All 1677 existing tests pass
  • New tests cover: function call args with commas, array literals, object literals, ternary operators, ++/--
  • Cross-language template expansion tests pass for JS, Python, and Perl

🤖 Generated with Claude Code

Replace the fragile 3-step regex pipeline in transformCode() with a
single regex that produces language-native lvalue expressions. The
language itself now handles reads, writes, compound assignments, and
increment/decrement — no assignment-detection regex needed.

- JS: {{x}} → __F["x"] via Proxy get/set traps
- Python: {{x}} → __F["x"] via __getitem__/__setitem__
- Perl: {{x}} → _f("x") via lvalue sub returning hash element

Fixes bugs where the old [^;,\n]+ RHS capture regex broke on commas
in function calls, array literals, object literals, and ternaries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link

Performance Benchmark Results

⚠️ 8 regressions detected out of 103 benchmarks (threshold: 25%)

Benchmark Median Baseline Delta
Record.toJSON — 10K records 505.3µs 289.6µs +74.5% 🔴
Compiled KeySpec.setValue — nested (address/zip) 276.1µs 147.7µs +87.0% 🔴
eval — 10K records (add computed field) 3.33ms 2.42ms +37.8% 🔴
chain — single op (grep), 10K records 10.54ms 7.28ms +44.7% 🔴
chain — 3 ops (grep eval grep), 10K records 9.59ms
chain — 5 ops (grep eval grep eval
new Record() empty — 10K 142.4µs 6.3µs +2164.5% 🔴
chain — 2 ops (grep eval), 10K records 1.35ms 1.07ms

103 benchmarks: 15 faster, 14 slower, 74 within noise (10%)

ℹ️ Note: Benchmarks are advisory-only. GitHub Actions shared runners have variable performance, so results may fluctuate ±25% between runs. For reliable benchmarking, run locally with bun run bench.

Full benchmark results

JSON Parsing

Benchmark Median Baseline Delta Throughput
Record.fromJSON — 100 lines 152.8µs 152.1µs +0.5% 654.32K rec/s
Record.fromJSON — 10K lines 14.45ms 13.11ms +10.2% 🔴 692.15K rec/s, 205.0 MB/s
InputStream.fromString — 100 records 211.3µs 207.3µs +1.9% 473.21K rec/s
InputStream.fromString — 10K records 19.15ms 18.06ms +6.0% 522.23K rec/s, 154.7 MB/s
JSON.parse baseline — 10K lines (no Record) 12.79ms 12.83ms -0.4% 782.11K rec/s, 231.6 MB/s
JSON.parse single array — 10K records 13.66ms 12.69ms +7.6% 732.11K rec/s, 216.8 MB/s

JSON Serialization

Benchmark Median Baseline Delta Throughput
Record.toString — 100 records 90.7µs 74.8µs +21.3% 🔴 1.10M rec/s
Record.toString — 10K records 8.73ms 8.60ms +1.6% 1.14M rec/s, 339.1 MB/s
Record.toJSON — 10K records 505.3µs 289.6µs +74.5% 🔴 19.79M rec/s
JSON.stringify baseline — 10K objects (no Record) 8.04ms 7.88ms +2.1% 1.24M rec/s, 368.3 MB/s
Batch join — 10K records (map+join) 8.52ms 8.43ms +1.2% 1.17M rec/s, 347.4 MB/s

KeySpec Access

Benchmark Median Baseline Delta Throughput
KeySpec — simple key (name) 215.2µs 238.5µs -9.8% 46.47M rec/s
KeySpec — nested key (address/zip) 675.9µs 558.7µs +21.0% 🔴 14.80M rec/s
KeySpec — deep nested (address/coords/lat) 1.06ms 1.07ms -1.1% 9.44M rec/s
KeySpec — array index (tags/#0) 962.6µs 892.1µs +7.9% 10.39M rec/s
Direct property access baseline (rec['name']) 73.9µs 73.0µs +1.2% 135.35M rec/s
Direct nested access baseline (rec.address.coords.lat) 103.4µs 97.4µs +6.2% 96.72M rec/s
KeySpec construction — cached (same spec 10K times) 307.0µs 477.3µs -35.7% 🟢 32.57M rec/s
KeySpec construction — unique specs (10K different) 4.12ms 3.53ms +16.8% 🔴 2.43M rec/s
Compiled KeySpec.resolveValue — nested (address/zip) 303.8µs 307.5µs -1.2% 32.91M rec/s
Compiled KeySpec.resolveValue — deep (address/coords/lat) 125.7µs 239.6µs -47.5% 🟢 79.55M rec/s
Compiled KeySpec.resolveValue — array (tags/#0) 153.9µs 261.3µs -41.1% 🟢 64.96M rec/s
Compiled KeySpec.setValue — nested (address/zip) 276.1µs 147.7µs +87.0% 🔴 36.22M rec/s

Core Operations

Benchmark Median Baseline Delta Throughput
grep — 10K records (r.age > 50) 471.3µs 441.4µs +6.8% 21.22M rec/s
grep — 10K records (string match) 440.8µs 439.2µs +0.4% 22.69M rec/s
eval — 10K records (add computed field) 3.33ms 2.42ms +37.8% 🔴 3.00M rec/s
xform — 10K records (push each record) 2.95ms 3.07ms -3.8% 3.39M rec/s
sort — 100 records (by score, numeric) 195.1µs 226.6µs -13.9% 🟢 512.44K rec/s
sort — 10K records (by score, numeric) 19.07ms 18.30ms +4.2% 524.41K rec/s
sort — 10K records (by name, lexical) 13.71ms 11.85ms +15.7% 🔴 729.58K rec/s
collate — 100 records (count by city) 426.3µs 430.7µs -1.0% 234.59K rec/s
collate — 10K records (count by city) 12.47ms 12.36ms +0.9% 801.89K rec/s
fromcsv — 10K rows (parse CSV to records) 14.92ms 14.10ms +5.8% 670.16K rec/s, 44.0 MB/s

Pipeline Overhead

Benchmark Median Baseline Delta Throughput
chain — single op (grep), 10K records 10.54ms 7.28ms +44.7% 🔴 948.87K rec/s
chain — 3 ops (grep eval grep), 10K records 9.59ms 7.61ms
chain — 5 ops (grep eval grep eval grep), 10K records
passthrough baseline — 10K records (direct collector) 7.12ms 6.08ms +17.2% 🔴 1.40M rec/s

Record Creation & Serialization

Benchmark Median Baseline Delta Throughput
new Record() — 10K objects 97.4µs 473.0µs -79.4% 🟢 102.62M rec/s
new Record() empty — 10K 142.4µs 6.3µs +2164.5% 🔴 70.24M rec/s
Record.get — 10K records × 3 fields 59.4µs 58.3µs +1.8% 505.21M rec/s
Record.set — 10K records × 1 field 64.2µs 60.2µs +6.6% 155.85M rec/s
Record.toJSON — 10K records 295.2µs 289.6µs +1.9% 33.88M rec/s
Record.toString — 10K records 8.81ms 8.60ms +2.5% 1.13M rec/s
Record.clone — 10K records 6.46ms 6.15ms +5.0% 1.55M rec/s
Record.fromJSON — 10K lines 13.29ms 13.11ms +1.4% 752.52K rec/s, 222.9 MB/s
Record.dataRef — 10K records (zero-copy) 38.3µs 38.2µs +0.3% 261.08M rec/s
Record.sort — 10K records (numeric field) 11.84ms 11.66ms +1.5% 844.50K rec/s
Record.sort — 10K records (lexical field) 6.17ms 6.16ms +0.2% 1.62M rec/s
Record.cmp — 1M comparisons (single field) 104.96ms 103.92ms +1.0% 9.53M rec/s
Record.sort — 10K records (nested field numeric) 16.32ms 16.08ms +1.5% 612.76K rec/s
Record.cmp — 1M comparisons (multi-field cached) 90.06ms 87.75ms +2.6% 11.10M rec/s
Record.sort — 10K records (cached comparator reuse) 12.03ms 12.23ms -1.7% 831.47K rec/s

Chain vs Pipe

Benchmark Median Baseline Delta Throughput
chain — 2 ops (grep eval), 100 records 152.9µs 151.4µs +0.9%
pipe — 2 ops (grep eval), 100 records 503.27ms 504.74ms -0.3%
implicit — 2 ops (grep eval), 100 records 131.9µs 152.8µs -13.7% 🟢
chain — 2 ops (grep eval), 1K records 271.1µs 290.4µs -6.6%
pipe — 2 ops (grep eval), 1K records 501.71ms 505.92ms -0.8%
implicit — 2 ops (grep eval), 1K records 215.2µs 199.8µs +7.7%
chain — 2 ops (grep eval), 10K records 1.35ms 1.07ms +26.3% 🔴
pipe — 2 ops (grep eval), 10K records 510.97ms 507.55ms +0.7%
implicit — 2 ops (grep eval), 10K records 1.12ms 1.08ms +3.5%
chain — 3 ops (grep eval grep), 100 records 160.0µs 167.3µs
pipe — 3 ops (grep eval grep), 100 records 750.17ms 748.14ms
implicit — 3 ops (grep eval grep), 100 records 81.0µs 174.5µs
chain — 3 ops (grep eval grep), 1K records 193.3µs 281.6µs
pipe — 3 ops (grep eval grep), 1K records 754.85ms 756.74ms
implicit — 3 ops (grep eval grep), 1K records 210.2µs 312.0µs
chain — 3 ops (grep eval grep), 10K records 1.12ms 1.38ms
pipe — 3 ops (grep eval grep), 10K records 778.57ms 749.39ms
implicit — 3 ops (grep eval grep), 10K records 1.15ms 1.35ms
chain — 5 ops (grep eval grep eval grep), 100 records
pipe — 5 ops (grep eval grep eval grep), 100 records
implicit — 5 ops (grep eval grep eval grep), 100 records
chain — 5 ops (grep eval grep eval grep), 1K records
pipe — 5 ops (grep eval grep eval grep), 1K records
implicit — 5 ops (grep eval grep eval grep), 1K records
chain — 5 ops (grep eval grep eval grep), 10K records
pipe — 5 ops (grep eval grep eval grep), 10K records
implicit — 5 ops (grep eval grep eval grep), 10K records

Line Reading

Benchmark Median Baseline Delta Throughput
InputStream.fromFile — 100 lines 477.8µs 515.7µs -7.3% 209.30K rec/s, 61.8 MB/s
InputStream.fromString — 100 lines 194.5µs 193.9µs +0.3% 514.25K rec/s, 151.9 MB/s
manual buffer (isolated) — 100 lines 282.3µs 260.2µs +8.5% 354.27K rec/s, 104.6 MB/s
bulk text + split — 100 lines 96.6µs 88.5µs +9.2% 1.04M rec/s, 305.8 MB/s
node readline — 100 lines 512.0µs 479.1µs +6.9% 195.31K rec/s, 57.7 MB/s
TextDecoderStream — 100 lines 246.4µs 265.2µs -7.1% 405.88K rec/s, 119.9 MB/s
binary newline scan — 100 lines 262.6µs 359.1µs -26.9% 🟢 380.87K rec/s, 112.5 MB/s
bun native stdin — 100 lines 25.46ms 24.73ms +3.0% 3.93K rec/s, 1.2 MB/s
InputStream.fromFile — 10K lines 23.82ms 23.78ms +0.2% 419.77K rec/s, 124.3 MB/s
InputStream.fromString — 10K lines 18.24ms 17.88ms +2.0% 548.24K rec/s, 162.4 MB/s
manual buffer (isolated) — 10K lines 6.04ms 6.32ms -4.4% 1.66M rec/s, 490.2 MB/s
bulk text + split — 10K lines 2.59ms 2.39ms +8.4% 3.86M rec/s, 1141.9 MB/s
node readline — 10K lines 9.65ms 9.02ms +7.0% 1.04M rec/s, 307.0 MB/s
TextDecoderStream — 10K lines 4.07ms 5.06ms -19.5% 🟢 2.46M rec/s, 728.1 MB/s
binary newline scan — 10K lines 7.76ms 8.55ms -9.2% 1.29M rec/s, 381.5 MB/s
bun native stdin — 10K lines 43.14ms 41.53ms +3.9% 231.82K rec/s, 68.7 MB/s
InputStream.fromFile — 100K lines 266.31ms 263.80ms +0.9% 375.51K rec/s, 111.6 MB/s
InputStream.fromString — 100K lines 222.14ms 210.77ms +5.4% 450.17K rec/s, 133.8 MB/s
manual buffer (isolated) — 100K lines 32.45ms 30.40ms +6.8% 3.08M rec/s, 915.7 MB/s
bulk text + split — 100K lines 29.38ms 27.34ms +7.5% 3.40M rec/s, 1011.4 MB/s
node readline — 100K lines 86.30ms 86.67ms -0.4% 1.16M rec/s, 344.3 MB/s
TextDecoderStream — 100K lines 37.15ms 39.80ms -6.7% 2.69M rec/s, 799.8 MB/s
binary newline scan — 100K lines 66.09ms 65.35ms +1.1% 1.51M rec/s, 449.6 MB/s
bun native stdin — 100K lines 115.71ms 113.30ms +2.1% 864.19K rec/s, 256.8 MB/s

@benbernard benbernard merged commit 161cf07 into master Feb 25, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant