Skip to content

Support reading ScriptLoader source from stdin#138

Merged
frostney merged 3 commits intomainfrom
feature/scriptloader-stdin
Mar 30, 2026
Merged

Support reading ScriptLoader source from stdin#138
frostney merged 3 commits intomainfrom
feature/scriptloader-stdin

Conversation

@frostney
Copy link
Copy Markdown
Owner

@frostney frostney commented Mar 30, 2026

Summary

  • read ScriptLoader source from stdin when no path is provided or when is used explicitly
  • support stdin across interpreted execution, bytecode execution, and bytecode emit flows
  • document the new CLI usage and cover stdin input parsing with a native regression test

Testing

  • ./build.pas loader tests
  • ./build/Goccia.ScriptLoader.Input.Test
  • printf 'const x = 2 + 2;\nx;\n' | ./build/ScriptLoader
  • printf 'const x = 2 + 2;\nx;\n' | ./build/ScriptLoader -
  • printf 'const x = 2 + 2;\nx;\n' | ./build/ScriptLoader --mode=bytecode
  • printf 'const x = 2 + 2;\nx;\n' | ./build/ScriptLoader --emit --output=/tmp/goccia-stdin-test.gbc

Closes #129

Summary by CodeRabbit

  • New Features

    • ScriptLoader and BenchmarkRunner accept stdin (pipe) as input; "-" or no path reads from stdin. CLI adds --help, treats unknown options as errors, and requires explicit --output when emitting bytecode from stdin.
  • Documentation

    • README and docs updated with stdin examples for interpreted, bytecode and benchmark usage.
  • Tests / CI

    • Added unit tests for stdin handling and CI smoke tests covering stdin for interpreter and bytecode paths.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 30, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1c88eb36-c52c-44fc-b58c-95dbde680129

📥 Commits

Reviewing files that changed from the base of the PR and between ddf87c4 and b250a78.

📒 Files selected for processing (1)
  • ScriptLoader.dpr
🚧 Files skipped from review as they are similar to previous changes (1)
  • ScriptLoader.dpr

📝 Walkthrough

Walkthrough

Added stdin support across tools: new input unit and tests; ScriptLoader and BenchmarkRunner accept and process source from stdin (or -/no-path) via in-memory parse/compile flows; CLI option handling updated; docs and CI updated with stdin examples and smoke tests.

Changes

Cohort / File(s) Summary
Stdin input unit & tests
units/Goccia.ScriptLoader.Input.pas, units/Goccia.ScriptLoader.Input.Test.pas
New unit defines STDIN_FILE_NAME, STDIN_PATH_MARKER, IsStdinPath() and ReadSourceFromText(); tests cover line preservation, empty input, and stdin marker detection.
ScriptLoader program
ScriptLoader.dpr
Refactored to operate on in-memory TStringList sources (ParseSource, CompileSource, RunSource), added RunScriptFromStdin, treat -/no-path as stdin, tightened --emit/--output error handling, and added --help/unknown-option handling.
BenchmarkRunner program
BenchmarkRunner.dpr
Added Goccia.ScriptLoader.Input use; moved TReportSpec to top-level; added Collect*FromSource helpers for interpreted/bytecode flows and RunBenchmarksFromStdin; CLI now runs benchmarks from stdin when appropriate and improved option/error handling.
Documentation
README.md, docs/build-system.md, docs/embedding.md, docs/benchmarks.md, AGENTS.md
Added examples and wording showing ScriptLoader/BenchmarkRunner accept files or stdin; updated embedding/build/benchmark docs and examples to demonstrate piping source via stdin.
CI / Workflows
.github/workflows/ci.yml, .github/workflows/pr.yml
Added example job/steps that run stdin smoke tests: pipe JS into ./build/ScriptLoader (interpreted and --mode=bytecode) and into ./build/BenchmarkRunner to produce JSON outputs and assert benchmark presence.

Sequence Diagram(s)

sequenceDiagram
    participant User as User/CLI
    participant SL as ScriptLoader / BenchmarkRunner
    participant Input as Input Unit\n(IsStdinPath / ReadSourceFromText)
    participant Parser as Parser/Compiler
    participant Engine as Engine\n(Interpreter / Bytecode backend)
    participant Output as Output\n(stdout / file)

    User->>SL: Invoke with no args or `-` (stdin)
    SL->>Input: IsStdinPath(path)
    Input-->>SL: true
    SL->>Input: ReadSourceFromText(stdin)
    Input-->>SL: TStringList (source)
    SL->>Parser: ParseSource(ASource, AFileName)
    Parser-->>SL: AST / parse result
    alt mode = bytecode
        SL->>Parser: CompileSource(ASource, AFileName)
        Parser-->>Engine: Bytecode module
        Engine-->>Output: Emit bytecode / Run module (per flags)
    else mode = interpreted
        SL->>Engine: RunInterpreted(ASource, AFileName)
        Engine-->>Output: Execution result to stdout
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I munch the bytes that piped on through,

From stdin's stream to engine new,
No temp file burrow, no dusty crate,
A hop, a parse, and code runs straight. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive The pull request introduces changes to BenchmarkRunner.dpr (stdin support, new test procedures) and documentation that extend beyond the stated objective of 'Support reading ScriptLoader source from stdin' in the title. While aligned with issue #129 scope, BenchmarkRunner modifications broaden the scope beyond ScriptLoader alone. Clarify whether BenchmarkRunner stdin support was an explicit requirement of issue #129 or if it represents scope expansion. If out of scope, consider separating into a follow-up PR for consistency with the primary objective.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and accurately summarizes the main change: adding stdin support to ScriptLoader. It is concise, specific, and aligns with the primary objective stated in issue #129.
Linked Issues check ✅ Passed The pull request successfully implements all primary coding objectives from issue #129: stdin reading when no path/'-' provided [#129], virtual '' filename for error reporting [#129], flag preservation [#129], TStringList collection approach [#129], and --emit support with stdin [#129]. Documentation and tests comprehensively cover the implementation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/scriptloader-stdin

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
units/Goccia.ScriptLoader.Input.Test.pas (1)

39-43: Add a fixture without a terminating newline.

The new CLI examples use printf "const x = 2 + 2; x;", which does not append a trailing newline. This setup writes the source with TStringList.SaveToFile, so it only exercises a newline-terminated last line and won't catch EOF-on-last-line regressions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.ScriptLoader.Input.Test.pas` around lines 39 - 43, The current
test uses TStringList.Create and SaveToFile (with TempFileName) which always
writes a trailing newline on the last line; replace that SaveToFile usage so the
fixture is written without a terminating newline — e.g. produce the file by
writing the two lines joined with sLineBreak but not ending with sLineBreak
using a raw writer (TFile.WriteAllText or similar) to TempFileName instead of
SaveToFile; update the block around TStringList.Create / Add / SaveToFile to
write "const x = 2 + 2;" + sLineBreak + "x;" directly to TempFileName so EOF has
no trailing newline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ScriptLoader.dpr`:
- Around line 409-416: When argument parsing leaves Paths.Count = 0 we must not
immediately fall back to RunScriptFromStdin; instead detect and reject
unknown/unsupported flags first. Update the argument-parsing flow so that after
parsing you track whether any unrecognized options were seen (or whether
exclusive flags like --help/--version were passed) and if so print a clear
error/help message and exit non‑zero; only call RunScriptFromStdin when no
unknown flags were present and no explicit paths were provided. Modify the
branch around Paths, RunScriptFromStdin and RunScripts to perform this
validation before defaulting to stdin.

---

Nitpick comments:
In `@units/Goccia.ScriptLoader.Input.Test.pas`:
- Around line 39-43: The current test uses TStringList.Create and SaveToFile
(with TempFileName) which always writes a trailing newline on the last line;
replace that SaveToFile usage so the fixture is written without a terminating
newline — e.g. produce the file by writing the two lines joined with sLineBreak
but not ending with sLineBreak using a raw writer (TFile.WriteAllText or
similar) to TempFileName instead of SaveToFile; update the block around
TStringList.Create / Add / SaveToFile to write "const x = 2 + 2;" + sLineBreak +
"x;" directly to TempFileName so EOF has no trailing newline.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4be2166-45cd-4e85-84c0-c5dc8a8428a6

📥 Commits

Reviewing files that changed from the base of the PR and between cf23908 and f285c72.

📒 Files selected for processing (6)
  • README.md
  • ScriptLoader.dpr
  • docs/build-system.md
  • docs/embedding.md
  • units/Goccia.ScriptLoader.Input.Test.pas
  • units/Goccia.ScriptLoader.Input.pas

Comment thread ScriptLoader.dpr
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 30, 2026

Benchmark Results

263 benchmarks

Interpreted: 🟢 47 improved · 216 unchanged · avg +4.5%
Bytecode: 🟢 14 improved · 🔴 10 regressed · 239 unchanged · avg -0.8%

arraybuffer.js — Interp: 14 unch. · avg +0.5% · Bytecode: 14 unch. · avg -0.9%
Benchmark Interpreted Δ Bytecode Δ
create ArrayBuffer(0) 460,941 → 475,967 +3.3% 495,931 → 488,941 -1.4%
create ArrayBuffer(64) 438,676 → 454,265 +3.6% 468,702 → 469,742 +0.2%
create ArrayBuffer(1024) 340,801 → 355,100 +4.2% 364,246 → 366,281 +0.6%
create ArrayBuffer(8192) 153,161 → 156,057 +1.9% 158,092 → 161,358 +2.1%
slice full buffer (64 bytes) 510,297 → 506,183 -0.8% 613,696 → 599,604 -2.3%
slice half buffer (512 of 1024 bytes) 443,995 → 428,211 -3.6% 514,002 → 516,243 +0.4%
slice with negative indices 420,545 → 418,271 -0.5% 556,195 → 553,796 -0.4%
slice empty range 492,633 → 474,396 -3.7% 586,081 → 590,685 +0.8%
byteLength access 1,373,383 → 1,395,704 +1.6% 1,617,053 → 1,553,218 -3.9%
Symbol.toStringTag access 1,058,950 → 1,077,335 +1.7% 1,086,785 → 1,030,007 -5.2%
ArrayBuffer.isView 775,339 → 755,947 -2.5% 891,671 → 868,656 -2.6%
clone ArrayBuffer(64) 419,168 → 420,389 +0.3% 463,484 → 464,317 +0.2%
clone ArrayBuffer(1024) 327,191 → 327,930 +0.2% 358,197 → 355,824 -0.7%
clone ArrayBuffer inside object 262,347 → 265,784 +1.3% 286,087 → 283,558 -0.9%
arrays.js — Interp: 🟢 1, 18 unch. · avg +2.1% · Bytecode: 🟢 3, 🔴 1, 15 unch. · avg +2.1%
Benchmark Interpreted Δ Bytecode Δ
Array.from length 100 13,491 → 13,401 -0.7% 14,184 → 14,376 +1.3%
Array.from 10 elements 214,985 → 223,490 +4.0% 241,622 → 233,878 -3.2%
Array.of 10 elements 275,483 → 282,343 +2.5% 302,710 → 298,373 -1.4%
spread into new array 322,851 → 331,540 +2.7% 196,204 → 180,473 🔴 -8.0%
map over 50 elements 24,635 → 24,865 +0.9% 27,151 → 26,855 -1.1%
filter over 50 elements 22,913 → 22,872 -0.2% 23,482 → 23,722 +1.0%
reduce sum 50 elements 26,112 → 26,677 +2.2% 16,056 → 19,692 🟢 +22.6%
forEach over 50 elements 23,121 → 23,389 +1.2% 27,528 → 28,574 +3.8%
find in 50 elements 33,145 → 32,534 -1.8% 30,392 → 30,573 +0.6%
sort 20 elements 12,449 → 12,431 -0.1% 13,457 → 13,685 +1.7%
flat nested array 109,704 → 117,819 🟢 +7.4% 127,573 → 120,374 -5.6%
flatMap 70,462 → 73,577 +4.4% 80,098 → 76,731 -4.2%
map inside map (5x5) 19,687 → 20,762 +5.5% 22,208 → 21,729 -2.2%
filter inside map (5x10) 15,317 → 15,824 +3.3% 16,265 → 16,220 -0.3%
reduce inside map (5x10) 18,791 → 19,265 +2.5% 13,585 → 16,294 🟢 +19.9%
forEach inside forEach (5x10) 16,662 → 16,970 +1.9% 14,260 → 17,243 🟢 +20.9%
find inside some (10x10) 13,505 → 13,385 -0.9% 12,942 → 12,949 +0.1%
map+filter chain nested (5x20) 5,102 → 5,189 +1.7% 5,436 → 5,411 -0.5%
reduce flatten (10x5) 37,206 → 38,416 +3.3% 17,488 → 16,657 -4.8%
async-await.js — Interp: 6 unch. · avg +4.6% · Bytecode: 6 unch. · avg -4.2%
Benchmark Interpreted Δ Bytecode Δ
single await 372,742 → 390,102 +4.7% 407,640 → 392,562 -3.7%
multiple awaits 167,530 → 174,545 +4.2% 176,902 → 172,755 -2.3%
await non-Promise value 854,598 → 886,010 +3.7% 1,102,890 → 1,035,024 -6.2%
await with try/catch 363,824 → 380,059 +4.5% 400,395 → 387,848 -3.1%
await Promise.all 49,829 → 52,279 +4.9% 50,490 → 48,162 -4.6%
nested async function call 189,559 → 200,008 +5.5% 231,699 → 219,597 -5.2%
classes.js — Interp: 🟢 1, 30 unch. · avg +4.0% · Bytecode: 🟢 1, 🔴 1, 29 unch. · avg -2.1%
Benchmark Interpreted Δ Bytecode Δ
simple class new 139,342 → 140,757 +1.0% 206,440 → 199,117 -3.5%
class with defaults 109,652 → 114,945 +4.8% 145,737 → 139,213 -4.5%
50 instances via Array.from 5,562 → 5,701 +2.5% 7,938 → 7,773 -2.1%
instance method call 70,321 → 71,588 +1.8% 94,011 → 91,812 -2.3%
static method call 113,631 → 116,434 +2.5% 186,232 → 178,822 -4.0%
single-level inheritance 55,776 → 56,369 +1.1% 74,674 → 72,720 -2.6%
two-level inheritance 48,062 → 48,420 +0.7% 60,688 → 58,797 -3.1%
private field access 71,584 → 73,680 +2.9% 94,112 → 92,362 -1.9%
private methods 78,592 → 80,310 +2.2% 101,038 → 97,823 -3.2%
getter/setter access 74,869 → 76,690 +2.4% 102,495 → 103,416 +0.9%
class decorator (identity) 92,129 → 94,990 +3.1% 100,445 → 98,277 -2.2%
class decorator (wrapping) 54,286 → 55,748 +2.7% 57,766 → 57,087 -1.2%
identity method decorator 64,211 → 66,690 +3.9% 85,710 → 87,876 +2.5%
wrapping method decorator 51,999 → 53,476 +2.8% 59,926 → 58,129 -3.0%
stacked method decorators (x3) 34,713 → 36,279 +4.5% 41,715 → 39,827 -4.5%
identity field decorator 70,189 → 73,276 +4.4% 73,754 → 70,620 -4.2%
field initializer decorator 58,673 → 60,803 +3.6% 62,262 → 60,270 -3.2%
getter decorator (identity) 63,775 → 67,914 +6.5% 75,653 → 70,741 -6.5%
setter decorator (identity) 53,636 → 56,264 +4.9% 59,111 → 58,355 -1.3%
static method decorator 69,756 → 72,640 +4.1% 81,194 → 77,465 -4.6%
static field decorator 80,826 → 83,898 +3.8% 87,932 → 83,076 -5.5%
private method decorator 52,600 → 54,832 +4.2% 68,372 → 69,393 +1.5%
private field decorator 58,469 → 60,592 +3.6% 62,322 → 60,595 -2.8%
plain auto-accessor (no decorator) 101,091 → 105,312 +4.2% 106,551 → 95,048 🔴 -10.8%
auto-accessor with decorator 53,256 → 56,517 +6.1% 54,970 → 61,202 🟢 +11.3%
decorator writing metadata 43,531 → 46,159 +6.0% 50,266 → 47,028 -6.4%
static getter read 123,808 → 133,662 🟢 +8.0% 169,328 → 172,542 +1.9%
static getter/setter pair 95,900 → 102,343 +6.7% 123,501 → 118,274 -4.2%
inherited static getter 75,569 → 80,096 +6.0% 95,570 → 98,569 +3.1%
inherited static setter 80,798 → 85,836 +6.2% 104,821 → 102,772 -2.0%
inherited static getter with this binding 69,205 → 72,691 +5.0% 90,039 → 92,027 +2.2%
closures.js — Interp: 🟢 1, 10 unch. · avg +4.4% · Bytecode: 🔴 1, 10 unch. · avg -0.9%
Benchmark Interpreted Δ Bytecode Δ
closure over single variable 133,548 → 141,911 +6.3% 277,191 → 277,976 +0.3%
closure over multiple variables 116,283 → 126,344 🟢 +8.7% 194,151 → 197,225 +1.6%
nested closures 124,452 → 131,501 +5.7% 229,882 → 225,698 -1.8%
function as argument 91,538 → 94,501 +3.2% 254,280 → 254,203 -0.0%
function returning function 116,989 → 121,844 +4.2% 282,700 → 285,148 +0.9%
compose two functions 70,113 → 72,068 +2.8% 151,223 → 151,782 +0.4%
fn.call 147,731 → 155,720 +5.4% 231,913 → 230,753 -0.5%
fn.apply 106,906 → 110,767 +3.6% 159,550 → 146,420 🔴 -8.2%
fn.bind 141,580 → 146,915 +3.8% 285,902 → 282,343 -1.2%
recursive sum to 50 11,965 → 12,233 +2.2% 23,993 → 24,215 +0.9%
recursive tree traversal 20,023 → 20,612 +2.9% 36,071 → 35,267 -2.2%
collections.js — Interp: 🟢 2, 10 unch. · avg +4.9% · Bytecode: 🟢 3, 9 unch. · avg +2.8%
Benchmark Interpreted Δ Bytecode Δ
add 50 elements 6,988 → 7,254 +3.8% 7,221 → 7,283 +0.9%
has lookup (50 elements) 89,257 → 91,190 +2.2% 99,831 → 98,791 -1.0%
delete elements 46,975 → 48,192 +2.6% 49,035 → 49,597 +1.1%
forEach iteration 14,948 → 15,849 +6.0% 12,681 → 15,033 🟢 +18.5%
spread to array 27,496 → 29,605 🟢 +7.7% 216,816 → 211,516 -2.4%
deduplicate array 36,344 → 38,488 +5.9% 70,451 → 71,090 +0.9%
set 50 entries 5,084 → 5,551 🟢 +9.2% 5,299 → 5,672 🟢 +7.0%
get lookup (50 entries) 85,887 → 89,481 +4.2% 93,754 → 91,886 -2.0%
has check 127,840 → 132,932 +4.0% 144,729 → 141,131 -2.5%
delete entries 45,662 → 47,396 +3.8% 47,428 → 47,747 +0.7%
forEach iteration 15,114 → 15,695 +3.8% 12,881 → 15,067 🟢 +17.0%
keys/values/entries 7,417 → 7,834 +5.6% 27,566 → 26,213 -4.9%
destructuring.js — Interp: 🟢 5, 17 unch. · avg +5.2% · Bytecode: 🟢 3, 🔴 1, 18 unch. · avg -0.9%
Benchmark Interpreted Δ Bytecode Δ
simple array destructuring 366,014 → 384,929 +5.2% 266,798 → 259,231 -2.8%
with rest element 239,209 → 246,627 +3.1% 204,028 → 199,078 -2.4%
with defaults 373,070 → 399,520 🟢 +7.1% 309,488 → 295,614 -4.5%
skip elements 385,015 → 407,377 +5.8% 281,898 → 271,019 -3.9%
nested array destructuring 165,270 → 172,351 +4.3% 90,160 → 84,964 -5.8%
swap variables 488,633 → 504,462 +3.2% 328,616 → 319,213 -2.9%
simple object destructuring 297,504 → 310,028 +4.2% 401,228 → 397,162 -1.0%
with defaults 329,763 → 351,242 +6.5% 563,623 → 539,544 -4.3%
with renaming 311,250 → 335,789 🟢 +7.9% 432,410 → 432,648 +0.1%
nested object destructuring 144,447 → 155,354 🟢 +7.6% 200,714 → 191,248 -4.7%
rest properties 177,696 → 191,317 🟢 +7.7% 182,029 → 182,253 +0.1%
object parameter 90,888 → 94,914 +4.4% 154,204 → 150,793 -2.2%
array parameter 116,529 → 122,984 +5.5% 109,915 → 105,786 -3.8%
mixed destructuring in map 32,821 → 33,368 +1.7% 29,934 → 35,134 🟢 +17.4%
forEach with array destructuring 60,737 → 63,653 +4.8% 41,506 → 40,407 -2.6%
map with array destructuring 59,916 → 64,429 🟢 +7.5% 41,247 → 40,107 -2.8%
filter with array destructuring 62,665 → 65,988 +5.3% 48,238 → 43,835 🔴 -9.1%
reduce with array destructuring 69,029 → 73,781 +6.9% 44,474 → 44,603 +0.3%
map with object destructuring 71,503 → 73,722 +3.1% 78,320 → 87,928 🟢 +12.3%
map with nested destructuring 60,065 → 61,535 +2.4% 87,338 → 83,333 -4.6%
map with rest in destructuring 40,139 → 42,515 +5.9% 23,025 → 22,938 -0.4%
map with defaults in destructuring 55,243 → 57,192 +3.5% 59,440 → 63,856 🟢 +7.4%
fibonacci.js — Interp: 8 unch. · avg +2.9% · Bytecode: 🔴 1, 7 unch. · avg -2.0%
Benchmark Interpreted Δ Bytecode Δ
recursive fib(15) 315 → 323 +2.6% 846 → 849 +0.4%
recursive fib(20) 29 → 29 -0.1% 76 → 76 +0.1%
recursive fib(15) typed 320 → 328 +2.3% 891 → 876 -1.7%
recursive fib(20) typed 29 → 29 +0.6% 80 → 80 +0.0%
iterative fib(20) via reduce 11,615 → 12,284 +5.8% 16,557 → 17,071 +3.1%
iterator fib(20) 9,091 → 9,511 +4.6% 18,482 → 17,484 -5.4%
iterator fib(20) via Iterator.from + take 14,714 → 15,278 +3.8% 20,763 → 19,781 -4.7%
iterator fib(20) last value via reduce 11,125 → 11,535 +3.7% 14,678 → 13,524 🔴 -7.9%
for-of.js — Interp: 🟢 7 · avg +8.7% · Bytecode: 7 unch. · avg -0.6%
Benchmark Interpreted Δ Bytecode Δ
for...of with 10-element array 43,456 → 47,602 🟢 +9.5% 165,570 → 165,616 +0.0%
for...of with 100-element array 5,000 → 5,430 🟢 +8.6% 17,595 → 17,921 +1.8%
for...of with string (10 chars) 32,204 → 35,055 🟢 +8.9% 87,390 → 87,082 -0.4%
for...of with Set (10 elements) 43,265 → 47,204 🟢 +9.1% 150,588 → 157,037 +4.3%
for...of with Map entries (10 entries) 27,939 → 30,329 🟢 +8.6% 28,324 → 27,198 -4.0%
for...of with destructuring 37,468 → 40,507 🟢 +8.1% 36,251 → 34,749 -4.1%
for-await-of with sync array 41,676 → 45,170 🟢 +8.4% 127,294 → 125,190 -1.7%
helpers/bench-module.js — Interp: 0 · Bytecode: 0
Benchmark Interpreted Δ Bytecode Δ
iterators.js — Interp: 🟢 5, 15 unch. · avg +5.7% · Bytecode: 🔴 1, 19 unch. · avg -2.0%
Benchmark Interpreted Δ Bytecode Δ
Iterator.from({next}).toArray() — 20 elements 14,588 → 15,520 +6.4% 20,343 → 18,681 🔴 -8.2%
Iterator.from({next}).toArray() — 50 elements 6,383 → 6,768 +6.0% 8,889 → 8,551 -3.8%
spread pre-wrapped iterator — 20 elements 10,757 → 11,897 🟢 +10.6% 19,662 → 18,314 -6.9%
Iterator.from({next}).forEach — 50 elements 4,386 → 4,614 +5.2% 5,085 → 5,439 +6.9%
Iterator.from({next}).reduce — 50 elements 4,457 → 4,695 +5.4% 5,129 → 4,997 -2.6%
wrap array iterator 153,764 → 167,179 🟢 +8.7% 177,396 → 176,266 -0.6%
wrap plain {next()} object 10,201 → 11,006 🟢 +7.9% 14,201 → 13,726 -3.3%
map + toArray (50 elements) 4,409 → 4,683 +6.2% 5,895 → 5,923 +0.5%
filter + toArray (50 elements) 4,288 → 4,590 🟢 +7.0% 5,778 → 5,589 -3.3%
take(10) + toArray (50 element source) 26,452 → 28,345 🟢 +7.2% 35,683 → 33,445 -6.3%
drop(40) + toArray (50 element source) 6,392 → 6,735 +5.4% 8,925 → 8,677 -2.8%
chained map + filter + take (100 element source) 7,902 → 8,226 +4.1% 10,301 → 9,764 -5.2%
some + every (50 elements) 2,504 → 2,661 +6.3% 3,438 → 3,419 -0.6%
find (50 elements) 5,424 → 5,758 +6.2% 7,021 → 6,780 -3.4%
array.values().map().filter().toArray() 7,846 → 8,000 +2.0% 9,519 → 9,548 +0.3%
array.values().take(5).toArray() 203,427 → 213,983 +5.2% 233,122 → 230,087 -1.3%
array.values().drop(45).toArray() 187,752 → 195,937 +4.4% 219,365 → 211,589 -3.5%
map.entries() chained helpers 9,660 → 9,850 +2.0% 5,847 → 5,800 -0.8%
set.values() chained helpers 16,248 → 16,897 +4.0% 19,532 → 19,689 +0.8%
string iterator map + toArray 12,971 → 13,533 +4.3% 13,823 → 14,505 +4.9%
json.js — Interp: 🟢 1, 19 unch. · avg +4.4% · Bytecode: 20 unch. · avg +0.4%
Benchmark Interpreted Δ Bytecode Δ
parse simple object 166,039 → 176,068 +6.0% 174,262 → 179,418 +3.0%
parse nested object 102,140 → 108,417 +6.1% 108,870 → 108,501 -0.3%
parse array of objects 58,901 → 63,977 🟢 +8.6% 61,076 → 64,264 +5.2%
parse large flat object 66,386 → 68,849 +3.7% 69,452 → 70,819 +2.0%
parse mixed types 75,794 → 79,731 +5.2% 78,416 → 79,056 +0.8%
stringify simple object 191,242 → 199,280 +4.2% 215,730 → 216,679 +0.4%
stringify nested object 105,000 → 111,663 +6.3% 114,781 → 112,861 -1.7%
stringify array of objects 60,285 → 62,385 +3.5% 62,744 → 63,434 +1.1%
stringify mixed types 85,300 → 89,515 +4.9% 88,832 → 88,481 -0.4%
reviver doubles numbers 47,713 → 49,007 +2.7% 53,752 → 52,275 -2.7%
reviver filters properties 40,418 → 41,165 +1.8% 43,220 → 42,465 -1.7%
reviver on nested object 51,258 → 51,904 +1.3% 55,197 → 54,415 -1.4%
reviver on array 31,615 → 33,049 +4.5% 31,171 → 32,570 +4.5%
replacer function doubles numbers 46,093 → 47,766 +3.6% 52,009 → 52,481 +0.9%
replacer function excludes properties 58,701 → 61,512 +4.8% 63,657 → 62,339 -2.1%
array replacer (allowlist) 108,686 → 114,895 +5.7% 116,253 → 118,789 +2.2%
stringify with 2-space indent 93,325 → 97,328 +4.3% 103,446 → 101,940 -1.5%
stringify with tab indent 95,465 → 97,811 +2.5% 102,399 → 102,870 +0.5%
parse then stringify 54,591 → 57,316 +5.0% 59,858 → 59,803 -0.1%
stringify then parse 33,645 → 34,895 +3.7% 36,672 → 36,395 -0.8%
jsx.jsx — Interp: 🟢 2, 19 unch. · avg +5.1% · Bytecode: 🔴 4, 17 unch. · avg -5.3%
Benchmark Interpreted Δ Bytecode Δ
simple element 205,694 → 212,397 +3.3% 343,642 → 317,989 🔴 -7.5%
self-closing element 213,942 → 222,559 +4.0% 353,831 → 339,248 -4.1%
element with string attribute 168,319 → 178,109 +5.8% 264,417 → 241,209 🔴 -8.8%
element with multiple attributes 150,805 → 157,765 +4.6% 203,139 → 196,718 -3.2%
element with expression attribute 156,864 → 168,069 🟢 +7.1% 263,450 → 241,037 🔴 -8.5%
text child 205,289 → 213,487 +4.0% 325,773 → 312,323 -4.1%
expression child 198,789 → 210,069 +5.7% 342,691 → 315,501 🔴 -7.9%
mixed text and expression 187,434 → 198,316 +5.8% 293,787 → 284,242 -3.2%
nested elements (3 levels) 76,802 → 79,760 +3.9% 124,856 → 116,461 -6.7%
sibling children 57,425 → 59,907 +4.3% 87,499 → 83,676 -4.4%
component element 142,098 → 151,063 +6.3% 232,923 → 216,624 -7.0%
component with children 88,778 → 93,062 +4.8% 138,516 → 130,387 -5.9%
dotted component 119,804 → 127,254 +6.2% 177,898 → 171,554 -3.6%
empty fragment 214,444 → 220,751 +2.9% 370,780 → 348,630 -6.0%
fragment with children 56,993 → 59,578 +4.5% 91,710 → 87,846 -4.2%
spread attributes 104,647 → 112,205 🟢 +7.2% 132,465 → 126,826 -4.3%
spread with overrides 91,442 → 95,572 +4.5% 118,247 → 114,929 -2.8%
shorthand props 152,893 → 161,831 +5.8% 237,292 → 226,108 -4.7%
nav bar structure 26,189 → 27,998 +6.9% 40,637 → 38,519 -5.2%
card component tree 30,935 → 32,197 +4.1% 45,123 → 43,036 -4.6%
10 list items via Array.from 13,839 → 14,656 +5.9% 19,357 → 18,506 -4.4%
modules.js — Interp: 9 unch. · avg +0.7% · Bytecode: 9 unch. · avg +2.1%
Benchmark Interpreted Δ Bytecode Δ
call imported function 464,381 → 479,176 +3.2% 633,078 → 669,863 +5.8%
call two imported functions 260,650 → 267,281 +2.5% 360,261 → 367,635 +2.0%
read imported constant 1,658,124 → 1,677,642 +1.2% 3,649,685 → 3,546,700 -2.8%
read imported string 1,660,082 → 1,660,298 +0.0% 3,621,450 → 3,621,405 -0.0%
read JSON string property 1,676,841 → 1,672,593 -0.3% 3,629,054 → 3,709,278 +2.2%
read JSON number property 1,648,678 → 1,627,482 -1.3% 3,616,919 → 3,660,199 +1.2%
read JSON boolean property 1,668,628 → 1,674,827 +0.4% 3,555,509 → 3,622,079 +1.9%
read JSON array property 1,657,885 → 1,666,110 +0.5% 3,546,051 → 3,718,797 +4.9%
read multiple JSON properties 947,909 → 944,678 -0.3% 3,194,278 → 3,319,671 +3.9%
numbers.js — Interp: 🟢 3, 8 unch. · avg +5.9% · Bytecode: 11 unch. · avg +0.5%
Benchmark Interpreted Δ Bytecode Δ
integer arithmetic 502,885 → 522,900 +4.0% 1,456,212 → 1,515,396 +4.1%
floating point arithmetic 579,536 → 620,998 🟢 +7.2% 945,920 → 969,925 +2.5%
number coercion 178,304 → 188,892 +5.9% 235,844 → 224,693 -4.7%
toFixed 99,458 → 103,998 +4.6% 110,863 → 111,370 +0.5%
toString 150,794 → 160,155 +6.2% 182,808 → 184,394 +0.9%
valueOf 221,634 → 240,281 🟢 +8.4% 260,666 → 265,122 +1.7%
toPrecision 141,238 → 148,857 +5.4% 164,957 → 163,361 -1.0%
Number.isNaN 315,858 → 334,046 +5.8% 340,023 → 341,793 +0.5%
Number.isFinite 309,679 → 329,845 +6.5% 324,616 → 323,816 -0.2%
Number.isInteger 319,143 → 342,846 🟢 +7.4% 345,748 → 345,745 -0.0%
Number.parseInt and parseFloat 248,754 → 256,833 +3.2% 262,008 → 264,070 +0.8%
objects.js — Interp: 🟢 2, 5 unch. · avg +5.5% · Bytecode: 7 unch. · avg -0.9%
Benchmark Interpreted Δ Bytecode Δ
create simple object 456,850 → 483,270 +5.8% 580,784 → 565,688 -2.6%
create nested object 218,686 → 236,428 🟢 +8.1% 249,296 → 250,547 +0.5%
create 50 objects via Array.from 8,803 → 9,275 +5.4% 9,816 → 9,783 -0.3%
property read 542,708 → 573,785 +5.7% 427,376 → 443,676 +3.8%
Object.keys 270,021 → 282,363 +4.6% 300,881 → 302,652 +0.6%
Object.entries 99,432 → 101,089 +1.7% 107,094 → 103,416 -3.4%
spread operator 169,210 → 181,418 🟢 +7.2% 208,794 → 198,969 -4.7%
promises.js — Interp: 🟢 6, 6 unch. · avg +7.0% · Bytecode: 12 unch. · avg -2.8%
Benchmark Interpreted Δ Bytecode Δ
Promise.resolve(value) 528,220 → 555,732 +5.2% 570,312 → 538,244 -5.6%
new Promise(resolve => resolve(value)) 181,819 → 191,020 +5.1% 223,033 → 215,001 -3.6%
Promise.reject(reason) 531,483 → 566,057 +6.5% 559,418 → 529,143 -5.4%
resolve + then (1 handler) 169,489 → 180,239 +6.3% 197,428 → 192,764 -2.4%
resolve + then chain (3 deep) 66,284 → 71,997 🟢 +8.6% 75,956 → 76,458 +0.7%
resolve + then chain (10 deep) 21,338 → 23,192 🟢 +8.7% 23,444 → 24,122 +2.9%
reject + catch + then 96,104 → 101,306 +5.4% 111,509 → 108,440 -2.8%
resolve + finally + then 82,229 → 85,580 +4.1% 91,036 → 88,941 -2.3%
Promise.all (5 resolved) 29,931 → 32,300 🟢 +7.9% 33,157 → 31,404 -5.3%
Promise.race (5 resolved) 31,833 → 34,334 🟢 +7.9% 34,410 → 33,154 -3.7%
Promise.allSettled (5 mixed) 25,148 → 27,288 🟢 +8.5% 27,069 → 26,807 -1.0%
Promise.any (5 mixed) 29,745 → 32,801 🟢 +10.3% 32,791 → 31,010 -5.4%
strings.js — Interp: 🟢 2, 9 unch. · avg +4.9% · Bytecode: 11 unch. · avg -1.3%
Benchmark Interpreted Δ Bytecode Δ
string concatenation 424,231 → 423,545 -0.2% 312,204 → 318,782 +2.1%
template literal 620,903 → 692,675 🟢 +11.6% 652,069 → 670,298 +2.8%
string repeat 394,169 → 406,833 +3.2% 462,802 → 448,921 -3.0%
split and join 132,757 → 139,840 +5.3% 156,701 → 154,909 -1.1%
indexOf and includes 162,234 → 170,307 +5.0% 192,158 → 187,199 -2.6%
toUpperCase and toLowerCase 245,867 → 265,942 🟢 +8.2% 334,120 → 324,950 -2.7%
slice and substring 150,016 → 156,724 +4.5% 197,358 → 195,118 -1.1%
trim operations 185,684 → 194,404 +4.7% 243,704 → 237,495 -2.5%
replace and replaceAll 206,727 → 213,771 +3.4% 240,202 → 236,154 -1.7%
startsWith and endsWith 131,971 → 138,141 +4.7% 159,326 → 155,804 -2.2%
padStart and padEnd 189,141 → 196,048 +3.7% 230,666 → 226,524 -1.8%
typed-arrays.js — Interp: 🟢 9, 13 unch. · avg +5.2% · Bytecode: 🟢 4, 18 unch. · avg +2.4%
Benchmark Interpreted Δ Bytecode Δ
new Int32Array(0) 327,452 → 336,169 +2.7% 364,303 → 355,533 -2.4%
new Int32Array(100) 297,472 → 305,445 +2.7% 329,045 → 329,173 +0.0%
new Int32Array(1000) 177,496 → 186,932 +5.3% 196,587 → 196,467 -0.1%
new Float64Array(100) 265,195 → 287,131 🟢 +8.3% 305,726 → 303,286 -0.8%
Int32Array.from([...]) 175,065 → 183,769 +5.0% 191,198 → 188,271 -1.5%
Int32Array.of(1, 2, 3, 4, 5) 292,592 → 314,180 🟢 +7.4% 355,822 → 347,423 -2.4%
sequential write 100 elements 3,343 → 3,614 🟢 +8.1% 15,106 → 16,856 🟢 +11.6%
sequential read 100 elements 3,388 → 3,716 🟢 +9.7% 10,517 → 11,180 +6.3%
Float64Array write 100 elements 3,108 → 3,364 🟢 +8.2% 10,821 → 11,700 🟢 +8.1%
fill(42) 43,916 → 45,725 +4.1% 44,603 → 45,949 +3.0%
slice() 196,584 → 203,671 +3.6% 220,649 → 219,050 -0.7%
map(x => x * 2) 7,553 → 8,087 🟢 +7.1% 8,645 → 8,732 +1.0%
filter(x => x > 50) 7,804 → 8,363 🟢 +7.2% 9,546 → 9,444 -1.1%
reduce (sum) 7,624 → 7,923 +3.9% 6,362 → 7,522 🟢 +18.2%
sort() 164,971 → 168,180 +1.9% 181,147 → 178,693 -1.4%
indexOf() 419,068 → 417,640 -0.3% 485,535 → 492,378 +1.4%
reverse() 319,863 → 326,313 +2.0% 356,545 → 356,508 -0.0%
create view over existing buffer 394,227 → 391,959 -0.6% 444,280 → 431,793 -2.8%
subarray() 408,053 → 415,035 +1.7% 517,670 → 521,207 +0.7%
set() from array 545,389 → 570,298 +4.6% 646,635 → 646,547 -0.0%
for-of loop 4,708 → 5,150 🟢 +9.4% 17,578 → 19,417 🟢 +10.5%
spread into array 15,730 → 17,530 🟢 +11.4% 95,027 → 100,646 +5.9%

Measured on ubuntu-latest x64. Changes within ±7% are considered insignificant.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 30, 2026

Suite Timing

Suite Metric Interpreted Bytecode
Tests Total 3523 3523
Tests Passed 3482 ✅ 3523 ✅
Tests Skipped 41 0
Tests Execution 158.1ms 140.4ms
Tests Engine 317.3ms 515.9ms
Benchmarks Total 263 263
Benchmarks Duration 7.27min 5.98min

Measured on ubuntu-latest x64.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
.github/workflows/ci.yml (1)

332-346: Please cover the remaining stdin paths in CI.

These smoke tests verify interpreted and bytecode execution, but the new Goccia.ScriptLoader.Input.Test binary is still only built—not run—and stdin --emit --output=... is the one new ScriptLoader flow still missing automated coverage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 332 - 346, CI lacks execution of the
new Goccia.ScriptLoader.Input.Test binary and the ScriptLoader stdin flow that
uses --emit --output; update the workflow to run the built
Goccia.ScriptLoader.Input.Test binary (e.g.,
./build/Goccia.ScriptLoader.Input.Test) to exercise its stdin path, and add a
smoke step that pipes a small script into ./build/ScriptLoader using the --emit
--output=... flag (pointing at a temp file), then assert the emitted output file
exists and contains expected content (or that subsequent bytecode run produces
"Result: 4"); reference Goccia.ScriptLoader.Input.Test and the ScriptLoader
--emit --output stdin flow when adding these checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ScriptLoader.dpr`:
- Around line 282-289: RunScriptFromFile is unconditionally loading every
AFileName as text which corrupts .gbc bytecode; before creating/LoadFromFile,
detect the file extension of AFileName (case-insensitive) and if it's ".gbc"
call RunBytecodeFromFile(AFileName) and return; only for non-.gbc paths continue
to create the TStringList, LoadFromFile and call RunSource(Source, AFileName).
Ensure you perform the extension check early in RunScriptFromFile so bytecode
files never go through TStringList.LoadFromFile.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 332-346: CI lacks execution of the new
Goccia.ScriptLoader.Input.Test binary and the ScriptLoader stdin flow that uses
--emit --output; update the workflow to run the built
Goccia.ScriptLoader.Input.Test binary (e.g.,
./build/Goccia.ScriptLoader.Input.Test) to exercise its stdin path, and add a
smoke step that pipes a small script into ./build/ScriptLoader using the --emit
--output=... flag (pointing at a temp file), then assert the emitted output file
exists and contains expected content (or that subsequent bytecode run produces
"Result: 4"); reference Goccia.ScriptLoader.Input.Test and the ScriptLoader
--emit --output stdin flow when adding these checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 842e4df1-2a90-4a20-9c24-af2a3d96fb3c

📥 Commits

Reviewing files that changed from the base of the PR and between f285c72 and ddf87c4.

📒 Files selected for processing (10)
  • .github/workflows/ci.yml
  • .github/workflows/pr.yml
  • AGENTS.md
  • BenchmarkRunner.dpr
  • README.md
  • ScriptLoader.dpr
  • docs/benchmarks.md
  • docs/build-system.md
  • docs/embedding.md
  • units/Goccia.ScriptLoader.Input.Test.pas
✅ Files skipped from review due to trivial changes (4)
  • docs/benchmarks.md
  • README.md
  • .github/workflows/pr.yml
  • docs/build-system.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/embedding.md

Comment thread ScriptLoader.dpr
@frostney frostney merged commit 1a91048 into main Mar 30, 2026
9 checks passed
@frostney frostney deleted the feature/scriptloader-stdin branch March 30, 2026 10:00
@frostney frostney added the new feature New feature or request label Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support reading source from stdin in ScriptLoader

1 participant