Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4fae529
Microtask draining
frostney Feb 26, 2026
57fb64d
Compare string values
frostney Feb 26, 2026
076b407
Fixes parser warnings
frostney Feb 26, 2026
aef1e7e
Remove `CloneWithScope` and unify timing output
frostney Feb 27, 2026
b48c463
Move global properties tests into their correct place
frostney Feb 27, 2026
1a7cd29
Constant folding and compound assignment
frostney Feb 27, 2026
bf7bbf7
Renaming
frostney Feb 27, 2026
f6ec747
Update compound types
frostney Feb 27, 2026
7c52d6d
Add metatable in VM
frostney Feb 27, 2026
92e30a1
Switch statement
frostney Feb 27, 2026
8a2ba04
Fixes throw-case errors
frostney Feb 27, 2026
312ac43
Fix throw handling
frostney Feb 27, 2026
1778b5d
JSX support and parameter handling for rest
frostney Feb 28, 2026
97868ae
Restructure value system and naming
frostney Mar 1, 2026
35bee0e
Rename on opcodes
frostney Mar 1, 2026
57bba94
Fixes destructuring and iterables
frostney Mar 1, 2026
610a0f7
Module support
frostney Mar 1, 2026
be43c57
for of operator
frostney Mar 2, 2026
513a337
Fixes async test failures in bytecode mode
frostney Mar 2, 2026
4e00012
Fixes for async and try/catch/finally
frostney Mar 2, 2026
5589d73
Fixes null/undefined cast
frostney Mar 2, 2026
04653ea
Fixes optional chaining
frostney Mar 2, 2026
e53cd25
Better boundaries
frostney Mar 3, 2026
c15f76e
Opcode cleanup + documentation
frostney Mar 3, 2026
6658111
Native delegate const sets and arithmetic function updates
frostney Mar 3, 2026
e588f73
Fix holey array tests
frostney Mar 3, 2026
2cd5feb
Feature parity
frostney Mar 3, 2026
bf74f4b
Various fixes and documentation updates
frostney Mar 3, 2026
cc3d7a4
Update documentation
frostney Mar 3, 2026
4a4bb5a
Fixes
frostney Mar 4, 2026
ba15a85
Fixes and Documentation Update
frostney Mar 5, 2026
65214a5
Various fixes
frostney Mar 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 39 additions & 13 deletions AGENTS.md

Large diffs are not rendered by default.

27 changes: 22 additions & 5 deletions BenchmarkRunner.dpr
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ uses
Goccia.Engine,
Goccia.Engine.Backend,
Goccia.FileExtensions,
Goccia.JSX.SourceMap,
Goccia.JSX.Transformer,
Goccia.Lexer,
Goccia.Parser,
Goccia.Token,
Expand Down Expand Up @@ -125,6 +127,7 @@ begin
FileResult.FileName := AFileName;
FileResult.LexTimeNanoseconds := 0;
FileResult.ParseTimeNanoseconds := 0;
FileResult.CompileTimeNanoseconds := 0;
FileResult.ExecuteTimeNanoseconds := 0;
FileResult.TotalBenchmarks := 0;
FileResult.DurationNanoseconds := 0;
Expand Down Expand Up @@ -170,6 +173,7 @@ begin
FileResult.FileName := AFileName;
FileResult.LexTimeNanoseconds := EngineResult.LexTimeNanoseconds;
FileResult.ParseTimeNanoseconds := EngineResult.ParseTimeNanoseconds;
FileResult.CompileTimeNanoseconds := 0;
FileResult.ExecuteTimeNanoseconds := EngineResult.ExecuteTimeNanoseconds;

ScriptResult := nil;
Expand All @@ -190,6 +194,8 @@ function CollectBenchmarkFileBytecode(const AFileName: string;
const AReporter: TBenchmarkReporter): TGocciaObjectValue;
var
Source: TStringList;
SourceText: string;
JSXResult: TGocciaJSXTransformResult;
Lexer: TGocciaLexer;
Tokens: TObjectList<TGocciaToken>;
Parser: TGocciaParser;
Expand All @@ -200,7 +206,7 @@ var
FileResult: TBenchmarkFileResult;
ScriptResult: TGocciaObjectValue;
BenchGlobals: TGocciaGlobalBuiltins;
StartTime, EndTime: Int64;
CompileStart, CompileEnd, ExecEnd: Int64;
begin
BenchGlobals := TGocciaEngine.DefaultGlobals + [ggBenchmark];

Expand All @@ -209,14 +215,22 @@ begin
Source.LoadFromFile(AFileName);
Source.Add('runBenchmarks();');

SourceText := Source.Text;
if ggJSX in BenchGlobals then
begin
JSXResult := TGocciaJSXTransformer.Transform(SourceText);
SourceText := JSXResult.Source;
JSXResult.SourceMap.Free;
end;

try
StartTime := GetNanoseconds;
CompileStart := GetNanoseconds;

Backend := TGocciaSouffleBackend.Create(AFileName);
try
Backend.RegisterBuiltIns(BenchGlobals);

Lexer := TGocciaLexer.Create(Source.Text, AFileName);
Lexer := TGocciaLexer.Create(SourceText, AFileName);
try
Tokens := Lexer.ScanTokens;
Parser := TGocciaParser.Create(Tokens, AFileName, Lexer.SourceLines);
Expand All @@ -234,14 +248,17 @@ begin
Lexer.Free;
end;

CompileEnd := GetNanoseconds;

try
ResultValue := Backend.RunModule(Module);
EndTime := GetNanoseconds;
ExecEnd := GetNanoseconds;

FileResult.FileName := AFileName;
FileResult.LexTimeNanoseconds := 0;
FileResult.ParseTimeNanoseconds := 0;
FileResult.ExecuteTimeNanoseconds := EndTime - StartTime;
FileResult.CompileTimeNanoseconds := CompileEnd - CompileStart;
FileResult.ExecuteTimeNanoseconds := ExecEnd - CompileEnd;

ScriptResult := nil;
if ResultValue is TGocciaObjectValue then
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ console.log(`Your order total: $${total.toFixed(2)}`);

### Run via Souffle VM (Bytecode)

GocciaScript includes an alternative bytecode execution backend — the **Souffle VM** — a general-purpose register-based virtual machine. The bytecode backend supports variables, closures, classes, property access, and invocation. Iteration, modules, and async/await are not yet implemented (see [Known Limitations](docs/souffle-vm.md#known-limitations)).
GocciaScript includes an alternative bytecode execution backend — the **Souffle VM** — a general-purpose register-based virtual machine that routes language features through a bridge layer to the GocciaScript evaluator. This approach passes 100% of the test suite (3,358 tests). Known structural limitations: `.sbc` files use native endianness (not yet cross-platform portable); the ABC-encoded instruction format limits constant pool references to 255 per prototype. See [Souffle VM Architecture](docs/souffle-vm.md) for full details.

```bash
# Compile and execute via Souffle VM
Expand Down Expand Up @@ -232,7 +232,7 @@ flowchart LR
|-----------|------|------|
| Compiler | `Goccia.Compiler.pas` | AST → Souffle bytecode |
| VM | `Souffle.VM.pas` | Register-based dispatch with two-tier ISA |
| Runtime Ops | `Goccia.Runtime.Operations.pas` | GocciaScript semantics for Tier 2 opcodes |
| Runtime Ops | `Goccia.Runtime.Operations.pas` | GocciaScript semantics, bridge caches, array sync, native delegates |
| Backend | `Goccia.Engine.Backend.pas` | Orchestration, built-in bridging |
| Binary I/O | `Souffle.Bytecode.Binary.pas` | `.sbc` file serialization/deserialization |

Expand Down
55 changes: 44 additions & 11 deletions ScriptLoader.dpr
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ uses
Goccia.Engine.Backend,
Goccia.FileExtensions,
Goccia.GarbageCollector,
Goccia.JSX.SourceMap,
Goccia.JSX.Transformer,
Goccia.Lexer,
Goccia.Parser,
Goccia.Token,
Expand All @@ -32,39 +34,70 @@ var
GEmitPath: string = '';
GEmitOnly: Boolean = False;

function ParseSourceFile(const AFileName: string): TGocciaProgram;
function ParseSourceFile(const AFileName: string; const AGlobals: TGocciaGlobalBuiltins): TGocciaProgram;
var
Source: TStringList;
SourceText: string;
JSXResult: TGocciaJSXTransformResult;
SourceMap: TGocciaSourceMap;
Lexer: TGocciaLexer;
Tokens: TObjectList<TGocciaToken>;
Parser: TGocciaParser;
Warning: TGocciaParserWarning;
OrigLine, OrigCol, I: Integer;
begin
Source := TStringList.Create;
try
Source.LoadFromFile(AFileName);
Lexer := TGocciaLexer.Create(Source.Text, AFileName);
SourceText := Source.Text;

SourceMap := nil;
if ggJSX in AGlobals then
begin
JSXResult := TGocciaJSXTransformer.Transform(SourceText);
SourceText := JSXResult.Source;
SourceMap := JSXResult.SourceMap;
end;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

try
Tokens := Lexer.ScanTokens;
Parser := TGocciaParser.Create(Tokens, AFileName, Lexer.SourceLines);
Lexer := TGocciaLexer.Create(SourceText, AFileName);
try
Result := Parser.Parse;
Tokens := Lexer.ScanTokens;
Parser := TGocciaParser.Create(Tokens, AFileName, Lexer.SourceLines);
try
Result := Parser.Parse;
for I := 0 to Parser.WarningCount - 1 do
begin
Warning := Parser.GetWarning(I);
WriteLn(SysUtils.Format('Warning: %s', [Warning.Message]));
if Warning.Suggestion <> '' then
WriteLn(SysUtils.Format(' Suggestion: %s', [Warning.Suggestion]));
if Assigned(SourceMap) and SourceMap.Translate(Warning.Line, Warning.Column, OrigLine, OrigCol) then
WriteLn(SysUtils.Format(' --> %s:%d:%d', [AFileName, OrigLine, OrigCol]))
else
WriteLn(SysUtils.Format(' --> %s:%d:%d', [AFileName, Warning.Line, Warning.Column]));
end;
finally
Parser.Free;
end;
finally
Parser.Free;
Lexer.Free;
end;
finally
Lexer.Free;
SourceMap.Free;
end;
finally
Source.Free;
end;
end;

function CompileSourceFile(const AFileName: string): TSouffleBytecodeModule;
function CompileSourceFile(const AFileName: string;
const AGlobals: TGocciaGlobalBuiltins): TSouffleBytecodeModule;
var
ProgramNode: TGocciaProgram;
Compiler: TGocciaCompiler;
begin
ProgramNode := ParseSourceFile(AFileName);
ProgramNode := ParseSourceFile(AFileName, AGlobals);
try
Compiler := TGocciaCompiler.Create(AFileName);
try
Expand Down Expand Up @@ -104,7 +137,7 @@ begin
try
Backend.RegisterBuiltIns(TGocciaEngine.DefaultGlobals);

ProgramNode := ParseSourceFile(AFileName);
ProgramNode := ParseSourceFile(AFileName, TGocciaEngine.DefaultGlobals);
try
Module := Backend.CompileToModule(ProgramNode);
finally
Expand Down Expand Up @@ -168,7 +201,7 @@ begin

TGocciaGarbageCollector.Initialize;
try
Module := CompileSourceFile(AFileName);
Module := CompileSourceFile(AFileName, TGocciaEngine.DefaultGlobals);
try
SaveModuleToFile(Module, AOutputPath);
EndTime := GetNanoseconds;
Expand Down
65 changes: 54 additions & 11 deletions TestRunner.dpr
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ uses
Goccia.Engine,
Goccia.Engine.Backend,
Goccia.FileExtensions,
Goccia.JSX.SourceMap,
Goccia.JSX.Transformer,
Goccia.Lexer,
Goccia.Parser,
Goccia.Token,
Expand Down Expand Up @@ -154,16 +156,21 @@ end;
function RunGocciaScriptBytecode(const AFileName: string): TTestFileResult;
var
Source: TStringList;
SourceText: string;
JSXResult: TGocciaJSXTransformResult;
SourceMap: TGocciaSourceMap;
Lexer: TGocciaLexer;
Tokens: TObjectList<TGocciaToken>;
Parser: TGocciaParser;
Warning: TGocciaParserWarning;
ProgramNode: TGocciaProgram;
Module: TSouffleBytecodeModule;
Backend: TGocciaSouffleBackend;
ScriptResult: TGocciaObjectValue;
ResultValue: TGocciaValue;
TestGlobals: TGocciaGlobalBuiltins;
StartTime, EndTime: Int64;
OrigLine, OrigCol, I: Integer;
CompileStart, CompileEnd, ExecEnd: Int64;
begin
TestGlobals := TGocciaEngine.DefaultGlobals + [ggTestAssertions];
ScriptResult := CreateDefaultScriptResult;
Expand All @@ -185,19 +192,40 @@ begin
Source.Add(Format('runTests({ exitOnFirstFailure: %s, showTestResults: false });',
[BoolToStr(GExitOnFirstFailure, 'true', 'false')]));

try
StartTime := GetNanoseconds;
SourceText := Source.Text;
SourceMap := nil;
if ggJSX in TestGlobals then
begin
JSXResult := TGocciaJSXTransformer.Transform(SourceText);
SourceText := JSXResult.Source;
SourceMap := JSXResult.SourceMap;
end;

try try
CompileStart := GetNanoseconds;

Backend := TGocciaSouffleBackend.Create(AFileName);
try
Backend.RegisterBuiltIns(TestGlobals);

Lexer := TGocciaLexer.Create(Source.Text, AFileName);
Lexer := TGocciaLexer.Create(SourceText, AFileName);
try
Tokens := Lexer.ScanTokens;
Parser := TGocciaParser.Create(Tokens, AFileName, Lexer.SourceLines);
try
ProgramNode := Parser.Parse;
if not GSilentConsole then
for I := 0 to Parser.WarningCount - 1 do
begin
Warning := Parser.GetWarning(I);
WriteLn(Format('Warning: %s', [Warning.Message]));
if Warning.Suggestion <> '' then
WriteLn(Format(' Suggestion: %s', [Warning.Suggestion]));
if Assigned(SourceMap) and SourceMap.Translate(Warning.Line, Warning.Column, OrigLine, OrigCol) then
WriteLn(Format(' --> %s:%d:%d', [AFileName, OrigLine, OrigCol]))
else
WriteLn(Format(' --> %s:%d:%d', [AFileName, Warning.Line, Warning.Column]));
end;
try
Module := Backend.CompileToModule(ProgramNode);
finally
Expand All @@ -210,19 +238,21 @@ begin
Lexer.Free;
end;

CompileEnd := GetNanoseconds;

try
ResultValue := Backend.RunModule(Module);
EndTime := GetNanoseconds;
ExecEnd := GetNanoseconds;

if ResultValue is TGocciaObjectValue then
MergeFileResult(ScriptResult, TGocciaObjectValue(ResultValue));

Result.TestResult := ScriptResult;
Result.Timing.Result := ResultValue;
Result.Timing.LexTimeNanoseconds := 0;
Result.Timing.LexTimeNanoseconds := CompileEnd - CompileStart;
Result.Timing.ParseTimeNanoseconds := 0;
Result.Timing.ExecuteTimeNanoseconds := EndTime - StartTime;
Result.Timing.TotalTimeNanoseconds := EndTime - StartTime;
Result.Timing.ExecuteTimeNanoseconds := ExecEnd - CompileEnd;
Result.Timing.TotalTimeNanoseconds := ExecEnd - CompileStart;
Result.Timing.FileName := AFileName;
finally
Module.Free;
Expand All @@ -238,6 +268,9 @@ begin
Result := MakeEmptyTestResult(ScriptResult);
end;
end;
finally
SourceMap.Free;
end;
finally
Source.Free;
end;
Expand All @@ -256,6 +289,7 @@ type
TestResult: TGocciaObjectValue;
TotalLexNanoseconds: Int64;
TotalParseNanoseconds: Int64;
TotalCompileNanoseconds: Int64;
TotalExecNanoseconds: Int64;
Comment on lines +292 to 293
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

TotalCompileNanoseconds is introduced but never populated from per-file timing.

The aggregate compile counter stays 0, and bytecode reporting still reads compile time from TotalLexNanoseconds. This makes the new compile metric inconsistent.

🔧 Suggested fix
 function RunScriptFromFile(const AFileName: string): TAggregatedTestResult;
 begin
@@
   Result.TotalLexNanoseconds := FileResult.Timing.LexTimeNanoseconds;
   Result.TotalParseNanoseconds := FileResult.Timing.ParseTimeNanoseconds;
+  Result.TotalCompileNanoseconds := FileResult.Timing.LexTimeNanoseconds;
   Result.TotalExecNanoseconds := FileResult.Timing.ExecuteTimeNanoseconds;
@@
       if GMode = ebSouffleVM then
         Writeln(Format('Test Results Engine Timing: Compile: %s | Execute: %s | Total: %s',
-          [FormatDuration(AResult.TotalLexNanoseconds), FormatDuration(AResult.TotalExecNanoseconds),
-           FormatDuration(AResult.TotalLexNanoseconds + AResult.TotalExecNanoseconds)]))
+          [FormatDuration(AResult.TotalCompileNanoseconds), FormatDuration(AResult.TotalExecNanoseconds),
+           FormatDuration(AResult.TotalCompileNanoseconds + AResult.TotalExecNanoseconds)]))

Also applies to: 303-311, 432-435

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

In `@TestRunner.dpr` around lines 292 - 293, The new aggregate fields
TotalCompileNanoseconds and TotalExecNanoseconds are declared but never
accumulated, so reporting still reads compile time from TotalLexNanoseconds;
update the code paths that process per-file timings (where per-file compile and
exec durations are computed) to add those durations into TotalCompileNanoseconds
and TotalExecNanoseconds respectively, and change any bytecode/reporting logic
that currently reads TotalLexNanoseconds to use TotalCompileNanoseconds for
compile-time metrics; reference the fields TotalCompileNanoseconds,
TotalExecNanoseconds and TotalLexNanoseconds and the per-file timing
accumulation sections around the compile/exec loop to locate where to add the
increments and adjust the reporting.

end;

Expand All @@ -266,12 +300,14 @@ begin
Result.TestResult := nil;
Result.TotalLexNanoseconds := 0;
Result.TotalParseNanoseconds := 0;
Result.TotalCompileNanoseconds := 0;
Result.TotalExecNanoseconds := 0;
try
FileResult := RunGocciaScript(AFileName);
Result.TestResult := FileResult.TestResult;
Result.TotalLexNanoseconds := FileResult.Timing.LexTimeNanoseconds;
Result.TotalParseNanoseconds := FileResult.Timing.ParseTimeNanoseconds;
Result.TotalCompileNanoseconds := FileResult.Timing.LexTimeNanoseconds;
Result.TotalExecNanoseconds := FileResult.Timing.ExecuteTimeNanoseconds;
except
on E: Exception do
Expand Down Expand Up @@ -311,6 +347,7 @@ begin
TotalDuration := 0;
Result.TotalLexNanoseconds := 0;
Result.TotalParseNanoseconds := 0;
Result.TotalCompileNanoseconds := 0;
Result.TotalExecNanoseconds := 0;

for I := 0 to AFiles.Count - 1 do
Expand All @@ -325,6 +362,7 @@ begin

Result.TotalLexNanoseconds := Result.TotalLexNanoseconds + FileResult.TotalLexNanoseconds;
Result.TotalParseNanoseconds := Result.TotalParseNanoseconds + FileResult.TotalParseNanoseconds;
Result.TotalCompileNanoseconds := Result.TotalCompileNanoseconds + FileResult.TotalCompileNanoseconds;
Result.TotalExecNanoseconds := Result.TotalExecNanoseconds + FileResult.TotalExecNanoseconds;

PassedCount := PassedCount + FileResult.TestResult.GetProperty('passed').ToNumberLiteral.Value;
Expand Down Expand Up @@ -392,9 +430,14 @@ begin
Writeln(Format('Test Results Skipped: %s (%2.2f%%)', [TotalSkipped, (StrToFloat(TotalSkipped) / RunCount * 100)]));
Writeln(Format('Test Results Assertions: %s', [TotalAssertions]));
Writeln(Format('Test Results Test Execution: %s (%s/test)', [FormatDuration(DurationNanoseconds), FormatDuration(PerTestNanoseconds)]));
Writeln(Format('Test Results Engine Timing: Lex: %s | Parse: %s | Execute: %s | Total: %s',
[FormatDuration(AResult.TotalLexNanoseconds), FormatDuration(AResult.TotalParseNanoseconds), FormatDuration(AResult.TotalExecNanoseconds),
FormatDuration(AResult.TotalLexNanoseconds + AResult.TotalParseNanoseconds + AResult.TotalExecNanoseconds)]));
if GMode = ebSouffleVM then
Writeln(Format('Test Results Engine Timing: Compile: %s | Execute: %s | Total: %s',
[FormatDuration(AResult.TotalCompileNanoseconds), FormatDuration(AResult.TotalExecNanoseconds),
FormatDuration(AResult.TotalCompileNanoseconds + AResult.TotalExecNanoseconds)]))
else
Writeln(Format('Test Results Engine Timing: Lex: %s | Parse: %s | Execute: %s | Total: %s',
[FormatDuration(AResult.TotalLexNanoseconds), FormatDuration(AResult.TotalParseNanoseconds), FormatDuration(AResult.TotalExecNanoseconds),
FormatDuration(AResult.TotalLexNanoseconds + AResult.TotalParseNanoseconds + AResult.TotalExecNanoseconds)]));
Writeln(Format('Test Results Failed Tests: %s', [TestResult.GetProperty('failedTests').ToStringLiteral.Value]));
end;
end;
Expand Down
Loading