Skip to content

feat: {{}} compound assignments and polyglot support#95

Merged
benbernard merged 2 commits intomasterfrom
feature/template-compound-and-polyglot
Feb 24, 2026
Merged

feat: {{}} compound assignments and polyglot support#95
benbernard merged 2 commits intomasterfrom
feature/template-compound-and-polyglot

Conversation

@benbernard
Copy link
Owner

Summary

  • Fix {{field}} += val compound assignments in JS (previously produced invalid code like __get(r, "field") += val)
  • Add {{}} template expansion to Python and Perl snippet runners, so the same {{field}} syntax works across all three languages
  • Add __get/__set helpers to Python runner.py and Perl runner.pl
  • Add recordVar parameter to transformCode() to support Perl's $r variable

Test plan

  • bun test tests/Executor.test.ts — 43 tests including 25 new compound/recordVar tests
  • bun test tests/snippets/TemplateExpansion.test.ts — 36 cross-language tests (12 cases × 3 languages)
  • bun test — full suite passes (1595 pass, 0 fail)
  • Manual: echo '{"x":5}' | recs eval '{{x}} += 10'{"x":15} (JS, Python, Perl)

🤖 Generated with Claude Code

benbernard and others added 2 commits February 24, 2026 13:19
- Add compound assignment regex to transformCode() (+=, -=, *=, etc.)
  so {{field}} += val expands to __set(r, "field", __get(r, "field") + val)
- Add recordVar parameter to transformCode() for language-specific
  variable names ("r" for JS/Python, "$r" for Perl)
- Apply transformCode() in Python/Perl snippet runners so {{}} templates
  work across all three languages
- Add __get/__set helpers to Python runner.py and Perl runner.pl
- Add cross-language TemplateExpansion test suite verifying identical
  behavior for reads, writes, compound assigns, and nested keys

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

Performance Benchmark Results

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

Benchmark Median Baseline Delta
Compiled KeySpec.resolveValue — nested (address/zip) 310.0µs 174.2µs +78.0% 🔴
Compiled KeySpec.resolveValue — deep (address/coords/lat) 237.7µs 122.5µs +93.9% 🔴
Compiled KeySpec.resolveValue — array (tags/#0) 241.2µs 153.6µs +57.1% 🔴
chain — 5 ops (grep eval grep eval
implicit — 2 ops (grep eval), 100 records 115.0µs 73.7µs
chain — 2 ops (grep eval), 1K records 328.1µs 197.4µs
implicit — 3 ops (grep eval grep), 1K records 303.1µs
implicit — 5 ops (grep eval grep eval
implicit — 5 ops (grep eval grep eval
manual buffer (isolated) — 100 lines 264.5µs 178.3µs +48.4% 🔴
binary newline scan — 100 lines 376.9µs 289.8µs +30.0% 🔴

103 benchmarks: 15 faster, 16 slower, 72 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 151.6µs 153.3µs -1.1% 659.49K rec/s
Record.fromJSON — 10K lines 14.16ms 13.12ms +8.0% 706.12K rec/s, 209.1 MB/s
InputStream.fromString — 100 records 211.2µs 206.3µs +2.4% 473.53K rec/s
InputStream.fromString — 10K records 18.39ms 18.53ms -0.8% 543.75K rec/s, 161.0 MB/s
JSON.parse baseline — 10K lines (no Record) 13.42ms 12.76ms +5.2% 745.21K rec/s, 220.7 MB/s
JSON.parse single array — 10K records 12.90ms 12.55ms +2.8% 775.21K rec/s, 229.6 MB/s

JSON Serialization

Benchmark Median Baseline Delta Throughput
Record.toString — 100 records 83.3µs 71.3µs +16.7% 🔴 1.20M rec/s
Record.toString — 10K records 8.52ms 8.98ms -5.1% 1.17M rec/s, 347.6 MB/s
Record.toJSON — 10K records 294.7µs 298.7µs -1.3% 33.93M rec/s
JSON.stringify baseline — 10K objects (no Record) 7.88ms 7.94ms -0.8% 1.27M rec/s, 376.0 MB/s
Batch join — 10K records (map+join) 8.59ms 8.41ms +2.1% 1.16M rec/s, 344.7 MB/s

KeySpec Access

Benchmark Median Baseline Delta Throughput
KeySpec — simple key (name) 228.7µs 214.4µs +6.7% 43.73M rec/s
KeySpec — nested key (address/zip) 566.0µs 822.2µs -31.2% 🟢 17.67M rec/s
KeySpec — deep nested (address/coords/lat) 1.09ms 1.09ms -0.6% 9.20M rec/s
KeySpec — array index (tags/#0) 865.3µs 884.1µs -2.1% 11.56M rec/s
Direct property access baseline (rec['name']) 76.0µs 88.2µs -13.9% 🟢 131.64M rec/s
Direct nested access baseline (rec.address.coords.lat) 109.4µs 110.5µs -1.0% 91.41M rec/s
KeySpec construction — cached (same spec 10K times) 411.6µs 481.3µs -14.5% 🟢 24.30M rec/s
KeySpec construction — unique specs (10K different) 2.61ms 2.81ms -7.0% 3.83M rec/s
Compiled KeySpec.resolveValue — nested (address/zip) 310.0µs 174.2µs +78.0% 🔴 32.26M rec/s
Compiled KeySpec.resolveValue — deep (address/coords/lat) 237.7µs 122.5µs +93.9% 🔴 42.08M rec/s
Compiled KeySpec.resolveValue — array (tags/#0) 241.2µs 153.6µs +57.1% 🔴 41.46M rec/s
Compiled KeySpec.setValue — nested (address/zip) 146.7µs 201.3µs -27.1% 🟢 68.14M rec/s

Core Operations

Benchmark Median Baseline Delta Throughput
grep — 10K records (r.age > 50) 491.2µs 442.0µs +11.1% 🔴 20.36M rec/s
grep — 10K records (string match) 467.2µs 435.7µs +7.2% 21.41M rec/s
eval — 10K records (add computed field) 2.45ms 2.46ms -0.2% 4.08M rec/s
xform — 10K records (push each record) 2.13ms 2.15ms -0.9% 4.70M rec/s
sort — 100 records (by score, numeric) 154.5µs 146.9µs +5.2% 647.07K rec/s
sort — 10K records (by score, numeric) 17.85ms 19.51ms -8.5% 560.21K rec/s
sort — 10K records (by name, lexical) 12.52ms 12.66ms -1.2% 798.87K rec/s
collate — 100 records (count by city) 305.8µs 399.6µs -23.5% 🟢 327.05K rec/s
collate — 10K records (count by city) 14.89ms 12.42ms +19.9% 🔴 671.65K rec/s
fromcsv — 10K rows (parse CSV to records) 14.19ms 14.52ms -2.3% 704.65K rec/s, 46.3 MB/s

Pipeline Overhead

Benchmark Median Baseline Delta Throughput
chain — single op (grep), 10K records 7.24ms 7.05ms +2.6% 1.38M rec/s
chain — 3 ops (grep eval grep), 10K records 7.44ms 7.34ms
chain — 5 ops (grep eval grep eval grep), 10K records
passthrough baseline — 10K records (direct collector) 6.08ms 6.33ms -3.9% 1.64M rec/s

Record Creation & Serialization

Benchmark Median Baseline Delta Throughput
new Record() — 10K objects 95.5µs 100.0µs -4.5% 104.68M rec/s
new Record() empty — 10K 127.5µs 134.6µs -5.3% 78.44M rec/s
Record.get — 10K records × 3 fields 93.0µs 99.4µs -6.5% 322.55M rec/s
Record.set — 10K records × 1 field 125.5µs 124.1µs +1.2% 79.66M rec/s
Record.toJSON — 10K records 287.2µs 298.7µs -3.9% 34.82M rec/s
Record.toString — 10K records 6.77ms 8.98ms -24.6% 🟢 1.48M rec/s
Record.clone — 10K records 6.17ms 6.60ms -6.5% 1.62M rec/s
Record.fromJSON — 10K lines 13.05ms 13.12ms -0.5% 766.12K rec/s, 226.9 MB/s
Record.dataRef — 10K records (zero-copy) 35.2µs 86.2µs -59.2% 🟢 284.25M rec/s
Record.sort — 10K records (numeric field) 11.76ms 11.64ms +1.0% 850.42K rec/s
Record.sort — 10K records (lexical field) 6.06ms 6.11ms -0.9% 1.65M rec/s
Record.cmp — 1M comparisons (single field) 111.23ms 110.37ms +0.8% 8.99M rec/s
Record.sort — 10K records (nested field numeric) 15.65ms 16.06ms -2.6% 638.86K rec/s
Record.cmp — 1M comparisons (multi-field cached) 85.60ms 87.19ms -1.8% 11.68M rec/s
Record.sort — 10K records (cached comparator reuse) 11.64ms 12.12ms -3.9% 859.14K rec/s

Chain vs Pipe

Benchmark Median Baseline Delta Throughput
chain — 2 ops (grep eval), 100 records 151.6µs 137.7µs +10.1% 🔴
pipe — 2 ops (grep eval), 100 records 420.89ms 388.66ms +8.3%
implicit — 2 ops (grep eval), 100 records 115.0µs 73.7µs +56.2% 🔴
chain — 2 ops (grep eval), 1K records 328.1µs 197.4µs +66.2% 🔴
pipe — 2 ops (grep eval), 1K records 381.75ms 380.07ms +0.4%
implicit — 2 ops (grep eval), 1K records 203.0µs 208.2µs -2.5%
chain — 2 ops (grep eval), 10K records 1.11ms 1.38ms -19.7% 🟢
pipe — 2 ops (grep eval), 10K records 376.25ms 375.39ms +0.2%
implicit — 2 ops (grep eval), 10K records 1.14ms 1.10ms +3.5%
chain — 3 ops (grep eval grep), 100 records 103.7µs 171.1µs
pipe — 3 ops (grep eval grep), 100 records 580.40ms 572.39ms
implicit — 3 ops (grep eval grep), 100 records 116.3µs 112.2µs
chain — 3 ops (grep eval grep), 1K records 221.7µs 212.4µs
pipe — 3 ops (grep eval grep), 1K records 584.05ms 566.66ms
implicit — 3 ops (grep eval grep), 1K records 303.1µs 210.3µs
chain — 3 ops (grep eval grep), 10K records 1.18ms 1.08ms
pipe — 3 ops (grep eval grep), 10K records 577.80ms 576.72ms
implicit — 3 ops (grep eval grep), 10K records 1.41ms 1.37ms
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 501.4µs 713.4µs -29.7% 🟢 199.45K rec/s, 58.9 MB/s
InputStream.fromString — 100 lines 190.1µs 335.0µs -43.2% 🟢 525.90K rec/s, 155.3 MB/s
manual buffer (isolated) — 100 lines 264.5µs 178.3µs +48.4% 🔴 378.14K rec/s, 111.7 MB/s
bulk text + split — 100 lines 69.3µs 70.3µs -1.4% 1.44M rec/s, 425.9 MB/s
node readline — 100 lines 459.1µs 430.0µs +6.8% 217.82K rec/s, 64.3 MB/s
TextDecoderStream — 100 lines 275.4µs 312.7µs -11.9% 🟢 363.07K rec/s, 107.2 MB/s
binary newline scan — 100 lines 376.9µs 289.8µs +30.0% 🔴 265.35K rec/s, 78.4 MB/s
bun native stdin — 100 lines 26.04ms 25.85ms +0.7% 3.84K rec/s, 1.1 MB/s
InputStream.fromFile — 10K lines 24.45ms 24.10ms +1.5% 408.98K rec/s, 121.1 MB/s
InputStream.fromString — 10K lines 17.90ms 17.97ms -0.4% 558.80K rec/s, 165.5 MB/s
manual buffer (isolated) — 10K lines 6.50ms 5.85ms +11.1% 🔴 1.54M rec/s, 455.9 MB/s
bulk text + split — 10K lines 2.40ms 2.45ms -1.8% 4.16M rec/s, 1232.6 MB/s
node readline — 10K lines 9.39ms 9.33ms +0.7% 1.06M rec/s, 315.3 MB/s
TextDecoderStream — 10K lines 4.67ms 4.72ms -0.9% 2.14M rec/s, 633.7 MB/s
binary newline scan — 10K lines 7.40ms 9.90ms -25.2% 🟢 1.35M rec/s, 400.1 MB/s
bun native stdin — 10K lines 42.76ms 42.60ms +0.4% 233.88K rec/s, 69.3 MB/s
InputStream.fromFile — 100K lines 263.53ms 263.84ms -0.1% 379.46K rec/s, 112.8 MB/s
InputStream.fromString — 100K lines 227.57ms 215.43ms +5.6% 439.42K rec/s, 130.6 MB/s
manual buffer (isolated) — 100K lines 29.52ms 30.50ms -3.2% 3.39M rec/s, 1006.6 MB/s
bulk text + split — 100K lines 32.06ms 30.82ms +4.0% 3.12M rec/s, 926.8 MB/s
node readline — 100K lines 92.20ms 89.11ms +3.5% 1.08M rec/s, 322.3 MB/s
TextDecoderStream — 100K lines 40.06ms 46.11ms -13.1% 🟢 2.50M rec/s, 741.8 MB/s
binary newline scan — 100K lines 66.61ms 86.48ms -23.0% 🟢 1.50M rec/s, 446.1 MB/s
bun native stdin — 100K lines 116.65ms 118.83ms -1.8% 857.28K rec/s, 254.7 MB/s

@benbernard benbernard merged commit ff9d030 into master Feb 24, 2026
4 checks passed
@benbernard benbernard deleted the feature/template-compound-and-polyglot branch February 25, 2026 17:54
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