diff --git a/AGENTS.md b/AGENTS.md index 12e1369f..d87b5309 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -127,7 +127,7 @@ See [docs/architecture.md](docs/architecture.md) for the full architecture deep- | Constants: Error Names | `Goccia.Constants.ErrorNames.pas` | Error type name constants (`'TypeError'`, `'RangeError'`, etc.) | | Constants: Constructor Names | `Goccia.Constants.ConstructorNames.pas` | Built-in constructor name constants (`'Object'`, `'Array'`, etc.) | | ToPrimitive | `Goccia.Values.ToPrimitive.pas` | ECMAScript `ToPrimitive` abstract operation | -| Error Helper | `Goccia.Values.ErrorHelper.pas` | `ThrowTypeError`, `ThrowRangeError`, `ThrowDataCloneError` (creates DOMException with code 25), centralized error construction with proper prototype chain | +| Error Helper | `Goccia.Values.ErrorHelper.pas` | `ThrowTypeError`, `ThrowRangeError`, `ThrowReferenceError`, `ThrowSyntaxError`, `ThrowError`, `ThrowDataCloneError` (creates DOMException with code 25), centralized error construction with proper prototype chain. All runtime errors must go through these helpers (not `TGocciaError.Create`) so the resulting exception is a `TGocciaThrowValue` with a proper JS Error object, catchable from JavaScript `try...catch`. | | Argument Validator | `Goccia.Arguments.Validator.pas` | `RequireExactly`, `RequireAtLeast` — standardized argument count/type validation | | Argument Callbacks | `Goccia.Arguments.Callbacks.pas` | Pre-typed callback argument collections for array prototype methods | | Ordered Map | `OrderedMap.pas` | Generic insertion-order-preserving string-keyed map (`TOrderedMap`) | @@ -138,29 +138,39 @@ See [docs/architecture.md](docs/architecture.md) for the full architecture deep- | Souffle VM | `Souffle.VM.pas` | Register-based bytecode VM with two-tier dispatch (Tier 1 intrinsic + Tier 2 runtime) | | Souffle Value | `Souffle.Value.pas` | `TSouffleValue` tagged union: Nil, Boolean, Integer, Float, Reference | | Souffle Bytecode | `Souffle.Bytecode.pas` | Opcode definitions (Tier 1 + Tier 2), 32-bit instruction encoding/decoding | -| Souffle Bytecode Chunk | `Souffle.Bytecode.Chunk.pas` | `TSouffleFunctionPrototype` — code, constants, upvalue descriptors, exception handlers | -| Souffle Bytecode Module | `Souffle.Bytecode.Module.pas` | `TSouffleBytecodeModule` — top-level prototype, imports, exports, runtime tag | +| Souffle Bytecode Chunk | `Souffle.Bytecode.Chunk.pas` | `TSouffleFunctionTemplate` — code, constants, upvalue descriptors, exception handlers | +| Souffle Bytecode Module | `Souffle.Bytecode.Module.pas` | `TSouffleBytecodeModule` — top-level function template, imports, exports, runtime tag | | Souffle Bytecode Binary | `Souffle.Bytecode.Binary.pas` | `.sbc` file serialization/deserialization | | Souffle Debug Info | `Souffle.Bytecode.Debug.pas` | Source line mapping, local variable info for debuggers | | Souffle Closure | `Souffle.VM.Closure.pas` | `TSouffleClosure` — function prototype + captured upvalue array | | Souffle Upvalue | `Souffle.VM.Upvalue.pas` | `TSouffleUpvalue` — open (register pointer) or closed (captured value) | | Souffle Call Frame | `Souffle.VM.CallFrame.pas` | `TSouffleVMCallFrame`, `TSouffleCallStack` | | Souffle Exception | `Souffle.VM.Exception.pas` | `TSouffleHandlerStack`, `ESouffleThrow` — handler-table exception model | -| Souffle Runtime Ops | `Souffle.VM.RuntimeOperations.pas` | `TSouffleRuntimeOperations` — abstract interface for language-specific semantics | +| Souffle Runtime Ops | `Souffle.VM.RuntimeOperations.pas` | `TSouffleRuntimeOperations` — 45-method abstract interface for language-specific semantics, plus `ExtendedOperation` for sub-opcode dispatch | | Souffle GC | `Souffle.GarbageCollector.pas` | Mark-and-sweep GC for `TSouffleHeapObject` instances | | Souffle Heap | `Souffle.Heap.pas` | `TSouffleHeapObject` base class, `TSouffleString`, heap kind constants | | GocciaScript Backend | `Goccia.Engine.Backend.pas` | `TGocciaSouffleBackend` — bridges GocciaScript engine to Souffle VM | -| GocciaScript Compiler | `Goccia.Compiler.pas` | `TGocciaCompiler` — AST → Souffle bytecode compilation | -| GocciaScript Runtime | `Goccia.Runtime.Operations.pas` | `TGocciaRuntimeOperations` — GocciaScript semantics for Souffle VM | -| Compiler Scope | `Goccia.Compiler.Scope.pas` | `TGocciaCompilerScope` — lexical scope tracking, local/upvalue resolution for the bytecode compiler | +| GocciaScript Compiler | `Goccia.Compiler.pas` | `TGocciaCompiler` — AST → Souffle bytecode compilation, top-level dispatch | +| Compiler Expressions | `Goccia.Compiler.Expressions.pas` | Expression compilation: functions, methods, identifiers, typed local load/store | +| Compiler Statements | `Goccia.Compiler.Statements.pas` | Statement compilation: variables, classes (`IsSimpleClass` + `CompileClassDeclaration`), control flow | +| Compiler Context | `Goccia.Compiler.Context.pas` | `TGocciaCompilationContext` — compilation state passed through sub-units | +| Compiler Scope | `Goccia.Compiler.Scope.pas` | `TGocciaCompilerScope` — lexical scope tracking, local/upvalue resolution, type hints (`TSouffleLocalType`) | +| Compiler Constant Folding | `Goccia.Compiler.ConstantFolding.pas` | Compile-time constant folding for arithmetic and comparison expressions | +| Compiler Extension Ops | `Goccia.Compiler.ExtOps.pas` | GocciaScript-specific sub-opcode constants (`GOCCIA_EXT_*`) for `OP_RT_EXT` dispatch | +| GocciaScript Runtime | `Goccia.Runtime.Operations.pas` | `TGocciaRuntimeOperations` — GocciaScript semantics for Souffle VM, bridge caches (`FClosureBridgeCache`, `FArrayBridgeCache`, `FArrayBridgeReverse`, `FRecordBridgeCache`), cross-GC rooting, array bridge sync (`SyncCachedGocciaToSouffle` at bridge entry, `SyncSouffleArrayToCache` after native mutations), native array delegate methods | -**Souffle VM known limitations:** Iteration (`GetIterator`, `IteratorNext`, `SpreadInto`), module imports (`ImportModule`), and async/await (`AwaitValue`) are currently stubbed in `TGocciaRuntimeOperations`. Closure receiver binding is accepted but not stored into the frame. `.sbc` binary format uses native endianness (not yet portable). ABC-encoded instructions limit constant pool references to 255 per prototype. See [docs/souffle-vm.md § Known Limitations](docs/souffle-vm.md#known-limitations) for the full list. +**Souffle VM known limitations:** The bytecode backend passes 100% of the test suite (3,358 tests). All language features work through a bridge layer that delegates to the GocciaScript evaluator for module imports, async/await, iteration, and built-in method calls. Bridged arrays use a dual-representation model with `SyncCachedGocciaToSouffle` at bridge entry and `SyncSouffleArrayToCache` after native mutations; `FArrayBridgeReverse` preserves reference identity across round-trips. Complex classes and classes extending built-in constructors (`Array`, `Map`, `Set`, `Promise`, `Object`) are deferred to the interpreter via `FPendingClasses` / `GOCCIA_EXT_EVAL_CLASS`. `.sbc` binary format uses native endianness (not yet portable). ABC-encoded instructions limit constant pool references to 255 per prototype. See [docs/souffle-vm.md § Known Limitations](docs/souffle-vm.md#known-limitations) for the full list. **Souffle VM design rules:** -- The `souffle/` directory must not import `Goccia.*` units — all GocciaScript dependencies live in the bridge files (`Goccia.Engine.Backend.pas`, `Goccia.Runtime.Operations.pas`, `Goccia.Compiler.pas`). +- The `souffle/` directory must not import `Goccia.*` units — all GocciaScript dependencies live in the bridge files (`Goccia.Engine.Backend.pas`, `Goccia.Runtime.Operations.pas`, `Goccia.Compiler.pas`, and `Goccia.Compiler.*.pas` sub-units). +- **Uniform receiver handling** — Register 0 always holds the receiver (`SouffleNil` for non-methods). There is no `AIsMethodCall` flag or side-table. The compiler reserves slot 0 for the receiver in every function. +- **Typed local variables** — The compiler infers `TSouffleLocalType` hints (`sltUntyped`, `sltInteger`, `sltFloat`, `sltBoolean`, `sltString`, `sltReference`) from literal initializers and stores them on `TSouffleFunctionTemplate`. Typed load/store opcodes (`OP_GET_LOCAL_INT`, `OP_SET_LOCAL_FLOAT`, etc.) are emitted for known-type locals. At runtime these are functionally identical to generic `OP_GET_LOCAL`/`OP_SET_LOCAL` but carry type information for future WASM/JIT use. +- **Simple class compilation** — Classes with only constructors and named methods are compiled to VM blueprint opcodes (`OP_NEW_BLUEPRINT`, `OP_INHERIT`, `OP_RECORD_SET`, `OP_INSTANTIATE`). Complex classes (getters, setters, statics, private members, decorators) are deferred to the interpreter via `FPendingClasses`. `OP_RECORD_SET` is overloaded: when the target is a `TSouffleBlueprint`, it stores into `Blueprint.Methods`. - NaN handling in the Souffle layer uses raw IEEE 754 bit-pattern checks (`FloatBitsAreNaN`), not FPC's `Math.IsNaN`, to avoid language-runtime dependencies and AArch64 pitfalls. -- New Tier 2 opcodes should only be added when no combination of existing opcodes can express the semantics efficiently. Language-specific features should be desugared by the compiler. -- The `TSouffleRuntimeOperations` abstract class defines the contract between the VM and any language frontend. GocciaScript's `TGocciaRuntimeOperations` is one implementation; future frontends provide their own. +- New Tier 2 opcodes should only be added when no combination of existing opcodes can express the semantics efficiently. Language-specific features should use `OP_RT_EXT` with sub-opcode IDs defined in the language's extension constants unit (e.g., `Goccia.Compiler.ExtOps.pas`). +- The `TSouffleRuntimeOperations` abstract class defines the contract between the VM and any language frontend (45 generic methods + 1 `ExtendedOperation` for sub-opcode dispatch). GocciaScript's `TGocciaRuntimeOperations` is one implementation; future frontends provide their own. +- **Spread calling** uses the C flags byte on `OP_RT_CALL` / `OP_RT_CALL_METHOD` (bit 0 = spread mode, B = args array register). No separate spread opcodes. +- **Per-property flags** (writable, configurable) on `TSouffleRecordEntry.Flags` are the fundamental primitive. `SetEntryFlags`, `PutWithFlags`, and `PreventExtensions` are the building blocks. Bulk operations like `Freeze` (set all flags to 0 + prevent extensions) are derived convenience methods called from language runtimes — not opcodes. ## Development Workflow @@ -397,6 +407,22 @@ Dictionary-based string interning (`TDictionary; Parser: TGocciaParser; @@ -200,7 +206,7 @@ var FileResult: TBenchmarkFileResult; ScriptResult: TGocciaObjectValue; BenchGlobals: TGocciaGlobalBuiltins; - StartTime, EndTime: Int64; + CompileStart, CompileEnd, ExecEnd: Int64; begin BenchGlobals := TGocciaEngine.DefaultGlobals + [ggBenchmark]; @@ -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); @@ -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 diff --git a/README.md b/README.md index 3ecff8a0..1631150e 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 | diff --git a/ScriptLoader.dpr b/ScriptLoader.dpr index 1019572a..0047e05f 100644 --- a/ScriptLoader.dpr +++ b/ScriptLoader.dpr @@ -17,6 +17,8 @@ uses Goccia.Engine.Backend, Goccia.FileExtensions, Goccia.GarbageCollector, + Goccia.JSX.SourceMap, + Goccia.JSX.Transformer, Goccia.Lexer, Goccia.Parser, Goccia.Token, @@ -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; 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; + 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 @@ -104,7 +137,7 @@ begin try Backend.RegisterBuiltIns(TGocciaEngine.DefaultGlobals); - ProgramNode := ParseSourceFile(AFileName); + ProgramNode := ParseSourceFile(AFileName, TGocciaEngine.DefaultGlobals); try Module := Backend.CompileToModule(ProgramNode); finally @@ -168,7 +201,7 @@ begin TGocciaGarbageCollector.Initialize; try - Module := CompileSourceFile(AFileName); + Module := CompileSourceFile(AFileName, TGocciaEngine.DefaultGlobals); try SaveModuleToFile(Module, AOutputPath); EndTime := GetNanoseconds; diff --git a/TestRunner.dpr b/TestRunner.dpr index ad87c7a7..e773f8af 100644 --- a/TestRunner.dpr +++ b/TestRunner.dpr @@ -16,6 +16,8 @@ uses Goccia.Engine, Goccia.Engine.Backend, Goccia.FileExtensions, + Goccia.JSX.SourceMap, + Goccia.JSX.Transformer, Goccia.Lexer, Goccia.Parser, Goccia.Token, @@ -154,16 +156,21 @@ end; function RunGocciaScriptBytecode(const AFileName: string): TTestFileResult; var Source: TStringList; + SourceText: string; + JSXResult: TGocciaJSXTransformResult; + SourceMap: TGocciaSourceMap; Lexer: TGocciaLexer; Tokens: TObjectList; 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; @@ -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 @@ -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; @@ -238,6 +268,9 @@ begin Result := MakeEmptyTestResult(ScriptResult); end; end; + finally + SourceMap.Free; + end; finally Source.Free; end; @@ -256,6 +289,7 @@ type TestResult: TGocciaObjectValue; TotalLexNanoseconds: Int64; TotalParseNanoseconds: Int64; + TotalCompileNanoseconds: Int64; TotalExecNanoseconds: Int64; end; @@ -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 @@ -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 @@ -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; @@ -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; diff --git a/docs/code-style.md b/docs/code-style.md index de3cc009..9d28e625 100644 --- a/docs/code-style.md +++ b/docs/code-style.md @@ -286,6 +286,28 @@ Result := FEpochMilliseconds * 1000000.0; This affects any code that converts `Int64` fields to `Double` for floating-point arithmetic. The same issue applies to intermediate `Int64` local variables cast to `Double`. +### Endian-Dependent Byte Indexing + +Do **not** inspect raw byte arrays of `Double` values to check the sign bit (e.g., `Bytes[7] and $80`). This assumes little-endian byte layout and breaks on big-endian platforms. + +Instead, overlay the `Double` with `Int64 absolute` and test via integer sign: + +```pascal +// WRONG — assumes little-endian byte order +var V: Double; Bytes: array[0..7] of Byte absolute V; +begin + Result := (V = 0.0) and ((Bytes[7] and $80) <> 0); +end; + +// CORRECT — endian-neutral sign bit check +var V: Double; Bits: Int64 absolute V; +begin + Result := (V = 0.0) and (Bits < 0); +end; +``` + +This works because `Int64` and `Double` share the same sign bit position (bit 63) at the integer level, regardless of byte ordering. + ## Design Patterns ### Singleton Pattern (Special Values) diff --git a/docs/design-decisions.md b/docs/design-decisions.md index 4503b169..cf39a83a 100644 --- a/docs/design-decisions.md +++ b/docs/design-decisions.md @@ -18,7 +18,7 @@ State changes (variable bindings, object mutations) happen through the scope and **Performance-aware evaluation:** Template literal evaluation and `Array.ToStringLiteral` use `TStringBuilder` for O(n) string assembly instead of O(n^2) repeated concatenation. `Boolean.ToNumberLiteral` returns the existing `ZeroValue`/`OneValue` singletons rather than allocating, avoiding an allocation on every boolean-to-number coercion. `Function.prototype.apply` uses a fast path for `TGocciaArrayValue` arguments (direct `Elements[I]` access) instead of per-element `IntToStr` + `GetProperty`. Numeric binary operations that share a common pattern (subtraction, multiplication, exponentiation) are consolidated through `EvaluateSimpleNumericBinaryOp` to avoid code duplication while maintaining clear semantics. -**IEEE-754 correctness:** Arithmetic operations handle special number values (`NaN`, `Infinity`, `-Infinity`, `-0`) via the `TGocciaNumberSpecialValue` enum rather than the stored `Double` (which is `0.0` for all special values). Division uses explicit `IsNegativeZero` checks to compute correct signed results (e.g., `1 / -Infinity` → `-0`, `-1 / 0` → `-Infinity`). Exponentiation uses an `IsActualZero` guard to distinguish true zero exponents from special values with `Value = 0.0`, and checks `RightNum.IsInfinite` before `RightNum.Value = 0` to correctly handle infinite exponents. The sort comparator (`CallCompareFunc`) maps infinite comparison results to `±1` to avoid passing `0.0` (the internal value of `Infinity`) to the quicksort partitioning logic. +**IEEE-754 correctness:** Arithmetic operations handle special number values (`NaN`, `Infinity`, `-Infinity`, `-0`) via the `TGocciaNumberSpecialValue` enum rather than the stored `Double` (which is `0.0` for all special values). Division uses explicit `IsNegativeZero` checks to compute correct signed results (e.g., `1 / -Infinity` → `-0`, `-1 / 0` → `-Infinity`). Exponentiation uses an `IsActualZero` guard to distinguish true zero exponents from special values with `Value = 0.0`, and checks `RightNum.IsInfinite` before `RightNum.Value = 0` to correctly handle infinite exponents. The sort comparator (`CallCompareFunc`) maps infinite comparison results to `±1` to avoid passing `0.0` (the internal value of `Infinity`) to the quicksort partitioning logic. Negative zero detection (`IsNegativeZero`) uses an endian-neutral `Int64 absolute` overlay to check the sign bit (`Bits < 0`) instead of byte-indexed checks (`Bytes[7] and $80`) that assume little-endian layout. ## Virtual Dispatch Value System @@ -361,7 +361,7 @@ String interning (caching `TGocciaStringLiteralValue` instances in a `TDictionar ## Souffle VM: Two-Tier ISA with Pluggable Runtime -GocciaScript includes an alternative bytecode execution backend — the **Souffle VM** — designed as a general-purpose virtual machine that can support multiple language frontends. See [souffle-vm.md](souffle-vm.md) for the full architecture. +GocciaScript includes an alternative bytecode execution backend — the **Souffle VM** — designed as a standalone, language-agnostic virtual machine that can support multiple language frontends. The Souffle VM is intended to be extractable from the GocciaScript repository as an independent project. See [souffle-vm.md](souffle-vm.md) for the full architecture. ### Why a Bytecode VM? @@ -377,43 +377,79 @@ Opcodes like "get property", "typeof", and "instanceof" have fundamentally diffe The solution is a split opcode space: -- **Tier 1 (0–127):** VM-intrinsic operations with fixed, universal semantics (register moves, control flow, closures, exceptions). Implemented directly in the dispatch loop. -- **Tier 2 (128–255):** Runtime operations dispatched through `TSouffleRuntimeOperations`, an abstract class each language provides. The VM calls `RuntimeOps.GetProperty(obj, key)` without knowing what "get property" means. +- **Tier 1 (0–127):** VM-intrinsic operations with fixed, universal semantics (register moves, control flow, closures, arrays, records, blueprints, exceptions). Implemented directly in the dispatch loop. The litmus test: *"Does this operation have one correct implementation regardless of language?"* +- **Tier 2 (128–255):** Runtime operations dispatched through `TSouffleRuntimeOperations`, an abstract class each language provides. The VM calls `RuntimeOps.GetProperty(obj, key)` without knowing what "get property" means. The litmus test: *"Does this operation's behavior depend on the language?"* -Adding a new language frontend requires implementing `TSouffleRuntimeOperations` — zero VM changes. +Adding a new language frontend requires implementing `TSouffleRuntimeOperations` — zero VM changes. Currently, the abstract interface has 45 generic methods + 1 `ExtendedOperation` entry point for language-specific sub-opcodes. ### Why a Self-Contained Value System? -The Souffle VM has its own `TSouffleValue` tagged union (16 bytes: 1-byte kind tag + 8-byte data) with only 5 value kinds: Nil, Boolean, Integer, Float, Reference. This is independent of `TGocciaValue`. +The Souffle VM has its own `TSouffleValue` tagged union (26 bytes: kind + flags + variant data) with 6 value kinds: Nil, Boolean, Integer, Float, String (inline ≤23 chars), Reference. This is independent of `TGocciaValue`. The separation exists because: - **Language-agnostic** — The VM doesn't know what "a string" or "an object" is. Those are all `svkReference` (pointer to a `TSouffleHeapObject`). The heap object's type tag is interpreted by the runtime, not the VM. - **No GocciaScript dependency** — The VM can be used without any GocciaScript code, making it viable for other frontends. -- **Inline primitives** — Booleans, integers, and floats live in the register file with zero heap allocation. Only complex types go through the heap. -- **WASM 3.0 mapping** — The 5 kinds map naturally to WASM's `anyref` hierarchy: Nil → `ref.null`, small integers/booleans → `i31ref`, floats → boxed `f64` GC structs, references → GC struct subtypes. - -Bridge conversions between `TSouffleValue` and `TGocciaValue` happen at the runtime operations boundary. `TGocciaWrappedValue` wraps a `TGocciaValue` as a `TSouffleHeapObject` for VM register storage; `TGocciaSouffleClosureBridge` wraps a `TSouffleClosure` as a `TGocciaFunctionBase` for GocciaScript callback compatibility. +- **Inline primitives** — Booleans, integers, floats, and short strings (≤23 chars) live in the register file with zero heap allocation. Only complex types and long strings go through the heap. +- **Opaque flags** — `svkNil` carries a `Flags` byte the VM stores and compares but never interprets. GocciaScript uses it to distinguish `undefined` (flags=0) from `null` (flags=1). Other languages may use it differently or not at all. +- **WASM 3.0 mapping** — The 6 kinds map naturally to WASM's `anyref` hierarchy: Nil → `ref.null`, small integers/booleans → `i31ref`, floats → boxed `f64` GC structs, references → GC struct subtypes. ### Compiler-Side Desugaring Language-specific features are compiled into sequences of generic VM instructions rather than adding specialized opcodes: -- **Classes** — No `CLASS` opcode. The compiler emits `OP_CLOSURE` (constructor/methods) + `OP_RT_NEW_COMPOUND` (prototype) + `OP_RT_SET_PROP` (method registration). -- **Nullish coalescing (`??`)** — No `IS_NULLISH` opcode. The compiler emits `OP_JUMP_IF_NOT_NIL` (check for `undefined`) followed by `OP_RT_GET_GLOBAL('null')` + `OP_RT_EQ` (check for `null`) with conditional jumps. -- **Template literals** — The compiler parses interpolations at compile time, emits string constants and expression compilations, then chains `OP_RT_ADD` instructions for concatenation. +- **Simple classes** — The compiler emits `OP_NEW_BLUEPRINT` + `OP_INHERIT` + `OP_CLOSURE` (per method) + `OP_RECORD_SET` (to blueprint methods) + `OP_INSTANTIATE`. +- **Nullish coalescing (`??`)** — The compiler emits `OP_JUMP_IF_NOT_NIL` (check for `undefined`) followed by `OP_RT_GET_GLOBAL('null')` + `OP_RT_EQ` (check for `null`) with conditional jumps. +- **Template literals** — The compiler parses interpolations at compile time, emits string constants and `OP_RT_TO_STRING` for expression parts, then chains `OP_CONCAT` instructions. +- **Object spread** — The compiler emits `OP_RT_EXT(GOCCIA_EXT_SPREAD_OBJ)`, routing to the GocciaScript-specific extension handler. + +This keeps the VM general-purpose: new language features are expressed through existing operations or routed through `OP_RT_EXT`. + +### Why `OP_RT_EXT` Over Per-Feature Opcodes? + +An early design added JS-specific opcodes directly to the Tier 2 table (`OP_RT_SPREAD_OBJ`, `OP_RT_DEF_GETTER`, `OP_RT_EVAL_CLASS`, etc.). This accumulated 13 JS-specific opcodes — 16 if you count the abstract methods they required. Each opcode was unusable by any other language frontend. + +A systematic boundary cleanup replaced all 13 opcodes with a single `OP_RT_EXT` that dispatches to `ExtendedOperation(subOpcode, ...)`. GocciaScript defines its sub-opcode constants in `Goccia.Compiler.ExtOps.pas`. The VM never interprets sub-opcode IDs — they are opaque bytes. A future C# or Ruby frontend would define its own sub-opcode constants and handler. See [souffle-vm.md § OP_RT_EXT](souffle-vm.md#op_rt_ext-language-extension-mechanism) for the full rationale on why alternatives were rejected. + +### Tier 1 Property Flags vs Tier 2 Visibility + +Property **mutability** (writable/configurable) is a Tier 1 concern. Every language with objects needs per-property control over mutability. These flags are stored as a `Flags` byte on each `TSouffleRecordEntry` and enforced by the VM's `PutChecked`/`DeleteChecked` methods — no runtime dispatch needed. The per-property flag is the fundamental primitive; bulk operations like freeze and seal are derived from it: + +- `SetEntryFlags(key, flags)` — modify flags on a single property +- `PutWithFlags(key, value, flags)` — create a property with specific flags +- `PreventExtensions` — stop new properties from being added +- `Freeze` = iterate all entries, set flags to 0, prevent extensions (a convenience, not a primitive) + +Property **visibility** (public/private/protected) and **accessor semantics** (getter/setter invocation) are Tier 2 concerns. JavaScript uses `#field` private name mangling; C# uses CLR visibility metadata; Ruby uses `attr_reader`/`attr_writer`. At the VM level, getters, setters, and methods are all functions — the runtime decides whether a property access triggers a function call. + +An earlier design included `OP_RECORD_FREEZE` as a dedicated opcode for bulk freezing. This was removed because (1) per-property flag setting is the real building block, (2) freezing semantics differ across languages, and (3) a bulk operation is trivially composed from per-property calls by the runtime. + +### Spread Calling Consolidation + +An earlier design had separate `OP_RT_CALL_SPREAD` and `OP_RT_CALL_METHOD_SPREAD` opcodes for spread-based function calls. These were consolidated into the C flags byte of `OP_RT_CALL`/`OP_RT_CALL_METHOD` (bit 0 = spread mode, B = args array register). Spread is a modifier on an existing operation, not a fundamentally different one. The C byte was unused in these opcodes, making it free to repurpose. + +### The Bridge Problem + +`TGocciaRuntimeOperations` implements most Tier 2 operations by: + +1. Converting `TSouffleValue` → `TGocciaValue` (unwrap) +2. Delegating to the GocciaScript evaluator/interpreter +3. Converting `TGocciaValue` → `TSouffleValue` (wrap) + +This "Unwrap-Delegate-Wrap" cycle was the fastest path to a working bytecode backend and achieves full language coverage — the bytecode backend passes 100% of the test suite (3,347 tests). However, the cycle creates deep coupling to the interpreter. -This keeps the VM general-purpose: new language features are expressed through existing operations. +The target architecture eliminates the bridge entirely: `TGocciaRuntimeOperations` would implement JavaScript semantics directly on Souffle types (`TSouffleValue`, `TSouffleArray`, `TSouffleRecord`, `TSouffleBlueprint`), with prototype chain walking on delegate chains, type coercion natively on `TSouffleValue`, and all built-in methods as `TSouffleNativeFunction` delegates. This is a ground-up rewrite, not an incremental fix. See [souffle-vm.md § Current State](souffle-vm.md#current-state-and-bridge-architecture) for the full analysis. -### Deferred Runtime Operations +### Bridge-Delegated Operations -Several runtime operations are currently stubbed in `TGocciaRuntimeOperations` and will need full implementations before the corresponding GocciaScript features work in bytecode mode: +The following operations delegate to the GocciaScript evaluator through the bridge layer. They are functionally correct but add overhead from value conversion and dual GC tracking: -- **Iteration** (`GetIterator`, `IteratorNext`, `SpreadInto`) — Not yet wired to GocciaScript's iterator protocol. Blocks `for...of`, spread syntax, and destructuring in bytecode mode. -- **Module imports** (`ImportModule`) — Returns nil. Blocks `import`/`export` in bytecode mode. -- **Async/await** (`AwaitValue`) — Returns the value unchanged. Blocks async function execution in bytecode mode. +- **Iteration** (`GetIterator`, `IteratorNext`, `SpreadInto`) — Delegates to GocciaScript's `GetIteratorFromValue` / `TGocciaIteratorValue` protocol. +- **Module imports** (`ImportModule`) — Delegates to the GocciaScript module resolver. +- **Async/await** (`AwaitValue`) — Delegates to `TGocciaPromiseValue` and the microtask queue. +- **Complex class compilation** — Classes with getters, setters, static properties, private members, or decorators are deferred to the interpreter via `GOCCIA_EXT_EVAL_CLASS`. Classes extending built-in constructors (`Array`, `Map`, `Set`, `Promise`, `Object`) are also deferred because `OP_INHERIT` requires both sides to be `TSouffleBlueprint`s. The goal is to compile all class features to Tier 1 + Tier 2 opcodes. -These stubs exist because the core execution pipeline (variables, closures, classes, property access, invocation) was prioritized first. Each stub has a clear contract defined by the abstract `TSouffleRuntimeOperations` base class. +Eliminating the bridge for these operations is the primary path to improving bytecode execution performance. ### Rejected Findings diff --git a/docs/embedding.md b/docs/embedding.md index 5f20d484..d84e0a97 100644 --- a/docs/embedding.md +++ b/docs/embedding.md @@ -459,6 +459,12 @@ end; | `TGocciaReferenceError` | Undefined variable access | | `TGocciaRangeError` | Value out of range | +## FPU Exception Mask + +FPU exceptions — divide-by-zero, overflow, underflow, invalid operation, denormalized operand, and precision loss — are hardware signals raised by the floating-point unit when an operation produces a special result. By default, FreePascal leaves some of these unmasked, which causes runtime exceptions on operations like `0.0 / 0.0` instead of returning `NaN`. + +Both `TGocciaEngine` and `TSouffleVM` mask all FPU exceptions on creation (via `SetExceptionMask`) to enable IEEE 754 semantics (`NaN`, `Infinity`, `-0`). The previous mask is saved in the constructor and **restored in the destructor**, so the host application's FPU state is not permanently altered. This is transparent for one-shot execution (`RunScript`), but embedders creating long-lived engine instances should be aware that FPU exceptions are suppressed while the engine exists. If the host application depends on FPU exception handlers for its own error handling, those handlers will not fire while a `TGocciaEngine` or `TSouffleVM` instance is alive. + ## Microtask Queue (Promises) When `ggPromise` is included in the globals set, the engine initializes a singleton microtask queue (`TGocciaMicrotaskQueue`) alongside the GC. Promise `.then()` callbacks are enqueued as microtasks and **drained automatically** after each `Execute`, `ExecuteWithTiming`, or `ExecuteProgram` call. Embedders do not need to drain the queue manually. diff --git a/docs/souffle-vm.md b/docs/souffle-vm.md index f64fa968..72b17c05 100644 --- a/docs/souffle-vm.md +++ b/docs/souffle-vm.md @@ -1,6 +1,8 @@ # Souffle VM -Souffle is a general-purpose bytecode virtual machine designed for extensibility, maintainability, and performance. It serves as an alternative execution backend for GocciaScript and is architected to support multiple programming language frontends and future WASM 3.0 output. +Souffle is a general-purpose, language-agnostic bytecode virtual machine designed for extensibility, maintainability, and performance. It is architected as a **standalone project** that can be extracted from the GocciaScript repository and used independently. While it currently serves as an alternative execution backend for GocciaScript, its architecture supports multiple programming language frontends (JavaScript/GocciaScript, Boo, C#, Ruby-like, or any other language) and future WASM 3.0 output. + +The key design goal: the `souffle/` directory has **zero imports** from `Goccia.*` units. All language-specific behavior is injected through a pluggable abstract runtime interface (`TSouffleRuntimeOperations`). Adding a new language frontend requires implementing this interface — zero VM changes. ## Architecture Overview @@ -37,8 +39,8 @@ graph TD BC --> VM BC --> WASM VM --> VALS - VM -->|"Tier 2 opcodes"| RTJS - VM -->|"Tier 2 opcodes"| RTOTHER + VM -->|"Runtime opcodes"| RTJS + VM -->|"Runtime opcodes"| RTOTHER ``` The key architectural principle is the **separation of language semantics from VM mechanics**. The VM itself knows nothing about JavaScript, prototype chains, or any language-specific behavior. All language semantics are injected through the pluggable runtime operations interface. @@ -63,10 +65,22 @@ Fixed semantics implemented directly in the dispatch loop. These are universal o | Category | Opcodes | Description | |----------|---------|-------------| -| Load/Store | `OP_LOAD_CONST`, `OP_LOAD_NIL`, `OP_LOAD_TRUE`, `OP_LOAD_FALSE`, `OP_LOAD_INT`, `OP_MOVE` | Register manipulation and constant loading | +| Load/Store | `OP_LOAD_CONST`, `OP_LOAD_NIL` (AB: R[A]:=nil(flags=B)), `OP_LOAD_TRUE`, `OP_LOAD_FALSE`, `OP_LOAD_INT`, `OP_MOVE` | Register manipulation and constant loading | | Variables | `OP_GET_LOCAL`, `OP_SET_LOCAL`, `OP_GET_UPVALUE`, `OP_SET_UPVALUE`, `OP_CLOSE_UPVALUE` | Local and upvalue access | +| Typed Variables | `OP_GET_LOCAL_INT`, `OP_SET_LOCAL_INT`, `OP_GET_LOCAL_FLOAT`, `OP_SET_LOCAL_FLOAT`, `OP_GET_LOCAL_BOOL`, `OP_SET_LOCAL_BOOL`, `OP_GET_LOCAL_STRING`, `OP_SET_LOCAL_STRING`, `OP_GET_LOCAL_REF`, `OP_SET_LOCAL_REF` | Type-specialized local access for future WASM/JIT optimization | | Control Flow | `OP_JUMP`, `OP_JUMP_IF_TRUE`, `OP_JUMP_IF_FALSE`, `OP_JUMP_IF_NIL`, `OP_JUMP_IF_NOT_NIL` | Branching and conditional jumps | | Closures | `OP_CLOSURE` | Closure creation from function prototypes | +| Compound Types | `OP_NEW_ARRAY`, `OP_ARRAY_PUSH`, `OP_ARRAY_POP`, `OP_ARRAY_GET`, `OP_ARRAY_SET` | VM-native dense array creation and access | +| Compound Types | `OP_NEW_RECORD`, `OP_RECORD_GET`, `OP_RECORD_SET`, `OP_RECORD_DELETE` | VM-native string-keyed record with per-entry property flags (writable, configurable) | +| Compound Types | `OP_GET_LENGTH` | Length query for arrays, records, and strings | +| Destructuring | `OP_UNPACK` | Extract rest of array from index N into a new array | +| Arguments | `OP_ARG_COUNT`, `OP_PACK_ARGS` | Actual argument count and variadic argument packing | +| Integer Arithmetic | `OP_ADD_INT`, `OP_SUB_INT`, `OP_MUL_INT`, `OP_DIV_INT`, `OP_MOD_INT`, `OP_NEG_INT` | Fast-path integer operations (no runtime dispatch) | +| Float Arithmetic | `OP_ADD_FLOAT`, `OP_SUB_FLOAT`, `OP_MUL_FLOAT`, `OP_DIV_FLOAT`, `OP_MOD_FLOAT`, `OP_NEG_FLOAT` | Fast-path float operations (no runtime dispatch) | +| Integer Comparison | `OP_EQ_INT`, `OP_NEQ_INT`, `OP_LT_INT`, `OP_GT_INT`, `OP_LTE_INT`, `OP_GTE_INT` | Fast-path integer comparison (no runtime dispatch) | +| String | `OP_CONCAT` | Direct string concatenation | +| Type Coercion | `OP_TO_PRIMITIVE` | Fast-path for primitives (nil/bool/int/float/string pass through), runtime callback for references | +| Blueprint | `OP_NEW_BLUEPRINT`, `OP_INHERIT`, `OP_INSTANTIATE`, `OP_GET_SLOT`, `OP_SET_SLOT` | Wren-inspired blueprint primitives for optimized dispatch | | Exceptions | `OP_PUSH_HANDLER`, `OP_POP_HANDLER`, `OP_THROW` | Handler-table exception model | | Return | `OP_RETURN`, `OP_RETURN_NIL` | Function return | | Debug | `OP_NOP`, `OP_LINE` | No-ops and source line annotations | @@ -77,17 +91,18 @@ Language-specific semantics dispatched through `TSouffleRuntimeOperations`, an a | Category | Opcodes | Description | |----------|---------|-------------| -| Arithmetic | `OP_RT_ADD` through `OP_RT_NEG` | Polymorphic arithmetic (addition, subtraction, etc.) | -| Bitwise | `OP_RT_BAND` through `OP_RT_BNOT` | Polymorphic bitwise operations | -| Comparison | `OP_RT_EQ` through `OP_RT_GTE` | Polymorphic comparison | -| Logical/Type | `OP_RT_NOT`, `OP_RT_TYPEOF`, `OP_RT_IS_INSTANCE`, `OP_RT_HAS_PROPERTY`, `OP_RT_TO_BOOLEAN` | Type checks and logical operations | -| Compound | `OP_RT_NEW_COMPOUND`, `OP_RT_INIT_FIELD`, `OP_RT_INIT_INDEX` | Object/array creation | -| Property Access | `OP_RT_GET_PROP`, `OP_RT_SET_PROP`, `OP_RT_GET_INDEX`, `OP_RT_SET_INDEX`, `OP_RT_DEL_PROP` | Property read/write/delete | -| Invocation | `OP_RT_CALL`, `OP_RT_CALL_METHOD`, `OP_RT_CONSTRUCT` | Function calls and construction | -| Iteration | `OP_RT_GET_ITER`, `OP_RT_ITER_NEXT`, `OP_RT_SPREAD` | Iterator protocol | -| Modules | `OP_RT_IMPORT`, `OP_RT_EXPORT` | Module system | -| Async | `OP_RT_AWAIT` | Async/await support | -| Globals | `OP_RT_GET_GLOBAL`, `OP_RT_SET_GLOBAL` | Global variable access | +| Arithmetic (7) | `OP_RT_ADD` through `OP_RT_NEG` | Polymorphic arithmetic with language-specific coercion | +| Bitwise (7) | `OP_RT_BAND` through `OP_RT_BNOT` | Polymorphic bitwise operations | +| Comparison (6) | `OP_RT_EQ` through `OP_RT_GTE` | Polymorphic comparison with language-specific equality/ordering | +| Logic/Type (5) | `OP_RT_NOT`, `OP_RT_TYPEOF`, `OP_RT_IS_INSTANCE`, `OP_RT_HAS_PROPERTY`, `OP_RT_TO_BOOLEAN` | Type checks, truthiness, logical operations | +| Property (6) | `OP_RT_GET_PROP`, `OP_RT_SET_PROP`, `OP_RT_GET_INDEX`, `OP_RT_SET_INDEX`, `OP_RT_DEL_PROP`, `OP_RT_DEL_INDEX` | Property read/write/delete with language-specific dispatch | +| Invocation (3) | `OP_RT_CALL`, `OP_RT_CALL_METHOD`, `OP_RT_CONSTRUCT` | Function calls (C flag bit 0 = spread mode) and construction | +| Iteration (2) | `OP_RT_GET_ITER`, `OP_RT_ITER_NEXT` | Iterator protocol | +| Modules (2) | `OP_RT_IMPORT`, `OP_RT_EXPORT` | Module system | +| Async (1) | `OP_RT_AWAIT` | Async/await support | +| Globals (3) | `OP_RT_GET_GLOBAL`, `OP_RT_SET_GLOBAL`, `OP_RT_HAS_GLOBAL` | Global variable access and existence check | +| Coercion (1) | `OP_RT_TO_STRING` | Language-specific value-to-string (template literals, interpolation) | +| Extension (1) | `OP_RT_EXT` | Generic dispatch to `ExtendedOperation` for language-specific sub-opcodes | The VM doesn't know what "get property" means. It calls `RuntimeOps.GetProperty(obj, key)`. GocciaScript's runtime walks prototype chains. A future Python runtime would do MRO + `__getattr__`. A future Lua runtime would check metatables. Same opcodes, completely different semantics — the **compiler** makes those choices, not the VM. @@ -101,24 +116,151 @@ Language-specific concepts like property access, arithmetic on polymorphic value By routing these through an abstract interface, the VM remains language-agnostic. Adding a new language frontend requires implementing `TSouffleRuntimeOperations` — zero VM changes. -### Classes as a Compiler Concern +### TSouffleRuntimeOperations: The Abstract Interface + +The runtime interface defines **45 methods (including the `ExtendedOperation` extension entry point)**, all expressed in language-agnostic terms. No method name, parameter, or return type references JavaScript concepts. The full method listing: + +| Group | Methods | Count | +|-------|---------|-------| +| Arithmetic | `Add`, `Subtract`, `Multiply`, `Divide`, `Modulo`, `Power`, `Negate` | 7 | +| Bitwise | `BitwiseAnd`, `BitwiseOr`, `BitwiseXor`, `ShiftLeft`, `ShiftRight`, `UnsignedShiftRight`, `BitwiseNot` | 7 | +| Comparison | `Equal`, `NotEqual`, `LessThan`, `GreaterThan`, `LessThanOrEqual`, `GreaterThanOrEqual` | 6 | +| Logic/Type | `LogicalNot`, `TypeOf`, `IsInstance`, `HasProperty`, `ToBoolean`, `ToPrimitive` | 6 | +| Property | `GetProperty`, `SetProperty`, `GetIndex`, `SetIndex`, `DeleteProperty`, `DeleteIndex` | 6 | +| Invocation | `Invoke`, `Construct` | 2 | +| Globals | `GetGlobal`, `SetGlobal`, `HasGlobal` | 3 | +| Iteration | `GetIterator`, `IteratorNext` | 2 | +| Modules | `ImportModule`, `ExportBinding` | 2 | +| Async | `AwaitValue`, `WrapInPromise` | 2 | +| Coercion | `CoerceValueToString` | 1 | +| Extension | `ExtendedOperation` | 1 | + +All methods operate on `TSouffleValue` — the VM's own tagged union. The runtime never touches registers, the instruction pointer, or the call stack. It receives values, performs language-specific logic, and returns values. The VM handles all register storage, frame management, and dispatch. + +**Default implementations**: `DeleteIndex` (delegates to `DeleteProperty` with string coercion), `CoerceValueToString` (returns `SouffleNil`), `WrapInPromise` (returns the value unchanged), and `ExtendedOperation` (no-op) have default implementations in the base class. All other methods are abstract. + +**Why these specific groups?** Every practical language needs arithmetic on polymorphic values, some form of property access, a way to call functions, and a way to compare values. The interface was designed by asking: *"What operations would a Boo, C#, Ruby, or fully-compliant ES engine need?"* Operations that only apply to one language (object spread, enum finalization, super method lookup) are routed through `ExtendedOperation` instead. + +### OP_RT_EXT: Language Extension Mechanism + +`OP_RT_EXT` (opcode 190) is a single generic extension opcode that allows language frontends to define their own sub-opcode IDs for complex, language-specific operations without polluting the VM's opcode space. The instruction encodes `ABC` where `B` is the sub-opcode ID, and `A`/`C` are register operands. The VM dispatches blindly to `RuntimeOps.ExtendedOperation(B, R[A], R[C], R[A+1], Template, C)`. + +#### Design Decision: Why One Extension Opcode? + +Three alternatives were evaluated during the boundary cleanup: + +1. **Per-feature language opcodes** (e.g., `OP_RT_SPREAD_OBJ`, `OP_RT_DEF_GETTER`, `OP_RT_EVAL_CLASS`) — Rejected because each opcode couples the VM to one language. A Python frontend does not need `OP_RT_EVAL_CLASS`; a Ruby frontend does not need `OP_RT_SPREAD_OBJ`. This was the original design and led to 13 JS-specific opcodes accumulating in the VM's opcode table — a pattern explicitly identified as architectural drift. + +2. **Multiple generic extension opcodes** (e.g., `OP_RT_EXT1` through `OP_RT_EXT8`) — Rejected as unnecessary complexity. One entry point with a sub-opcode byte provides 256 extension slots per language, which is more than sufficient. Multiple entry points add dispatch overhead without adding expressiveness. + +3. **Single `OP_RT_EXT` with sub-opcode dispatch** — Chosen. The VM performs one virtual dispatch to `ExtendedOperation`, passing the sub-opcode ID. The runtime's `case` statement on sub-opcode IDs is compiled to a jump table by FPC — effectively zero overhead beyond the single virtual call. The sub-opcode constants live in the language's compiler unit, invisible to the VM. + +#### GocciaScript Sub-opcodes + +GocciaScript defines its sub-opcodes in `Goccia.Compiler.ExtOps.pas`: + +| Sub-opcode | ID | Description | +|------------|----|-------------| +| `GOCCIA_EXT_SPREAD_OBJ` | 1 | Spread object properties into a target record | +| `GOCCIA_EXT_OBJ_REST` | 2 | Object rest destructuring (collect remaining properties) | +| `GOCCIA_EXT_FINALIZE_ENUM` | 3 | Finalize enum value (freeze, set up iterator) | +| `GOCCIA_EXT_DEF_GETTER` | 4 | Define getter on blueprint | +| `GOCCIA_EXT_DEF_SETTER` | 5 | Define setter on blueprint | +| `GOCCIA_EXT_DEF_STATIC_GETTER` | 6 | Define static getter on class | +| `GOCCIA_EXT_DEF_STATIC_SETTER` | 7 | Define static setter on class | +| `GOCCIA_EXT_REQUIRE_OBJECT` | 8 | Require object-coercible value (throw TypeError on null/undefined) | +| `GOCCIA_EXT_EVAL_CLASS` | 9 | Evaluate complex class definition (temporary bridge, see [Current State](#current-state-and-bridge-architecture)) | +| `GOCCIA_EXT_THROW_TYPE_ERROR` | 10 | Throw TypeError with message from constant pool | +| `GOCCIA_EXT_SUPER_GET` | 11 | Super method lookup in class hierarchy | +| `GOCCIA_EXT_SPREAD` | 12 | Spread iterable into array | +| `GOCCIA_EXT_REQUIRE_ITERABLE` | 13 | Require iterable value (throw TypeError if not iterable) | + +A future C# frontend would define its own sub-opcode constants (e.g., `CSHARP_EXT_LINQ_SELECT`, `CSHARP_EXT_ASYNC_STATE_MACHINE`) and its own `ExtendedOperation` handler. The VM itself never interprets the sub-opcode IDs — they are opaque bytes. + +### Record Property Flags + +`TSouffleRecordEntry` carries a `Flags: Byte` field with per-entry property metadata: + +- **Bit 0** (`SOUFFLE_PROP_WRITABLE`): Can this entry be reassigned? Default: 1 (writable) +- **Bit 1** (`SOUFFLE_PROP_CONFIGURABLE`): Can this entry be deleted/redefined? Default: 1 (configurable) + +The per-property flag is the fundamental primitive. Bulk operations (freeze, seal) are derived from it. + +#### Per-Property Primitives + +| Method | Description | +|--------|-------------| +| `PutWithFlags(key, value, flags)` | Create or update a property with explicit flags | +| `SetEntryFlags(key, flags)` | Modify flags on an existing property | +| `GetEntryFlags(key)` | Read flags from an existing property | +| `PutChecked(key, value)` | Write that respects the writable flag (silently no-ops if non-writable) | +| `DeleteChecked(key)` | Delete that respects the configurable flag (silently no-ops if non-configurable) | +| `PreventExtensions` | Stop new properties from being added to the record | + +`OP_RECORD_SET` in the VM dispatch uses `PutChecked`. `OP_RECORD_DELETE` uses `DeleteChecked`. These enforce per-property flags without runtime dispatch. + +#### Derived Operations -There is no `CLASS` opcode. Classes are syntactic sugar that the **compiler** desugars into generic operations: +Bulk operations like freeze and seal are built from the per-property primitives: ```text -; class Foo extends Bar { constructor(x) { this.x = x; } greet() { return "hi"; } } - -CLOSURE r0, ; constructor function -CLOSURE r1, ; method -RT_NEW_COMPOUND r2 ; create prototype object -RT_SET_PROP r2, "greet", r1 ; prototype.greet = greet -RT_SET_PROP r0, "prototype", r2 ; Foo.prototype = proto -; Inheritance is handled by the runtime via Invoke/Construct — no dedicated opcode. -; The compiler emits RT_SET_PROP calls to wire prototype chains as appropriate -; for the language (JS: Object.setPrototypeOf, Python: metaclass, etc.). +Freeze = for each entry: SetEntryFlags(key, 0) + PreventExtensions +Seal = for each entry: SetEntryFlags(key, flags & ~CONFIGURABLE) + PreventExtensions +PreventExtensions = just set the extensibility flag (no per-property changes) ``` -Every language compiles its "class" concept differently, but all use the same generic opcodes. JavaScript does prototype chains, Python does metaclass invocation + MRO, Lua does metatables. +`TSouffleRecord.Freeze` exists as a convenience method that implements the first pattern. Language runtimes call it (or compose their own variant from the primitives) — there is no freeze opcode. JavaScript's `Object.freeze`, `Object.seal`, and `Object.preventExtensions` all reduce to different combinations of `SetEntryFlags` and `PreventExtensions`. + +#### Design Decision: Per-Property Flags as Tier 1 + +Property mutability (writable/configurable) is a **Tier 1 concern** — every language with objects needs per-property control over mutability. The flags live directly on `TSouffleRecordEntry` and are enforced by the VM's `PutChecked`/`DeleteChecked` — no runtime dispatch needed. This is universal: JavaScript's `Object.defineProperty`, Ruby's `.freeze`, C#'s `readonly`, and Python's `__slots__` all reduce to per-property mutability control. + +Property **visibility** (public/private/protected) and **accessor semantics** (getter/setter invocation) are **Tier 2 concerns** — these vary fundamentally across languages. JavaScript uses private name mangling with `#field`, C# uses CLR visibility metadata, Ruby uses `attr_reader`/`attr_writer`. These remain in the runtime operations layer. + +An earlier design included a dedicated `OP_RECORD_FREEZE` opcode for bulk freezing. This was removed because: +1. The per-property flag primitive (`SetEntryFlags`) is the real building block — freezing is derived +2. Freezing semantics differ across languages (JS freezes shallowly at one level, Ruby freezes differently, some languages don't support freezing) +3. A bulk operation is trivially composed from per-property calls by the runtime — no opcode needed + +### Spread Calling + +Function call spread (`fn(...args)`) is handled via a **flag bit** on the existing invocation opcodes, not via dedicated spread opcodes: + +- `OP_RT_CALL` and `OP_RT_CALL_METHOD` encode spread mode in **C flag bit 0** +- When bit 0 is set: register B holds the arguments array (a `TSouffleArray`), and the runtime's `InvokeWithSpread` unpacks and invokes +- When bit 0 is clear: B holds the argument count, and arguments are in consecutive registers starting at `R[Base + A + 1]` + +#### Design Decision: Flags Over Separate Opcodes + +An earlier design included `OP_RT_CALL_SPREAD` and `OP_RT_CALL_METHOD_SPREAD` as separate opcodes. These were removed and consolidated into the C flags byte on the existing opcodes. The rationale: + +- Spread is a **modifier** on an existing operation (calling), not a fundamentally different operation +- Adding separate opcodes doubles the invocation opcode count (2→4) without adding expressiveness +- The C byte was previously unused in these opcodes, making it free to repurpose +- The same pattern (flag bits for mode selection) is used by other VMs (Lua's `OP_CALL` uses flags for vararg passing) + +### Blueprint Compilation + +The compiler uses a two-path strategy for class declarations: + +**Simple classes** (constructor + named methods, no getters/setters, statics, private members, decorators, or computed properties) are compiled directly to VM blueprint opcodes: + +```text +; class Dog extends Animal { constructor(name) { super(name); } bark() { return name + " barks"; } } + +NEW_BLUEPRINT r0, "Dog" ; create TSouffleBlueprint +RT_GET_GLOBAL r1, "Animal" ; load super blueprint +INHERIT r0, r1 ; wire super blueprint chain +CLOSURE r2, ; compile constructor +RECORD_SET r0, "constructor", r2 ; attach to blueprint methods +CLOSURE r3, ; compile method +RECORD_SET r0, "bark", r3 ; attach to blueprint methods +RT_SET_GLOBAL "Dog", r0 ; register globally +``` + +The `IsSimpleClass` function in `Goccia.Compiler.Statements.pas` performs the complexity detection. At runtime, `TGocciaRuntimeOperations.Construct` walks the `TSouffleBlueprint` super blueprint chain to find inherited constructors, and `GetProperty` on blueprint-backed records checks dynamic properties first, then walks the blueprint's method record hierarchy. + +**Complex classes** (with getters, setters, static properties, private members, decorators, or computed properties) are compiled using a mix of blueprint opcodes and `OP_RT_EXT` sub-opcodes for language-specific features (e.g., `GOCCIA_EXT_DEF_GETTER`, `GOCCIA_EXT_DEF_SETTER`, `GOCCIA_EXT_EVAL_CLASS`). Classes that cannot be fully compiled to bytecode are deferred to the interpreter via `FPendingClasses` and evaluated through `GOCCIA_EXT_EVAL_CLASS`. ## Instruction Encoding @@ -138,7 +280,7 @@ Signed operands use bias encoding: `sBx` is stored as `sBx + 32767`, `Ax` is sto Souffle uses a register-based architecture (like Lua 5, LuaJIT, and Dalvik) rather than a stack-based one. ```text - Register File (array of TSouffleValue, 16 bytes each): + Register File (array of TSouffleValue, 26 bytes each): ┌──────────────────┬─────────────────┬─────────────────┬─────┐ │ Frame 0 (global) │ Frame 1 (fn A) │ Frame 2 (fn B) │ ... │ │ R[0]..R[15] │ R[0]..R[8] │ R[0]..R[5] │ │ @@ -161,46 +303,94 @@ Each function call pushes a `TSouffleVMCallFrame` onto the call stack: - `Base` — absolute register offset for this frame - `ReturnRegister` — absolute register index where the return value should be stored -The VM supports re-entrant execution: when a runtime operation (e.g., a closure bridge) needs to call back into the VM, `ExecuteFunction` saves and restores `FBaseFrameCount` to correctly handle nested dispatch loops. +The VM supports re-entrant execution: when a runtime operation (e.g., a closure bridge) needs to call back into the VM, `ExecuteFunction` saves and restores `FBaseFrameCount` to correctly handle nested dispatch loops. If `OP_THROW` fires with an empty handler stack, `ExecuteFunction` cleans up the call stack (closing upvalues) and re-raises `ESouffleThrow` — the VM never swallows unhandled throws. The GocciaScript integration layer (`TGocciaSouffleClosureBridge.Call`, `TGocciaSouffleBackend.RunModule`) catches `ESouffleThrow` and converts it to `TGocciaThrowValue` so exceptions propagate correctly through the interpreter. + +### Uniform Receiver Handling + +Every function call uses a uniform register layout: **register 0 always holds the receiver**. For non-method calls (top-level functions, arrow functions), the receiver is `SouffleNil`. For method calls, it holds the object the method was called on. + +```text +Frame layout: + R[Base + 0] = receiver (SouffleNil for non-methods) + R[Base + 1] = first argument + R[Base + 2] = second argument + ... +``` + +`ExecuteFunction` and `CallClosure` always place the receiver in register 0 unconditionally — there is no `AIsMethodCall` flag or side-table lookup. The compiler reserves slot 0 for the receiver in every function by declaring a `__receiver` local (for arrow functions and the top-level module) or `this` (for methods). Arrow functions inherit `this` from their lexical closure scope, so the `SouffleNil` in register 0 is ignored by the language runtime. `TGocciaSouffleClosureBridge.Call` always passes `AThisValue` as `Args[0]`, making the bridge layer equally uniform. ## Tagged Union Value System The VM has its own value system, completely independent of `TGocciaValue`: ```text -TSouffleValue (16 bytes): -┌──────────────────┬────────────────────────────────┐ -│ Kind: UInt8 (1B) │ padding (7B) │ -├──────────────────┴────────────────────────────────┤ -│ Data (8B): Int64 / Double / Pointer │ -└───────────────────────────────────────────────────┘ +TSouffleValue (packed record, 26 bytes): +┌──────────────────┬──────────────┬──────────────────────────────┐ +│ Kind: UInt8 (1B) │ Flags: (1B) │ Variant Data (24B) │ +├──────────────────┴──────────────┴──────────────────────────────┤ +│ svkNil: (nothing) │ +│ svkBoolean: AsBoolean: Boolean │ +│ svkInteger: AsInteger: Int64 │ +│ svkFloat: AsFloat: Double │ +│ svkString: AsInlineString: TSouffleInlineString (string[23]) │ +│ svkReference: AsReference: TSouffleHeapObject │ +└────────────────────────────────────────────────────────────────┘ ``` -### Five Value Kinds +### Six Value Kinds | Kind | In-Value Data | Heap? | Description | |------|--------------|-------|-------------| -| `svkNil` | — | No | Absence of value | +| `svkNil` | — | No | Absence of value (flags distinguish flavors) | | `svkBoolean` | `AsBoolean: Boolean` | No | True or false | | `svkInteger` | `AsInteger: Int64` | No | 64-bit integer | | `svkFloat` | `AsFloat: Double` | No | 64-bit float | +| `svkString` | `AsInlineString: TSouffleInlineString` | No | Short string (up to 23 bytes, inline) | | `svkReference` | `AsReference: TSouffleHeapObject` | Yes | Pointer to heap object | -Primitives are inline — zero heap allocation, zero GC pressure. All complex types (strings, objects, closures) are `svkReference` pointing to `TSouffleHeapObject` subclasses. The VM never inspects what kind of reference it is — that's the runtime's job. +### Flags Byte + +Every `TSouffleValue` carries a `Flags: Byte` field alongside `Kind`. This provides per-value metadata without adding new value kinds, keeping the VM general-purpose while allowing language runtimes to attach semantics: -### Why Only 5 Kinds? +- **`svkNil` flags** — Distinguishes flavors of "nothing". The VM treats all nils identically (falsy, numeric NaN). The runtime interprets flags: GocciaScript maps flags=0 to `undefined` (the default absent value), flags=1 to `null` (explicit null assignment), flags=2 to "the hole" (sparse array gap). `SouffleValuesEqual` compares flags for `svkNil` values — two nils with different flags are not equal. +- **Other kinds** — Flags default to 0. Reserved for future use (e.g., frozen strings, tainted values). -The VM does **not** distinguish between strings, objects, arrays, closures, classes, etc. Those are all `svkReference` — a pointer to a heap object. The heap object carries its own type tag (`HeapKind: UInt8`) that the **runtime** interprets. +`OP_LOAD_NIL` uses AB encoding: `R[A] := Nil(flags=B)`. The compiler emits the appropriate flag for each language construct. + +### Inline Short String Optimization + +Strings up to 23 characters are stored inline as `svkString` using `TSouffleInlineString = string[23]`. This avoids heap allocation for the vast majority of strings (property names, short literals, single characters). Strings longer than 23 characters are stored as `svkReference` pointing to a `TSouffleHeapString` heap object. + +The `SouffleString(AValue)` constructor auto-selects the representation: +- `Length(AValue) <= 23` → `svkString` with `AsInlineString` +- `Length(AValue) > 23` → `svkReference` to a new `TSouffleHeapString` + +The `SouffleGetString(AValue)` accessor handles both representations transparently. `SouffleIsStringValue(AValue)` checks for both `svkString` and `svkReference` to `TSouffleHeapString`. + +### Design Rationale + +**Packed record (26 bytes)**: The `packed` directive removes alignment padding between `Kind` (1B) and `Flags` (1B), saving 6 bytes per value compared to a non-packed layout (which would pad to 32 bytes). On modern hardware, the unaligned access penalty for `Int64`/`Double` is negligible — measured at less than 1% in benchmarks. With 65,536 registers, this saves ~384 KB of register file memory. + +**Why not NaN boxing**: NaN boxing (as used by V8/SpiderMonkey/Wren) encodes type tags in the mantissa bits of a 64-bit double, achieving 8 bytes per value. However, FPC's `Double` type does not expose raw bit manipulation as idiomatically as C, making NaN boxing fragile and non-portable across FPC targets (x86, ARM, WASM). The packed record approach is transparent, debuggable, and works identically on all FPC targets. + +**Why not a class hierarchy**: A class-based `TSouffleValue` (one subclass per kind) would require heap allocation for every primitive, adding pointer indirection, GC pressure, and ~40 bytes overhead per value (VMT + instance header). The flat variant record keeps primitives inline with zero allocation. + +### Why Only 6 Kinds? + +The VM does **not** distinguish between objects, arrays, closures, classes, etc. at the value kind level. Those are all `svkReference` — a pointer to a heap object. The heap object carries its own type tag (`HeapKind: UInt8`). Strings are the exception: short strings are inline (`svkString`) for performance, while long strings use `svkReference` to `TSouffleHeapString`. + +For performance-critical compound types (arrays and records), the VM provides inline type checks in core opcodes. For example, `OP_ARRAY_GET` checks `HeapKind = SOUFFLE_HEAP_ARRAY` and performs a direct element access; if the check fails, it falls through to the runtime. This gives the best of both worlds: VM-native speed for common operations, full language-specific semantics via runtime dispatch for complex cases. This means: -- The VM is truly language-agnostic — it doesn't know what "a string" or "an object" is -- Adding new heap types requires zero VM changes -- The register file is a flat array of 16-byte values — cache-friendly, no indirection for primitives +- The VM is language-agnostic — it provides generic array/record primitives usable by any language +- Adding new heap types requires zero VM changes (runtime dispatch handles them) +- Core compound opcodes provide fast paths that eliminate wrapping overhead for the most common operations +- The register file is a flat array of 26-byte packed values — cache-friendly, no indirection for primitives or short strings ### Truthiness Semantics -`IsTrue` is the one Tier 1 operation that touches value kinds. It has universal behavior: Nil is falsy, Boolean checks the flag, Integer 0 is falsy, Float 0.0/NaN is falsy, all References are truthy. Languages that need different truthiness (e.g., Python where empty list is falsy) can use `RT_TO_BOOLEAN` instead of `JUMP_IF_TRUE`. +`SouffleIsTrue` is the one Tier 1 operation that touches value kinds. It has universal behavior: Nil is falsy (regardless of flags), Boolean checks the flag, Integer 0 is falsy, Float 0.0/NaN is falsy, empty inline string is falsy, all References are truthy. Languages that need different truthiness (e.g., Python where empty list is falsy) can use `RT_TO_BOOLEAN` instead of `JUMP_IF_TRUE`. ### Heap Objects @@ -208,18 +398,248 @@ All heap-allocated values inherit from `TSouffleHeapObject`: | Heap Kind | Constant | Class | Description | |-----------|----------|-------|-------------| -| 0 | `SOUFFLE_HEAP_STRING` | `TSouffleString` | Immutable string value | | 1 | `SOUFFLE_HEAP_CLOSURE` | `TSouffleClosure` | Function prototype + captured upvalues | | 2 | `SOUFFLE_HEAP_UPVALUE` | `TSouffleUpvalue` | Open or closed upvalue | +| 3 | `SOUFFLE_HEAP_ARRAY` | `TSouffleArray` | Dense dynamic array of `TSouffleValue` | +| 4 | `SOUFFLE_HEAP_RECORD` | `TSouffleRecord` | Unified compound type: plain key-value map or blueprint-backed with indexed slots | +| 5 | `SOUFFLE_HEAP_NATIVE_FUNCTION` | `TSouffleNativeFunction` | Pascal callback wrapped as a callable heap object | +| 6 | `SOUFFLE_HEAP_BLUEPRINT` | `TSouffleBlueprint` | Type descriptor with SlotCount, method record (TSouffleRecord), and optional SuperBlueprint | +| 7 | `SOUFFLE_HEAP_STRING` | `TSouffleHeapString` | Heap-allocated string (for strings exceeding 23-byte inline limit) | | 128 | `SOUFFLE_HEAP_RUNTIME` | `TGocciaWrappedValue` | Language-specific wrapped value | -Kind 128+ is reserved for runtime-specific heap types. GocciaScript uses `TGocciaWrappedValue` to wrap `TGocciaValue` instances as Souffle heap objects, enabling GocciaScript's rich type system (arrays, objects, classes, promises, etc.) to be referenced from VM registers. +Kind 128+ is reserved for runtime-specific heap types. GocciaScript uses `TGocciaWrappedValue` to wrap `TGocciaValue` instances as Souffle heap objects, enabling GocciaScript's rich type system (classes, promises, etc.) to be referenced from VM registers. + +All heap objects have an optional **delegate** field (`TSouffleHeapObject.Delegate`), which enables prototype-chain-like method dispatch at the VM level without crossing the language boundary. See [Delegates](#delegates) below. + +## Native Compound Types + +The VM provides two compound types at the heap level, enabling direct operations without runtime dispatch for the most common data structures: + +### `TSouffleArray` (heap kind 3) + +Dense dynamic array of `TSouffleValue`. Universal across languages (JS arrays, Python lists, Lua array part, Wren lists, WASM GC arrays). + +- O(1) indexed get/set +- O(1) amortized push (doubling capacity, minimum 8) +- O(1) pop via `OP_ARRAY_POP` (AB format: R[A] := Array(R[B]).Pop()) +- Negative or out-of-range indices return `nil` +- `Put` auto-extends with `nil` fill when the index exceeds the current count + +### `TSouffleRecord` (heap kind 4) + +Unified compound type for all key-value data. Records without a blueprint are plain string-keyed hash maps (object literals, Python dicts, Lua hash part). Records with a blueprint (created via `CreateFromBlueprint`) have both key-value entries and indexed slots for O(1) field access. + +- FNV-1a 32-bit hash with open addressing (linear probing) +- 75% max load factor, doubles capacity on grow +- Insertion-order iteration via `GetOrderedKey`/`GetOrderedValue` +- Tombstone-based deletion preserves probe chains +- Blueprint-backed records: O(1) indexed slot access alongside key-value map + +**Why string-keyed only**: String keys cover the vast majority of property access across target languages. Non-string keys (JS Symbols, Python arbitrary-type dict keys) are rare and inherently language-specific — they remain in the runtime layer. + +### Core vs Runtime Property Access + +The compiler emits core compound opcodes for statically known operations: + +- Array literals `[1, 2, 3]` → `OP_NEW_ARRAY` + `OP_ARRAY_PUSH` +- Object literals `{a: 1}` → `OP_NEW_RECORD` + `OP_RECORD_SET` +- Computed index `arr[i]` → `OP_ARRAY_GET` (fast path for native arrays, fallback to runtime) +- Named property `obj.key` → `OP_RECORD_GET` (fast path for native records, fallback to runtime) +- Property assignment `obj.key = val` → `OP_RECORD_SET` (fast path for native records, fallback to runtime) + +All core compound opcodes have runtime fallbacks: if the operand is not the expected native type (e.g., a wrapped GocciaScript value instead of a `TSouffleRecord`), the VM delegates to `TSouffleRuntimeOperations.GetProperty` / `SetProperty` / `GetIndex` / `SetIndex`. + +The runtime opcodes (`OP_RT_GET_PROP`, `OP_RT_SET_PROP`, etc.) remain for complex cases: prototype chain walking, Symbol-keyed properties, accessor invocation, and `typeof`/`instanceof`/`in`. + +### Blueprint Primitives (Wren-inspired) + +The VM provides blueprint primitives (`TSouffleBlueprint` heap type) for class-like type descriptors. Blueprints sit alongside the unified `TSouffleRecord` and enable efficient method dispatch and instance creation without runtime dispatch. + +#### `TSouffleBlueprint` Fields + +| Field | Type | Description | +|-------|------|-------------| +| `FName` | `string` | Type name (e.g., `"Dog"`) — used for debug output and `OP_RT_IS_INSTANCE` | +| `FSlotCount` | `Integer` | Number of indexed instance slots for O(1) field access | +| `FMethods` | `TSouffleRecord` | Key-value record holding method closures (created in constructor) | +| `FSuperBlueprint` | `TSouffleBlueprint` | Optional parent blueprint for inheritance (set by `OP_INHERIT`) | +| `FPrototype` | `TSouffleRecord` | Lazily created: if a super blueprint exists, created via `CreateFromBlueprint(Super)`; otherwise a plain `TSouffleRecord` | + +#### Lifecycle + +1. **`OP_NEW_BLUEPRINT`** — Creates a `TSouffleBlueprint` with the given name and slot count. The `FMethods` record is created empty. + +2. **`OP_INHERIT`** — Sets `Blueprint.SuperBlueprint := SuperBlueprint`. This wires up the inheritance chain but does not copy methods. + +3. **`OP_RECORD_SET` on a blueprint** — `OP_RECORD_SET` is overloaded to handle blueprints: when the target register holds a `TSouffleBlueprint`, the operation stores the value in `Blueprint.Methods`. This eliminates the need for a dedicated blueprint-method opcode. Called once per method during class definition. + +4. **`OP_INSTANTIATE`** — Creates a new `TSouffleRecord` via `CreateFromBlueprint(Blueprint)`. The instance gets indexed slots (for O(1) field access) and its delegate is set to `Blueprint.Methods`. This means method lookups on instances walk the delegate chain: instance → methods → super methods → ... + +5. **Construction** — The runtime's `Construct` operation walks the super blueprint chain to find a `constructor` method, then calls it as a closure with the instance as receiver. + +#### Method Dispatch + +When accessing a property on a blueprint-backed instance: + +```text +obj.bark() + 1. OP_RECORD_GET checks obj's own entries (dynamic properties) + 2. DelegateGet walks: obj.Delegate (= Blueprint.Methods) + → Blueprint.Methods.Delegate (= SuperBlueprint.Methods) + → ... until the key is found or the chain ends + 3. Runtime fallback: TSouffleRuntimeOperations.GetProperty +``` + +This gives O(1) own-property access and O(depth) inherited method lookup — all within the VM, no language boundary crossing. + +#### Optimization Properties + +- **Blueprint-backed record slot access** uses O(1) integer-indexed slots instead of string-keyed hash lookups +- **Method dispatch** uses the delegate chain directly, enabling efficient inheritance without runtime dispatch +- The runtime opcodes (`OP_RT_CONSTRUCT`, `OP_RT_IS_INSTANCE`, `OP_RT_GET_PROP`, `OP_RT_SET_PROP`) remain as polymorphic fallbacks for cases where the compiler can't statically determine the type + +The blueprint opcodes are a fast path — not a replacement for the record-based approach. Languages that don't have classes (or use different class semantics) continue to use plain `TSouffleRecord` for everything. + +### WASM GC Mapping + +```text +TSouffleArray → WASM array type (array.new, array.get, array.set) +TSouffleRecord → WASM struct type (struct.new, struct.get, struct.set) + For dynamic records: WASM GC hash map (runtime library) +OP_ARRAY_GET → array.get +OP_RECORD_GET → struct.get (static) or runtime hash lookup (dynamic) +``` + +### Performance + +With native compound types, the hot path for `arr[i]` is: + +```text +1. Decode instruction (inline) +2. Check R[B].Kind = svkReference (branch) +3. Check R[B].AsReference is TSouffleArray (HeapKind check) +4. Check R[C].Kind = svkInteger (branch) +5. Bounds check + direct array access (one load) +6. Store into R[A] +``` + +vs. the previous path via `TGocciaWrappedValue`: + +```text +1. Decode instruction → call RuntimeOps.GetIndex (virtual dispatch) +2. Check wrapped value type +3. Unwrap TGocciaWrappedValue → TGocciaValue +4. Type-check TGocciaValue (is TGocciaArrayValue) +5. Convert key to string +6. Call TGocciaArrayValue.GetProperty +7. Wrap result in TGocciaWrappedValue (heap allocation) +8. Store into R[A] +``` + +### GC Integration + +Both compound types participate in mark-and-sweep: + +- `TSouffleArray.MarkReferences` — marks all elements that are `svkReference` +- `TSouffleRecord.MarkReferences` — marks all occupied values that are `svkReference` +- Both are registered via `AllocateObject` and tracked by the Souffle GC + +## Delegates + +Every `TSouffleHeapObject` can have an optional **delegate** — another `TSouffleHeapObject` (typically a `TSouffleRecord`) that the VM consults for key lookups that miss on the primary data. Delegates form a chain: each delegate can itself have a delegate, enabling inheritance-like method dispatch at the VM level without crossing into language-specific runtime code. + +### How It Works + +When `OP_RECORD_GET` resolves a string-keyed property: + +1. **Direct lookup** — If the object is a `TSouffleRecord`, check the record's own entries +2. **Delegate chain walk** — `DelegateGet` walks `Object.Delegate → Delegate.Delegate → ...`, checking each `TSouffleRecord` for the key +3. **Runtime fallback** — Delegate to `TSouffleRuntimeOperations.GetProperty` + +For `arr.push(6)` on a `TSouffleArray`: +- Step 1 skips (arrays aren't records) +- Step 2 finds `push` in the array's delegate → returns a `TSouffleNativeFunction` +- `OP_RT_CALL_METHOD` invokes the native function directly with the array as receiver +- **Zero wrapping** — the entire call stays within the Souffle VM + +For blueprint-backed instances (e.g., `dog.bark()`): +- Step 1 checks the instance record's own entries (dynamic properties) +- Step 2 walks the delegate chain: instance → `Blueprint.Methods` → `SuperBlueprint.Methods` → ... +- The first record containing `bark` returns the closure +- This enables class inheritance without any runtime dispatch + +### `TSouffleNativeFunction` + +A heap object (`SOUFFLE_HEAP_NATIVE_FUNCTION`) that wraps a Pascal function pointer: + +```pascal +TSouffleNativeCallback = function( + const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; + const AArgCount: Integer): TSouffleValue; +``` + +The callback receives `TSouffleValue` arguments and returns a `TSouffleValue` result. It operates directly on Souffle's value system — no language boundary crossing. + +### VM Integration + +The VM holds configurable default delegates per compound type: + +- `TSouffleVM.ArrayDelegate` — assigned to every `TSouffleArray` created by `OP_NEW_ARRAY` +- `TSouffleVM.RecordDelegate` — assigned to every `TSouffleRecord` created by `OP_NEW_RECORD` + +The language frontend populates these delegates during initialization. Individual instances can override their delegate after creation. + +`OP_RT_CALL` and `OP_RT_CALL_METHOD` dispatch `TSouffleNativeFunction` directly (inline invocation), before falling to the runtime operations interface. This gives delegate methods the same call overhead as closures. + +### GocciaScript Registration + +The GocciaScript bridge layer (`TGocciaRuntimeOperations.RegisterDelegates`) creates a `TSouffleRecord` for each type and populates it with native function implementations: + +| Type | Method | Description | +|------|--------|-------------| +| Array | `push(value)` | Appends value, returns new length (syncs to cache) | +| Array | `pop()` | Removes and returns last element (syncs to cache) | +| Array | `shift()` | Removes and returns first element (syncs to cache) | +| Array | `unshift(...values)` | Prepends values, returns new length (syncs to cache) | +| Array | `splice(start, count, ...items)` | Removes/inserts elements (syncs to cache) | +| Array | `reverse()` | Reverses in place (syncs to cache) | +| Array | `sort([comparator])` | Sorts in place (syncs to cache) | +| Array | `fill(value [, start [, end]])` | Fills range with value (syncs to cache) | +| Array | `join(sep)` | Joins elements with separator string | +| Array | `indexOf(value)` | First index of value, or -1 | +| Array | `includes(value)` | Whether array contains value | +| Array | `slice([start [, end]])` | Returns shallow copy of range | +| Array | `concat(...args)` | Returns merged array | +| Array | `at(index)` | Element at index (negative wraps) | +| Array | `forEach(callback)` | Calls callback for each element | +| Array | `map(callback)` | Returns mapped array | +| Array | `filter(callback)` | Returns filtered array | +| Array | `find(callback)` | First element passing test | +| Array | `findIndex(callback)` | Index of first passing element | +| Array | `findLast(callback)` | Last element passing test | +| Array | `findLastIndex(callback)` | Index of last passing element | +| Array | `every(callback)` | Whether all pass test | +| Array | `some(callback)` | Whether any passes test | +| Array | `reduce(callback [, init])` | Left fold | +| Array | `reduceRight(callback [, init])` | Right fold | +| Array | `flat([depth])` | Flattens nested arrays | +| Array | `flatMap(callback)` | Maps then flattens one level | +| Array | `toString()` | String representation | +| Array | `length` | Element count (read-only) | + +### Language Agnosticism + +The delegate mechanism is language-agnostic: +- The VM only performs string-keyed record lookups — it knows nothing about prototypes, `this`, or method binding +- The receiver is passed as a `TSouffleValue` to the native callback — the callback decides how to interpret it +- Different language frontends can register different delegates with different methods for the same compound types ## Closures and Upvalues Souffle uses a Lua-style upvalue model for lexical closures: -- **`TSouffleClosure`** — Pairs a `TSouffleFunctionPrototype` (code + constants) with an array of `TSouffleUpvalue` references +- **`TSouffleClosure`** — Pairs a `TSouffleFunctionTemplate` (code + constants) with an array of `TSouffleUpvalue` references - **`TSouffleUpvalue`** — Either *open* (pointing to a live register) or *closed* (holding a captured value) - **`OP_CLOSURE`** — Creates a closure from a nested function prototype, capturing upvalues as described by the prototype's `UpvalueDescriptors` - **`OP_GET_UPVALUE` / `OP_SET_UPVALUE`** — Read/write through upvalue indirection @@ -244,6 +664,10 @@ TSouffleExceptionHandler: At runtime, `OP_PUSH_HANDLER` pushes a handler entry; `OP_POP_HANDLER` removes it. When `OP_THROW` fires, the VM searches the handler stack for a matching handler and jumps to `CatchTarget`, storing the thrown value in `CatchRegister`. +### Non-Local Exits Through Finally + +When `break` or `return` occurs inside a `try...finally` block, the compiler inlines the finally block before the exit instruction. The compiler tracks pending finally blocks via `GPendingFinally` (a stack of `TPendingFinallyEntry` records). `CompileReturnStatement` pops all pending handlers and compiles all finally blocks before emitting `OP_RETURN`. `CompileBreakStatement` uses `GBreakFinallyBase` to process only the finally blocks added since the current loop/switch started — outer finally blocks are left intact. This ensures finally blocks execute on all exit paths without runtime support. + ## Garbage Collection Souffle has its own mark-and-sweep garbage collector (`Souffle.GarbageCollector.pas`), independent of GocciaScript's GC: @@ -262,7 +686,7 @@ Souffle has its own mark-and-sweep garbage collector (`Souffle.GarbageCollector. TSouffleBytecodeModule ├── FormatVersion: UInt16 ├── RuntimeTag: string (e.g., "goccia-js", "goccia-py") - ├── TopLevel: TSouffleFunctionPrototype + ├── TopLevel: TSouffleFunctionTemplate ├── SourcePath: string ├── Imports: array of TSouffleModuleImport │ ├── ModulePath: string @@ -275,11 +699,11 @@ TSouffleBytecodeModule ### Function Prototype ```text -TSouffleFunctionPrototype +TSouffleFunctionTemplate ├── Name: string ├── Code: array of UInt32 (instruction words) ├── Constants: array of TSouffleBytecodeConstant (typed constant pool) - ├── Functions: array of TSouffleFunctionPrototype (nested closures) + ├── Functions: array of TSouffleFunctionTemplate (nested closures) ├── MaxRegisters: UInt8 (register window size) ├── ParameterCount: UInt8 ├── UpvalueCount: UInt8 @@ -291,12 +715,18 @@ TSouffleFunctionPrototype │ ├── CatchTarget: UInt32 │ ├── FinallyTarget: UInt32 ($FFFFFFFF if none) │ └── CatchRegister: UInt8 - └── DebugInfo: TSouffleDebugInfo (optional) - ├── SourceFile: string - ├── LineMap: array of (PC, Line, Column) - └── LocalNames: array of (Slot, Name, StartPC, EndPC) + ├── DebugInfo: TSouffleDebugInfo (optional) + │ ├── SourceFile: string + │ ├── LineMap: array of (PC, Line, Column) + │ └── LocalNames: array of (Slot, Name, StartPC, EndPC) + └── LocalTypes: array of TSouffleLocalType (per-slot type hints) + ├── LocalTypeCount: UInt8 + └── Per slot: sltUntyped (0), sltInteger (1), sltFloat (2), + sltBoolean (3), sltString (4), sltReference (5) ``` +The compiler infers type hints from literal initializers (e.g., `const x = 42` gets `sltInteger`, `const y = 3.14` gets `sltFloat`). These hints are stored on the template and used to emit type-specialized load/store opcodes (`OP_GET_LOCAL_INT`, `OP_SET_LOCAL_FLOAT`, etc.) instead of generic `OP_GET_LOCAL`/`OP_SET_LOCAL`. At runtime the typed opcodes are functionally identical to the generic versions, but they carry type information for future WASM generation and JIT compilation. + ### Constant Pool Six constant kinds are supported: @@ -368,6 +798,9 @@ The `.sbc` (Souffle ByteCode) binary format enables ahead-of-time compilation an │ HasDebug: UInt8 (boolean) │ │ [if debug:] │ │ DebugInfo block │ +│ LocalTypeCount: UInt8 │ +│ Per local type: │ +│ Kind: UInt8 (0=untyped..5=ref) │ └──────────────────────────────────────┘ ``` @@ -395,7 +828,93 @@ The `ScriptLoader`, `TestRunner`, and `BenchmarkRunner` all support switching be When `--emit` is used without a path, the output filename is derived from the input (e.g., `example.js` → `example.sbc`). -## GocciaScript Runtime Bridge +## Current State and Bridge Architecture + +### What Works Natively + +The following features execute entirely within the Souffle VM without crossing the language boundary: + +- **Primitives**: Integer and float arithmetic, string concatenation, boolean logic, nil semantics (with null/undefined distinction via flags) +- **Variables**: `const`/`let` declarations with compile-time const enforcement, proper scoping, closures with upvalue capture/close +- **Compound types**: Array/record literals, property access, indexed access, `delete`, spread, rest destructuring +- **Control flow**: If/else, ternary, logical and/or, nullish coalescing, short-circuit evaluation +- **Functions**: Arrow functions, shorthand methods, default parameters, rest parameters, variadic argument packing +- **Simple classes**: Constructor + named methods compiled to blueprint opcodes (`OP_NEW_BLUEPRINT`, `OP_INHERIT`, `OP_RECORD_SET`, `OP_INSTANTIATE`) +- **Exception handling**: try/catch/finally, throw, handler-table model +- **Delegates**: Array prototype methods (`push`, `pop`, `shift`, `unshift`, `join`, `indexOf`, `includes`, `slice`, `reverse`, `concat`, `fill`, `at`, `forEach`, `map`, `filter`, `find`, `findIndex`, `findLast`, `findLastIndex`, `every`, `some`, `reduce`, `reduceRight`, `sort`, `flat`, `flatMap`, `splice`, `toString`, `length`) dispatch within the VM via delegate chain — zero wrapping. Mutating methods (`push`, `pop`, `shift`, `unshift`, `splice`, `reverse`, `sort`, `fill`) immediately sync changes to the bridge cache via `SyncSouffleArrayToCache` +- **Global built-ins**: All interpreter-side built-ins (`console`, `Math`, `JSON`, `Object`, `Array`, etc.) are available as wrapped values + +### What Uses Bridge Code + +The bridge layer is the set of components in `TGocciaRuntimeOperations` that convert between `TSouffleValue` and `TGocciaValue` to delegate work to the GocciaScript interpreter/evaluator. This layer implements most Tier 2 operations by unwrapping Souffle values, calling the existing evaluator, and wrapping the result back. The bridge includes: + +| Component | Role | Size | +|-----------|------|------| +| `UnwrapToGocciaValue` / `ToSouffleValue` | Value conversion between the two type systems | ~200 LOC | +| `TGocciaSouffleProxy` | Wraps Souffle compound types as `TGocciaValue` for interpreter consumption | ~100 LOC | +| `TGocciaWrappedValue` | Wraps `TGocciaValue` as Souffle heap object for VM register storage | ~50 LOC | +| `TGocciaSouffleClosureBridge` | Wraps `TSouffleClosure` as `TGocciaFunctionBase` for interpreter callbacks | ~80 LOC | +| `FClosureBridgeCache` / `FArrayBridgeCache` | Forward caches (`TSouffleHeapObject` → `TGocciaValue`) to avoid re-wrapping | ~30 LOC | +| `FArrayBridgeReverse` | Reverse cache (`TGocciaArrayValue` → `TSouffleArray`) for reference identity preservation | ~10 LOC | +| `SyncCachedGocciaToSouffle` | One-way sync at bridge entry: pushes `TGocciaArrayValue` contents to `TSouffleArray` | ~15 LOC | +| `SyncSouffleArrayToCache` | Immediate sync after native array mutations: pushes `TSouffleArray` contents to `TGocciaArrayValue` | ~15 LOC | +| `CreateBridgedContext` | Maps VM globals to an interpreter scope for evaluator calls | ~80 LOC | +| `FPendingClasses` | Deferred class definitions that require interpreter evaluation | ~50 LOC | + +The bridge is currently used by these runtime operations: + +- **`Invoke`** / **`Construct`**: For non-closure callables (wrapped `TGocciaFunctionBase`, `TGocciaClassValue`), unwraps the callee, converts arguments, calls via the GocciaScript evaluator, wraps the result +- **`GetProperty`** / **`SetProperty`**: For wrapped values (classes, promises, symbols, etc.), unwraps the target, delegates to `TGocciaValue.GetProperty`/`SetProperty` +- **`IsInstance`** / **`TypeOf`**: For wrapped values, delegates to the GocciaScript type system +- **`ExtendedOperation` sub-ops**: All 13 `GOCCIA_EXT_*` sub-opcodes delegate to GocciaScript evaluator methods +- **Complex class evaluation**: `GOCCIA_EXT_EVAL_CLASS` bootstraps an interpreter scope from VM globals and evaluates the class AST + +### The Unwrap-Delegate-Wrap Cycle + +The current runtime follows this pattern for most Tier 2 operations: + +```text +1. VM calls RuntimeOps.SomeOperation(souffleValue) [Tier 2 dispatch] +2. RuntimeOps converts: gocciaValue := UnwrapToGocciaValue(souffleValue) [bridge] +3. RuntimeOps delegates: result := gocciaValue.SomeMethod(args) [evaluator] +4. RuntimeOps converts: souffleResult := ToSouffleValue(result) [bridge] +5. VM stores souffleResult in register [Tier 1] +``` + +This cycle has three costs: +- **Allocation**: Each unwrap/wrap may allocate proxy or wrapper objects +- **Cache pollution**: Two parallel value hierarchies active simultaneously +- **Architectural coupling**: The runtime imports `Goccia.Evaluator`, `Goccia.Interpreter`, and `Goccia.Engine` — binding the entire GocciaScript interpreter into the bytecode path + +### Bridge Constraints + +1. **Two GC systems**: Bridge objects must be tracked by both the Souffle GC (as `TSouffleHeapObject`) and the GocciaScript GC (as `TGocciaValue`), complicating memory management. + +2. **Built-in subclassing**: Classes extending built-in constructors (`Array`, `Map`, `Set`, `Promise`, `Object`) are deferred to the interpreter because `OP_INHERIT` requires both class and superclass to be `TSouffleBlueprint`s, while built-in constructors are `TGocciaClassValue`s. + +3. **Architectural coupling**: The runtime imports `Goccia.Evaluator`, `Goccia.Interpreter`, and `Goccia.Engine`, binding the interpreter into the bytecode path for bridge operations. + +### Target Architecture + +The long-term goal is to **eliminate the bridge entirely** by having `TGocciaRuntimeOperations` implement JavaScript semantics directly on Souffle types (`TSouffleValue`, `TSouffleArray`, `TSouffleRecord`, `TSouffleBlueprint`). The target: + +```text +1. VM calls RuntimeOps.SomeOperation(souffleValue) [Tier 2 dispatch] +2. RuntimeOps operates directly on souffleValue [native Souffle types] +3. VM stores result in register [Tier 1] +``` + +This requires a ground-up rewrite of `TGocciaRuntimeOperations` — not incremental patching of the bridge. The rewrite would: + +- Remove all `UnwrapToGocciaValue` / `ToSouffleValue` conversions +- Implement prototype chain walking on `TSouffleRecord` delegate chains +- Implement type coercion (ToPrimitive, ToString, ToNumber) natively on `TSouffleValue` +- Compile all class features (getters, setters, statics, private members, decorators) to Tier 1 + Tier 2 opcodes, eliminating `FPendingClasses` +- Implement all built-in methods as `TSouffleNativeFunction` delegates operating on Souffle types + +Until the rewrite, the bridge code is functional and correct — both execution modes pass 100% of the test suite. The bridge adds overhead from value conversion and dual GC tracking, but does not limit language feature coverage. + +## GocciaScript Runtime Bridge (Current Implementation) `TGocciaSouffleBackend` (`Goccia.Engine.Backend.pas`) bridges GocciaScript to the Souffle VM: @@ -407,10 +926,14 @@ When `--emit` is used without a path, the output filename is derived from the in `TGocciaRuntimeOperations` (`Goccia.Runtime.Operations.pas`) implements `TSouffleRuntimeOperations` with GocciaScript semantics. It bridges between `TSouffleValue` and `TGocciaValue` at the runtime boundary: -- **`ToSouffleValue`** / **`UnwrapToGocciaValue`** — Convert between value systems -- **`TGocciaWrappedValue`** — Wraps a `TGocciaValue` as a `TSouffleHeapObject` for VM register storage -- **`TGocciaSouffleClosureBridge`** — Wraps a `TSouffleClosure` as a `TGocciaFunctionBase`, enabling Souffle closures to be called by GocciaScript built-in methods (e.g., `Array.prototype.map` callbacks) +- **`ToSouffleValue`** / **`UnwrapToGocciaValue`** — Convert between value systems. `UnwrapToGocciaValue` creates a `TGocciaArrayValue` bridge for `TSouffleArray` (cached in `FArrayBridgeCache`) and a lazy `TGocciaSouffleProxy` for `TSouffleRecord`. `ToSouffleValue` checks `FArrayBridgeReverse` to recover the original `TSouffleArray` from a bridge `TGocciaArrayValue`, preserving reference identity across round-trips +- **`TGocciaSouffleProxy`** — A single `TGocciaValue` subclass that wraps any Souffle compound type. Property access is delegated to `TGocciaRuntimeOperations.ResolveProxyGet`/`ResolveProxySet`, which dispatch based on the target's type. This is a higher-order function pattern: one proxy class handles all compound types, and the resolution logic lives in the runtime. No per-type subclassing needed +- **`TGocciaWrappedValue`** — Wraps a `TGocciaValue` as a `TSouffleHeapObject` for VM register storage (used for types without native VM representation: classes, promises, symbols, etc.) +- **`TGocciaSouffleClosureBridge`** — Wraps a `TSouffleClosure` as a `TGocciaFunctionBase`, enabling Souffle closures to be called by GocciaScript built-in methods (e.g., `Array.prototype.map` callbacks). At bridge entry, `SyncCachedGocciaToSouffle` pushes GocciaScript-side array changes to their Souffle counterparts. No exit sync is needed — native array mutations sync immediately via `SyncSouffleArrayToCache` +- **Delegate registration** — `RegisterDelegates` creates VM-native delegate records for arrays and records (with all array prototype methods as `TSouffleNativeFunction` callbacks) and assigns them to the VM's default delegate slots. Method calls like `arr.push(6)` are dispatched entirely within the VM without crossing the language boundary. Mutating array methods immediately sync changes to the bridge cache +- **Native compound fast paths** — `GetProperty`, `SetProperty`, `GetIndex`, `SetIndex`, `HasProperty`, `DeleteProperty`, and `TypeOf` all check for `TSouffleArray`/`TSouffleRecord` before falling through to wrapped value unwrapping, avoiding unnecessary materialization - **Auto-boxing** — `GetProperty` performs primitive boxing when a direct property lookup returns `nil`, enabling prototype methods on primitives (e.g., `(42).toFixed(2)`) +- **Array bridge sync model** — Bridged arrays have dual representation: `TSouffleArray` (read by the VM) and `TGocciaArrayValue` (read by GocciaScript built-ins). `TGocciaArrayValue` is the authoritative source. Two sync paths keep them consistent: (1) `SyncSouffleArrayToCache` — called immediately after native delegate methods mutate a `TSouffleArray`, propagates the change to the cached `TGocciaArrayValue`; (2) `SyncCachedGocciaToSouffle` — called once at bridge entry (when GocciaScript calls back into the Souffle VM), pushes `TGocciaArrayValue` contents to the `TSouffleArray`. `FArrayBridgeReverse` (never cleared at bridge depth 0) preserves reference identity for arrays held by long-lived objects like Promises ## File Organization @@ -418,19 +941,21 @@ All Souffle VM source files live in the `souffle/` directory with `Souffle.` pre | File | Description | |------|-------------| -| `Souffle.Value.pas` | `TSouffleValue` tagged union, constructors, type checks, truthiness | -| `Souffle.Heap.pas` | `TSouffleHeapObject` base class, `TSouffleString` | +| `Souffle.Value.pas` | `TSouffleValue` packed record (26B), `TSouffleInlineString`, constructors, `SouffleGetString`/`SouffleIsStringValue`, type checks, truthiness | +| `Souffle.Heap.pas` | `TSouffleHeapObject` base class, `TSouffleHeapString` (long string fallback), heap kind constants | +| `Souffle.Compound.pas` | `TSouffleArray` (dense array), `TSouffleRecord` (unified compound: plain key-value map or blueprint-backed with slots), `TSouffleBlueprint` (type descriptor with method record and optional super blueprint) | | `Souffle.Bytecode.pas` | Opcode definitions, instruction encoding/decoding helpers | -| `Souffle.Bytecode.Chunk.pas` | `TSouffleFunctionPrototype`, constant pool, upvalue descriptors | +| `Souffle.Bytecode.Chunk.pas` | `TSouffleFunctionTemplate`, constant pool, upvalue descriptors | | `Souffle.Bytecode.Module.pas` | `TSouffleBytecodeModule`, import/export tables | | `Souffle.Bytecode.Binary.pas` | `.sbc` serialization/deserialization | | `Souffle.Bytecode.Debug.pas` | `TSouffleDebugInfo`, source line mapping, local variable info | | `Souffle.VM.pas` | Core VM: dispatch loop, register file, execution | | `Souffle.VM.CallFrame.pas` | `TSouffleVMCallFrame`, `TSouffleCallStack` | | `Souffle.VM.Closure.pas` | `TSouffleClosure` (prototype + upvalue array) | +| `Souffle.VM.NativeFunction.pas` | `TSouffleNativeFunction` (Pascal callback as callable heap object) | | `Souffle.VM.Upvalue.pas` | `TSouffleUpvalue` (open/closed variable capture) | | `Souffle.VM.Exception.pas` | `TSouffleHandlerStack`, `ESouffleThrow` | -| `Souffle.VM.RuntimeOperations.pas` | `TSouffleRuntimeOperations` abstract interface | +| `Souffle.VM.RuntimeOperations.pas` | `TSouffleRuntimeOperations` abstract interface for language-specific semantics | | `Souffle.GarbageCollector.pas` | Mark-and-sweep GC for `TSouffleHeapObject` | GocciaScript-specific bridge files in `units/`: @@ -438,7 +963,12 @@ GocciaScript-specific bridge files in `units/`: | File | Description | |------|-------------| | `Goccia.Engine.Backend.pas` | `TGocciaSouffleBackend` — orchestration, built-in registration | -| `Goccia.Compiler.pas` | `TGocciaCompiler` — AST to Souffle bytecode | +| `Goccia.Compiler.pas` | `TGocciaCompiler` — AST to Souffle bytecode, top-level compilation dispatch | +| `Goccia.Compiler.Expressions.pas` | Expression compilation: functions, methods, identifiers, typed local load/store | +| `Goccia.Compiler.Statements.pas` | Statement compilation: variables, classes (`IsSimpleClass` + `CompileClassDeclaration`), control flow | +| `Goccia.Compiler.Context.pas` | `TGocciaCompilationContext` — compilation state passed through sub-units | +| `Goccia.Compiler.Scope.pas` | `TGocciaCompilerScope` — lexical scope tracking, local/upvalue resolution, type hints | +| `Goccia.Compiler.ConstantFolding.pas` | Compile-time constant folding for arithmetic and comparison expressions | | `Goccia.Runtime.Operations.pas` | `TGocciaRuntimeOperations` — GocciaScript runtime semantics | ## WASM 3.0 Alignment @@ -449,6 +979,8 @@ The architecture is designed to make a future WASM 3.0 backend a natural fit: |-----------------|-------------------| | `TSouffleValue` tagged union | `anyref` hierarchy (`i31ref` for small ints/bools, GC structs for boxed values) | | `TSouffleHeapObject` | GC struct types with subtyping (`ref.cast`, `ref.test`) | +| `TSouffleArray` | GC array type (`array.new`, `array.get`, `array.set`) | +| `TSouffleRecord` | GC struct (static fields) or runtime hash map (dynamic keys) | | `TSouffleClosure` | GC struct with `funcref` field + upvalue array | | `TSouffleUpvalue` | Mutable GC struct field | | Handler-table exception model | `try_table` / `throw` / `throw_ref` / `exnref` | @@ -467,7 +999,8 @@ The architecture is designed to make a future WASM 3.0 backend a natural fit: | `svkInteger` (large) | `(ref $boxed_i64)` GC struct with `i64` field | | `svkFloat` | `(ref $boxed_f64)` GC struct with `f64` field | | `svkReference` → String | `(ref $string)` GC array of `i8` | -| `svkReference` → Object | `(ref $object)` GC struct | +| `svkReference` → Array | `(ref $array)` GC array of `anyref` (`array.new`, `array.get`, `array.set`) | +| `svkReference` → Record | `(ref $record)` GC struct (static fields) or runtime hash map (dynamic) | | `svkReference` → Closure | `(ref $closure)` GC struct with `funcref` + upvalue array | Key WASM 3.0 features this leverages: @@ -482,20 +1015,7 @@ A WASM backend would read `TSouffleBytecodeModule` and emit a `.wasm` binary. Th ## Known Limitations -The Souffle VM bytecode backend is functional for basic scripts but has several areas not yet implemented. These are tracked here to prevent duplicate work and to guide future contributors. - -### Not Yet Implemented (Deferred) - -These features are stubbed in the runtime operations layer and will need real implementations before the corresponding GocciaScript features work in bytecode mode: - -| Feature | Current State | What's Needed | -|---------|---------------|---------------| -| **Iteration** (`for...of`, spread) | `GetIterator` returns the value unchanged; `IteratorNext` always returns done; `SpreadInto` is a no-op | Wire to GocciaScript's `GetIteratorFromValue` / `TGocciaIteratorValue` protocol; handle arrays, strings, maps, sets, and user-defined iterables | -| **Module imports** | `ImportModule` returns `SouffleNil` | Resolve module paths, compile or load `.sbc` modules, return the module namespace object | -| **Async/await** | `AwaitValue` returns its argument unchanged | Integrate with `TGocciaPromiseValue` and the microtask queue | -| **Closure receiver slot** | `CallClosure` accepts `AReceiver` but does not store it into the new frame's register window | Define a receiver register convention in `TSouffleFunctionPrototype` and assign `AReceiver` into that slot when creating the closure call frame | -| **Template literal parsing** | The compiler re-lexes/re-parses each `${...}` interpolation from the raw template string | Enhance the parser to produce a template AST node with pre-parsed static and expression parts; the compiler would then iterate those directly | -| **Binary endianness** | `.sbc` serialization uses native-endian writes | Normalize to little-endian for cross-platform `.sbc` portability | +The Souffle VM bytecode backend passes 100% of the GocciaScript test suite (3,358 tests across 514 test files). The limitations below are structural constraints, not correctness gaps. ### Intentionally Not Changed (Rejected Findings) @@ -507,28 +1027,207 @@ These were reviewed and determined to be correct or not applicable: | **Tokens leak in `Goccia.Compiler.Test.pas`** | Not a leak | `Lexer.ScanTokens` returns a reference to the lexer's internal `FTokens` list, which the lexer frees in its destructor. Manually freeing `Tokens` causes a double-free crash. | | **`Souffle.VM.RuntimeOperations.pas` uses `{$I Souffle.inc}`** | Intentional | This unit is part of the Souffle VM layer and follows Souffle naming conventions. The abstract `TSouffleRuntimeOperations` class is VM-level infrastructure, not a GocciaScript bridge. | | **`InitScope`/`SuperInitScope` leak in `InstantiateClass`** | Not a leak | `TGocciaClassInitScope` is a `TGocciaScope` subclass that auto-registers with the GC in its constructor. All scopes are GC-managed — they are collected during sweep, not manually freed. This is consistent with every scope creation in the evaluator. | -| **null vs undefined conflation in runtime ops** | Intentional design | `svkNil` maps to `undefined`; `null` is a wrapped `TGocciaNullLiteralValue` reference registered as a global via `OP_RT_GET_GLOBAL('null')`. This keeps language-specific null semantics out of the VM's value system — adding `svkNull` would violate the language-agnostic design principle. | -| **`InitIndex` ignores array index** | Correct for current use | `OP_RT_INIT_INDEX` is emitted by the compiler for array literal initialization, which always emits elements in sequential order. `TGocciaArrayValue.Elements.Add` appends in order, matching the compiler's emission pattern. | +| **null vs undefined via nil flags** | Intentional design | `svkNil` with `Flags=0` (the VM's default) maps to `undefined` in GocciaScript; `Flags=1` maps to `null`. The compiler emits `OP_LOAD_NIL` with B=0 for undefined literals and B=1 for null literals. `SouffleValuesEqual` compares flags for nil values, so `null === null` is true but `null === undefined` is false. All VM-internal absent values (uninitialized registers, missing properties, function-with-no-return) naturally produce flags=0, matching JavaScript's `undefined` semantics. The runtime constants `GOCCIA_NIL_UNDEFINED=0`, `GOCCIA_NIL_NULL=1`, `GOCCIA_NIL_HOLE=2` are defined in `Goccia.Runtime.Operations.pas` — the VM itself only knows about `SOUFFLE_NIL_DEFAULT=0`. | | **`MergeFileResult` string comparison for undefined** | Style preference | The `GetProperty(...).ToStringLiteral.Value = 'undefined'` check works correctly because `TGocciaUndefinedLiteralValue.ToStringLiteral` always returns `'undefined'`. A type check would be marginally more robust but the current code has no known failure mode. | +### Binary Endianness + +`.sbc` serialization uses native-endian writes. Cross-platform `.sbc` portability requires normalizing to little-endian. + ### Constant Pool Limitation (ABC Encoding) -The ABC instruction format encodes operand B and C as 8-bit values. This limits constant pool indices used in `OP_RT_SET_PROP`, `OP_RT_INIT_FIELD`, `OP_RT_GET_PROP`, and `OP_RT_DEL_PROP` to 255 entries per prototype. The compiler raises a clear error if this limit is exceeded. A future ABx-style wide-operand variant could lift this restriction if needed. +The ABC instruction format encodes operand B and C as 8-bit values. This limits constant pool indices used in `OP_RECORD_GET`, `OP_RECORD_SET`, `OP_RT_SET_PROP`, `OP_RT_GET_PROP`, and `OP_RT_DEL_PROP` to 255 entries per prototype. `OP_RECORD_DELETE` uses ABx encoding (16-bit Bx) and does not have this limitation. The compiler raises a clear error if the ABC limit is exceeded. A future ABx-style wide-operand variant could lift this restriction for the remaining opcodes if needed. + +### Complex Class Compilation + +Classes with getters, setters, statics, private members, or decorators are deferred to the interpreter via `GOCCIA_EXT_EVAL_CLASS`. Classes extending built-in constructors (`Array`, `Map`, `Set`, `Promise`, `Object`) are also deferred because `OP_INHERIT` requires both sides to be `TSouffleBlueprint`s. Compiling all class features to Tier 1 + Tier 2 opcodes is a target for the bridge elimination effort. ### NaN Handling in the Constant Pool Float constant deduplication uses a raw IEEE 754 bit-pattern check (`FloatBitsAreNaN`) rather than FPC's `Math.IsNaN`. This avoids introducing language-runtime dependencies into the Souffle layer and works reliably across platforms including AArch64, where FPC's floating-point behavior has known pitfalls (see [code-style.md](code-style.md) § Platform Pitfall). +## Responsibility Boundaries + +The bytecode execution pipeline has four distinct layers with strict boundaries. Mixing responsibilities across layers is an anti-pattern that compromises the VM's generality. These boundaries were established through a systematic audit that removed 13 JS-specific opcodes and 16 JS-specific abstract methods from the Souffle VM. + +```text +┌──────────────────────────────────────────────────────────────┐ +│ Interpreter (Goccia.Evaluator*.pas) │ +│ Direct AST execution — also invoked by bridge code │ +│ Owns: scope chain walking, evaluator purity, `this` binding│ +│ Note: Invoked from bytecode path for complex class │ +│ evaluation, built-in subclassing, and wrapped value │ +│ operations. Target: elimination from bytecode path. │ +└──────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────┐ +│ Compiler (Goccia.Compiler*.pas) │ +│ AST → Souffle bytecode translation │ +│ Owns: desugaring, constant folding, OP_LOAD_NIL flags, │ +│ simple-vs-complex class detection, type hint emission,│ +│ const enforcement (compile-time), OP_RT_EXT sub-ops │ +│ Boundary: emits only Souffle opcodes; no TGocciaValue refs │ +│ Note: Extension sub-opcodes in Goccia.Compiler.ExtOps.pas │ +└──────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────┐ +│ Runtime (Goccia.Runtime.Operations.pas) │ +│ Language-specific callbacks for OP_RT_* opcodes │ +│ Owns: null/undefined flag interpretation, prototype chains,│ +│ type coercion, property descriptors, Symbol keys, │ +│ const enforcement (runtime, global scope), │ +│ ExtendedOperation dispatch for GOCCIA_EXT_* sub-ops │ +│ Bridges TSouffleValue ↔ TGocciaValue (see above) │ +│ Target: implements JS semantics directly on Souffle types │ +│ Boundary: implements TSouffleRuntimeOperations interface │ +└──────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────┐ +│ VM (souffle/*.pas) │ +│ Opcode dispatch, register file, call frames, GC │ +│ Owns: delegate chain lookup, upvalue management, │ +│ exception handler stack, native function invocation, │ +│ record property flag enforcement (PutChecked/DeleteChecked) │ +│ Boundary: NO language imports (Goccia.* forbidden) │ +│ NO language keywords in opcodes │ +│ All absent values are svkNil (flags=0) │ +│ ExtendedOperation dispatches opaque sub-op bytes │ +└──────────────────────────────────────────────────────────────┘ +``` + +### What Lives Where + +The following table clarifies where specific concerns are handled: + +| Concern | Layer | Rationale | +|---------|-------|-----------| +| Property mutability (writable/configurable) | VM (Tier 1) | Per-property flags are the fundamental primitive; enforced by `PutChecked`/`DeleteChecked` | +| Property visibility (public/private) | Runtime (Tier 2) | Language-specific semantics | +| Accessor invocation (getter/setter) | Runtime (Tier 2) | At the VM level, getters, setters, and methods are all functions — the difference is in how they are invoked. The runtime decides whether a property access triggers a getter call. | +| `null` vs `undefined` distinction | Runtime (Tier 2) | The VM only knows `svkNil` with a flags byte. GocciaScript interprets flags=0 as `undefined` and flags=1 as `null`. Other languages may not have this distinction. | +| Prototype chain walking | Runtime (Tier 2), VM delegate chain (Tier 1) | The VM's delegate chain provides O(depth) method lookup for native records. Full prototype semantics (Symbol keys, accessor invocation, `Object.getPrototypeOf`) are in the runtime. | +| `const` enforcement (locals) | Compiler | Emit `OP_RT_EXT(GOCCIA_EXT_THROW_TYPE_ERROR)` at assignment sites for known-const locals/upvalues. Caught at compile time. | +| `const` enforcement (globals) | Runtime | `FConstGlobals` dictionary + `SetGlobal` check. Caught at runtime because globals are shared across modules. | +| Class compilation (simple) | Compiler → VM | Blueprint opcodes: `OP_NEW_BLUEPRINT`, `OP_INHERIT`, `OP_RECORD_SET`, `OP_INSTANTIATE` | +| Class compilation (complex) | Compiler → Runtime → Interpreter | `GOCCIA_EXT_EVAL_CLASS` defers to the interpreter (bridge code, to be eliminated) | +| Built-in methods (native) | VM delegates | `TSouffleNativeFunction` in delegate records — zero language boundary crossing | +| Built-in methods (wrapped) | Runtime → Interpreter | Wrapped `TGocciaValue` methods (bridge code, to be eliminated) | + +### Anti-patterns + +These anti-patterns were identified during the boundary cleanup and are enforced going forward: + +| Anti-pattern | Why it's wrong | Correct approach | +|-------------|---------------|-----------------| +| VM opcode named after a language keyword (e.g., `OP_LOAD_UNDEFINED`) | Couples VM to one language; other frontends don't have "undefined" | Use `OP_LOAD_NIL` with flags; runtime interprets flags | +| VM checking `Flags` to decide `null` vs `undefined` semantics | The VM should not know what the flags mean | Runtime interprets flags in its value handling | +| Compiler importing `TGocciaValue` or `TGocciaScope` | Compiler emits bytecode, not runtime objects | Compiler works with `TGocciaCompilationContext` and `TSouffleFunctionTemplate` | +| Runtime modifying VM internals (register file, IP) | The VM is a black box to the runtime | Runtime returns `TSouffleValue`; VM handles register storage | +| Souffle unit importing `Goccia.*` | Breaks VM independence; prevents extraction to a separate project | All cross-boundary ops go through `TSouffleRuntimeOperations` | +| Adding JS-specific opcodes to Tier 2 (e.g., `OP_RT_SPREAD_OBJ`) | Pollutes the VM's opcode space with one language's concepts | Use `OP_RT_EXT` with language-specific sub-opcode constants | +| Extending bridge code to handle more types | Each fix increases coupling rather than reducing it | Rewrite the runtime to operate directly on Souffle types | +| Adding language-specific methods to `TSouffleRuntimeOperations` abstract class | Forces all language frontends to implement operations they don't need | Use `ExtendedOperation` for language-specific features | + +### Boundary Cleanup History + +The current boundary was established through a systematic multi-session process: + +1. **Audit** — Identified 13 JS-specific Tier 2 opcodes (`OP_RT_SPREAD`, `OP_RT_SPREAD_OBJ`, `OP_RT_OBJ_REST`, `OP_RT_EVAL_CLASS`, `OP_RT_FINALIZE_ENUM`, `OP_RT_SUPER_GET`, `OP_RT_THROW_TYPE_ERROR`, `OP_RT_REQUIRE_OBJECT`, `OP_RT_REQUIRE_ITERABLE`, `OP_RT_DEF_GETTER`, `OP_RT_DEF_SETTER`, `OP_RT_DEF_STATIC_GETTER`, `OP_RT_DEF_STATIC_SETTER`) and 16 JS-specific abstract methods on `TSouffleRuntimeOperations`. + +2. **Mechanism selection** — Evaluated three alternatives (per-feature opcodes, multiple extension opcodes, single extension opcode). Chose single `OP_RT_EXT` with sub-opcode dispatch. See [OP_RT_EXT](#design-decision-why-one-extension-opcode) for the rationale. + +3. **Removal** — All 13 opcodes and 16 abstract methods were removed from the Souffle VM. The JS-specific functionality was preserved as private methods on `TGocciaRuntimeOperations`, dispatched via `ExtendedOperation`. The VM dispatch loop, compiler, and runtime were updated in a coordinated change. + +4. **Consolidation** — Separate spread opcodes (`OP_RT_CALL_SPREAD`, `OP_RT_CALL_METHOD_SPREAD`) were consolidated into the C flags byte of `OP_RT_CALL`/`OP_RT_CALL_METHOD`. `OP_RECORD_FREEZE` was removed (freezing is a `TSouffleRecord` method, not an opcode). `OP_UNPACK` was made fully Tier 1 by inlining the array rest logic. + +5. **Verification** — After cleanup: Tier 1 has 44 language-agnostic opcodes, Tier 2 has 44 runtime-dispatched opcodes + 1 extension opcode. The `souffle/` directory has zero `Goccia.*` imports. The abstract interface has 45 generic methods + 1 extension entry point. + +### NaN/Infinity Rationale + +IEEE 754 floating-point is enabled at the FreePascal compiler level (`SetExceptionMask` in both `Goccia.Engine.pas` and `Souffle.VM.pas`). Both classes save the previous FPU exception mask in their constructor and restore it in their destructor, so the host application's FPU state is not permanently altered. This means: +- FPC's `Double` type natively produces `NaN` for `0.0/0.0`, `Infinity` for `1.0/0.0`, etc. +- The VM stores these as ordinary `svkFloat` values — no special float flags or singletons +- The interpreter's `TGocciaNumberLiteralValue` stores real IEEE 754 `NaN`/`Infinity` Doubles directly +- Language-specific NaN/Infinity semantics (e.g., `NaN !== NaN`, `typeof NaN === "number"`) are handled by the evaluator/runtime, not the VM + +## Souffle VM as a Standalone Project + +Souffle is designed to be extracted from the GocciaScript repository as an independent project. This is not hypothetical — it is a core architectural constraint that drives all design decisions. + +### Independence Guarantee + +The `souffle/` directory contains a self-contained bytecode VM with zero knowledge of any specific programming language: + +- **Zero language imports**: No `Goccia.*` units, no `uses` clauses referencing the host language +- **Generic value system**: `TSouffleValue` represents values without language-specific type tags +- **Abstract runtime interface**: `TSouffleRuntimeOperations` is the sole injection point for language semantics +- **Own GC**: `Souffle.GarbageCollector.pas` manages Souffle heap objects independently +- **Self-describing binary format**: `.sbc` files include a runtime tag, version, and debug info. Serialization currently uses native endianness; cross-platform portability requires byte-order normalization (see [Known Limitations](#known-limitations)) + +### Multi-Frontend Vision + +The architecture supports compiling multiple languages to the same bytecode: + +```text +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ GocciaScript │ │ Boo │ │ C# │ │ Ruby-like │ +│ (ES subset) │ │ │ │ │ │ │ +└──────┬───────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ + │ │ │ │ + Compiler Compiler Compiler Compiler + │ │ │ │ + ▼ ▼ ▼ ▼ +┌──────────────────────────────────────────────────────────────┐ +│ Souffle Bytecode (.sbc) │ +│ Same instruction set, same encoding, same module format │ +└────────────────────────────┬─────────────────────────────────┘ + │ + ┌──────────────┼──────────────┐ + ▼ ▼ ▼ + ┌──────────────┐ ┌──────────┐ ┌──────────────┐ + │ Souffle VM │ │ WASM │ │ Native JIT │ + │ (FreePascal) │ │ Backend │ │ (future) │ + └──────────────┘ └──────────┘ └──────────────┘ +``` + +Each frontend provides: +1. **A compiler** — Translates its AST to `TSouffleBytecodeModule` +2. **A runtime** — Implements `TSouffleRuntimeOperations` with that language's semantics +3. **Sub-opcodes** — Defines `LANGUAGE_EXT_*` constants for `OP_RT_EXT` dispatch + +The VM provides: +1. **Execution** — Dispatch loop, register file, call frames, exception handling +2. **Compound types** — Arrays, records, blueprints (usable by any language) +3. **GC** — Mark-and-sweep for heap objects +4. **Delegates** — Method dispatch via delegate chains (any language can populate) + +### What Makes This Possible + +The design constraints that enable multi-frontend use: + +| Constraint | Enforcement | +|-----------|-------------| +| No language keywords in opcodes | `OP_LOAD_NIL` not `OP_LOAD_UNDEFINED` | +| Flags are opaque bytes | VM stores and compares them; runtime interprets them | +| No language-specific abstract methods | 45 generic methods; language extensions via `ExtendedOperation` | +| Record property flags are universal | Writable/configurable — every OOP language needs these | +| Getters, setters, methods are all functions | No accessor-specific opcodes; the runtime decides invocation semantics | +| Blueprint methods are just records | `OP_RECORD_SET` on a blueprint stores to its method record; no visibility opcodes | +| Runtime tag on modules | `"goccia-js"`, `"boo"`, `"csharp"` — loader rejects mismatches | + ## Design Principles -1. **Language-agnostic VM** — The VM knows about 5 value kinds and generic operations. All language semantics live in the runtime operations layer. +These principles emerged from four planning sessions and a systematic boundary cleanup: + +1. **Language-agnostic VM** — The VM knows about 6 value kinds and generic operations. All language semantics live in the runtime operations layer. The litmus test: *"Would a Boo, C#, or Ruby frontend need this opcode/method?"* If not, it belongs in `OP_RT_EXT` or the language's runtime, not the VM. + +2. **Compiler-side desugaring** — Language-specific features (classes, nullish coalescing, template literals, object spread) are compiled into sequences of generic VM instructions or `OP_RT_EXT` sub-opcodes. The compiler makes semantic choices, not the VM. + +3. **Self-contained value system** — `TSouffleValue` is independent of `TGocciaValue`. The current bridge layer converts between them, but the target architecture operates directly on Souffle types. No language-specific value types should leak into the VM layer. -2. **Compiler-side desugaring** — Language-specific features (classes, nullish coalescing, template literals) are compiled into sequences of generic VM instructions. The compiler makes semantic choices, not the VM. +4. **Minimal opcode surface** — New language features should be expressible using existing Tier 1 + Tier 2 opcodes. Adding a new Tier 2 opcode to the abstract interface is acceptable only when no combination of existing operations can express the semantics efficiently and the operation is **universally useful across languages**. Language-specific operations use `OP_RT_EXT` sub-opcodes. -3. **Self-contained value system** — `TSouffleValue` is independent of `TGocciaValue`. Bridge conversions happen at the runtime operations boundary, keeping the VM free of GocciaScript dependencies. +5. **Zero-overhead abstraction boundary** — The runtime tag on modules ensures type safety (a module compiled for one runtime cannot be loaded by another), but the actual dispatch is a single virtual method call per Tier 2 instruction. `OP_RT_EXT` adds one more virtual call for sub-opcode dispatch, but the runtime's `case` statement is compiled to a jump table. -4. **Minimal opcode surface** — New language features should be expressible using existing Tier 1 + Tier 2 opcodes. Adding a new Tier 2 opcode is acceptable only when no combination of existing operations can express the semantics efficiently. +6. **No language-runtime dependencies in the VM** — The `souffle/` directory must not import GocciaScript units (`Goccia.*`). All cross-boundary operations (NaN checks, type coercion, property semantics) use IEEE 754 bit-level operations or are delegated to the runtime operations interface. This ensures the VM layer remains extractable as a standalone project. -5. **Zero-overhead abstraction boundary** — The runtime tag on modules ensures type safety (a module compiled for one runtime cannot be loaded by another), but the actual dispatch is a single virtual method call per Tier 2 instruction. +7. **Tier 1 is universal, Tier 2 is pluggable** — Tier 1 opcodes have fixed semantics that every language needs (load/store, control flow, closures, arrays, records, blueprints, exceptions). Tier 2 opcodes delegate to an abstract interface that each language implements differently. The boundary between them is: *"Does the operation have one correct implementation, or does it depend on the language?"* -6. **No language-runtime dependencies in the VM** — The `souffle/` directory must not import GocciaScript units (`Goccia.*`). All cross-boundary operations (NaN checks, type coercion, property semantics) use IEEE 754 bit-level operations or are delegated to the runtime operations interface. This ensures the VM layer remains reusable for other language frontends. +8. **Properties over opcodes for language-specific VM features** — When the VM needs to expose a capability that languages use differently (record freezing, property flags), it provides a method or data field on the compound type rather than a dedicated opcode. This keeps the opcode table small and the API flexible. diff --git a/docs/value-system.md b/docs/value-system.md index abe22dee..83166bef 100644 --- a/docs/value-system.md +++ b/docs/value-system.md @@ -158,6 +158,9 @@ Used by: - `Function.prototype.call/apply/bind` — to validate that the receiver is callable. - Array method callbacks (`map`, `filter`, `reduce`, `sort`, etc.) — to validate user-provided callbacks. - `Set.prototype.forEach` and `Map.prototype.forEach` — to validate the callback argument. +- `IsDeepEqual` — to reject callable objects from deep structural comparison when TypeNames differ. + +**Important:** When code needs to cast to `TGocciaFunctionBase` after the check (e.g., accessor property invocation via `.Call()`), use `is TGocciaFunctionBase` instead of `IsCallable`. This is because `TGocciaClassValue` inherits from `TGocciaValue` (not `TGocciaFunctionBase`) but returns `True` for `IsCallable`. The RTTI check ensures the cast is safe. ## Virtual Property Access @@ -226,7 +229,7 @@ end; Special number singletons: `NaNValue`, `PositiveInfinityValue`, `NegativeInfinityValue`, `NegativeZeroValue`. -**Checking for special values:** Always use the property accessors (`IsNaN`, `IsInfinity`, `IsNegativeZero`) rather than inspecting `FValue` directly. Since special values store `0.0` in `FValue`, standard floating-point checks like `Math.IsNaN(FValue)` will return incorrect results. When checking for actual zero (not a special value), use `(Value = 0) and not IsNaN and not IsInfinite` — see `IsActualZero` in `Goccia.Evaluator.Arithmetic.pas` for the canonical helper. Similarly, the sort comparator in `Goccia.Values.ArrayValue.pas` uses `NumericRank` to map each special value to a distinct `Double` for correct ordering. +**Checking for special values:** Always use the property accessors (`IsNaN`, `IsInfinity`, `IsNegativeZero`) rather than inspecting `FValue` directly. Since special values store `0.0` in `FValue`, standard floating-point checks like `Math.IsNaN(FValue)` will return incorrect results. `IsNegativeZero` uses an endian-neutral `Int64 absolute` overlay (`Bits < 0`) to detect the sign bit — see [docs/code-style.md](code-style.md) for the pattern. When checking for actual zero (not a special value), use `(Value = 0) and not IsNaN and not IsInfinite` — see `IsActualZero` in `Goccia.Evaluator.Arithmetic.pas` for the canonical helper. Similarly, the sort comparator in `Goccia.Values.ArrayValue.pas` uses `NumericRank` to map each special value to a distinct `Double` for correct ordering. ### Number Prototype (`TGocciaNumberObjectValue`) diff --git a/souffle/Souffle.Bytecode.Binary.pas b/souffle/Souffle.Bytecode.Binary.pas index 05896448..32b3c6f8 100644 --- a/souffle/Souffle.Bytecode.Binary.pas +++ b/souffle/Souffle.Bytecode.Binary.pas @@ -21,7 +21,7 @@ TSouffleBytecodeWriter = class procedure WriteDouble(const AValue: Double); procedure WriteString(const AValue: string); procedure WriteBoolean(const AValue: Boolean); - procedure WriteFunctionPrototype(const AProto: TSouffleFunctionPrototype); + procedure WriteFunctionTemplate(const AProto: TSouffleFunctionTemplate); public constructor Create(const AStream: TStream); procedure WriteModule(const AModule: TSouffleBytecodeModule); @@ -37,7 +37,7 @@ TSouffleBytecodeReader = class function ReadDouble: Double; function ReadString: string; function ReadBoolean: Boolean; - function ReadFunctionPrototype: TSouffleFunctionPrototype; + function ReadFunctionTemplate: TSouffleFunctionTemplate; public constructor Create(const AStream: TStream); function ReadModule: TSouffleBytecodeModule; @@ -108,8 +108,8 @@ procedure TSouffleBytecodeWriter.WriteBoolean(const AValue: Boolean); WriteUInt8(0); end; -procedure TSouffleBytecodeWriter.WriteFunctionPrototype( - const AProto: TSouffleFunctionPrototype); +procedure TSouffleBytecodeWriter.WriteFunctionTemplate( + const AProto: TSouffleFunctionTemplate); var I: Integer; Constant: TSouffleBytecodeConstant; @@ -167,7 +167,7 @@ procedure TSouffleBytecodeWriter.WriteFunctionPrototype( // Nested functions WriteUInt16(UInt16(AProto.FunctionCount)); for I := 0 to AProto.FunctionCount - 1 do - WriteFunctionPrototype(AProto.GetFunction(I)); + WriteFunctionTemplate(AProto.GetFunction(I)); // Debug info WriteBoolean(Assigned(AProto.DebugInfo)); @@ -190,6 +190,11 @@ procedure TSouffleBytecodeWriter.WriteFunctionPrototype( WriteUInt32(AProto.DebugInfo.GetLocalInfo(I).EndPC); end; end; + + // Local type hints + WriteUInt8(AProto.LocalTypeCount); + for I := 0 to AProto.LocalTypeCount - 1 do + WriteUInt8(Ord(AProto.GetLocalType(UInt8(I)))); end; procedure TSouffleBytecodeWriter.WriteModule( @@ -232,7 +237,7 @@ procedure TSouffleBytecodeWriter.WriteModule( end; // Top-level function - WriteFunctionPrototype(AModule.TopLevel); + WriteFunctionTemplate(AModule.TopLevel); end; { TSouffleBytecodeReader } @@ -286,10 +291,10 @@ function TSouffleBytecodeReader.ReadBoolean: Boolean; Result := ReadUInt8 <> 0; end; -function TSouffleBytecodeReader.ReadFunctionPrototype: TSouffleFunctionPrototype; +function TSouffleBytecodeReader.ReadFunctionTemplate: TSouffleFunctionTemplate; var Name: string; - MaxRegs, ParamCount, UpvalueCount: UInt8; + MaxRegs, ParamCount, UpvalueCount, LocalTypeCount: UInt8; CodeCount: UInt32; ConstCount, FuncCount, HandlerCount: UInt16; I: Integer; @@ -304,7 +309,7 @@ function TSouffleBytecodeReader.ReadFunctionPrototype: TSouffleFunctionPrototype ParamCount := ReadUInt8; UpvalueCount := ReadUInt8; - Result := TSouffleFunctionPrototype.Create(Name); + Result := TSouffleFunctionTemplate.Create(Name); Result.MaxRegisters := MaxRegs; Result.ParameterCount := ParamCount; @@ -341,7 +346,7 @@ function TSouffleBytecodeReader.ReadFunctionPrototype: TSouffleFunctionPrototype // Nested functions FuncCount := ReadUInt16; for I := 0 to FuncCount - 1 do - Result.AddFunction(ReadFunctionPrototype); + Result.AddFunction(ReadFunctionTemplate); // Debug info HasDebug := ReadBoolean; @@ -360,6 +365,11 @@ function TSouffleBytecodeReader.ReadFunctionPrototype: TSouffleFunctionPrototype Result.DebugInfo := DebugInfo; end; + + // Local type hints + LocalTypeCount := ReadUInt8; + for I := 0 to LocalTypeCount - 1 do + Result.SetLocalType(UInt8(I), TSouffleLocalType(ReadUInt8)); end; function TSouffleBytecodeReader.ReadModule: TSouffleBytecodeModule; @@ -412,7 +422,7 @@ function TSouffleBytecodeReader.ReadModule: TSouffleBytecodeModule; Result.AddExport(ReadString, ReadUInt16); // Top-level function - Result.TopLevel := ReadFunctionPrototype; + Result.TopLevel := ReadFunctionTemplate; end; { File I/O helpers } diff --git a/souffle/Souffle.Bytecode.Chunk.pas b/souffle/Souffle.Bytecode.Chunk.pas index 37966fd3..0ea4a4e7 100644 --- a/souffle/Souffle.Bytecode.Chunk.pas +++ b/souffle/Souffle.Bytecode.Chunk.pas @@ -39,14 +39,23 @@ TSouffleExceptionHandler = record CatchRegister: UInt8; end; - TSouffleFunctionPrototype = class + TSouffleLocalType = ( + sltUntyped, + sltInteger, + sltFloat, + sltBoolean, + sltString, + sltReference + ); + + TSouffleFunctionTemplate = class private FName: string; FCode: array of UInt32; FCodeCount: Integer; FConstants: array of TSouffleBytecodeConstant; FConstantCount: Integer; - FFunctions: TObjectList; + FFunctions: TObjectList; FUpvalueDescriptors: array of TSouffleUpvalueDescriptor; FExceptionHandlers: array of TSouffleExceptionHandler; FExceptionHandlerCount: Integer; @@ -54,6 +63,9 @@ TSouffleFunctionPrototype = class FParameterCount: UInt8; FUpvalueCount: UInt8; FDebugInfo: TSouffleDebugInfo; + FLocalTypes: array of TSouffleLocalType; + FLocalTypeCount: UInt8; + FIsAsync: Boolean; function GetFunctionCount: Integer; public constructor Create(const AName: string); @@ -66,18 +78,18 @@ TSouffleFunctionPrototype = class function AddConstantInteger(const AValue: Int64): UInt16; function AddConstantFloat(const AValue: Double): UInt16; function AddConstantString(const AValue: string): UInt16; - function AddFunction(const AFunction: TSouffleFunctionPrototype): UInt16; + function AddFunction(const AFunction: TSouffleFunctionTemplate): UInt16; procedure AddUpvalueDescriptor(const AIsLocal: Boolean; const AIndex: UInt8); procedure AddExceptionHandler(const ATryStart, ATryEnd, ACatchTarget, AFinallyTarget: UInt32; const ACatchRegister: UInt8); function GetInstruction(const AIndex: Integer): UInt32; function GetConstant(const AIndex: Integer): TSouffleBytecodeConstant; - function GetFunction(const AIndex: Integer): TSouffleFunctionPrototype; + function GetFunction(const AIndex: Integer): TSouffleFunctionTemplate; function GetUpvalueDescriptor(const AIndex: Integer): TSouffleUpvalueDescriptor; function GetExceptionHandler(const AIndex: Integer): TSouffleExceptionHandler; - property Name: string read FName; + property Name: string read FName write FName; property CodeCount: Integer read FCodeCount; property ConstantCount: Integer read FConstantCount; property FunctionCount: Integer read GetFunctionCount; @@ -86,6 +98,11 @@ TSouffleFunctionPrototype = class property ParameterCount: UInt8 read FParameterCount write FParameterCount; property UpvalueCount: UInt8 read FUpvalueCount; property DebugInfo: TSouffleDebugInfo read FDebugInfo write FDebugInfo; + + procedure SetLocalType(const ASlot: UInt8; const AKind: TSouffleLocalType); + function GetLocalType(const ASlot: UInt8): TSouffleLocalType; + property LocalTypeCount: UInt8 read FLocalTypeCount; + property IsAsync: Boolean read FIsAsync write FIsAsync; end; const @@ -105,30 +122,31 @@ function FloatBitsAreNaN(const AValue: Double): Boolean; inline; ((Bits and $000FFFFFFFFFFFFF) <> 0); end; -{ TSouffleFunctionPrototype } +{ TSouffleFunctionTemplate } -constructor TSouffleFunctionPrototype.Create(const AName: string); +constructor TSouffleFunctionTemplate.Create(const AName: string); begin inherited Create; FName := AName; FCodeCount := 0; FConstantCount := 0; - FFunctions := TObjectList.Create(True); + FFunctions := TObjectList.Create(True); FExceptionHandlerCount := 0; FMaxRegisters := 0; FParameterCount := 0; FUpvalueCount := 0; FDebugInfo := nil; + FLocalTypeCount := 0; end; -destructor TSouffleFunctionPrototype.Destroy; +destructor TSouffleFunctionTemplate.Destroy; begin FFunctions.Free; FDebugInfo.Free; inherited; end; -function TSouffleFunctionPrototype.EmitInstruction( +function TSouffleFunctionTemplate.EmitInstruction( const AInstruction: UInt32): Integer; begin if FCodeCount >= Length(FCode) then @@ -138,7 +156,7 @@ function TSouffleFunctionPrototype.EmitInstruction( Inc(FCodeCount); end; -procedure TSouffleFunctionPrototype.PatchInstruction(const AIndex: Integer; +procedure TSouffleFunctionTemplate.PatchInstruction(const AIndex: Integer; const AInstruction: UInt32); begin if (AIndex < 0) or (AIndex >= FCodeCount) then @@ -146,7 +164,7 @@ procedure TSouffleFunctionPrototype.PatchInstruction(const AIndex: Integer; FCode[AIndex] := AInstruction; end; -function TSouffleFunctionPrototype.AddConstantNil: UInt16; +function TSouffleFunctionTemplate.AddConstantNil: UInt16; var I: Integer; begin @@ -163,7 +181,7 @@ function TSouffleFunctionPrototype.AddConstantNil: UInt16; Inc(FConstantCount); end; -function TSouffleFunctionPrototype.AddConstantBoolean( +function TSouffleFunctionTemplate.AddConstantBoolean( const AValue: Boolean): UInt16; var I: Integer; @@ -187,7 +205,7 @@ function TSouffleFunctionPrototype.AddConstantBoolean( Inc(FConstantCount); end; -function TSouffleFunctionPrototype.AddConstantInteger( +function TSouffleFunctionTemplate.AddConstantInteger( const AValue: Int64): UInt16; var I: Integer; @@ -206,7 +224,7 @@ function TSouffleFunctionPrototype.AddConstantInteger( Inc(FConstantCount); end; -function TSouffleFunctionPrototype.AddConstantFloat( +function TSouffleFunctionTemplate.AddConstantFloat( const AValue: Double): UInt16; var I: Integer; @@ -227,7 +245,7 @@ function TSouffleFunctionPrototype.AddConstantFloat( Inc(FConstantCount); end; -function TSouffleFunctionPrototype.AddConstantString( +function TSouffleFunctionTemplate.AddConstantString( const AValue: string): UInt16; var I: Integer; @@ -246,8 +264,8 @@ function TSouffleFunctionPrototype.AddConstantString( Inc(FConstantCount); end; -function TSouffleFunctionPrototype.AddFunction( - const AFunction: TSouffleFunctionPrototype): UInt16; +function TSouffleFunctionTemplate.AddFunction( + const AFunction: TSouffleFunctionTemplate): UInt16; begin if FFunctions.Count > High(UInt16) then raise Exception.Create('Function pool overflow: exceeds 65535 entries'); @@ -255,7 +273,7 @@ function TSouffleFunctionPrototype.AddFunction( FFunctions.Add(AFunction); end; -procedure TSouffleFunctionPrototype.AddUpvalueDescriptor( +procedure TSouffleFunctionTemplate.AddUpvalueDescriptor( const AIsLocal: Boolean; const AIndex: UInt8); begin if FUpvalueCount >= High(UInt8) then @@ -267,7 +285,7 @@ procedure TSouffleFunctionPrototype.AddUpvalueDescriptor( Inc(FUpvalueCount); end; -procedure TSouffleFunctionPrototype.AddExceptionHandler( +procedure TSouffleFunctionTemplate.AddExceptionHandler( const ATryStart, ATryEnd, ACatchTarget, AFinallyTarget: UInt32; const ACatchRegister: UInt8); begin @@ -281,7 +299,7 @@ procedure TSouffleFunctionPrototype.AddExceptionHandler( Inc(FExceptionHandlerCount); end; -function TSouffleFunctionPrototype.GetInstruction( +function TSouffleFunctionTemplate.GetInstruction( const AIndex: Integer): UInt32; begin {$IFDEF DEBUG} @@ -291,7 +309,7 @@ function TSouffleFunctionPrototype.GetInstruction( Result := FCode[AIndex]; end; -function TSouffleFunctionPrototype.GetConstant( +function TSouffleFunctionTemplate.GetConstant( const AIndex: Integer): TSouffleBytecodeConstant; begin {$IFDEF DEBUG} @@ -301,8 +319,8 @@ function TSouffleFunctionPrototype.GetConstant( Result := FConstants[AIndex]; end; -function TSouffleFunctionPrototype.GetFunction( - const AIndex: Integer): TSouffleFunctionPrototype; +function TSouffleFunctionTemplate.GetFunction( + const AIndex: Integer): TSouffleFunctionTemplate; begin {$IFDEF DEBUG} if (AIndex < 0) or (AIndex >= FFunctions.Count) then @@ -311,7 +329,7 @@ function TSouffleFunctionPrototype.GetFunction( Result := FFunctions[AIndex]; end; -function TSouffleFunctionPrototype.GetUpvalueDescriptor( +function TSouffleFunctionTemplate.GetUpvalueDescriptor( const AIndex: Integer): TSouffleUpvalueDescriptor; begin {$IFDEF DEBUG} @@ -321,12 +339,12 @@ function TSouffleFunctionPrototype.GetUpvalueDescriptor( Result := FUpvalueDescriptors[AIndex]; end; -function TSouffleFunctionPrototype.GetFunctionCount: Integer; +function TSouffleFunctionTemplate.GetFunctionCount: Integer; begin Result := FFunctions.Count; end; -function TSouffleFunctionPrototype.GetExceptionHandler( +function TSouffleFunctionTemplate.GetExceptionHandler( const AIndex: Integer): TSouffleExceptionHandler; begin {$IFDEF DEBUG} @@ -336,4 +354,23 @@ function TSouffleFunctionPrototype.GetExceptionHandler( Result := FExceptionHandlers[AIndex]; end; +procedure TSouffleFunctionTemplate.SetLocalType(const ASlot: UInt8; + const AKind: TSouffleLocalType); +begin + if ASlot >= Length(FLocalTypes) then + SetLength(FLocalTypes, ASlot + 1); + FLocalTypes[ASlot] := AKind; + if ASlot >= FLocalTypeCount then + FLocalTypeCount := ASlot + 1; +end; + +function TSouffleFunctionTemplate.GetLocalType( + const ASlot: UInt8): TSouffleLocalType; +begin + if ASlot < FLocalTypeCount then + Result := FLocalTypes[ASlot] + else + Result := sltUntyped; +end; + end. diff --git a/souffle/Souffle.Bytecode.Module.pas b/souffle/Souffle.Bytecode.Module.pas index d5f5766f..8dd85464 100644 --- a/souffle/Souffle.Bytecode.Module.pas +++ b/souffle/Souffle.Bytecode.Module.pas @@ -28,7 +28,7 @@ TSouffleBytecodeModule = class FFormatVersion: UInt16; FRuntimeTag: string; FSourcePath: string; - FTopLevel: TSouffleFunctionPrototype; + FTopLevel: TSouffleFunctionTemplate; FImports: array of TSouffleModuleImport; FImportCount: Integer; FExports: array of TSouffleModuleExport; @@ -48,7 +48,7 @@ TSouffleBytecodeModule = class property FormatVersion: UInt16 read FFormatVersion; property RuntimeTag: string read FRuntimeTag; property SourcePath: string read FSourcePath; - property TopLevel: TSouffleFunctionPrototype read FTopLevel write FTopLevel; + property TopLevel: TSouffleFunctionTemplate read FTopLevel write FTopLevel; property ImportCount: Integer read FImportCount; property ExportCount: Integer read FExportCount; property HasDebugInfo: Boolean read FHasDebugInfo write FHasDebugInfo; diff --git a/souffle/Souffle.Bytecode.pas b/souffle/Souffle.Bytecode.pas index b486d595..0267a49f 100644 --- a/souffle/Souffle.Bytecode.pas +++ b/souffle/Souffle.Bytecode.pas @@ -10,49 +10,121 @@ interface OP_RT_FIRST = 128; + MIN_SBX: Int16 = -32768; + MAX_SBX: Int16 = 32767; + type - { Tier 1: Core operations (VM-intrinsic, fixed semantics) — opcodes 0..127 } - { Tier 2: Runtime operations (pluggable semantics) — opcodes 128..255 } + { Core operations (VM-intrinsic, fixed semantics) — opcodes 0..127 } + { Runtime operations (pluggable semantics) — opcodes 128..255 } TSouffleOpCode = ( - // ── Tier 1: Load / Store / Move ── + // ── Core: Load / Store / Move ── OP_LOAD_CONST = 0, // ABx R[A] := Constants[Bx] - OP_LOAD_NIL = 1, // A R[A] := Nil + OP_LOAD_NIL = 1, // AB R[A] := Nil(flags=B) OP_LOAD_TRUE = 2, // A R[A] := Boolean(true) OP_LOAD_FALSE = 3, // A R[A] := Boolean(false) OP_LOAD_INT = 4, // AsBx R[A] := Integer(sBx) OP_MOVE = 5, // AB R[A] := R[B] - // ── Tier 1: Variables ── + // ── Core: Variables ── OP_GET_LOCAL = 6, // ABx R[A] := Locals[Bx] OP_SET_LOCAL = 7, // ABx Locals[Bx] := R[A] OP_GET_UPVALUE = 8, // ABx R[A] := Upvalues[Bx] OP_SET_UPVALUE = 9, // ABx Upvalues[Bx] := R[A] OP_CLOSE_UPVALUE = 10, // A Close upvalue for R[A] - // ── Tier 1: Control Flow ── + // ── Core: Control Flow ── OP_JUMP = 11, // Ax PC += Ax (signed 24-bit) OP_JUMP_IF_TRUE = 12, // AsBx if IsTrue(R[A]): PC += sBx OP_JUMP_IF_FALSE = 13, // AsBx if not IsTrue(R[A]): PC += sBx - OP_JUMP_IF_NIL = 14, // AsBx if R[A].Kind = svkNil: PC += sBx - OP_JUMP_IF_NOT_NIL = 15, // AsBx if R[A].Kind <> svkNil: PC += sBx + OP_JUMP_IF_NIL = 14, // ABC if R[A] is nil(flags match B): PC += C — B=255 matches any nil, B=0..254 matches specific flag + OP_JUMP_IF_NOT_NIL = 15, // ABC if R[A] is not nil(flags match B): PC += C — B=255 skips unless any nil, B=0..254 skips unless specific flag - // ── Tier 1: Closures ── - OP_CLOSURE = 16, // ABx R[A] := Closure(FunctionPrototypes[Bx]) + // ── Core: Closures ── + OP_CLOSURE = 16, // ABx R[A] := Closure(FunctionTemplates[Bx]) - // ── Tier 1: Exception Handling ── + // ── Core: Exception Handling ── OP_PUSH_HANDLER = 17, // ABx Push handler: catch at PC+Bx, value in R[A] OP_POP_HANDLER = 18, // Pop innermost handler OP_THROW = 19, // A Throw R[A] - // ── Tier 1: Return ── + // ── Core: Return ── OP_RETURN = 20, // A Return R[A] to caller OP_RETURN_NIL = 21, // Return Nil to caller - // ── Tier 1: Debug ── - OP_NOP = 22, // No operation - OP_LINE = 23, // Bx Source line annotation - - // ── Tier 2: Polymorphic Arithmetic ── + // ── Core: Compound Types ── + OP_ARRAY_POP = 23, // AB R[A] := Array(R[B]).Pop() + OP_NEW_ARRAY = 24, // AB R[A] := new Array(capacity=B) + OP_ARRAY_PUSH = 25, // AB Array(R[A]).push(R[B]) + OP_ARRAY_GET = 26, // ABC R[A] := Array(R[B])[R[C].AsInteger] + OP_ARRAY_SET = 27, // ABC Array(R[A])[R[B].AsInteger] := R[C] + OP_NEW_RECORD = 28, // AB R[A] := new Record(capacity=B) + OP_RECORD_GET = 29, // ABC R[A] := Record(R[B])[Constants[C]] + OP_RECORD_SET = 30, // ABC Record(R[A])[Constants[B]] := R[C] + OP_RECORD_DELETE = 31, // ABx delete Record(R[A])[Constants[Bx]] + OP_GET_LENGTH = 32, // AB R[A] := Length(R[B]) + + // ── Core: Arguments ── + OP_ARG_COUNT = 33, // A R[A] := ArgCount (number of actual arguments passed) + OP_PACK_ARGS = 34, // AB R[A] := Array(args[B..ArgCount-1]) + + // ── Core: Debug ── + OP_NOP = 35, // No operation + OP_LINE = 36, // Bx Source line annotation + + // ── Core: Integer Arithmetic ── + OP_ADD_INT = 37, // ABC R[A] := R[B].AsInteger + R[C].AsInteger + OP_SUB_INT = 38, // ABC R[A] := R[B].AsInteger - R[C].AsInteger + OP_MUL_INT = 39, // ABC R[A] := R[B].AsInteger * R[C].AsInteger + OP_DIV_INT = 40, // ABC R[A] := Float(R[B].AsInteger) / Float(R[C].AsInteger) + OP_MOD_INT = 41, // ABC R[A] := R[B].AsInteger mod R[C].AsInteger + OP_NEG_INT = 42, // AB R[A] := -R[B].AsInteger + + // ── Core: Float Arithmetic ── + OP_ADD_FLOAT = 43, // ABC R[A] := R[B].AsFloat + R[C].AsFloat + OP_SUB_FLOAT = 44, // ABC R[A] := R[B].AsFloat - R[C].AsFloat + OP_MUL_FLOAT = 45, // ABC R[A] := R[B].AsFloat * R[C].AsFloat + OP_DIV_FLOAT = 46, // ABC R[A] := R[B].AsFloat / R[C].AsFloat + OP_MOD_FLOAT = 47, // ABC R[A] := FMod(R[B].AsFloat, R[C].AsFloat) + OP_NEG_FLOAT = 48, // AB R[A] := -R[B].AsFloat + + // ── Core: Integer Comparison ── + OP_EQ_INT = 49, // ABC R[A] := Boolean(R[B].AsInteger = R[C].AsInteger) + OP_NEQ_INT = 50, // ABC R[A] := Boolean(R[B].AsInteger <> R[C].AsInteger) + OP_LT_INT = 51, // ABC R[A] := Boolean(R[B].AsInteger < R[C].AsInteger) + OP_GT_INT = 52, // ABC R[A] := Boolean(R[B].AsInteger > R[C].AsInteger) + OP_LTE_INT = 53, // ABC R[A] := Boolean(R[B].AsInteger <= R[C].AsInteger) + OP_GTE_INT = 54, // ABC R[A] := Boolean(R[B].AsInteger >= R[C].AsInteger) + + // ── Core: String ── + OP_CONCAT = 55, // ABC R[A] := String(R[B]) + String(R[C]) + + // ── Core: Typed Locals ── + OP_GET_LOCAL_INT = 56, // ABx R[A] := Locals[Bx] (typed: integer) + OP_SET_LOCAL_INT = 57, // ABx Locals[Bx] := R[A] (typed: integer) + OP_GET_LOCAL_FLOAT = 58, // ABx R[A] := Locals[Bx] (typed: float) + OP_SET_LOCAL_FLOAT = 59, // ABx Locals[Bx] := R[A] (typed: float) + OP_GET_LOCAL_BOOL = 60, // ABx R[A] := Locals[Bx] (typed: boolean) + OP_SET_LOCAL_BOOL = 61, // ABx Locals[Bx] := R[A] (typed: boolean) + OP_GET_LOCAL_STRING = 62, // ABx R[A] := Locals[Bx] (typed: string reference) + OP_SET_LOCAL_STRING = 63, // ABx Locals[Bx] := R[A] (typed: string reference) + OP_GET_LOCAL_REF = 64, // ABx R[A] := Locals[Bx] (typed: heap reference) + OP_SET_LOCAL_REF = 65, // ABx Locals[Bx] := R[A] (typed: heap reference) + + // ── Core: Blueprint ── + OP_NEW_BLUEPRINT = 66, // ABx R[A] := new Blueprint(name=Constants[Bx]) + OP_INHERIT = 67, // AB Blueprint(R[A]).super := Blueprint(R[B]) + // opcode 68 removed: was OP_BLUEPRINT_METHOD, use OP_RECORD_SET on blueprint instead + OP_INSTANTIATE = 69, // AB R[A] := new Record(blueprint=Blueprint(R[B])) + OP_GET_SLOT = 70, // ABC R[A] := Record(R[B]).slots[C] + OP_SET_SLOT = 71, // ABC Record(R[A]).slots[B] := R[C] + + // ── Core: Type Coercion ── + OP_TO_PRIMITIVE = 72, // AB R[A] := ToPrimitive(R[B]) — fast-path for nil/bool/int/float/string, runtime callback for references + + // ── Core: Destructuring ── + OP_UNPACK = 73, // ABC R[A] := Array(R[B])[C..] — unpack rest of TSouffleArray from index C + + // ── Runtime: Polymorphic Arithmetic ── OP_RT_ADD = 128, // ABC R[A] := Runtime.Add(R[B], R[C]) OP_RT_SUB = 129, // ABC R[A] := Runtime.Subtract(R[B], R[C]) OP_RT_MUL = 130, // ABC R[A] := Runtime.Multiply(R[B], R[C]) @@ -61,7 +133,7 @@ interface OP_RT_POW = 133, // ABC R[A] := Runtime.Power(R[B], R[C]) OP_RT_NEG = 134, // AB R[A] := Runtime.Negate(R[B]) - // ── Tier 2: Polymorphic Bitwise ── + // ── Runtime: Polymorphic Bitwise ── OP_RT_BAND = 135, // ABC R[A] := Runtime.BitwiseAnd(R[B], R[C]) OP_RT_BOR = 136, // ABC R[A] := Runtime.BitwiseOr(R[B], R[C]) OP_RT_BXOR = 137, // ABC R[A] := Runtime.BitwiseXor(R[B], R[C]) @@ -70,7 +142,7 @@ interface OP_RT_USHR = 140, // ABC R[A] := Runtime.UnsignedShiftRight(R[B], R[C]) OP_RT_BNOT = 141, // AB R[A] := Runtime.BitwiseNot(R[B]) - // ── Tier 2: Polymorphic Comparison ── + // ── Runtime: Polymorphic Comparison ── OP_RT_EQ = 142, // ABC R[A] := Runtime.Equal(R[B], R[C]) OP_RT_NEQ = 143, // ABC R[A] := Runtime.NotEqual(R[B], R[C]) OP_RT_LT = 144, // ABC R[A] := Runtime.LessThan(R[B], R[C]) @@ -78,45 +150,49 @@ interface OP_RT_LTE = 146, // ABC R[A] := Runtime.LessThanOrEqual(R[B], R[C]) OP_RT_GTE = 147, // ABC R[A] := Runtime.GreaterThanOrEqual(R[B], R[C]) - // ── Tier 2: Logical / Type ── + // ── Runtime: Logical / Type ── OP_RT_NOT = 148, // AB R[A] := Runtime.LogicalNot(R[B]) OP_RT_TYPEOF = 149, // AB R[A] := Runtime.TypeOf(R[B]) OP_RT_IS_INSTANCE = 150, // ABC R[A] := Runtime.IsInstance(R[B], R[C]) OP_RT_HAS_PROPERTY = 151, // ABC R[A] := Runtime.HasProperty(R[B], R[C]) OP_RT_TO_BOOLEAN = 152, // AB R[A] := Runtime.ToBoolean(R[B]) - // ── Tier 2: Compound Creation ── - OP_RT_NEW_COMPOUND = 153, // AB R[A] := Runtime.CreateCompound(B) - OP_RT_INIT_FIELD = 154, // ABC Runtime.InitField(R[A], Constants[B], R[C]) - OP_RT_INIT_INDEX = 155, // ABC Runtime.InitIndex(R[A], R[B], R[C]) - - // ── Tier 2: Property Access ── + // ── Runtime: Property Access ── OP_RT_GET_PROP = 156, // ABC R[A] := Runtime.GetProperty(R[B], Constants[C]) OP_RT_SET_PROP = 157, // ABC Runtime.SetProperty(R[A], Constants[B], R[C]) OP_RT_GET_INDEX = 158, // ABC R[A] := Runtime.GetIndex(R[B], R[C]) OP_RT_SET_INDEX = 159, // ABC Runtime.SetIndex(R[A], R[B], R[C]) OP_RT_DEL_PROP = 160, // ABC R[A] := Runtime.DeleteProperty(R[B], Constants[C]) - // ── Tier 2: Invocation ── - OP_RT_CALL = 161, // ABC R[A] := Runtime.Invoke(R[A], args, B=argc, C=flags) - OP_RT_CALL_METHOD = 162, // ABC Like RT_CALL but receiver = R[A-1] + // ── Runtime: Invocation ── + OP_RT_CALL = 161, // ABC R[A] := Runtime.Invoke(R[A], args, B=argc, C=flags); C bit 0: spread (B=argsArray reg) + OP_RT_CALL_METHOD = 162, // ABC Like RT_CALL but receiver = R[A-1]; C bit 0: spread (B=argsArray reg) OP_RT_CONSTRUCT = 163, // ABC R[A] := Runtime.Construct(R[B], args, C=argc) - // ── Tier 2: Iteration ── - OP_RT_GET_ITER = 164, // AB R[A] := Runtime.GetIterator(R[B]) + // ── Runtime: Iteration ── + OP_RT_GET_ITER = 164, // ABC R[A] := Runtime.GetIterator(R[B], C) — C: 0=sync, 1=try-async-first OP_RT_ITER_NEXT = 165, // ABC (R[A], R[B]) := Runtime.IteratorNext(R[C]) - OP_RT_SPREAD = 166, // AB Runtime.SpreadInto(R[A], R[B]) - // ── Tier 2: Modules ── + // ── Runtime: Modules ── OP_RT_IMPORT = 167, // ABx R[A] := Runtime.ImportModule(Constants[Bx]) OP_RT_EXPORT = 168, // ABx Runtime.ExportBinding(R[A], Constants[Bx]) - // ── Tier 2: Async ── + // ── Runtime: Async ── OP_RT_AWAIT = 169, // AB R[A] := Runtime.Await(R[B]) - // ── Tier 2: Globals ── + // ── Runtime: Globals ── OP_RT_GET_GLOBAL = 170, // ABx R[A] := Runtime.GetGlobal(Constants[Bx]) - OP_RT_SET_GLOBAL = 171 // ABx Runtime.SetGlobal(Constants[Bx], R[A]) + OP_RT_SET_GLOBAL = 171, // ABx Runtime.SetGlobal(Constants[Bx], R[A]) + OP_RT_HAS_GLOBAL = 172, // ABx R[A] := Boolean(Runtime.HasGlobal(Constants[Bx])) + + // ── Runtime: Extended Property ── + OP_RT_DEL_INDEX = 175, // ABC R[A] := Runtime.DeleteIndex(R[B], R[C]) + + // ── Runtime: Coercion ── + OP_RT_TO_STRING = 182, // AB R[A] := Runtime.CoerceValueToString(R[B]) + + // ── Runtime: Language Extension ── + OP_RT_EXT = 190 // ABC Runtime.ExtendedOperation(B=sub-opcode, R[A], R[C], R[A+1], Template, C) ); { Instruction encoding/decoding helpers } diff --git a/souffle/Souffle.Compound.pas b/souffle/Souffle.Compound.pas new file mode 100644 index 00000000..fc9361ac --- /dev/null +++ b/souffle/Souffle.Compound.pas @@ -0,0 +1,861 @@ +unit Souffle.Compound; + +{$I Souffle.inc} + +interface + +uses + Souffle.Heap, + Souffle.Value; + +const + SOUFFLE_PROP_WRITABLE = $01; + SOUFFLE_PROP_CONFIGURABLE = $02; + SOUFFLE_PROP_ENUMERABLE = $04; + SOUFFLE_PROP_DEFAULT = SOUFFLE_PROP_WRITABLE or SOUFFLE_PROP_CONFIGURABLE or SOUFFLE_PROP_ENUMERABLE; + +type + { Dense dynamic array of TSouffleValue — universal across languages + (JS arrays, Python lists, Lua array part, Wren lists, WASM GC arrays) } + TSouffleArray = class(TSouffleHeapObject) + private + FElements: array of TSouffleValue; + FCount: Integer; + FCapacity: Integer; + procedure Grow; + public + constructor Create(const AInitialCapacity: Integer = 0); + + procedure Push(const AValue: TSouffleValue); + function Get(const AIndex: Integer): TSouffleValue; inline; + procedure Put(const AIndex: Integer; const AValue: TSouffleValue); inline; + function Pop: TSouffleValue; + procedure Clear; + + procedure MarkReferences; override; + function DebugString: string; override; + + property Count: Integer read FCount; + property Capacity: Integer read FCapacity; + end; + + TSouffleRecordEntry = record + Key: string; + Value: TSouffleValue; + Hash: UInt32; + Occupied: Boolean; + Deleted: Boolean; + Flags: Byte; + end; + + TSouffleBlueprint = class; + + { String-keyed ordered hash map with optional blueprint (type descriptor) + and indexed slots. Without a blueprint: plain key-value record (covers + JS objects, Python dicts, Lua hash part). With a blueprint: structured + object with fast O(1) slot access plus dynamic named properties. } + TSouffleRecord = class(TSouffleHeapObject) + private + FEntries: array of TSouffleRecordEntry; + FOrder: array of Integer; + FCount: Integer; + FDeletedCount: Integer; + FCapacity: Integer; + FBlueprint: TSouffleBlueprint; + FSlots: array of TSouffleValue; + FExtensible: Boolean; + FGetters: TSouffleRecord; + FSetters: TSouffleRecord; + function HashKey(const AKey: string): UInt32; + function FindEntry(const AKey: string; const AHash: UInt32): Integer; + function FindInsertSlot(const AKey: string; const AHash: UInt32): Integer; + procedure Grow; + function GetGetters: TSouffleRecord; + function GetSetters: TSouffleRecord; + public + constructor Create(const AInitialCapacity: Integer = 0); + constructor CreateFromBlueprint(const ABlueprint: TSouffleBlueprint); + destructor Destroy; override; + + function Get(const AKey: string; out AValue: TSouffleValue): Boolean; + procedure Put(const AKey: string; const AValue: TSouffleValue); + procedure PutWithFlags(const AKey: string; const AValue: TSouffleValue; + const AFlags: Byte); + function PutChecked(const AKey: string; + const AValue: TSouffleValue): Boolean; + function Delete(const AKey: string): Boolean; + function DeleteChecked(const AKey: string): Boolean; + function Has(const AKey: string): Boolean; + function GetEntryFlags(const AKey: string): Byte; + function SetEntryFlags(const AKey: string; const AFlags: Byte): Boolean; + procedure Freeze; + + function GetOrderedKey(const AIndex: Integer): string; inline; + function GetOrderedValue(const AIndex: Integer): TSouffleValue; inline; + + function GetSlot(const AIndex: Integer): TSouffleValue; inline; + procedure SetSlot(const AIndex: Integer; const AValue: TSouffleValue); inline; + + function HasGetters: Boolean; inline; + function HasSetters: Boolean; inline; + + procedure MarkReferences; override; + function DebugString: string; override; + + procedure PreventExtensions; inline; + + property Count: Integer read FCount; + property Blueprint: TSouffleBlueprint read FBlueprint; + property Extensible: Boolean read FExtensible; + property Getters: TSouffleRecord read GetGetters; + property Setters: TSouffleRecord read GetSetters; + end; + + { Blueprint — type descriptor with method table and optional super link. + Language-agnostic equivalent of a class/struct definition. } + TSouffleBlueprint = class(TSouffleHeapObject) + private + FName: string; + FSlotCount: Integer; + FMethods: TSouffleRecord; + FSuperBlueprint: TSouffleBlueprint; + FPrototype: TSouffleRecord; + FGetters: TSouffleRecord; + FSetters: TSouffleRecord; + FStaticGetters: TSouffleRecord; + FStaticSetters: TSouffleRecord; + FStaticFields: TSouffleRecord; + function GetPrototype: TSouffleRecord; + function GetGetters: TSouffleRecord; + function GetSetters: TSouffleRecord; + function GetStaticGetters: TSouffleRecord; + function GetStaticSetters: TSouffleRecord; + function GetStaticFields: TSouffleRecord; + public + constructor Create(const AName: string; const ASlotCount: Integer); + destructor Destroy; override; + + procedure MarkReferences; override; + function DebugString: string; override; + + function HasGetters: Boolean; inline; + function HasSetters: Boolean; inline; + function HasStaticGetters: Boolean; inline; + function HasStaticSetters: Boolean; inline; + function HasStaticFields: Boolean; inline; + + property Name: string read FName; + property SlotCount: Integer read FSlotCount write FSlotCount; + property Methods: TSouffleRecord read FMethods; + property SuperBlueprint: TSouffleBlueprint read FSuperBlueprint write FSuperBlueprint; + property Prototype: TSouffleRecord read GetPrototype; + property Getters: TSouffleRecord read GetGetters; + property Setters: TSouffleRecord read GetSetters; + property StaticGetters: TSouffleRecord read GetStaticGetters; + property StaticSetters: TSouffleRecord read GetStaticSetters; + property StaticFields: TSouffleRecord read GetStaticFields; + end; + +implementation + +uses + SysUtils; + +const + MIN_ARRAY_CAPACITY = 8; + MIN_RECORD_CAPACITY = 8; + RECORD_MAX_LOAD_FACTOR = 75; // percent + +{ TSouffleArray } + +constructor TSouffleArray.Create(const AInitialCapacity: Integer); +begin + inherited Create(SOUFFLE_HEAP_ARRAY); + FCount := 0; + if AInitialCapacity > 0 then + FCapacity := AInitialCapacity + else + FCapacity := 0; + SetLength(FElements, FCapacity); +end; + +procedure TSouffleArray.Grow; +var + NewCapacity: Integer; +begin + if FCapacity < MIN_ARRAY_CAPACITY then + NewCapacity := MIN_ARRAY_CAPACITY + else + NewCapacity := FCapacity * 2; + FCapacity := NewCapacity; + SetLength(FElements, FCapacity); +end; + +procedure TSouffleArray.Push(const AValue: TSouffleValue); +begin + if FCount >= FCapacity then + Grow; + FElements[FCount] := AValue; + Inc(FCount); +end; + +function TSouffleArray.Get(const AIndex: Integer): TSouffleValue; +begin + if (AIndex < 0) or (AIndex >= FCount) then + Result := SouffleNil + else + Result := FElements[AIndex]; +end; + +procedure TSouffleArray.Put(const AIndex: Integer; const AValue: TSouffleValue); +var + I: Integer; +begin + if AIndex < 0 then + Exit; + if AIndex >= FCapacity then + begin + while FCapacity <= AIndex do + begin + if FCapacity < MIN_ARRAY_CAPACITY then + FCapacity := MIN_ARRAY_CAPACITY + else + FCapacity := FCapacity * 2; + end; + SetLength(FElements, FCapacity); + end; + if AIndex >= FCount then + begin + for I := FCount to AIndex - 1 do + FElements[I] := SouffleNil; + FCount := AIndex + 1; + end; + FElements[AIndex] := AValue; +end; + +function TSouffleArray.Pop: TSouffleValue; +begin + if FCount = 0 then + Exit(SouffleNil); + Dec(FCount); + Result := FElements[FCount]; + FElements[FCount] := SouffleNil; +end; + +procedure TSouffleArray.Clear; +var + I: Integer; +begin + for I := 0 to FCount - 1 do + FElements[I] := SouffleNil; + FCount := 0; +end; + +procedure TSouffleArray.MarkReferences; +var + I: Integer; +begin + inherited; + for I := 0 to FCount - 1 do + if SouffleIsReference(FElements[I]) and Assigned(FElements[I].AsReference) then + FElements[I].AsReference.MarkReferences; +end; + +function TSouffleArray.DebugString: string; +begin + Result := ''; +end; + +{ TSouffleRecord } + +constructor TSouffleRecord.Create(const AInitialCapacity: Integer); +var + Cap: Integer; +begin + inherited Create(SOUFFLE_HEAP_RECORD); + FCount := 0; + FDeletedCount := 0; + FBlueprint := nil; + FExtensible := True; + FGetters := nil; + FSetters := nil; + if AInitialCapacity > MIN_RECORD_CAPACITY then + Cap := AInitialCapacity + else + Cap := MIN_RECORD_CAPACITY; + FCapacity := Cap; + SetLength(FEntries, FCapacity); + SetLength(FOrder, 0); +end; + +constructor TSouffleRecord.CreateFromBlueprint(const ABlueprint: TSouffleBlueprint); +begin + Create; + FBlueprint := ABlueprint; + SetLength(FSlots, ABlueprint.SlotCount); +end; + +destructor TSouffleRecord.Destroy; +begin + FGetters.Free; + FSetters.Free; + inherited; +end; + +function TSouffleRecord.HashKey(const AKey: string): UInt32; +var + I: Integer; + H: UInt64; +begin + H := 2166136261; + for I := 1 to Length(AKey) do + H := ((H xor UInt64(Ord(AKey[I]))) * UInt64(16777619)) and $FFFFFFFF; + Result := UInt32(H); +end; + +function TSouffleRecord.FindEntry(const AKey: string; const AHash: UInt32): Integer; +var + Idx: Integer; +begin + Idx := Integer(AHash mod UInt32(FCapacity)); + while True do + begin + if not FEntries[Idx].Occupied then + begin + if not FEntries[Idx].Deleted then + Exit(Idx); + end + else if (FEntries[Idx].Hash = AHash) and (FEntries[Idx].Key = AKey) then + Exit(Idx); + Idx := (Idx + 1) mod FCapacity; + end; +end; + +function TSouffleRecord.FindInsertSlot(const AKey: string; const AHash: UInt32): Integer; +var + Idx, FirstAvail: Integer; +begin + Idx := Integer(AHash mod UInt32(FCapacity)); + FirstAvail := -1; + while True do + begin + if not FEntries[Idx].Occupied then + begin + if FEntries[Idx].Deleted then + begin + if FirstAvail < 0 then + FirstAvail := Idx; + end + else + begin + if FirstAvail >= 0 then + Exit(FirstAvail) + else + Exit(Idx); + end; + end + else if (FEntries[Idx].Hash = AHash) and (FEntries[Idx].Key = AKey) then + Exit(Idx); + Idx := (Idx + 1) mod FCapacity; + end; +end; + +procedure TSouffleRecord.Grow; +var + OldEntries: array of TSouffleRecordEntry; + OldOrder: array of Integer; + OldCount, I, Slot, OldSlot: Integer; + NewCapacity: Integer; +begin + OldEntries := FEntries; + OldCount := FCount; + NewCapacity := FCapacity * 2; + + SetLength(OldOrder, OldCount); + for I := 0 to OldCount - 1 do + OldOrder[I] := FOrder[I]; + + FCapacity := NewCapacity; + FDeletedCount := 0; + SetLength(FEntries, FCapacity); + for I := 0 to FCapacity - 1 do + begin + FEntries[I].Occupied := False; + FEntries[I].Deleted := False; + end; + + SetLength(FOrder, OldCount); + FCount := 0; + + for I := 0 to OldCount - 1 do + begin + OldSlot := OldOrder[I]; + Slot := FindEntry(OldEntries[OldSlot].Key, OldEntries[OldSlot].Hash); + FEntries[Slot] := OldEntries[OldSlot]; + FEntries[Slot].Deleted := False; + FOrder[FCount] := Slot; + Inc(FCount); + end; +end; + +function TSouffleRecord.Get(const AKey: string; out AValue: TSouffleValue): Boolean; +var + Hash: UInt32; + Slot: Integer; +begin + if FCount = 0 then + begin + AValue := SouffleNil; + Exit(False); + end; + Hash := HashKey(AKey); + Slot := FindEntry(AKey, Hash); + if FEntries[Slot].Occupied then + begin + AValue := FEntries[Slot].Value; + Result := True; + end + else + begin + AValue := SouffleNil; + Result := False; + end; +end; + +procedure TSouffleRecord.Put(const AKey: string; const AValue: TSouffleValue); +var + Hash: UInt32; + Slot: Integer; + WasTombstone: Boolean; +begin + if (FCount + FDeletedCount + 1) * 100 > FCapacity * RECORD_MAX_LOAD_FACTOR then + Grow; + + Hash := HashKey(AKey); + Slot := FindInsertSlot(AKey, Hash); + + if not FEntries[Slot].Occupied then + begin + WasTombstone := FEntries[Slot].Deleted; + FEntries[Slot].Key := AKey; + FEntries[Slot].Hash := Hash; + FEntries[Slot].Occupied := True; + FEntries[Slot].Deleted := False; + FEntries[Slot].Flags := SOUFFLE_PROP_DEFAULT; + if WasTombstone then + Dec(FDeletedCount); + if FCount >= Length(FOrder) then + SetLength(FOrder, FCount + 8); + FOrder[FCount] := Slot; + Inc(FCount); + end; + + FEntries[Slot].Value := AValue; +end; + +procedure TSouffleRecord.PutWithFlags(const AKey: string; + const AValue: TSouffleValue; const AFlags: Byte); +var + Hash: UInt32; + Slot: Integer; + WasTombstone: Boolean; +begin + if (FCount + FDeletedCount + 1) * 100 > FCapacity * RECORD_MAX_LOAD_FACTOR then + Grow; + + Hash := HashKey(AKey); + Slot := FindInsertSlot(AKey, Hash); + + if not FEntries[Slot].Occupied then + begin + WasTombstone := FEntries[Slot].Deleted; + FEntries[Slot].Key := AKey; + FEntries[Slot].Hash := Hash; + FEntries[Slot].Occupied := True; + FEntries[Slot].Deleted := False; + if WasTombstone then + Dec(FDeletedCount); + if FCount >= Length(FOrder) then + SetLength(FOrder, FCount + 8); + FOrder[FCount] := Slot; + Inc(FCount); + end; + + FEntries[Slot].Value := AValue; + FEntries[Slot].Flags := AFlags; +end; + +function TSouffleRecord.PutChecked(const AKey: string; + const AValue: TSouffleValue): Boolean; +var + Hash: UInt32; + Slot: Integer; + WasTombstone: Boolean; +begin + if (FCount + FDeletedCount + 1) * 100 > FCapacity * RECORD_MAX_LOAD_FACTOR then + Grow; + + Hash := HashKey(AKey); + Slot := FindInsertSlot(AKey, Hash); + + if FEntries[Slot].Occupied then + begin + if FEntries[Slot].Flags and SOUFFLE_PROP_WRITABLE = 0 then + Exit(False); + FEntries[Slot].Value := AValue; + Exit(True); + end; + + if not FExtensible then + Exit(False); + + WasTombstone := FEntries[Slot].Deleted; + FEntries[Slot].Key := AKey; + FEntries[Slot].Hash := Hash; + FEntries[Slot].Occupied := True; + FEntries[Slot].Deleted := False; + FEntries[Slot].Flags := SOUFFLE_PROP_DEFAULT; + if WasTombstone then + Dec(FDeletedCount); + if FCount >= Length(FOrder) then + SetLength(FOrder, FCount + 8); + FOrder[FCount] := Slot; + Inc(FCount); + FEntries[Slot].Value := AValue; + Result := True; +end; + +function TSouffleRecord.Delete(const AKey: string): Boolean; +var + Hash: UInt32; + Slot, I: Integer; +begin + if FCount = 0 then + Exit(False); + + Hash := HashKey(AKey); + Slot := FindEntry(AKey, Hash); + + if not FEntries[Slot].Occupied then + Exit(False); + + FEntries[Slot].Occupied := False; + FEntries[Slot].Deleted := True; + FEntries[Slot].Key := ''; + FEntries[Slot].Value := SouffleNil; + Inc(FDeletedCount); + + for I := 0 to FCount - 1 do + begin + if FOrder[I] = Slot then + begin + if I < FCount - 1 then + Move(FOrder[I + 1], FOrder[I], (FCount - I - 1) * SizeOf(Integer)); + Dec(FCount); + Break; + end; + end; + + Result := True; +end; + +function TSouffleRecord.DeleteChecked(const AKey: string): Boolean; +var + Hash: UInt32; + Slot, I: Integer; +begin + if FCount = 0 then + Exit(True); + + Hash := HashKey(AKey); + Slot := FindEntry(AKey, Hash); + + if not FEntries[Slot].Occupied then + Exit(True); + + if FEntries[Slot].Flags and SOUFFLE_PROP_CONFIGURABLE = 0 then + Exit(False); + + FEntries[Slot].Occupied := False; + FEntries[Slot].Deleted := True; + FEntries[Slot].Key := ''; + FEntries[Slot].Value := SouffleNil; + FEntries[Slot].Flags := SOUFFLE_PROP_DEFAULT; + Inc(FDeletedCount); + + for I := 0 to FCount - 1 do + begin + if FOrder[I] = Slot then + begin + if I < FCount - 1 then + Move(FOrder[I + 1], FOrder[I], (FCount - I - 1) * SizeOf(Integer)); + Dec(FCount); + Break; + end; + end; + + Result := True; +end; + +function TSouffleRecord.Has(const AKey: string): Boolean; +var + Hash: UInt32; + Slot: Integer; +begin + if FCount = 0 then + Exit(False); + Hash := HashKey(AKey); + Slot := FindEntry(AKey, Hash); + Result := FEntries[Slot].Occupied; +end; + +function TSouffleRecord.GetEntryFlags(const AKey: string): Byte; +var + Hash: UInt32; + Slot: Integer; +begin + if FCount = 0 then + Exit(SOUFFLE_PROP_DEFAULT); + Hash := HashKey(AKey); + Slot := FindEntry(AKey, Hash); + if not FEntries[Slot].Occupied then + Exit(SOUFFLE_PROP_DEFAULT); + Result := FEntries[Slot].Flags; +end; + +function TSouffleRecord.SetEntryFlags(const AKey: string; + const AFlags: Byte): Boolean; +var + Hash: UInt32; + Slot: Integer; +begin + if FCount = 0 then + Exit(False); + Hash := HashKey(AKey); + Slot := FindEntry(AKey, Hash); + if not FEntries[Slot].Occupied then + Exit(False); + FEntries[Slot].Flags := AFlags; + Result := True; +end; + +procedure TSouffleRecord.Freeze; +var + I: Integer; +begin + FExtensible := False; + for I := 0 to FCapacity - 1 do + if FEntries[I].Occupied then + FEntries[I].Flags := FEntries[I].Flags and SOUFFLE_PROP_ENUMERABLE; +end; + +procedure TSouffleRecord.PreventExtensions; +begin + FExtensible := False; +end; + +function TSouffleRecord.GetGetters: TSouffleRecord; +begin + if not Assigned(FGetters) then + FGetters := TSouffleRecord.Create; + Result := FGetters; +end; + +function TSouffleRecord.GetSetters: TSouffleRecord; +begin + if not Assigned(FSetters) then + FSetters := TSouffleRecord.Create; + Result := FSetters; +end; + +function TSouffleRecord.HasGetters: Boolean; +begin + Result := Assigned(FGetters) and (FGetters.Count > 0); +end; + +function TSouffleRecord.HasSetters: Boolean; +begin + Result := Assigned(FSetters) and (FSetters.Count > 0); +end; + +function TSouffleRecord.GetOrderedKey(const AIndex: Integer): string; +begin + Result := FEntries[FOrder[AIndex]].Key; +end; + +function TSouffleRecord.GetOrderedValue(const AIndex: Integer): TSouffleValue; +begin + Result := FEntries[FOrder[AIndex]].Value; +end; + +function TSouffleRecord.GetSlot(const AIndex: Integer): TSouffleValue; +begin + if (AIndex >= 0) and (AIndex < Length(FSlots)) then + Result := FSlots[AIndex] + else + Result := SouffleNil; +end; + +procedure TSouffleRecord.SetSlot(const AIndex: Integer; const AValue: TSouffleValue); +begin + if (AIndex >= 0) and (AIndex < Length(FSlots)) then + FSlots[AIndex] := AValue; +end; + +procedure TSouffleRecord.MarkReferences; +var + I: Integer; +begin + inherited; + for I := 0 to FCapacity - 1 do + if FEntries[I].Occupied and + SouffleIsReference(FEntries[I].Value) and + Assigned(FEntries[I].Value.AsReference) then + FEntries[I].Value.AsReference.MarkReferences; + if Assigned(FBlueprint) and not FBlueprint.GCMarked then + FBlueprint.MarkReferences; + for I := 0 to High(FSlots) do + if SouffleIsReference(FSlots[I]) and Assigned(FSlots[I].AsReference) then + FSlots[I].AsReference.MarkReferences; + if Assigned(FGetters) and not FGetters.GCMarked then + FGetters.MarkReferences; + if Assigned(FSetters) and not FSetters.GCMarked then + FSetters.MarkReferences; +end; + +function TSouffleRecord.DebugString: string; +begin + if Assigned(FBlueprint) then + Result := '<' + FBlueprint.Name + '>' + else + Result := ''; +end; + +{ TSouffleBlueprint } + +constructor TSouffleBlueprint.Create(const AName: string; const ASlotCount: Integer); +begin + inherited Create(SOUFFLE_HEAP_BLUEPRINT); + FName := AName; + FSlotCount := ASlotCount; + FMethods := TSouffleRecord.Create; + FSuperBlueprint := nil; + FPrototype := nil; + FGetters := nil; + FSetters := nil; + FStaticGetters := nil; + FStaticSetters := nil; + FStaticFields := nil; +end; + +destructor TSouffleBlueprint.Destroy; +begin + FPrototype.Free; + FMethods.Free; + FGetters.Free; + FSetters.Free; + FStaticGetters.Free; + FStaticSetters.Free; + FStaticFields.Free; + inherited; +end; + +function TSouffleBlueprint.GetPrototype: TSouffleRecord; +begin + if not Assigned(FPrototype) then + begin + if Assigned(FSuperBlueprint) then + FPrototype := TSouffleRecord.CreateFromBlueprint(FSuperBlueprint) + else + FPrototype := TSouffleRecord.Create; + end; + Result := FPrototype; +end; + +function TSouffleBlueprint.GetGetters: TSouffleRecord; +begin + if not Assigned(FGetters) then + FGetters := TSouffleRecord.Create; + Result := FGetters; +end; + +function TSouffleBlueprint.GetSetters: TSouffleRecord; +begin + if not Assigned(FSetters) then + FSetters := TSouffleRecord.Create; + Result := FSetters; +end; + +function TSouffleBlueprint.GetStaticGetters: TSouffleRecord; +begin + if not Assigned(FStaticGetters) then + FStaticGetters := TSouffleRecord.Create; + Result := FStaticGetters; +end; + +function TSouffleBlueprint.GetStaticSetters: TSouffleRecord; +begin + if not Assigned(FStaticSetters) then + FStaticSetters := TSouffleRecord.Create; + Result := FStaticSetters; +end; + +function TSouffleBlueprint.HasGetters: Boolean; +begin + Result := Assigned(FGetters) and (FGetters.Count > 0); +end; + +function TSouffleBlueprint.HasSetters: Boolean; +begin + Result := Assigned(FSetters) and (FSetters.Count > 0); +end; + +function TSouffleBlueprint.HasStaticGetters: Boolean; +begin + Result := Assigned(FStaticGetters) and (FStaticGetters.Count > 0); +end; + +function TSouffleBlueprint.HasStaticSetters: Boolean; +begin + Result := Assigned(FStaticSetters) and (FStaticSetters.Count > 0); +end; + +function TSouffleBlueprint.GetStaticFields: TSouffleRecord; +begin + if not Assigned(FStaticFields) then + FStaticFields := TSouffleRecord.Create; + Result := FStaticFields; +end; + +function TSouffleBlueprint.HasStaticFields: Boolean; +begin + Result := Assigned(FStaticFields) and (FStaticFields.Count > 0); +end; + +procedure TSouffleBlueprint.MarkReferences; +begin + inherited; + if Assigned(FMethods) and not FMethods.GCMarked then + FMethods.MarkReferences; + if Assigned(FSuperBlueprint) and not FSuperBlueprint.GCMarked then + FSuperBlueprint.MarkReferences; + if Assigned(FPrototype) and not FPrototype.GCMarked then + FPrototype.MarkReferences; + if Assigned(FGetters) and not FGetters.GCMarked then + FGetters.MarkReferences; + if Assigned(FSetters) and not FSetters.GCMarked then + FSetters.MarkReferences; + if Assigned(FStaticGetters) and not FStaticGetters.GCMarked then + FStaticGetters.MarkReferences; + if Assigned(FStaticSetters) and not FStaticSetters.GCMarked then + FStaticSetters.MarkReferences; + if Assigned(FStaticFields) and not FStaticFields.GCMarked then + FStaticFields.MarkReferences; +end; + +function TSouffleBlueprint.DebugString: string; +begin + Result := ''; +end; + +end. diff --git a/souffle/Souffle.Heap.pas b/souffle/Souffle.Heap.pas index 48d62d37..a55d9f8b 100644 --- a/souffle/Souffle.Heap.pas +++ b/souffle/Souffle.Heap.pas @@ -9,6 +9,7 @@ TSouffleHeapObject = class private FGCMarked: Boolean; FHeapKind: UInt8; + FDelegate: TSouffleHeapObject; public constructor Create(const AHeapKind: UInt8); @@ -17,23 +18,26 @@ TSouffleHeapObject = class property GCMarked: Boolean read FGCMarked write FGCMarked; property HeapKind: UInt8 read FHeapKind; + property Delegate: TSouffleHeapObject read FDelegate write FDelegate; end; - TSouffleString = class(TSouffleHeapObject) + TSouffleHeapString = class(TSouffleHeapObject) private FValue: string; public constructor Create(const AValue: string); - function DebugString: string; override; - property Value: string read FValue; end; const - SOUFFLE_HEAP_STRING = 0; SOUFFLE_HEAP_CLOSURE = 1; SOUFFLE_HEAP_UPVALUE = 2; + SOUFFLE_HEAP_ARRAY = 3; + SOUFFLE_HEAP_RECORD = 4; + SOUFFLE_HEAP_NATIVE_FUNCTION = 5; + SOUFFLE_HEAP_BLUEPRINT = 6; + SOUFFLE_HEAP_STRING = 7; SOUFFLE_HEAP_RUNTIME = 128; implementation @@ -48,11 +52,14 @@ constructor TSouffleHeapObject.Create(const AHeapKind: UInt8); inherited Create; FGCMarked := False; FHeapKind := AHeapKind; + FDelegate := nil; end; procedure TSouffleHeapObject.MarkReferences; begin FGCMarked := True; + if Assigned(FDelegate) and not FDelegate.GCMarked then + FDelegate.MarkReferences; end; function TSouffleHeapObject.DebugString: string; @@ -60,17 +67,17 @@ function TSouffleHeapObject.DebugString: string; Result := ''; end; -{ TSouffleString } +{ TSouffleHeapString } -constructor TSouffleString.Create(const AValue: string); +constructor TSouffleHeapString.Create(const AValue: string); begin inherited Create(SOUFFLE_HEAP_STRING); FValue := AValue; end; -function TSouffleString.DebugString: string; +function TSouffleHeapString.DebugString: string; begin - Result := '"' + FValue + '"'; + Result := FValue; end; end. diff --git a/souffle/Souffle.VM.CallFrame.pas b/souffle/Souffle.VM.CallFrame.pas index d5075e89..0e07cf52 100644 --- a/souffle/Souffle.VM.CallFrame.pas +++ b/souffle/Souffle.VM.CallFrame.pas @@ -12,12 +12,14 @@ interface PSouffleVMCallFrame = ^TSouffleVMCallFrame; TSouffleVMCallFrame = record - Prototype: TSouffleFunctionPrototype; + Template: TSouffleFunctionTemplate; Closure: TSouffleClosure; IP: Integer; BaseRegister: Integer; HandlerDepth: Integer; ReturnRegister: Integer; + ArgCount: Integer; + ArgSourceBase: Integer; end; TSouffleCallStack = class @@ -28,7 +30,7 @@ TSouffleCallStack = class public constructor Create(const AInitialCapacity: Integer = 64); - function Push(const APrototype: TSouffleFunctionPrototype; + function Push(const ATemplate: TSouffleFunctionTemplate; const AClosure: TSouffleClosure; const ABaseRegister: Integer; const AReturnRegister: Integer; @@ -53,7 +55,7 @@ constructor TSouffleCallStack.Create(const AInitialCapacity: Integer); FCount := 0; end; -function TSouffleCallStack.Push(const APrototype: TSouffleFunctionPrototype; +function TSouffleCallStack.Push(const ATemplate: TSouffleFunctionTemplate; const AClosure: TSouffleClosure; const ABaseRegister: Integer; const AReturnRegister: Integer; @@ -64,12 +66,14 @@ function TSouffleCallStack.Push(const APrototype: TSouffleFunctionPrototype; FCapacity := FCapacity * 2; SetLength(FFrames, FCapacity); end; - FFrames[FCount].Prototype := APrototype; + FFrames[FCount].Template := ATemplate; FFrames[FCount].Closure := AClosure; FFrames[FCount].IP := 0; FFrames[FCount].BaseRegister := ABaseRegister; FFrames[FCount].ReturnRegister := AReturnRegister; FFrames[FCount].HandlerDepth := AHandlerDepth; + FFrames[FCount].ArgCount := 0; + FFrames[FCount].ArgSourceBase := 0; Result := @FFrames[FCount]; Inc(FCount); end; diff --git a/souffle/Souffle.VM.Closure.pas b/souffle/Souffle.VM.Closure.pas index f8326543..cc09974a 100644 --- a/souffle/Souffle.VM.Closure.pas +++ b/souffle/Souffle.VM.Closure.pas @@ -12,11 +12,11 @@ interface type TSouffleClosure = class(TSouffleHeapObject) private - FPrototype: TSouffleFunctionPrototype; + FTemplate: TSouffleFunctionTemplate; FUpvalues: array of TSouffleUpvalue; FUpvalueCount: Integer; public - constructor Create(const APrototype: TSouffleFunctionPrototype); + constructor Create(const ATemplate: TSouffleFunctionTemplate); function GetUpvalue(const AIndex: Integer): TSouffleUpvalue; inline; procedure SetUpvalue(const AIndex: Integer; const AUpvalue: TSouffleUpvalue); inline; @@ -24,7 +24,7 @@ TSouffleClosure = class(TSouffleHeapObject) procedure MarkReferences; override; function DebugString: string; override; - property Prototype: TSouffleFunctionPrototype read FPrototype; + property Template: TSouffleFunctionTemplate read FTemplate; property UpvalueCount: Integer read FUpvalueCount; end; @@ -32,11 +32,11 @@ implementation { TSouffleClosure } -constructor TSouffleClosure.Create(const APrototype: TSouffleFunctionPrototype); +constructor TSouffleClosure.Create(const ATemplate: TSouffleFunctionTemplate); begin inherited Create(SOUFFLE_HEAP_CLOSURE); - FPrototype := APrototype; - FUpvalueCount := APrototype.UpvalueCount; + FTemplate := ATemplate; + FUpvalueCount := ATemplate.UpvalueCount; SetLength(FUpvalues, FUpvalueCount); end; @@ -63,7 +63,7 @@ procedure TSouffleClosure.MarkReferences; function TSouffleClosure.DebugString: string; begin - Result := ''; + Result := ''; end; end. diff --git a/souffle/Souffle.VM.NativeFunction.pas b/souffle/Souffle.VM.NativeFunction.pas new file mode 100644 index 00000000..8892e87d --- /dev/null +++ b/souffle/Souffle.VM.NativeFunction.pas @@ -0,0 +1,98 @@ +unit Souffle.VM.NativeFunction; + +{$I Souffle.inc} + +interface + +uses + Souffle.Heap, + Souffle.Value; + +type + TSouffleNativeCallback = function( + const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; + const AArgCount: Integer): TSouffleValue; + + TSouffleMethodEntry = record + Name: string; + Arity: Integer; + Callback: TSouffleNativeCallback; + end; + + TSouffleMethodTable = array of TSouffleMethodEntry; + + TSouffleNativeFunction = class(TSouffleHeapObject) + private + FCallback: TSouffleNativeCallback; + FName: string; + FArity: Integer; + public + constructor Create(const AName: string; const AArity: Integer; + const ACallback: TSouffleNativeCallback); + + function Invoke(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; + const AArgCount: Integer): TSouffleValue; inline; + + function DebugString: string; override; + + property Name: string read FName; + property Arity: Integer read FArity; + end; + +function BuildDelegate( + const AEntries: array of TSouffleMethodEntry): TSouffleHeapObject; + +implementation + +uses + Souffle.Compound, + Souffle.GarbageCollector; + +{ TSouffleNativeFunction } + +constructor TSouffleNativeFunction.Create(const AName: string; + const AArity: Integer; const ACallback: TSouffleNativeCallback); +begin + inherited Create(SOUFFLE_HEAP_NATIVE_FUNCTION); + FName := AName; + FArity := AArity; + FCallback := ACallback; +end; + +function TSouffleNativeFunction.Invoke(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +begin + Result := FCallback(AReceiver, AArgs, AArgCount); +end; + +function TSouffleNativeFunction.DebugString: string; +begin + Result := ''; +end; + +function BuildDelegate( + const AEntries: array of TSouffleMethodEntry): TSouffleHeapObject; +var + Rec: TSouffleRecord; + Fn: TSouffleNativeFunction; + GC: TSouffleGarbageCollector; + I: Integer; +begin + GC := TSouffleGarbageCollector.Instance; + Rec := TSouffleRecord.Create(Length(AEntries)); + if Assigned(GC) then + GC.AllocateObject(Rec); + for I := 0 to High(AEntries) do + begin + Fn := TSouffleNativeFunction.Create( + AEntries[I].Name, AEntries[I].Arity, AEntries[I].Callback); + if Assigned(GC) then + GC.AllocateObject(Fn); + Rec.Put(AEntries[I].Name, SouffleReference(Fn)); + end; + Result := Rec; +end; + +end. diff --git a/souffle/Souffle.VM.RuntimeOperations.pas b/souffle/Souffle.VM.RuntimeOperations.pas index 5a075c82..00b3dbf7 100644 --- a/souffle/Souffle.VM.RuntimeOperations.pas +++ b/souffle/Souffle.VM.RuntimeOperations.pas @@ -5,12 +5,26 @@ interface uses + Souffle.Bytecode.Chunk, Souffle.Value; type + { TSouffleRuntimeOperations — abstract contract between the VM and a + language frontend. Every method operates exclusively on TSouffleValue; + frontends must NOT expose their own value systems through this interface. + + Grouped by domain (45 abstract + 4 virtual with defaults). A new + language frontend implements this class and passes it to the VM. } + TSouffleRuntimeOperations = class abstract public - // Arithmetic + + { ── Arithmetic (7) ── + Operands may be any TSouffleValue kind. Implementations must coerce + non-numeric operands to numbers following the language's rules (e.g. + string-to-number, boolean-to-number). Add typically also handles + string concatenation when either operand is a string. } + function Add(const A, B: TSouffleValue): TSouffleValue; virtual; abstract; function Subtract(const A, B: TSouffleValue): TSouffleValue; virtual; abstract; function Multiply(const A, B: TSouffleValue): TSouffleValue; virtual; abstract; @@ -19,7 +33,10 @@ TSouffleRuntimeOperations = class abstract function Power(const A, B: TSouffleValue): TSouffleValue; virtual; abstract; function Negate(const A: TSouffleValue): TSouffleValue; virtual; abstract; - // Bitwise + { ── Bitwise (7) ── + Operands are coerced to 32-bit integers before the operation. + UnsignedShiftRight returns a float/unsigned result. } + function BitwiseAnd(const A, B: TSouffleValue): TSouffleValue; virtual; abstract; function BitwiseOr(const A, B: TSouffleValue): TSouffleValue; virtual; abstract; function BitwiseXor(const A, B: TSouffleValue): TSouffleValue; virtual; abstract; @@ -28,7 +45,12 @@ TSouffleRuntimeOperations = class abstract function UnsignedShiftRight(const A, B: TSouffleValue): TSouffleValue; virtual; abstract; function BitwiseNot(const A: TSouffleValue): TSouffleValue; virtual; abstract; - // Comparison + { ── Comparison (6) ── + Strict equality: no implicit type coercion — values of different + kinds are never equal. Relational operators coerce operands as + needed (string comparison when both are strings, numeric otherwise). + All return SouffleBoolean. } + function Equal(const A, B: TSouffleValue): TSouffleValue; virtual; abstract; function NotEqual(const A, B: TSouffleValue): TSouffleValue; virtual; abstract; function LessThan(const A, B: TSouffleValue): TSouffleValue; virtual; abstract; @@ -36,21 +58,30 @@ TSouffleRuntimeOperations = class abstract function LessThanOrEqual(const A, B: TSouffleValue): TSouffleValue; virtual; abstract; function GreaterThanOrEqual(const A, B: TSouffleValue): TSouffleValue; virtual; abstract; - // Logical / Type + { ── Logic / Type (6) ── + LogicalNot: returns the boolean negation of the truthiness of A. + TypeOf: returns a SouffleString with the type name. + IsInstance: returns SouffleBoolean — true when A is an instance of B. + HasProperty: returns SouffleBoolean — true when AKey exists on AObject. + ToBoolean: coerces A to a SouffleBoolean (truthiness). + ToPrimitive: converts reference types to a primitive value. } + function LogicalNot(const A: TSouffleValue): TSouffleValue; virtual; abstract; function TypeOf(const A: TSouffleValue): TSouffleValue; virtual; abstract; function IsInstance(const A, B: TSouffleValue): TSouffleValue; virtual; abstract; function HasProperty(const AObject, AKey: TSouffleValue): TSouffleValue; virtual; abstract; function ToBoolean(const A: TSouffleValue): TSouffleValue; virtual; abstract; + function ToPrimitive(const A: TSouffleValue): TSouffleValue; virtual; abstract; - // Compound creation - function CreateCompound(const ATypeTag: UInt8): TSouffleValue; virtual; abstract; - procedure InitField(const ACompound: TSouffleValue; const AKey: string; - const AValue: TSouffleValue); virtual; abstract; - procedure InitIndex(const ACompound: TSouffleValue; const AIndex: TSouffleValue; - const AValue: TSouffleValue); virtual; abstract; + { ── Property access (6) ── + GetProperty/SetProperty: named property access on any value. + Implementations handle prototype chain walking, auto-boxing + of primitives, and delegate chain lookup. + GetIndex/SetIndex: computed property access where AKey is a + TSouffleValue (integer index, string key, or symbol key). + DeleteProperty/DeleteIndex: remove a property. Returns + SouffleBoolean(True) on success. } - // Property access function GetProperty(const AObject: TSouffleValue; const AKey: string): TSouffleValue; virtual; abstract; procedure SetProperty(const AObject: TSouffleValue; const AKey: string; @@ -60,34 +91,116 @@ TSouffleRuntimeOperations = class abstract const AKey, AValue: TSouffleValue); virtual; abstract; function DeleteProperty(const AObject: TSouffleValue; const AKey: string): TSouffleValue; virtual; abstract; + function DeleteIndex(const AObject, AKey: TSouffleValue): TSouffleValue; virtual; + + { ── Invocation (2) ── + Invoke: call ACallee with AArgs (AArgCount entries starting at + AArgs^). AReceiver is the `this` binding (SouffleNil for bare + calls). Returns the function's return value. + Construct: call AConstructor as a constructor (`new`). Returns + the newly created instance. } - // Invocation function Invoke(const ACallee: TSouffleValue; const AArgs: PSouffleValue; const AArgCount: Integer; const AReceiver: TSouffleValue): TSouffleValue; virtual; abstract; function Construct(const AConstructor: TSouffleValue; const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; virtual; abstract; - // Iteration - function GetIterator(const AIterable: TSouffleValue): TSouffleValue; virtual; abstract; + { ── Globals (3) ── + Language-level global variable access. The VM calls these for + OP_GET_GLOBAL / OP_SET_GLOBAL / OP_HAS_GLOBAL. Implementations + manage const enforcement and undefined-variable errors. } + + function GetGlobal(const AName: string): TSouffleValue; virtual; abstract; + procedure SetGlobal(const AName: string; + const AValue: TSouffleValue); virtual; abstract; + function HasGlobal(const AName: string): Boolean; virtual; abstract; + + { ── Iteration (2) ── + GetIterator: obtain an iterator from AIterable. ATryAsync hints + that an async iterator is preferred (for `for await`). + IteratorNext: advance the iterator, returning the next value. + Sets ADone to True when the iterator is exhausted. } + + function GetIterator(const AIterable: TSouffleValue; + const ATryAsync: Boolean = False): TSouffleValue; virtual; abstract; function IteratorNext(const AIterator: TSouffleValue; out ADone: Boolean): TSouffleValue; virtual; abstract; - procedure SpreadInto(const ATarget, ASource: TSouffleValue); virtual; abstract; - // Modules + { ── Modules (2) ── + ImportModule: load and return the module namespace for APath. + ExportBinding: register AValue as a named export. } + function ImportModule(const APath: string): TSouffleValue; virtual; abstract; procedure ExportBinding(const AValue: TSouffleValue; const AName: string); virtual; abstract; - // Async + { ── Async (2) ── + AwaitValue: suspend until AValue resolves (for `await`). + WrapInPromise: wrap AValue in a promise. Default returns AValue + unchanged (for languages without promises). } + function AwaitValue(const AValue: TSouffleValue): TSouffleValue; virtual; abstract; + function WrapInPromise(const AValue: TSouffleValue; + const AIsRejected: Boolean): TSouffleValue; virtual; - // Globals - function GetGlobal(const AName: string): TSouffleValue; virtual; abstract; - procedure SetGlobal(const AName: string; - const AValue: TSouffleValue); virtual; abstract; + { ── Coercion (1) ── + CoerceValueToString: convert A to its string representation for + OP_RT_TO_STRING. Default returns SouffleNil (caller falls back + to SouffleValueToString). } + + function CoerceValueToString(const A: TSouffleValue): TSouffleValue; virtual; + + { ── Extension dispatch (1) ── + Language-specific operations dispatched by OP_RT_EXT. ASubOp is + the sub-opcode ID defined by the frontend. Default is a no-op. + Frontends define their own sub-opcode constants and handle them + in their override. } + + procedure ExtendedOperation(const ASubOp: UInt8; + var ADest: TSouffleValue; const AOperand, AExtra: TSouffleValue; + const ATemplate: TSouffleFunctionTemplate; + const AOperandIndex: UInt8); virtual; + + { ── GC coordination (1) ── + Called by the VM's external root marker during Souffle GC mark + phase. Frontends that cache Souffle heap objects outside the VM + (e.g. bridge caches, global tables) must override this to mark + those objects so the Souffle GC does not collect them. Default + is a no-op. } + + procedure MarkExternalRoots; virtual; end; implementation +function TSouffleRuntimeOperations.DeleteIndex( + const AObject, AKey: TSouffleValue): TSouffleValue; +begin + Result := DeleteProperty(AObject, SouffleValueToString(AKey)); +end; + +function TSouffleRuntimeOperations.CoerceValueToString( + const A: TSouffleValue): TSouffleValue; +begin + Result := SouffleNil; +end; + +function TSouffleRuntimeOperations.WrapInPromise(const AValue: TSouffleValue; + const AIsRejected: Boolean): TSouffleValue; +begin + Result := AValue; +end; + +procedure TSouffleRuntimeOperations.ExtendedOperation(const ASubOp: UInt8; + var ADest: TSouffleValue; const AOperand, AExtra: TSouffleValue; + const ATemplate: TSouffleFunctionTemplate; + const AOperandIndex: UInt8); +begin +end; + +procedure TSouffleRuntimeOperations.MarkExternalRoots; +begin +end; + end. diff --git a/souffle/Souffle.VM.pas b/souffle/Souffle.VM.pas index f7b889a0..00458334 100644 --- a/souffle/Souffle.VM.pas +++ b/souffle/Souffle.VM.pas @@ -5,15 +5,19 @@ interface uses + Math, + Souffle.Bytecode, Souffle.Bytecode.Chunk, Souffle.Bytecode.Module, + Souffle.Compound, Souffle.GarbageCollector, Souffle.Heap, Souffle.Value, Souffle.VM.CallFrame, Souffle.VM.Closure, Souffle.VM.Exception, + Souffle.VM.NativeFunction, Souffle.VM.RuntimeOperations, Souffle.VM.Upvalue; @@ -31,21 +35,31 @@ TSouffleVM = class FGC: TSouffleGarbageCollector; FBaseFrameCount: Integer; + FPreviousExceptionMask: TFPUExceptionMask; + FArrayDelegate: TSouffleHeapObject; + FRecordDelegate: TSouffleHeapObject; + function CaptureUpvalue(const ARegisterIndex: Integer): TSouffleUpvalue; procedure CloseUpvalues(const ALastRegister: Integer); procedure MarkVMRoots; procedure CallClosure(const AClosure: TSouffleClosure; const AArgBase, AArgCount, AReturnAbsolute: Integer; const AReceiver: TSouffleValue); + function InvokeWithSpread(const ACallee, AArgsArray, + AReceiver: TSouffleValue): TSouffleValue; procedure ExecuteLoop; - procedure ExecuteTier1(const AFrame: PSouffleVMCallFrame; + procedure ExecuteCoreOp(const AFrame: PSouffleVMCallFrame; const AInstruction: UInt32; const AOp: UInt8); - procedure ExecuteTier2(const AFrame: PSouffleVMCallFrame; + procedure ExecuteRuntimeOp(const AFrame: PSouffleVMCallFrame; const AInstruction: UInt32; const AOp: UInt8); + function ResolveAsyncThrow(const AThrownValue: TSouffleValue): Boolean; function MaterializeConstant( const AConstant: TSouffleBytecodeConstant): TSouffleValue; + + function DelegateGet(const AObject: TSouffleHeapObject; + const AKey: string; out AValue: TSouffleValue): Boolean; public constructor Create(const ARuntimeOps: TSouffleRuntimeOperations); destructor Destroy; override; @@ -53,6 +67,10 @@ TSouffleVM = class function Execute(const AModule: TSouffleBytecodeModule): TSouffleValue; function ExecuteFunction(const AClosure: TSouffleClosure; const AArgs: array of TSouffleValue): TSouffleValue; + + property ArrayDelegate: TSouffleHeapObject read FArrayDelegate write FArrayDelegate; + property RecordDelegate: TSouffleHeapObject read FRecordDelegate write FRecordDelegate; + property CallStack: TSouffleCallStack read FCallStack; end; implementation @@ -65,11 +83,15 @@ implementation constructor TSouffleVM.Create(const ARuntimeOps: TSouffleRuntimeOperations); begin inherited Create; + FPreviousExceptionMask := GetExceptionMask; + SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision]); SetLength(FRegisters, MAX_REGISTERS); FCallStack := TSouffleCallStack.Create(INITIAL_FRAMES); FHandlerStack := TSouffleHandlerStack.Create; FOpenUpvalues := nil; FRuntimeOps := ARuntimeOps; + FArrayDelegate := nil; + FRecordDelegate := nil; FGC := TSouffleGarbageCollector.Instance; if Assigned(FGC) then @@ -82,6 +104,7 @@ destructor TSouffleVM.Destroy; FGC.SetExternalRootMarker(nil); FCallStack.Free; FHandlerStack.Free; + SetExceptionMask(FPreviousExceptionMask); inherited; end; @@ -96,82 +119,158 @@ function TSouffleVM.Execute(const AModule: TSouffleBytecodeModule): TSouffleValu if Assigned(FGC) then FGC.AllocateObject(TopClosure); - Result := ExecuteFunction(TopClosure, []); + Result := ExecuteFunction(TopClosure, [SouffleNil]); end; function TSouffleVM.ExecuteFunction(const AClosure: TSouffleClosure; const AArgs: array of TSouffleValue): TSouffleValue; var Frame: PSouffleVMCallFrame; - I, Base, SavedBaseFrameCount, ArgsToCopy: Integer; + I, Base, SavedBaseFrameCount, ArgsToCopy, RequiredSpace: Integer; begin Base := 0; if not FCallStack.IsEmpty then - Base := FCallStack.Peek^.BaseRegister + FCallStack.Peek^.Prototype.MaxRegisters; + Base := FCallStack.Peek^.BaseRegister + FCallStack.Peek^.Template.MaxRegisters; - if Base + AClosure.Prototype.MaxRegisters > Length(FRegisters) then + RequiredSpace := AClosure.Template.MaxRegisters; + if Length(AArgs) > RequiredSpace then + RequiredSpace := Length(AArgs); + if Base + RequiredSpace > Length(FRegisters) then raise Exception.Create('Stack overflow: register window exceeds capacity'); - Frame := FCallStack.Push(AClosure.Prototype, AClosure, Base, Base, FHandlerStack.Count); + Frame := FCallStack.Push(AClosure.Template, AClosure, Base, Base, FHandlerStack.Count); ArgsToCopy := Length(AArgs); - if ArgsToCopy > AClosure.Prototype.MaxRegisters then - ArgsToCopy := AClosure.Prototype.MaxRegisters; + if ArgsToCopy > AClosure.Template.MaxRegisters then + ArgsToCopy := AClosure.Template.MaxRegisters; for I := 0 to ArgsToCopy - 1 do FRegisters[Base + I] := AArgs[I]; - for I := ArgsToCopy to AClosure.Prototype.MaxRegisters - 1 do + for I := ArgsToCopy to AClosure.Template.MaxRegisters - 1 do FRegisters[Base + I] := SouffleNil; + for I := AClosure.Template.MaxRegisters to Length(AArgs) - 1 do + FRegisters[Base + I] := AArgs[I]; + + if Length(AArgs) > 1 then + begin + Frame^.ArgCount := Length(AArgs) - 1; + Frame^.ArgSourceBase := Base + 1; + end + else + begin + Frame^.ArgCount := 0; + Frame^.ArgSourceBase := Base + 1; + end; + SavedBaseFrameCount := FBaseFrameCount; FBaseFrameCount := FCallStack.Count; try - ExecuteLoop; - except - on E: ESouffleThrow do - begin - while FCallStack.Count >= FBaseFrameCount do + try + ExecuteLoop; + except + on E: ESouffleThrow do begin - CloseUpvalues(FCallStack.Peek^.BaseRegister); - FCallStack.Pop; + while FCallStack.Count >= FBaseFrameCount do + begin + CloseUpvalues(FCallStack.Peek^.BaseRegister); + FCallStack.Pop; + end; + raise; end; - FBaseFrameCount := SavedBaseFrameCount; - Result := SouffleNil; - Exit; end; + finally + FBaseFrameCount := SavedBaseFrameCount; end; - FBaseFrameCount := SavedBaseFrameCount; Result := FRegisters[Base]; end; +function TSouffleVM.ResolveAsyncThrow( + const AThrownValue: TSouffleValue): Boolean; +var + CurrentFrame: PSouffleVMCallFrame; + ReturnReg: Integer; +begin + Result := False; + while FCallStack.Count >= FBaseFrameCount do + begin + CurrentFrame := FCallStack.Peek; + CloseUpvalues(CurrentFrame^.BaseRegister); + while FHandlerStack.Count > CurrentFrame^.HandlerDepth do + FHandlerStack.Pop; + if CurrentFrame^.Template.IsAsync then + begin + ReturnReg := CurrentFrame^.ReturnRegister; + FCallStack.Pop; + FRegisters[ReturnReg] := + FRuntimeOps.WrapInPromise(AThrownValue, True); + Result := True; + Exit; + end; + FCallStack.Pop; + end; +end; + procedure TSouffleVM.ExecuteLoop; var Frame: PSouffleVMCallFrame; Instruction: UInt32; Op: UInt8; + HandlerEntry: TSouffleHandlerEntry; begin while FCallStack.Count >= FBaseFrameCount do begin Frame := FCallStack.Peek; - if Frame^.IP >= Frame^.Prototype.CodeCount then + if Frame^.IP >= Frame^.Template.CodeCount then begin CloseUpvalues(Frame^.BaseRegister); - FRegisters[Frame^.ReturnRegister] := SouffleNil; + if Frame^.Template.IsAsync then + FRegisters[Frame^.ReturnRegister] := + FRuntimeOps.WrapInPromise(SouffleNil, False) + else + FRegisters[Frame^.ReturnRegister] := SouffleNil; FCallStack.Pop; Continue; end; - Instruction := Frame^.Prototype.GetInstruction(Frame^.IP); + Instruction := Frame^.Template.GetInstruction(Frame^.IP); Inc(Frame^.IP); Op := DecodeOp(Instruction); - if Op < OP_RT_FIRST then - ExecuteTier1(Frame, Instruction, Op) - else - ExecuteTier2(Frame, Instruction, Op); + try + if Op < OP_RT_FIRST then + ExecuteCoreOp(Frame, Instruction, Op) + else + ExecuteRuntimeOp(Frame, Instruction, Op); + except + on E: ESouffleThrow do + begin + if (not FHandlerStack.IsEmpty) and + (FHandlerStack.Peek.FrameIndex >= FBaseFrameCount - 1) then + begin + HandlerEntry := FHandlerStack.Peek; + FHandlerStack.Pop; + + while FCallStack.Count - 1 > HandlerEntry.FrameIndex do + begin + CloseUpvalues(FCallStack.Peek^.BaseRegister); + FCallStack.Pop; + end; + + FCallStack.Peek^.IP := HandlerEntry.CatchIP; + FRegisters[HandlerEntry.BaseRegister + HandlerEntry.CatchRegister] := + E.ThrownValue; + end + else + begin + if not ResolveAsyncThrow(E.ThrownValue) then + raise; + end; + end; + end; end; end; @@ -182,37 +281,87 @@ procedure TSouffleVM.CallClosure(const AClosure: TSouffleClosure; NewBase, I, ArgsToCopy: Integer; Frame: PSouffleVMCallFrame; begin - NewBase := FCallStack.Peek^.BaseRegister + FCallStack.Peek^.Prototype.MaxRegisters; - if NewBase + AClosure.Prototype.MaxRegisters > MAX_REGISTERS then + NewBase := FCallStack.Peek^.BaseRegister + FCallStack.Peek^.Template.MaxRegisters; + if NewBase + AClosure.Template.MaxRegisters > MAX_REGISTERS then raise Exception.Create('Stack overflow'); + FRegisters[NewBase] := AReceiver; + ArgsToCopy := AArgCount; - if ArgsToCopy > AClosure.Prototype.MaxRegisters then - ArgsToCopy := AClosure.Prototype.MaxRegisters; + if ArgsToCopy + 1 > AClosure.Template.MaxRegisters then + ArgsToCopy := AClosure.Template.MaxRegisters - 1; for I := 0 to ArgsToCopy - 1 do - FRegisters[NewBase + I] := FRegisters[AArgBase + I]; - for I := ArgsToCopy to AClosure.Prototype.MaxRegisters - 1 do + FRegisters[NewBase + 1 + I] := FRegisters[AArgBase + I]; + for I := ArgsToCopy + 1 to AClosure.Template.MaxRegisters - 1 do FRegisters[NewBase + I] := SouffleNil; - Frame := FCallStack.Push(AClosure.Prototype, AClosure, NewBase, + Frame := FCallStack.Push(AClosure.Template, AClosure, NewBase, AReturnAbsolute, FHandlerStack.Count); + Frame^.ArgCount := ArgsToCopy; + Frame^.ArgSourceBase := NewBase + 1; end; -procedure TSouffleVM.ExecuteTier1(const AFrame: PSouffleVMCallFrame; +function TSouffleVM.InvokeWithSpread(const ACallee, AArgsArray, + AReceiver: TSouffleValue): TSouffleValue; +var + Arr: TSouffleArray; + Args: array of TSouffleValue; + I, Count: Integer; +begin + if SouffleIsReference(AArgsArray) and (AArgsArray.AsReference is TSouffleArray) then + Arr := TSouffleArray(AArgsArray.AsReference) + else + Arr := nil; + + if Assigned(Arr) then + Count := Arr.Count + else + Count := 0; + + SetLength(Args, Count); + for I := 0 to Count - 1 do + Args[I] := Arr.Get(I); + + if SouffleIsReference(ACallee) and Assigned(ACallee.AsReference) then + begin + if ACallee.AsReference is TSouffleNativeFunction then + begin + if Count > 0 then + Result := TSouffleNativeFunction(ACallee.AsReference).Invoke( + AReceiver, @Args[0], Count) + else + Result := TSouffleNativeFunction(ACallee.AsReference).Invoke( + AReceiver, nil, 0); + Exit; + end; + end; + + if Count > 0 then + Result := FRuntimeOps.Invoke(ACallee, @Args[0], Count, AReceiver) + else + Result := FRuntimeOps.Invoke(ACallee, nil, 0, AReceiver); +end; + +procedure TSouffleVM.ExecuteCoreOp(const AFrame: PSouffleVMCallFrame; const AInstruction: UInt32; const AOp: UInt8); var - A, B: UInt8; + A, B, C: UInt8; Bx: UInt16; sBx: Int16; Ax: Int32; Base: Integer; Closure: TSouffleClosure; Upval: TSouffleUpvalue; - Proto: TSouffleFunctionPrototype; + Tmpl: TSouffleFunctionTemplate; Desc: TSouffleUpvalueDescriptor; I: Integer; Handler: TSouffleHandlerEntry; + Arr, RestArr: TSouffleArray; + RestCount: Integer; + Rec: TSouffleRecord; + RecVal: TSouffleValue; + Bp, WalkBp: TSouffleBlueprint; begin Base := AFrame^.BaseRegister; @@ -222,13 +371,14 @@ procedure TSouffleVM.ExecuteTier1(const AFrame: PSouffleVMCallFrame; A := DecodeA(AInstruction); Bx := DecodeBx(AInstruction); FRegisters[Base + A] := MaterializeConstant( - AFrame^.Prototype.GetConstant(Bx)); + AFrame^.Template.GetConstant(Bx)); end; OP_LOAD_NIL: begin A := DecodeA(AInstruction); - FRegisters[Base + A] := SouffleNil; + B := DecodeB(AInstruction); + FRegisters[Base + A] := SouffleNilWithFlags(B); end; OP_LOAD_TRUE: @@ -342,31 +492,35 @@ procedure TSouffleVM.ExecuteTier1(const AFrame: PSouffleVMCallFrame; OP_JUMP_IF_NIL: begin A := DecodeA(AInstruction); - sBx := DecodesBx(AInstruction); - if SouffleIsNil(FRegisters[Base + A]) then - AFrame^.IP := AFrame^.IP + sBx; + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + if SouffleIsNil(FRegisters[Base + A]) and + ((B = SOUFFLE_NIL_MATCH_ANY) or (FRegisters[Base + A].Flags = B)) then + AFrame^.IP := AFrame^.IP + C; end; OP_JUMP_IF_NOT_NIL: begin A := DecodeA(AInstruction); - sBx := DecodesBx(AInstruction); - if not SouffleIsNil(FRegisters[Base + A]) then - AFrame^.IP := AFrame^.IP + sBx; + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + if not (SouffleIsNil(FRegisters[Base + A]) and + ((B = SOUFFLE_NIL_MATCH_ANY) or (FRegisters[Base + A].Flags = B))) then + AFrame^.IP := AFrame^.IP + C; end; OP_CLOSURE: begin A := DecodeA(AInstruction); Bx := DecodeBx(AInstruction); - Proto := AFrame^.Prototype.GetFunction(Bx); - Closure := TSouffleClosure.Create(Proto); + Tmpl := AFrame^.Template.GetFunction(Bx); + Closure := TSouffleClosure.Create(Tmpl); if Assigned(FGC) then FGC.AllocateObject(Closure); - for I := 0 to Proto.UpvalueCount - 1 do + for I := 0 to Tmpl.UpvalueCount - 1 do begin - Desc := Proto.GetUpvalueDescriptor(I); + Desc := Tmpl.GetUpvalueDescriptor(I); if Desc.IsLocal then Upval := CaptureUpvalue(Base + Desc.Index) else if Assigned(AFrame^.Closure) then @@ -421,72 +575,636 @@ procedure TSouffleVM.ExecuteTier1(const AFrame: PSouffleVMCallFrame; begin A := DecodeA(AInstruction); CloseUpvalues(Base); - FRegisters[AFrame^.ReturnRegister] := FRegisters[Base + A]; + if AFrame^.Template.IsAsync then + FRegisters[AFrame^.ReturnRegister] := + FRuntimeOps.WrapInPromise(FRegisters[Base + A], False) + else + FRegisters[AFrame^.ReturnRegister] := FRegisters[Base + A]; FCallStack.Pop; end; OP_RETURN_NIL: begin CloseUpvalues(Base); - FRegisters[AFrame^.ReturnRegister] := SouffleNil; + if AFrame^.Template.IsAsync then + FRegisters[AFrame^.ReturnRegister] := + FRuntimeOps.WrapInPromise(SouffleNil, False) + else + FRegisters[AFrame^.ReturnRegister] := SouffleNil; FCallStack.Pop; end; + OP_NEW_ARRAY: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + Arr := TSouffleArray.Create(B); + Arr.Delegate := FArrayDelegate; + if Assigned(FGC) then + FGC.AllocateObject(Arr); + FRegisters[Base + A] := SouffleReference(Arr); + end; + + OP_ARRAY_PUSH: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + if SouffleIsReference(FRegisters[Base + A]) and + (FRegisters[Base + A].AsReference is TSouffleArray) then + TSouffleArray(FRegisters[Base + A].AsReference).Push(FRegisters[Base + B]); + end; + + OP_ARRAY_GET: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + if SouffleIsReference(FRegisters[Base + B]) and + (FRegisters[Base + B].AsReference is TSouffleArray) and + (FRegisters[Base + C].Kind = svkInteger) then + FRegisters[Base + A] := TSouffleArray( + FRegisters[Base + B].AsReference).Get( + Integer(FRegisters[Base + C].AsInteger)) + else + FRegisters[Base + A] := FRuntimeOps.GetIndex( + FRegisters[Base + B], FRegisters[Base + C]); + end; + + OP_ARRAY_SET: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + if SouffleIsReference(FRegisters[Base + A]) and + (FRegisters[Base + A].AsReference is TSouffleArray) and + (FRegisters[Base + B].Kind = svkInteger) then + TSouffleArray(FRegisters[Base + A].AsReference).Put( + Integer(FRegisters[Base + B].AsInteger), FRegisters[Base + C]) + else + FRuntimeOps.SetIndex( + FRegisters[Base + A], FRegisters[Base + B], FRegisters[Base + C]); + end; + + OP_NEW_RECORD: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + Rec := TSouffleRecord.Create(B); + Rec.Delegate := FRecordDelegate; + if Assigned(FGC) then + FGC.AllocateObject(Rec); + FRegisters[Base + A] := SouffleReference(Rec); + end; + + OP_RECORD_GET: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + if SouffleIsReference(FRegisters[Base + B]) and + Assigned(FRegisters[Base + B].AsReference) then + begin + if FRegisters[Base + B].AsReference is TSouffleRecord then + begin + Rec := TSouffleRecord(FRegisters[Base + B].AsReference); + if Rec.Get(AFrame^.Template.GetConstant(C).StringValue, RecVal) then + begin + FRegisters[Base + A] := RecVal; + Exit; + end; + end; + if DelegateGet(FRegisters[Base + B].AsReference, + AFrame^.Template.GetConstant(C).StringValue, RecVal) then + begin + FRegisters[Base + A] := RecVal; + Exit; + end; + end; + FRegisters[Base + A] := FRuntimeOps.GetProperty( + FRegisters[Base + B], AFrame^.Template.GetConstant(C).StringValue); + end; + + OP_RECORD_SET: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + if SouffleIsReference(FRegisters[Base + A]) then + begin + if FRegisters[Base + A].AsReference is TSouffleRecord then + TSouffleRecord(FRegisters[Base + A].AsReference).PutChecked( + AFrame^.Template.GetConstant(B).StringValue, + FRegisters[Base + C]) + else if FRegisters[Base + A].AsReference is TSouffleBlueprint then + TSouffleBlueprint(FRegisters[Base + A].AsReference).Methods.Put( + AFrame^.Template.GetConstant(B).StringValue, FRegisters[Base + C]) + else + FRuntimeOps.SetProperty( + FRegisters[Base + A], AFrame^.Template.GetConstant(B).StringValue, + FRegisters[Base + C]); + end + else + FRuntimeOps.SetProperty( + FRegisters[Base + A], AFrame^.Template.GetConstant(B).StringValue, + FRegisters[Base + C]); + end; + + OP_RECORD_DELETE: + begin + A := DecodeA(AInstruction); + Bx := DecodeBx(AInstruction); + if SouffleIsReference(FRegisters[Base + A]) and + (FRegisters[Base + A].AsReference is TSouffleRecord) then + TSouffleRecord(FRegisters[Base + A].AsReference).DeleteChecked( + AFrame^.Template.GetConstant(Bx).StringValue) + else + FRuntimeOps.DeleteProperty( + FRegisters[Base + A], AFrame^.Template.GetConstant(Bx).StringValue); + end; + + OP_GET_LENGTH: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + if SouffleIsStringValue(FRegisters[Base + B]) then + FRegisters[Base + A] := SouffleInteger( + Length(SouffleGetString(FRegisters[Base + B]))) + else if SouffleIsReference(FRegisters[Base + B]) and + Assigned(FRegisters[Base + B].AsReference) then + begin + if FRegisters[Base + B].AsReference is TSouffleArray then + FRegisters[Base + A] := SouffleInteger( + TSouffleArray(FRegisters[Base + B].AsReference).Count) + else if FRegisters[Base + B].AsReference is TSouffleRecord then + FRegisters[Base + A] := SouffleInteger( + TSouffleRecord(FRegisters[Base + B].AsReference).Count) + else + FRegisters[Base + A] := FRuntimeOps.GetProperty( + FRegisters[Base + B], 'length'); + end + else + FRegisters[Base + A] := SouffleInteger(0); + end; + + OP_ARG_COUNT: + begin + A := DecodeA(AInstruction); + FRegisters[Base + A] := SouffleInteger(AFrame^.ArgCount); + end; + + OP_PACK_ARGS: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + RestCount := AFrame^.ArgCount - Integer(B); + if RestCount < 0 then + RestCount := 0; + RestArr := TSouffleArray.Create(RestCount); + RestArr.Delegate := FArrayDelegate; + if Assigned(FGC) then + FGC.AllocateObject(RestArr); + for I := 0 to RestCount - 1 do + RestArr.Push(FRegisters[AFrame^.ArgSourceBase + Integer(B) + I]); + FRegisters[Base + A] := SouffleReference(RestArr); + end; + + OP_ADD_INT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleInteger( + FRegisters[Base + B].AsInteger + FRegisters[Base + C].AsInteger); + end; + + OP_SUB_INT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleInteger( + FRegisters[Base + B].AsInteger - FRegisters[Base + C].AsInteger); + end; + + OP_MUL_INT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleInteger( + FRegisters[Base + B].AsInteger * FRegisters[Base + C].AsInteger); + end; + + OP_DIV_INT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleFloat( + (FRegisters[Base + B].AsInteger * 1.0) / + (FRegisters[Base + C].AsInteger * 1.0)); + end; + + OP_MOD_INT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + if FRegisters[Base + C].AsInteger = 0 then + FRegisters[Base + A] := SouffleFloat(NaN) + else + FRegisters[Base + A] := SouffleInteger( + FRegisters[Base + B].AsInteger mod FRegisters[Base + C].AsInteger); + end; + + OP_NEG_INT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + FRegisters[Base + A] := SouffleInteger( + -FRegisters[Base + B].AsInteger); + end; + + OP_ADD_FLOAT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleFloat( + FRegisters[Base + B].AsFloat + FRegisters[Base + C].AsFloat); + end; + + OP_SUB_FLOAT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleFloat( + FRegisters[Base + B].AsFloat - FRegisters[Base + C].AsFloat); + end; + + OP_MUL_FLOAT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleFloat( + FRegisters[Base + B].AsFloat * FRegisters[Base + C].AsFloat); + end; + + OP_DIV_FLOAT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleFloat( + FRegisters[Base + B].AsFloat / FRegisters[Base + C].AsFloat); + end; + + OP_MOD_FLOAT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleFloat( + FMod(FRegisters[Base + B].AsFloat, FRegisters[Base + C].AsFloat)); + end; + + OP_NEG_FLOAT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + FRegisters[Base + A] := SouffleFloat( + -FRegisters[Base + B].AsFloat); + end; + + OP_EQ_INT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsInteger = FRegisters[Base + C].AsInteger); + end; + + OP_NEQ_INT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsInteger <> FRegisters[Base + C].AsInteger); + end; + + OP_LT_INT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsInteger < FRegisters[Base + C].AsInteger); + end; + + OP_GT_INT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsInteger > FRegisters[Base + C].AsInteger); + end; + + OP_LTE_INT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsInteger <= FRegisters[Base + C].AsInteger); + end; + + OP_GTE_INT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsInteger >= FRegisters[Base + C].AsInteger); + end; + + OP_CONCAT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRegisters[Base + B] := FRuntimeOps.CoerceValueToString(FRegisters[Base + B]); + FRegisters[Base + C] := FRuntimeOps.CoerceValueToString(FRegisters[Base + C]); + FRegisters[Base + A] := SouffleString( + SouffleGetString(FRegisters[Base + B]) + + SouffleGetString(FRegisters[Base + C])); + end; + + OP_GET_LOCAL_INT, OP_GET_LOCAL_FLOAT, OP_GET_LOCAL_BOOL, + OP_GET_LOCAL_STRING, OP_GET_LOCAL_REF: + begin + A := DecodeA(AInstruction); + Bx := DecodeBx(AInstruction); + FRegisters[Base + A] := FRegisters[Base + Bx]; + end; + + OP_SET_LOCAL_INT, OP_SET_LOCAL_FLOAT, OP_SET_LOCAL_BOOL, + OP_SET_LOCAL_STRING, OP_SET_LOCAL_REF: + begin + A := DecodeA(AInstruction); + Bx := DecodeBx(AInstruction); + FRegisters[Base + Bx] := FRegisters[Base + A]; + end; + + OP_ARRAY_POP: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + if SouffleIsReference(FRegisters[Base + B]) and + (FRegisters[Base + B].AsReference is TSouffleArray) then + FRegisters[Base + A] := TSouffleArray( + FRegisters[Base + B].AsReference).Pop + else + FRegisters[Base + A] := SouffleNil; + end; + + OP_NEW_BLUEPRINT: + begin + A := DecodeA(AInstruction); + Bx := DecodeBx(AInstruction); + Bp := TSouffleBlueprint.Create( + AFrame^.Template.GetConstant(Bx).StringValue, 0); + if Assigned(FGC) then + FGC.AllocateObject(Bp); + FRegisters[Base + A] := SouffleReference(Bp); + end; + + OP_INHERIT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + if SouffleIsReference(FRegisters[Base + A]) and + (FRegisters[Base + A].AsReference is TSouffleBlueprint) and + SouffleIsReference(FRegisters[Base + B]) and + (FRegisters[Base + B].AsReference is TSouffleBlueprint) then + TSouffleBlueprint(FRegisters[Base + A].AsReference).SuperBlueprint := + TSouffleBlueprint(FRegisters[Base + B].AsReference); + end; + + OP_INSTANTIATE: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + if SouffleIsReference(FRegisters[Base + B]) and + (FRegisters[Base + B].AsReference is TSouffleBlueprint) then + begin + Bp := TSouffleBlueprint(FRegisters[Base + B].AsReference); + Rec := TSouffleRecord.CreateFromBlueprint(Bp); + Rec.Delegate := Bp.Prototype; + WalkBp := Bp; + while Assigned(WalkBp) do + begin + if not Assigned(WalkBp.Prototype.Delegate) then + WalkBp.Prototype.Delegate := WalkBp.Methods; + if not Assigned(WalkBp.Methods.Delegate) then + begin + if Assigned(WalkBp.SuperBlueprint) then + WalkBp.Methods.Delegate := WalkBp.SuperBlueprint.Prototype + else if Assigned(FRecordDelegate) then + WalkBp.Methods.Delegate := FRecordDelegate; + end; + WalkBp := WalkBp.SuperBlueprint; + end; + if Assigned(FGC) then + FGC.AllocateObject(Rec); + FRegisters[Base + A] := SouffleReference(Rec); + end + else + FRegisters[Base + A] := SouffleNil; + end; + + OP_GET_SLOT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + if SouffleIsReference(FRegisters[Base + B]) and + (FRegisters[Base + B].AsReference is TSouffleRecord) then + FRegisters[Base + A] := + TSouffleRecord(FRegisters[Base + B].AsReference).GetSlot(C) + else + FRegisters[Base + A] := SouffleNil; + end; + + OP_SET_SLOT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + if SouffleIsReference(FRegisters[Base + A]) and + (FRegisters[Base + A].AsReference is TSouffleRecord) then + TSouffleRecord(FRegisters[Base + A].AsReference).SetSlot( + B, FRegisters[Base + C]); + end; + + OP_TO_PRIMITIVE: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + case FRegisters[Base + B].Kind of + svkNil, svkBoolean, svkInteger, svkFloat: + FRegisters[Base + A] := FRegisters[Base + B]; + else + if SouffleIsStringValue(FRegisters[Base + B]) then + FRegisters[Base + A] := FRegisters[Base + B] + else + FRegisters[Base + A] := FRuntimeOps.ToPrimitive(FRegisters[Base + B]); + end; + end; + + OP_UNPACK: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + if SouffleIsReference(FRegisters[Base + B]) and + (FRegisters[Base + B].AsReference is TSouffleArray) then + begin + Arr := TSouffleArray(FRegisters[Base + B].AsReference); + RestCount := Arr.Count - Integer(C); + if RestCount < 0 then + RestCount := 0; + RestArr := TSouffleArray.Create(RestCount); + RestArr.Delegate := FArrayDelegate; + if Assigned(FGC) then + FGC.AllocateObject(RestArr); + for I := Integer(C) to Arr.Count - 1 do + RestArr.Push(Arr.Get(I)); + FRegisters[Base + A] := SouffleReference(RestArr); + end + else + FRegisters[Base + A] := SouffleNil; + end; + + OP_NOP:; OP_LINE:; end; end; -procedure TSouffleVM.ExecuteTier2(const AFrame: PSouffleVMCallFrame; +procedure TSouffleVM.ExecuteRuntimeOp(const AFrame: PSouffleVMCallFrame; const AInstruction: UInt32; const AOp: UInt8); var A, B, C: UInt8; Bx: UInt16; Base: Integer; Done: Boolean; + FloatB, FloatC: Double; begin Base := AFrame^.BaseRegister; case TSouffleOpCode(AOp) of - // Arithmetic OP_RT_ADD: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); - FRegisters[Base + A] := FRuntimeOps.Add(FRegisters[Base + B], FRegisters[Base + C]); + if (FRegisters[Base + B].Kind = svkInteger) and + (FRegisters[Base + C].Kind = svkInteger) then + FRegisters[Base + A] := SouffleInteger( + FRegisters[Base + B].AsInteger + FRegisters[Base + C].AsInteger) + else if SouffleIsNumeric(FRegisters[Base + B]) and + SouffleIsNumeric(FRegisters[Base + C]) then + FRegisters[Base + A] := SouffleFloat( + SouffleAsNumber(FRegisters[Base + B]) + SouffleAsNumber(FRegisters[Base + C])) + else + FRegisters[Base + A] := FRuntimeOps.Add( + FRegisters[Base + B], FRegisters[Base + C]); end; OP_RT_SUB: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); - FRegisters[Base + A] := FRuntimeOps.Subtract(FRegisters[Base + B], FRegisters[Base + C]); + if (FRegisters[Base + B].Kind = svkInteger) and + (FRegisters[Base + C].Kind = svkInteger) then + FRegisters[Base + A] := SouffleInteger( + FRegisters[Base + B].AsInteger - FRegisters[Base + C].AsInteger) + else if SouffleIsNumeric(FRegisters[Base + B]) and + SouffleIsNumeric(FRegisters[Base + C]) then + FRegisters[Base + A] := SouffleFloat( + SouffleAsNumber(FRegisters[Base + B]) - SouffleAsNumber(FRegisters[Base + C])) + else + FRegisters[Base + A] := FRuntimeOps.Subtract( + FRegisters[Base + B], FRegisters[Base + C]); end; OP_RT_MUL: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); - FRegisters[Base + A] := FRuntimeOps.Multiply(FRegisters[Base + B], FRegisters[Base + C]); + if (FRegisters[Base + B].Kind = svkInteger) and + (FRegisters[Base + C].Kind = svkInteger) then + FRegisters[Base + A] := SouffleInteger( + FRegisters[Base + B].AsInteger * FRegisters[Base + C].AsInteger) + else if SouffleIsNumeric(FRegisters[Base + B]) and + SouffleIsNumeric(FRegisters[Base + C]) then + FRegisters[Base + A] := SouffleFloat( + SouffleAsNumber(FRegisters[Base + B]) * SouffleAsNumber(FRegisters[Base + C])) + else + FRegisters[Base + A] := FRuntimeOps.Multiply( + FRegisters[Base + B], FRegisters[Base + C]); end; OP_RT_DIV: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); - FRegisters[Base + A] := FRuntimeOps.Divide(FRegisters[Base + B], FRegisters[Base + C]); + if SouffleIsNumeric(FRegisters[Base + B]) and + SouffleIsNumeric(FRegisters[Base + C]) then + FRegisters[Base + A] := SouffleFloat( + SouffleAsNumber(FRegisters[Base + B]) / + SouffleAsNumber(FRegisters[Base + C])) + else + FRegisters[Base + A] := FRuntimeOps.Divide( + FRegisters[Base + B], FRegisters[Base + C]); end; OP_RT_MOD: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); - FRegisters[Base + A] := FRuntimeOps.Modulo(FRegisters[Base + B], FRegisters[Base + C]); + if (FRegisters[Base + B].Kind = svkInteger) and + (FRegisters[Base + C].Kind = svkInteger) and + (FRegisters[Base + C].AsInteger <> 0) then + FRegisters[Base + A] := SouffleInteger( + FRegisters[Base + B].AsInteger mod FRegisters[Base + C].AsInteger) + else if SouffleIsNumeric(FRegisters[Base + B]) and + SouffleIsNumeric(FRegisters[Base + C]) then + begin + FloatC := SouffleAsNumber(FRegisters[Base + C]); + if FloatC = 0.0 then + FRegisters[Base + A] := SouffleFloat(NaN) + else + FRegisters[Base + A] := SouffleFloat( + FMod(SouffleAsNumber(FRegisters[Base + B]), FloatC)); + end + else + FRegisters[Base + A] := FRuntimeOps.Modulo( + FRegisters[Base + B], FRegisters[Base + C]); end; OP_RT_POW: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); - FRegisters[Base + A] := FRuntimeOps.Power(FRegisters[Base + B], FRegisters[Base + C]); + FRegisters[Base + A] := FRuntimeOps.Power( + FRegisters[Base + B], FRegisters[Base + C]); end; OP_RT_NEG: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); - FRegisters[Base + A] := FRuntimeOps.Negate(FRegisters[Base + B]); + if FRegisters[Base + B].Kind = svkInteger then + FRegisters[Base + A] := SouffleInteger(-FRegisters[Base + B].AsInteger) + else if FRegisters[Base + B].Kind = svkFloat then + FRegisters[Base + A] := SouffleFloat(-FRegisters[Base + B].AsFloat) + else + FRegisters[Base + A] := FRuntimeOps.Negate(FRegisters[Base + B]); end; - // Bitwise OP_RT_BAND: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); @@ -523,39 +1241,137 @@ procedure TSouffleVM.ExecuteTier2(const AFrame: PSouffleVMCallFrame; FRegisters[Base + A] := FRuntimeOps.BitwiseNot(FRegisters[Base + B]); end; - // Comparison OP_RT_EQ: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); - FRegisters[Base + A] := FRuntimeOps.Equal(FRegisters[Base + B], FRegisters[Base + C]); + if FRegisters[Base + B].Kind <> FRegisters[Base + C].Kind then + begin + if SouffleIsNumeric(FRegisters[Base + B]) and + SouffleIsNumeric(FRegisters[Base + C]) then + FRegisters[Base + A] := SouffleBoolean( + SouffleAsNumber(FRegisters[Base + B]) = SouffleAsNumber(FRegisters[Base + C])) + else + FRegisters[Base + A] := FRuntimeOps.Equal( + FRegisters[Base + B], FRegisters[Base + C]); + end + else + case FRegisters[Base + B].Kind of + svkNil: + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].Flags = FRegisters[Base + C].Flags); + svkBoolean: + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsBoolean = FRegisters[Base + C].AsBoolean); + svkInteger: + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsInteger = FRegisters[Base + C].AsInteger); + svkFloat: + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsFloat = FRegisters[Base + C].AsFloat); + svkString: + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsInlineString = FRegisters[Base + C].AsInlineString); + else + FRegisters[Base + A] := FRuntimeOps.Equal( + FRegisters[Base + B], FRegisters[Base + C]); + end; end; OP_RT_NEQ: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); - FRegisters[Base + A] := FRuntimeOps.NotEqual(FRegisters[Base + B], FRegisters[Base + C]); + if FRegisters[Base + B].Kind <> FRegisters[Base + C].Kind then + begin + if SouffleIsNumeric(FRegisters[Base + B]) and + SouffleIsNumeric(FRegisters[Base + C]) then + FRegisters[Base + A] := SouffleBoolean( + SouffleAsNumber(FRegisters[Base + B]) <> SouffleAsNumber(FRegisters[Base + C])) + else + FRegisters[Base + A] := FRuntimeOps.NotEqual( + FRegisters[Base + B], FRegisters[Base + C]); + end + else + case FRegisters[Base + B].Kind of + svkNil: + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].Flags <> FRegisters[Base + C].Flags); + svkBoolean: + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsBoolean <> FRegisters[Base + C].AsBoolean); + svkInteger: + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsInteger <> FRegisters[Base + C].AsInteger); + svkFloat: + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsFloat <> FRegisters[Base + C].AsFloat); + svkString: + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsInlineString <> FRegisters[Base + C].AsInlineString); + else + FRegisters[Base + A] := FRuntimeOps.NotEqual( + FRegisters[Base + B], FRegisters[Base + C]); + end; end; OP_RT_LT: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); - FRegisters[Base + A] := FRuntimeOps.LessThan(FRegisters[Base + B], FRegisters[Base + C]); + if (FRegisters[Base + B].Kind = svkInteger) and + (FRegisters[Base + C].Kind = svkInteger) then + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsInteger < FRegisters[Base + C].AsInteger) + else if SouffleIsNumeric(FRegisters[Base + B]) and + SouffleIsNumeric(FRegisters[Base + C]) then + FRegisters[Base + A] := SouffleBoolean( + SouffleAsNumber(FRegisters[Base + B]) < SouffleAsNumber(FRegisters[Base + C])) + else + FRegisters[Base + A] := FRuntimeOps.LessThan( + FRegisters[Base + B], FRegisters[Base + C]); end; OP_RT_GT: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); - FRegisters[Base + A] := FRuntimeOps.GreaterThan(FRegisters[Base + B], FRegisters[Base + C]); + if (FRegisters[Base + B].Kind = svkInteger) and + (FRegisters[Base + C].Kind = svkInteger) then + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsInteger > FRegisters[Base + C].AsInteger) + else if SouffleIsNumeric(FRegisters[Base + B]) and + SouffleIsNumeric(FRegisters[Base + C]) then + FRegisters[Base + A] := SouffleBoolean( + SouffleAsNumber(FRegisters[Base + B]) > SouffleAsNumber(FRegisters[Base + C])) + else + FRegisters[Base + A] := FRuntimeOps.GreaterThan( + FRegisters[Base + B], FRegisters[Base + C]); end; OP_RT_LTE: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); - FRegisters[Base + A] := FRuntimeOps.LessThanOrEqual(FRegisters[Base + B], FRegisters[Base + C]); + if (FRegisters[Base + B].Kind = svkInteger) and + (FRegisters[Base + C].Kind = svkInteger) then + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsInteger <= FRegisters[Base + C].AsInteger) + else if SouffleIsNumeric(FRegisters[Base + B]) and + SouffleIsNumeric(FRegisters[Base + C]) then + FRegisters[Base + A] := SouffleBoolean( + SouffleAsNumber(FRegisters[Base + B]) <= SouffleAsNumber(FRegisters[Base + C])) + else + FRegisters[Base + A] := FRuntimeOps.LessThanOrEqual( + FRegisters[Base + B], FRegisters[Base + C]); end; OP_RT_GTE: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); - FRegisters[Base + A] := FRuntimeOps.GreaterThanOrEqual(FRegisters[Base + B], FRegisters[Base + C]); + if (FRegisters[Base + B].Kind = svkInteger) and + (FRegisters[Base + C].Kind = svkInteger) then + FRegisters[Base + A] := SouffleBoolean( + FRegisters[Base + B].AsInteger >= FRegisters[Base + C].AsInteger) + else if SouffleIsNumeric(FRegisters[Base + B]) and + SouffleIsNumeric(FRegisters[Base + C]) then + FRegisters[Base + A] := SouffleBoolean( + SouffleAsNumber(FRegisters[Base + B]) >= SouffleAsNumber(FRegisters[Base + C])) + else + FRegisters[Base + A] := FRuntimeOps.GreaterThanOrEqual( + FRegisters[Base + B], FRegisters[Base + C]); end; - // Logical / Type OP_RT_NOT: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); @@ -582,38 +1398,17 @@ procedure TSouffleVM.ExecuteTier2(const AFrame: PSouffleVMCallFrame; FRegisters[Base + A] := FRuntimeOps.ToBoolean(FRegisters[Base + B]); end; - // Compound creation - OP_RT_NEW_COMPOUND: - begin - A := DecodeA(AInstruction); B := DecodeB(AInstruction); - FRegisters[Base + A] := FRuntimeOps.CreateCompound(B); - end; - OP_RT_INIT_FIELD: - begin - A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); - FRuntimeOps.InitField(FRegisters[Base + A], - AFrame^.Prototype.GetConstant(B).StringValue, - FRegisters[Base + C]); - end; - OP_RT_INIT_INDEX: - begin - A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); - FRuntimeOps.InitIndex(FRegisters[Base + A], - FRegisters[Base + B], FRegisters[Base + C]); - end; - - // Property access OP_RT_GET_PROP: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); FRegisters[Base + A] := FRuntimeOps.GetProperty(FRegisters[Base + B], - AFrame^.Prototype.GetConstant(C).StringValue); + AFrame^.Template.GetConstant(C).StringValue); end; OP_RT_SET_PROP: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); FRuntimeOps.SetProperty(FRegisters[Base + A], - AFrame^.Prototype.GetConstant(B).StringValue, + AFrame^.Template.GetConstant(B).StringValue, FRegisters[Base + C]); end; OP_RT_GET_INDEX: @@ -630,46 +1425,66 @@ procedure TSouffleVM.ExecuteTier2(const AFrame: PSouffleVMCallFrame; begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); FRegisters[Base + A] := FRuntimeOps.DeleteProperty(FRegisters[Base + B], - AFrame^.Prototype.GetConstant(C).StringValue); + AFrame^.Template.GetConstant(C).StringValue); end; - // Invocation OP_RT_CALL: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); - if SouffleIsReference(FRegisters[Base + A]) and - (FRegisters[Base + A].AsReference is TSouffleClosure) then - CallClosure(TSouffleClosure(FRegisters[Base + A].AsReference), - Base + A + 1, B, Base + A, SouffleNil) - else + C := DecodeC(AInstruction); + if C and 1 = 1 then + FRegisters[Base + A] := InvokeWithSpread( + FRegisters[Base + A], FRegisters[Base + B], SouffleNil) + else if SouffleIsReference(FRegisters[Base + A]) and + Assigned(FRegisters[Base + A].AsReference) then begin + if FRegisters[Base + A].AsReference is TSouffleClosure then + CallClosure(TSouffleClosure(FRegisters[Base + A].AsReference), + Base + A + 1, B, Base + A, SouffleNil) + else if FRegisters[Base + A].AsReference is TSouffleNativeFunction then + FRegisters[Base + A] := TSouffleNativeFunction( + FRegisters[Base + A].AsReference).Invoke( + SouffleNil, @FRegisters[Base + A + 1], B) + else + FRegisters[Base + A] := FRuntimeOps.Invoke( + FRegisters[Base + A], @FRegisters[Base + A + 1], B, SouffleNil); + end + else FRegisters[Base + A] := FRuntimeOps.Invoke( - FRegisters[Base + A], - @FRegisters[Base + A + 1], - B, - SouffleNil); - end; + FRegisters[Base + A], @FRegisters[Base + A + 1], B, SouffleNil); if Assigned(FGC) then FGC.CollectIfNeeded; end; OP_RT_CALL_METHOD: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); - if SouffleIsReference(FRegisters[Base + A]) and - (FRegisters[Base + A].AsReference is TSouffleClosure) then - CallClosure(TSouffleClosure(FRegisters[Base + A].AsReference), - Base + A + 1, B, Base + A, FRegisters[Base + A - 1]) - else + C := DecodeC(AInstruction); + if C and 1 = 1 then + FRegisters[Base + A] := InvokeWithSpread( + FRegisters[Base + A], FRegisters[Base + B], FRegisters[Base + A - 1]) + else if SouffleIsReference(FRegisters[Base + A]) and + Assigned(FRegisters[Base + A].AsReference) then begin + if FRegisters[Base + A].AsReference is TSouffleClosure then + CallClosure(TSouffleClosure(FRegisters[Base + A].AsReference), + Base + A + 1, B, Base + A, FRegisters[Base + A - 1]) + else if FRegisters[Base + A].AsReference is TSouffleNativeFunction then + FRegisters[Base + A] := TSouffleNativeFunction( + FRegisters[Base + A].AsReference).Invoke( + FRegisters[Base + A - 1], @FRegisters[Base + A + 1], B) + else + FRegisters[Base + A] := FRuntimeOps.Invoke( + FRegisters[Base + A], @FRegisters[Base + A + 1], B, + FRegisters[Base + A - 1]); + end + else FRegisters[Base + A] := FRuntimeOps.Invoke( - FRegisters[Base + A], - @FRegisters[Base + A + 1], - B, + FRegisters[Base + A], @FRegisters[Base + A + 1], B, FRegisters[Base + A - 1]); - end; if Assigned(FGC) then FGC.CollectIfNeeded; end; + OP_RT_CONSTRUCT: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); @@ -681,11 +1496,10 @@ procedure TSouffleVM.ExecuteTier2(const AFrame: PSouffleVMCallFrame; FGC.CollectIfNeeded; end; - // Iteration OP_RT_GET_ITER: begin - A := DecodeA(AInstruction); B := DecodeB(AInstruction); - FRegisters[Base + A] := FRuntimeOps.GetIterator(FRegisters[Base + B]); + A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); + FRegisters[Base + A] := FRuntimeOps.GetIterator(FRegisters[Base + B], C = 1); end; OP_RT_ITER_NEXT: begin @@ -693,47 +1507,71 @@ procedure TSouffleVM.ExecuteTier2(const AFrame: PSouffleVMCallFrame; FRegisters[Base + A] := FRuntimeOps.IteratorNext(FRegisters[Base + C], Done); FRegisters[Base + B] := SouffleBoolean(Done); end; - OP_RT_SPREAD: - begin - A := DecodeA(AInstruction); B := DecodeB(AInstruction); - FRuntimeOps.SpreadInto(FRegisters[Base + A], FRegisters[Base + B]); - end; - // Modules OP_RT_IMPORT: begin A := DecodeA(AInstruction); Bx := DecodeBx(AInstruction); FRegisters[Base + A] := FRuntimeOps.ImportModule( - AFrame^.Prototype.GetConstant(Bx).StringValue); + AFrame^.Template.GetConstant(Bx).StringValue); end; OP_RT_EXPORT: begin A := DecodeA(AInstruction); Bx := DecodeBx(AInstruction); FRuntimeOps.ExportBinding(FRegisters[Base + A], - AFrame^.Prototype.GetConstant(Bx).StringValue); + AFrame^.Template.GetConstant(Bx).StringValue); end; - // Async OP_RT_AWAIT: begin A := DecodeA(AInstruction); B := DecodeB(AInstruction); FRegisters[Base + A] := FRuntimeOps.AwaitValue(FRegisters[Base + B]); end; - // Globals OP_RT_GET_GLOBAL: begin A := DecodeA(AInstruction); Bx := DecodeBx(AInstruction); FRegisters[Base + A] := FRuntimeOps.GetGlobal( - AFrame^.Prototype.GetConstant(Bx).StringValue); + AFrame^.Template.GetConstant(Bx).StringValue); end; OP_RT_SET_GLOBAL: begin A := DecodeA(AInstruction); Bx := DecodeBx(AInstruction); FRuntimeOps.SetGlobal( - AFrame^.Prototype.GetConstant(Bx).StringValue, + AFrame^.Template.GetConstant(Bx).StringValue, FRegisters[Base + A]); end; + OP_RT_HAS_GLOBAL: + begin + A := DecodeA(AInstruction); Bx := DecodeBx(AInstruction); + FRegisters[Base + A] := SouffleBoolean(FRuntimeOps.HasGlobal( + AFrame^.Template.GetConstant(Bx).StringValue)); + end; + + + OP_RT_DEL_INDEX: + begin + A := DecodeA(AInstruction); B := DecodeB(AInstruction); C := DecodeC(AInstruction); + FRegisters[Base + A] := FRuntimeOps.DeleteIndex( + FRegisters[Base + B], FRegisters[Base + C]); + end; + + OP_RT_TO_STRING: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + FRegisters[Base + A] := FRuntimeOps.CoerceValueToString(FRegisters[Base + B]); + end; + + + OP_RT_EXT: + begin + A := DecodeA(AInstruction); + B := DecodeB(AInstruction); + C := DecodeC(AInstruction); + FRuntimeOps.ExtendedOperation(B, + FRegisters[Base + A], FRegisters[Base + C], + FRegisters[Base + A + 1], AFrame^.Template, C); + end; end; end; @@ -783,10 +1621,18 @@ procedure TSouffleVM.MarkVMRoots; HighWater: Integer; Frame: PSouffleVMCallFrame; begin + if Assigned(FRuntimeOps) then + FRuntimeOps.MarkExternalRoots; + + if Assigned(FArrayDelegate) and not FArrayDelegate.GCMarked then + FArrayDelegate.MarkReferences; + if Assigned(FRecordDelegate) and not FRecordDelegate.GCMarked then + FRecordDelegate.MarkReferences; + if FCallStack.IsEmpty then Exit; - HighWater := FCallStack.Peek^.BaseRegister + FCallStack.Peek^.Prototype.MaxRegisters; + HighWater := FCallStack.Peek^.BaseRegister + FCallStack.Peek^.Template.MaxRegisters; for I := 0 to HighWater - 1 do if SouffleIsReference(FRegisters[I]) and Assigned(FRegisters[I].AsReference) then FRegisters[I].AsReference.MarkReferences; @@ -802,10 +1648,24 @@ procedure TSouffleVM.MarkVMRoots; FOpenUpvalues.MarkReferences; end; +function TSouffleVM.DelegateGet(const AObject: TSouffleHeapObject; + const AKey: string; out AValue: TSouffleValue): Boolean; +var + Current: TSouffleHeapObject; +begin + Current := AObject.Delegate; + while Assigned(Current) do + begin + if (Current is TSouffleRecord) and + TSouffleRecord(Current).Get(AKey, AValue) then + Exit(True); + Current := Current.Delegate; + end; + Result := False; +end; + function TSouffleVM.MaterializeConstant( const AConstant: TSouffleBytecodeConstant): TSouffleValue; -var - Str: TSouffleString; begin case AConstant.Kind of bckNil: Result := SouffleNil; @@ -813,13 +1673,7 @@ function TSouffleVM.MaterializeConstant( bckFalse: Result := SouffleBoolean(False); bckInteger: Result := SouffleInteger(AConstant.IntValue); bckFloat: Result := SouffleFloat(AConstant.FloatValue); - bckString: - begin - Str := TSouffleString.Create(AConstant.StringValue); - if Assigned(FGC) then - FGC.AllocateObject(Str); - Result := SouffleReference(Str); - end; + bckString: Result := SouffleString(AConstant.StringValue); else Result := SouffleNil; end; diff --git a/souffle/Souffle.Value.pas b/souffle/Souffle.Value.pas index f112c5d8..0b5af531 100644 --- a/souffle/Souffle.Value.pas +++ b/souffle/Souffle.Value.pas @@ -7,45 +7,60 @@ interface uses Souffle.Heap; +const + SOUFFLE_INLINE_STRING_MAX = 23; + SOUFFLE_NIL_DEFAULT = 0; + SOUFFLE_NIL_MATCH_ANY = 255; + type TSouffleValueKind = ( svkNil, svkBoolean, svkInteger, svkFloat, + svkString, svkReference ); + TSouffleInlineString = string[SOUFFLE_INLINE_STRING_MAX]; + PSouffleValue = ^TSouffleValue; - TSouffleValue = record + TSouffleValue = packed record Kind: TSouffleValueKind; + Flags: Byte; case TSouffleValueKind of svkNil: (); svkBoolean: (AsBoolean: Boolean); svkInteger: (AsInteger: Int64); svkFloat: (AsFloat: Double); + svkString: (AsInlineString: TSouffleInlineString); svkReference: (AsReference: TSouffleHeapObject); end; TSouffleValueArray = array of TSouffleValue; PSouffleValueArray = ^TSouffleValueArray; -function SouffleNil: TSouffleValue; inline; -function SouffleBoolean(const AValue: Boolean): TSouffleValue; inline; -function SouffleInteger(const AValue: Int64): TSouffleValue; inline; -function SouffleFloat(const AValue: Double): TSouffleValue; inline; -function SouffleReference(const AObject: TSouffleHeapObject): TSouffleValue; inline; +function SouffleNil: TSouffleValue; +function SouffleNilWithFlags(const AFlags: Byte): TSouffleValue; +function SouffleBoolean(const AValue: Boolean): TSouffleValue; +function SouffleInteger(const AValue: Int64): TSouffleValue; +function SouffleFloat(const AValue: Double): TSouffleValue; +function SouffleString(const AValue: string): TSouffleValue; +function SouffleReference(const AObject: TSouffleHeapObject): TSouffleValue; function SouffleIsNil(const AValue: TSouffleValue): Boolean; inline; function SouffleIsBoolean(const AValue: TSouffleValue): Boolean; inline; function SouffleIsInteger(const AValue: TSouffleValue): Boolean; inline; function SouffleIsFloat(const AValue: TSouffleValue): Boolean; inline; +function SouffleIsInlineString(const AValue: TSouffleValue): Boolean; inline; function SouffleIsReference(const AValue: TSouffleValue): Boolean; inline; function SouffleIsNumeric(const AValue: TSouffleValue): Boolean; inline; +function SouffleIsStringValue(const AValue: TSouffleValue): Boolean; inline; -function SouffleIsTrue(const AValue: TSouffleValue): Boolean; inline; +function SouffleIsTrue(const AValue: TSouffleValue): Boolean; function SouffleAsNumber(const AValue: TSouffleValue): Double; inline; +function SouffleGetString(const AValue: TSouffleValue): string; function SouffleValuesEqual(const A, B: TSouffleValue): Boolean; function SouffleValueToString(const AValue: TSouffleValue): string; @@ -54,37 +69,72 @@ implementation uses Math, - SysUtils; + SysUtils, + + Souffle.GarbageCollector; { Value constructors } function SouffleNil: TSouffleValue; begin + FillChar(Result, SizeOf(Result), 0); Result.Kind := svkNil; - Result.AsInteger := 0; + Result.Flags := SOUFFLE_NIL_DEFAULT; +end; + +function SouffleNilWithFlags(const AFlags: Byte): TSouffleValue; +begin + FillChar(Result, SizeOf(Result), 0); + Result.Kind := svkNil; + Result.Flags := AFlags; end; function SouffleBoolean(const AValue: Boolean): TSouffleValue; begin + FillChar(Result, SizeOf(Result), 0); Result.Kind := svkBoolean; - Result.AsInteger := 0; Result.AsBoolean := AValue; end; function SouffleInteger(const AValue: Int64): TSouffleValue; begin + FillChar(Result, SizeOf(Result), 0); Result.Kind := svkInteger; Result.AsInteger := AValue; end; function SouffleFloat(const AValue: Double): TSouffleValue; begin + FillChar(Result, SizeOf(Result), 0); Result.Kind := svkFloat; Result.AsFloat := AValue; end; +function SouffleString(const AValue: string): TSouffleValue; +var + HeapStr: TSouffleHeapString; + GC: TSouffleGarbageCollector; +begin + FillChar(Result, SizeOf(Result), 0); + if Length(AValue) <= SOUFFLE_INLINE_STRING_MAX then + begin + Result.Kind := svkString; + Result.AsInlineString := AValue; + end + else + begin + HeapStr := TSouffleHeapString.Create(AValue); + GC := TSouffleGarbageCollector.Instance; + if Assigned(GC) then + GC.AllocateObject(HeapStr); + Result.Kind := svkReference; + Result.AsReference := HeapStr; + end; +end; + function SouffleReference(const AObject: TSouffleHeapObject): TSouffleValue; begin + FillChar(Result, SizeOf(Result), 0); Result.Kind := svkReference; Result.AsReference := AObject; end; @@ -111,6 +161,11 @@ function SouffleIsFloat(const AValue: TSouffleValue): Boolean; Result := AValue.Kind = svkFloat; end; +function SouffleIsInlineString(const AValue: TSouffleValue): Boolean; +begin + Result := AValue.Kind = svkString; +end; + function SouffleIsReference(const AValue: TSouffleValue): Boolean; begin Result := AValue.Kind = svkReference; @@ -121,6 +176,12 @@ function SouffleIsNumeric(const AValue: TSouffleValue): Boolean; Result := (AValue.Kind = svkInteger) or (AValue.Kind = svkFloat); end; +function SouffleIsStringValue(const AValue: TSouffleValue): Boolean; +begin + Result := (AValue.Kind = svkString) or + ((AValue.Kind = svkReference) and (AValue.AsReference is TSouffleHeapString)); +end; + { Truthiness -- universal semantics used by JUMP_IF_TRUE/JUMP_IF_FALSE } function SouffleIsTrue(const AValue: TSouffleValue): Boolean; @@ -134,6 +195,8 @@ function SouffleIsTrue(const AValue: TSouffleValue): Boolean; Result := AValue.AsInteger <> 0; svkFloat: Result := (AValue.AsFloat <> 0.0) and not IsNaN(AValue.AsFloat); + svkString: + Result := AValue.AsInlineString <> ''; svkReference: Result := Assigned(AValue.AsReference); else @@ -147,7 +210,7 @@ function SouffleAsNumber(const AValue: TSouffleValue): Double; begin case AValue.Kind of svkInteger: - Result := AValue.AsInteger * 1.0; + Result := AValue.AsInteger; svkFloat: Result := AValue.AsFloat; else @@ -155,22 +218,40 @@ function SouffleAsNumber(const AValue: TSouffleValue): Double; end; end; -{ Identity equality for Tier 1 operations } +{ String access -- handles both inline (svkString) and heap (TSouffleHeapString) } + +function SouffleGetString(const AValue: TSouffleValue): string; +begin + if AValue.Kind = svkString then + Result := AValue.AsInlineString + else if (AValue.Kind = svkReference) and (AValue.AsReference is TSouffleHeapString) then + Result := TSouffleHeapString(AValue.AsReference).Value + else + Result := ''; +end; + +{ Identity equality for core operations } function SouffleValuesEqual(const A, B: TSouffleValue): Boolean; begin if A.Kind <> B.Kind then + begin + if SouffleIsStringValue(A) and SouffleIsStringValue(B) then + Exit(SouffleGetString(A) = SouffleGetString(B)); Exit(False); + end; case A.Kind of svkNil: - Result := True; + Result := A.Flags = B.Flags; svkBoolean: Result := A.AsBoolean = B.AsBoolean; svkInteger: Result := A.AsInteger = B.AsInteger; svkFloat: Result := A.AsFloat = B.AsFloat; + svkString: + Result := A.AsInlineString = B.AsInlineString; svkReference: Result := A.AsReference = B.AsReference; else @@ -193,15 +274,26 @@ function SouffleValueToString(const AValue: TSouffleValue): string; svkInteger: Result := IntToStr(AValue.AsInteger); svkFloat: - Result := FloatToStr(AValue.AsFloat); - svkReference: - if Assigned(AValue.AsReference) then + if IsNaN(AValue.AsFloat) then + Result := 'NaN' + else if IsInfinite(AValue.AsFloat) then begin - if AValue.AsReference is TSouffleString then - Result := TSouffleString(AValue.AsReference).Value + if AValue.AsFloat > 0 then + Result := 'Infinity' else - Result := AValue.AsReference.DebugString; + Result := '-Infinity'; end + else if (Frac(AValue.AsFloat) = 0.0) and (Abs(AValue.AsFloat) < 1e20) then + Result := FloatToStrF(AValue.AsFloat, ffFixed, 20, 0) + else + Result := FloatToStr(AValue.AsFloat); + svkString: + Result := AValue.AsInlineString; + svkReference: + if AValue.AsReference is TSouffleHeapString then + Result := TSouffleHeapString(AValue.AsReference).Value + else if Assigned(AValue.AsReference) then + Result := AValue.AsReference.DebugString else Result := 'nil'; else diff --git a/tests/language/global-properties/global-this.js b/tests/built-ins/global-properties/global-this.js similarity index 100% rename from tests/language/global-properties/global-this.js rename to tests/built-ins/global-properties/global-this.js diff --git a/tests/language/global-properties/gocciascript.js b/tests/built-ins/global-properties/gocciascript.js similarity index 100% rename from tests/language/global-properties/gocciascript.js rename to tests/built-ins/global-properties/gocciascript.js diff --git a/tests/language/global-properties/infinity.js b/tests/built-ins/global-properties/infinity.js similarity index 100% rename from tests/language/global-properties/infinity.js rename to tests/built-ins/global-properties/infinity.js diff --git a/tests/language/global-properties/nan.js b/tests/built-ins/global-properties/nan.js similarity index 100% rename from tests/language/global-properties/nan.js rename to tests/built-ins/global-properties/nan.js diff --git a/tests/language/global-properties/undefined.js b/tests/built-ins/global-properties/undefined.js similarity index 100% rename from tests/language/global-properties/undefined.js rename to tests/built-ins/global-properties/undefined.js diff --git a/tests/language/expressions/arithmetic/decrement.js b/tests/language/expressions/arithmetic/decrement.js index 14854b3a..75f05767 100644 --- a/tests/language/expressions/arithmetic/decrement.js +++ b/tests/language/expressions/arithmetic/decrement.js @@ -14,3 +14,13 @@ test("post-decrement", () => { expect(a--).toBe(5); expect(a).toBe(4); }); + +test("decrement preserves fractional part", () => { + let x = 2.5; + x--; + expect(x).toBe(1.5); + + let y = 0.5; + --y; + expect(y).toBe(-0.5); +}); diff --git a/tests/language/expressions/arithmetic/increment.js b/tests/language/expressions/arithmetic/increment.js index 5f185ff2..417c5b3e 100644 --- a/tests/language/expressions/arithmetic/increment.js +++ b/tests/language/expressions/arithmetic/increment.js @@ -1,6 +1,6 @@ /*--- -description: Increment and decrement operators work correctly -features: [increment-operators, decrement-operators] +description: Increment operator works correctly +features: [increment-operators] ---*/ test("pre-increment", () => { @@ -16,3 +16,13 @@ test("post-increment", () => { expect(a++).toBe(5); expect(a).toBe(6); }); + +test("increment preserves fractional part", () => { + let x = 1.5; + x++; + expect(x).toBe(2.5); + + let y = -0.5; + ++y; + expect(y).toBe(0.5); +}); diff --git a/tests/language/expressions/bitwise/bitwise-rightshift.js b/tests/language/expressions/bitwise/bitwise-rightshift.js index d08329a3..247471be 100644 --- a/tests/language/expressions/bitwise/bitwise-rightshift.js +++ b/tests/language/expressions/bitwise/bitwise-rightshift.js @@ -7,3 +7,20 @@ test("bitwise right shift operator", () => { expect(5 >> 3).toBe(0); expect(5 >> 2).toBe(1); }); + +test("arithmetic right shift preserves sign bit", () => { + expect(-8 >> 1).toBe(-4); + expect(-1 >> 1).toBe(-1); + expect(-16 >> 2).toBe(-4); + expect(-100 >> 3).toBe(-13); +}); + +test("right shift with zero shift amount", () => { + expect(-8 >> 0).toBe(-8); + expect(8 >> 0).toBe(8); +}); + +test("right shift uses only low 5 bits of shift amount", () => { + expect(-8 >> 32).toBe(-8); + expect(-8 >> 33).toBe(-4); +}); diff --git a/tests/language/expressions/destructuring/reassignment.js b/tests/language/expressions/destructuring/reassignment.js index a8078665..234d13e0 100644 --- a/tests/language/expressions/destructuring/reassignment.js +++ b/tests/language/expressions/destructuring/reassignment.js @@ -35,3 +35,25 @@ test("reassignment in object destructuring", () => { expect(secondAge).toBe(25); expect(secondCity).toBe("Los Angeles"); }); + +test("const object destructuring throws on reassignment", () => { + const { x, y } = { x: 1, y: 2 }; + expect(x).toBe(1); + expect(y).toBe(2); + expect(() => { x = 10; }).toThrow(TypeError); + expect(() => { y = 20; }).toThrow(TypeError); +}); + +test("const array destructuring throws on reassignment", () => { + const [a, b] = [3, 4]; + expect(a).toBe(3); + expect(b).toBe(4); + expect(() => { a = 30; }).toThrow(TypeError); + expect(() => { b = 40; }).toThrow(TypeError); +}); + +test("const nested destructuring throws on reassignment", () => { + const { nested: { deep } } = { nested: { deep: 42 } }; + expect(deep).toBe(42); + expect(() => { deep = 0; }).toThrow(TypeError); +}); diff --git a/tests/language/for-of/for-of-break.js b/tests/language/for-of/for-of-break.js index 5bb44c8b..6e142299 100644 --- a/tests/language/for-of/for-of-break.js +++ b/tests/language/for-of/for-of-break.js @@ -27,4 +27,56 @@ describe("for...of break", () => { } expect(result).toEqual(["a1", "b1"]); }); + + test("break inside try-finally executes finally block", () => { + const log = []; + for (const x of [1, 2, 3]) { + try { + if (x === 2) { + break; + } + log.push("body:" + x); + } finally { + log.push("finally:" + x); + } + } + expect(log).toEqual(["body:1", "finally:1", "finally:2"]); + }); + + test("break inside nested try-finally executes all finally blocks", () => { + const log = []; + for (const x of [1, 2]) { + try { + try { + if (x === 1) { + break; + } + } finally { + log.push("inner:" + x); + } + } finally { + log.push("outer:" + x); + } + } + expect(log).toEqual(["inner:1", "outer:1"]); + }); + + test("break inside try-finally inside outer try-finally", () => { + const log = []; + try { + for (const x of [1, 2, 3]) { + try { + if (x === 2) { + break; + } + log.push("body:" + x); + } finally { + log.push("loop-finally:" + x); + } + } + } finally { + log.push("outer-finally"); + } + expect(log).toEqual(["body:1", "loop-finally:1", "loop-finally:2", "outer-finally"]); + }); }); diff --git a/units/Goccia.Benchmark.Reporter.pas b/units/Goccia.Benchmark.Reporter.pas index 34d17e9a..f5051ae6 100644 --- a/units/Goccia.Benchmark.Reporter.pas +++ b/units/Goccia.Benchmark.Reporter.pas @@ -24,6 +24,7 @@ TBenchmarkFileResult = record FileName: string; LexTimeNanoseconds: Int64; ParseTimeNanoseconds: Int64; + CompileTimeNanoseconds: Int64; ExecuteTimeNanoseconds: Int64; Entries: array of TBenchmarkEntry; TotalBenchmarks: Integer; @@ -169,9 +170,14 @@ procedure TBenchmarkReporter.RenderConsole; if FFileCount > 1 then FOutput.Add('Running benchmark: ' + FFiles[F].FileName); - FOutput.Add(SysUtils.Format(' Lex: %s | Parse: %s | Execute: %s | Total: %s', - [FormatDuration(FFiles[F].LexTimeNanoseconds), FormatDuration(FFiles[F].ParseTimeNanoseconds), FormatDuration(FFiles[F].ExecuteTimeNanoseconds), - FormatDuration(FFiles[F].LexTimeNanoseconds + FFiles[F].ParseTimeNanoseconds + FFiles[F].ExecuteTimeNanoseconds)])); + if FFiles[F].CompileTimeNanoseconds > 0 then + FOutput.Add(SysUtils.Format(' Compile: %s | Execute: %s | Total: %s', + [FormatDuration(FFiles[F].CompileTimeNanoseconds), FormatDuration(FFiles[F].ExecuteTimeNanoseconds), + FormatDuration(FFiles[F].CompileTimeNanoseconds + FFiles[F].ExecuteTimeNanoseconds)])) + else + FOutput.Add(SysUtils.Format(' Lex: %s | Parse: %s | Execute: %s | Total: %s', + [FormatDuration(FFiles[F].LexTimeNanoseconds), FormatDuration(FFiles[F].ParseTimeNanoseconds), FormatDuration(FFiles[F].ExecuteTimeNanoseconds), + FormatDuration(FFiles[F].LexTimeNanoseconds + FFiles[F].ParseTimeNanoseconds + FFiles[F].ExecuteTimeNanoseconds)])); FOutput.Add(''); CurrentSuite := ''; @@ -314,8 +320,13 @@ procedure TBenchmarkReporter.RenderJSON; begin FOutput.Add(' {'); FOutput.Add(SysUtils.Format(' "file": "%s",', [EscapeJSON(FFiles[F].FileName)])); - FOutput.Add(SysUtils.Format(' "lexTimeNanoseconds": %d,', [FFiles[F].LexTimeNanoseconds])); - FOutput.Add(SysUtils.Format(' "parseTimeNanoseconds": %d,', [FFiles[F].ParseTimeNanoseconds])); + if FFiles[F].CompileTimeNanoseconds > 0 then + FOutput.Add(SysUtils.Format(' "compileTimeNanoseconds": %d,', [FFiles[F].CompileTimeNanoseconds])) + else + begin + FOutput.Add(SysUtils.Format(' "lexTimeNanoseconds": %d,', [FFiles[F].LexTimeNanoseconds])); + FOutput.Add(SysUtils.Format(' "parseTimeNanoseconds": %d,', [FFiles[F].ParseTimeNanoseconds])); + end; FOutput.Add(SysUtils.Format(' "executeTimeNanoseconds": %d,', [FFiles[F].ExecuteTimeNanoseconds])); FOutput.Add(' "benchmarks": ['); diff --git a/units/Goccia.Builtins.Benchmark.pas b/units/Goccia.Builtins.Benchmark.pas index 3de7f142..e9270d8d 100644 --- a/units/Goccia.Builtins.Benchmark.pas +++ b/units/Goccia.Builtins.Benchmark.pas @@ -14,7 +14,7 @@ interface Goccia.Builtins.Base, Goccia.Error.ThrowErrorCallback, Goccia.Scope, - Goccia.Values.FunctionValue, + Goccia.Values.FunctionBase, Goccia.Values.Primitives; type @@ -26,12 +26,12 @@ TBenchmarkCase = class public Name: string; SuiteName: string; - SetupFunction: TGocciaFunctionValue; - RunFunction: TGocciaFunctionValue; - TeardownFunction: TGocciaFunctionValue; - constructor Create(const AName: string; const ARunFunction: TGocciaFunctionValue; - const ASuiteName: string; const ASetupFunction: TGocciaFunctionValue = nil; - const ATeardownFunction: TGocciaFunctionValue = nil); + SetupFunction: TGocciaFunctionBase; + RunFunction: TGocciaFunctionBase; + TeardownFunction: TGocciaFunctionBase; + constructor Create(const AName: string; const ARunFunction: TGocciaFunctionBase; + const ASuiteName: string; const ASetupFunction: TGocciaFunctionBase = nil; + const ATeardownFunction: TGocciaFunctionBase = nil); end; TBenchmarkResult = record @@ -117,9 +117,9 @@ procedure InitBenchmarkConfig; { TBenchmarkCase } -constructor TBenchmarkCase.Create(const AName: string; const ARunFunction: TGocciaFunctionValue; - const ASuiteName: string; const ASetupFunction: TGocciaFunctionValue; - const ATeardownFunction: TGocciaFunctionValue); +constructor TBenchmarkCase.Create(const AName: string; const ARunFunction: TGocciaFunctionBase; + const ASuiteName: string; const ASetupFunction: TGocciaFunctionBase; + const ATeardownFunction: TGocciaFunctionBase); begin inherited Create; Name := AName; @@ -154,7 +154,7 @@ destructor TGocciaBenchmark.Destroy; function TGocciaBenchmark.Suite(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue; var SuiteName: string; - SuiteFunction: TGocciaFunctionValue; + SuiteFunction: TGocciaFunctionBase; EmptyArgs: TGocciaArgumentsCollection; PreviousSuiteName: string; begin @@ -162,10 +162,10 @@ function TGocciaBenchmark.Suite(const AArgs: TGocciaArgumentsCollection; const A if AArgs.Length < 2 then Exit; if not (AArgs.GetElement(0) is TGocciaStringLiteralValue) then Exit; - if not (AArgs.GetElement(1) is TGocciaFunctionValue) then Exit; + if not (AArgs.GetElement(1) is TGocciaFunctionBase) then Exit; SuiteName := TGocciaStringLiteralValue(AArgs.GetElement(0)).Value; - SuiteFunction := TGocciaFunctionValue(AArgs.GetElement(1)); + SuiteFunction := TGocciaFunctionBase(AArgs.GetElement(1)); FRegisteredSuites.Add(SuiteName); @@ -188,7 +188,7 @@ function TGocciaBenchmark.Bench(const AArgs: TGocciaArgumentsCollection; const A BenchName: string; OptionsObj: TGocciaObjectValue; RunProp, SetupProp, TeardownProp: TGocciaValue; - RunFn, SetupFn, TeardownFn: TGocciaFunctionValue; + RunFn, SetupFn, TeardownFn: TGocciaFunctionBase; begin Result := TGocciaUndefinedLiteralValue.UndefinedValue; @@ -200,18 +200,18 @@ function TGocciaBenchmark.Bench(const AArgs: TGocciaArgumentsCollection; const A OptionsObj := TGocciaObjectValue(AArgs.GetElement(1)); RunProp := OptionsObj.GetProperty('run'); - if not Assigned(RunProp) or not (RunProp is TGocciaFunctionValue) then Exit; - RunFn := TGocciaFunctionValue(RunProp); + if not Assigned(RunProp) or not (RunProp is TGocciaFunctionBase) then Exit; + RunFn := TGocciaFunctionBase(RunProp); SetupFn := nil; SetupProp := OptionsObj.GetProperty('setup'); - if Assigned(SetupProp) and (SetupProp is TGocciaFunctionValue) then - SetupFn := TGocciaFunctionValue(SetupProp); + if Assigned(SetupProp) and (SetupProp is TGocciaFunctionBase) then + SetupFn := TGocciaFunctionBase(SetupProp); TeardownFn := nil; TeardownProp := OptionsObj.GetProperty('teardown'); - if Assigned(TeardownProp) and (TeardownProp is TGocciaFunctionValue) then - TeardownFn := TGocciaFunctionValue(TeardownProp); + if Assigned(TeardownProp) and (TeardownProp is TGocciaFunctionBase) then + TeardownFn := TGocciaFunctionBase(TeardownProp); FRegisteredBenchmarks.Add(TBenchmarkCase.Create(BenchName, RunFn, FCurrentSuiteName, SetupFn, TeardownFn)); end; diff --git a/units/Goccia.Builtins.GlobalNumber.pas b/units/Goccia.Builtins.GlobalNumber.pas index 648ba51f..efd9c123 100644 --- a/units/Goccia.Builtins.GlobalNumber.pas +++ b/units/Goccia.Builtins.GlobalNumber.pas @@ -43,8 +43,8 @@ constructor TGocciaGlobalNumber.Create(const AName: string; const AScope: TGocci FBuiltinObject.DefineProperty('NaN', TGocciaPropertyDescriptorData.Create(TGocciaNumberLiteralValue.NaNValue, [])); FBuiltinObject.DefineProperty('POSITIVE_INFINITY', TGocciaPropertyDescriptorData.Create(TGocciaNumberLiteralValue.InfinityValue, [])); FBuiltinObject.DefineProperty('NEGATIVE_INFINITY', TGocciaPropertyDescriptorData.Create(TGocciaNumberLiteralValue.NegativeInfinityValue, [])); - FBuiltinObject.DefineProperty('MAX_SAFE_INTEGER', TGocciaPropertyDescriptorData.Create(TGocciaNumberLiteralValue.Create(9007199254740991), [])); - FBuiltinObject.DefineProperty('MIN_SAFE_INTEGER', TGocciaPropertyDescriptorData.Create(TGocciaNumberLiteralValue.Create(-9007199254740991), [])); + FBuiltinObject.DefineProperty('MAX_SAFE_INTEGER', TGocciaPropertyDescriptorData.Create(TGocciaNumberLiteralValue.Create(9007199254740991.0), [])); + FBuiltinObject.DefineProperty('MIN_SAFE_INTEGER', TGocciaPropertyDescriptorData.Create(TGocciaNumberLiteralValue.Create(-9007199254740991.0), [])); FBuiltinObject.DefineProperty('MAX_VALUE', TGocciaPropertyDescriptorData.Create(TGocciaNumberLiteralValue.Create(1.7976931348623157e+308), [])); FBuiltinObject.DefineProperty('MIN_VALUE', TGocciaPropertyDescriptorData.Create(TGocciaNumberLiteralValue.Create(5e-324), [])); FBuiltinObject.DefineProperty('EPSILON', TGocciaPropertyDescriptorData.Create(TGocciaNumberLiteralValue.Create(2.2204460492503131e-16), [])); diff --git a/units/Goccia.Builtins.GlobalObject.pas b/units/Goccia.Builtins.GlobalObject.pas index 8776fdaf..ddb703ec 100644 --- a/units/Goccia.Builtins.GlobalObject.pas +++ b/units/Goccia.Builtins.GlobalObject.pas @@ -461,7 +461,7 @@ function TGocciaGlobalObject.ObjectDefineProperty(const AArgs: TGocciaArgumentsC if HasGet then begin Getter := DescriptorObject.GetProperty(PROP_GET); - if not (Getter is TGocciaUndefinedLiteralValue) and not (Getter is TGocciaFunctionValue) and not (Getter is TGocciaNativeFunctionValue) then + if not (Getter is TGocciaUndefinedLiteralValue) and not Getter.IsCallable then ThrowError('TypeError: Object.defineProperty: getter must be a function or undefined', 0, 0); if Getter is TGocciaUndefinedLiteralValue then Getter := nil; @@ -469,7 +469,7 @@ function TGocciaGlobalObject.ObjectDefineProperty(const AArgs: TGocciaArgumentsC if HasSet then begin Setter := DescriptorObject.GetProperty(PROP_SET); - if not (Setter is TGocciaUndefinedLiteralValue) and not (Setter is TGocciaFunctionValue) and not (Setter is TGocciaNativeFunctionValue) then + if not (Setter is TGocciaUndefinedLiteralValue) and not Setter.IsCallable then ThrowError('TypeError: Object.defineProperty: setter must be a function or undefined', 0, 0); if Setter is TGocciaUndefinedLiteralValue then Setter := nil; diff --git a/units/Goccia.Builtins.TestAssertions.Test.pas b/units/Goccia.Builtins.TestAssertions.Test.pas index 05d0d7e1..c70f0669 100644 --- a/units/Goccia.Builtins.TestAssertions.Test.pas +++ b/units/Goccia.Builtins.TestAssertions.Test.pas @@ -15,6 +15,7 @@ Goccia.Builtins.TestAssertions, Goccia.Error.ThrowErrorCallback, Goccia.Scope, + Goccia.TestSetup, Goccia.Values.ArrayValue, Goccia.Values.FunctionValue, Goccia.Values.NativeFunction, diff --git a/units/Goccia.Builtins.TestAssertions.pas b/units/Goccia.Builtins.TestAssertions.pas index 195f3d35..08ed28bd 100644 --- a/units/Goccia.Builtins.TestAssertions.pas +++ b/units/Goccia.Builtins.TestAssertions.pas @@ -14,7 +14,7 @@ interface Goccia.Error.ThrowErrorCallback, Goccia.Scope, Goccia.Values.ArrayValue, - Goccia.Values.FunctionValue, + Goccia.Values.FunctionBase, Goccia.Values.NativeFunction, Goccia.Values.ObjectValue, Goccia.Values.Primitives; @@ -26,19 +26,18 @@ TGocciaTestAssertions = class; TGocciaTestSuite = class public Name: string; - SuiteFunction: TGocciaFunctionValue; + SuiteFunction: TGocciaFunctionBase; IsSkipped: Boolean; - constructor Create(const AName: string; const ASuiteFunction: TGocciaFunctionValue; const AIsSkipped: Boolean = False); + constructor Create(const AName: string; const ASuiteFunction: TGocciaFunctionBase; const AIsSkipped: Boolean = False); end; - // Class to hold registered tests (instead of record) TGocciaTestCase = class public Name: string; - TestFunction: TGocciaFunctionValue; + TestFunction: TGocciaFunctionBase; SuiteName: string; IsSkipped: Boolean; - constructor Create(const AName: string; const ATestFunction: TGocciaFunctionValue; const ASuiteName: string; const AIsSkipped: Boolean = False); + constructor Create(const AName: string; const ATestFunction: TGocciaFunctionBase; const ASuiteName: string; const AIsSkipped: Boolean = False); end; // Expectation object that provides matchers @@ -159,14 +158,13 @@ implementation Goccia.Values.ClassHelper, Goccia.Values.ClassValue, Goccia.Values.Error, - Goccia.Values.FunctionBase, Goccia.Values.ObjectPropertyDescriptor, Goccia.Values.PromiseValue, Goccia.Values.SetValue; { TGocciaTestSuite } -constructor TGocciaTestSuite.Create(const AName: string; const ASuiteFunction: TGocciaFunctionValue; const AIsSkipped: Boolean = False); +constructor TGocciaTestSuite.Create(const AName: string; const ASuiteFunction: TGocciaFunctionBase; const AIsSkipped: Boolean = False); begin inherited Create; Name := AName; @@ -176,7 +174,7 @@ constructor TGocciaTestSuite.Create(const AName: string; const ASuiteFunction: T { TGocciaTestCase } -constructor TGocciaTestCase.Create(const AName: string; const ATestFunction: TGocciaFunctionValue; const ASuiteName: string; const AIsSkipped: Boolean = False); +constructor TGocciaTestCase.Create(const AName: string; const ATestFunction: TGocciaFunctionBase; const ASuiteName: string; const AIsSkipped: Boolean = False); begin inherited Create; Name := AName; @@ -649,7 +647,7 @@ function TGocciaExpectationValue.ToBeInstanceOf(const AArgs: TGocciaArgumentsCol if ConstructorName = 'Function' then begin // Check if actual value is any kind of function - IsInstance := (FActualValue is TGocciaFunctionValue) or + IsInstance := (FActualValue is TGocciaFunctionBase) or (FActualValue is TGocciaNativeFunctionValue) or (FActualValue.ClassName = 'TGocciaFunctionPrototypeMethod') or (FActualValue.ClassName = 'TGocciaBoundFunctionValue'); @@ -681,7 +679,7 @@ function TGocciaExpectationValue.ToBeInstanceOf(const AArgs: TGocciaArgumentsCol if ConstructorName = 'Function' then begin // Check if actual value is any kind of function - IsInstance := (FActualValue is TGocciaFunctionValue) or + IsInstance := (FActualValue is TGocciaFunctionBase) or (FActualValue is TGocciaNativeFunctionValue) or (FActualValue.ClassName = 'TGocciaFunctionPrototypeMethod') or (FActualValue.ClassName = 'TGocciaBoundFunctionValue'); @@ -805,10 +803,7 @@ function TGocciaExpectationValue.ToThrow(const AArgs: TGocciaArgumentsCollection var ExpectedErrorType: string; EmptyArgs: TGocciaArgumentsCollection; - TestContext: TGocciaEvaluationContext; - TestFunc: TGocciaFunctionValue; - TestScope: TGocciaScope; - I: Integer; + TestFunc: TGocciaFunctionBase; ThrownObj: TGocciaObjectValue; ErrorConstructor: TGocciaValue; ConstructorName: string; @@ -831,7 +826,7 @@ function TGocciaExpectationValue.ToThrow(const AArgs: TGocciaArgumentsCollection ExpectedErrorType := ErrorClassArg.ToStringLiteral.Value; end; - if not (FActualValue is TGocciaFunctionValue) then + if not (FActualValue is TGocciaFunctionBase) then begin // Support .rejects.toThrow(TypeError) — actual value is the rejection reason if (FActualValue is TGocciaObjectValue) and TGocciaObjectValue(FActualValue).HasErrorData then @@ -864,27 +859,12 @@ function TGocciaExpectationValue.ToThrow(const AArgs: TGocciaArgumentsCollection Exit; end; - TestFunc := TGocciaFunctionValue(FActualValue); + TestFunc := TGocciaFunctionBase(FActualValue); EmptyArgs := TGocciaArgumentsCollection.Create; - // Create a strict test scope that throws for undefined variables - TestScope := TestFunc.Closure.CreateChild(skFunction, 'ToThrow Test'); try - // Set up evaluation context with an OnError handler that throws exceptions - TestContext.Scope := TestScope; - TestContext.OnError := FTestAssertions.ThrowError; - TestContext.LoadModule := nil; - try - // Execute all function body statements with proper context - for I := 0 to TestFunc.BodyStatements.Count - 1 do - begin - // Guard against invalid AST nodes from syntax errors - if TestFunc.BodyStatements[I] <> nil then - Evaluate(TestFunc.BodyStatements[I], TestContext) - else - raise TGocciaSyntaxError.Create('Invalid syntax in function body', 0, 0, '', nil); - end; + TestFunc.Call(EmptyArgs, TGocciaUndefinedLiteralValue.UndefinedValue); except on E: TGocciaThrowValue do begin @@ -998,7 +978,6 @@ function TGocciaExpectationValue.ToThrow(const AArgs: TGocciaArgumentsCollection end; finally EmptyArgs.Free; - TestScope.Free; // Properly free the test scope to prevent access violations end; TGocciaTestAssertions(FTestAssertions).AssertionFailed('toThrow', @@ -1340,7 +1319,7 @@ function TGocciaTestAssertions.Expect(const AArgs: TGocciaArgumentsCollection; c function TGocciaTestAssertions.Describe(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue; var SuiteName: string; - SuiteFunction: TGocciaFunctionValue; + SuiteFunction: TGocciaFunctionBase; Suite: TGocciaTestSuite; begin TGocciaArgumentValidator.RequireExactly(AArgs, 2, 'describe', ThrowError); @@ -1348,14 +1327,14 @@ function TGocciaTestAssertions.Describe(const AArgs: TGocciaArgumentsCollection; if not (AArgs.GetElement(0) is TGocciaStringLiteralValue) then ThrowError('describe expects first argument to be a string', 0, 0); - if not (AArgs.GetElement(1) is TGocciaFunctionValue) then + if not (AArgs.GetElement(1) is TGocciaFunctionBase) then ThrowError('describe expects second argument to be a function', 0, 0); if FTestStats.CurrentSuiteName <> '' then SuiteName := FTestStats.CurrentSuiteName + ' > ' + AArgs.GetElement(0).ToStringLiteral.Value else SuiteName := AArgs.GetElement(0).ToStringLiteral.Value; - SuiteFunction := AArgs.GetElement(1) as TGocciaFunctionValue; + SuiteFunction := AArgs.GetElement(1) as TGocciaFunctionBase; Suite := TGocciaTestSuite.Create(SuiteName, SuiteFunction, FCurrentSuiteIsSkipped); FRegisteredSuites.Add(Suite); @@ -1366,7 +1345,7 @@ function TGocciaTestAssertions.Describe(const AArgs: TGocciaArgumentsCollection; function TGocciaTestAssertions.DescribeSkip(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue; var SuiteName: string; - SuiteFunction: TGocciaFunctionValue; + SuiteFunction: TGocciaFunctionBase; Suite: TGocciaTestSuite; begin TGocciaArgumentValidator.RequireExactly(AArgs, 2, 'describe.skip', ThrowError); @@ -1374,14 +1353,14 @@ function TGocciaTestAssertions.DescribeSkip(const AArgs: TGocciaArgumentsCollect if not (AArgs.GetElement(0) is TGocciaStringLiteralValue) then ThrowError('describe.skip expects first argument to be a string', 0, 0); - if not (AArgs.GetElement(1) is TGocciaFunctionValue) then + if not (AArgs.GetElement(1) is TGocciaFunctionBase) then ThrowError('describe.skip expects second argument to be a function', 0, 0); if FTestStats.CurrentSuiteName <> '' then SuiteName := FTestStats.CurrentSuiteName + ' > ' + AArgs.GetElement(0).ToStringLiteral.Value else SuiteName := AArgs.GetElement(0).ToStringLiteral.Value; - SuiteFunction := AArgs.GetElement(1) as TGocciaFunctionValue; + SuiteFunction := AArgs.GetElement(1) as TGocciaFunctionBase; Suite := TGocciaTestSuite.Create(SuiteName, SuiteFunction, True); FRegisteredSuites.Add(Suite); @@ -1408,7 +1387,7 @@ function TGocciaTestAssertions.DescribeRunIf(const AArgs: TGocciaArgumentsCollec function TGocciaTestAssertions.DescribeConditional(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue; var SuiteName: string; - SuiteFunction: TGocciaFunctionValue; + SuiteFunction: TGocciaFunctionBase; Suite: TGocciaTestSuite; begin TGocciaArgumentValidator.RequireExactly(AArgs, 2, 'describe', ThrowError); @@ -1416,14 +1395,14 @@ function TGocciaTestAssertions.DescribeConditional(const AArgs: TGocciaArguments if not (AArgs.GetElement(0) is TGocciaStringLiteralValue) then ThrowError('describe expects first argument to be a string', 0, 0); - if not (AArgs.GetElement(1) is TGocciaFunctionValue) then + if not (AArgs.GetElement(1) is TGocciaFunctionBase) then ThrowError('describe expects second argument to be a function', 0, 0); if FTestStats.CurrentSuiteName <> '' then SuiteName := FTestStats.CurrentSuiteName + ' > ' + AArgs.GetElement(0).ToStringLiteral.Value else SuiteName := AArgs.GetElement(0).ToStringLiteral.Value; - SuiteFunction := AArgs.GetElement(1) as TGocciaFunctionValue; + SuiteFunction := AArgs.GetElement(1) as TGocciaFunctionBase; Suite := TGocciaTestSuite.Create(SuiteName, SuiteFunction, FSkipNextDescribe or FCurrentSuiteIsSkipped); FRegisteredSuites.Add(Suite); @@ -1434,7 +1413,7 @@ function TGocciaTestAssertions.DescribeConditional(const AArgs: TGocciaArguments function TGocciaTestAssertions.Test(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue; var TestName: string; - TestFunction: TGocciaFunctionValue; + TestFunction: TGocciaFunctionBase; TestCase: TGocciaTestCase; begin TGocciaArgumentValidator.RequireExactly(AArgs, 2, 'test', ThrowError); @@ -1442,11 +1421,11 @@ function TGocciaTestAssertions.Test(const AArgs: TGocciaArgumentsCollection; con if not (AArgs.GetElement(0) is TGocciaStringLiteralValue) then ThrowError('test expects first argument to be a string', 0, 0); - if not (AArgs.GetElement(1) is TGocciaFunctionValue) then + if not (AArgs.GetElement(1) is TGocciaFunctionBase) then ThrowError('test expects second argument to be a function', 0, 0); TestName := AArgs.GetElement(0).ToStringLiteral.Value; - TestFunction := AArgs.GetElement(1) as TGocciaFunctionValue; + TestFunction := AArgs.GetElement(1) as TGocciaFunctionBase; TestCase := TGocciaTestCase.Create(TestName, TestFunction, FTestStats.CurrentSuiteName, FCurrentSuiteIsSkipped); FRegisteredTests.Add(TestCase); @@ -1463,7 +1442,7 @@ function TGocciaTestAssertions.It(const AArgs: TGocciaArgumentsCollection; const function TGocciaTestAssertions.Skip(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue; var TestName: string; - TestFunction: TGocciaFunctionValue; + TestFunction: TGocciaFunctionBase; TestCase: TGocciaTestCase; begin TGocciaArgumentValidator.RequireExactly(AArgs, 2, 'test.skip', ThrowError); @@ -1471,11 +1450,11 @@ function TGocciaTestAssertions.Skip(const AArgs: TGocciaArgumentsCollection; con if not (AArgs.GetElement(0) is TGocciaStringLiteralValue) then ThrowError('test.skip expects first argument to be a string', 0, 0); - if not (AArgs.GetElement(1) is TGocciaFunctionValue) then + if not (AArgs.GetElement(1) is TGocciaFunctionBase) then ThrowError('test.skip expects second argument to be a function', 0, 0); TestName := AArgs.GetElement(0).ToStringLiteral.Value; - TestFunction := AArgs.GetElement(1) as TGocciaFunctionValue; + TestFunction := AArgs.GetElement(1) as TGocciaFunctionBase; // Register the test as skipped with the current suite name TestCase := TGocciaTestCase.Create(TestName, TestFunction, FTestStats.CurrentSuiteName, True); @@ -1503,7 +1482,7 @@ function TGocciaTestAssertions.TestRunIf(const AArgs: TGocciaArgumentsCollection function TGocciaTestAssertions.TestConditional(const AArgs: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue; var TestName: string; - TestFunction: TGocciaFunctionValue; + TestFunction: TGocciaFunctionBase; TestCase: TGocciaTestCase; begin TGocciaArgumentValidator.RequireExactly(AArgs, 2, 'test', ThrowError); @@ -1511,11 +1490,11 @@ function TGocciaTestAssertions.TestConditional(const AArgs: TGocciaArgumentsColl if not (AArgs.GetElement(0) is TGocciaStringLiteralValue) then ThrowError('test expects first argument to be a string', 0, 0); - if not (AArgs.GetElement(1) is TGocciaFunctionValue) then + if not (AArgs.GetElement(1) is TGocciaFunctionBase) then ThrowError('test expects second argument to be a function', 0, 0); TestName := AArgs.GetElement(0).ToStringLiteral.Value; - TestFunction := AArgs.GetElement(1) as TGocciaFunctionValue; + TestFunction := AArgs.GetElement(1) as TGocciaFunctionBase; TestCase := TGocciaTestCase.Create(TestName, TestFunction, FTestStats.CurrentSuiteName, FSkipNextTest or FCurrentSuiteIsSkipped); FRegisteredTests.Add(TestCase); @@ -1527,7 +1506,7 @@ function TGocciaTestAssertions.BeforeEach(const AArgs: TGocciaArgumentsCollectio begin TGocciaArgumentValidator.RequireExactly(AArgs, 1, 'beforeEach', ThrowError); - if not (AArgs.GetElement(0) is TGocciaFunctionValue) then + if not (AArgs.GetElement(0) is TGocciaFunctionBase) then ThrowError('beforeEach expects a function argument', 0, 0); FBeforeEachCallbacks.Add(AArgs.GetElement(0)); @@ -1539,7 +1518,7 @@ function TGocciaTestAssertions.AfterEach(const AArgs: TGocciaArgumentsCollection begin TGocciaArgumentValidator.RequireExactly(AArgs, 1, 'afterEach', ThrowError); - if not (AArgs.GetElement(0) is TGocciaFunctionValue) then + if not (AArgs.GetElement(0) is TGocciaFunctionBase) then ThrowError('afterEach expects a function argument', 0, 0); FAfterEachCallbacks.Add(AArgs.GetElement(0)); @@ -1562,8 +1541,8 @@ function TGocciaTestAssertions.RunTests(const AArgs: TGocciaArgumentsCollection; PreviousSuiteIsSkipped: Boolean; FailedTestDetails: TStringList; FailedTestDetailsArray: TGocciaArrayValue; - ClonedFunction: TGocciaFunctionValue; - TestParams: TGocciaObjectValue; + Param: TGocciaValue; + Val: TGocciaValue; TestResult: TGocciaValue; RejectionReason: string; begin @@ -1582,15 +1561,17 @@ function TGocciaTestAssertions.RunTests(const AArgs: TGocciaArgumentsCollection; if AArgs.Length > 0 then begin - if AArgs.GetElement(0) is TGocciaObjectValue then + Param := AArgs.GetElement(0); + if not Param.IsPrimitive then begin - TestParams := AArgs.GetElement(0) as TGocciaObjectValue; - if TestParams.HasProperty('exitOnFirstFailure') then - ExitOnFirstFailure := TestParams.GetProperty('exitOnFirstFailure').ToBooleanLiteral.Value + Val := Param.GetProperty('exitOnFirstFailure'); + if Assigned(Val) and not (Val is TGocciaUndefinedLiteralValue) then + ExitOnFirstFailure := Val.ToBooleanLiteral.Value else ExitOnFirstFailure := False; - if TestParams.HasProperty('showTestResults') then - ShowTestResults := TestParams.GetProperty('showTestResults').ToBooleanLiteral.Value + Val := Param.GetProperty('showTestResults'); + if Assigned(Val) and not (Val is TGocciaUndefinedLiteralValue) then + ShowTestResults := Val.ToBooleanLiteral.Value else ShowTestResults := True; end @@ -1659,12 +1640,7 @@ function TGocciaTestAssertions.RunTests(const AArgs: TGocciaArgumentsCollection; RunCallbacks(FBeforeEachCallbacks); try - // Execute the test function — use the test's original closure as parent so - // lexical captures (e.g. from forEach) remain visible; create a child scope - // for test-local bindings. - ClonedFunction := TestCase.TestFunction.CloneWithNewScope( - TestCase.TestFunction.Closure.CreateChild(skFunction, 'TestFunction')); - TestResult := ClonedFunction.Call(EmptyArgs, TGocciaUndefinedLiteralValue.UndefinedValue); + TestResult := TestCase.TestFunction.Call(EmptyArgs, TGocciaUndefinedLiteralValue.UndefinedValue); // Drain microtask queue after each test to process Promise reactions if Assigned(TGocciaMicrotaskQueue.Instance) then diff --git a/units/Goccia.CallStack.pas b/units/Goccia.CallStack.pas index f78727ec..4dccc237 100644 --- a/units/Goccia.CallStack.pas +++ b/units/Goccia.CallStack.pas @@ -12,6 +12,8 @@ TGocciaCallFrame = record Column: Integer; end; + TGocciaCallFrameArray = array of TGocciaCallFrame; + TGocciaCallStack = class private class var FInstance: TGocciaCallStack; diff --git a/units/Goccia.Compiler.ConstantFolding.pas b/units/Goccia.Compiler.ConstantFolding.pas new file mode 100644 index 00000000..acdabb87 --- /dev/null +++ b/units/Goccia.Compiler.ConstantFolding.pas @@ -0,0 +1,267 @@ +unit Goccia.Compiler.ConstantFolding; + +{$I Goccia.inc} + +interface + +uses + Goccia.AST.Expressions, + Goccia.Compiler.Context; + +function TryFoldBinary(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaBinaryExpression; const ADest: UInt8): Boolean; +function TryFoldUnary(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaUnaryExpression; const ADest: UInt8): Boolean; +function IsNegativeZeroFloat(const AValue: Double): Boolean; inline; + +implementation + +uses + Math, + + Souffle.Bytecode, + + Goccia.Token, + Goccia.Values.Primitives; + +function IsNegativeZeroFloat(const AValue: Double): Boolean; inline; +var + V: Double; + Bits: Int64 absolute V; +begin + V := AValue; + Result := (V = 0.0) and (Bits < 0); +end; + +procedure EmitFoldedNumber(const ACtx: TGocciaCompilationContext; + const AValue: Double; const ADest: UInt8); +var + Idx: UInt16; +begin + if not IsNaN(AValue) and not IsInfinite(AValue) + and not IsNegativeZeroFloat(AValue) + and (Frac(AValue) = 0.0) and (AValue >= MIN_SBX) and (AValue <= MAX_SBX) then + EmitInstruction(ACtx, EncodeAsBx(OP_LOAD_INT, ADest, Int16(Trunc(AValue)))) + else + begin + Idx := ACtx.Template.AddConstantFloat(AValue); + EmitInstruction(ACtx, EncodeABx(OP_LOAD_CONST, ADest, Idx)); + end; +end; + +function TryFoldBinary(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaBinaryExpression; const ADest: UInt8): Boolean; +var + LeftLit, RightLit: TGocciaLiteralExpression; + LeftNum, RightNum: TGocciaNumberLiteralValue; + LeftVal, RightVal, FoldedVal: Double; + LeftStr, RightStr: TGocciaStringLiteralValue; + Idx: UInt16; +begin + Result := False; + + if not (AExpr.Left is TGocciaLiteralExpression) or + not (AExpr.Right is TGocciaLiteralExpression) then + Exit; + + LeftLit := TGocciaLiteralExpression(AExpr.Left); + RightLit := TGocciaLiteralExpression(AExpr.Right); + + if (LeftLit.Value is TGocciaNumberLiteralValue) and + (RightLit.Value is TGocciaNumberLiteralValue) then + begin + LeftNum := TGocciaNumberLiteralValue(LeftLit.Value); + RightNum := TGocciaNumberLiteralValue(RightLit.Value); + + if LeftNum.IsNaN or LeftNum.IsInfinite or LeftNum.IsNegativeZero or + RightNum.IsNaN or RightNum.IsInfinite or RightNum.IsNegativeZero then + Exit; + + LeftVal := LeftNum.Value; + RightVal := RightNum.Value; + + case AExpr.Operator of + gttPlus: + begin + EmitFoldedNumber(ACtx, LeftVal + RightVal, ADest); + Result := True; + end; + gttMinus: + begin + EmitFoldedNumber(ACtx, LeftVal - RightVal, ADest); + Result := True; + end; + gttStar: + begin + EmitFoldedNumber(ACtx, LeftVal * RightVal, ADest); + Result := True; + end; + gttSlash: + begin + if RightVal = 0.0 then + Exit; + FoldedVal := LeftVal / RightVal; + if not IsNaN(FoldedVal) and not IsInfinite(FoldedVal) then + begin + EmitFoldedNumber(ACtx, FoldedVal, ADest); + Result := True; + end; + end; + gttPercent: + begin + if RightVal = 0.0 then + Exit; + if (Frac(LeftVal) = 0.0) and (Frac(RightVal) = 0.0) then + EmitFoldedNumber(ACtx, Trunc(LeftVal) mod Trunc(RightVal), ADest) + else + EmitFoldedNumber(ACtx, FMod(LeftVal, RightVal), ADest); + Result := True; + end; + gttPower: + begin + FoldedVal := Math.Power(LeftVal, RightVal); + if not IsNaN(FoldedVal) and not IsInfinite(FoldedVal) then + begin + EmitFoldedNumber(ACtx, FoldedVal, ADest); + Result := True; + end; + end; + gttEqual: + begin + if LeftVal = RightVal then + EmitInstruction(ACtx, EncodeABC(OP_LOAD_TRUE, ADest, 0, 0)) + else + EmitInstruction(ACtx, EncodeABC(OP_LOAD_FALSE, ADest, 0, 0)); + Result := True; + end; + gttNotEqual: + begin + if LeftVal <> RightVal then + EmitInstruction(ACtx, EncodeABC(OP_LOAD_TRUE, ADest, 0, 0)) + else + EmitInstruction(ACtx, EncodeABC(OP_LOAD_FALSE, ADest, 0, 0)); + Result := True; + end; + gttLess: + begin + if LeftVal < RightVal then + EmitInstruction(ACtx, EncodeABC(OP_LOAD_TRUE, ADest, 0, 0)) + else + EmitInstruction(ACtx, EncodeABC(OP_LOAD_FALSE, ADest, 0, 0)); + Result := True; + end; + gttGreater: + begin + if LeftVal > RightVal then + EmitInstruction(ACtx, EncodeABC(OP_LOAD_TRUE, ADest, 0, 0)) + else + EmitInstruction(ACtx, EncodeABC(OP_LOAD_FALSE, ADest, 0, 0)); + Result := True; + end; + gttLessEqual: + begin + if LeftVal <= RightVal then + EmitInstruction(ACtx, EncodeABC(OP_LOAD_TRUE, ADest, 0, 0)) + else + EmitInstruction(ACtx, EncodeABC(OP_LOAD_FALSE, ADest, 0, 0)); + Result := True; + end; + gttGreaterEqual: + begin + if LeftVal >= RightVal then + EmitInstruction(ACtx, EncodeABC(OP_LOAD_TRUE, ADest, 0, 0)) + else + EmitInstruction(ACtx, EncodeABC(OP_LOAD_FALSE, ADest, 0, 0)); + Result := True; + end; + gttBitwiseAnd: + begin + EmitFoldedNumber(ACtx, Int32(Trunc(LeftVal)) and Int32(Trunc(RightVal)), ADest); + Result := True; + end; + gttBitwiseOr: + begin + EmitFoldedNumber(ACtx, Int32(Trunc(LeftVal)) or Int32(Trunc(RightVal)), ADest); + Result := True; + end; + gttBitwiseXor: + begin + EmitFoldedNumber(ACtx, Int32(Trunc(LeftVal)) xor Int32(Trunc(RightVal)), ADest); + Result := True; + end; + gttLeftShift: + begin + EmitFoldedNumber(ACtx, Int32(Trunc(LeftVal)) shl (Int32(Trunc(RightVal)) and $1F), ADest); + Result := True; + end; + gttRightShift: + begin + EmitFoldedNumber(ACtx, SarLongint(Int32(Trunc(LeftVal)), Int32(Trunc(RightVal)) and $1F), ADest); + Result := True; + end; + gttUnsignedRightShift: + begin + EmitFoldedNumber(ACtx, Int64(UInt32(Trunc(LeftVal)) shr + (Int32(Trunc(RightVal)) and $1F)), ADest); + Result := True; + end; + end; + Exit; + end; + + if (AExpr.Operator = gttPlus) and + (LeftLit.Value is TGocciaStringLiteralValue) and + (RightLit.Value is TGocciaStringLiteralValue) then + begin + LeftStr := TGocciaStringLiteralValue(LeftLit.Value); + RightStr := TGocciaStringLiteralValue(RightLit.Value); + Idx := ACtx.Template.AddConstantString(LeftStr.Value + RightStr.Value); + EmitInstruction(ACtx, EncodeABx(OP_LOAD_CONST, ADest, Idx)); + Result := True; + end; +end; + +function TryFoldUnary(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaUnaryExpression; const ADest: UInt8): Boolean; +var + Lit: TGocciaLiteralExpression; + Num: TGocciaNumberLiteralValue; + NumVal: Double; +begin + Result := False; + + if not (AExpr.Operand is TGocciaLiteralExpression) then + Exit; + + Lit := TGocciaLiteralExpression(AExpr.Operand); + + if not (Lit.Value is TGocciaNumberLiteralValue) then + Exit; + + Num := TGocciaNumberLiteralValue(Lit.Value); + + if Num.IsNaN or Num.IsInfinite or Num.IsNegativeZero then + Exit; + + NumVal := Num.Value; + + case AExpr.Operator of + gttMinus: + begin + EmitFoldedNumber(ACtx, -NumVal, ADest); + Result := True; + end; + gttPlus: + begin + EmitFoldedNumber(ACtx, NumVal, ADest); + Result := True; + end; + gttBitwiseNot: + begin + EmitFoldedNumber(ACtx, not Int32(Trunc(NumVal)), ADest); + Result := True; + end; + end; +end; + +end. diff --git a/units/Goccia.Compiler.Context.pas b/units/Goccia.Compiler.Context.pas new file mode 100644 index 00000000..f6a7e233 --- /dev/null +++ b/units/Goccia.Compiler.Context.pas @@ -0,0 +1,185 @@ +unit Goccia.Compiler.Context; + +{$I Goccia.inc} + +interface + +uses + Generics.Collections, + + Souffle.Bytecode, + Souffle.Bytecode.Chunk, + Souffle.Bytecode.Debug, + Souffle.Value, + + Goccia.AST.Expressions, + Goccia.AST.Node, + Goccia.AST.Statements, + Goccia.Compiler.Scope, + Goccia.Token; + +type + TCompileExpressionProc = procedure(const AExpr: TGocciaExpression; + const ADest: UInt8) of object; + TCompileStatementProc = procedure(const AStmt: TGocciaStatement) of object; + TCompileFunctionBodyProc = procedure(const ABody: TGocciaASTNode) of object; + TSwapStateProc = procedure(const ATemplate: TSouffleFunctionTemplate; + const AScope: TGocciaCompilerScope) of object; + + TFormalParameterCountMap = TDictionary; + + TGocciaCompilationContext = record + Template: TSouffleFunctionTemplate; + Scope: TGocciaCompilerScope; + SourcePath: string; + FormalParameterCounts: TFormalParameterCountMap; + CompileExpression: TCompileExpressionProc; + CompileStatement: TCompileStatementProc; + CompileFunctionBody: TCompileFunctionBodyProc; + SwapState: TSwapStateProc; + end; + +function EmitInstruction(const ACtx: TGocciaCompilationContext; + const AInstruction: UInt32): Integer; +procedure EmitLineMapping(const ACtx: TGocciaCompilationContext; + const ALine, AColumn: Integer); +function EmitJumpInstruction(const ACtx: TGocciaCompilationContext; + const AOp: TSouffleOpCode; const AReg: UInt8): Integer; overload; +function EmitJumpInstruction(const ACtx: TGocciaCompilationContext; + const AOp: TSouffleOpCode; const AReg, AFlags: UInt8): Integer; overload; +procedure PatchJumpTarget(const ACtx: TGocciaCompilationContext; + const AIndex: Integer); +function CurrentCodePosition(const ACtx: TGocciaCompilationContext): Integer; + +function TokenTypeToRuntimeOp( + const ATokenType: TGocciaTokenType): TSouffleOpCode; +function CompoundOpToRuntimeOp( + const ATokenType: TGocciaTokenType): TSouffleOpCode; + +implementation + +function EmitInstruction(const ACtx: TGocciaCompilationContext; + const AInstruction: UInt32): Integer; +begin + Result := ACtx.Template.EmitInstruction(AInstruction); +end; + +procedure EmitLineMapping(const ACtx: TGocciaCompilationContext; + const ALine, AColumn: Integer); +begin + if Assigned(ACtx.Template.DebugInfo) then + ACtx.Template.DebugInfo.AddLineMapping( + UInt32(ACtx.Template.CodeCount), UInt32(ALine), UInt16(AColumn)); +end; + +function EmitJumpInstruction(const ACtx: TGocciaCompilationContext; + const AOp: TSouffleOpCode; const AReg: UInt8): Integer; +begin + if AOp = OP_JUMP then + Result := EmitInstruction(ACtx, EncodeAx(AOp, 0)) + else if AOp = OP_PUSH_HANDLER then + Result := EmitInstruction(ACtx, EncodeABx(AOp, AReg, 0)) + else if (AOp = OP_JUMP_IF_NIL) or (AOp = OP_JUMP_IF_NOT_NIL) then + Result := EmitInstruction(ACtx, EncodeABC(AOp, AReg, SOUFFLE_NIL_MATCH_ANY, 0)) + else + Result := EmitInstruction(ACtx, EncodeAsBx(AOp, AReg, 0)); +end; + +function EmitJumpInstruction(const ACtx: TGocciaCompilationContext; + const AOp: TSouffleOpCode; const AReg, AFlags: UInt8): Integer; +begin + Result := EmitInstruction(ACtx, EncodeABC(AOp, AReg, AFlags, 0)); +end; + +procedure PatchJumpTarget(const ACtx: TGocciaCompilationContext; + const AIndex: Integer); +var + Offset: Integer; + Instruction: UInt32; + Op, A, B: UInt8; +begin + Offset := CurrentCodePosition(ACtx) - AIndex - 1; + Instruction := ACtx.Template.GetInstruction(AIndex); + Op := DecodeOp(Instruction); + + if TSouffleOpCode(Op) = OP_JUMP then + ACtx.Template.PatchInstruction(AIndex, EncodeAx(OP_JUMP, Offset)) + else if TSouffleOpCode(Op) = OP_PUSH_HANDLER then + begin + A := DecodeA(Instruction); + ACtx.Template.PatchInstruction(AIndex, + EncodeABx(OP_PUSH_HANDLER, A, UInt16(Offset))); + end + else if (TSouffleOpCode(Op) = OP_JUMP_IF_NIL) or + (TSouffleOpCode(Op) = OP_JUMP_IF_NOT_NIL) then + begin + A := DecodeA(Instruction); + B := DecodeB(Instruction); + Assert(Offset <= 255, 'Nil jump offset exceeds 8-bit range'); + ACtx.Template.PatchInstruction(AIndex, + EncodeABC(TSouffleOpCode(Op), A, B, UInt8(Offset))); + end + else + begin + A := DecodeA(Instruction); + ACtx.Template.PatchInstruction(AIndex, + EncodeAsBx(TSouffleOpCode(Op), A, Int16(Offset))); + end; +end; + +function CurrentCodePosition(const ACtx: TGocciaCompilationContext): Integer; +begin + Result := ACtx.Template.CodeCount; +end; + +function TokenTypeToRuntimeOp( + const ATokenType: TGocciaTokenType): TSouffleOpCode; +begin + case ATokenType of + gttPlus: Result := OP_RT_ADD; + gttMinus: Result := OP_RT_SUB; + gttStar: Result := OP_RT_MUL; + gttSlash: Result := OP_RT_DIV; + gttPercent: Result := OP_RT_MOD; + gttPower: Result := OP_RT_POW; + gttEqual: Result := OP_RT_EQ; + gttNotEqual: Result := OP_RT_NEQ; + gttLess: Result := OP_RT_LT; + gttGreater: Result := OP_RT_GT; + gttLessEqual: Result := OP_RT_LTE; + gttGreaterEqual: Result := OP_RT_GTE; + gttInstanceof: Result := OP_RT_IS_INSTANCE; + gttIn: Result := OP_RT_HAS_PROPERTY; + gttBitwiseAnd: Result := OP_RT_BAND; + gttBitwiseOr: Result := OP_RT_BOR; + gttBitwiseXor: Result := OP_RT_BXOR; + gttLeftShift: Result := OP_RT_SHL; + gttRightShift: Result := OP_RT_SHR; + gttUnsignedRightShift: Result := OP_RT_USHR; + else + Result := OP_RT_ADD; + end; +end; + +function CompoundOpToRuntimeOp( + const ATokenType: TGocciaTokenType): TSouffleOpCode; +begin + case ATokenType of + gttPlusAssign: Result := OP_RT_ADD; + gttMinusAssign: Result := OP_RT_SUB; + gttStarAssign: Result := OP_RT_MUL; + gttSlashAssign: Result := OP_RT_DIV; + gttPercentAssign: Result := OP_RT_MOD; + gttPowerAssign: Result := OP_RT_POW; + gttBitwiseAndAssign: Result := OP_RT_BAND; + gttBitwiseOrAssign: Result := OP_RT_BOR; + gttBitwiseXorAssign: Result := OP_RT_BXOR; + gttLeftShiftAssign: Result := OP_RT_SHL; + gttRightShiftAssign: Result := OP_RT_SHR; + gttUnsignedRightShiftAssign: Result := OP_RT_USHR; + else + Result := OP_RT_ADD; + end; +end; + +end. diff --git a/units/Goccia.Compiler.Expressions.pas b/units/Goccia.Compiler.Expressions.pas new file mode 100644 index 00000000..16047940 --- /dev/null +++ b/units/Goccia.Compiler.Expressions.pas @@ -0,0 +1,1816 @@ +unit Goccia.Compiler.Expressions; + +{$I Goccia.inc} + +interface + +uses + Goccia.AST.Expressions, + Goccia.AST.Node, + Goccia.AST.Statements, + Goccia.Compiler.Context, + Goccia.Compiler.Scope; + +procedure CompileLiteral(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaLiteralExpression; const ADest: UInt8); +procedure CompileIdentifier(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaIdentifierExpression; const ADest: UInt8); +procedure CompileIdentifierAccess(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaIdentifierExpression; const ADest: UInt8; + const ASafe: Boolean); +procedure CompileBinary(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaBinaryExpression; const ADest: UInt8); +procedure CompileUnary(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaUnaryExpression; const ADest: UInt8); +procedure CompileAssignment(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaAssignmentExpression; const ADest: UInt8); +procedure CompilePropertyAssignment(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaPropertyAssignmentExpression; const ADest: UInt8); +procedure CompileArrowFunction(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaArrowFunctionExpression; const ADest: UInt8); +procedure CompileCall(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaCallExpression; const ADest: UInt8); +procedure CompileMember(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaMemberExpression; const ADest: UInt8); +procedure CompileConditional(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaConditionalExpression; const ADest: UInt8); +procedure CompileArray(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaArrayExpression; const ADest: UInt8); +procedure CompileObject(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaObjectExpression; const ADest: UInt8); +procedure CompileTemplateLiteral(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaTemplateLiteralExpression; const ADest: UInt8); +procedure CompileTemplateWithInterpolation(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaTemplateWithInterpolationExpression; const ADest: UInt8); +procedure CompileNewExpression(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaNewExpression; const ADest: UInt8); +procedure CompileComputedPropertyAssignment(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaComputedPropertyAssignmentExpression; const ADest: UInt8); +procedure CompileCompoundAssignment(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaCompoundAssignmentExpression; const ADest: UInt8); +procedure CompilePropertyCompoundAssignment(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaPropertyCompoundAssignmentExpression; const ADest: UInt8); +procedure CompileComputedPropertyCompoundAssignment( + const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaComputedPropertyCompoundAssignmentExpression; + const ADest: UInt8); +procedure CompileMethod(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaMethodExpression; const ADest: UInt8); +procedure CompileIncrement(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaIncrementExpression; const ADest: UInt8); +procedure CompileThis(const ACtx: TGocciaCompilationContext; + const ADest: UInt8); +procedure CompileSuperAccess(const ACtx: TGocciaCompilationContext; + const ADest: UInt8); + +procedure EmitDefaultParameters(const ACtx: TGocciaCompilationContext; + const AParams: TGocciaParameterArray); +procedure CollectDestructuringBindings(const APattern: TGocciaDestructuringPattern; + const AScope: TGocciaCompilerScope; const AIsConst: Boolean = False); +procedure EmitDestructuring(const ACtx: TGocciaCompilationContext; + const APattern: TGocciaDestructuringPattern; const ASrcReg: UInt8); +procedure CompileDestructuringAssignment(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaDestructuringAssignmentExpression; const ADest: UInt8); + +implementation + +uses + Classes, + Generics.Collections, + Math, + SysUtils, + + Souffle.Bytecode, + Souffle.Bytecode.Chunk, + Souffle.Bytecode.Debug, + Souffle.Value, + + Goccia.Compiler.ConstantFolding, + Goccia.Compiler.ExtOps, + Goccia.Constants.ConstructorNames, + Goccia.Constants.ErrorNames, + Goccia.Constants.PropertyNames, + Goccia.Keywords.Reserved, + Goccia.Lexer, + Goccia.Parser, + Goccia.Token, + Goccia.Values.Primitives; + +function TypedGetLocalOp(const AHint: TSouffleLocalType): TSouffleOpCode; +begin + case AHint of + sltInteger: Result := OP_GET_LOCAL_INT; + sltFloat: Result := OP_GET_LOCAL_FLOAT; + sltBoolean: Result := OP_GET_LOCAL_BOOL; + sltString: Result := OP_GET_LOCAL_STRING; + sltReference: Result := OP_GET_LOCAL_REF; + else + Result := OP_GET_LOCAL; + end; +end; + +function TypedSetLocalOp(const AHint: TSouffleLocalType): TSouffleOpCode; +begin + case AHint of + sltInteger: Result := OP_SET_LOCAL_INT; + sltFloat: Result := OP_SET_LOCAL_FLOAT; + sltBoolean: Result := OP_SET_LOCAL_BOOL; + sltString: Result := OP_SET_LOCAL_STRING; + sltReference: Result := OP_SET_LOCAL_REF; + else + Result := OP_SET_LOCAL; + end; +end; + +procedure CompileLiteral(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaLiteralExpression; const ADest: UInt8); +var + Value: TGocciaValue; + Idx: UInt16; +begin + Value := AExpr.Value; + + if Value is TGocciaNullLiteralValue then + EmitInstruction(ACtx, EncodeABC(OP_LOAD_NIL, ADest, 1, 0)) + else if Value is TGocciaUndefinedLiteralValue then + EmitInstruction(ACtx, EncodeABC(OP_LOAD_NIL, ADest, 0, 0)) + else if Value is TGocciaBooleanLiteralValue then + begin + if TGocciaBooleanLiteralValue(Value).Value then + EmitInstruction(ACtx, EncodeABC(OP_LOAD_TRUE, ADest, 0, 0)) + else + EmitInstruction(ACtx, EncodeABC(OP_LOAD_FALSE, ADest, 0, 0)); + end + else if Value is TGocciaNumberLiteralValue then + begin + if not TGocciaNumberLiteralValue(Value).IsNaN + and not TGocciaNumberLiteralValue(Value).IsInfinite + and (Frac(TGocciaNumberLiteralValue(Value).Value) = 0.0) + and (TGocciaNumberLiteralValue(Value).Value >= MIN_SBX) + and (TGocciaNumberLiteralValue(Value).Value <= MAX_SBX) then + EmitInstruction(ACtx, EncodeAsBx(OP_LOAD_INT, ADest, + Int16(Trunc(TGocciaNumberLiteralValue(Value).Value)))) + else + begin + Idx := ACtx.Template.AddConstantFloat( + TGocciaNumberLiteralValue(Value).Value); + EmitInstruction(ACtx, EncodeABx(OP_LOAD_CONST, ADest, Idx)); + end; + end + else if Value is TGocciaStringLiteralValue then + begin + Idx := ACtx.Template.AddConstantString( + TGocciaStringLiteralValue(Value).Value); + EmitInstruction(ACtx, EncodeABx(OP_LOAD_CONST, ADest, Idx)); + end + else + EmitInstruction(ACtx, EncodeABC(OP_LOAD_NIL, ADest, 0, 0)); +end; + +procedure CompileIdentifier(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaIdentifierExpression; const ADest: UInt8); +begin + CompileIdentifierAccess(ACtx, AExpr, ADest, False); +end; + +procedure CompileIdentifierAccess(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaIdentifierExpression; const ADest: UInt8; + const ASafe: Boolean); +var + LocalIdx, UpvalIdx: Integer; + Local: TGocciaCompilerLocal; + Slot: UInt8; + NameIdx: UInt16; + CondReg, ArgReg: UInt8; + OkJump: Integer; + Hint: TSouffleLocalType; +begin + LocalIdx := ACtx.Scope.ResolveLocal(AExpr.Name); + if LocalIdx >= 0 then + begin + Local := ACtx.Scope.GetLocal(LocalIdx); + if Local.IsGlobalBacked then + begin + NameIdx := ACtx.Template.AddConstantString(AExpr.Name); + EmitInstruction(ACtx, EncodeABx(OP_RT_GET_GLOBAL, ADest, NameIdx)); + Exit; + end; + Slot := Local.Slot; + Hint := Local.TypeHint; + if (Hint <> sltUntyped) and (Slot <> ADest) then + EmitInstruction(ACtx, EncodeABx(TypedGetLocalOp(Hint), ADest, Slot)) + else if Slot <> ADest then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, Slot, 0)); + Exit; + end; + + UpvalIdx := ACtx.Scope.ResolveUpvalue(AExpr.Name); + if UpvalIdx >= 0 then + begin + EmitInstruction(ACtx, EncodeABx(OP_GET_UPVALUE, ADest, UInt16(UpvalIdx))); + Exit; + end; + + NameIdx := ACtx.Template.AddConstantString(AExpr.Name); + + if ASafe then + begin + EmitInstruction(ACtx, EncodeABx(OP_RT_GET_GLOBAL, ADest, NameIdx)); + Exit; + end; + + CondReg := ACtx.Scope.AllocateRegister; + ArgReg := ACtx.Scope.AllocateRegister; + + EmitInstruction(ACtx, EncodeABx(OP_RT_HAS_GLOBAL, CondReg, NameIdx)); + OkJump := EmitJumpInstruction(ACtx, OP_JUMP_IF_TRUE, CondReg); + + EmitInstruction(ACtx, EncodeABx(OP_RT_GET_GLOBAL, CondReg, + ACtx.Template.AddConstantString(REFERENCE_ERROR_NAME))); + EmitInstruction(ACtx, EncodeABx(OP_LOAD_CONST, ArgReg, + ACtx.Template.AddConstantString(AExpr.Name + ' is not defined'))); + EmitInstruction(ACtx, EncodeABC(OP_RT_CONSTRUCT, CondReg, CondReg, 1)); + EmitInstruction(ACtx, EncodeABC(OP_THROW, CondReg, 0, 0)); + + PatchJumpTarget(ACtx, OkJump); + + EmitInstruction(ACtx, EncodeABx(OP_RT_GET_GLOBAL, ADest, NameIdx)); + + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; +end; + +procedure CompileBinary(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaBinaryExpression; const ADest: UInt8); +var + RegB, RegC: UInt8; + Op: TSouffleOpCode; + JumpIdx, JumpIdx2: Integer; +begin + if TryFoldBinary(ACtx, AExpr, ADest) then + Exit; + + if AExpr.Operator = gttAnd then + begin + ACtx.CompileExpression(AExpr.Left, ADest); + JumpIdx := EmitJumpInstruction(ACtx, OP_JUMP_IF_FALSE, ADest); + ACtx.CompileExpression(AExpr.Right, ADest); + PatchJumpTarget(ACtx, JumpIdx); + Exit; + end; + + if AExpr.Operator = gttOr then + begin + ACtx.CompileExpression(AExpr.Left, ADest); + JumpIdx := EmitJumpInstruction(ACtx, OP_JUMP_IF_TRUE, ADest); + ACtx.CompileExpression(AExpr.Right, ADest); + PatchJumpTarget(ACtx, JumpIdx); + Exit; + end; + + if AExpr.Operator = gttNullishCoalescing then + begin + ACtx.CompileExpression(AExpr.Left, ADest); + JumpIdx := EmitJumpInstruction(ACtx, OP_JUMP_IF_NOT_NIL, ADest); + ACtx.CompileExpression(AExpr.Right, ADest); + PatchJumpTarget(ACtx, JumpIdx); + Exit; + end; + + RegB := ACtx.Scope.AllocateRegister; + RegC := ACtx.Scope.AllocateRegister; + + ACtx.CompileExpression(AExpr.Left, RegB); + ACtx.CompileExpression(AExpr.Right, RegC); + + Op := TokenTypeToRuntimeOp(AExpr.Operator); + if AExpr.Operator = gttPlus then + begin + EmitInstruction(ACtx, EncodeABx(OP_TO_PRIMITIVE, RegB, RegB)); + EmitInstruction(ACtx, EncodeABx(OP_TO_PRIMITIVE, RegC, RegC)); + end; + if AExpr.Operator = gttIn then + EmitInstruction(ACtx, EncodeABC(Op, ADest, RegC, RegB)) + else + EmitInstruction(ACtx, EncodeABC(Op, ADest, RegB, RegC)); + + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; +end; + +procedure CompileDelete(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaUnaryExpression; const ADest: UInt8); +var + MemberExpr: TGocciaMemberExpression; + ObjReg, KeyReg: UInt8; + PropIdx: UInt16; +begin + if AExpr.Operand is TGocciaMemberExpression then + begin + MemberExpr := TGocciaMemberExpression(AExpr.Operand); + ObjReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(MemberExpr.ObjectExpr, ObjReg); + + if MemberExpr.Computed then + begin + KeyReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(MemberExpr.PropertyExpression, KeyReg); + EmitInstruction(ACtx, EncodeABC(OP_RT_DEL_INDEX, ADest, ObjReg, KeyReg)); + ACtx.Scope.FreeRegister; + end + else + begin + PropIdx := ACtx.Template.AddConstantString(MemberExpr.PropertyName); + if PropIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: property name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_DEL_PROP, ADest, ObjReg, UInt8(PropIdx))); + end; + ACtx.Scope.FreeRegister; + end + else + EmitInstruction(ACtx, EncodeABC(OP_LOAD_TRUE, ADest, 0, 0)); +end; + +procedure CompileUnary(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaUnaryExpression; const ADest: UInt8); +var + RegB: UInt8; +begin + if AExpr.Operator = gttDelete then + begin + CompileDelete(ACtx, AExpr, ADest); + Exit; + end; + + if TryFoldUnary(ACtx, AExpr, ADest) then + Exit; + + RegB := ACtx.Scope.AllocateRegister; + + if (AExpr.Operator = gttTypeof) and (AExpr.Operand is TGocciaIdentifierExpression) then + CompileIdentifierAccess(ACtx, TGocciaIdentifierExpression(AExpr.Operand), RegB, True) + else + ACtx.CompileExpression(AExpr.Operand, RegB); + + case AExpr.Operator of + gttNot: EmitInstruction(ACtx, EncodeABC(OP_RT_NOT, ADest, RegB, 0)); + gttMinus: EmitInstruction(ACtx, EncodeABC(OP_RT_NEG, ADest, RegB, 0)); + gttPlus: + begin + EmitInstruction(ACtx, EncodeABC(OP_RT_NEG, ADest, RegB, 0)); + EmitInstruction(ACtx, EncodeABC(OP_RT_NEG, ADest, ADest, 0)); + end; + gttTypeof: EmitInstruction(ACtx, EncodeABC(OP_RT_TYPEOF, ADest, RegB, 0)); + gttBitwiseNot: EmitInstruction(ACtx, EncodeABC(OP_RT_BNOT, ADest, RegB, 0)); + else + EmitInstruction(ACtx, EncodeABC(OP_LOAD_NIL, ADest, 0, 0)); + end; + + ACtx.Scope.FreeRegister; +end; + +procedure CompileAssignment(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaAssignmentExpression; const ADest: UInt8); +var + LocalIdx, UpvalIdx: Integer; + Local: TGocciaCompilerLocal; + Slot: UInt8; + MsgIdx: UInt16; + Hint: TSouffleLocalType; +begin + ACtx.CompileExpression(AExpr.Value, ADest); + + LocalIdx := ACtx.Scope.ResolveLocal(AExpr.Name); + if LocalIdx >= 0 then + begin + Local := ACtx.Scope.GetLocal(LocalIdx); + if Local.IsConst then + begin + MsgIdx := ACtx.Template.AddConstantString( + 'Assignment to constant variable.'); + if MsgIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: error message index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, 0, + GOCCIA_EXT_THROW_TYPE_ERROR, UInt8(MsgIdx))); + Exit; + end; + if Local.IsGlobalBacked then + begin + EmitInstruction(ACtx, EncodeABx(OP_RT_SET_GLOBAL, ADest, + ACtx.Template.AddConstantString(AExpr.Name))); + Exit; + end; + Slot := Local.Slot; + Hint := Local.TypeHint; + if ADest <> Slot then + begin + if Hint <> sltUntyped then + EmitInstruction(ACtx, EncodeABx(TypedSetLocalOp(Hint), ADest, Slot)) + else + EmitInstruction(ACtx, EncodeABC(OP_MOVE, Slot, ADest, 0)); + end; + Exit; + end; + + UpvalIdx := ACtx.Scope.ResolveUpvalue(AExpr.Name); + if UpvalIdx >= 0 then + begin + if ACtx.Scope.GetUpvalue(UpvalIdx).IsConst then + begin + MsgIdx := ACtx.Template.AddConstantString( + 'Assignment to constant variable.'); + if MsgIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: error message index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, 0, + GOCCIA_EXT_THROW_TYPE_ERROR, UInt8(MsgIdx))); + Exit; + end; + EmitInstruction(ACtx, EncodeABx(OP_SET_UPVALUE, ADest, UInt16(UpvalIdx))); + Exit; + end; + + EmitInstruction(ACtx, EncodeABx(OP_RT_SET_GLOBAL, ADest, + ACtx.Template.AddConstantString(AExpr.Name))); +end; + +procedure CompilePropertyAssignment(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaPropertyAssignmentExpression; const ADest: UInt8); +var + ObjReg, ValReg: UInt8; + KeyIdx: UInt16; +begin + ObjReg := ACtx.Scope.AllocateRegister; + ValReg := ACtx.Scope.AllocateRegister; + + ACtx.CompileExpression(AExpr.ObjectExpr, ObjReg); + ACtx.CompileExpression(AExpr.Value, ValReg); + + KeyIdx := ACtx.Template.AddConstantString(AExpr.PropertyName); + if KeyIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: property name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_SET_PROP, ObjReg, UInt8(KeyIdx), ValReg)); + + if ADest <> ValReg then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, ValReg, 0)); + + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; +end; + +procedure EmitUndefinedCheck(const ACtx: TGocciaCompilationContext; + const ASlot: UInt8; out AJumpIdx: Integer); +begin + AJumpIdx := EmitJumpInstruction(ACtx, OP_JUMP_IF_NOT_NIL, ASlot, + SOUFFLE_NIL_DEFAULT); +end; + +procedure EmitDefaultParameters(const ACtx: TGocciaCompilationContext; + const AParams: TGocciaParameterArray); +var + I, LocalIdx: Integer; + Slot: UInt8; + JumpIdx: Integer; +begin + for I := 0 to High(AParams) do + begin + if not Assigned(AParams[I].DefaultValue) then + Continue; + if AParams[I].IsPattern then + begin + LocalIdx := ACtx.Scope.ResolveLocal('__param' + IntToStr(I)); + if LocalIdx < 0 then + Continue; + Slot := ACtx.Scope.GetLocal(LocalIdx).Slot; + EmitUndefinedCheck(ACtx, Slot, JumpIdx); + ACtx.CompileExpression(AParams[I].DefaultValue, Slot); + PatchJumpTarget(ACtx, JumpIdx); + Continue; + end; + + LocalIdx := ACtx.Scope.ResolveLocal(AParams[I].Name); + if LocalIdx < 0 then + Continue; + + Slot := ACtx.Scope.GetLocal(LocalIdx).Slot; + EmitUndefinedCheck(ACtx, Slot, JumpIdx); + ACtx.CompileExpression(AParams[I].DefaultValue, Slot); + PatchJumpTarget(ACtx, JumpIdx); + end; +end; + +procedure CollectDestructuringBindings(const APattern: TGocciaDestructuringPattern; + const AScope: TGocciaCompilerScope; const AIsConst: Boolean); +var + ObjPat: TGocciaObjectDestructuringPattern; + ArrPat: TGocciaArrayDestructuringPattern; + AssignPat: TGocciaAssignmentDestructuringPattern; + I: Integer; +begin + if APattern is TGocciaIdentifierDestructuringPattern then + AScope.DeclareLocal(TGocciaIdentifierDestructuringPattern(APattern).Name, AIsConst) + else if APattern is TGocciaObjectDestructuringPattern then + begin + ObjPat := TGocciaObjectDestructuringPattern(APattern); + for I := 0 to ObjPat.Properties.Count - 1 do + CollectDestructuringBindings(ObjPat.Properties[I].Pattern, AScope, AIsConst); + end + else if APattern is TGocciaArrayDestructuringPattern then + begin + ArrPat := TGocciaArrayDestructuringPattern(APattern); + for I := 0 to ArrPat.Elements.Count - 1 do + if Assigned(ArrPat.Elements[I]) then + CollectDestructuringBindings(ArrPat.Elements[I], AScope, AIsConst); + end + else if APattern is TGocciaAssignmentDestructuringPattern then + begin + AssignPat := TGocciaAssignmentDestructuringPattern(APattern); + CollectDestructuringBindings(AssignPat.Left, AScope, AIsConst); + end + else if APattern is TGocciaRestDestructuringPattern then + CollectDestructuringBindings( + TGocciaRestDestructuringPattern(APattern).Argument, AScope, AIsConst); +end; + +procedure EmitDestructuring(const ACtx: TGocciaCompilationContext; + const APattern: TGocciaDestructuringPattern; const ASrcReg: UInt8); +var + ObjPat: TGocciaObjectDestructuringPattern; + ArrPat: TGocciaArrayDestructuringPattern; + AssignPat: TGocciaAssignmentDestructuringPattern; + IdentPat: TGocciaIdentifierDestructuringPattern; + Prop: TGocciaDestructuringProperty; + LocalIdx: Integer; + DestSlot, IdxReg: UInt8; + PropIdx: UInt16; + JumpIdx, I: Integer; +begin + if APattern is TGocciaIdentifierDestructuringPattern then + begin + IdentPat := TGocciaIdentifierDestructuringPattern(APattern); + LocalIdx := ACtx.Scope.ResolveLocal(IdentPat.Name); + if LocalIdx >= 0 then + begin + DestSlot := ACtx.Scope.GetLocal(LocalIdx).Slot; + if DestSlot <> ASrcReg then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, DestSlot, ASrcReg, 0)); + end + else + begin + LocalIdx := ACtx.Scope.ResolveUpvalue(IdentPat.Name); + if LocalIdx >= 0 then + EmitInstruction(ACtx, EncodeABx(OP_SET_UPVALUE, ASrcReg, UInt16(LocalIdx))) + else + EmitInstruction(ACtx, EncodeABx(OP_RT_SET_GLOBAL, ASrcReg, + ACtx.Template.AddConstantString(IdentPat.Name))); + end; + end + else if APattern is TGocciaObjectDestructuringPattern then + begin + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, ASrcReg, GOCCIA_EXT_REQUIRE_OBJECT, 0)); + ObjPat := TGocciaObjectDestructuringPattern(APattern); + for I := 0 to ObjPat.Properties.Count - 1 do + begin + Prop := ObjPat.Properties[I]; + + if Prop.Pattern is TGocciaRestDestructuringPattern then + begin + IdxReg := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeABC(OP_NEW_ARRAY, IdxReg, UInt8(I), 0)); + for JumpIdx := 0 to I - 1 do + begin + DestSlot := ACtx.Scope.AllocateRegister; + PropIdx := ACtx.Template.AddConstantString(ObjPat.Properties[JumpIdx].Key); + EmitInstruction(ACtx, EncodeABx(OP_LOAD_CONST, DestSlot, PropIdx)); + EmitInstruction(ACtx, EncodeABC(OP_ARRAY_PUSH, IdxReg, DestSlot, 0)); + ACtx.Scope.FreeRegister; + end; + DestSlot := ACtx.Scope.AllocateRegister; + if IdxReg <> DestSlot + 1 then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, DestSlot + 1, IdxReg, 0)); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, DestSlot, + GOCCIA_EXT_OBJ_REST, ASrcReg)); + EmitDestructuring(ACtx, + TGocciaRestDestructuringPattern(Prop.Pattern).Argument, DestSlot); + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + Break; + end; + + DestSlot := ACtx.Scope.AllocateRegister; + if Prop.Computed and Assigned(Prop.KeyExpression) then + begin + IdxReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(Prop.KeyExpression, IdxReg); + EmitInstruction(ACtx, EncodeABC(OP_RT_GET_INDEX, DestSlot, ASrcReg, + IdxReg)); + ACtx.Scope.FreeRegister; + end + else + begin + PropIdx := ACtx.Template.AddConstantString(Prop.Key); + if PropIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: property name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_GET_PROP, DestSlot, ASrcReg, + UInt8(PropIdx))); + end; + + EmitDestructuring(ACtx, Prop.Pattern, DestSlot); + ACtx.Scope.FreeRegister; + end; + end + else if APattern is TGocciaArrayDestructuringPattern then + begin + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, ASrcReg, GOCCIA_EXT_REQUIRE_ITERABLE, 0)); + ArrPat := TGocciaArrayDestructuringPattern(APattern); + for I := 0 to ArrPat.Elements.Count - 1 do + begin + if not Assigned(ArrPat.Elements[I]) then + Continue; + + if ArrPat.Elements[I] is TGocciaRestDestructuringPattern then + begin + DestSlot := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeABC(OP_UNPACK, DestSlot, ASrcReg, + UInt8(I))); + EmitDestructuring(ACtx, + TGocciaRestDestructuringPattern(ArrPat.Elements[I]).Argument, DestSlot); + ACtx.Scope.FreeRegister; + Break; + end; + + DestSlot := ACtx.Scope.AllocateRegister; + IdxReg := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeAsBx(OP_LOAD_INT, IdxReg, Int16(I))); + EmitInstruction(ACtx, EncodeABC(OP_ARRAY_GET, DestSlot, ASrcReg, IdxReg)); + ACtx.Scope.FreeRegister; + + EmitDestructuring(ACtx, ArrPat.Elements[I], DestSlot); + ACtx.Scope.FreeRegister; + end; + end + else if APattern is TGocciaAssignmentDestructuringPattern then + begin + AssignPat := TGocciaAssignmentDestructuringPattern(APattern); + EmitUndefinedCheck(ACtx, ASrcReg, JumpIdx); + ACtx.CompileExpression(AssignPat.Right, ASrcReg); + PatchJumpTarget(ACtx, JumpIdx); + EmitDestructuring(ACtx, AssignPat.Left, ASrcReg); + end; +end; + +procedure CompileDestructuringAssignment(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaDestructuringAssignmentExpression; const ADest: UInt8); +var + SrcReg: UInt8; +begin + SrcReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(AExpr.Right, SrcReg); + EmitDestructuring(ACtx, AExpr.Left, SrcReg); + if ADest <> SrcReg then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, SrcReg, 0)); + ACtx.Scope.FreeRegister; +end; + +procedure EmitDestructuringParameters(const ACtx: TGocciaCompilationContext; + const AParams: TGocciaParameterArray); +var + I, LocalIdx: Integer; + ParamSlot: UInt8; +begin + for I := 0 to High(AParams) do + begin + if not AParams[I].IsPattern then + Continue; + if not Assigned(AParams[I].Pattern) then + Continue; + + LocalIdx := ACtx.Scope.ResolveLocal('__param' + IntToStr(I)); + if LocalIdx < 0 then + Continue; + ParamSlot := ACtx.Scope.GetLocal(LocalIdx).Slot; + EmitDestructuring(ACtx, AParams[I].Pattern, ParamSlot); + end; +end; + +procedure CompileArrowFunction(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaArrowFunctionExpression; const ADest: UInt8); +var + OldTemplate: TSouffleFunctionTemplate; + OldScope: TGocciaCompilerScope; + ChildTemplate: TSouffleFunctionTemplate; + ChildScope: TGocciaCompilerScope; + ChildCtx: TGocciaCompilationContext; + FuncIdx: UInt16; + FormalCount: Integer; + RestParamIndex: Integer; + RestReg: Integer; + I: Integer; +begin + OldTemplate := ACtx.Template; + OldScope := ACtx.Scope; + + ChildTemplate := TSouffleFunctionTemplate.Create(''); + ChildTemplate.DebugInfo := TSouffleDebugInfo.Create(ACtx.SourcePath); + ChildTemplate.IsAsync := AExpr.IsAsync; + ChildScope := TGocciaCompilerScope.Create(OldScope, 0); + + ChildScope.DeclareLocal('__receiver', False); + ChildTemplate.ParameterCount := Length(AExpr.Parameters); + + FormalCount := -1; + RestParamIndex := -1; + for I := 0 to High(AExpr.Parameters) do + begin + if AExpr.Parameters[I].IsRest or Assigned(AExpr.Parameters[I].DefaultValue) then + begin + if FormalCount < 0 then + FormalCount := I; + if AExpr.Parameters[I].IsRest then + RestParamIndex := I; + end; + if AExpr.Parameters[I].IsPattern then + ChildScope.DeclareLocal('__param' + IntToStr(I), False) + else + ChildScope.DeclareLocal(AExpr.Parameters[I].Name, False); + end; + for I := 0 to High(AExpr.Parameters) do + if AExpr.Parameters[I].IsPattern and Assigned(AExpr.Parameters[I].Pattern) then + CollectDestructuringBindings(AExpr.Parameters[I].Pattern, ChildScope); + + if FormalCount < 0 then + FormalCount := Length(AExpr.Parameters); + if Assigned(ACtx.FormalParameterCounts) then + ACtx.FormalParameterCounts.AddOrSetValue(ChildTemplate, FormalCount); + + ACtx.SwapState(ChildTemplate, ChildScope); + try + ChildCtx := ACtx; + ChildCtx.Template := ChildTemplate; + ChildCtx.Scope := ChildScope; + + if RestParamIndex >= 0 then + begin + RestReg := ChildScope.ResolveLocal(AExpr.Parameters[RestParamIndex].Name); + EmitInstruction(ChildCtx, + EncodeABC(OP_PACK_ARGS, UInt8(RestReg), UInt8(RestParamIndex), 0)); + end; + + EmitDefaultParameters(ChildCtx, AExpr.Parameters); + EmitDestructuringParameters(ChildCtx, AExpr.Parameters); + + ACtx.CompileFunctionBody(AExpr.Body); + + ChildTemplate.MaxRegisters := ChildScope.MaxSlot; + + for I := 0 to ChildScope.UpvalueCount - 1 do + ChildTemplate.AddUpvalueDescriptor( + ChildScope.GetUpvalue(I).IsLocal, + ChildScope.GetUpvalue(I).Index); + finally + ACtx.SwapState(OldTemplate, OldScope); + ChildScope.Free; + end; + + FuncIdx := OldTemplate.AddFunction(ChildTemplate); + EmitInstruction(ACtx, EncodeABx(OP_CLOSURE, ADest, FuncIdx)); +end; + +function HasSpreadArgument(const AExpr: TGocciaCallExpression): Boolean; +var + I: Integer; +begin + for I := 0 to AExpr.Arguments.Count - 1 do + if AExpr.Arguments[I] is TGocciaSpreadExpression then + Exit(True); + Result := False; +end; + +procedure CompileSpreadArgsArray(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaCallExpression; const AArrayReg: UInt8); +var + I: Integer; + ElemReg: UInt8; +begin + EmitInstruction(ACtx, EncodeABC(OP_NEW_ARRAY, AArrayReg, 0, 0)); + for I := 0 to AExpr.Arguments.Count - 1 do + begin + ElemReg := ACtx.Scope.AllocateRegister; + if AExpr.Arguments[I] is TGocciaSpreadExpression then + begin + ACtx.CompileExpression( + TGocciaSpreadExpression(AExpr.Arguments[I]).Argument, ElemReg); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, AArrayReg, GOCCIA_EXT_SPREAD, ElemReg)); + end + else + begin + ACtx.CompileExpression(AExpr.Arguments[I], ElemReg); + EmitInstruction(ACtx, EncodeABC(OP_ARRAY_PUSH, AArrayReg, ElemReg, 0)); + end; + ACtx.Scope.FreeRegister; + end; +end; + +procedure CompileCall(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaCallExpression; const ADest: UInt8); +var + ArgCount, I: Integer; + BaseReg, ObjReg, ArgsReg, SuperReg: UInt8; + MemberExpr: TGocciaMemberExpression; + PropIdx: UInt16; + UseSpread: Boolean; +begin + ArgCount := AExpr.Arguments.Count; + if ArgCount > High(UInt8) then + raise Exception.Create('Compiler error: too many arguments (>255)'); + UseSpread := HasSpreadArgument(AExpr); + + if AExpr.Callee is TGocciaSuperExpression then + begin + ObjReg := ACtx.Scope.AllocateRegister; + CompileThis(ACtx, ObjReg); + BaseReg := ACtx.Scope.AllocateRegister; + SuperReg := ACtx.Scope.AllocateRegister; + CompileSuperAccess(ACtx, SuperReg); + PropIdx := ACtx.Template.AddConstantString(PROP_CONSTRUCTOR); + if PropIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow'); + if SuperReg <> BaseReg + 1 then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, BaseReg + 1, SuperReg, 0)); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, BaseReg, GOCCIA_EXT_SUPER_GET, UInt8(PropIdx))); + ACtx.Scope.FreeRegister; + + for I := 0 to ArgCount - 1 do + ACtx.CompileExpression(AExpr.Arguments[I], ACtx.Scope.AllocateRegister); + EmitInstruction(ACtx, EncodeABC(OP_RT_CALL_METHOD, BaseReg, UInt8(ArgCount), 0)); + for I := 0 to ArgCount - 1 do + ACtx.Scope.FreeRegister; + + if ADest <> BaseReg then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, BaseReg, 0)); + + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + end + else if AExpr.Callee is TGocciaMemberExpression then + begin + MemberExpr := TGocciaMemberExpression(AExpr.Callee); + + if MemberExpr.ObjectExpr is TGocciaSuperExpression then + begin + ObjReg := ACtx.Scope.AllocateRegister; + CompileThis(ACtx, ObjReg); + BaseReg := ACtx.Scope.AllocateRegister; + SuperReg := ACtx.Scope.AllocateRegister; + CompileSuperAccess(ACtx, SuperReg); + PropIdx := ACtx.Template.AddConstantString(MemberExpr.PropertyName); + if PropIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow'); + if SuperReg <> BaseReg + 1 then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, BaseReg + 1, SuperReg, 0)); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, BaseReg, GOCCIA_EXT_SUPER_GET, UInt8(PropIdx))); + ACtx.Scope.FreeRegister; + + for I := 0 to ArgCount - 1 do + ACtx.CompileExpression(AExpr.Arguments[I], ACtx.Scope.AllocateRegister); + EmitInstruction(ACtx, EncodeABC(OP_RT_CALL_METHOD, BaseReg, UInt8(ArgCount), 0)); + for I := 0 to ArgCount - 1 do + ACtx.Scope.FreeRegister; + + if ADest <> BaseReg then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, BaseReg, 0)); + + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + end + else + begin + ObjReg := ACtx.Scope.AllocateRegister; + BaseReg := ACtx.Scope.AllocateRegister; + + ACtx.CompileExpression(MemberExpr.ObjectExpr, ObjReg); + + if MemberExpr.Computed then + begin + ACtx.CompileExpression(MemberExpr.PropertyExpression, BaseReg); + EmitInstruction(ACtx, EncodeABC(OP_ARRAY_GET, BaseReg, ObjReg, BaseReg)); + end + else + begin + PropIdx := ACtx.Template.AddConstantString(MemberExpr.PropertyName); + if PropIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: property name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RECORD_GET, BaseReg, ObjReg, UInt8(PropIdx))); + end; + + if UseSpread then + begin + ArgsReg := ACtx.Scope.AllocateRegister; + CompileSpreadArgsArray(ACtx, AExpr, ArgsReg); + EmitInstruction(ACtx, EncodeABC(OP_RT_CALL_METHOD, BaseReg, ArgsReg, 1)); + ACtx.Scope.FreeRegister; + end + else + begin + for I := 0 to ArgCount - 1 do + ACtx.CompileExpression(AExpr.Arguments[I], ACtx.Scope.AllocateRegister); + EmitInstruction(ACtx, EncodeABC(OP_RT_CALL_METHOD, BaseReg, UInt8(ArgCount), 0)); + for I := 0 to ArgCount - 1 do + ACtx.Scope.FreeRegister; + end; + + if ADest <> BaseReg then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, BaseReg, 0)); + + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + end; + end + else + begin + BaseReg := ACtx.Scope.AllocateRegister; + + ACtx.CompileExpression(AExpr.Callee, BaseReg); + + if UseSpread then + begin + ArgsReg := ACtx.Scope.AllocateRegister; + CompileSpreadArgsArray(ACtx, AExpr, ArgsReg); + EmitInstruction(ACtx, EncodeABC(OP_RT_CALL, BaseReg, ArgsReg, 1)); + ACtx.Scope.FreeRegister; + end + else + begin + for I := 0 to ArgCount - 1 do + ACtx.CompileExpression(AExpr.Arguments[I], ACtx.Scope.AllocateRegister); + EmitInstruction(ACtx, EncodeABC(OP_RT_CALL, BaseReg, UInt8(ArgCount), 0)); + for I := 0 to ArgCount - 1 do + ACtx.Scope.FreeRegister; + end; + + if ADest <> BaseReg then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, BaseReg, 0)); + + ACtx.Scope.FreeRegister; + end; +end; + +procedure CompileMember(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaMemberExpression; const ADest: UInt8); +var + ObjReg, IdxReg: UInt8; + PropIdx: UInt16; + NilJump, EndJump: Integer; +begin + ObjReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(AExpr.ObjectExpr, ObjReg); + + if AExpr.Optional then + begin + NilJump := EmitJumpInstruction(ACtx, OP_JUMP_IF_NIL, ObjReg); + + if AExpr.Computed then + begin + IdxReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(AExpr.PropertyExpression, IdxReg); + EmitInstruction(ACtx, EncodeABC(OP_ARRAY_GET, ADest, ObjReg, IdxReg)); + ACtx.Scope.FreeRegister; + end + else + begin + PropIdx := ACtx.Template.AddConstantString(AExpr.PropertyName); + if PropIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: property name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RECORD_GET, ADest, ObjReg, UInt8(PropIdx))); + end; + + EndJump := EmitJumpInstruction(ACtx, OP_JUMP, 0); + PatchJumpTarget(ACtx, NilJump); + EmitInstruction(ACtx, EncodeABC(OP_LOAD_NIL, ADest, 0, 0)); + PatchJumpTarget(ACtx, EndJump); + end + else + begin + if AExpr.Computed then + begin + IdxReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(AExpr.PropertyExpression, IdxReg); + EmitInstruction(ACtx, EncodeABC(OP_ARRAY_GET, ADest, ObjReg, IdxReg)); + ACtx.Scope.FreeRegister; + end + else + begin + PropIdx := ACtx.Template.AddConstantString(AExpr.PropertyName); + if PropIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: property name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RECORD_GET, ADest, ObjReg, UInt8(PropIdx))); + end; + end; + + ACtx.Scope.FreeRegister; +end; + +procedure CompileConditional(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaConditionalExpression; const ADest: UInt8); +var + ElseJump, EndJump: Integer; +begin + ACtx.CompileExpression(AExpr.Condition, ADest); + ElseJump := EmitJumpInstruction(ACtx, OP_JUMP_IF_FALSE, ADest); + ACtx.CompileExpression(AExpr.Consequent, ADest); + EndJump := EmitJumpInstruction(ACtx, OP_JUMP, 0); + PatchJumpTarget(ACtx, ElseJump); + ACtx.CompileExpression(AExpr.Alternate, ADest); + PatchJumpTarget(ACtx, EndJump); +end; + +procedure CompileArray(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaArrayExpression; const ADest: UInt8); +var + I: Integer; + ElemReg: UInt8; +begin + EmitInstruction(ACtx, EncodeABC(OP_NEW_ARRAY, ADest, + UInt8(Min(AExpr.Elements.Count, 255)), 0)); + + for I := 0 to AExpr.Elements.Count - 1 do + begin + ElemReg := ACtx.Scope.AllocateRegister; + if AExpr.Elements[I] is TGocciaSpreadExpression then + begin + ACtx.CompileExpression(TGocciaSpreadExpression(AExpr.Elements[I]).Argument, ElemReg); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, ADest, GOCCIA_EXT_SPREAD, ElemReg)); + end + else + begin + ACtx.CompileExpression(AExpr.Elements[I], ElemReg); + EmitInstruction(ACtx, EncodeABC(OP_ARRAY_PUSH, ADest, ElemReg, 0)); + end; + ACtx.Scope.FreeRegister; + end; +end; + +procedure CompileObjectProperty(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaObjectExpression; const ADest: UInt8; + const AKey: string; const AValExpr: TGocciaExpression); +var + ValReg: UInt8; + KeyIdx: UInt16; +begin + ValReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(AValExpr, ValReg); + KeyIdx := ACtx.Template.AddConstantString(AKey); + if KeyIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: property name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_SET_PROP, ADest, KeyIdx, ValReg)); + ACtx.Scope.FreeRegister; +end; + +procedure CompileGetterProperty(const ACtx: TGocciaCompilationContext; + const ADest: UInt8; const AKey: string; + const AGetter: TGocciaGetterExpression); +var + OldTemplate: TSouffleFunctionTemplate; + OldScope: TGocciaCompilerScope; + ChildTemplate: TSouffleFunctionTemplate; + ChildScope: TGocciaCompilerScope; + FuncIdx: UInt16; + GetterReg: UInt8; + KeyIdx: UInt16; + I: Integer; +begin + OldTemplate := ACtx.Template; + OldScope := ACtx.Scope; + + ChildTemplate := TSouffleFunctionTemplate.Create(''); + ChildTemplate.DebugInfo := TSouffleDebugInfo.Create(ACtx.SourcePath); + ChildScope := TGocciaCompilerScope.Create(OldScope, 0); + ChildScope.DeclareLocal(KEYWORD_THIS, False); + ChildTemplate.ParameterCount := 0; + + ACtx.SwapState(ChildTemplate, ChildScope); + try + ACtx.CompileFunctionBody(AGetter.Body); + ChildTemplate.MaxRegisters := ChildScope.MaxSlot; + + for I := 0 to ChildScope.UpvalueCount - 1 do + ChildTemplate.AddUpvalueDescriptor( + ChildScope.GetUpvalue(I).IsLocal, + ChildScope.GetUpvalue(I).Index); + finally + ACtx.SwapState(OldTemplate, OldScope); + ChildScope.Free; + end; + + FuncIdx := OldTemplate.AddFunction(ChildTemplate); + GetterReg := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeABx(OP_CLOSURE, GetterReg, FuncIdx)); + KeyIdx := ACtx.Template.AddConstantString(AKey); + if KeyIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: property name index exceeds 255'); + if GetterReg <> ADest + 1 then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest + 1, GetterReg, 0)); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, ADest, GOCCIA_EXT_DEF_GETTER, UInt8(KeyIdx))); + ACtx.Scope.FreeRegister; +end; + +procedure CompileSetterProperty(const ACtx: TGocciaCompilationContext; + const ADest: UInt8; const AKey: string; + const ASetter: TGocciaSetterExpression); +var + OldTemplate: TSouffleFunctionTemplate; + OldScope: TGocciaCompilerScope; + ChildTemplate: TSouffleFunctionTemplate; + ChildScope: TGocciaCompilerScope; + FuncIdx: UInt16; + SetterReg: UInt8; + KeyIdx: UInt16; + I: Integer; +begin + OldTemplate := ACtx.Template; + OldScope := ACtx.Scope; + + ChildTemplate := TSouffleFunctionTemplate.Create(''); + ChildTemplate.DebugInfo := TSouffleDebugInfo.Create(ACtx.SourcePath); + ChildScope := TGocciaCompilerScope.Create(OldScope, 0); + ChildScope.DeclareLocal(KEYWORD_THIS, False); + ChildScope.DeclareLocal(ASetter.Parameter, False); + ChildTemplate.ParameterCount := 1; + + ACtx.SwapState(ChildTemplate, ChildScope); + try + ACtx.CompileFunctionBody(ASetter.Body); + ChildTemplate.MaxRegisters := ChildScope.MaxSlot; + + for I := 0 to ChildScope.UpvalueCount - 1 do + ChildTemplate.AddUpvalueDescriptor( + ChildScope.GetUpvalue(I).IsLocal, + ChildScope.GetUpvalue(I).Index); + finally + ACtx.SwapState(OldTemplate, OldScope); + ChildScope.Free; + end; + + FuncIdx := OldTemplate.AddFunction(ChildTemplate); + SetterReg := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeABx(OP_CLOSURE, SetterReg, FuncIdx)); + KeyIdx := ACtx.Template.AddConstantString(AKey); + if KeyIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: property name index exceeds 255'); + if SetterReg <> ADest + 1 then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest + 1, SetterReg, 0)); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, ADest, GOCCIA_EXT_DEF_SETTER, UInt8(KeyIdx))); + ACtx.Scope.FreeRegister; +end; + +procedure CompileObject(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaObjectExpression; const ADest: UInt8); +var + I: Integer; + Key: string; + ValExpr: TGocciaExpression; + KeyReg, ValReg: UInt8; + Names: TStringList; + Order: TArray; + Pair: TPair; + GetterExpr: TGocciaGetterExpression; + SetterExpr: TGocciaSetterExpression; +begin + EmitInstruction(ACtx, EncodeABx(OP_NEW_RECORD, ADest, AExpr.Properties.Count)); + + Order := AExpr.PropertySourceOrder; + if Length(Order) > 0 then + begin + for I := 0 to High(Order) do + begin + Key := Order[I].StaticKey; + case Order[I].PropertyType of + pstStatic: + if AExpr.Properties.TryGetValue(Key, ValExpr) then + CompileObjectProperty(ACtx, AExpr, ADest, Key, ValExpr); + pstComputed: + begin + if (Order[I].ComputedIndex >= 0) and + (Order[I].ComputedIndex <= High(AExpr.ComputedPropertiesInOrder)) then + begin + Pair := AExpr.ComputedPropertiesInOrder[Order[I].ComputedIndex]; + if Pair.Key is TGocciaSpreadExpression then + begin + ValReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(TGocciaSpreadExpression(Pair.Key).Argument, ValReg); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, ADest, GOCCIA_EXT_SPREAD_OBJ, ValReg)); + ACtx.Scope.FreeRegister; + end + else + begin + KeyReg := ACtx.Scope.AllocateRegister; + ValReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(Pair.Key, KeyReg); + ACtx.CompileExpression(Pair.Value, ValReg); + EmitInstruction(ACtx, EncodeABC(OP_RT_SET_INDEX, ADest, KeyReg, ValReg)); + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + end; + end; + end; + pstGetter: + if Assigned(AExpr.Getters) and AExpr.Getters.TryGetValue(Key, GetterExpr) then + CompileGetterProperty(ACtx, ADest, Key, GetterExpr); + pstSetter: + if Assigned(AExpr.Setters) and AExpr.Setters.TryGetValue(Key, SetterExpr) then + CompileSetterProperty(ACtx, ADest, Key, SetterExpr); + end; + end; + end + else + begin + Names := AExpr.GetPropertyNamesInOrder; + for I := 0 to Names.Count - 1 do + begin + Key := Names[I]; + if AExpr.Properties.TryGetValue(Key, ValExpr) then + CompileObjectProperty(ACtx, AExpr, ADest, Key, ValExpr); + end; + end; +end; + +procedure CompileTemplateLiteral(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaTemplateLiteralExpression; const ADest: UInt8); +var + Template: string; + I, Start, BraceCount: Integer; + ExprText, StaticPart: string; + PartReg: UInt8; + HasParts: Boolean; + Lexer: TGocciaLexer; + Parser: TGocciaParser; + ProgramNode: TGocciaProgram; + Tokens: TObjectList; + SourceLines: TStringList; + ParsedExpr: TGocciaExpression; +begin + Template := AExpr.Value; + + if Pos('${', Template) = 0 then + begin + EmitInstruction(ACtx, EncodeABx(OP_LOAD_CONST, ADest, + ACtx.Template.AddConstantString(Template))); + Exit; + end; + + HasParts := False; + I := 1; + while I <= Length(Template) do + begin + if (I < Length(Template)) and (Template[I] = '$') and (Template[I + 1] = '{') then + begin + Inc(I, 2); + Start := I; + BraceCount := 1; + while (I <= Length(Template)) and (BraceCount > 0) do + begin + if Template[I] = '{' then + Inc(BraceCount) + else if Template[I] = '}' then + Dec(BraceCount); + if BraceCount > 0 then + Inc(I); + end; + + ExprText := Trim(Copy(Template, Start, I - Start)); + Inc(I); + + if ExprText <> '' then + begin + Lexer := TGocciaLexer.Create('(' + ExprText + ');', ACtx.SourcePath); + try + Tokens := Lexer.ScanTokens; + SourceLines := TStringList.Create; + try + SourceLines.Text := ExprText; + Parser := TGocciaParser.Create(Tokens, ACtx.SourcePath, SourceLines); + try + ProgramNode := Parser.Parse; + try + ParsedExpr := nil; + if (ProgramNode.Body.Count > 0) and + (ProgramNode.Body[0] is TGocciaExpressionStatement) then + ParsedExpr := TGocciaExpressionStatement( + ProgramNode.Body[0]).Expression; + + if Assigned(ParsedExpr) then + begin + if not HasParts then + begin + ACtx.CompileExpression(ParsedExpr, ADest); + EmitInstruction(ACtx, EncodeABC(OP_RT_TO_STRING, ADest, ADest, 0)); + HasParts := True; + end + else + begin + PartReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(ParsedExpr, PartReg); + EmitInstruction(ACtx, EncodeABC(OP_RT_TO_STRING, PartReg, PartReg, 0)); + EmitInstruction(ACtx, EncodeABC(OP_RT_ADD, ADest, ADest, PartReg)); + ACtx.Scope.FreeRegister; + end; + end; + finally + ProgramNode.Free; + end; + finally + Parser.Free; + end; + finally + SourceLines.Free; + end; + finally + Lexer.Free; + end; + end; + end + else + begin + Start := I; + while (I <= Length(Template)) and + not ((I < Length(Template)) and (Template[I] = '$') and (Template[I + 1] = '{')) do + Inc(I); + + StaticPart := Copy(Template, Start, I - Start); + if StaticPart <> '' then + begin + if not HasParts then + begin + EmitInstruction(ACtx, EncodeABx(OP_LOAD_CONST, ADest, + ACtx.Template.AddConstantString(StaticPart))); + HasParts := True; + end + else + begin + PartReg := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeABx(OP_LOAD_CONST, PartReg, + ACtx.Template.AddConstantString(StaticPart))); + EmitInstruction(ACtx, EncodeABC(OP_RT_ADD, ADest, ADest, PartReg)); + ACtx.Scope.FreeRegister; + end; + end; + end; + end; + + if not HasParts then + EmitInstruction(ACtx, EncodeABx(OP_LOAD_CONST, ADest, + ACtx.Template.AddConstantString(''))); +end; + +procedure CompileTemplateWithInterpolation(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaTemplateWithInterpolationExpression; const ADest: UInt8); +var + I: Integer; + PartReg: UInt8; +begin + if AExpr.Parts.Count = 0 then + begin + EmitInstruction(ACtx, EncodeABx(OP_LOAD_CONST, ADest, + ACtx.Template.AddConstantString(''))); + Exit; + end; + + ACtx.CompileExpression(AExpr.Parts[0], ADest); + + for I := 1 to AExpr.Parts.Count - 1 do + begin + PartReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(AExpr.Parts[I], PartReg); + EmitInstruction(ACtx, EncodeABC(OP_RT_ADD, ADest, ADest, PartReg)); + ACtx.Scope.FreeRegister; + end; +end; + +procedure CompileNewExpression(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaNewExpression; const ADest: UInt8); +var + CtorReg: UInt8; + NameIdx: UInt16; + ArgCount, I: Integer; + Local: TGocciaCompilerLocal; +begin + CtorReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(AExpr.Callee, CtorReg); + + ArgCount := AExpr.Arguments.Count; + if ArgCount > High(UInt8) then + raise Exception.Create('Compiler error: too many constructor arguments (>255)'); + for I := 0 to ArgCount - 1 do + ACtx.CompileExpression(AExpr.Arguments[I], ACtx.Scope.AllocateRegister); + + EmitInstruction(ACtx, EncodeABC(OP_RT_CONSTRUCT, ADest, CtorReg, UInt8(ArgCount))); + + for I := 0 to ArgCount - 1 do + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + + for I := 0 to ACtx.Scope.LocalCount - 1 do + begin + Local := ACtx.Scope.GetLocal(I); + if Local.IsGlobalBacked then + begin + NameIdx := ACtx.Template.AddConstantString(Local.Name); + if NameIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: global name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, Local.Slot, + GOCCIA_EXT_DEFINE_GLOBAL, UInt8(NameIdx))); + end; + end; +end; + +procedure CompileComputedPropertyAssignment(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaComputedPropertyAssignmentExpression; const ADest: UInt8); +var + ObjReg, KeyReg, ValReg: UInt8; +begin + ObjReg := ACtx.Scope.AllocateRegister; + KeyReg := ACtx.Scope.AllocateRegister; + ValReg := ACtx.Scope.AllocateRegister; + + ACtx.CompileExpression(AExpr.ObjectExpr, ObjReg); + ACtx.CompileExpression(AExpr.PropertyExpression, KeyReg); + ACtx.CompileExpression(AExpr.Value, ValReg); + + EmitInstruction(ACtx, EncodeABC(OP_ARRAY_SET, ObjReg, KeyReg, ValReg)); + + if ADest <> ValReg then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, ValReg, 0)); + + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; +end; + +procedure CompileMethod(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaMethodExpression; const ADest: UInt8); +var + OldTemplate: TSouffleFunctionTemplate; + OldScope: TGocciaCompilerScope; + ChildTemplate: TSouffleFunctionTemplate; + ChildScope: TGocciaCompilerScope; + ChildCtx: TGocciaCompilationContext; + FuncIdx: UInt16; + FormalCount: Integer; + RestParamIndex: Integer; + RestReg: Integer; + I: Integer; +begin + OldTemplate := ACtx.Template; + OldScope := ACtx.Scope; + + ChildTemplate := TSouffleFunctionTemplate.Create(''); + ChildTemplate.DebugInfo := TSouffleDebugInfo.Create(ACtx.SourcePath); + ChildTemplate.IsAsync := AExpr.IsAsync; + ChildScope := TGocciaCompilerScope.Create(OldScope, 0); + + ChildScope.DeclareLocal(KEYWORD_THIS, False); + ChildTemplate.ParameterCount := Length(AExpr.Parameters); + + FormalCount := -1; + RestParamIndex := -1; + for I := 0 to High(AExpr.Parameters) do + begin + if AExpr.Parameters[I].IsRest or Assigned(AExpr.Parameters[I].DefaultValue) then + begin + if FormalCount < 0 then + FormalCount := I; + if AExpr.Parameters[I].IsRest then + RestParamIndex := I; + end; + if AExpr.Parameters[I].IsPattern then + ChildScope.DeclareLocal('__param' + IntToStr(I), False) + else + ChildScope.DeclareLocal(AExpr.Parameters[I].Name, False); + end; + for I := 0 to High(AExpr.Parameters) do + if AExpr.Parameters[I].IsPattern and Assigned(AExpr.Parameters[I].Pattern) then + CollectDestructuringBindings(AExpr.Parameters[I].Pattern, ChildScope); + + if FormalCount < 0 then + FormalCount := Length(AExpr.Parameters); + if Assigned(ACtx.FormalParameterCounts) then + ACtx.FormalParameterCounts.AddOrSetValue(ChildTemplate, FormalCount); + + ACtx.SwapState(ChildTemplate, ChildScope); + try + ChildCtx := ACtx; + ChildCtx.Template := ChildTemplate; + ChildCtx.Scope := ChildScope; + + if RestParamIndex >= 0 then + begin + RestReg := ChildScope.ResolveLocal(AExpr.Parameters[RestParamIndex].Name); + EmitInstruction(ChildCtx, + EncodeABC(OP_PACK_ARGS, UInt8(RestReg), UInt8(RestParamIndex), 0)); + end; + + EmitDefaultParameters(ChildCtx, AExpr.Parameters); + EmitDestructuringParameters(ChildCtx, AExpr.Parameters); + + ACtx.CompileFunctionBody(AExpr.Body); + + ChildTemplate.MaxRegisters := ChildScope.MaxSlot; + + for I := 0 to ChildScope.UpvalueCount - 1 do + ChildTemplate.AddUpvalueDescriptor( + ChildScope.GetUpvalue(I).IsLocal, + ChildScope.GetUpvalue(I).Index); + finally + ACtx.SwapState(OldTemplate, OldScope); + ChildScope.Free; + end; + + FuncIdx := OldTemplate.AddFunction(ChildTemplate); + EmitInstruction(ACtx, EncodeABx(OP_CLOSURE, ADest, FuncIdx)); +end; + +procedure CompileCompoundAssignment(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaCompoundAssignmentExpression; const ADest: UInt8); +var + LocalIdx, UpvalIdx: Integer; + Slot, RegVal, RegResult: UInt8; + MsgIdx: UInt16; + Op: TSouffleOpCode; +begin + Op := CompoundOpToRuntimeOp(AExpr.Operator); + + LocalIdx := ACtx.Scope.ResolveLocal(AExpr.Name); + if LocalIdx >= 0 then + begin + if ACtx.Scope.GetLocal(LocalIdx).IsConst then + begin + MsgIdx := ACtx.Template.AddConstantString( + 'Assignment to constant variable.'); + if MsgIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: error message index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, 0, + GOCCIA_EXT_THROW_TYPE_ERROR, UInt8(MsgIdx))); + Exit; + end; + if ACtx.Scope.GetLocal(LocalIdx).IsGlobalBacked then + begin + RegVal := ACtx.Scope.AllocateRegister; + RegResult := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeABx(OP_RT_GET_GLOBAL, RegResult, + ACtx.Template.AddConstantString(AExpr.Name))); + ACtx.CompileExpression(AExpr.Value, RegVal); + EmitInstruction(ACtx, EncodeABC(Op, ADest, RegResult, RegVal)); + EmitInstruction(ACtx, EncodeABx(OP_RT_SET_GLOBAL, ADest, + ACtx.Template.AddConstantString(AExpr.Name))); + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + Exit; + end; + Slot := ACtx.Scope.GetLocal(LocalIdx).Slot; + RegVal := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(AExpr.Value, RegVal); + EmitInstruction(ACtx, EncodeABC(Op, Slot, Slot, RegVal)); + if ADest <> Slot then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, Slot, 0)); + ACtx.Scope.FreeRegister; + Exit; + end; + + UpvalIdx := ACtx.Scope.ResolveUpvalue(AExpr.Name); + if UpvalIdx >= 0 then + begin + RegVal := ACtx.Scope.AllocateRegister; + RegResult := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeABx(OP_GET_UPVALUE, RegResult, UInt16(UpvalIdx))); + ACtx.CompileExpression(AExpr.Value, RegVal); + EmitInstruction(ACtx, EncodeABC(Op, RegResult, RegResult, RegVal)); + EmitInstruction(ACtx, EncodeABx(OP_SET_UPVALUE, RegResult, UInt16(UpvalIdx))); + if ADest <> RegResult then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, RegResult, 0)); + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + Exit; + end; + + RegVal := ACtx.Scope.AllocateRegister; + RegResult := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeABx(OP_RT_GET_GLOBAL, RegResult, + ACtx.Template.AddConstantString(AExpr.Name))); + ACtx.CompileExpression(AExpr.Value, RegVal); + EmitInstruction(ACtx, EncodeABC(Op, ADest, RegResult, RegVal)); + EmitInstruction(ACtx, EncodeABx(OP_RT_SET_GLOBAL, ADest, + ACtx.Template.AddConstantString(AExpr.Name))); + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; +end; + +procedure CompilePropertyCompoundAssignment(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaPropertyCompoundAssignmentExpression; const ADest: UInt8); +var + ObjReg, CurReg, ValReg: UInt8; + Op: TSouffleOpCode; + PropIdx: UInt16; +begin + Op := CompoundOpToRuntimeOp(AExpr.Operator); + ObjReg := ACtx.Scope.AllocateRegister; + CurReg := ACtx.Scope.AllocateRegister; + ValReg := ACtx.Scope.AllocateRegister; + + ACtx.CompileExpression(AExpr.ObjectExpr, ObjReg); + PropIdx := ACtx.Template.AddConstantString(AExpr.PropertyName); + if PropIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: property name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_GET_PROP, CurReg, ObjReg, + UInt8(PropIdx))); + ACtx.CompileExpression(AExpr.Value, ValReg); + EmitInstruction(ACtx, EncodeABC(Op, CurReg, CurReg, ValReg)); + EmitInstruction(ACtx, EncodeABC(OP_RT_SET_PROP, ObjReg, UInt8(PropIdx), + CurReg)); + + if ADest <> CurReg then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, CurReg, 0)); + + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; +end; + +procedure CompileComputedPropertyCompoundAssignment( + const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaComputedPropertyCompoundAssignmentExpression; + const ADest: UInt8); +var + ObjReg, KeyReg, CurReg, ValReg: UInt8; + Op: TSouffleOpCode; +begin + Op := CompoundOpToRuntimeOp(AExpr.Operator); + ObjReg := ACtx.Scope.AllocateRegister; + KeyReg := ACtx.Scope.AllocateRegister; + CurReg := ACtx.Scope.AllocateRegister; + ValReg := ACtx.Scope.AllocateRegister; + + ACtx.CompileExpression(AExpr.ObjectExpr, ObjReg); + ACtx.CompileExpression(AExpr.PropertyExpression, KeyReg); + EmitInstruction(ACtx, EncodeABC(OP_ARRAY_GET, CurReg, ObjReg, KeyReg)); + ACtx.CompileExpression(AExpr.Value, ValReg); + EmitInstruction(ACtx, EncodeABC(Op, CurReg, CurReg, ValReg)); + EmitInstruction(ACtx, EncodeABC(OP_ARRAY_SET, ObjReg, KeyReg, CurReg)); + + if ADest <> CurReg then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, CurReg, 0)); + + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; +end; + +procedure CompileIncrement(const ACtx: TGocciaCompilationContext; + const AExpr: TGocciaIncrementExpression; const ADest: UInt8); +var + LocalIdx, UpvalIdx: Integer; + Slot, RegOne, RegResult: UInt8; + MsgIdx: UInt16; + Op: TSouffleOpCode; + Ident: TGocciaIdentifierExpression; +begin + if AExpr.Operator = gttIncrement then + Op := OP_RT_ADD + else + Op := OP_RT_SUB; + + if not (AExpr.Operand is TGocciaIdentifierExpression) then + begin + EmitInstruction(ACtx, EncodeABC(OP_LOAD_NIL, ADest, 0, 0)); + Exit; + end; + + Ident := TGocciaIdentifierExpression(AExpr.Operand); + + LocalIdx := ACtx.Scope.ResolveLocal(Ident.Name); + if LocalIdx >= 0 then + begin + if ACtx.Scope.GetLocal(LocalIdx).IsConst then + begin + MsgIdx := ACtx.Template.AddConstantString( + 'Assignment to constant variable.'); + if MsgIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: error message index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, 0, + GOCCIA_EXT_THROW_TYPE_ERROR, UInt8(MsgIdx))); + Exit; + end; + if ACtx.Scope.GetLocal(LocalIdx).IsGlobalBacked then + begin + RegResult := ACtx.Scope.AllocateRegister; + RegOne := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeABx(OP_RT_GET_GLOBAL, RegResult, + ACtx.Template.AddConstantString(Ident.Name))); + EmitInstruction(ACtx, EncodeAsBx(OP_LOAD_INT, RegOne, 1)); + if not AExpr.IsPrefix then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, RegResult, 0)); + EmitInstruction(ACtx, EncodeABC(Op, RegResult, RegResult, RegOne)); + EmitInstruction(ACtx, EncodeABx(OP_RT_SET_GLOBAL, RegResult, + ACtx.Template.AddConstantString(Ident.Name))); + if AExpr.IsPrefix then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, RegResult, 0)); + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + Exit; + end; + Slot := ACtx.Scope.GetLocal(LocalIdx).Slot; + RegOne := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeAsBx(OP_LOAD_INT, RegOne, 1)); + if not AExpr.IsPrefix then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, Slot, 0)); + EmitInstruction(ACtx, EncodeABC(Op, Slot, Slot, RegOne)); + if AExpr.IsPrefix then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, Slot, 0)); + ACtx.Scope.FreeRegister; + Exit; + end; + + UpvalIdx := ACtx.Scope.ResolveUpvalue(Ident.Name); + if UpvalIdx >= 0 then + begin + RegResult := ACtx.Scope.AllocateRegister; + RegOne := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeABx(OP_GET_UPVALUE, RegResult, UInt16(UpvalIdx))); + EmitInstruction(ACtx, EncodeAsBx(OP_LOAD_INT, RegOne, 1)); + if not AExpr.IsPrefix then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, RegResult, 0)); + EmitInstruction(ACtx, EncodeABC(Op, RegResult, RegResult, RegOne)); + EmitInstruction(ACtx, EncodeABx(OP_SET_UPVALUE, RegResult, UInt16(UpvalIdx))); + if AExpr.IsPrefix then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, RegResult, 0)); + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + Exit; + end; + + RegResult := ACtx.Scope.AllocateRegister; + RegOne := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeABx(OP_RT_GET_GLOBAL, RegResult, + ACtx.Template.AddConstantString(Ident.Name))); + EmitInstruction(ACtx, EncodeAsBx(OP_LOAD_INT, RegOne, 1)); + if not AExpr.IsPrefix then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, RegResult, 0)); + EmitInstruction(ACtx, EncodeABC(Op, RegResult, RegResult, RegOne)); + EmitInstruction(ACtx, EncodeABx(OP_RT_SET_GLOBAL, RegResult, + ACtx.Template.AddConstantString(Ident.Name))); + if AExpr.IsPrefix then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, RegResult, 0)); + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; +end; + +procedure CompileThis(const ACtx: TGocciaCompilationContext; + const ADest: UInt8); +var + LocalIdx, UpvalIdx: Integer; + Slot: UInt8; +begin + LocalIdx := ACtx.Scope.ResolveLocal(KEYWORD_THIS); + if LocalIdx >= 0 then + begin + Slot := ACtx.Scope.GetLocal(LocalIdx).Slot; + if Slot <> ADest then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, Slot, 0)); + Exit; + end; + + UpvalIdx := ACtx.Scope.ResolveUpvalue(KEYWORD_THIS); + if UpvalIdx >= 0 then + begin + EmitInstruction(ACtx, EncodeABx(OP_GET_UPVALUE, ADest, UInt16(UpvalIdx))); + Exit; + end; + + EmitInstruction(ACtx, EncodeABC(OP_LOAD_NIL, ADest, 0, 0)); +end; + +procedure CompileSuperAccess(const ACtx: TGocciaCompilationContext; + const ADest: UInt8); +var + LocalIdx, UpvalIdx: Integer; + Slot: UInt8; +begin + LocalIdx := ACtx.Scope.ResolveLocal('__super__'); + if LocalIdx >= 0 then + begin + Slot := ACtx.Scope.GetLocal(LocalIdx).Slot; + if Slot <> ADest then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ADest, Slot, 0)); + Exit; + end; + + UpvalIdx := ACtx.Scope.ResolveUpvalue('__super__'); + if UpvalIdx >= 0 then + begin + EmitInstruction(ACtx, EncodeABx(OP_GET_UPVALUE, ADest, UInt16(UpvalIdx))); + Exit; + end; + + EmitInstruction(ACtx, EncodeABC(OP_LOAD_NIL, ADest, 0, 0)); +end; + +end. diff --git a/units/Goccia.Compiler.ExtOps.pas b/units/Goccia.Compiler.ExtOps.pas new file mode 100644 index 00000000..f2fa8c6e --- /dev/null +++ b/units/Goccia.Compiler.ExtOps.pas @@ -0,0 +1,25 @@ +unit Goccia.Compiler.ExtOps; + +{$I Goccia.inc} + +interface + +const + GOCCIA_EXT_SPREAD_OBJ = 1; // A=target, C=source reg + GOCCIA_EXT_OBJ_REST = 2; // A=dest, C=source reg, R[A+1]=exclusion keys + GOCCIA_EXT_FINALIZE_ENUM = 3; // A=record (overwritten), C=name constant + GOCCIA_EXT_DEF_GETTER = 4; // A=target, C=name constant, R[A+1]=closure + GOCCIA_EXT_DEF_SETTER = 5; // A=target, C=name constant, R[A+1]=closure + GOCCIA_EXT_DEF_STATIC_GETTER = 6; // A=target, C=name constant, R[A+1]=closure + GOCCIA_EXT_DEF_STATIC_SETTER = 7; // A=target, C=name constant, R[A+1]=closure + GOCCIA_EXT_REQUIRE_OBJECT = 8; // A=value (throws if null/undefined) + GOCCIA_EXT_EVAL_CLASS = 9; // A=dest, C=class index + GOCCIA_EXT_THROW_TYPE_ERROR = 10; // C=message constant + GOCCIA_EXT_SUPER_GET = 11; // A=dest, C=prop constant, R[A+1]=super blueprint + GOCCIA_EXT_SPREAD = 12; // A=target array, C=source reg + GOCCIA_EXT_REQUIRE_ITERABLE = 13; // A=value (overwritten with array) + GOCCIA_EXT_DEFINE_GLOBAL = 14; // A=value, C=name constant (create-or-update) + +implementation + +end. diff --git a/units/Goccia.Compiler.Scope.pas b/units/Goccia.Compiler.Scope.pas index eb38f2e7..059cccce 100644 --- a/units/Goccia.Compiler.Scope.pas +++ b/units/Goccia.Compiler.Scope.pas @@ -5,7 +5,9 @@ interface uses - Generics.Collections; + Generics.Collections, + + Souffle.Bytecode.Chunk; type TGocciaCompilerVariableKind = (cvkLocal, cvkUpvalue, cvkGlobal); @@ -16,11 +18,14 @@ TGocciaCompilerLocal = record Depth: Integer; IsCaptured: Boolean; IsConst: Boolean; + IsGlobalBacked: Boolean; + TypeHint: TSouffleLocalType; end; TGocciaCompilerUpvalue = record Index: UInt8; IsLocal: Boolean; + IsConst: Boolean; end; TGocciaCompilerScope = class @@ -42,7 +47,7 @@ TGocciaCompilerScope = class function ResolveLocal(const AName: string): Integer; function ResolveUpvalue(const AName: string): Integer; function AddUpvalue(const AIndex: UInt8; - const AIsLocal: Boolean): Integer; + const AIsLocal: Boolean; const AIsConst: Boolean = False): Integer; function AllocateRegister: UInt8; procedure FreeRegister; @@ -60,6 +65,9 @@ TGocciaCompilerScope = class function GetLocal(const AIndex: Integer): TGocciaCompilerLocal; function GetUpvalue(const AIndex: Integer): TGocciaCompilerUpvalue; procedure MarkCaptured(const AIndex: Integer); + procedure MarkGlobalBacked(const AIndex: Integer); + procedure SetLocalTypeHint(const AIndex: Integer; + const ATypeHint: TSouffleLocalType); end; implementation @@ -93,6 +101,7 @@ function TGocciaCompilerScope.DeclareLocal(const AName: string; FLocals[FLocalCount].Depth := FDepth; FLocals[FLocalCount].IsCaptured := False; FLocals[FLocalCount].IsConst := AIsConst; + FLocals[FLocalCount].TypeHint := sltUntyped; Result := FNextSlot; Inc(FLocalCount); Inc(FNextSlot); @@ -122,7 +131,8 @@ function TGocciaCompilerScope.ResolveUpvalue(const AName: string): Integer; if LocalIdx >= 0 then begin FParent.MarkCaptured(LocalIdx); - Exit(AddUpvalue(FParent.FLocals[LocalIdx].Slot, True)); + Exit(AddUpvalue(FParent.FLocals[LocalIdx].Slot, True, + FParent.FLocals[LocalIdx].IsConst)); end; UpvalueIdx := FParent.ResolveUpvalue(AName); @@ -130,14 +140,15 @@ function TGocciaCompilerScope.ResolveUpvalue(const AName: string): Integer; begin if UpvalueIdx > High(UInt8) then raise Exception.Create('Compiler error: upvalue index overflow (>255)'); - Exit(AddUpvalue(UInt8(UpvalueIdx), False)); + Exit(AddUpvalue(UInt8(UpvalueIdx), False, + FParent.FUpvalues[UpvalueIdx].IsConst)); end; Result := -1; end; function TGocciaCompilerScope.AddUpvalue(const AIndex: UInt8; - const AIsLocal: Boolean): Integer; + const AIsLocal: Boolean; const AIsConst: Boolean): Integer; var I: Integer; begin @@ -151,6 +162,7 @@ function TGocciaCompilerScope.AddUpvalue(const AIndex: UInt8; SetLength(FUpvalues, FUpvalueCount * 2 + 4); FUpvalues[FUpvalueCount].Index := AIndex; FUpvalues[FUpvalueCount].IsLocal := AIsLocal; + FUpvalues[FUpvalueCount].IsConst := AIsConst; Result := FUpvalueCount; Inc(FUpvalueCount); end; @@ -212,4 +224,15 @@ procedure TGocciaCompilerScope.MarkCaptured(const AIndex: Integer); FLocals[AIndex].IsCaptured := True; end; +procedure TGocciaCompilerScope.MarkGlobalBacked(const AIndex: Integer); +begin + FLocals[AIndex].IsGlobalBacked := True; +end; + +procedure TGocciaCompilerScope.SetLocalTypeHint(const AIndex: Integer; + const ATypeHint: TSouffleLocalType); +begin + FLocals[AIndex].TypeHint := ATypeHint; +end; + end. diff --git a/units/Goccia.Compiler.Statements.pas b/units/Goccia.Compiler.Statements.pas new file mode 100644 index 00000000..7a4c3434 --- /dev/null +++ b/units/Goccia.Compiler.Statements.pas @@ -0,0 +1,1126 @@ +unit Goccia.Compiler.Statements; + +{$I Goccia.inc} + +interface + +uses + Generics.Collections, + + Goccia.AST.Expressions, + Goccia.AST.Node, + Goccia.AST.Statements, + Goccia.Compiler.Context; + +procedure CompileExpressionStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaExpressionStatement); +procedure CompileVariableDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaVariableDeclaration); +procedure CompileBlockStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaBlockStatement); +procedure CompileIfStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaIfStatement); +procedure CompileReturnStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaReturnStatement); +procedure CompileThrowStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaThrowStatement); +procedure CompileTryStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaTryStatement); +procedure CompileForOfStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaForOfStatement); +procedure CompileForAwaitOfStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaForAwaitOfStatement); +procedure CompileImportDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaImportDeclaration); +procedure CompileExportDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaExportDeclaration); +procedure CompileExportVariableDeclaration( + const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaExportVariableDeclaration); +procedure CompileReExportDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaReExportDeclaration); +procedure CompileSwitchStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaSwitchStatement); +procedure CompileBreakStatement(const ACtx: TGocciaCompilationContext); +procedure CompileDestructuringDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaDestructuringDeclaration); +procedure CompileEnumDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaEnumDeclaration); + +function IsSimpleClass(const AClassDef: TGocciaClassDefinition): Boolean; +procedure CompileClassDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaClassDeclaration); +procedure CompileComplexClassDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaClassDeclaration; const APendingIndex: Integer); + +function SavePendingFinally: TObject; +procedure RestorePendingFinally(const ASaved: TObject); + +implementation + +uses + SysUtils, + + Souffle.Bytecode, + Souffle.Bytecode.Chunk, + Souffle.Bytecode.Debug, + + Goccia.Compiler.Expressions, + Goccia.Compiler.ExtOps, + Goccia.Compiler.Scope, + Goccia.Keywords.Reserved, + Goccia.Values.Primitives; + +type + TPendingFinallyEntry = record + FinallyBlock: TGocciaBlockStatement; + end; + +var + GBreakJumps: TList = nil; + GPendingFinally: TList; + GBreakFinallyBase: Integer = 0; + +procedure CompileExpressionStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaExpressionStatement); +var + Reg: UInt8; +begin + Reg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(AStmt.Expression, Reg); + ACtx.Scope.FreeRegister; +end; + +function InferLocalType(const AExpr: TGocciaExpression): TSouffleLocalType; +var + Lit: TGocciaLiteralExpression; +begin + Result := sltUntyped; + if AExpr is TGocciaLiteralExpression then + begin + Lit := TGocciaLiteralExpression(AExpr); + if Lit.Value is TGocciaNumberLiteralValue then + begin + if (Frac(TGocciaNumberLiteralValue(Lit.Value).Value) = 0) and + (Abs(TGocciaNumberLiteralValue(Lit.Value).Value) < MaxInt) then + Result := sltInteger + else + Result := sltFloat; + end + else if Lit.Value is TGocciaBooleanLiteralValue then + Result := sltBoolean + else if Lit.Value is TGocciaStringLiteralValue then + Result := sltString; + end + else if AExpr is TGocciaTemplateLiteralExpression then + Result := sltString + else if AExpr is TGocciaTemplateWithInterpolationExpression then + Result := sltString; +end; + +procedure CompileVariableDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaVariableDeclaration); +var + I, FuncCount, LocalIdx: Integer; + Info: TGocciaVariableInfo; + Slot: UInt8; + InferredTemplate: TSouffleFunctionTemplate; + TypeHint: TSouffleLocalType; +begin + for I := 0 to High(AStmt.Variables) do + begin + Info := AStmt.Variables[I]; + Slot := ACtx.Scope.DeclareLocal(Info.Name, AStmt.IsConst); + if Assigned(Info.Initializer) then + begin + TypeHint := InferLocalType(Info.Initializer); + if TypeHint <> sltUntyped then + begin + LocalIdx := ACtx.Scope.ResolveLocal(Info.Name); + if LocalIdx >= 0 then + begin + ACtx.Scope.SetLocalTypeHint(LocalIdx, TypeHint); + ACtx.Template.SetLocalType(Slot, TypeHint); + end; + end; + + FuncCount := ACtx.Template.FunctionCount; + ACtx.CompileExpression(Info.Initializer, Slot); + + if (Info.Initializer is TGocciaArrowFunctionExpression) or + (Info.Initializer is TGocciaMethodExpression) then + begin + if ACtx.Template.FunctionCount > FuncCount then + begin + InferredTemplate := ACtx.Template.GetFunction( + ACtx.Template.FunctionCount - 1); + if (InferredTemplate.Name = '') or + (InferredTemplate.Name = '') then + InferredTemplate.Name := Info.Name; + end; + end; + end + else + EmitInstruction(ACtx, EncodeABC(OP_LOAD_NIL, Slot, 0, 0)); + end; +end; + +procedure CompileBlockStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaBlockStatement); +var + I: Integer; + ClosedLocals: array[0..255] of UInt8; + ClosedCount: Integer; + Node: TGocciaASTNode; + Reg: UInt8; +begin + ACtx.Scope.BeginScope; + + for I := 0 to AStmt.Nodes.Count - 1 do + begin + Node := AStmt.Nodes[I]; + if Node is TGocciaStatement then + ACtx.CompileStatement(TGocciaStatement(Node)) + else if Node is TGocciaExpression then + begin + Reg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(TGocciaExpression(Node), Reg); + ACtx.Scope.FreeRegister; + end; + end; + + ACtx.Scope.EndScope(ClosedLocals, ClosedCount); + for I := 0 to ClosedCount - 1 do + EmitInstruction(ACtx, EncodeABC(OP_CLOSE_UPVALUE, ClosedLocals[I], 0, 0)); +end; + +procedure CompileIfStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaIfStatement); +var + CondReg: UInt8; + ElseJump, EndJump: Integer; +begin + CondReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(AStmt.Condition, CondReg); + ACtx.Scope.FreeRegister; + + ElseJump := EmitJumpInstruction(ACtx, OP_JUMP_IF_FALSE, CondReg); + ACtx.CompileStatement(AStmt.Consequent); + + if Assigned(AStmt.Alternate) then + begin + EndJump := EmitJumpInstruction(ACtx, OP_JUMP, 0); + PatchJumpTarget(ACtx, ElseJump); + ACtx.CompileStatement(AStmt.Alternate); + PatchJumpTarget(ACtx, EndJump); + end + else + PatchJumpTarget(ACtx, ElseJump); +end; + +procedure CompileReturnStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaReturnStatement); +var + Reg: UInt8; + I: Integer; + SavedFinally: TList; +begin + if Assigned(AStmt.Value) then + begin + Reg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(AStmt.Value, Reg); + + if Assigned(GPendingFinally) then + begin + SavedFinally := GPendingFinally; + GPendingFinally := nil; + for I := SavedFinally.Count - 1 downto 0 do + begin + EmitInstruction(ACtx, EncodeABC(OP_POP_HANDLER, 0, 0, 0)); + CompileBlockStatement(ACtx, SavedFinally[I].FinallyBlock); + end; + GPendingFinally := SavedFinally; + end; + + EmitInstruction(ACtx, EncodeABC(OP_RETURN, Reg, 0, 0)); + ACtx.Scope.FreeRegister; + end + else + begin + if Assigned(GPendingFinally) then + begin + SavedFinally := GPendingFinally; + GPendingFinally := nil; + for I := SavedFinally.Count - 1 downto 0 do + begin + EmitInstruction(ACtx, EncodeABC(OP_POP_HANDLER, 0, 0, 0)); + CompileBlockStatement(ACtx, SavedFinally[I].FinallyBlock); + end; + GPendingFinally := SavedFinally; + end; + + EmitInstruction(ACtx, EncodeABC(OP_RETURN_NIL, 0, 0, 0)); + end; +end; + +procedure CompileThrowStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaThrowStatement); +var + Reg: UInt8; +begin + Reg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(AStmt.Value, Reg); + EmitInstruction(ACtx, EncodeABC(OP_THROW, Reg, 0, 0)); + ACtx.Scope.FreeRegister; +end; + +procedure CompileTryStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaTryStatement); +var + CatchReg: UInt8; + HandlerJump, EndJump: Integer; + HasCatch, HasFinally: Boolean; + I: Integer; + ClosedLocals: array[0..255] of UInt8; + ClosedCount: Integer; + Entry: TPendingFinallyEntry; +begin + HasCatch := Assigned(AStmt.CatchBlock); + HasFinally := Assigned(AStmt.FinallyBlock); + + CatchReg := ACtx.Scope.AllocateRegister; + HandlerJump := EmitJumpInstruction(ACtx, OP_PUSH_HANDLER, CatchReg); + + if HasFinally then + begin + if not Assigned(GPendingFinally) then + GPendingFinally := TList.Create; + Entry.FinallyBlock := AStmt.FinallyBlock; + GPendingFinally.Add(Entry); + end; + + CompileBlockStatement(ACtx, AStmt.Block); + + EmitInstruction(ACtx, EncodeABC(OP_POP_HANDLER, 0, 0, 0)); + + if HasFinally then + GPendingFinally.Delete(GPendingFinally.Count - 1); + + if HasFinally then + CompileBlockStatement(ACtx, AStmt.FinallyBlock); + + EndJump := EmitJumpInstruction(ACtx, OP_JUMP, 0); + + PatchJumpTarget(ACtx, HandlerJump); + + if HasCatch then + begin + if HasFinally then + GPendingFinally.Add(Entry); + + if AStmt.CatchParam <> '' then + begin + ACtx.Scope.BeginScope; + ACtx.Scope.DeclareLocal(AStmt.CatchParam, False); + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ACtx.Scope.NextSlot - 1, CatchReg, 0)); + end; + + CompileBlockStatement(ACtx, AStmt.CatchBlock); + + if AStmt.CatchParam <> '' then + begin + ACtx.Scope.EndScope(ClosedLocals, ClosedCount); + for I := 0 to ClosedCount - 1 do + EmitInstruction(ACtx, EncodeABC(OP_CLOSE_UPVALUE, ClosedLocals[I], 0, 0)); + end; + + if HasFinally then + begin + GPendingFinally.Delete(GPendingFinally.Count - 1); + CompileBlockStatement(ACtx, AStmt.FinallyBlock); + end; + end + else + begin + if HasFinally then + CompileBlockStatement(ACtx, AStmt.FinallyBlock); + EmitInstruction(ACtx, EncodeABC(OP_THROW, CatchReg, 0, 0)); + end; + + PatchJumpTarget(ACtx, EndJump); + ACtx.Scope.FreeRegister; +end; + +procedure CompileForOfStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaForOfStatement); +var + IterReg, ValueReg, DoneReg: UInt8; + LoopStart, ExitJump, I: Integer; + Slot: UInt8; + ClosedLocals: array[0..255] of UInt8; + ClosedCount: Integer; + OldBreakJumps: TList; + OldBreakFinallyBase: Integer; + BreakJumps: TList; +begin + IterReg := ACtx.Scope.AllocateRegister; + ValueReg := ACtx.Scope.AllocateRegister; + DoneReg := ACtx.Scope.AllocateRegister; + + ACtx.CompileExpression(AStmt.Iterable, IterReg); + EmitInstruction(ACtx, EncodeABC(OP_RT_GET_ITER, IterReg, IterReg, 0)); + + OldBreakJumps := GBreakJumps; + OldBreakFinallyBase := GBreakFinallyBase; + BreakJumps := TList.Create; + GBreakJumps := BreakJumps; + if Assigned(GPendingFinally) then + GBreakFinallyBase := GPendingFinally.Count + else + GBreakFinallyBase := 0; + try + LoopStart := CurrentCodePosition(ACtx); + + EmitInstruction(ACtx, EncodeABC(OP_RT_ITER_NEXT, ValueReg, DoneReg, IterReg)); + ExitJump := EmitJumpInstruction(ACtx, OP_JUMP_IF_TRUE, DoneReg); + + ACtx.Scope.BeginScope; + + if Assigned(AStmt.BindingPattern) then + begin + CollectDestructuringBindings(AStmt.BindingPattern, ACtx.Scope, AStmt.IsConst); + EmitDestructuring(ACtx, AStmt.BindingPattern, ValueReg); + end + else if AStmt.BindingName <> '' then + begin + Slot := ACtx.Scope.DeclareLocal(AStmt.BindingName, AStmt.IsConst); + EmitInstruction(ACtx, EncodeABC(OP_MOVE, Slot, ValueReg, 0)); + end; + + ACtx.CompileStatement(AStmt.Body); + + ACtx.Scope.EndScope(ClosedLocals, ClosedCount); + for I := 0 to ClosedCount - 1 do + EmitInstruction(ACtx, EncodeABC(OP_CLOSE_UPVALUE, ClosedLocals[I], 0, 0)); + + EmitInstruction(ACtx, EncodeAx(OP_JUMP, LoopStart - CurrentCodePosition(ACtx) - 1)); + + PatchJumpTarget(ACtx, ExitJump); + + for I := 0 to BreakJumps.Count - 1 do + PatchJumpTarget(ACtx, BreakJumps[I]); + finally + BreakJumps.Free; + GBreakJumps := OldBreakJumps; + GBreakFinallyBase := OldBreakFinallyBase; + end; + + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; +end; + +procedure CompileForAwaitOfStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaForAwaitOfStatement); +var + IterReg, ValueReg, DoneReg: UInt8; + LoopStart, ExitJump, I: Integer; + Slot: UInt8; + ClosedLocals: array[0..255] of UInt8; + ClosedCount: Integer; + OldBreakJumps: TList; + OldBreakFinallyBase: Integer; + BreakJumps: TList; +begin + IterReg := ACtx.Scope.AllocateRegister; + ValueReg := ACtx.Scope.AllocateRegister; + DoneReg := ACtx.Scope.AllocateRegister; + + ACtx.CompileExpression(AStmt.Iterable, IterReg); + EmitInstruction(ACtx, EncodeABC(OP_RT_GET_ITER, IterReg, IterReg, 1)); + + OldBreakJumps := GBreakJumps; + OldBreakFinallyBase := GBreakFinallyBase; + BreakJumps := TList.Create; + GBreakJumps := BreakJumps; + if Assigned(GPendingFinally) then + GBreakFinallyBase := GPendingFinally.Count + else + GBreakFinallyBase := 0; + try + LoopStart := CurrentCodePosition(ACtx); + + EmitInstruction(ACtx, EncodeABC(OP_RT_ITER_NEXT, ValueReg, DoneReg, IterReg)); + ExitJump := EmitJumpInstruction(ACtx, OP_JUMP_IF_TRUE, DoneReg); + EmitInstruction(ACtx, EncodeABC(OP_RT_AWAIT, ValueReg, ValueReg, 0)); + + ACtx.Scope.BeginScope; + + if Assigned(AStmt.BindingPattern) then + begin + CollectDestructuringBindings(AStmt.BindingPattern, ACtx.Scope, AStmt.IsConst); + EmitDestructuring(ACtx, AStmt.BindingPattern, ValueReg); + end + else if AStmt.BindingName <> '' then + begin + Slot := ACtx.Scope.DeclareLocal(AStmt.BindingName, AStmt.IsConst); + EmitInstruction(ACtx, EncodeABC(OP_MOVE, Slot, ValueReg, 0)); + end; + + ACtx.CompileStatement(AStmt.Body); + + ACtx.Scope.EndScope(ClosedLocals, ClosedCount); + for I := 0 to ClosedCount - 1 do + EmitInstruction(ACtx, EncodeABC(OP_CLOSE_UPVALUE, ClosedLocals[I], 0, 0)); + + EmitInstruction(ACtx, EncodeAx(OP_JUMP, LoopStart - CurrentCodePosition(ACtx) - 1)); + + PatchJumpTarget(ACtx, ExitJump); + + for I := 0 to BreakJumps.Count - 1 do + PatchJumpTarget(ACtx, BreakJumps[I]); + finally + BreakJumps.Free; + GBreakJumps := OldBreakJumps; + GBreakFinallyBase := OldBreakFinallyBase; + end; + + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; +end; + +procedure CompileImportDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaImportDeclaration); +var + ModReg: UInt8; + PathIdx, NameIdx: UInt16; + Pair: TPair; + Slots: array of UInt8; + Names: array of string; + I, Count: Integer; +begin + Count := AStmt.Imports.Count; + SetLength(Slots, Count); + SetLength(Names, Count); + + I := 0; + for Pair in AStmt.Imports do + begin + Slots[I] := ACtx.Scope.DeclareLocal(Pair.Key, True); + Names[I] := Pair.Value; + Inc(I); + end; + + ModReg := ACtx.Scope.AllocateRegister; + PathIdx := ACtx.Template.AddConstantString(AStmt.ModulePath); + EmitInstruction(ACtx, EncodeABx(OP_RT_IMPORT, ModReg, PathIdx)); + + for I := 0 to Count - 1 do + begin + NameIdx := ACtx.Template.AddConstantString(Names[I]); + if NameIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: import name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RECORD_GET, Slots[I], ModReg, + UInt8(NameIdx))); + end; + + ACtx.Scope.FreeRegister; +end; + +procedure CompileExportDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaExportDeclaration); +var + Pair: TPair; + LocalIdx: Integer; + Reg: UInt8; + NameIdx: UInt16; +begin + for Pair in AStmt.ExportsTable do + begin + LocalIdx := ACtx.Scope.ResolveLocal(Pair.Value); + if LocalIdx >= 0 then + begin + Reg := ACtx.Scope.GetLocal(LocalIdx).Slot; + NameIdx := ACtx.Template.AddConstantString(Pair.Key); + EmitInstruction(ACtx, EncodeABx(OP_RT_EXPORT, Reg, NameIdx)); + end; + end; +end; + +procedure CompileExportVariableDeclaration( + const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaExportVariableDeclaration); +var + I: Integer; + VarInfo: TGocciaVariableInfo; + LocalIdx: Integer; + Reg: UInt8; + NameIdx: UInt16; +begin + CompileVariableDeclaration(ACtx, AStmt.Declaration); + + for I := 0 to Length(AStmt.Declaration.Variables) - 1 do + begin + VarInfo := AStmt.Declaration.Variables[I]; + LocalIdx := ACtx.Scope.ResolveLocal(VarInfo.Name); + if LocalIdx >= 0 then + begin + Reg := ACtx.Scope.GetLocal(LocalIdx).Slot; + NameIdx := ACtx.Template.AddConstantString(VarInfo.Name); + EmitInstruction(ACtx, EncodeABx(OP_RT_EXPORT, Reg, NameIdx)); + end; + end; +end; + +procedure CompileReExportDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaReExportDeclaration); +var + ModReg, ValReg: UInt8; + PathIdx, SrcNameIdx, ExportNameIdx: UInt16; + Pair: TPair; +begin + ModReg := ACtx.Scope.AllocateRegister; + ValReg := ACtx.Scope.AllocateRegister; + PathIdx := ACtx.Template.AddConstantString(AStmt.ModulePath); + EmitInstruction(ACtx, EncodeABx(OP_RT_IMPORT, ModReg, PathIdx)); + + for Pair in AStmt.ExportsTable do + begin + SrcNameIdx := ACtx.Template.AddConstantString(Pair.Value); + if SrcNameIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: re-export source name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RECORD_GET, ValReg, ModReg, + UInt8(SrcNameIdx))); + ExportNameIdx := ACtx.Template.AddConstantString(Pair.Key); + EmitInstruction(ACtx, EncodeABx(OP_RT_EXPORT, ValReg, ExportNameIdx)); + end; + + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; +end; + +procedure CompileSwitchStatement(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaSwitchStatement); +var + DiscReg, TestReg, CmpReg: UInt8; + I, J, DefaultIndex: Integer; + CaseClause: TGocciaCaseClause; + CaseBodyJumps: array of Integer; + DefaultJump, EndJump: Integer; + OldBreakJumps: TList; + OldBreakFinallyBase: Integer; + BreakJumps: TList; + Node: TGocciaASTNode; + Reg: UInt8; + ClosedLocals: array[0..255] of UInt8; + ClosedCount: Integer; +begin + DiscReg := ACtx.Scope.AllocateRegister; + TestReg := ACtx.Scope.AllocateRegister; + CmpReg := ACtx.Scope.AllocateRegister; + + ACtx.CompileExpression(AStmt.Discriminant, DiscReg); + + SetLength(CaseBodyJumps, AStmt.Cases.Count); + DefaultIndex := -1; + DefaultJump := -1; + EndJump := -1; + + for I := 0 to AStmt.Cases.Count - 1 do + begin + CaseClause := AStmt.Cases[I]; + if not Assigned(CaseClause.Test) then + begin + DefaultIndex := I; + CaseBodyJumps[I] := -1; + Continue; + end; + ACtx.CompileExpression(CaseClause.Test, TestReg); + EmitInstruction(ACtx, EncodeABC(OP_RT_EQ, CmpReg, DiscReg, TestReg)); + CaseBodyJumps[I] := EmitJumpInstruction(ACtx, OP_JUMP_IF_TRUE, CmpReg); + end; + + if DefaultIndex >= 0 then + DefaultJump := EmitJumpInstruction(ACtx, OP_JUMP, 0) + else + EndJump := EmitJumpInstruction(ACtx, OP_JUMP, 0); + + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + ACtx.Scope.FreeRegister; + + OldBreakJumps := GBreakJumps; + OldBreakFinallyBase := GBreakFinallyBase; + BreakJumps := TList.Create; + GBreakJumps := BreakJumps; + if Assigned(GPendingFinally) then + GBreakFinallyBase := GPendingFinally.Count + else + GBreakFinallyBase := 0; + try + for I := 0 to AStmt.Cases.Count - 1 do + begin + CaseClause := AStmt.Cases[I]; + + if I = DefaultIndex then + begin + if DefaultJump >= 0 then + PatchJumpTarget(ACtx, DefaultJump); + end + else + begin + if CaseBodyJumps[I] >= 0 then + PatchJumpTarget(ACtx, CaseBodyJumps[I]); + end; + + ACtx.Scope.BeginScope; + for J := 0 to CaseClause.Consequent.Count - 1 do + begin + Node := CaseClause.Consequent[J]; + if Node is TGocciaStatement then + ACtx.CompileStatement(TGocciaStatement(Node)) + else if Node is TGocciaExpression then + begin + Reg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(TGocciaExpression(Node), Reg); + ACtx.Scope.FreeRegister; + end; + end; + ACtx.Scope.EndScope(ClosedLocals, ClosedCount); + for J := 0 to ClosedCount - 1 do + EmitInstruction(ACtx, EncodeABC(OP_CLOSE_UPVALUE, ClosedLocals[J], 0, 0)); + end; + + if EndJump >= 0 then + PatchJumpTarget(ACtx, EndJump); + + for I := 0 to BreakJumps.Count - 1 do + PatchJumpTarget(ACtx, BreakJumps[I]); + finally + BreakJumps.Free; + GBreakJumps := OldBreakJumps; + GBreakFinallyBase := OldBreakFinallyBase; + end; +end; + +procedure CompileBreakStatement(const ACtx: TGocciaCompilationContext); +var + I: Integer; +begin + if not Assigned(GBreakJumps) then + Exit; + + if Assigned(GPendingFinally) and (GPendingFinally.Count > GBreakFinallyBase) then + for I := GPendingFinally.Count - 1 downto GBreakFinallyBase do + begin + EmitInstruction(ACtx, EncodeABC(OP_POP_HANDLER, 0, 0, 0)); + CompileBlockStatement(ACtx, GPendingFinally[I].FinallyBlock); + end; + + GBreakJumps.Add(EmitJumpInstruction(ACtx, OP_JUMP, 0)); +end; + +function IsSimpleClass(const AClassDef: TGocciaClassDefinition): Boolean; +begin + Result := (AClassDef.InstanceProperties.Count = 0) and + (AClassDef.PrivateInstanceProperties.Count = 0) and + (Length(AClassDef.Decorators) = 0) and + (Length(AClassDef.FElements) = 0) and + (Length(AClassDef.FComputedStaticGetters) = 0) and + (Length(AClassDef.FComputedStaticSetters) = 0) and + (Length(AClassDef.FComputedInstanceGetters) = 0) and + (Length(AClassDef.FComputedInstanceSetters) = 0); +end; + +procedure CompileMethodBody(const ACtx: TGocciaCompilationContext; + const AClassReg: UInt8; const AMethodName: string; + const AMethod: TGocciaClassMethod; const AStoreOpcode: TSouffleOpCode); +var + OldTemplate: TSouffleFunctionTemplate; + OldScope: TGocciaCompilerScope; + ChildTemplate: TSouffleFunctionTemplate; + ChildScope: TGocciaCompilerScope; + ChildCtx: TGocciaCompilationContext; + FuncIdx: UInt16; + MethodReg: UInt8; + MethodNameIdx: UInt16; + FormalCount, RestParamIndex, I: Integer; +begin + OldTemplate := ACtx.Template; + OldScope := ACtx.Scope; + + ChildTemplate := TSouffleFunctionTemplate.Create( + ''); + ChildTemplate.DebugInfo := TSouffleDebugInfo.Create(ACtx.SourcePath); + ChildTemplate.IsAsync := AMethod.IsAsync; + ChildScope := TGocciaCompilerScope.Create(OldScope, 0); + + ChildScope.DeclareLocal(KEYWORD_THIS, False); + ChildTemplate.ParameterCount := Length(AMethod.Parameters); + + FormalCount := -1; + RestParamIndex := -1; + for I := 0 to High(AMethod.Parameters) do + begin + if AMethod.Parameters[I].IsRest or + Assigned(AMethod.Parameters[I].DefaultValue) then + begin + if FormalCount < 0 then + FormalCount := I; + if AMethod.Parameters[I].IsRest then + RestParamIndex := I; + end; + ChildScope.DeclareLocal(AMethod.Parameters[I].Name, False); + end; + if FormalCount < 0 then + FormalCount := Length(AMethod.Parameters); + if Assigned(ACtx.FormalParameterCounts) then + ACtx.FormalParameterCounts.AddOrSetValue(ChildTemplate, FormalCount); + + ACtx.SwapState(ChildTemplate, ChildScope); + + ChildCtx := ACtx; + ChildCtx.Template := ChildTemplate; + ChildCtx.Scope := ChildScope; + + if RestParamIndex >= 0 then + EmitInstruction(ChildCtx, EncodeABC(OP_PACK_ARGS, + UInt8(ChildScope.ResolveLocal( + AMethod.Parameters[RestParamIndex].Name)), + UInt8(RestParamIndex), 0)); + + EmitDefaultParameters(ChildCtx, AMethod.Parameters); + + ACtx.CompileFunctionBody(AMethod.Body); + ChildTemplate.MaxRegisters := ChildScope.MaxSlot; + + for I := 0 to ChildScope.UpvalueCount - 1 do + ChildTemplate.AddUpvalueDescriptor( + ChildScope.GetUpvalue(I).IsLocal, + ChildScope.GetUpvalue(I).Index); + + ACtx.SwapState(OldTemplate, OldScope); + ChildScope.Free; + + FuncIdx := OldTemplate.AddFunction(ChildTemplate); + MethodReg := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeABx(OP_CLOSURE, MethodReg, FuncIdx)); + + MethodNameIdx := ACtx.Template.AddConstantString(AMethodName); + if MethodNameIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: method name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(AStoreOpcode, + AClassReg, UInt8(MethodNameIdx), MethodReg)); + ACtx.Scope.FreeRegister; +end; + +procedure CompileGetterBody(const ACtx: TGocciaCompilationContext; + const ATargetReg: UInt8; const AName: string; + const AGetter: TGocciaGetterExpression; const AExtOp: UInt8); +var + OldTemplate: TSouffleFunctionTemplate; + OldScope: TGocciaCompilerScope; + ChildTemplate: TSouffleFunctionTemplate; + ChildScope: TGocciaCompilerScope; + FuncIdx: UInt16; + FnReg: UInt8; + NameIdx: UInt16; + I: Integer; +begin + OldTemplate := ACtx.Template; + OldScope := ACtx.Scope; + + ChildTemplate := TSouffleFunctionTemplate.Create(''); + ChildTemplate.DebugInfo := TSouffleDebugInfo.Create(ACtx.SourcePath); + ChildTemplate.ParameterCount := 0; + ChildScope := TGocciaCompilerScope.Create(OldScope, 0); + ChildScope.DeclareLocal(KEYWORD_THIS, False); + + ACtx.SwapState(ChildTemplate, ChildScope); + ACtx.CompileFunctionBody(AGetter.Body); + ChildTemplate.MaxRegisters := ChildScope.MaxSlot; + + for I := 0 to ChildScope.UpvalueCount - 1 do + ChildTemplate.AddUpvalueDescriptor( + ChildScope.GetUpvalue(I).IsLocal, + ChildScope.GetUpvalue(I).Index); + + ACtx.SwapState(OldTemplate, OldScope); + ChildScope.Free; + + FuncIdx := OldTemplate.AddFunction(ChildTemplate); + FnReg := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeABx(OP_CLOSURE, FnReg, FuncIdx)); + + NameIdx := ACtx.Template.AddConstantString(AName); + if NameIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: getter name index exceeds 255'); + if FnReg <> ATargetReg + 1 then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ATargetReg + 1, FnReg, 0)); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, ATargetReg, AExtOp, UInt8(NameIdx))); + ACtx.Scope.FreeRegister; +end; + +procedure CompileSetterBody(const ACtx: TGocciaCompilationContext; + const ATargetReg: UInt8; const AName: string; + const ASetter: TGocciaSetterExpression; const AExtOp: UInt8); +var + OldTemplate: TSouffleFunctionTemplate; + OldScope: TGocciaCompilerScope; + ChildTemplate: TSouffleFunctionTemplate; + ChildScope: TGocciaCompilerScope; + FuncIdx: UInt16; + FnReg: UInt8; + NameIdx: UInt16; + I: Integer; +begin + OldTemplate := ACtx.Template; + OldScope := ACtx.Scope; + + ChildTemplate := TSouffleFunctionTemplate.Create(''); + ChildTemplate.DebugInfo := TSouffleDebugInfo.Create(ACtx.SourcePath); + ChildTemplate.ParameterCount := 1; + ChildScope := TGocciaCompilerScope.Create(OldScope, 0); + ChildScope.DeclareLocal(KEYWORD_THIS, False); + ChildScope.DeclareLocal(ASetter.Parameter, False); + + ACtx.SwapState(ChildTemplate, ChildScope); + ACtx.CompileFunctionBody(ASetter.Body); + ChildTemplate.MaxRegisters := ChildScope.MaxSlot; + + for I := 0 to ChildScope.UpvalueCount - 1 do + ChildTemplate.AddUpvalueDescriptor( + ChildScope.GetUpvalue(I).IsLocal, + ChildScope.GetUpvalue(I).Index); + + ACtx.SwapState(OldTemplate, OldScope); + ChildScope.Free; + + FuncIdx := OldTemplate.AddFunction(ChildTemplate); + FnReg := ACtx.Scope.AllocateRegister; + EmitInstruction(ACtx, EncodeABx(OP_CLOSURE, FnReg, FuncIdx)); + + NameIdx := ACtx.Template.AddConstantString(AName); + if NameIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: setter name index exceeds 255'); + if FnReg <> ATargetReg + 1 then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, ATargetReg + 1, FnReg, 0)); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, ATargetReg, AExtOp, UInt8(NameIdx))); + ACtx.Scope.FreeRegister; +end; + +procedure CompileClassDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaClassDeclaration); +var + ClassDef: TGocciaClassDefinition; + ClassReg, SuperReg, ValReg: UInt8; + NameIdx, KeyIdx: UInt16; + MethodPair: TPair; + GetterPair: TPair; + SetterPair: TPair; + StaticPropPair: TPair; + LocalIdx, UpvalIdx: Integer; + HasSuper: Boolean; +begin + ClassDef := AStmt.ClassDefinition; + HasSuper := ClassDef.SuperClass <> ''; + + ClassReg := ACtx.Scope.DeclareLocal(ClassDef.Name, True); + NameIdx := ACtx.Template.AddConstantString(ClassDef.Name); + EmitInstruction(ACtx, EncodeABx(OP_NEW_BLUEPRINT, ClassReg, NameIdx)); + + if HasSuper then + begin + SuperReg := ACtx.Scope.DeclareLocal('__super__', False); + + LocalIdx := ACtx.Scope.ResolveLocal(ClassDef.SuperClass); + if LocalIdx >= 0 then + EmitInstruction(ACtx, EncodeABC(OP_MOVE, SuperReg, + ACtx.Scope.GetLocal(LocalIdx).Slot, 0)) + else + begin + UpvalIdx := ACtx.Scope.ResolveUpvalue(ClassDef.SuperClass); + if UpvalIdx >= 0 then + EmitInstruction(ACtx, EncodeABx(OP_GET_UPVALUE, SuperReg, + UInt16(UpvalIdx))) + else + EmitInstruction(ACtx, EncodeABx(OP_RT_GET_GLOBAL, SuperReg, + ACtx.Template.AddConstantString(ClassDef.SuperClass))); + end; + + EmitInstruction(ACtx, EncodeABC(OP_INHERIT, ClassReg, SuperReg, 0)); + end; + + for MethodPair in ClassDef.Methods do + begin + if MethodPair.Value.IsStatic then + CompileMethodBody(ACtx, ClassReg, MethodPair.Key, + MethodPair.Value, OP_RT_SET_PROP) + else + CompileMethodBody(ACtx, ClassReg, MethodPair.Key, + MethodPair.Value, OP_RECORD_SET); + end; + + for MethodPair in ClassDef.PrivateMethods do + CompileMethodBody(ACtx, ClassReg, '#' + MethodPair.Key, + MethodPair.Value, OP_RECORD_SET); + + for GetterPair in ClassDef.Getters do + CompileGetterBody(ACtx, ClassReg, GetterPair.Key, + GetterPair.Value, GOCCIA_EXT_DEF_GETTER); + + for SetterPair in ClassDef.Setters do + CompileSetterBody(ACtx, ClassReg, SetterPair.Key, + SetterPair.Value, GOCCIA_EXT_DEF_SETTER); + + for GetterPair in ClassDef.StaticGetters do + CompileGetterBody(ACtx, ClassReg, GetterPair.Key, + GetterPair.Value, GOCCIA_EXT_DEF_STATIC_GETTER); + + for SetterPair in ClassDef.StaticSetters do + CompileSetterBody(ACtx, ClassReg, SetterPair.Key, + SetterPair.Value, GOCCIA_EXT_DEF_STATIC_SETTER); + + for StaticPropPair in ClassDef.StaticProperties do + begin + ValReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(StaticPropPair.Value, ValReg); + KeyIdx := ACtx.Template.AddConstantString(StaticPropPair.Key); + if KeyIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: static property name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_SET_PROP, ClassReg, + UInt8(KeyIdx), ValReg)); + ACtx.Scope.FreeRegister; + end; + + for StaticPropPair in ClassDef.PrivateStaticProperties do + begin + ValReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(StaticPropPair.Value, ValReg); + KeyIdx := ACtx.Template.AddConstantString('#' + StaticPropPair.Key); + if KeyIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: static property name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_SET_PROP, ClassReg, + UInt8(KeyIdx), ValReg)); + ACtx.Scope.FreeRegister; + end; +end; + +procedure CompileComplexClassDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaClassDeclaration; const APendingIndex: Integer); +var + ClassDef: TGocciaClassDefinition; + ClassReg: UInt8; + NameIdx: UInt16; + I: Integer; + Local: TGocciaCompilerLocal; +begin + ClassDef := AStmt.ClassDefinition; + + for I := 0 to ACtx.Scope.LocalCount - 1 do + begin + Local := ACtx.Scope.GetLocal(I); + if (Local.Name <> '') and (Local.Name <> '__receiver') and + (Local.Name <> KEYWORD_THIS) then + begin + ACtx.Scope.MarkGlobalBacked(I); + NameIdx := ACtx.Template.AddConstantString(Local.Name); + if NameIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: global name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, Local.Slot, + GOCCIA_EXT_DEFINE_GLOBAL, UInt8(NameIdx))); + end; + end; + + ClassReg := ACtx.Scope.DeclareLocal(ClassDef.Name, True); + if APendingIndex > High(UInt8) then + raise Exception.Create('Compiler error: pending class index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, ClassReg, + GOCCIA_EXT_EVAL_CLASS, UInt8(APendingIndex))); + + NameIdx := ACtx.Template.AddConstantString(ClassDef.Name); + if NameIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: global name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, ClassReg, + GOCCIA_EXT_DEFINE_GLOBAL, UInt8(NameIdx))); + + for I := 0 to ACtx.Scope.LocalCount - 1 do + begin + Local := ACtx.Scope.GetLocal(I); + if Local.IsGlobalBacked then + begin + NameIdx := ACtx.Template.AddConstantString(Local.Name); + if NameIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: global name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, Local.Slot, + GOCCIA_EXT_DEFINE_GLOBAL, UInt8(NameIdx))); + end; + end; +end; + +procedure CompileDestructuringDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaDestructuringDeclaration); +var + SrcReg: UInt8; +begin + CollectDestructuringBindings(AStmt.Pattern, ACtx.Scope, AStmt.IsConst); + + SrcReg := ACtx.Scope.AllocateRegister; + ACtx.CompileExpression(AStmt.Initializer, SrcReg); + EmitDestructuring(ACtx, AStmt.Pattern, SrcReg); + ACtx.Scope.FreeRegister; +end; + +procedure CompileEnumDeclaration(const ACtx: TGocciaCompilationContext; + const AStmt: TGocciaEnumDeclaration); +var + EnumSlot, InnerSlot, MemberSlot: UInt8; + I: Integer; + KeyIdx: UInt16; + ClosedLocals: array[0..255] of UInt8; + ClosedCount, J: Integer; +begin + EnumSlot := ACtx.Scope.DeclareLocal(AStmt.Name, False); + EmitInstruction(ACtx, EncodeABx(OP_NEW_RECORD, EnumSlot, + Length(AStmt.Members))); + + ACtx.Scope.BeginScope; + + InnerSlot := ACtx.Scope.DeclareLocal(AStmt.Name, False); + EmitInstruction(ACtx, EncodeABC(OP_MOVE, InnerSlot, EnumSlot, 0)); + + for I := 0 to High(AStmt.Members) do + begin + MemberSlot := ACtx.Scope.DeclareLocal(AStmt.Members[I].Name, False); + ACtx.CompileExpression(AStmt.Members[I].Initializer, MemberSlot); + KeyIdx := ACtx.Template.AddConstantString(AStmt.Members[I].Name); + if KeyIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: enum member name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RECORD_SET, EnumSlot, UInt8(KeyIdx), MemberSlot)); + end; + + KeyIdx := ACtx.Template.AddConstantString(AStmt.Name); + if KeyIdx > High(UInt8) then + raise Exception.Create('Constant pool overflow: enum name index exceeds 255'); + EmitInstruction(ACtx, EncodeABC(OP_RT_EXT, EnumSlot, + GOCCIA_EXT_FINALIZE_ENUM, UInt8(KeyIdx))); + + ACtx.Scope.EndScope(ClosedLocals, ClosedCount); + for J := 0 to ClosedCount - 1 do + EmitInstruction(ACtx, EncodeABx(OP_CLOSE_UPVALUE, ClosedLocals[J], 0)); +end; + +function SavePendingFinally: TObject; +begin + Result := TObject(GPendingFinally); + GPendingFinally := nil; +end; + +procedure RestorePendingFinally(const ASaved: TObject); +begin + GPendingFinally.Free; + GPendingFinally := TList(ASaved); +end; + +end. diff --git a/units/Goccia.Compiler.Test.pas b/units/Goccia.Compiler.Test.pas index 03bfdcd1..c76027fb 100644 --- a/units/Goccia.Compiler.Test.pas +++ b/units/Goccia.Compiler.Test.pas @@ -22,6 +22,7 @@ Goccia.GarbageCollector, Goccia.Lexer, Goccia.Parser, + Goccia.TestSetup, Goccia.Token, Goccia.Values.Primitives; diff --git a/units/Goccia.Compiler.pas b/units/Goccia.Compiler.pas index 48da62fe..8170ad7c 100644 --- a/units/Goccia.Compiler.pas +++ b/units/Goccia.Compiler.pas @@ -5,20 +5,16 @@ interface uses - Classes, Generics.Collections, - Souffle.Bytecode, Souffle.Bytecode.Chunk, - Souffle.Bytecode.Debug, Souffle.Bytecode.Module, Goccia.AST.Expressions, Goccia.AST.Node, Goccia.AST.Statements, - Goccia.Compiler.Scope, - Goccia.Token, - Goccia.Values.Primitives; + Goccia.Compiler.Context, + Goccia.Compiler.Scope; type TGocciaCompilerClassEntry = record @@ -30,49 +26,23 @@ TGocciaCompilerClassEntry = record TGocciaCompiler = class private FModule: TSouffleBytecodeModule; - FCurrentPrototype: TSouffleFunctionPrototype; + FCurrentTemplate: TSouffleFunctionTemplate; FCurrentScope: TGocciaCompilerScope; FSourcePath: string; + FFormalParameterCounts: TFormalParameterCountMap; FPendingClasses: array of TGocciaCompilerClassEntry; - - procedure CompileExpression(const AExpr: TGocciaExpression; const ADest: UInt8); - procedure CompileStatement(const AStmt: TGocciaStatement); - - procedure CompileLiteral(const AExpr: TGocciaLiteralExpression; const ADest: UInt8); - procedure CompileIdentifier(const AExpr: TGocciaIdentifierExpression; const ADest: UInt8); - procedure CompileBinary(const AExpr: TGocciaBinaryExpression; const ADest: UInt8); - procedure CompileUnary(const AExpr: TGocciaUnaryExpression; const ADest: UInt8); - procedure CompileAssignment(const AExpr: TGocciaAssignmentExpression; const ADest: UInt8); - procedure CompilePropertyAssignment(const AExpr: TGocciaPropertyAssignmentExpression; const ADest: UInt8); - procedure CompileArrowFunction(const AExpr: TGocciaArrowFunctionExpression; const ADest: UInt8); - procedure CompileCall(const AExpr: TGocciaCallExpression; const ADest: UInt8); - procedure CompileMember(const AExpr: TGocciaMemberExpression; const ADest: UInt8); - procedure CompileConditional(const AExpr: TGocciaConditionalExpression; const ADest: UInt8); - procedure CompileArray(const AExpr: TGocciaArrayExpression; const ADest: UInt8); - procedure CompileObject(const AExpr: TGocciaObjectExpression; const ADest: UInt8); - procedure CompileTemplateLiteral(const AExpr: TGocciaTemplateLiteralExpression; const ADest: UInt8); - procedure CompileTemplateWithInterpolation(const AExpr: TGocciaTemplateWithInterpolationExpression; const ADest: UInt8); - procedure CompileNewExpression(const AExpr: TGocciaNewExpression; const ADest: UInt8); - - procedure CompileExpressionStatement(const AStmt: TGocciaExpressionStatement); - procedure CompileVariableDeclaration(const AStmt: TGocciaVariableDeclaration); - procedure CompileBlockStatement(const AStmt: TGocciaBlockStatement); - procedure CompileIfStatement(const AStmt: TGocciaIfStatement); - procedure CompileReturnStatement(const AStmt: TGocciaReturnStatement); - procedure CompileThrowStatement(const AStmt: TGocciaThrowStatement); - procedure CompileTryStatement(const AStmt: TGocciaTryStatement); - procedure CompileForOfStatement(const AStmt: TGocciaForOfStatement); - procedure CompileImportDeclaration(const AStmt: TGocciaImportDeclaration); - procedure CompileExportDeclaration(const AStmt: TGocciaExportDeclaration); - - function Emit(const AInstruction: UInt32): Integer; - procedure EmitLine(const ALine, AColumn: Integer); - function EmitJump(const AOp: TSouffleOpCode; const AReg: UInt8): Integer; - procedure PatchJump(const AIndex: Integer); - function CurrentCodeCount: Integer; - - procedure CompileFunctionBody(const ABody: TGocciaASTNode); - function TokenTypeToRTOp(const ATokenType: TGocciaTokenType): TSouffleOpCode; + FPendingClassNames: TDictionary; + + function ShouldDeferClass( + const AClassDef: TGocciaClassDefinition): Boolean; + + procedure DoCompileExpression(const AExpr: TGocciaExpression; + const ADest: UInt8); + procedure DoCompileStatement(const AStmt: TGocciaStatement); + procedure DoCompileFunctionBody(const ABody: TGocciaASTNode); + procedure DoSwapState(const ATemplate: TSouffleFunctionTemplate; + const AScope: TGocciaCompilerScope); + function BuildContext: TGocciaCompilationContext; public constructor Create(const ASourcePath: string); destructor Destroy; override; @@ -80,20 +50,24 @@ TGocciaCompiler = class function Compile(const AProgram: TGocciaProgram): TSouffleBytecodeModule; function PendingClassCount: Integer; function GetPendingClass(const AIndex: Integer): TGocciaCompilerClassEntry; + property FormalParameterCounts: TFormalParameterCountMap + read FFormalParameterCounts; end; const GOCCIA_RUNTIME_TAG = 'goccia-js'; - COMPOUND_TAG_OBJECT = 0; - COMPOUND_TAG_ARRAY = 1; implementation uses SysUtils, - Goccia.Lexer, - Goccia.Parser; + Souffle.Bytecode, + Souffle.Bytecode.Debug, + + Goccia.Compiler.Expressions, + Goccia.Compiler.Statements, + Goccia.Constants.ConstructorNames; { TGocciaCompiler } @@ -101,1036 +75,289 @@ constructor TGocciaCompiler.Create(const ASourcePath: string); begin inherited Create; FSourcePath := ASourcePath; + FFormalParameterCounts := TFormalParameterCountMap.Create; + FPendingClassNames := TDictionary.Create; FModule := nil; - FCurrentPrototype := nil; + FCurrentTemplate := nil; FCurrentScope := nil; end; destructor TGocciaCompiler.Destroy; begin + FPendingClassNames.Free; + FFormalParameterCounts.Free; inherited; end; -function TGocciaCompiler.Compile( - const AProgram: TGocciaProgram): TSouffleBytecodeModule; -var - I: Integer; +function TGocciaCompiler.ShouldDeferClass( + const AClassDef: TGocciaClassDefinition): Boolean; begin - FModule := TSouffleBytecodeModule.Create(GOCCIA_RUNTIME_TAG, FSourcePath); - FCurrentPrototype := TSouffleFunctionPrototype.Create(''); - FCurrentPrototype.DebugInfo := TSouffleDebugInfo.Create(FSourcePath); - FCurrentScope := TGocciaCompilerScope.Create(nil, 0); - - try - for I := 0 to AProgram.Body.Count - 1 do - CompileStatement(AProgram.Body[I]); - - Emit(EncodeABC(OP_RETURN_NIL, 0, 0, 0)); - - FCurrentPrototype.MaxRegisters := FCurrentScope.MaxSlot; - FModule.TopLevel := FCurrentPrototype; + if not Goccia.Compiler.Statements.IsSimpleClass(AClassDef) then + Exit(True); + if (AClassDef.SuperClass <> '') and + FPendingClassNames.ContainsKey(AClassDef.SuperClass) then + Exit(True); + if (AClassDef.SuperClass = CONSTRUCTOR_ARRAY) or + (AClassDef.SuperClass = CONSTRUCTOR_MAP) or + (AClassDef.SuperClass = CONSTRUCTOR_SET) or + (AClassDef.SuperClass = CONSTRUCTOR_PROMISE) or + (AClassDef.SuperClass = CONSTRUCTOR_OBJECT) then + Exit(True); + Result := False; +end; - Result := FModule; - FModule := nil; - FCurrentPrototype := nil; - finally - FreeAndNil(FCurrentScope); - if Assigned(FModule) then - begin - FCurrentPrototype := nil; - FreeAndNil(FModule); - end; - end; +function TGocciaCompiler.BuildContext: TGocciaCompilationContext; +begin + Result.Template := FCurrentTemplate; + Result.Scope := FCurrentScope; + Result.SourcePath := FSourcePath; + Result.FormalParameterCounts := FFormalParameterCounts; + Result.CompileExpression := DoCompileExpression; + Result.CompileStatement := DoCompileStatement; + Result.CompileFunctionBody := DoCompileFunctionBody; + Result.SwapState := DoSwapState; end; -{ Expression dispatch } +procedure TGocciaCompiler.DoSwapState( + const ATemplate: TSouffleFunctionTemplate; + const AScope: TGocciaCompilerScope); +begin + FCurrentTemplate := ATemplate; + FCurrentScope := AScope; +end; -procedure TGocciaCompiler.CompileExpression(const AExpr: TGocciaExpression; - const ADest: UInt8); +procedure TGocciaCompiler.DoCompileExpression( + const AExpr: TGocciaExpression; const ADest: UInt8); +var + Ctx: TGocciaCompilationContext; begin - EmitLine(AExpr.Line, AExpr.Column); + Ctx := BuildContext; + EmitLineMapping(Ctx, AExpr.Line, AExpr.Column); if AExpr is TGocciaLiteralExpression then - CompileLiteral(TGocciaLiteralExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompileLiteral(Ctx, TGocciaLiteralExpression(AExpr), ADest) else if AExpr is TGocciaIdentifierExpression then - CompileIdentifier(TGocciaIdentifierExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompileIdentifier(Ctx, TGocciaIdentifierExpression(AExpr), ADest) else if AExpr is TGocciaBinaryExpression then - CompileBinary(TGocciaBinaryExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompileBinary(Ctx, TGocciaBinaryExpression(AExpr), ADest) else if AExpr is TGocciaUnaryExpression then - CompileUnary(TGocciaUnaryExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompileUnary(Ctx, TGocciaUnaryExpression(AExpr), ADest) + else if AExpr is TGocciaPropertyCompoundAssignmentExpression then + Goccia.Compiler.Expressions.CompilePropertyCompoundAssignment(Ctx, + TGocciaPropertyCompoundAssignmentExpression(AExpr), ADest) + else if AExpr is TGocciaComputedPropertyCompoundAssignmentExpression then + Goccia.Compiler.Expressions.CompileComputedPropertyCompoundAssignment(Ctx, + TGocciaComputedPropertyCompoundAssignmentExpression(AExpr), ADest) + else if AExpr is TGocciaCompoundAssignmentExpression then + Goccia.Compiler.Expressions.CompileCompoundAssignment(Ctx, TGocciaCompoundAssignmentExpression(AExpr), ADest) else if AExpr is TGocciaAssignmentExpression then - CompileAssignment(TGocciaAssignmentExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompileAssignment(Ctx, TGocciaAssignmentExpression(AExpr), ADest) else if AExpr is TGocciaPropertyAssignmentExpression then - CompilePropertyAssignment(TGocciaPropertyAssignmentExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompilePropertyAssignment(Ctx, TGocciaPropertyAssignmentExpression(AExpr), ADest) + else if AExpr is TGocciaComputedPropertyAssignmentExpression then + Goccia.Compiler.Expressions.CompileComputedPropertyAssignment(Ctx, TGocciaComputedPropertyAssignmentExpression(AExpr), ADest) + else if AExpr is TGocciaMethodExpression then + Goccia.Compiler.Expressions.CompileMethod(Ctx, TGocciaMethodExpression(AExpr), ADest) else if AExpr is TGocciaArrowFunctionExpression then - CompileArrowFunction(TGocciaArrowFunctionExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompileArrowFunction(Ctx, TGocciaArrowFunctionExpression(AExpr), ADest) else if AExpr is TGocciaCallExpression then - CompileCall(TGocciaCallExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompileCall(Ctx, TGocciaCallExpression(AExpr), ADest) else if AExpr is TGocciaMemberExpression then - CompileMember(TGocciaMemberExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompileMember(Ctx, TGocciaMemberExpression(AExpr), ADest) else if AExpr is TGocciaConditionalExpression then - CompileConditional(TGocciaConditionalExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompileConditional(Ctx, TGocciaConditionalExpression(AExpr), ADest) else if AExpr is TGocciaArrayExpression then - CompileArray(TGocciaArrayExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompileArray(Ctx, TGocciaArrayExpression(AExpr), ADest) else if AExpr is TGocciaObjectExpression then - CompileObject(TGocciaObjectExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompileObject(Ctx, TGocciaObjectExpression(AExpr), ADest) else if AExpr is TGocciaTemplateWithInterpolationExpression then - CompileTemplateWithInterpolation(TGocciaTemplateWithInterpolationExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompileTemplateWithInterpolation(Ctx, TGocciaTemplateWithInterpolationExpression(AExpr), ADest) else if AExpr is TGocciaTemplateLiteralExpression then - CompileTemplateLiteral(TGocciaTemplateLiteralExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompileTemplateLiteral(Ctx, TGocciaTemplateLiteralExpression(AExpr), ADest) else if AExpr is TGocciaNewExpression then - CompileNewExpression(TGocciaNewExpression(AExpr), ADest) + Goccia.Compiler.Expressions.CompileNewExpression(Ctx, TGocciaNewExpression(AExpr), ADest) + else if AExpr is TGocciaIncrementExpression then + Goccia.Compiler.Expressions.CompileIncrement(Ctx, TGocciaIncrementExpression(AExpr), ADest) + else if AExpr is TGocciaThisExpression then + Goccia.Compiler.Expressions.CompileThis(Ctx, ADest) + else if AExpr is TGocciaSuperExpression then + begin + Goccia.Compiler.Expressions.CompileSuperAccess(Ctx, ADest); + end + else if AExpr is TGocciaDestructuringAssignmentExpression then + Goccia.Compiler.Expressions.CompileDestructuringAssignment(Ctx, + TGocciaDestructuringAssignmentExpression(AExpr), ADest) + else if AExpr is TGocciaAwaitExpression then + begin + DoCompileExpression(TGocciaAwaitExpression(AExpr).Operand, ADest); + EmitInstruction(Ctx, EncodeABC(OP_RT_AWAIT, ADest, ADest, 0)); + end + else if AExpr is TGocciaHoleExpression then + EmitInstruction(Ctx, EncodeABC(OP_LOAD_NIL, ADest, 2 {GOCCIA_NIL_HOLE}, 0)) else - Emit(EncodeABC(OP_LOAD_NIL, ADest, 0, 0)); + EmitInstruction(Ctx, EncodeABC(OP_LOAD_NIL, ADest, 0, 0)); end; -{ Statement dispatch } - -procedure TGocciaCompiler.CompileStatement(const AStmt: TGocciaStatement); +procedure TGocciaCompiler.DoCompileStatement(const AStmt: TGocciaStatement); +var + Ctx: TGocciaCompilationContext; begin - EmitLine(AStmt.Line, AStmt.Column); + Ctx := BuildContext; + EmitLineMapping(Ctx, AStmt.Line, AStmt.Column); if AStmt is TGocciaExpressionStatement then - CompileExpressionStatement(TGocciaExpressionStatement(AStmt)) + Goccia.Compiler.Statements.CompileExpressionStatement(Ctx, TGocciaExpressionStatement(AStmt)) else if AStmt is TGocciaVariableDeclaration then - CompileVariableDeclaration(TGocciaVariableDeclaration(AStmt)) + Goccia.Compiler.Statements.CompileVariableDeclaration(Ctx, TGocciaVariableDeclaration(AStmt)) else if AStmt is TGocciaBlockStatement then - CompileBlockStatement(TGocciaBlockStatement(AStmt)) + Goccia.Compiler.Statements.CompileBlockStatement(Ctx, TGocciaBlockStatement(AStmt)) else if AStmt is TGocciaIfStatement then - CompileIfStatement(TGocciaIfStatement(AStmt)) + Goccia.Compiler.Statements.CompileIfStatement(Ctx, TGocciaIfStatement(AStmt)) else if AStmt is TGocciaReturnStatement then - CompileReturnStatement(TGocciaReturnStatement(AStmt)) + Goccia.Compiler.Statements.CompileReturnStatement(Ctx, TGocciaReturnStatement(AStmt)) else if AStmt is TGocciaThrowStatement then - CompileThrowStatement(TGocciaThrowStatement(AStmt)) + Goccia.Compiler.Statements.CompileThrowStatement(Ctx, TGocciaThrowStatement(AStmt)) else if AStmt is TGocciaTryStatement then - CompileTryStatement(TGocciaTryStatement(AStmt)) + Goccia.Compiler.Statements.CompileTryStatement(Ctx, TGocciaTryStatement(AStmt)) + else if AStmt is TGocciaForAwaitOfStatement then + Goccia.Compiler.Statements.CompileForAwaitOfStatement(Ctx, TGocciaForAwaitOfStatement(AStmt)) else if AStmt is TGocciaForOfStatement then - CompileForOfStatement(TGocciaForOfStatement(AStmt)) + Goccia.Compiler.Statements.CompileForOfStatement(Ctx, TGocciaForOfStatement(AStmt)) else if AStmt is TGocciaClassDeclaration then begin - SetLength(FPendingClasses, Length(FPendingClasses) + 1); - FPendingClasses[High(FPendingClasses)].ClassDeclaration := - TGocciaClassDeclaration(AStmt); - FPendingClasses[High(FPendingClasses)].Line := AStmt.Line; - FPendingClasses[High(FPendingClasses)].Column := AStmt.Column; - end - else if AStmt is TGocciaImportDeclaration then - CompileImportDeclaration(TGocciaImportDeclaration(AStmt)) - else if AStmt is TGocciaExportDeclaration then - CompileExportDeclaration(TGocciaExportDeclaration(AStmt)); -end; - -{ Expression compilation } - -procedure TGocciaCompiler.CompileLiteral( - const AExpr: TGocciaLiteralExpression; const ADest: UInt8); -var - Value: TGocciaValue; - Idx: UInt16; -begin - Value := AExpr.Value; - - if Value is TGocciaUndefinedLiteralValue then - Emit(EncodeABC(OP_LOAD_NIL, ADest, 0, 0)) - else if Value is TGocciaNullLiteralValue then - Emit(EncodeABx(OP_RT_GET_GLOBAL, ADest, - FCurrentPrototype.AddConstantString('null'))) - else if Value is TGocciaBooleanLiteralValue then - begin - if TGocciaBooleanLiteralValue(Value).Value then - Emit(EncodeABC(OP_LOAD_TRUE, ADest, 0, 0)) - else - Emit(EncodeABC(OP_LOAD_FALSE, ADest, 0, 0)); - end - else if Value is TGocciaNumberLiteralValue then - begin - if not TGocciaNumberLiteralValue(Value).IsNaN - and not TGocciaNumberLiteralValue(Value).IsInfinite - and (Frac(TGocciaNumberLiteralValue(Value).Value) = 0.0) - and (TGocciaNumberLiteralValue(Value).Value >= -32768) - and (TGocciaNumberLiteralValue(Value).Value <= 32767) then - Emit(EncodeAsBx(OP_LOAD_INT, ADest, - Int16(Trunc(TGocciaNumberLiteralValue(Value).Value)))) + if not ShouldDeferClass(TGocciaClassDeclaration(AStmt).ClassDefinition) then + Goccia.Compiler.Statements.CompileClassDeclaration(Ctx, + TGocciaClassDeclaration(AStmt)) else begin - Idx := FCurrentPrototype.AddConstantFloat( - TGocciaNumberLiteralValue(Value).Value); - Emit(EncodeABx(OP_LOAD_CONST, ADest, Idx)); + FPendingClassNames.AddOrSetValue( + TGocciaClassDeclaration(AStmt).ClassDefinition.Name, True); + SetLength(FPendingClasses, Length(FPendingClasses) + 1); + FPendingClasses[High(FPendingClasses)].ClassDeclaration := + TGocciaClassDeclaration(AStmt); + FPendingClasses[High(FPendingClasses)].Line := AStmt.Line; + FPendingClasses[High(FPendingClasses)].Column := AStmt.Column; + Goccia.Compiler.Statements.CompileComplexClassDeclaration(Ctx, + TGocciaClassDeclaration(AStmt), High(FPendingClasses)); end; end - else if Value is TGocciaStringLiteralValue then - begin - Idx := FCurrentPrototype.AddConstantString( - TGocciaStringLiteralValue(Value).Value); - Emit(EncodeABx(OP_LOAD_CONST, ADest, Idx)); - end - else - Emit(EncodeABC(OP_LOAD_NIL, ADest, 0, 0)); -end; - -procedure TGocciaCompiler.CompileIdentifier( - const AExpr: TGocciaIdentifierExpression; const ADest: UInt8); -var - LocalIdx, UpvalIdx: Integer; - Slot: UInt8; -begin - LocalIdx := FCurrentScope.ResolveLocal(AExpr.Name); - if LocalIdx >= 0 then - begin - Slot := FCurrentScope.GetLocal(LocalIdx).Slot; - if Slot <> ADest then - Emit(EncodeABC(OP_MOVE, ADest, Slot, 0)); - Exit; - end; - - UpvalIdx := FCurrentScope.ResolveUpvalue(AExpr.Name); - if UpvalIdx >= 0 then - begin - Emit(EncodeABx(OP_GET_UPVALUE, ADest, UInt16(UpvalIdx))); - Exit; - end; - - Emit(EncodeABx(OP_RT_GET_GLOBAL, ADest, - FCurrentPrototype.AddConstantString(AExpr.Name))); -end; - -procedure TGocciaCompiler.CompileBinary( - const AExpr: TGocciaBinaryExpression; const ADest: UInt8); -var - RegB, RegC: UInt8; - Op: TSouffleOpCode; - JumpIdx, JumpIdx2: Integer; -begin - if AExpr.Operator = gttAnd then - begin - CompileExpression(AExpr.Left, ADest); - JumpIdx := EmitJump(OP_JUMP_IF_FALSE, ADest); - CompileExpression(AExpr.Right, ADest); - PatchJump(JumpIdx); - Exit; - end; - - if AExpr.Operator = gttOr then - begin - CompileExpression(AExpr.Left, ADest); - JumpIdx := EmitJump(OP_JUMP_IF_TRUE, ADest); - CompileExpression(AExpr.Right, ADest); - PatchJump(JumpIdx); - Exit; - end; - - if AExpr.Operator = gttNullishCoalescing then - begin - CompileExpression(AExpr.Left, ADest); - - JumpIdx := EmitJump(OP_JUMP_IF_NOT_NIL, ADest); - JumpIdx2 := EmitJump(OP_JUMP, 0); - - PatchJump(JumpIdx); - RegB := FCurrentScope.AllocateRegister; - RegC := FCurrentScope.AllocateRegister; - Emit(EncodeABx(OP_RT_GET_GLOBAL, RegB, - FCurrentPrototype.AddConstantString('null'))); - Emit(EncodeABC(OP_RT_EQ, RegC, ADest, RegB)); - FCurrentScope.FreeRegister; - FCurrentScope.FreeRegister; - JumpIdx := EmitJump(OP_JUMP_IF_FALSE, RegC); - - PatchJump(JumpIdx2); - CompileExpression(AExpr.Right, ADest); - - PatchJump(JumpIdx); - Exit; - end; - - RegB := FCurrentScope.AllocateRegister; - RegC := FCurrentScope.AllocateRegister; - - CompileExpression(AExpr.Left, RegB); - CompileExpression(AExpr.Right, RegC); - - Op := TokenTypeToRTOp(AExpr.Operator); - Emit(EncodeABC(Op, ADest, RegB, RegC)); - - FCurrentScope.FreeRegister; - FCurrentScope.FreeRegister; -end; - -procedure TGocciaCompiler.CompileUnary( - const AExpr: TGocciaUnaryExpression; const ADest: UInt8); -var - RegB: UInt8; -begin - RegB := FCurrentScope.AllocateRegister; - CompileExpression(AExpr.Operand, RegB); - - case AExpr.Operator of - gttNot: Emit(EncodeABC(OP_RT_NOT, ADest, RegB, 0)); - gttMinus: Emit(EncodeABC(OP_RT_NEG, ADest, RegB, 0)); - gttTypeof: Emit(EncodeABC(OP_RT_TYPEOF, ADest, RegB, 0)); - gttBitwiseNot: Emit(EncodeABC(OP_RT_BNOT, ADest, RegB, 0)); - else - Emit(EncodeABC(OP_LOAD_NIL, ADest, 0, 0)); - end; - - FCurrentScope.FreeRegister; -end; - -procedure TGocciaCompiler.CompileAssignment( - const AExpr: TGocciaAssignmentExpression; const ADest: UInt8); -var - LocalIdx, UpvalIdx: Integer; - Slot: UInt8; -begin - CompileExpression(AExpr.Value, ADest); - - LocalIdx := FCurrentScope.ResolveLocal(AExpr.Name); - if LocalIdx >= 0 then - begin - Slot := FCurrentScope.GetLocal(LocalIdx).Slot; - if ADest <> Slot then - Emit(EncodeABC(OP_MOVE, Slot, ADest, 0)); - Exit; - end; - - UpvalIdx := FCurrentScope.ResolveUpvalue(AExpr.Name); - if UpvalIdx >= 0 then - begin - Emit(EncodeABx(OP_SET_UPVALUE, ADest, UInt16(UpvalIdx))); - Exit; - end; - - Emit(EncodeABx(OP_RT_SET_GLOBAL, ADest, - FCurrentPrototype.AddConstantString(AExpr.Name))); -end; - -procedure TGocciaCompiler.CompilePropertyAssignment( - const AExpr: TGocciaPropertyAssignmentExpression; const ADest: UInt8); -var - ObjReg, ValReg: UInt8; - KeyIdx: UInt16; -begin - ObjReg := FCurrentScope.AllocateRegister; - ValReg := FCurrentScope.AllocateRegister; - - CompileExpression(AExpr.ObjectExpr, ObjReg); - CompileExpression(AExpr.Value, ValReg); - - KeyIdx := FCurrentPrototype.AddConstantString(AExpr.PropertyName); - if KeyIdx > High(UInt8) then - raise Exception.Create('Constant pool overflow: property name index exceeds 255'); - Emit(EncodeABC(OP_RT_SET_PROP, ObjReg, KeyIdx, ValReg)); - - if ADest <> ValReg then - Emit(EncodeABC(OP_MOVE, ADest, ValReg, 0)); - - FCurrentScope.FreeRegister; - FCurrentScope.FreeRegister; -end; - -procedure TGocciaCompiler.CompileArrowFunction( - const AExpr: TGocciaArrowFunctionExpression; const ADest: UInt8); -var - OldProto: TSouffleFunctionPrototype; - OldScope: TGocciaCompilerScope; - ChildProto: TSouffleFunctionPrototype; - ChildScope: TGocciaCompilerScope; - FuncIdx: UInt16; - I: Integer; -begin - OldProto := FCurrentPrototype; - OldScope := FCurrentScope; - - ChildProto := TSouffleFunctionPrototype.Create(''); - ChildProto.DebugInfo := TSouffleDebugInfo.Create(FSourcePath); - ChildScope := TGocciaCompilerScope.Create(OldScope, 0); - - ChildProto.ParameterCount := Length(AExpr.Parameters); - for I := 0 to High(AExpr.Parameters) do - if not AExpr.Parameters[I].IsPattern then - ChildScope.DeclareLocal(AExpr.Parameters[I].Name, False); - - FCurrentPrototype := ChildProto; - FCurrentScope := ChildScope; - - CompileFunctionBody(AExpr.Body); - - ChildProto.MaxRegisters := ChildScope.MaxSlot; - - for I := 0 to ChildScope.UpvalueCount - 1 do - ChildProto.AddUpvalueDescriptor( - ChildScope.GetUpvalue(I).IsLocal, - ChildScope.GetUpvalue(I).Index); - - FCurrentPrototype := OldProto; - FCurrentScope := OldScope; - ChildScope.Free; - - FuncIdx := FCurrentPrototype.AddFunction(ChildProto); - Emit(EncodeABx(OP_CLOSURE, ADest, FuncIdx)); -end; - -procedure TGocciaCompiler.CompileFunctionBody(const ABody: TGocciaASTNode); + else if AStmt is TGocciaSwitchStatement then + Goccia.Compiler.Statements.CompileSwitchStatement(Ctx, TGocciaSwitchStatement(AStmt)) + else if AStmt is TGocciaBreakStatement then + Goccia.Compiler.Statements.CompileBreakStatement(Ctx) + else if AStmt is TGocciaImportDeclaration then + Goccia.Compiler.Statements.CompileImportDeclaration(Ctx, TGocciaImportDeclaration(AStmt)) + else if AStmt is TGocciaExportVariableDeclaration then + Goccia.Compiler.Statements.CompileExportVariableDeclaration(Ctx, TGocciaExportVariableDeclaration(AStmt)) + else if AStmt is TGocciaExportDeclaration then + Goccia.Compiler.Statements.CompileExportDeclaration(Ctx, TGocciaExportDeclaration(AStmt)) + else if AStmt is TGocciaReExportDeclaration then + Goccia.Compiler.Statements.CompileReExportDeclaration(Ctx, TGocciaReExportDeclaration(AStmt)) + else if AStmt is TGocciaDestructuringDeclaration then + Goccia.Compiler.Statements.CompileDestructuringDeclaration(Ctx, TGocciaDestructuringDeclaration(AStmt)) + else if AStmt is TGocciaEnumDeclaration then + Goccia.Compiler.Statements.CompileEnumDeclaration(Ctx, TGocciaEnumDeclaration(AStmt)) + else if AStmt is TGocciaExportEnumDeclaration then + Goccia.Compiler.Statements.CompileEnumDeclaration(Ctx, + TGocciaExportEnumDeclaration(AStmt).Declaration); +end; + +procedure TGocciaCompiler.DoCompileFunctionBody(const ABody: TGocciaASTNode); var Block: TGocciaBlockStatement; I: Integer; Node: TGocciaASTNode; Reg: UInt8; + SavedFinally: TObject; begin - if ABody is TGocciaBlockStatement then - begin - Block := TGocciaBlockStatement(ABody); - for I := 0 to Block.Nodes.Count - 1 do + SavedFinally := Goccia.Compiler.Statements.SavePendingFinally; + try + if ABody is TGocciaBlockStatement then begin - Node := Block.Nodes[I]; - if Node is TGocciaStatement then - CompileStatement(TGocciaStatement(Node)) - else if Node is TGocciaExpression then + Block := TGocciaBlockStatement(ABody); + for I := 0 to Block.Nodes.Count - 1 do begin - Reg := FCurrentScope.AllocateRegister; - CompileExpression(TGocciaExpression(Node), Reg); - FCurrentScope.FreeRegister; + Node := Block.Nodes[I]; + if Node is TGocciaStatement then + DoCompileStatement(TGocciaStatement(Node)) + else if Node is TGocciaExpression then + begin + Reg := FCurrentScope.AllocateRegister; + DoCompileExpression(TGocciaExpression(Node), Reg); + FCurrentScope.FreeRegister; + end; end; - end; - Emit(EncodeABC(OP_RETURN_NIL, 0, 0, 0)); - end - else if ABody is TGocciaExpression then - begin - Reg := FCurrentScope.AllocateRegister; - CompileExpression(TGocciaExpression(ABody), Reg); - Emit(EncodeABC(OP_RETURN, Reg, 0, 0)); - FCurrentScope.FreeRegister; - end - else - Emit(EncodeABC(OP_RETURN_NIL, 0, 0, 0)); -end; - -procedure TGocciaCompiler.CompileCall( - const AExpr: TGocciaCallExpression; const ADest: UInt8); -var - ArgCount, I: Integer; - BaseReg, ObjReg: UInt8; - MemberExpr: TGocciaMemberExpression; - PropIdx: UInt16; -begin - ArgCount := AExpr.Arguments.Count; - - if AExpr.Callee is TGocciaMemberExpression then - begin - MemberExpr := TGocciaMemberExpression(AExpr.Callee); - ObjReg := FCurrentScope.AllocateRegister; - BaseReg := FCurrentScope.AllocateRegister; - - CompileExpression(MemberExpr.ObjectExpr, ObjReg); - - if MemberExpr.Computed then - begin - CompileExpression(MemberExpr.PropertyExpression, BaseReg); - Emit(EncodeABC(OP_RT_GET_INDEX, BaseReg, ObjReg, BaseReg)); + EmitInstruction(BuildContext, EncodeABC(OP_RETURN_NIL, 0, 0, 0)); end - else + else if ABody is TGocciaExpression then begin - PropIdx := FCurrentPrototype.AddConstantString(MemberExpr.PropertyName); - if PropIdx > High(UInt8) then - raise Exception.Create('Constant pool overflow: property name index exceeds 255'); - Emit(EncodeABC(OP_RT_GET_PROP, BaseReg, ObjReg, UInt8(PropIdx))); - end; - - for I := 0 to ArgCount - 1 do - CompileExpression(AExpr.Arguments[I], FCurrentScope.AllocateRegister); - - Emit(EncodeABC(OP_RT_CALL_METHOD, BaseReg, UInt8(ArgCount), 0)); - - for I := 0 to ArgCount - 1 do - FCurrentScope.FreeRegister; - - if ADest <> BaseReg then - Emit(EncodeABC(OP_MOVE, ADest, BaseReg, 0)); - - FCurrentScope.FreeRegister; - FCurrentScope.FreeRegister; - end - else - begin - BaseReg := FCurrentScope.AllocateRegister; - - CompileExpression(AExpr.Callee, BaseReg); - - for I := 0 to ArgCount - 1 do - CompileExpression(AExpr.Arguments[I], FCurrentScope.AllocateRegister); - - Emit(EncodeABC(OP_RT_CALL, BaseReg, UInt8(ArgCount), 0)); - - for I := 0 to ArgCount - 1 do + Reg := FCurrentScope.AllocateRegister; + DoCompileExpression(TGocciaExpression(ABody), Reg); + EmitInstruction(BuildContext, EncodeABC(OP_RETURN, Reg, 0, 0)); FCurrentScope.FreeRegister; - - if ADest <> BaseReg then - Emit(EncodeABC(OP_MOVE, ADest, BaseReg, 0)); - - FCurrentScope.FreeRegister; - end; -end; - -procedure TGocciaCompiler.CompileMember( - const AExpr: TGocciaMemberExpression; const ADest: UInt8); -var - ObjReg: UInt8; - PropIdx: UInt16; -begin - ObjReg := FCurrentScope.AllocateRegister; - CompileExpression(AExpr.ObjectExpr, ObjReg); - - if AExpr.Computed then - begin - CompileExpression(AExpr.PropertyExpression, ADest); - Emit(EncodeABC(OP_RT_GET_INDEX, ADest, ObjReg, ADest)); - end - else - begin - PropIdx := FCurrentPrototype.AddConstantString(AExpr.PropertyName); - if PropIdx > High(UInt8) then - raise Exception.Create('Constant pool overflow: property name index exceeds 255'); - Emit(EncodeABC(OP_RT_GET_PROP, ADest, ObjReg, UInt8(PropIdx))); - end; - - FCurrentScope.FreeRegister; -end; - -procedure TGocciaCompiler.CompileConditional( - const AExpr: TGocciaConditionalExpression; const ADest: UInt8); -var - ElseJump, EndJump: Integer; -begin - CompileExpression(AExpr.Condition, ADest); - ElseJump := EmitJump(OP_JUMP_IF_FALSE, ADest); - CompileExpression(AExpr.Consequent, ADest); - EndJump := EmitJump(OP_JUMP, 0); - PatchJump(ElseJump); - CompileExpression(AExpr.Alternate, ADest); - PatchJump(EndJump); -end; - -procedure TGocciaCompiler.CompileArray( - const AExpr: TGocciaArrayExpression; const ADest: UInt8); -var - I: Integer; - ElemReg, IdxReg: UInt8; -begin - Emit(EncodeABC(OP_RT_NEW_COMPOUND, ADest, COMPOUND_TAG_ARRAY, 0)); - - for I := 0 to AExpr.Elements.Count - 1 do - begin - ElemReg := FCurrentScope.AllocateRegister; - IdxReg := FCurrentScope.AllocateRegister; - CompileExpression(AExpr.Elements[I], ElemReg); - Emit(EncodeAsBx(OP_LOAD_INT, IdxReg, Int16(I))); - Emit(EncodeABC(OP_RT_INIT_INDEX, ADest, IdxReg, ElemReg)); - FCurrentScope.FreeRegister; - FCurrentScope.FreeRegister; + end + else + EmitInstruction(BuildContext, EncodeABC(OP_RETURN_NIL, 0, 0, 0)); + finally + Goccia.Compiler.Statements.RestorePendingFinally(SavedFinally); end; end; -procedure TGocciaCompiler.CompileObject( - const AExpr: TGocciaObjectExpression; const ADest: UInt8); +function TGocciaCompiler.Compile( + const AProgram: TGocciaProgram): TSouffleBytecodeModule; var I: Integer; - Key: string; - ValExpr: TGocciaExpression; - ValReg: UInt8; - KeyIdx: UInt16; - Names: TStringList; + LastStmt: TGocciaStatement; + RetReg: UInt8; + Ctx: TGocciaCompilationContext; begin - Emit(EncodeABC(OP_RT_NEW_COMPOUND, ADest, COMPOUND_TAG_OBJECT, 0)); + FModule := TSouffleBytecodeModule.Create(GOCCIA_RUNTIME_TAG, FSourcePath); + FCurrentTemplate := TSouffleFunctionTemplate.Create(''); + FCurrentTemplate.DebugInfo := TSouffleDebugInfo.Create(FSourcePath); + FCurrentScope := TGocciaCompilerScope.Create(nil, 0); + FCurrentScope.DeclareLocal('__receiver', False); - Names := AExpr.GetPropertyNamesInOrder; try - for I := 0 to Names.Count - 1 do + if AProgram.Body.Count > 0 then begin - Key := Names[I]; - if AExpr.Properties.TryGetValue(Key, ValExpr) then - begin - ValReg := FCurrentScope.AllocateRegister; - CompileExpression(ValExpr, ValReg); - KeyIdx := FCurrentPrototype.AddConstantString(Key); - if KeyIdx > High(UInt8) then - raise Exception.Create('Constant pool overflow: property name index exceeds 255'); - Emit(EncodeABC(OP_RT_INIT_FIELD, ADest, KeyIdx, ValReg)); - FCurrentScope.FreeRegister; - end; - end; - finally - Names.Free; - end; -end; - -procedure TGocciaCompiler.CompileTemplateLiteral( - const AExpr: TGocciaTemplateLiteralExpression; const ADest: UInt8); -var - Template: string; - I, Start, BraceCount: Integer; - ExprText, StaticPart: string; - PartReg: UInt8; - HasParts: Boolean; - Lexer: TGocciaLexer; - Parser: TGocciaParser; - ProgramNode: TGocciaProgram; - Tokens: TObjectList; - SourceLines: TStringList; - ParsedExpr: TGocciaExpression; -begin - Template := AExpr.Value; + for I := 0 to AProgram.Body.Count - 2 do + DoCompileStatement(AProgram.Body[I]); - if Pos('${', Template) = 0 then - begin - Emit(EncodeABx(OP_LOAD_CONST, ADest, - FCurrentPrototype.AddConstantString(Template))); - Exit; - end; - - HasParts := False; - I := 1; - while I <= Length(Template) do - begin - if (I < Length(Template)) and (Template[I] = '$') and (Template[I + 1] = '{') then - begin - Inc(I, 2); - Start := I; - BraceCount := 1; - while (I <= Length(Template)) and (BraceCount > 0) do + LastStmt := AProgram.Body[AProgram.Body.Count - 1]; + if LastStmt is TGocciaExpressionStatement then begin - if Template[I] = '{' then - Inc(BraceCount) - else if Template[I] = '}' then - Dec(BraceCount); - if BraceCount > 0 then - Inc(I); - end; - - ExprText := Trim(Copy(Template, Start, I - Start)); - Inc(I); - - if ExprText <> '' then + RetReg := FCurrentScope.AllocateRegister; + Ctx := BuildContext; + EmitLineMapping(Ctx, LastStmt.Line, LastStmt.Column); + DoCompileExpression(TGocciaExpressionStatement(LastStmt).Expression, RetReg); + EmitInstruction(Ctx, EncodeABC(OP_RETURN, RetReg, 0, 0)); + FCurrentScope.FreeRegister; + end + else begin - Lexer := TGocciaLexer.Create(ExprText + ';', FSourcePath); - try - Tokens := Lexer.ScanTokens; - SourceLines := TStringList.Create; - try - SourceLines.Text := ExprText; - Parser := TGocciaParser.Create(Tokens, FSourcePath, SourceLines); - try - ProgramNode := Parser.Parse; - try - ParsedExpr := nil; - if (ProgramNode.Body.Count > 0) and - (ProgramNode.Body[0] is TGocciaExpressionStatement) then - ParsedExpr := TGocciaExpressionStatement( - ProgramNode.Body[0]).Expression; - - if Assigned(ParsedExpr) then - begin - if not HasParts then - begin - CompileExpression(ParsedExpr, ADest); - HasParts := True; - end - else - begin - PartReg := FCurrentScope.AllocateRegister; - CompileExpression(ParsedExpr, PartReg); - Emit(EncodeABC(OP_RT_ADD, ADest, ADest, PartReg)); - FCurrentScope.FreeRegister; - end; - end; - finally - ProgramNode.Free; - end; - finally - Parser.Free; - end; - finally - SourceLines.Free; - end; - finally - Lexer.Free; - end; + DoCompileStatement(LastStmt); + EmitInstruction(BuildContext, EncodeABC(OP_RETURN_NIL, 0, 0, 0)); end; end else - begin - Start := I; - while (I <= Length(Template)) and - not ((I < Length(Template)) and (Template[I] = '$') and (Template[I + 1] = '{')) do - Inc(I); - - StaticPart := Copy(Template, Start, I - Start); - if StaticPart <> '' then - begin - if not HasParts then - begin - Emit(EncodeABx(OP_LOAD_CONST, ADest, - FCurrentPrototype.AddConstantString(StaticPart))); - HasParts := True; - end - else - begin - PartReg := FCurrentScope.AllocateRegister; - Emit(EncodeABx(OP_LOAD_CONST, PartReg, - FCurrentPrototype.AddConstantString(StaticPart))); - Emit(EncodeABC(OP_RT_ADD, ADest, ADest, PartReg)); - FCurrentScope.FreeRegister; - end; - end; - end; - end; - - if not HasParts then - Emit(EncodeABx(OP_LOAD_CONST, ADest, - FCurrentPrototype.AddConstantString(''))); -end; - -procedure TGocciaCompiler.CompileTemplateWithInterpolation( - const AExpr: TGocciaTemplateWithInterpolationExpression; const ADest: UInt8); -var - I: Integer; - PartReg: UInt8; -begin - if AExpr.Parts.Count = 0 then - begin - Emit(EncodeABx(OP_LOAD_CONST, ADest, - FCurrentPrototype.AddConstantString(''))); - Exit; - end; + EmitInstruction(BuildContext, EncodeABC(OP_RETURN_NIL, 0, 0, 0)); - CompileExpression(AExpr.Parts[0], ADest); + FCurrentTemplate.MaxRegisters := FCurrentScope.MaxSlot; + FModule.TopLevel := FCurrentTemplate; - for I := 1 to AExpr.Parts.Count - 1 do - begin - PartReg := FCurrentScope.AllocateRegister; - CompileExpression(AExpr.Parts[I], PartReg); - Emit(EncodeABC(OP_RT_ADD, ADest, ADest, PartReg)); - FCurrentScope.FreeRegister; - end; -end; - -procedure TGocciaCompiler.CompileNewExpression( - const AExpr: TGocciaNewExpression; const ADest: UInt8); -var - CtorReg: UInt8; - ArgCount, I: Integer; -begin - CtorReg := FCurrentScope.AllocateRegister; - CompileExpression(AExpr.Callee, CtorReg); - - ArgCount := AExpr.Arguments.Count; - for I := 0 to ArgCount - 1 do - CompileExpression(AExpr.Arguments[I], FCurrentScope.AllocateRegister); - - Emit(EncodeABC(OP_RT_CONSTRUCT, ADest, CtorReg, UInt8(ArgCount))); - - for I := 0 to ArgCount - 1 do - FCurrentScope.FreeRegister; - FCurrentScope.FreeRegister; -end; - -{ Statement compilation } - -procedure TGocciaCompiler.CompileExpressionStatement( - const AStmt: TGocciaExpressionStatement); -var - Reg: UInt8; -begin - Reg := FCurrentScope.AllocateRegister; - CompileExpression(AStmt.Expression, Reg); - FCurrentScope.FreeRegister; -end; - -procedure TGocciaCompiler.CompileVariableDeclaration( - const AStmt: TGocciaVariableDeclaration); -var - I: Integer; - Info: TGocciaVariableInfo; - Slot: UInt8; -begin - for I := 0 to High(AStmt.Variables) do - begin - Info := AStmt.Variables[I]; - Slot := FCurrentScope.DeclareLocal(Info.Name, AStmt.IsConst); - if Assigned(Info.Initializer) then - CompileExpression(Info.Initializer, Slot) - else - Emit(EncodeABC(OP_LOAD_NIL, Slot, 0, 0)); - end; -end; - -procedure TGocciaCompiler.CompileBlockStatement( - const AStmt: TGocciaBlockStatement); -var - I: Integer; - ClosedLocals: array[0..255] of UInt8; - ClosedCount: Integer; - Node: TGocciaASTNode; - Reg: UInt8; -begin - FCurrentScope.BeginScope; - - for I := 0 to AStmt.Nodes.Count - 1 do - begin - Node := AStmt.Nodes[I]; - if Node is TGocciaStatement then - CompileStatement(TGocciaStatement(Node)) - else if Node is TGocciaExpression then - begin - Reg := FCurrentScope.AllocateRegister; - CompileExpression(TGocciaExpression(Node), Reg); - FCurrentScope.FreeRegister; - end; - end; - - FCurrentScope.EndScope(ClosedLocals, ClosedCount); - for I := 0 to ClosedCount - 1 do - Emit(EncodeABC(OP_CLOSE_UPVALUE, ClosedLocals[I], 0, 0)); -end; - -procedure TGocciaCompiler.CompileIfStatement( - const AStmt: TGocciaIfStatement); -var - CondReg: UInt8; - ElseJump, EndJump: Integer; -begin - CondReg := FCurrentScope.AllocateRegister; - CompileExpression(AStmt.Condition, CondReg); - FCurrentScope.FreeRegister; - - ElseJump := EmitJump(OP_JUMP_IF_FALSE, CondReg); - CompileStatement(AStmt.Consequent); - - if Assigned(AStmt.Alternate) then - begin - EndJump := EmitJump(OP_JUMP, 0); - PatchJump(ElseJump); - CompileStatement(AStmt.Alternate); - PatchJump(EndJump); - end - else - PatchJump(ElseJump); -end; - -procedure TGocciaCompiler.CompileReturnStatement( - const AStmt: TGocciaReturnStatement); -var - Reg: UInt8; -begin - if Assigned(AStmt.Value) then - begin - Reg := FCurrentScope.AllocateRegister; - CompileExpression(AStmt.Value, Reg); - Emit(EncodeABC(OP_RETURN, Reg, 0, 0)); - FCurrentScope.FreeRegister; - end - else - Emit(EncodeABC(OP_RETURN_NIL, 0, 0, 0)); -end; - -procedure TGocciaCompiler.CompileThrowStatement( - const AStmt: TGocciaThrowStatement); -var - Reg: UInt8; -begin - Reg := FCurrentScope.AllocateRegister; - CompileExpression(AStmt.Value, Reg); - Emit(EncodeABC(OP_THROW, Reg, 0, 0)); - FCurrentScope.FreeRegister; -end; - -procedure TGocciaCompiler.CompileTryStatement( - const AStmt: TGocciaTryStatement); -var - CatchReg: UInt8; - HandlerJump, EndJump, I: Integer; - ClosedLocals: array[0..255] of UInt8; - ClosedCount: Integer; -begin - CatchReg := FCurrentScope.AllocateRegister; - HandlerJump := EmitJump(OP_PUSH_HANDLER, CatchReg); - - CompileBlockStatement(AStmt.Block); - - Emit(EncodeABC(OP_POP_HANDLER, 0, 0, 0)); - EndJump := EmitJump(OP_JUMP, 0); - - PatchJump(HandlerJump); - - if AStmt.CatchParam <> '' then - begin - FCurrentScope.BeginScope; - FCurrentScope.DeclareLocal(AStmt.CatchParam, False); - Emit(EncodeABC(OP_MOVE, FCurrentScope.NextSlot - 1, CatchReg, 0)); - end; - - if Assigned(AStmt.CatchBlock) then - CompileBlockStatement(AStmt.CatchBlock); - - if AStmt.CatchParam <> '' then - begin - FCurrentScope.EndScope(ClosedLocals, ClosedCount); - for I := 0 to ClosedCount - 1 do - Emit(EncodeABC(OP_CLOSE_UPVALUE, ClosedLocals[I], 0, 0)); - end; - - PatchJump(EndJump); - - if Assigned(AStmt.FinallyBlock) then - CompileBlockStatement(AStmt.FinallyBlock); - - FCurrentScope.FreeRegister; -end; - -procedure TGocciaCompiler.CompileForOfStatement( - const AStmt: TGocciaForOfStatement); -var - IterReg, ValueReg, DoneReg: UInt8; - LoopStart, ExitJump, I: Integer; - Slot: UInt8; - ClosedLocals: array[0..255] of UInt8; - ClosedCount: Integer; -begin - IterReg := FCurrentScope.AllocateRegister; - ValueReg := FCurrentScope.AllocateRegister; - DoneReg := FCurrentScope.AllocateRegister; - - CompileExpression(AStmt.Iterable, IterReg); - Emit(EncodeABC(OP_RT_GET_ITER, IterReg, IterReg, 0)); - - LoopStart := CurrentCodeCount; - - Emit(EncodeABC(OP_RT_ITER_NEXT, ValueReg, DoneReg, IterReg)); - ExitJump := EmitJump(OP_JUMP_IF_TRUE, DoneReg); - - FCurrentScope.BeginScope; - - if AStmt.BindingName <> '' then - begin - Slot := FCurrentScope.DeclareLocal(AStmt.BindingName, AStmt.IsConst); - Emit(EncodeABC(OP_MOVE, Slot, ValueReg, 0)); - end; - - CompileStatement(AStmt.Body); - - FCurrentScope.EndScope(ClosedLocals, ClosedCount); - for I := 0 to ClosedCount - 1 do - Emit(EncodeABC(OP_CLOSE_UPVALUE, ClosedLocals[I], 0, 0)); - - Emit(EncodeAx(OP_JUMP, LoopStart - CurrentCodeCount - 1)); - - PatchJump(ExitJump); - - FCurrentScope.FreeRegister; - FCurrentScope.FreeRegister; - FCurrentScope.FreeRegister; -end; - -procedure TGocciaCompiler.CompileImportDeclaration( - const AStmt: TGocciaImportDeclaration); -var - ModReg: UInt8; - PathIdx, NameIdx: UInt16; - Pair: TPair; - Slot: UInt8; -begin - ModReg := FCurrentScope.AllocateRegister; - PathIdx := FCurrentPrototype.AddConstantString(AStmt.ModulePath); - Emit(EncodeABx(OP_RT_IMPORT, ModReg, PathIdx)); - - for Pair in AStmt.Imports do - begin - Slot := FCurrentScope.DeclareLocal(Pair.Key, True); - NameIdx := FCurrentPrototype.AddConstantString(Pair.Value); - if NameIdx > High(UInt8) then - raise Exception.Create('Constant pool overflow: import name index exceeds 255'); - Emit(EncodeABC(OP_RT_GET_PROP, Slot, ModReg, UInt8(NameIdx))); - end; - - FCurrentScope.FreeRegister; -end; - -procedure TGocciaCompiler.CompileExportDeclaration( - const AStmt: TGocciaExportDeclaration); -var - Pair: TPair; - LocalIdx: Integer; - Reg: UInt8; - NameIdx: UInt16; -begin - for Pair in AStmt.ExportsTable do - begin - LocalIdx := FCurrentScope.ResolveLocal(Pair.Value); - if LocalIdx >= 0 then + Result := FModule; + FModule := nil; + FCurrentTemplate := nil; + finally + FreeAndNil(FCurrentScope); + if Assigned(FModule) then begin - Reg := FCurrentScope.GetLocal(LocalIdx).Slot; - NameIdx := FCurrentPrototype.AddConstantString(Pair.Key); - Emit(EncodeABx(OP_RT_EXPORT, Reg, NameIdx)); + FreeAndNil(FCurrentTemplate); + FreeAndNil(FModule); end; end; end; -{ Helpers } - -function TGocciaCompiler.Emit(const AInstruction: UInt32): Integer; -begin - Result := FCurrentPrototype.EmitInstruction(AInstruction); -end; - -procedure TGocciaCompiler.EmitLine(const ALine, AColumn: Integer); -begin - if Assigned(FCurrentPrototype.DebugInfo) then - FCurrentPrototype.DebugInfo.AddLineMapping( - UInt32(FCurrentPrototype.CodeCount), UInt32(ALine), UInt16(AColumn)); -end; - -function TGocciaCompiler.EmitJump(const AOp: TSouffleOpCode; - const AReg: UInt8): Integer; -begin - if AOp = OP_JUMP then - Result := Emit(EncodeAx(AOp, 0)) - else if AOp = OP_PUSH_HANDLER then - Result := Emit(EncodeABx(AOp, AReg, 0)) - else - Result := Emit(EncodeAsBx(AOp, AReg, 0)); -end; - -procedure TGocciaCompiler.PatchJump(const AIndex: Integer); -var - Offset: Integer; - Instruction: UInt32; - Op: UInt8; - A: UInt8; -begin - Offset := CurrentCodeCount - AIndex - 1; - Instruction := FCurrentPrototype.GetInstruction(AIndex); - Op := DecodeOp(Instruction); - - if TSouffleOpCode(Op) = OP_JUMP then - FCurrentPrototype.PatchInstruction(AIndex, EncodeAx(OP_JUMP, Offset)) - else if TSouffleOpCode(Op) = OP_PUSH_HANDLER then - begin - A := DecodeA(Instruction); - FCurrentPrototype.PatchInstruction(AIndex, - EncodeABx(OP_PUSH_HANDLER, A, UInt16(Offset))); - end - else - begin - A := DecodeA(Instruction); - FCurrentPrototype.PatchInstruction(AIndex, - EncodeAsBx(TSouffleOpCode(Op), A, Int16(Offset))); - end; -end; - -function TGocciaCompiler.CurrentCodeCount: Integer; -begin - Result := FCurrentPrototype.CodeCount; -end; - function TGocciaCompiler.PendingClassCount: Integer; begin Result := Length(FPendingClasses); @@ -1142,33 +369,4 @@ function TGocciaCompiler.GetPendingClass( Result := FPendingClasses[AIndex]; end; -function TGocciaCompiler.TokenTypeToRTOp( - const ATokenType: TGocciaTokenType): TSouffleOpCode; -begin - case ATokenType of - gttPlus: Result := OP_RT_ADD; - gttMinus: Result := OP_RT_SUB; - gttStar: Result := OP_RT_MUL; - gttSlash: Result := OP_RT_DIV; - gttPercent: Result := OP_RT_MOD; - gttPower: Result := OP_RT_POW; - gttEqual: Result := OP_RT_EQ; - gttNotEqual: Result := OP_RT_NEQ; - gttLess: Result := OP_RT_LT; - gttGreater: Result := OP_RT_GT; - gttLessEqual: Result := OP_RT_LTE; - gttGreaterEqual: Result := OP_RT_GTE; - gttInstanceof: Result := OP_RT_IS_INSTANCE; - gttIn: Result := OP_RT_HAS_PROPERTY; - gttBitwiseAnd: Result := OP_RT_BAND; - gttBitwiseOr: Result := OP_RT_BOR; - gttBitwiseXor: Result := OP_RT_BXOR; - gttLeftShift: Result := OP_RT_SHL; - gttRightShift: Result := OP_RT_SHR; - gttUnsignedRightShift: Result := OP_RT_USHR; - else - Result := OP_RT_ADD; - end; -end; - end. diff --git a/units/Goccia.Engine.Backend.pas b/units/Goccia.Engine.Backend.pas index 3e89409e..18521db2 100644 --- a/units/Goccia.Engine.Backend.pas +++ b/units/Goccia.Engine.Backend.pas @@ -12,6 +12,7 @@ interface Goccia.AST.Node, Goccia.Compiler, Goccia.Engine, + Goccia.MicrotaskQueue, Goccia.Runtime.Operations, Goccia.Values.Primitives; @@ -49,15 +50,14 @@ implementation Classes, SysUtils, - Souffle.Heap, + Souffle.Bytecode.Chunk, Souffle.Value, + Souffle.VM.Exception, - Goccia.AST.Statements, - Goccia.Evaluator, - Goccia.GarbageCollector, Goccia.Interpreter, Goccia.Scope, - Goccia.Scope.BindingMap; + Goccia.Scope.BindingMap, + Goccia.Values.Error; { TGocciaSouffleBackend } @@ -99,34 +99,22 @@ function TGocciaSouffleBackend.CompileToModule( Compiler: TGocciaCompiler; I: Integer; Entry: TGocciaCompilerClassEntry; - Context: TGocciaEvaluationContext; - ClassValue: TGocciaValue; + Template: TSouffleFunctionTemplate; begin Compiler := TGocciaCompiler.Create(FSourcePath); try Result := Compiler.Compile(AProgram); - if Assigned(FEngine) and (Compiler.PendingClassCount > 0) then + for Template in Compiler.FormalParameterCounts.Keys do + FRuntime.RegisterFormalParameterCount( + Template, Compiler.FormalParameterCounts[Template]); + + for I := 0 to Compiler.PendingClassCount - 1 do begin - Context := FEngine.Interpreter.CreateEvaluationContext; - for I := 0 to Compiler.PendingClassCount - 1 do - begin - Entry := Compiler.GetPendingClass(I); - ClassValue := EvaluateClassDefinition( - Entry.ClassDeclaration.ClassDefinition, - Context, Entry.Line, Entry.Column); - if Assigned(ClassValue) then - begin - TGocciaGarbageCollector.Instance.AddTempRoot(ClassValue); - try - RegisterGlobal(Entry.ClassDeclaration.ClassDefinition.Name, ClassValue); - Context.Scope.DefineLexicalBinding( - Entry.ClassDeclaration.ClassDefinition.Name, ClassValue, dtConst); - finally - TGocciaGarbageCollector.Instance.RemoveTempRoot(ClassValue); - end; - end; - end; + Entry := Compiler.GetPendingClass(I); + FRuntime.AddPendingClassDef( + Entry.ClassDeclaration.ClassDefinition, + Entry.Line, Entry.Column); end; finally Compiler.Free; @@ -138,8 +126,21 @@ function TGocciaSouffleBackend.RunModule( var SouffleResult: TSouffleValue; begin - SouffleResult := FVM.Execute(AModule); - Result := FRuntime.UnwrapToGocciaValue(SouffleResult); + try + try + SouffleResult := FVM.Execute(AModule); + if Assigned(TGocciaMicrotaskQueue.Instance) then + TGocciaMicrotaskQueue.Instance.DrainQueue; + Result := FRuntime.UnwrapToGocciaValue(SouffleResult); + except + on E: ESouffleThrow do + raise TGocciaThrowValue.Create( + FRuntime.UnwrapToGocciaValue(E.ThrownValue)); + end; + finally + if Assigned(TGocciaMicrotaskQueue.Instance) then + TGocciaMicrotaskQueue.Instance.ClearQueue; + end; end; procedure TGocciaSouffleBackend.RegisterGlobal(const AName: string; @@ -167,11 +168,20 @@ procedure TGocciaSouffleBackend.RegisterBuiltIns( end; FRuntime.Engine := FEngine; + FRuntime.SourcePath := FSourcePath; GlobalScope := FEngine.Interpreter.GlobalScope; Names := GlobalScope.GetOwnBindingNames; for I := 0 to Length(Names) - 1 do - RegisterGlobal(Names[I], GlobalScope.GetValue(Names[I])); + begin + if GlobalScope.GetLexicalBinding(Names[I]).DeclarationType = dtConst then + FRuntime.RegisterConstGlobal(Names[I], + FRuntime.ToSouffleValue(GlobalScope.GetValue(Names[I]))) + else + RegisterGlobal(Names[I], GlobalScope.GetValue(Names[I])); + end; + + FRuntime.RegisterDelegates; end; end. diff --git a/units/Goccia.Engine.pas b/units/Goccia.Engine.pas index c4b3f261..6ce8cd09 100644 --- a/units/Goccia.Engine.pas +++ b/units/Goccia.Engine.pas @@ -94,6 +94,7 @@ TGocciaEngine = class FBuiltinBenchmark: TGocciaBenchmark; FBuiltinTemporal: TGocciaTemporalBuiltin; FBuiltinArrayBuffer: TGocciaGlobalArrayBuffer; + FPreviousExceptionMask: TFPUExceptionMask; FSuppressWarnings: Boolean; procedure PinSingletons; @@ -148,6 +149,7 @@ implementation uses Generics.Collections, + Math, SysUtils, TypInfo, @@ -184,6 +186,8 @@ constructor TGocciaEngine.Create(const AFileName: string; const ASourceLines: TS constructor TGocciaEngine.Create(const AFileName: string; const ASourceLines: TStringList; const AGlobals: TGocciaGlobalBuiltins; const AResolver: TGocciaModuleResolver); begin + FPreviousExceptionMask := GetExceptionMask; + SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision]); FFileName := AFileName; FSourceLines := ASourceLines; FGlobals := AGlobals; @@ -215,29 +219,33 @@ constructor TGocciaEngine.Create(const AFileName: string; const ASourceLines: TS destructor TGocciaEngine.Destroy; begin - if Assigned(TGocciaGarbageCollector.Instance) and Assigned(FInterpreter) then - TGocciaGarbageCollector.Instance.RemoveRoot(FInterpreter.GlobalScope); - - FBuiltinConsole.Free; - FBuiltinMath.Free; - FBuiltinGlobalObject.Free; - FBuiltinGlobalArray.Free; - FBuiltinGlobalNumber.Free; - FBuiltinGlobalString.Free; - FBuiltinGlobals.Free; - FBuiltinJSON.Free; - FBuiltinSymbol.Free; - FBuiltinSet.Free; - FBuiltinMap.Free; - FBuiltinPromise.Free; - FBuiltinTestAssertions.Free; - FBuiltinBenchmark.Free; - FBuiltinTemporal.Free; - FBuiltinArrayBuffer.Free; - - FInterpreter.Free; - if FOwnsResolver then - FResolver.Free; + try + if Assigned(TGocciaGarbageCollector.Instance) and Assigned(FInterpreter) then + TGocciaGarbageCollector.Instance.RemoveRoot(FInterpreter.GlobalScope); + + FBuiltinConsole.Free; + FBuiltinMath.Free; + FBuiltinGlobalObject.Free; + FBuiltinGlobalArray.Free; + FBuiltinGlobalNumber.Free; + FBuiltinGlobalString.Free; + FBuiltinGlobals.Free; + FBuiltinJSON.Free; + FBuiltinSymbol.Free; + FBuiltinSet.Free; + FBuiltinMap.Free; + FBuiltinPromise.Free; + FBuiltinTestAssertions.Free; + FBuiltinBenchmark.Free; + FBuiltinTemporal.Free; + FBuiltinArrayBuffer.Free; + + FInterpreter.Free; + if FOwnsResolver then + FResolver.Free; + finally + SetExceptionMask(FPreviousExceptionMask); + end; inherited; end; diff --git a/units/Goccia.Evaluator.Bitwise.pas b/units/Goccia.Evaluator.Bitwise.pas index a63b0bb7..54c67c9d 100644 --- a/units/Goccia.Evaluator.Bitwise.pas +++ b/units/Goccia.Evaluator.Bitwise.pas @@ -40,9 +40,12 @@ function EvaluateLeftShift(const ALeft, ARight: TGocciaValue): TGocciaValue; Result := TGocciaNumberLiteralValue.Create(Trunc(ALeft.ToNumberLiteral.Value) shl (Trunc(ARight.ToNumberLiteral.Value) and 31)); end; +// ES2026 §13.15.3 ShiftExpression : ShiftExpression >> AdditiveExpression function EvaluateRightShift(const ALeft, ARight: TGocciaValue): TGocciaValue; begin - Result := TGocciaNumberLiteralValue.Create(Trunc(ALeft.ToNumberLiteral.Value) shr (Trunc(ARight.ToNumberLiteral.Value) and 31)); + Result := TGocciaNumberLiteralValue.Create( + SarLongint(Int32(Trunc(ALeft.ToNumberLiteral.Value)), + Trunc(ARight.ToNumberLiteral.Value) and 31)); end; function EvaluateUnsignedRightShift(const ALeft, ARight: TGocciaValue): TGocciaValue; diff --git a/units/Goccia.Evaluator.Comparison.pas b/units/Goccia.Evaluator.Comparison.pas index 9454bb66..5eae35cd 100644 --- a/units/Goccia.Evaluator.Comparison.pas +++ b/units/Goccia.Evaluator.Comparison.pas @@ -203,11 +203,19 @@ function IsDeepEqual(const AActual, AExpected: TGocciaValue): Boolean; Exit; end; - // Type mismatch + // Type mismatch — allow TGocciaObjectValue/TGocciaInstanceValue interop if AActual.TypeName <> AExpected.TypeName then begin - Result := False; - Exit; + if AActual.IsCallable or AExpected.IsCallable then + begin + Result := False; + Exit; + end; + if not ((AActual is TGocciaObjectValue) and (AExpected is TGocciaObjectValue)) then + begin + Result := False; + Exit; + end; end; // Handle arrays diff --git a/units/Goccia.Evaluator.pas b/units/Goccia.Evaluator.pas index 3d0105a0..ca13029a 100644 --- a/units/Goccia.Evaluator.pas +++ b/units/Goccia.Evaluator.pas @@ -957,6 +957,8 @@ function EvaluateCall(const ACallExpression: TGocciaCallExpression; const AConte Result := TGocciaBoundFunctionValue(Callee).Call(Arguments, ThisValue) else if Callee is TGocciaClassValue then Result := TGocciaClassValue(Callee).Call(Arguments, ThisValue) + else if Callee is TGocciaFunctionBase then + Result := TGocciaFunctionBase(Callee).Call(Arguments, ThisValue) else begin ThrowTypeError(Format('%s is not a function', [Callee.TypeName])); diff --git a/units/Goccia.GarbageCollector.pas b/units/Goccia.GarbageCollector.pas index f1726123..43edce33 100644 --- a/units/Goccia.GarbageCollector.pas +++ b/units/Goccia.GarbageCollector.pas @@ -11,6 +11,8 @@ interface Goccia.Values.Primitives; type + TGocciaGCExternalRootMarker = procedure of object; + TGocciaGarbageCollector = class private class var FInstance: TGocciaGarbageCollector; @@ -29,6 +31,8 @@ TGocciaGarbageCollector = class FTotalCollected: Int64; FTotalCollections: Integer; + FExternalRootMarker: TGocciaGCExternalRootMarker; + procedure MarkPhase; procedure SweepPhase; function GetManagedValueCount: Integer; @@ -53,6 +57,9 @@ TGocciaGarbageCollector = class procedure RemoveTempRoot(const AValue: TGocciaValue); function IsTempRoot(const AValue: TGocciaValue): Boolean; + procedure SetExternalRootMarker( + const AMarker: TGocciaGCExternalRootMarker); + procedure Collect; procedure CollectIfNeeded; @@ -104,6 +111,7 @@ constructor TGocciaGarbageCollector.Create; FCollecting := False; FTotalCollected := 0; FTotalCollections := 0; + FExternalRootMarker := nil; end; destructor TGocciaGarbageCollector.Destroy; @@ -180,6 +188,12 @@ function TGocciaGarbageCollector.IsTempRoot(const AValue: TGocciaValue): Boolean Result := Assigned(AValue) and FTempRoots.ContainsKey(AValue); end; +procedure TGocciaGarbageCollector.SetExternalRootMarker( + const AMarker: TGocciaGCExternalRootMarker); +begin + FExternalRootMarker := AMarker; +end; + procedure TGocciaGarbageCollector.MarkPhase; var I: Integer; @@ -207,6 +221,10 @@ procedure TGocciaGarbageCollector.MarkPhase; // Mark temporary roots (values held by Pascal code outside the scope chain) for Value in FTempRoots.Keys do Value.MarkReferences; + + // Mark roots held by external systems (e.g. Souffle VM wrapped values) + if Assigned(FExternalRootMarker) then + FExternalRootMarker(); end; procedure TGocciaGarbageCollector.SweepPhase; diff --git a/units/Goccia.Runtime.Operations.pas b/units/Goccia.Runtime.Operations.pas index bcd8dd8b..dff4bc0b 100644 --- a/units/Goccia.Runtime.Operations.pas +++ b/units/Goccia.Runtime.Operations.pas @@ -5,15 +5,22 @@ interface uses + Classes, Generics.Collections, + Souffle.Bytecode.Chunk, Souffle.Heap, Souffle.Value, Souffle.VM, Souffle.VM.Closure, Souffle.VM.RuntimeOperations, - Goccia.Values.Primitives; + Goccia.AST.Statements, + Goccia.Values.Error, + Goccia.Values.ObjectPropertyDescriptor, + Goccia.Values.ObjectValue, + Goccia.Values.Primitives, + Goccia.Values.SymbolValue; const GOCCIA_HEAP_WRAPPED = 128; @@ -21,6 +28,12 @@ interface type TGocciaRuntimeOperations = class; + TGocciaPendingClassEntry = record + ClassDefinition: TGocciaClassDefinition; + Line: Integer; + Column: Integer; + end; + TGocciaWrappedValue = class(TSouffleHeapObject) private FValue: TGocciaValue; @@ -31,14 +44,73 @@ TGocciaWrappedValue = class(TSouffleHeapObject) property Value: TGocciaValue read FValue; end; + TGocciaSouffleProxy = class(TGocciaObjectValue) + private + FTarget: TSouffleHeapObject; + FRuntime: TGocciaRuntimeOperations; + FPrototypeResolved: Boolean; + public + constructor Create(const ATarget: TSouffleHeapObject; + const ARuntime: TGocciaRuntimeOperations); + + function GetProperty(const AName: string): TGocciaValue; override; + procedure SetProperty(const AName: string; + const AValue: TGocciaValue); override; + function TypeName: string; override; + function TypeOf: string; override; + function IsPrimitive: Boolean; override; + function ToBooleanLiteral: TGocciaBooleanLiteralValue; override; + function ToNumberLiteral: TGocciaNumberLiteralValue; override; + function ToStringLiteral: TGocciaStringLiteralValue; override; + procedure MarkReferences; override; + + function HasOwnProperty(const AName: string): Boolean; override; + function GetOwnPropertyDescriptor(const AName: string): TGocciaPropertyDescriptor; override; + function DeleteProperty(const AName: string): Boolean; override; + procedure DefineProperty(const AName: string; const ADescriptor: TGocciaPropertyDescriptor); override; + function GetEnumerablePropertyNames: TArray; override; + function GetEnumerablePropertyValues: TArray; override; + function GetEnumerablePropertyEntries: TArray>; override; + function GetOwnPropertyNames: TArray; override; + function GetOwnPropertyKeys: TArray; override; + function GetAllPropertyNames: TArray; override; + function GetSymbolProperty(const ASymbol: TGocciaSymbolValue): TGocciaValue; override; + function HasSymbolProperty(const ASymbol: TGocciaSymbolValue): Boolean; override; + procedure Freeze; override; + function IsFrozen: Boolean; override; + procedure Seal; override; + function IsSealed: Boolean; override; + procedure PreventExtensions; override; + function IsExtensible: Boolean; override; + + property Target: TSouffleHeapObject read FTarget; + end; + TGocciaRuntimeOperations = class(TSouffleRuntimeOperations) private FGlobals: TDictionary; + FConstGlobals: TDictionary; FExports: TDictionary; + FClosureBridgeCache: TDictionary; + FArrayBridgeCache: TDictionary; + FArrayBridgeReverse: TDictionary; + FRecordBridgeCache: TDictionary; + FFormalParameterCounts: TDictionary; + FPendingClasses: array of TGocciaPendingClassEntry; + FPendingClassCount: Integer; + FClassDefinitionScopes: TDictionary; + FWrappedValues: TList; + FBridgeCallDepth: Integer; FVM: TSouffleVM; FEngine: TObject; + FSourcePath: string; function WrapGocciaValue(const AValue: TGocciaValue): TSouffleValue; + function CoerceKeyToString(const AKey: TSouffleValue): string; + function CoerceToNumber(const A: TSouffleValue): Double; + function CoerceToString(const A: TSouffleValue): string; + procedure RethrowAsVM(const E: TGocciaThrowValue); + procedure MarkWrappedGocciaValues; public constructor Create; destructor Destroy; override; @@ -71,12 +143,7 @@ TGocciaRuntimeOperations = class(TSouffleRuntimeOperations) function IsInstance(const A, B: TSouffleValue): TSouffleValue; override; function HasProperty(const AObject, AKey: TSouffleValue): TSouffleValue; override; function ToBoolean(const A: TSouffleValue): TSouffleValue; override; - - function CreateCompound(const ATypeTag: UInt8): TSouffleValue; override; - procedure InitField(const ACompound: TSouffleValue; const AKey: string; - const AValue: TSouffleValue); override; - procedure InitIndex(const ACompound: TSouffleValue; const AIndex: TSouffleValue; - const AValue: TSouffleValue); override; + function ToPrimitive(const A: TSouffleValue): TSouffleValue; override; function GetProperty(const AObject: TSouffleValue; const AKey: string): TSouffleValue; override; @@ -87,16 +154,25 @@ TGocciaRuntimeOperations = class(TSouffleRuntimeOperations) const AKey, AValue: TSouffleValue); override; function DeleteProperty(const AObject: TSouffleValue; const AKey: string): TSouffleValue; override; + function DeleteIndex(const AObject, AKey: TSouffleValue): TSouffleValue; override; function Invoke(const ACallee: TSouffleValue; const AArgs: PSouffleValue; const AArgCount: Integer; const AReceiver: TSouffleValue): TSouffleValue; override; function Construct(const AConstructor: TSouffleValue; const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; override; - function GetIterator(const AIterable: TSouffleValue): TSouffleValue; override; + function GetIterator(const AIterable: TSouffleValue; + const ATryAsync: Boolean = False): TSouffleValue; override; function IteratorNext(const AIterator: TSouffleValue; out ADone: Boolean): TSouffleValue; override; - procedure SpreadInto(const ATarget, ASource: TSouffleValue); override; + procedure SpreadInto(const ATarget, ASource: TSouffleValue); + procedure SpreadObjectInto(const ATarget, ASource: TSouffleValue); + + function ObjectRest(const ASource, + AExclusionKeys: TSouffleValue): TSouffleValue; + procedure RequireObjectCoercible(const AValue: TSouffleValue); + function RequireIterable(const AValue: TSouffleValue): TSouffleValue; + function CoerceValueToString(const A: TSouffleValue): TSouffleValue; override; function ImportModule(const APath: string): TSouffleValue; override; procedure ExportBinding(const AValue: TSouffleValue; @@ -104,17 +180,67 @@ TGocciaRuntimeOperations = class(TSouffleRuntimeOperations) function AwaitValue(const AValue: TSouffleValue): TSouffleValue; override; + function SuperMethodGet(const ASuperBlueprint: TSouffleValue; + const AMethodName: string): TSouffleValue; + function WrapInPromise(const AValue: TSouffleValue; + const AIsRejected: Boolean): TSouffleValue; override; + function GetGlobal(const AName: string): TSouffleValue; override; procedure SetGlobal(const AName: string; const AValue: TSouffleValue); override; + function HasGlobal(const AName: string): Boolean; override; + + procedure DefineGetter(const AObject: TSouffleValue; const AKey: string; + const AGetter: TSouffleValue); + procedure DefineSetter(const AObject: TSouffleValue; const AKey: string; + const ASetter: TSouffleValue); + procedure DefineStaticGetter(const AObject: TSouffleValue; const AKey: string; + const AGetter: TSouffleValue); + procedure DefineStaticSetter(const AObject: TSouffleValue; const AKey: string; + const ASetter: TSouffleValue); + + procedure PropertyWriteViolation(const AObject: TSouffleValue; + const AKey: string); + procedure PropertyDeleteViolation(const AObject: TSouffleValue; + const AKey: string); + procedure ThrowTypeErrorMessage( + const AMessage: string); + + function EvaluateClassByIndex( + const AIndex: Integer): TSouffleValue; + function FinalizeEnum(const ARecord: TSouffleValue; + const AName: string): TSouffleValue; + + procedure ExtendedOperation(const ASubOp: UInt8; + var ADest: TSouffleValue; const AOperand, AExtra: TSouffleValue; + const ATemplate: TSouffleFunctionTemplate; + const AOperandIndex: UInt8); override; + procedure MarkExternalRoots; override; + + function AddPendingClassDef( + const AClassDef: TGocciaClassDefinition; + const ALine, AColumn: Integer): Integer; function UnwrapToGocciaValue(const AValue: TSouffleValue): TGocciaValue; function ToSouffleValue(const AValue: TGocciaValue): TSouffleValue; + function ResolveProxyGet(const ATarget: TSouffleHeapObject; + const AName: string): TGocciaValue; + procedure ResolveProxySet(const ATarget: TSouffleHeapObject; + const AName: string; const AValue: TGocciaValue); + + procedure RegisterDelegates; procedure RegisterGlobal(const AName: string; const AValue: TSouffleValue); + procedure RegisterConstGlobal(const AName: string; + const AValue: TSouffleValue); + procedure RegisterFormalParameterCount( + const ATemplate: TSouffleFunctionTemplate; const ACount: Integer); + function GetFormalParameterCount( + const ATemplate: TSouffleFunctionTemplate): Integer; property ModuleExports: TDictionary read FExports; property VM: TSouffleVM read FVM write FVM; property Engine: TObject read FEngine write FEngine; + property SourcePath: string read FSourcePath write FSourcePath; end; implementation @@ -123,17 +249,63 @@ implementation Math, SysUtils, + Souffle.Bytecode.Debug, + Souffle.Compound, + Souffle.GarbageCollector, + Souffle.VM.CallFrame, + Souffle.VM.Exception, + Souffle.VM.NativeFunction, + Goccia.Arguments.Collection, + Goccia.CallStack, + Goccia.Constants.ConstructorNames, + Goccia.Constants.PropertyNames, Goccia.Engine, Goccia.Evaluator, + Goccia.Evaluator.TypeOperations, + Goccia.GarbageCollector, Goccia.Interpreter, + Goccia.MicrotaskQueue, + Goccia.Modules, + Goccia.Scope, + Goccia.Scope.BindingMap, Goccia.Values.ArrayValue, Goccia.Values.ClassHelper, Goccia.Values.ClassValue, + Goccia.Values.EnumValue, + Goccia.Values.ErrorHelper, Goccia.Values.FunctionBase, - Goccia.Values.ObjectValue; + Goccia.Values.FunctionValue, + Goccia.Values.Iterator.Concrete, + Goccia.Values.Iterator.Generic, + Goccia.Values.IteratorValue, + Goccia.Values.MapValue, + Goccia.Values.NativeFunction, + Goccia.Values.PromiseValue, + Goccia.Values.SetValue, + Goccia.Values.ToPrimitive; + +const + GOCCIA_NIL_UNDEFINED = 0; + GOCCIA_NIL_NULL = 1; + GOCCIA_NIL_HOLE = 2; type + TGocciaSouffleNativeFunctionBridge = class(TGocciaFunctionBase) + private + FNativeFunction: TSouffleNativeFunction; + FRuntime: TGocciaRuntimeOperations; + protected + function GetFunctionLength: Integer; override; + function GetFunctionName: string; override; + public + constructor Create(const ANativeFunction: TSouffleNativeFunction; + const ARuntime: TGocciaRuntimeOperations); + function Call(const AArguments: TGocciaArgumentsCollection; + const AThisValue: TGocciaValue): TGocciaValue; override; + property NativeFunction: TSouffleNativeFunction read FNativeFunction; + end; + TGocciaSouffleClosureBridge = class(TGocciaFunctionBase) private FClosure: TSouffleClosure; @@ -141,6 +313,21 @@ TGocciaSouffleClosureBridge = class(TGocciaFunctionBase) protected function GetFunctionLength: Integer; override; function GetFunctionName: string; override; + public + constructor Create(const AClosure: TSouffleClosure; + const ARuntime: TGocciaRuntimeOperations); + function Call(const AArguments: TGocciaArgumentsCollection; + const AThisValue: TGocciaValue): TGocciaValue; override; + property Closure: TSouffleClosure read FClosure; + end; + + TGocciaSouffleMethodBridge = class(TGocciaMethodValue) + private + FSouffleClosure: TSouffleClosure; + FBridgeRuntime: TGocciaRuntimeOperations; + protected + function GetFunctionLength: Integer; override; + function GetFunctionName: string; override; public constructor Create(const AClosure: TSouffleClosure; const ARuntime: TGocciaRuntimeOperations); @@ -148,6 +335,56 @@ TGocciaSouffleClosureBridge = class(TGocciaFunctionBase) const AThisValue: TGocciaValue): TGocciaValue; override; end; +procedure RebuildArrayBridgeCache( + const ARuntime: TGocciaRuntimeOperations; + const AScope: TGocciaScope); forward; +procedure SyncCachedGocciaToSouffle( + const ARuntime: TGocciaRuntimeOperations); forward; +function SouffleIsHole(const AValue: TSouffleValue): Boolean; +begin + Result := (AValue.Kind = svkNil) and (AValue.Flags = GOCCIA_NIL_HOLE); +end; + +{ TGocciaSouffleNativeFunctionBridge } + +constructor TGocciaSouffleNativeFunctionBridge.Create( + const ANativeFunction: TSouffleNativeFunction; + const ARuntime: TGocciaRuntimeOperations); +begin + inherited Create; + FNativeFunction := ANativeFunction; + FRuntime := ARuntime; +end; + +function TGocciaSouffleNativeFunctionBridge.GetFunctionLength: Integer; +begin + Result := FNativeFunction.Arity; +end; + +function TGocciaSouffleNativeFunctionBridge.GetFunctionName: string; +begin + Result := FNativeFunction.Name; +end; + +function TGocciaSouffleNativeFunctionBridge.Call( + const AArguments: TGocciaArgumentsCollection; + const AThisValue: TGocciaValue): TGocciaValue; +var + Args: array of TSouffleValue; + I: Integer; + Receiver, SouffleResult: TSouffleValue; +begin + SetLength(Args, AArguments.Length); + for I := 0 to AArguments.Length - 1 do + Args[I] := FRuntime.ToSouffleValue(AArguments.GetElement(I)); + Receiver := FRuntime.ToSouffleValue(AThisValue); + if AArguments.Length > 0 then + SouffleResult := FNativeFunction.Invoke(Receiver, @Args[0], AArguments.Length) + else + SouffleResult := FNativeFunction.Invoke(Receiver, nil, 0); + Result := FRuntime.UnwrapToGocciaValue(SouffleResult); +end; + { TGocciaSouffleClosureBridge } constructor TGocciaSouffleClosureBridge.Create( @@ -161,12 +398,14 @@ constructor TGocciaSouffleClosureBridge.Create( function TGocciaSouffleClosureBridge.GetFunctionLength: Integer; begin - Result := FClosure.Prototype.ParameterCount; + Result := FRuntime.GetFormalParameterCount(FClosure.Template); + if Result < 0 then + Result := FClosure.Template.ParameterCount; end; function TGocciaSouffleClosureBridge.GetFunctionName: string; begin - Result := FClosure.Prototype.Name; + Result := FClosure.Template.Name; end; function TGocciaSouffleClosureBridge.Call( @@ -177,14 +416,96 @@ function TGocciaSouffleClosureBridge.Call( I: Integer; SouffleResult: TSouffleValue; begin - SetLength(Args, AArguments.Length); + SetLength(Args, AArguments.Length + 1); + Args[0] := FRuntime.ToSouffleValue(AThisValue); for I := 0 to AArguments.Length - 1 do - Args[I] := FRuntime.ToSouffleValue(AArguments.GetElement(I)); + Args[1 + I] := FRuntime.ToSouffleValue(AArguments.GetElement(I)); - SouffleResult := FRuntime.VM.ExecuteFunction(FClosure, Args); + SyncCachedGocciaToSouffle(FRuntime); + Inc(FRuntime.FBridgeCallDepth); + try + SouffleResult := FRuntime.VM.ExecuteFunction(FClosure, Args); + except + on E: ESouffleThrow do + begin + Dec(FRuntime.FBridgeCallDepth); + if FRuntime.FBridgeCallDepth = 0 then + begin + FRuntime.FArrayBridgeCache.Clear; + FRuntime.FRecordBridgeCache.Clear; + end; + raise TGocciaThrowValue.Create( + FRuntime.UnwrapToGocciaValue(E.ThrownValue)); + end; + end; + Dec(FRuntime.FBridgeCallDepth); + if FRuntime.FBridgeCallDepth = 0 then + begin + FRuntime.FArrayBridgeCache.Clear; + FRuntime.FRecordBridgeCache.Clear; + end; Result := FRuntime.UnwrapToGocciaValue(SouffleResult); end; +{ TGocciaSouffleMethodBridge } + +constructor TGocciaSouffleMethodBridge.Create(const AClosure: TSouffleClosure; + const ARuntime: TGocciaRuntimeOperations); +begin + inherited Create(nil, nil, nil, AClosure.Template.Name); + FSouffleClosure := AClosure; + FBridgeRuntime := ARuntime; +end; + +function TGocciaSouffleMethodBridge.GetFunctionLength: Integer; +begin + Result := FSouffleClosure.Template.ParameterCount - 1; +end; + +function TGocciaSouffleMethodBridge.GetFunctionName: string; +begin + Result := FSouffleClosure.Template.Name; +end; + +function TGocciaSouffleMethodBridge.Call( + const AArguments: TGocciaArgumentsCollection; + const AThisValue: TGocciaValue): TGocciaValue; +var + Args: array of TSouffleValue; + I: Integer; + SouffleResult: TSouffleValue; +begin + SetLength(Args, AArguments.Length + 1); + Args[0] := FBridgeRuntime.ToSouffleValue(AThisValue); + for I := 0 to AArguments.Length - 1 do + Args[1 + I] := FBridgeRuntime.ToSouffleValue(AArguments.GetElement(I)); + + SyncCachedGocciaToSouffle(FBridgeRuntime); + Inc(FBridgeRuntime.FBridgeCallDepth); + try + SouffleResult := FBridgeRuntime.VM.ExecuteFunction(FSouffleClosure, Args); + except + on E: ESouffleThrow do + begin + Dec(FBridgeRuntime.FBridgeCallDepth); + if FBridgeRuntime.FBridgeCallDepth = 0 then + begin + FBridgeRuntime.FArrayBridgeCache.Clear; + FBridgeRuntime.FRecordBridgeCache.Clear; + end; + raise TGocciaThrowValue.Create( + FBridgeRuntime.UnwrapToGocciaValue(E.ThrownValue)); + end; + end; + Dec(FBridgeRuntime.FBridgeCallDepth); + if FBridgeRuntime.FBridgeCallDepth = 0 then + begin + FBridgeRuntime.FArrayBridgeCache.Clear; + FBridgeRuntime.FRecordBridgeCache.Clear; + end; + Result := FBridgeRuntime.UnwrapToGocciaValue(SouffleResult); +end; + { TGocciaWrappedValue } constructor TGocciaWrappedValue.Create(const AValue: TGocciaValue); @@ -208,579 +529,4594 @@ function TGocciaWrappedValue.DebugString: string; Result := '[Wrapped: nil]'; end; -{ TGocciaRuntimeOperations } +{ TGocciaSouffleProxy } -constructor TGocciaRuntimeOperations.Create; +constructor TGocciaSouffleProxy.Create(const ATarget: TSouffleHeapObject; + const ARuntime: TGocciaRuntimeOperations); begin inherited Create; - FGlobals := TDictionary.Create; - FExports := TDictionary.Create; - FVM := nil; - FGlobals.AddOrSetValue('null', - WrapGocciaValue(TGocciaNullLiteralValue.Create)); + FTarget := ATarget; + FRuntime := ARuntime; end; -destructor TGocciaRuntimeOperations.Destroy; +function TGocciaSouffleProxy.GetProperty(const AName: string): TGocciaValue; begin - FGlobals.Free; - FExports.Free; - inherited; + Result := FRuntime.ResolveProxyGet(FTarget, AName); + if not Assigned(Result) then + Result := inherited GetProperty(AName); end; -function TGocciaRuntimeOperations.WrapGocciaValue( - const AValue: TGocciaValue): TSouffleValue; +procedure TGocciaSouffleProxy.SetProperty(const AName: string; + const AValue: TGocciaValue); begin - Result := SouffleReference(TGocciaWrappedValue.Create(AValue)); + FRuntime.ResolveProxySet(FTarget, AName, AValue); end; -function TGocciaRuntimeOperations.UnwrapToGocciaValue( - const AValue: TSouffleValue): TGocciaValue; +function TGocciaSouffleProxy.ToStringLiteral: TGocciaStringLiteralValue; begin - case AValue.Kind of - svkNil: - Result := TGocciaUndefinedLiteralValue.UndefinedValue; - svkBoolean: - if AValue.AsBoolean then - Result := TGocciaBooleanLiteralValue.TrueValue - else - Result := TGocciaBooleanLiteralValue.FalseValue; - svkInteger: - Result := TGocciaNumberLiteralValue.Create(AValue.AsInteger * 1.0); - svkFloat: - Result := TGocciaNumberLiteralValue.Create(AValue.AsFloat); - svkReference: - if AValue.AsReference is TGocciaWrappedValue then - Result := TGocciaWrappedValue(AValue.AsReference).Value - else if AValue.AsReference is TSouffleString then - Result := TGocciaStringLiteralValue.Create( - TSouffleString(AValue.AsReference).Value) - else if (AValue.AsReference is TSouffleClosure) and Assigned(FVM) then - Result := TGocciaSouffleClosureBridge.Create( - TSouffleClosure(AValue.AsReference), Self) - else - Result := TGocciaUndefinedLiteralValue.UndefinedValue; - else - Result := TGocciaUndefinedLiteralValue.UndefinedValue; - end; + Result := TGocciaStringLiteralValue.Create( + FRuntime.CoerceToString(SouffleReference(FTarget))); end; -function TGocciaRuntimeOperations.ToSouffleValue( - const AValue: TGocciaValue): TSouffleValue; -var - NumVal: TGocciaNumberLiteralValue; +function TGocciaSouffleProxy.TypeName: string; begin - if AValue is TGocciaUndefinedLiteralValue then - Result := SouffleNil - else if AValue is TGocciaNullLiteralValue then - Result := SouffleNil - else if AValue is TGocciaBooleanLiteralValue then - Result := SouffleBoolean(TGocciaBooleanLiteralValue(AValue).Value) - else if AValue is TGocciaNumberLiteralValue then - begin - NumVal := TGocciaNumberLiteralValue(AValue); - if NumVal.IsNaN then - Result := SouffleFloat(NaN) - else if NumVal.IsInfinite then - begin - if NumVal.IsNegativeInfinity then - Result := SouffleFloat(NegInfinity) - else - Result := SouffleFloat(Infinity); - end - else if (Frac(NumVal.Value) = 0.0) - and (NumVal.Value >= Low(Int64) * 1.0) - and (NumVal.Value <= High(Int64) * 1.0) then - Result := SouffleInteger(Trunc(NumVal.Value)) - else - Result := SouffleFloat(NumVal.Value); - end - else if AValue is TGocciaStringLiteralValue then - Result := SouffleReference( - TSouffleString.Create(TGocciaStringLiteralValue(AValue).Value)) - else - Result := WrapGocciaValue(AValue); + Result := 'object'; end; -{ Arithmetic } +function TGocciaSouffleProxy.TypeOf: string; +begin + Result := 'object'; +end; -function TGocciaRuntimeOperations.Add(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.IsPrimitive: Boolean; begin - if SouffleIsInteger(A) and SouffleIsInteger(B) then - Result := SouffleInteger(A.AsInteger + B.AsInteger) - else if SouffleIsNumeric(A) and SouffleIsNumeric(B) then - Result := SouffleFloat(SouffleAsNumber(A) + SouffleAsNumber(B)) - else if (A.Kind = svkReference) and (A.AsReference is TSouffleString) then - Result := SouffleReference(TSouffleString.Create( - TSouffleString(A.AsReference).Value + SouffleValueToString(B))) - else if (B.Kind = svkReference) and (B.AsReference is TSouffleString) then - Result := SouffleReference(TSouffleString.Create( - SouffleValueToString(A) + TSouffleString(B.AsReference).Value)) - else - Result := SouffleFloat(SouffleAsNumber(A) + SouffleAsNumber(B)); + Result := False; end; -function TGocciaRuntimeOperations.Subtract(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.ToBooleanLiteral: TGocciaBooleanLiteralValue; begin - if SouffleIsInteger(A) and SouffleIsInteger(B) then - Result := SouffleInteger(A.AsInteger - B.AsInteger) - else - Result := SouffleFloat(SouffleAsNumber(A) - SouffleAsNumber(B)); + Result := TGocciaBooleanLiteralValue.TrueValue; end; -function TGocciaRuntimeOperations.Multiply(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.ToNumberLiteral: TGocciaNumberLiteralValue; +begin + Result := TGocciaNumberLiteralValue.NaNValue; +end; + +procedure TGocciaSouffleProxy.MarkReferences; begin - if SouffleIsInteger(A) and SouffleIsInteger(B) then - Result := SouffleInteger(A.AsInteger * B.AsInteger) + inherited MarkReferences; + if Assigned(FTarget) and not FTarget.GCMarked then + FTarget.MarkReferences; +end; + +function TGocciaSouffleProxy.HasOwnProperty(const AName: string): Boolean; +begin + if FTarget is TSouffleRecord then + Result := TSouffleRecord(FTarget).Has(AName) else - Result := SouffleFloat(SouffleAsNumber(A) * SouffleAsNumber(B)); + Result := False; end; -function TGocciaRuntimeOperations.Divide(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.GetOwnPropertyDescriptor( + const AName: string): TGocciaPropertyDescriptor; var - Divisor: Double; + Rec: TSouffleRecord; + Val, GetterVal, SetterVal: TSouffleValue; + Flags: Byte; + PFlags: TPropertyFlags; + HasGetter, HasSetter: Boolean; + GocciaGetter, GocciaSetter: TGocciaValue; begin - Divisor := SouffleAsNumber(B); - if Divisor = 0.0 then + if FTarget is TSouffleRecord then begin - if SouffleAsNumber(A) = 0.0 then - Result := SouffleFloat(NaN) - else if SouffleAsNumber(A) > 0 then - Result := SouffleFloat(Infinity) + Rec := TSouffleRecord(FTarget); + HasGetter := Rec.HasGetters and Rec.Getters.Get(AName, GetterVal); + HasSetter := Rec.HasSetters and Rec.Setters.Get(AName, SetterVal); + if HasGetter or HasSetter then + begin + GocciaGetter := nil; + GocciaSetter := nil; + if HasGetter then + GocciaGetter := FRuntime.UnwrapToGocciaValue(GetterVal); + if HasSetter then + GocciaSetter := FRuntime.UnwrapToGocciaValue(SetterVal); + PFlags := []; + if HasGetter then + Flags := Rec.Getters.GetEntryFlags(AName) + else + Flags := Rec.Setters.GetEntryFlags(AName); + if Flags and SOUFFLE_PROP_CONFIGURABLE <> 0 then + Include(PFlags, pfConfigurable); + if Flags and SOUFFLE_PROP_ENUMERABLE <> 0 then + Include(PFlags, pfEnumerable); + Result := TGocciaPropertyDescriptorAccessor.Create( + GocciaGetter, GocciaSetter, PFlags); + end + else if Rec.Get(AName, Val) then + begin + Flags := Rec.GetEntryFlags(AName); + PFlags := []; + if Flags and SOUFFLE_PROP_WRITABLE <> 0 then + Include(PFlags, pfWritable); + if Flags and SOUFFLE_PROP_CONFIGURABLE <> 0 then + Include(PFlags, pfConfigurable); + if Flags and SOUFFLE_PROP_ENUMERABLE <> 0 then + Include(PFlags, pfEnumerable); + Result := TGocciaPropertyDescriptorData.Create( + FRuntime.UnwrapToGocciaValue(Val), PFlags); + end else - Result := SouffleFloat(NegInfinity); + Result := nil; end else - Result := SouffleFloat(SouffleAsNumber(A) / Divisor); + Result := nil; end; -function TGocciaRuntimeOperations.Modulo(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.DeleteProperty(const AName: string): Boolean; var - Divisor: Double; + Flags: Byte; begin - if SouffleIsInteger(A) and SouffleIsInteger(B) and (B.AsInteger <> 0) then - Result := SouffleInteger(A.AsInteger mod B.AsInteger) + if FTarget is TSouffleRecord then + begin + if TSouffleRecord(FTarget).Has(AName) then + begin + Flags := TSouffleRecord(FTarget).GetEntryFlags(AName); + if Flags and SOUFFLE_PROP_CONFIGURABLE = 0 then + ThrowTypeError('Cannot delete property ''' + AName + + ''' of a non-configurable property'); + end; + Result := TSouffleRecord(FTarget).Delete(AName) + end else + Result := False; +end; + +procedure TGocciaSouffleProxy.DefineProperty(const AName: string; + const ADescriptor: TGocciaPropertyDescriptor); +var + Rec: TSouffleRecord; + SFlags, ExistingFlags: Byte; +begin + if FTarget is TSouffleRecord then begin - Divisor := SouffleAsNumber(B); - if Divisor = 0.0 then - Result := SouffleFloat(NaN) - else - Result := SouffleFloat(FMod(SouffleAsNumber(A), Divisor)); + Rec := TSouffleRecord(FTarget); + if Rec.Has(AName) then + begin + ExistingFlags := Rec.GetEntryFlags(AName); + if ExistingFlags and SOUFFLE_PROP_CONFIGURABLE = 0 then + ThrowTypeError('Cannot redefine property: ' + AName); + end; + SFlags := 0; + if pfWritable in ADescriptor.Flags then + SFlags := SFlags or SOUFFLE_PROP_WRITABLE; + if pfConfigurable in ADescriptor.Flags then + SFlags := SFlags or SOUFFLE_PROP_CONFIGURABLE; + if pfEnumerable in ADescriptor.Flags then + SFlags := SFlags or SOUFFLE_PROP_ENUMERABLE; + if ADescriptor is TGocciaPropertyDescriptorData then + begin + if Rec.HasGetters then Rec.Getters.Delete(AName); + if Rec.HasSetters then Rec.Setters.Delete(AName); + Rec.PutWithFlags(AName, + FRuntime.ToSouffleValue(TGocciaPropertyDescriptorData(ADescriptor).Value), + SFlags); + end + else if ADescriptor is TGocciaPropertyDescriptorAccessor then + begin + if Rec.Has(AName) then Rec.Delete(AName); + if Assigned(TGocciaPropertyDescriptorAccessor(ADescriptor).Getter) then + Rec.Getters.PutWithFlags(AName, + FRuntime.ToSouffleValue(TGocciaPropertyDescriptorAccessor(ADescriptor).Getter), + SFlags); + if Assigned(TGocciaPropertyDescriptorAccessor(ADescriptor).Setter) then + Rec.Setters.PutWithFlags(AName, + FRuntime.ToSouffleValue(TGocciaPropertyDescriptorAccessor(ADescriptor).Setter), + SFlags); + end; end; end; -function TGocciaRuntimeOperations.Power(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.GetEnumerablePropertyNames: TArray; +var + Rec: TSouffleRecord; + I, Count: Integer; + Flags: Byte; begin - Result := SouffleFloat(Math.Power(SouffleAsNumber(A), SouffleAsNumber(B))); + if FTarget is TSouffleRecord then + begin + Rec := TSouffleRecord(FTarget); + SetLength(Result, Rec.Count); + Count := 0; + for I := 0 to Rec.Count - 1 do + begin + Flags := Rec.GetEntryFlags(Rec.GetOrderedKey(I)); + if Flags and SOUFFLE_PROP_ENUMERABLE <> 0 then + begin + Result[Count] := Rec.GetOrderedKey(I); + Inc(Count); + end; + end; + SetLength(Result, Count); + end + else + Result := nil; end; -function TGocciaRuntimeOperations.Negate(const A: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.GetEnumerablePropertyValues: TArray; +var + Rec: TSouffleRecord; + I, Count: Integer; + Flags: Byte; begin - if SouffleIsInteger(A) then - Result := SouffleInteger(-A.AsInteger) + if FTarget is TSouffleRecord then + begin + Rec := TSouffleRecord(FTarget); + SetLength(Result, Rec.Count); + Count := 0; + for I := 0 to Rec.Count - 1 do + begin + Flags := Rec.GetEntryFlags(Rec.GetOrderedKey(I)); + if Flags and SOUFFLE_PROP_ENUMERABLE <> 0 then + begin + Result[Count] := FRuntime.UnwrapToGocciaValue(Rec.GetOrderedValue(I)); + Inc(Count); + end; + end; + SetLength(Result, Count); + end else - Result := SouffleFloat(-SouffleAsNumber(A)); + Result := nil; end; -{ Bitwise } - -function TGocciaRuntimeOperations.BitwiseAnd(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.GetEnumerablePropertyEntries: TArray>; +var + Rec: TSouffleRecord; + I, Count: Integer; + Flags: Byte; + Key: string; + Seen: TDictionary; begin - Result := SouffleInteger(Int32(Trunc(SouffleAsNumber(A))) and - Int32(Trunc(SouffleAsNumber(B)))); + if FTarget is TSouffleRecord then + begin + Rec := TSouffleRecord(FTarget); + Seen := TDictionary.Create; + try + SetLength(Result, Rec.Count + 16); + Count := 0; + for I := 0 to Rec.Count - 1 do + begin + Key := Rec.GetOrderedKey(I); + Flags := Rec.GetEntryFlags(Key); + if Flags and SOUFFLE_PROP_ENUMERABLE <> 0 then + begin + Seen.AddOrSetValue(Key, True); + if Count >= Length(Result) then + SetLength(Result, Count + 16); + Result[Count].Key := Key; + Result[Count].Value := FRuntime.UnwrapToGocciaValue(Rec.GetOrderedValue(I)); + Inc(Count); + end; + end; + if Rec.HasGetters then + for I := 0 to Rec.Getters.Count - 1 do + begin + Key := Rec.Getters.GetOrderedKey(I); + if not Seen.ContainsKey(Key) then + begin + Flags := Rec.Getters.GetEntryFlags(Key); + if Flags and SOUFFLE_PROP_ENUMERABLE <> 0 then + begin + Seen.AddOrSetValue(Key, True); + if Count >= Length(Result) then + SetLength(Result, Count + 16); + Result[Count].Key := Key; + Result[Count].Value := GetProperty(Key); + Inc(Count); + end; + end; + end; + SetLength(Result, Count); + finally + Seen.Free; + end; + end + else + Result := nil; end; -function TGocciaRuntimeOperations.BitwiseOr(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.GetOwnPropertyNames: TArray; +var + Rec: TSouffleRecord; + I, Count: Integer; + Key: string; + Seen: TDictionary; begin - Result := SouffleInteger(Int32(Trunc(SouffleAsNumber(A))) or - Int32(Trunc(SouffleAsNumber(B)))); + if FTarget is TSouffleRecord then + begin + Rec := TSouffleRecord(FTarget); + Seen := TDictionary.Create; + try + SetLength(Result, Rec.Count + 16); + Count := 0; + for I := 0 to Rec.Count - 1 do + begin + Key := Rec.GetOrderedKey(I); + if not Seen.ContainsKey(Key) then + begin + Seen.Add(Key, True); + if Count >= Length(Result) then + SetLength(Result, Count + 16); + Result[Count] := Key; + Inc(Count); + end; + end; + if Rec.HasGetters then + for I := 0 to Rec.Getters.Count - 1 do + begin + Key := Rec.Getters.GetOrderedKey(I); + if not Seen.ContainsKey(Key) then + begin + Seen.Add(Key, True); + if Count >= Length(Result) then + SetLength(Result, Count + 16); + Result[Count] := Key; + Inc(Count); + end; + end; + if Rec.HasSetters then + for I := 0 to Rec.Setters.Count - 1 do + begin + Key := Rec.Setters.GetOrderedKey(I); + if not Seen.ContainsKey(Key) then + begin + Seen.Add(Key, True); + if Count >= Length(Result) then + SetLength(Result, Count + 16); + Result[Count] := Key; + Inc(Count); + end; + end; + SetLength(Result, Count); + finally + Seen.Free; + end; + end + else + Result := nil; end; -function TGocciaRuntimeOperations.BitwiseXor(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.GetOwnPropertyKeys: TArray; begin - Result := SouffleInteger(Int32(Trunc(SouffleAsNumber(A))) xor - Int32(Trunc(SouffleAsNumber(B)))); + Result := GetOwnPropertyNames; end; -function TGocciaRuntimeOperations.ShiftLeft(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.GetAllPropertyNames: TArray; begin - Result := SouffleInteger(Int32(Trunc(SouffleAsNumber(A))) shl - (Int32(Trunc(SouffleAsNumber(B))) and $1F)); + Result := GetOwnPropertyNames; end; -function TGocciaRuntimeOperations.ShiftRight(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.GetSymbolProperty( + const ASymbol: TGocciaSymbolValue): TGocciaValue; begin - Result := SouffleInteger(Int32(Trunc(SouffleAsNumber(A))) shr - (Int32(Trunc(SouffleAsNumber(B))) and $1F)); + Result := inherited GetSymbolProperty(ASymbol); end; -function TGocciaRuntimeOperations.UnsignedShiftRight(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.HasSymbolProperty( + const ASymbol: TGocciaSymbolValue): Boolean; begin - Result := SouffleInteger(Int64(UInt32(Trunc(SouffleAsNumber(A))) shr - (Int32(Trunc(SouffleAsNumber(B))) and $1F))); + Result := inherited HasSymbolProperty(ASymbol); end; -function TGocciaRuntimeOperations.BitwiseNot(const A: TSouffleValue): TSouffleValue; +procedure TGocciaSouffleProxy.Freeze; begin - Result := SouffleInteger(not Int32(Trunc(SouffleAsNumber(A)))); + if FTarget is TSouffleRecord then + TSouffleRecord(FTarget).Freeze; end; -{ Comparison } - -function TGocciaRuntimeOperations.Equal(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.IsFrozen: Boolean; begin - Result := SouffleBoolean(SouffleValuesEqual(A, B)); + if FTarget is TSouffleRecord then + Result := not TSouffleRecord(FTarget).Extensible + else + Result := False; end; -function TGocciaRuntimeOperations.NotEqual(const A, B: TSouffleValue): TSouffleValue; +procedure TGocciaSouffleProxy.Seal; begin - Result := SouffleBoolean(not SouffleValuesEqual(A, B)); + if FTarget is TSouffleRecord then + TSouffleRecord(FTarget).PreventExtensions; end; -function TGocciaRuntimeOperations.LessThan(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.IsSealed: Boolean; begin - if SouffleIsNumeric(A) and SouffleIsNumeric(B) then - Result := SouffleBoolean(SouffleAsNumber(A) < SouffleAsNumber(B)) + if FTarget is TSouffleRecord then + Result := not TSouffleRecord(FTarget).Extensible else - Result := SouffleBoolean(SouffleValueToString(A) < SouffleValueToString(B)); + Result := False; end; -function TGocciaRuntimeOperations.GreaterThan(const A, B: TSouffleValue): TSouffleValue; +procedure TGocciaSouffleProxy.PreventExtensions; begin - if SouffleIsNumeric(A) and SouffleIsNumeric(B) then - Result := SouffleBoolean(SouffleAsNumber(A) > SouffleAsNumber(B)) - else - Result := SouffleBoolean(SouffleValueToString(A) > SouffleValueToString(B)); + if FTarget is TSouffleRecord then + TSouffleRecord(FTarget).PreventExtensions; end; -function TGocciaRuntimeOperations.LessThanOrEqual(const A, B: TSouffleValue): TSouffleValue; +function TGocciaSouffleProxy.IsExtensible: Boolean; begin - if SouffleIsNumeric(A) and SouffleIsNumeric(B) then - Result := SouffleBoolean(SouffleAsNumber(A) <= SouffleAsNumber(B)) + if FTarget is TSouffleRecord then + Result := TSouffleRecord(FTarget).Extensible else - Result := SouffleBoolean(SouffleValueToString(A) <= SouffleValueToString(B)); + Result := False; end; -function TGocciaRuntimeOperations.GreaterThanOrEqual(const A, B: TSouffleValue): TSouffleValue; +{ TGocciaRuntimeOperations } + +constructor TGocciaRuntimeOperations.Create; +begin + inherited Create; + FGlobals := TDictionary.Create; + FConstGlobals := TDictionary.Create; + FExports := TDictionary.Create; + FClosureBridgeCache := TDictionary.Create; + FArrayBridgeCache := TDictionary.Create; + FArrayBridgeReverse := TDictionary.Create; + FRecordBridgeCache := TDictionary.Create; + FClassDefinitionScopes := TDictionary.Create; + FWrappedValues := TList.Create; + FFormalParameterCounts := TDictionary.Create; + FBridgeCallDepth := 0; + FVM := nil; + TGocciaGarbageCollector.Initialize; + TGocciaGarbageCollector.Instance.SetExternalRootMarker(MarkWrappedGocciaValues); +end; + +destructor TGocciaRuntimeOperations.Destroy; +begin + if Assigned(TGocciaGarbageCollector.Instance) then + TGocciaGarbageCollector.Instance.SetExternalRootMarker(nil); + FGlobals.Free; + FConstGlobals.Free; + FExports.Free; + FClosureBridgeCache.Free; + FArrayBridgeCache.Free; + FArrayBridgeReverse.Free; + FRecordBridgeCache.Free; + FClassDefinitionScopes.Free; + FWrappedValues.Free; + FFormalParameterCounts.Free; + inherited; +end; + +function TGocciaRuntimeOperations.WrapGocciaValue( + const AValue: TGocciaValue): TSouffleValue; +var + Wrapped: TGocciaWrappedValue; +begin + Wrapped := TGocciaWrappedValue.Create(AValue); + FWrappedValues.Add(Wrapped); + Result := SouffleReference(Wrapped); +end; + +procedure TGocciaRuntimeOperations.RethrowAsVM(const E: TGocciaThrowValue); +begin + raise ESouffleThrow.Create(WrapGocciaValue(E.Value)); +end; + +procedure PushVMFramesToGoccia(const AVM: TSouffleVM); +var + I: Integer; + Frame: PSouffleVMCallFrame; + FuncName, FilePath: string; + Line: Integer; + Column: Integer; +begin + if not Assigned(AVM) or not Assigned(TGocciaCallStack.Instance) then Exit; + if AVM.CallStack.Count = 0 then Exit; + for I := 0 to AVM.CallStack.Count - 1 do + begin + Frame := AVM.CallStack.GetFrame(I); + FuncName := Frame^.Template.Name; + if Assigned(Frame^.Template.DebugInfo) then + begin + FilePath := Frame^.Template.DebugInfo.SourceFile; + Line := Frame^.Template.DebugInfo.GetLineForPC(Frame^.IP); + Column := Frame^.Template.DebugInfo.GetColumnForPC(Frame^.IP); + end + else + begin + FilePath := ''; + Line := 0; + Column := 0; + end; + TGocciaCallStack.Instance.Push(FuncName, FilePath, Line, Column); + end; +end; + +procedure PopVMFramesFromGoccia(const AVM: TSouffleVM); +var + I: Integer; +begin + if not Assigned(AVM) or not Assigned(TGocciaCallStack.Instance) then Exit; + for I := 0 to AVM.CallStack.Count - 1 do + TGocciaCallStack.Instance.Pop; +end; + +function CoerceToPrimitiveSouffle(const ARuntime: TGocciaRuntimeOperations; + const A: TSouffleValue): TSouffleValue; forward; + +function StringToNumber(const S: string): Double; +var + Trimmed: string; + Code: Integer; + HexVal: Int64; +begin + Trimmed := Trim(S); + if Trimmed = '' then + Exit(0.0); + if Trimmed = 'Infinity' then + Exit(Infinity); + if Trimmed = '-Infinity' then + Exit(NegInfinity); + Val(Trimmed, Result, Code); + if Code <> 0 then + begin + if (Length(Trimmed) > 2) and (Trimmed[1] = '0') and + ((Trimmed[2] = 'x') or (Trimmed[2] = 'X')) then + begin + Val(Trimmed, HexVal, Code); + if Code = 0 then + Result := HexVal + else + Result := NaN; + end + else + Result := NaN; + end; +end; + +function TGocciaRuntimeOperations.CoerceToNumber( + const A: TSouffleValue): Double; +var + Prim: TSouffleValue; +begin + case A.Kind of + svkInteger: + Result := A.AsInteger; + svkFloat: + Result := A.AsFloat; + svkBoolean: + if A.AsBoolean then + Result := 1.0 + else + Result := 0.0; + svkNil: + if A.Flags = GOCCIA_NIL_NULL then + Result := 0.0 + else + Result := NaN; + svkString: + Result := StringToNumber(SouffleGetString(A)); + svkReference: + if A.AsReference is TSouffleHeapString then + Result := StringToNumber(TSouffleHeapString(A.AsReference).Value) + else if (A.AsReference is TGocciaWrappedValue) and + (TGocciaWrappedValue(A.AsReference).Value is TGocciaSymbolValue) then + begin + ThrowTypeError('Cannot convert a Symbol value to a number'); + Result := NaN; + end + else + begin + Prim := CoerceToPrimitiveSouffle(Self, A); + if (Prim.Kind <> svkReference) or SouffleIsStringValue(Prim) then + Result := CoerceToNumber(Prim) + else + Result := NaN; + end; + else + Result := NaN; + end; +end; + +function JoinArrayElements(const AArr: TSouffleArray; + const ASep: string; const ARuntime: TGocciaRuntimeOperations): string; forward; +function JoinElementToString(const AElem: TSouffleValue; + const ARuntime: TGocciaRuntimeOperations): string; forward; +function CreateBridgedContext( + const ARuntime: TGocciaRuntimeOperations): TGocciaEvaluationContext; forward; + +procedure SyncArraysBack(const ARuntime: TGocciaRuntimeOperations; + const AScope: TGocciaScope); forward; + +procedure SyncScopeToGlobals(const ARuntime: TGocciaRuntimeOperations; + const AScope: TGocciaScope); forward; + +procedure SyncDefinitionScopesBack( + const ARuntime: TGocciaRuntimeOperations); forward; + +function BlueprintToClassValue(const ABlueprint: TSouffleBlueprint; + const ARuntime: TGocciaRuntimeOperations): TGocciaClassValue; forward; + +function SouffleArrayToString(const ARuntime: TGocciaRuntimeOperations; + const AArr: TSouffleArray): string; +begin + Result := JoinArrayElements(AArr, ',', ARuntime); +end; + +function FloatToECMAString(const AValue: Double): string; +begin + if IsNaN(AValue) then + Result := 'NaN' + else if IsInfinite(AValue) then + begin + if AValue > 0 then + Result := 'Infinity' + else + Result := '-Infinity'; + end + else if (Frac(AValue) = 0.0) and (Abs(AValue) < 1e20) then + Result := FloatToStrF(AValue, ffFixed, 20, 0) + else + Result := FloatToStr(AValue); +end; + +function InvokeMethodForPrimitive(const ARuntime: TGocciaRuntimeOperations; + const AMethodVal, ASelf: TSouffleValue; + out APrimitive: TSouffleValue): Boolean; +var + VMArgs: array of TSouffleValue; +begin + Result := False; + if not SouffleIsReference(AMethodVal) or not Assigned(AMethodVal.AsReference) then + Exit; + SetLength(VMArgs, 1); + VMArgs[0] := ASelf; + if AMethodVal.AsReference is TSouffleClosure then + APrimitive := ARuntime.VM.ExecuteFunction( + TSouffleClosure(AMethodVal.AsReference), VMArgs) + else if AMethodVal.AsReference is TSouffleNativeFunction then + APrimitive := TSouffleNativeFunction(AMethodVal.AsReference) + .Invoke(ASelf, nil, 0) + else + Exit; + Result := APrimitive.Kind <> svkReference; +end; + +function FindRecordMethod(const ARec: TSouffleRecord; + const AName: string; out AMethod: TSouffleValue): Boolean; +var + Bp: TSouffleBlueprint; +begin + if ARec.Get(AName, AMethod) then + Exit(True); + if Assigned(ARec.Blueprint) then + begin + Bp := ARec.Blueprint; + while Assigned(Bp) do + begin + if Bp.Methods.Get(AName, AMethod) then + Exit(True); + Bp := Bp.SuperBlueprint; + end; + end; + Result := False; +end; + +function RecordToString(const ARuntime: TGocciaRuntimeOperations; + const ARec: TSouffleRecord; const ASelf: TSouffleValue): string; +var + MethodVal, PrimResult: TSouffleValue; + TriedMethod: Boolean; +begin + TriedMethod := False; + if FindRecordMethod(ARec, PROP_TO_STRING, MethodVal) then + begin + TriedMethod := True; + if InvokeMethodForPrimitive(ARuntime, MethodVal, ASelf, PrimResult) then + begin + Result := ARuntime.CoerceToString(PrimResult); + Exit; + end; + end; + if FindRecordMethod(ARec, PROP_VALUE_OF, MethodVal) then + begin + TriedMethod := True; + if InvokeMethodForPrimitive(ARuntime, MethodVal, ASelf, PrimResult) then + begin + Result := ARuntime.CoerceToString(PrimResult); + Exit; + end; + end; + if TriedMethod then + ThrowTypeError('Cannot convert object to primitive value'); + Result := '[object Object]'; +end; + +function TGocciaRuntimeOperations.CoerceToString( + const A: TSouffleValue): string; +begin + case A.Kind of + svkNil: + if A.Flags = GOCCIA_NIL_NULL then + Result := 'null' + else + Result := 'undefined'; + svkBoolean: + if A.AsBoolean then + Result := 'true' + else + Result := 'false'; + svkInteger: + Result := IntToStr(A.AsInteger); + svkFloat: + Result := FloatToECMAString(A.AsFloat); + svkString: + Result := SouffleGetString(A); + svkReference: + if A.AsReference is TSouffleHeapString then + Result := TSouffleHeapString(A.AsReference).Value + else if A.AsReference is TSouffleArray then + Result := SouffleArrayToString(Self, TSouffleArray(A.AsReference)) + else if A.AsReference is TSouffleRecord then + Result := RecordToString(Self, TSouffleRecord(A.AsReference), A) + else if A.AsReference is TSouffleClosure then + begin + if TSouffleClosure(A.AsReference).Template.Name <> '' then + Result := 'function ' + TSouffleClosure(A.AsReference).Template.Name + + '() { [native code] }' + else + Result := 'function () { [native code] }'; + end + else if A.AsReference is TSouffleNativeFunction then + Result := 'function ' + TSouffleNativeFunction(A.AsReference).Name + + '() { [native code] }' + else if A.AsReference is TSouffleBlueprint then + Result := 'function ' + TSouffleBlueprint(A.AsReference).Name + + '() { [native code] }' + else if A.AsReference is TGocciaWrappedValue then + begin + if TGocciaWrappedValue(A.AsReference).Value is TGocciaSymbolValue then + ThrowTypeError('Cannot convert a Symbol value to a string'); + Result := TGocciaWrappedValue(A.AsReference).Value.ToStringLiteral.Value; + end + else + Result := '[object Object]'; + else + Result := 'undefined'; + end; +end; + +procedure ConvertSouffleArrayInto( + const ARuntime: TGocciaRuntimeOperations; + const AArr: TSouffleArray; const ADest: TGocciaArrayValue); +var + I: Integer; +begin + for I := 0 to AArr.Count - 1 do + ADest.Elements.Add(ARuntime.UnwrapToGocciaValue(AArr.Get(I))); +end; + +function ConvertSouffleArrayToGocciaArray( + const ARuntime: TGocciaRuntimeOperations; + const AArr: TSouffleArray): TGocciaArrayValue; +begin + Result := TGocciaArrayValue.Create; + ConvertSouffleArrayInto(ARuntime, AArr, Result); +end; + +function TGocciaRuntimeOperations.UnwrapToGocciaValue( + const AValue: TSouffleValue): TGocciaValue; +var + CachedBridge: TObject; +begin + case AValue.Kind of + svkNil: + if AValue.Flags = GOCCIA_NIL_NULL then + Result := TGocciaNullLiteralValue.Create + else + Result := TGocciaUndefinedLiteralValue.UndefinedValue; + svkBoolean: + if AValue.AsBoolean then + Result := TGocciaBooleanLiteralValue.TrueValue + else + Result := TGocciaBooleanLiteralValue.FalseValue; + svkInteger: + Result := TGocciaNumberLiteralValue.Create(AValue.AsInteger); + svkFloat: + Result := TGocciaNumberLiteralValue.Create(AValue.AsFloat); + svkString: + Result := TGocciaStringLiteralValue.Create(SouffleGetString(AValue)); + svkReference: + if AValue.AsReference is TSouffleHeapString then + Result := TGocciaStringLiteralValue.Create( + TSouffleHeapString(AValue.AsReference).Value) + else if AValue.AsReference is TGocciaWrappedValue then + Result := TGocciaWrappedValue(AValue.AsReference).Value + else if AValue.AsReference is TSouffleArray then + begin + if not FArrayBridgeCache.TryGetValue(AValue.AsReference, CachedBridge) then + begin + CachedBridge := TGocciaArrayValue.Create; + FArrayBridgeCache.Add(AValue.AsReference, CachedBridge); + FArrayBridgeReverse.AddOrSetValue(CachedBridge, AValue.AsReference); + ConvertSouffleArrayInto(Self, + TSouffleArray(AValue.AsReference), + TGocciaArrayValue(CachedBridge)); + end; + Result := TGocciaValue(CachedBridge); + end + else if AValue.AsReference is TSouffleRecord then + begin + if not FRecordBridgeCache.TryGetValue(AValue.AsReference, CachedBridge) then + begin + CachedBridge := TGocciaSouffleProxy.Create(AValue.AsReference, Self); + FRecordBridgeCache.Add(AValue.AsReference, CachedBridge); + end; + if not TGocciaSouffleProxy(CachedBridge).FPrototypeResolved and + Assigned(TSouffleRecord(AValue.AsReference).Delegate) and + (TSouffleRecord(AValue.AsReference).Delegate is TSouffleRecord) then + begin + TGocciaSouffleProxy(CachedBridge).FPrototypeResolved := True; + if Assigned(FVM) and + (TSouffleRecord(AValue.AsReference).Delegate = FVM.RecordDelegate) and + Assigned(TGocciaObjectValue.SharedObjectPrototype) then + TGocciaSouffleProxy(CachedBridge).Prototype := + TGocciaObjectValue.SharedObjectPrototype + else + TGocciaSouffleProxy(CachedBridge).Prototype := + TGocciaSouffleProxy(UnwrapToGocciaValue( + SouffleReference(TSouffleRecord(AValue.AsReference).Delegate))); + end; + Result := TGocciaValue(CachedBridge); + end + else if (AValue.AsReference is TSouffleClosure) and Assigned(FVM) then + begin + if not FClosureBridgeCache.TryGetValue( + TSouffleClosure(AValue.AsReference), CachedBridge) then + begin + CachedBridge := TGocciaSouffleClosureBridge.Create( + TSouffleClosure(AValue.AsReference), Self); + FClosureBridgeCache.Add(TSouffleClosure(AValue.AsReference), + CachedBridge); + end; + Result := TGocciaValue(CachedBridge); + end + else if AValue.AsReference is TSouffleNativeFunction then + Result := TGocciaSouffleNativeFunctionBridge.Create( + TSouffleNativeFunction(AValue.AsReference), Self) + else if AValue.AsReference is TSouffleBlueprint then + Result := BlueprintToClassValue( + TSouffleBlueprint(AValue.AsReference), Self) + else + Result := TGocciaUndefinedLiteralValue.UndefinedValue; + else + Result := TGocciaUndefinedLiteralValue.UndefinedValue; + end; +end; + +function TGocciaRuntimeOperations.ToSouffleValue( + const AValue: TGocciaValue): TSouffleValue; +var + NumVal: TGocciaNumberLiteralValue; +begin + if not Assigned(AValue) then + Exit(SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED)); + + if AValue is TGocciaSouffleProxy then + Exit(SouffleReference(TGocciaSouffleProxy(AValue).Target)); + + if AValue is TGocciaSouffleClosureBridge then + Exit(SouffleReference(TGocciaSouffleClosureBridge(AValue).Closure)); + + if AValue is TGocciaSouffleNativeFunctionBridge then + Exit(SouffleReference(TGocciaSouffleNativeFunctionBridge(AValue).NativeFunction)); + + if AValue is TGocciaNullLiteralValue then + Result := SouffleNilWithFlags(GOCCIA_NIL_NULL) + else if AValue is TGocciaUndefinedLiteralValue then + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED) + else if AValue is TGocciaBooleanLiteralValue then + Result := SouffleBoolean(TGocciaBooleanLiteralValue(AValue).Value) + else if AValue is TGocciaNumberLiteralValue then + begin + NumVal := TGocciaNumberLiteralValue(AValue); + if NumVal.IsNaN then + Result := SouffleFloat(NaN) + else if NumVal.IsInfinite then + begin + if NumVal.IsNegativeInfinity then + Result := SouffleFloat(NegInfinity) + else + Result := SouffleFloat(Infinity); + end + else if NumVal.IsNegativeZero then + Result := SouffleFloat(-0.0) + else if (Frac(NumVal.Value) = 0.0) + and (NumVal.Value >= -2147483648.0) + and (NumVal.Value <= 2147483647.0) then + Result := SouffleInteger(Trunc(NumVal.Value)) + else + Result := SouffleFloat(NumVal.Value); + end + else if AValue is TGocciaStringLiteralValue then + Result := SouffleString(TGocciaStringLiteralValue(AValue).Value) + else if FArrayBridgeReverse.ContainsKey(AValue) then + Result := SouffleReference(TSouffleHeapObject(FArrayBridgeReverse[AValue])) + else + Result := WrapGocciaValue(AValue); +end; + +{ Arithmetic } + +function InvokePrimitiveMethod(const ARuntime: TGocciaRuntimeOperations; + const AMethodVal, ASelf: TSouffleValue; + out AResult: TSouffleValue): Boolean; +var + VMArgs: array of TSouffleValue; +begin + Result := False; + if not SouffleIsReference(AMethodVal) or not Assigned(AMethodVal.AsReference) then + Exit; + SetLength(VMArgs, 1); + VMArgs[0] := ASelf; + if AMethodVal.AsReference is TSouffleClosure then + AResult := ARuntime.VM.ExecuteFunction( + TSouffleClosure(AMethodVal.AsReference), VMArgs) + else if AMethodVal.AsReference is TSouffleNativeFunction then + AResult := TSouffleNativeFunction(AMethodVal.AsReference) + .Invoke(ASelf, nil, 0) + else + Exit; + Result := not SouffleIsReference(AResult) or SouffleIsStringValue(AResult); +end; + +function CoerceToPrimitiveSouffle(const ARuntime: TGocciaRuntimeOperations; + const A: TSouffleValue): TSouffleValue; +var + GocciaVal, Prim: TGocciaValue; + Rec: TSouffleRecord; + Bp: TSouffleBlueprint; + MethodVal, MethodResult: TSouffleValue; +begin + if not SouffleIsReference(A) or not Assigned(A.AsReference) then + Exit(A); + if SouffleIsStringValue(A) then + Exit(A); + if A.AsReference is TSouffleArray then + Exit(SouffleString( + SouffleArrayToString(ARuntime, TSouffleArray(A.AsReference)))); + if A.AsReference is TSouffleRecord then + begin + Rec := TSouffleRecord(A.AsReference); + + if Rec.Get(PROP_VALUE_OF, MethodVal) then + if InvokePrimitiveMethod(ARuntime, MethodVal, A, MethodResult) then + Exit(MethodResult); + if Rec.Get(PROP_TO_STRING, MethodVal) then + if InvokePrimitiveMethod(ARuntime, MethodVal, A, MethodResult) then + Exit(MethodResult); + + if Assigned(Rec.Blueprint) then + begin + Bp := Rec.Blueprint; + while Assigned(Bp) do + begin + if Bp.Methods.Get(PROP_VALUE_OF, MethodVal) then + if InvokePrimitiveMethod(ARuntime, MethodVal, A, MethodResult) then + Exit(MethodResult); + if Bp.Methods.Get(PROP_TO_STRING, MethodVal) then + if InvokePrimitiveMethod(ARuntime, MethodVal, A, MethodResult) then + Exit(MethodResult); + Bp := Bp.SuperBlueprint; + end; + end; + Exit(SouffleString('[object Object]')); + end; + if A.AsReference is TGocciaWrappedValue then + begin + GocciaVal := TGocciaWrappedValue(A.AsReference).Value; + if GocciaVal.IsPrimitive then + Exit(ARuntime.ToSouffleValue(GocciaVal)); + Prim := Goccia.Values.ToPrimitive.ToPrimitive(GocciaVal); + Exit(ARuntime.ToSouffleValue(Prim)); + end; + Result := SouffleString('[object Object]'); +end; + +function IsWrappedSymbol(const A: TSouffleValue): Boolean; inline; +begin + Result := SouffleIsReference(A) and Assigned(A.AsReference) and + (A.AsReference is TGocciaWrappedValue) and + (TGocciaWrappedValue(A.AsReference).Value is TGocciaSymbolValue); +end; + +function TGocciaRuntimeOperations.Add(const A, B: TSouffleValue): TSouffleValue; +begin + try + if SouffleIsInteger(A) and SouffleIsInteger(B) then + Exit(SouffleInteger(A.AsInteger + B.AsInteger)); + if SouffleIsNumeric(A) and SouffleIsNumeric(B) then + Exit(SouffleFloat(SouffleAsNumber(A) + SouffleAsNumber(B))); + + if IsWrappedSymbol(A) or IsWrappedSymbol(B) then + ThrowTypeError('Cannot convert a Symbol value to a string'); + + if SouffleIsStringValue(A) then + Result := SouffleString(SouffleGetString(A) + CoerceToString(B)) + else if SouffleIsStringValue(B) then + Result := SouffleString(CoerceToString(A) + SouffleGetString(B)) + else + Result := SouffleFloat(CoerceToNumber(A) + CoerceToNumber(B)); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.Subtract(const A, B: TSouffleValue): TSouffleValue; +begin + try + if SouffleIsInteger(A) and SouffleIsInteger(B) then + Result := SouffleInteger(A.AsInteger - B.AsInteger) + else + Result := SouffleFloat(CoerceToNumber(A) - CoerceToNumber(B)); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.Multiply(const A, B: TSouffleValue): TSouffleValue; +begin + try + if SouffleIsInteger(A) and SouffleIsInteger(B) then + Result := SouffleInteger(A.AsInteger * B.AsInteger) + else + Result := SouffleFloat(CoerceToNumber(A) * CoerceToNumber(B)); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +// ES2026 §13.7 Multiplicative Operators — IEEE 754 handles all edge cases +// (NaN propagation, division by ±0, Infinity arithmetic, negative zero sign) +function TGocciaRuntimeOperations.Divide(const A, B: TSouffleValue): TSouffleValue; +begin + try + Result := SouffleFloat(CoerceToNumber(A) / CoerceToNumber(B)); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.Modulo(const A, B: TSouffleValue): TSouffleValue; +var + Dividend, Divisor: Double; +begin + try + if SouffleIsInteger(A) and SouffleIsInteger(B) and (B.AsInteger <> 0) then + Result := SouffleInteger(A.AsInteger mod B.AsInteger) + else + begin + Dividend := CoerceToNumber(A); + Divisor := CoerceToNumber(B); + if IsNaN(Dividend) or IsNaN(Divisor) then + Result := SouffleFloat(NaN) + else if Divisor = 0.0 then + Result := SouffleFloat(NaN) + else + Result := SouffleFloat(FMod(Dividend, Divisor)); + end; + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.Power(const A, B: TSouffleValue): TSouffleValue; +var + Base, Exp: Double; +begin + try + Base := CoerceToNumber(A); + Exp := CoerceToNumber(B); + if Exp = 0.0 then + Exit(SouffleFloat(1.0)); + if IsNaN(Exp) or IsNaN(Base) then + Exit(SouffleFloat(NaN)); + if IsInfinite(Base) then + begin + if Exp > 0 then + begin + if (Base < 0) and (Frac(Exp) = 0) and (Frac(Exp / 2) <> 0) then + Result := SouffleFloat(NegInfinity) + else + Result := SouffleFloat(Infinity); + end + else + begin + if (Base < 0) and (Frac(Exp) = 0) and (Frac(Exp / 2) <> 0) then + Result := SouffleFloat(-0.0) + else + Result := SouffleFloat(0.0); + end; + Exit; + end; + Result := SouffleFloat(Math.Power(Abs(Base), Exp)); + if (Base < 0) and (Frac(Exp) = 0) and (Frac(Exp / 2) <> 0) then + Result := SouffleFloat(-SouffleAsNumber(Result)); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.Negate(const A: TSouffleValue): TSouffleValue; +begin + try + if SouffleIsInteger(A) then + begin + if A.AsInteger = 0 then + Result := SouffleFloat(-0.0) + else + Result := SouffleInteger(-A.AsInteger); + end + else + Result := SouffleFloat(-CoerceToNumber(A)); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +{ Bitwise } + +function ToInt32(const AValue: Double): Int32; +begin + if IsNaN(AValue) or IsInfinite(AValue) or (AValue = 0.0) then + Result := 0 + else + Result := Int32(Trunc(AValue)); +end; + +function TGocciaRuntimeOperations.BitwiseAnd(const A, B: TSouffleValue): TSouffleValue; +begin + try + Result := SouffleInteger(ToInt32(CoerceToNumber(A)) and + ToInt32(CoerceToNumber(B))); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.BitwiseOr(const A, B: TSouffleValue): TSouffleValue; +begin + try + Result := SouffleInteger(ToInt32(CoerceToNumber(A)) or + ToInt32(CoerceToNumber(B))); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.BitwiseXor(const A, B: TSouffleValue): TSouffleValue; +begin + try + Result := SouffleInteger(ToInt32(CoerceToNumber(A)) xor + ToInt32(CoerceToNumber(B))); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.ShiftLeft(const A, B: TSouffleValue): TSouffleValue; +begin + try + Result := SouffleInteger(ToInt32(CoerceToNumber(A)) shl + (ToInt32(CoerceToNumber(B)) and $1F)); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.ShiftRight(const A, B: TSouffleValue): TSouffleValue; +begin + try + Result := SouffleInteger(SarLongint(ToInt32(CoerceToNumber(A)), + ToInt32(CoerceToNumber(B)) and $1F)); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.UnsignedShiftRight(const A, B: TSouffleValue): TSouffleValue; +var + LVal: Cardinal; + Shift: Integer; + FloatResult: Double; +begin + try + LVal := Cardinal(Trunc(CoerceToNumber(A))); + Shift := Trunc(CoerceToNumber(B)) and 31; + LVal := LVal shr Shift; + FloatResult := LVal; + Result := SouffleFloat(FloatResult); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.BitwiseNot(const A: TSouffleValue): TSouffleValue; +begin + try + Result := SouffleInteger(not ToInt32(CoerceToNumber(A))); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +{ Comparison } + +function WrappedValuesEqual(const A, B: TSouffleValue): Boolean; +begin + if SouffleIsReference(A) and SouffleIsReference(B) and + Assigned(A.AsReference) and Assigned(B.AsReference) and + (A.AsReference is TGocciaWrappedValue) and + (B.AsReference is TGocciaWrappedValue) then + Exit(TGocciaWrappedValue(A.AsReference).Value = + TGocciaWrappedValue(B.AsReference).Value); + Result := SouffleValuesEqual(A, B); +end; + +function TGocciaRuntimeOperations.Equal(const A, B: TSouffleValue): TSouffleValue; +begin + if SouffleIsNumeric(A) and SouffleIsNumeric(B) then + Result := SouffleBoolean(SouffleAsNumber(A) = SouffleAsNumber(B)) + else + Result := SouffleBoolean(WrappedValuesEqual(A, B)); +end; + +function TGocciaRuntimeOperations.NotEqual(const A, B: TSouffleValue): TSouffleValue; +begin + if SouffleIsNumeric(A) and SouffleIsNumeric(B) then + Result := SouffleBoolean(SouffleAsNumber(A) <> SouffleAsNumber(B)) + else + Result := SouffleBoolean(not WrappedValuesEqual(A, B)); +end; + +function AbstractRelationalComparison( + const ARuntime: TGocciaRuntimeOperations; + const A, B: TSouffleValue): Double; +var + NumA, NumB: Double; +begin + if SouffleIsStringValue(A) and SouffleIsStringValue(B) then + begin + if SouffleGetString(A) < SouffleGetString(B) then + Result := -1 + else if SouffleGetString(A) > SouffleGetString(B) then + Result := 1 + else + Result := 0; + Exit; + end; + NumA := ARuntime.CoerceToNumber(A); + NumB := ARuntime.CoerceToNumber(B); + if IsNaN(NumA) or IsNaN(NumB) then + Result := NaN + else + Result := NumA - NumB; +end; + +function TGocciaRuntimeOperations.LessThan(const A, B: TSouffleValue): TSouffleValue; +var + Cmp: Double; +begin + try + Cmp := AbstractRelationalComparison(Self, A, B); + if IsNaN(Cmp) then + Result := SouffleBoolean(False) + else + Result := SouffleBoolean(Cmp < 0); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.GreaterThan(const A, B: TSouffleValue): TSouffleValue; +var + Cmp: Double; +begin + try + Cmp := AbstractRelationalComparison(Self, A, B); + if IsNaN(Cmp) then + Result := SouffleBoolean(False) + else + Result := SouffleBoolean(Cmp > 0); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.LessThanOrEqual(const A, B: TSouffleValue): TSouffleValue; +var + Cmp: Double; +begin + try + Cmp := AbstractRelationalComparison(Self, A, B); + if IsNaN(Cmp) then + Result := SouffleBoolean(False) + else + Result := SouffleBoolean(Cmp <= 0); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.GreaterThanOrEqual(const A, B: TSouffleValue): TSouffleValue; +var + Cmp: Double; +begin + try + Cmp := AbstractRelationalComparison(Self, A, B); + if IsNaN(Cmp) then + Result := SouffleBoolean(False) + else + Result := SouffleBoolean(Cmp >= 0); + except + on E: TGocciaThrowValue do RethrowAsVM(E); + end; +end; + +{ Logical / Type } + +function TGocciaRuntimeOperations.LogicalNot(const A: TSouffleValue): TSouffleValue; +begin + Result := SouffleBoolean(not SouffleIsTrue(A)); +end; + +function TGocciaRuntimeOperations.TypeOf(const A: TSouffleValue): TSouffleValue; +begin + case A.Kind of + svkNil: + if A.Flags = GOCCIA_NIL_NULL then + Result := SouffleString('object') + else + Result := SouffleString('undefined'); + svkBoolean: + Result := SouffleString('boolean'); + svkInteger, svkFloat: + Result := SouffleString('number'); + svkString: + Result := SouffleString('string'); + svkReference: + begin + if not Assigned(A.AsReference) then + Exit(SouffleString('undefined')); + if (A.AsReference is TSouffleClosure) or + (A.AsReference is TSouffleNativeFunction) or + (A.AsReference is TSouffleBlueprint) then + Result := SouffleString('function') + else if A.AsReference is TSouffleHeapString then + Result := SouffleString('string') + else if A.AsReference is TGocciaWrappedValue then + Result := SouffleString( + TGocciaWrappedValue(A.AsReference).Value.TypeOf) + else + Result := SouffleString('object'); + end; + else + Result := SouffleString('undefined'); + end; +end; + +function TGocciaRuntimeOperations.IsInstance(const A, B: TSouffleValue): TSouffleValue; + + function ResolveClassName: string; + var + GV: TGocciaValue; + begin + if SouffleIsReference(B) and Assigned(B.AsReference) then + begin + if B.AsReference is TSouffleBlueprint then + Exit(TSouffleBlueprint(B.AsReference).Name); + GV := UnwrapToGocciaValue(B); + if GV is TGocciaClassValue then + Exit(TGocciaClassValue(GV).Name); + end; + Result := ''; + end; + +var + GocciaObj, GocciaClass: TGocciaValue; + Rec: TSouffleRecord; + Bp: TSouffleBlueprint; + ClassName: string; + R: TGocciaValue; +begin + if not SouffleIsReference(A) or not Assigned(A.AsReference) then + Exit(SouffleBoolean(False)); + + if A.AsReference is TSouffleClosure then + begin + ClassName := ResolveClassName; + Exit(SouffleBoolean(ClassName = 'Function')); + end; + + if A.AsReference is TSouffleBlueprint then + begin + ClassName := ResolveClassName; + Exit(SouffleBoolean(ClassName = 'Function')); + end; + + if A.AsReference is TSouffleRecord then + begin + Rec := TSouffleRecord(A.AsReference); + + if SouffleIsReference(B) and Assigned(B.AsReference) and + (B.AsReference is TSouffleBlueprint) then + begin + if Assigned(Rec.Blueprint) then + begin + Bp := Rec.Blueprint; + while Assigned(Bp) do + begin + if Bp = TSouffleBlueprint(B.AsReference) then + Exit(SouffleBoolean(True)); + Bp := Bp.SuperBlueprint; + end; + end; + Exit(SouffleBoolean(False)); + end; + + GocciaClass := UnwrapToGocciaValue(B); + if GocciaClass is TGocciaClassValue then + begin + ClassName := TGocciaClassValue(GocciaClass).Name; + if ClassName = CONSTRUCTOR_OBJECT then + Exit(SouffleBoolean(True)); + if Assigned(Rec.Blueprint) then + begin + Bp := Rec.Blueprint; + while Assigned(Bp) do + begin + if Bp.Name = ClassName then + Exit(SouffleBoolean(True)); + Bp := Bp.SuperBlueprint; + end; + end; + Exit(SouffleBoolean(False)); + end; + end; + + GocciaObj := UnwrapToGocciaValue(A); + GocciaClass := UnwrapToGocciaValue(B); + + R := EvaluateInstanceof(GocciaObj, GocciaClass, IsObjectInstanceOfClass); + Result := SouffleBoolean(R = TGocciaBooleanLiteralValue.TrueValue); +end; + +function TGocciaRuntimeOperations.HasProperty( + const AObject, AKey: TSouffleValue): TSouffleValue; +var + KeyStr: string; + Prop: TGocciaValue; + Idx: Int64; + GocciaVal: TGocciaValue; + Rec: TSouffleRecord; +begin + try + if SouffleIsStringValue(AObject) or + (not SouffleIsReference(AObject) or not Assigned(AObject.AsReference)) then + begin + if SouffleIsNil(AObject) or + SouffleIsBoolean(AObject) or SouffleIsInteger(AObject) or + SouffleIsFloat(AObject) or SouffleIsStringValue(AObject) then + ThrowTypeError('Cannot use ''in'' operator to search for ''' + + SouffleValueToString(AKey) + ''' in ' + SouffleValueToString(AObject)); + Exit(SouffleBoolean(False)); + end; + + if SouffleIsReference(AKey) and Assigned(AKey.AsReference) and + (AKey.AsReference is TGocciaWrappedValue) and + (TGocciaWrappedValue(AKey.AsReference).Value is TGocciaSymbolValue) then + begin + GocciaVal := UnwrapToGocciaValue(AObject); + if GocciaVal is TGocciaObjectValue then + Exit(SouffleBoolean(TGocciaObjectValue(GocciaVal).HasSymbolProperty( + TGocciaSymbolValue(TGocciaWrappedValue(AKey.AsReference).Value)))); + Exit(SouffleBoolean(False)); + end; + + KeyStr := SouffleValueToString(AKey); + + if AObject.AsReference is TSouffleRecord then + begin + Rec := TSouffleRecord(AObject.AsReference); + while Assigned(Rec) do + begin + if Rec.Has(KeyStr) then + Exit(SouffleBoolean(True)); + if Assigned(Rec.Blueprint) and Rec.Blueprint.Methods.Has(KeyStr) then + Exit(SouffleBoolean(True)); + if Assigned(Rec.Delegate) and (Rec.Delegate is TSouffleRecord) then + Rec := TSouffleRecord(Rec.Delegate) + else + Break; + end; + Exit(SouffleBoolean(False)); + end; + + if AObject.AsReference is TSouffleArray then + begin + if SouffleIsInteger(AKey) then + begin + Idx := AKey.AsInteger; + if (Idx >= 0) and (Idx < TSouffleArray(AObject.AsReference).Count) then + Exit(SouffleBoolean(not SouffleIsNil( + TSouffleArray(AObject.AsReference).Get(Integer(Idx))))) + else + Exit(SouffleBoolean(False)); + end; + if KeyStr = PROP_LENGTH then + Exit(SouffleBoolean(True)); + if TryStrToInt64(KeyStr, Idx) then + begin + if (Idx >= 0) and (Idx < TSouffleArray(AObject.AsReference).Count) then + Exit(SouffleBoolean(not SouffleIsNil( + TSouffleArray(AObject.AsReference).Get(Integer(Idx))))) + else + Exit(SouffleBoolean(False)); + end; + Prop := UnwrapToGocciaValue(AObject).GetProperty(KeyStr); + Exit(SouffleBoolean(Assigned(Prop) and + not (Prop is TGocciaUndefinedLiteralValue))); + end; + + if AObject.AsReference is TGocciaWrappedValue then + begin + GocciaVal := TGocciaWrappedValue(AObject.AsReference).Value; + if GocciaVal is TGocciaObjectValue then + Exit(SouffleBoolean(TGocciaObjectValue(GocciaVal).HasProperty(KeyStr))); + if GocciaVal is TGocciaArrayValue then + begin + if TryStrToInt64(KeyStr, Idx) then + Exit(SouffleBoolean((Idx >= 0) and (Idx < TGocciaArrayValue(GocciaVal).Elements.Count))) + else + begin + Prop := GocciaVal.GetProperty(KeyStr); + Exit(SouffleBoolean(Assigned(Prop) and not (Prop is TGocciaUndefinedLiteralValue))); + end; + end; + Prop := GocciaVal.GetProperty(KeyStr); + Exit(SouffleBoolean(Assigned(Prop) and not (Prop is TGocciaUndefinedLiteralValue))); + end; + + Result := SouffleBoolean(False); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.ToBoolean(const A: TSouffleValue): TSouffleValue; +begin + Result := SouffleBoolean(SouffleIsTrue(A)); +end; + +function TGocciaRuntimeOperations.ToPrimitive(const A: TSouffleValue): TSouffleValue; +begin + Result := CoerceToPrimitiveSouffle(Self, A); +end; + +{ Property access } + +function TGocciaRuntimeOperations.GetProperty(const AObject: TSouffleValue; + const AKey: string): TSouffleValue; +var + GocciaObj, Prop: TGocciaValue; + Boxed: TGocciaObjectValue; + Rec: TSouffleRecord; + Arr: TSouffleArray; + Bp: TSouffleBlueprint; + GetterVal: TSouffleValue; +begin + try + if AObject.Kind = svkNil then + begin + if AObject.Flags = GOCCIA_NIL_NULL then + ThrowTypeError('Cannot read properties of null (reading ''' + AKey + ''')') + else + ThrowTypeError('Cannot read properties of undefined (reading ''' + AKey + ''')'); + end; + + if SouffleIsStringValue(AObject) then + begin + if AKey = PROP_LENGTH then + Exit(SouffleInteger(Length(SouffleGetString(AObject)))); + end; + + if SouffleIsReference(AObject) and Assigned(AObject.AsReference) then + begin + if AObject.AsReference is TSouffleRecord then + begin + Rec := TSouffleRecord(AObject.AsReference); + if Rec.HasGetters and Rec.Getters.Get(AKey, GetterVal) then + begin + if SouffleIsReference(GetterVal) and + (GetterVal.AsReference is TSouffleClosure) then + Exit(FVM.ExecuteFunction( + TSouffleClosure(GetterVal.AsReference), [AObject])); + Exit(SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED)); + end; + if Rec.Get(AKey, Result) then + Exit; + if Assigned(Rec.Blueprint) then + begin + Bp := Rec.Blueprint; + while Assigned(Bp) do + begin + if Bp.HasGetters and Bp.Getters.Get(AKey, GetterVal) then + begin + if SouffleIsReference(GetterVal) and + (GetterVal.AsReference is TSouffleClosure) then + Exit(FVM.ExecuteFunction( + TSouffleClosure(GetterVal.AsReference), [AObject])); + Exit(SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED)); + end; + if Bp.Methods.Get(AKey, Result) then + Exit; + Bp := Bp.SuperBlueprint; + end; + end; + while Assigned(Rec.Delegate) and (Rec.Delegate is TSouffleRecord) do + begin + Rec := TSouffleRecord(Rec.Delegate); + if Rec.HasGetters and Rec.Getters.Get(AKey, GetterVal) then + begin + if SouffleIsReference(GetterVal) and + (GetterVal.AsReference is TSouffleClosure) then + Exit(FVM.ExecuteFunction( + TSouffleClosure(GetterVal.AsReference), [AObject])); + Exit(SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED)); + end; + if Rec.Get(AKey, Result) then + Exit; + end; + end + else if AObject.AsReference is TSouffleBlueprint then + begin + Bp := TSouffleBlueprint(AObject.AsReference); + if AKey = PROP_PROTOTYPE then + Exit(SouffleReference(Bp.Prototype)) + else if AKey = PROP_NAME then + Exit(SouffleString(Bp.Name)); + + if Bp.HasStaticGetters and Bp.StaticGetters.Get(AKey, GetterVal) then + begin + if SouffleIsReference(GetterVal) and + (GetterVal.AsReference is TSouffleClosure) then + Exit(FVM.ExecuteFunction( + TSouffleClosure(GetterVal.AsReference), [AObject])); + Exit(SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED)); + end; + + if Bp.HasStaticFields and Bp.StaticFields.Get(AKey, Result) then + Exit; + + if Bp.Methods.Get(AKey, Result) then + Exit; + + // Walk super blueprint chain for inherited static getters, fields, methods + Bp := Bp.SuperBlueprint; + while Assigned(Bp) do + begin + if Bp.HasStaticGetters and Bp.StaticGetters.Get(AKey, GetterVal) then + begin + if SouffleIsReference(GetterVal) and + (GetterVal.AsReference is TSouffleClosure) then + Exit(FVM.ExecuteFunction( + TSouffleClosure(GetterVal.AsReference), [AObject])); + Exit(SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED)); + end; + if Bp.HasStaticFields and Bp.StaticFields.Get(AKey, Result) then + Exit; + Bp := Bp.SuperBlueprint; + end; + + Exit(SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED)); + end + else if AObject.AsReference is TSouffleArray then + begin + Arr := TSouffleArray(AObject.AsReference); + if AKey = PROP_LENGTH then + Exit(SouffleInteger(Arr.Count)); + if Assigned(Arr.Delegate) and (Arr.Delegate is TSouffleRecord) and + TSouffleRecord(Arr.Delegate).Get(AKey, Result) then + Exit; + end + else if AObject.AsReference is TSouffleClosure then + begin + if AKey = PROP_NAME then + Exit(SouffleString(TSouffleClosure(AObject.AsReference).Template.Name)); + if AKey = PROP_LENGTH then + Exit(SouffleInteger( + GetFormalParameterCount(TSouffleClosure(AObject.AsReference).Template))); + end + else if AObject.AsReference is TSouffleNativeFunction then + begin + if AKey = PROP_NAME then + Exit(SouffleString( + TSouffleNativeFunction(AObject.AsReference).Name)); + if AKey = PROP_LENGTH then + Exit(SouffleInteger( + TSouffleNativeFunction(AObject.AsReference).Arity)); + end; + end; + + GocciaObj := UnwrapToGocciaValue(AObject); + Prop := GocciaObj.GetProperty(AKey); + + if not Assigned(Prop) then + begin + Boxed := GocciaObj.Box; + if Assigned(Boxed) then + Prop := Boxed.GetProperty(AKey); + end; + + if Assigned(Prop) then + Result := ToSouffleValue(Prop) + else + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; + +procedure TGocciaRuntimeOperations.SetProperty(const AObject: TSouffleValue; + const AKey: string; const AValue: TSouffleValue); +var + GocciaObj, Val: TGocciaValue; + Rec: TSouffleRecord; + Bp: TSouffleBlueprint; + SetterVal: TSouffleValue; +begin + try + if AObject.Kind = svkNil then + begin + if AObject.Flags = GOCCIA_NIL_NULL then + ThrowTypeError('Cannot set properties of null (setting ''' + AKey + ''')') + else + ThrowTypeError('Cannot set properties of undefined (setting ''' + AKey + ''')'); + end; + + if SouffleIsReference(AObject) and Assigned(AObject.AsReference) then + begin + if AObject.AsReference is TSouffleRecord then + begin + Rec := TSouffleRecord(AObject.AsReference); + if Rec.HasSetters and Rec.Setters.Get(AKey, SetterVal) then + begin + if SouffleIsReference(SetterVal) and + (SetterVal.AsReference is TSouffleClosure) then + FVM.ExecuteFunction( + TSouffleClosure(SetterVal.AsReference), [AObject, AValue]); + Exit; + end; + if Assigned(Rec.Blueprint) then + begin + Bp := Rec.Blueprint; + while Assigned(Bp) do + begin + if Bp.HasSetters and Bp.Setters.Get(AKey, SetterVal) then + begin + if SouffleIsReference(SetterVal) and + (SetterVal.AsReference is TSouffleClosure) then + FVM.ExecuteFunction( + TSouffleClosure(SetterVal.AsReference), [AObject, AValue]); + Exit; + end; + Bp := Bp.SuperBlueprint; + end; + end; + if not Rec.PutChecked(AKey, AValue) then + ThrowTypeError('Cannot assign to read only property ''' + AKey + ''''); + Exit; + end; + if AObject.AsReference is TSouffleBlueprint then + begin + Bp := TSouffleBlueprint(AObject.AsReference); + while Assigned(Bp) do + begin + if Bp.HasStaticSetters and Bp.StaticSetters.Get(AKey, SetterVal) then + begin + if SouffleIsReference(SetterVal) and + (SetterVal.AsReference is TSouffleClosure) then + FVM.ExecuteFunction( + TSouffleClosure(SetterVal.AsReference), [AObject, AValue]); + Exit; + end; + Bp := Bp.SuperBlueprint; + end; + TSouffleBlueprint(AObject.AsReference).StaticFields.Put(AKey, AValue); + Exit; + end; + if AObject.AsReference is TGocciaWrappedValue then + begin + GocciaObj := TGocciaWrappedValue(AObject.AsReference).Value; + Val := UnwrapToGocciaValue(AValue); + GocciaObj.SetProperty(AKey, Val); + end; + end; + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.CoerceKeyToString( + const AKey: TSouffleValue): string; +begin + Result := CoerceToString(AKey); +end; + +function TGocciaRuntimeOperations.GetIndex( + const AObject, AKey: TSouffleValue): TSouffleValue; +var + GocciaObj: TGocciaValue; + SymKey: TGocciaSymbolValue; + PropVal: TGocciaValue; +begin + try + if AObject.Kind = svkNil then + begin + if AObject.Flags = GOCCIA_NIL_NULL then + ThrowTypeError('Cannot read properties of null (reading ''' + CoerceKeyToString(AKey) + ''')') + else + ThrowTypeError('Cannot read properties of undefined (reading ''' + CoerceKeyToString(AKey) + ''')'); + end; + + if SouffleIsReference(AObject) and Assigned(AObject.AsReference) and + (AObject.AsReference is TSouffleArray) and (AKey.Kind = svkInteger) then + begin + Result := TSouffleArray(AObject.AsReference).Get(Integer(AKey.AsInteger)); + if SouffleIsHole(Result) then + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + end + else if SouffleIsReference(AKey) and Assigned(AKey.AsReference) and + (AKey.AsReference is TGocciaWrappedValue) and + (TGocciaWrappedValue(AKey.AsReference).Value is TGocciaSymbolValue) then + begin + SymKey := TGocciaSymbolValue(TGocciaWrappedValue(AKey.AsReference).Value); + GocciaObj := UnwrapToGocciaValue(AObject); + if GocciaObj is TGocciaClassValue then + begin + PropVal := TGocciaClassValue(GocciaObj).GetSymbolProperty(SymKey); + if Assigned(PropVal) and + not (PropVal is TGocciaUndefinedLiteralValue) then + Exit(ToSouffleValue(PropVal)); + end + else if GocciaObj is TGocciaObjectValue then + begin + PropVal := TGocciaObjectValue(GocciaObj).GetSymbolProperty(SymKey); + if Assigned(PropVal) then + Exit(ToSouffleValue(PropVal)); + end + else if GocciaObj.IsPrimitive then + begin + PropVal := nil; + if Assigned(GocciaObj.Box) then + PropVal := GocciaObj.Box.GetSymbolProperty(SymKey); + if Assigned(PropVal) then + Exit(ToSouffleValue(PropVal)); + end; + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + end + else + Result := GetProperty(AObject, CoerceKeyToString(AKey)); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; + +procedure TGocciaRuntimeOperations.SetIndex(const AObject: TSouffleValue; + const AKey, AValue: TSouffleValue); +var + GocciaObj: TGocciaValue; + SymKey: TGocciaSymbolValue; + ScopeObj: TObject; + ClassKey: TObject; +begin + try + if AObject.Kind = svkNil then + begin + if AObject.Flags = GOCCIA_NIL_NULL then + ThrowTypeError('Cannot set properties of null (setting ''' + CoerceKeyToString(AKey) + ''')') + else + ThrowTypeError('Cannot set properties of undefined (setting ''' + CoerceKeyToString(AKey) + ''')'); + end; + + if SouffleIsReference(AObject) and Assigned(AObject.AsReference) and + (AObject.AsReference is TSouffleArray) and (AKey.Kind = svkInteger) then + TSouffleArray(AObject.AsReference).Put(Integer(AKey.AsInteger), AValue) + else if SouffleIsReference(AKey) and Assigned(AKey.AsReference) and + (AKey.AsReference is TGocciaWrappedValue) and + (TGocciaWrappedValue(AKey.AsReference).Value is TGocciaSymbolValue) then + begin + SymKey := TGocciaSymbolValue(TGocciaWrappedValue(AKey.AsReference).Value); + GocciaObj := UnwrapToGocciaValue(AObject); + ClassKey := nil; + if GocciaObj is TGocciaClassValue then + begin + TGocciaClassValue(GocciaObj).AssignSymbolProperty( + SymKey, UnwrapToGocciaValue(AValue)); + ClassKey := GocciaObj; + end + else if GocciaObj is TGocciaObjectValue then + begin + TGocciaObjectValue(GocciaObj).AssignSymbolProperty( + SymKey, UnwrapToGocciaValue(AValue)); + if GocciaObj is TGocciaInstanceValue then + ClassKey := TGocciaInstanceValue(GocciaObj).ClassValue; + end; + if Assigned(ClassKey) and + FClassDefinitionScopes.TryGetValue(ClassKey, ScopeObj) then + SyncScopeToGlobals(Self, TGocciaScope(ScopeObj)); + end + else + SetProperty(AObject, CoerceKeyToString(AKey), AValue); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.DeleteProperty(const AObject: TSouffleValue; + const AKey: string): TSouffleValue; +var + GocciaObj: TGocciaValue; +begin + if SouffleIsReference(AObject) and Assigned(AObject.AsReference) then + begin + if AObject.AsReference is TSouffleRecord then + begin + if TSouffleRecord(AObject.AsReference).Has(AKey) and + (TSouffleRecord(AObject.AsReference).GetEntryFlags(AKey) + and SOUFFLE_PROP_CONFIGURABLE = 0) then + begin + try + ThrowTypeError('Cannot delete property ''' + AKey + ''''); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; + Exit(SouffleBoolean(False)); + end; + Result := SouffleBoolean(TSouffleRecord(AObject.AsReference).Delete(AKey)); + Exit; + end; + if AObject.AsReference is TGocciaWrappedValue then + begin + GocciaObj := TGocciaWrappedValue(AObject.AsReference).Value; + if GocciaObj is TGocciaObjectValue then + begin + TGocciaObjectValue(GocciaObj).DeleteProperty(AKey); + Result := SouffleBoolean(True); + Exit; + end; + end; + end; + Result := SouffleBoolean(False); +end; + +function TGocciaRuntimeOperations.DeleteIndex( + const AObject, AKey: TSouffleValue): TSouffleValue; +var + Idx: Int64; +begin + if SouffleIsReference(AObject) and (AObject.AsReference is TSouffleArray) then + begin + Idx := Trunc(SouffleAsNumber(AKey)); + if (Idx >= 0) and (Idx < TSouffleArray(AObject.AsReference).Count) then + TSouffleArray(AObject.AsReference).Put(Integer(Idx), + SouffleNilWithFlags(GOCCIA_NIL_HOLE)); + Exit(SouffleBoolean(True)); + end; + Result := DeleteProperty(AObject, CoerceKeyToString(AKey)); +end; + +{ Invocation } + +function TGocciaRuntimeOperations.Invoke(const ACallee: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer; + const AReceiver: TSouffleValue): TSouffleValue; +var + GocciaCallee, GocciaReceiver, GocciaResult: TGocciaValue; + Args: TGocciaArgumentsCollection; + I: Integer; +begin + try + if SouffleIsReference(ACallee) and Assigned(ACallee.AsReference) and + (ACallee.AsReference is TSouffleBlueprint) then + begin + ThrowTypeError('Class constructor ' + + TSouffleBlueprint(ACallee.AsReference).Name + + ' cannot be invoked without ''new'''); + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + Exit; + end; + GocciaCallee := UnwrapToGocciaValue(ACallee); + if not GocciaCallee.IsCallable then + begin + ThrowTypeError(GocciaCallee.ToStringLiteral.Value + ' is not a function'); + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + Exit; + end; + + Args := TGocciaArgumentsCollection.Create; + try + for I := 0 to AArgCount - 1 do + Args.Add(UnwrapToGocciaValue(PSouffleValue(PByte(AArgs) + I * SizeOf(TSouffleValue))^)); + + GocciaReceiver := UnwrapToGocciaValue(AReceiver); + if GocciaCallee is TGocciaFunctionBase then + GocciaResult := TGocciaFunctionBase(GocciaCallee).Call(Args, GocciaReceiver) + else if GocciaCallee is TGocciaClassValue then + GocciaResult := TGocciaClassValue(GocciaCallee).Call(Args, GocciaReceiver) + else + GocciaResult := nil; + + if Assigned(GocciaResult) then + Result := ToSouffleValue(GocciaResult) + else + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + finally + Args.Free; + end; + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.Construct(const AConstructor: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + GocciaConstructor, GocciaResult: TGocciaValue; + Args: TGocciaArgumentsCollection; + Context: TGocciaEvaluationContext; + CachedScope: TObject; + I: Integer; + Bp, WalkBp: TSouffleBlueprint; + Rec: TSouffleRecord; + CtorMethod: TSouffleValue; + VMArgs: array of TSouffleValue; +begin + PushVMFramesToGoccia(FVM); + try try + if SouffleIsReference(AConstructor) and + Assigned(AConstructor.AsReference) and + (AConstructor.AsReference is TSouffleBlueprint) then + begin + Bp := TSouffleBlueprint(AConstructor.AsReference); + Rec := TSouffleRecord.CreateFromBlueprint(Bp); + Rec.Delegate := Bp.Prototype; + WalkBp := Bp; + while Assigned(WalkBp) do + begin + if not Assigned(WalkBp.Prototype.Delegate) then + WalkBp.Prototype.Delegate := WalkBp.Methods; + if not Assigned(WalkBp.Methods.Delegate) then + begin + if Assigned(WalkBp.SuperBlueprint) then + WalkBp.Methods.Delegate := WalkBp.SuperBlueprint.Prototype + else if Assigned(FVM) then + WalkBp.Methods.Delegate := FVM.RecordDelegate; + end; + WalkBp := WalkBp.SuperBlueprint; + end; + if Assigned(TSouffleGarbageCollector.Instance) then + TSouffleGarbageCollector.Instance.AllocateObject(Rec); + + WalkBp := Bp; + CtorMethod := SouffleNil; + while Assigned(WalkBp) do + begin + if WalkBp.Methods.Get('constructor', CtorMethod) then + Break; + WalkBp := WalkBp.SuperBlueprint; + end; + + if SouffleIsReference(CtorMethod) and + (CtorMethod.AsReference is TSouffleClosure) then + begin + SetLength(VMArgs, AArgCount + 1); + VMArgs[0] := SouffleReference(Rec); + for I := 0 to AArgCount - 1 do + VMArgs[1 + I] := PSouffleValue(PByte(AArgs) + I * SizeOf(TSouffleValue))^; + FVM.ExecuteFunction(TSouffleClosure(CtorMethod.AsReference), VMArgs); + end; + + Result := SouffleReference(Rec); + Exit; + end; + + GocciaConstructor := UnwrapToGocciaValue(AConstructor); + + if (GocciaConstructor is TGocciaClassValue) then + begin + Args := TGocciaArgumentsCollection.Create; + try + for I := 0 to AArgCount - 1 do + Args.Add(UnwrapToGocciaValue(PSouffleValue(PByte(AArgs) + I * SizeOf(TSouffleValue))^)); + + if FClassDefinitionScopes.TryGetValue(GocciaConstructor, CachedScope) then + begin + Context := TGocciaEngine(FEngine).Interpreter.CreateEvaluationContext; + Context.Scope := TGocciaScope(CachedScope); + RebuildArrayBridgeCache(Self, TGocciaScope(CachedScope)); + end + else + Context := CreateBridgedContext(Self); + + GocciaResult := InstantiateClass( + TGocciaClassValue(GocciaConstructor), Args, Context); + + SyncArraysBack(Self, Context.Scope); + FArrayBridgeCache.Clear; + + if Assigned(GocciaResult) then + Result := ToSouffleValue(GocciaResult) + else + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + finally + Args.Free; + end; + Exit; + end; + + if GocciaConstructor is TGocciaNativeFunctionValue then + begin + Args := TGocciaArgumentsCollection.Create; + try + for I := 0 to AArgCount - 1 do + Args.Add(UnwrapToGocciaValue(PSouffleValue(PByte(AArgs) + I * SizeOf(TSouffleValue))^)); + + if Assigned(TGocciaCallStack.Instance) then + TGocciaCallStack.Instance.Push( + TGocciaNativeFunctionValue(GocciaConstructor).Name, '', 0, 0); + try + GocciaResult := TGocciaNativeFunctionValue(GocciaConstructor).Call( + Args, TGocciaUndefinedLiteralValue.UndefinedValue); + finally + if Assigned(TGocciaCallStack.Instance) then + TGocciaCallStack.Instance.Pop; + end; + + if Assigned(GocciaResult) then + Result := ToSouffleValue(GocciaResult) + else + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + finally + Args.Free; + end; + Exit; + end; + + ThrowTypeError(GocciaConstructor.TypeName + ' is not a constructor'); + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; + finally + PopVMFramesFromGoccia(FVM); + end; +end; + +{ Iteration } + +function TGocciaRuntimeOperations.GetIterator( + const AIterable: TSouffleValue; + const ATryAsync: Boolean): TSouffleValue; +var + GocciaVal: TGocciaValue; + Iterator: TGocciaIteratorValue; + IteratorMethod, IteratorObj, NextMethod: TGocciaValue; + CallArgs: TGocciaArgumentsCollection; +begin + try + GocciaVal := UnwrapToGocciaValue(AIterable); + + Iterator := nil; + + if ATryAsync and (GocciaVal is TGocciaObjectValue) then + begin + IteratorMethod := TGocciaObjectValue(GocciaVal).GetSymbolProperty( + TGocciaSymbolValue.WellKnownAsyncIterator); + if Assigned(IteratorMethod) and + not (IteratorMethod is TGocciaUndefinedLiteralValue) and + IteratorMethod.IsCallable then + begin + CallArgs := TGocciaArgumentsCollection.Create; + try + IteratorObj := TGocciaFunctionBase(IteratorMethod).Call( + CallArgs, GocciaVal); + finally + CallArgs.Free; + end; + if Assigned(IteratorObj) then + begin + if Assigned(TGocciaGarbageCollector.Instance) then + TGocciaGarbageCollector.Instance.AddTempRoot(IteratorObj); + Result := WrapGocciaValue(IteratorObj); + Exit; + end; + end; + end; + + if Iterator = nil then + begin + if GocciaVal is TGocciaIteratorValue then + Iterator := TGocciaIteratorValue(GocciaVal) + else if GocciaVal is TGocciaStringLiteralValue then + Iterator := TGocciaStringIteratorValue.Create(GocciaVal) + else if GocciaVal is TGocciaObjectValue then + begin + IteratorMethod := TGocciaObjectValue(GocciaVal).GetSymbolProperty( + TGocciaSymbolValue.WellKnownIterator); + if Assigned(IteratorMethod) and + not (IteratorMethod is TGocciaUndefinedLiteralValue) and + IteratorMethod.IsCallable then + begin + CallArgs := TGocciaArgumentsCollection.Create; + try + IteratorObj := TGocciaFunctionBase(IteratorMethod).Call( + CallArgs, GocciaVal); + finally + CallArgs.Free; + end; + if IteratorObj is TGocciaIteratorValue then + Iterator := TGocciaIteratorValue(IteratorObj) + else if IteratorObj is TGocciaObjectValue then + begin + NextMethod := IteratorObj.GetProperty(PROP_NEXT); + if Assigned(NextMethod) and + not (NextMethod is TGocciaUndefinedLiteralValue) and + NextMethod.IsCallable then + Iterator := TGocciaGenericIteratorValue.Create(IteratorObj); + end; + end; + end; + end; + + if Iterator = nil then + begin + ThrowTypeError(GocciaVal.TypeName + ' is not iterable'); + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + Exit; + end; + + if Assigned(TGocciaGarbageCollector.Instance) then + TGocciaGarbageCollector.Instance.AddTempRoot(Iterator); + Result := WrapGocciaValue(Iterator); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.IteratorNext(const AIterator: TSouffleValue; + out ADone: Boolean): TSouffleValue; +var + GocciaVal: TGocciaValue; + Iterator: TGocciaIteratorValue; + IterResult: TGocciaObjectValue; + Value, NextMethod, NextResult, Resolved, DoneValue: TGocciaValue; + CallArgs: TGocciaArgumentsCollection; +begin + ADone := True; + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + try + GocciaVal := UnwrapToGocciaValue(AIterator); + + if GocciaVal is TGocciaIteratorValue then + begin + Iterator := TGocciaIteratorValue(GocciaVal); + IterResult := Iterator.AdvanceNext; + + ADone := IterResult.GetProperty(PROP_DONE).ToBooleanLiteral.Value; + if not ADone then + begin + Value := IterResult.GetProperty(PROP_VALUE); + Result := ToSouffleValue(Value); + end; + end + else if GocciaVal is TGocciaObjectValue then + begin + NextMethod := GocciaVal.GetProperty(PROP_NEXT); + if not Assigned(NextMethod) or not NextMethod.IsCallable then + Exit; + + CallArgs := TGocciaArgumentsCollection.Create; + try + NextResult := TGocciaFunctionBase(NextMethod).Call(CallArgs, GocciaVal); + finally + CallArgs.Free; + end; + + Resolved := Goccia.Evaluator.AwaitValue(NextResult); + if Resolved.IsPrimitive then + begin + ThrowTypeError('Iterator result ' + + Resolved.ToStringLiteral.Value + ' is not an object'); + Exit; + end; + + DoneValue := Resolved.GetProperty(PROP_DONE); + ADone := Assigned(DoneValue) and DoneValue.ToBooleanLiteral.Value; + if not ADone then + begin + Value := Resolved.GetProperty(PROP_VALUE); + if not Assigned(Value) then + Value := TGocciaUndefinedLiteralValue.UndefinedValue; + Result := ToSouffleValue(Value); + end; + end; + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; + +procedure TGocciaRuntimeOperations.SpreadInto( + const ATarget, ASource: TSouffleValue); +var + TgtArr: TSouffleArray; + SrcArr: TSouffleArray; + I: Integer; + StrVal: string; + IterVal: TSouffleValue; + ElemVal: TSouffleValue; + Done: Boolean; +begin + try + if not (SouffleIsReference(ATarget) and (ATarget.AsReference is TSouffleArray)) then + Exit; + + TgtArr := TSouffleArray(ATarget.AsReference); + + if SouffleIsNil(ASource) then + begin + ThrowTypeError('Cannot spread a non-iterable value'); + Exit; + end; + + if SouffleIsStringValue(ASource) then + begin + StrVal := SouffleGetString(ASource); + for I := 1 to Length(StrVal) do + TgtArr.Push(SouffleString(StrVal[I])); + Exit; + end; + + if not SouffleIsReference(ASource) then + begin + ThrowTypeError('Cannot spread a non-iterable value'); + Exit; + end; + + if ASource.AsReference is TSouffleArray then + begin + SrcArr := TSouffleArray(ASource.AsReference); + for I := 0 to SrcArr.Count - 1 do + TgtArr.Push(SrcArr.Get(I)); + Exit; + end; + + IterVal := GetIterator(ASource); + repeat + ElemVal := IteratorNext(IterVal, Done); + if not Done then + TgtArr.Push(ElemVal); + until Done; + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; + +procedure TGocciaRuntimeOperations.SpreadObjectInto( + const ATarget, ASource: TSouffleValue); +var + SrcRec: TSouffleRecord; + SrcGoccia, TgtGoccia: TGocciaValue; + SrcObj, TgtObj: TGocciaObjectValue; + Key: string; + I: Integer; + SymPair: TPair; +begin + if not SouffleIsReference(ATarget) or not Assigned(ATarget.AsReference) then + Exit; + if SouffleIsNil(ASource) then + Exit; + + if ATarget.AsReference is TSouffleRecord then + begin + if SouffleIsReference(ASource) and (ASource.AsReference is TSouffleRecord) then + begin + SrcRec := TSouffleRecord(ASource.AsReference); + for I := 0 to SrcRec.Count - 1 do + begin + Key := SrcRec.GetOrderedKey(I); + if SrcRec.GetEntryFlags(Key) and SOUFFLE_PROP_ENUMERABLE <> 0 then + TSouffleRecord(ATarget.AsReference).Put(Key, SrcRec.GetOrderedValue(I)); + end; + if SrcRec.HasGetters then + for I := 0 to SrcRec.Getters.Count - 1 do + begin + Key := SrcRec.Getters.GetOrderedKey(I); + TSouffleRecord(ATarget.AsReference).Put(Key, + GetProperty(ASource, Key)); + end; + SrcGoccia := UnwrapToGocciaValue(ASource); + TgtGoccia := UnwrapToGocciaValue(ATarget); + if (SrcGoccia is TGocciaObjectValue) and (TgtGoccia is TGocciaObjectValue) then + for SymPair in TGocciaObjectValue(SrcGoccia).GetEnumerableSymbolProperties do + TGocciaObjectValue(TgtGoccia).AssignSymbolProperty(SymPair.Key, SymPair.Value); + Exit; + end; + if SouffleIsStringValue(ASource) then + begin + Key := SouffleGetString(ASource); + for I := 1 to Length(Key) do + TSouffleRecord(ATarget.AsReference).Put(IntToStr(I - 1), + SouffleString(Key[I])); + Exit; + end; + if SouffleIsReference(ASource) and Assigned(ASource.AsReference) and + (ASource.AsReference is TGocciaWrappedValue) then + begin + SrcGoccia := TGocciaWrappedValue(ASource.AsReference).Value; + if SrcGoccia is TGocciaStringLiteralValue then + begin + Key := TGocciaStringLiteralValue(SrcGoccia).Value; + for I := 1 to Length(Key) do + TSouffleRecord(ATarget.AsReference).Put(IntToStr(I - 1), + SouffleString(Key[I])); + end + else if SrcGoccia is TGocciaObjectValue then + begin + for Key in TGocciaObjectValue(SrcGoccia).GetEnumerablePropertyNames do + TSouffleRecord(ATarget.AsReference).Put(Key, + ToSouffleValue(TGocciaObjectValue(SrcGoccia).GetProperty(Key))); + TgtGoccia := UnwrapToGocciaValue(ATarget); + if TgtGoccia is TGocciaObjectValue then + for SymPair in TGocciaObjectValue(SrcGoccia).GetEnumerableSymbolProperties do + TGocciaObjectValue(TgtGoccia).AssignSymbolProperty(SymPair.Key, SymPair.Value); + end + else if SrcGoccia is TGocciaArrayValue then + begin + for I := 0 to TGocciaArrayValue(SrcGoccia).Elements.Count - 1 do + if TGocciaArrayValue(SrcGoccia).Elements[I] <> nil then + TSouffleRecord(ATarget.AsReference).Put(IntToStr(I), + ToSouffleValue(TGocciaArrayValue(SrcGoccia).Elements[I])); + end; + Exit; + end; + end; + + if ATarget.AsReference is TGocciaWrappedValue then + TgtGoccia := TGocciaWrappedValue(ATarget.AsReference).Value + else + TgtGoccia := nil; + + if not (TgtGoccia is TGocciaObjectValue) then + Exit; + TgtObj := TGocciaObjectValue(TgtGoccia); + + if SouffleIsReference(ASource) and Assigned(ASource.AsReference) then + begin + if ASource.AsReference is TSouffleRecord then + begin + SrcRec := TSouffleRecord(ASource.AsReference); + for I := 0 to SrcRec.Count - 1 do + begin + Key := SrcRec.GetOrderedKey(I); + if SrcRec.GetEntryFlags(Key) and SOUFFLE_PROP_ENUMERABLE <> 0 then + TgtObj.SetProperty(Key, UnwrapToGocciaValue(SrcRec.GetOrderedValue(I))); + end; + if SrcRec.HasGetters then + for I := 0 to SrcRec.Getters.Count - 1 do + begin + Key := SrcRec.Getters.GetOrderedKey(I); + TgtObj.SetProperty(Key, + UnwrapToGocciaValue(GetProperty(ASource, Key))); + end; + end + else if ASource.AsReference is TGocciaWrappedValue then + begin + SrcGoccia := TGocciaWrappedValue(ASource.AsReference).Value; + if SrcGoccia is TGocciaObjectValue then + begin + SrcObj := TGocciaObjectValue(SrcGoccia); + for Key in SrcObj.GetEnumerablePropertyNames do + TgtObj.SetProperty(Key, SrcObj.GetProperty(Key)); + for SymPair in SrcObj.GetEnumerableSymbolProperties do + TgtObj.AssignSymbolProperty(SymPair.Key, SymPair.Value); + end + else if SrcGoccia is TGocciaArrayValue then + begin + for I := 0 to TGocciaArrayValue(SrcGoccia).Elements.Count - 1 do + if TGocciaArrayValue(SrcGoccia).Elements[I] <> nil then + TgtObj.SetProperty(IntToStr(I), TGocciaArrayValue(SrcGoccia).Elements[I]); + end + else if SrcGoccia is TGocciaStringLiteralValue then + begin + for I := 1 to Length(TGocciaStringLiteralValue(SrcGoccia).Value) do + TgtObj.SetProperty(IntToStr(I - 1), + TGocciaStringLiteralValue.Create(TGocciaStringLiteralValue(SrcGoccia).Value[I])); + end; + end; + end + else if SouffleIsStringValue(ASource) then + begin + for I := 1 to Length(SouffleGetString(ASource)) do + TgtObj.SetProperty(IntToStr(I - 1), + TGocciaStringLiteralValue.Create(SouffleGetString(ASource)[I])); + end; +end; + +{ Rest Operations } + +function TGocciaRuntimeOperations.ObjectRest( + const ASource, AExclusionKeys: TSouffleValue): TSouffleValue; +var + GocciaVal: TGocciaValue; + SrcObj: TGocciaObjectValue; + RestObj: TGocciaObjectValue; + SrcRec: TSouffleRecord; + ExclArr: TSouffleArray; + I, J: Integer; + Key: string; + Excluded: Boolean; + PropNames: TArray; +begin + if not SouffleIsReference(ASource) or not Assigned(ASource.AsReference) then + Exit(SouffleNil); + + if SouffleIsReference(AExclusionKeys) and + (AExclusionKeys.AsReference is TSouffleArray) then + ExclArr := TSouffleArray(AExclusionKeys.AsReference) + else + ExclArr := nil; + + if ASource.AsReference is TSouffleRecord then + begin + SrcRec := TSouffleRecord(ASource.AsReference); + RestObj := TGocciaObjectValue.Create; + for I := 0 to SrcRec.Count - 1 do + begin + Key := SrcRec.GetOrderedKey(I); + Excluded := False; + if Assigned(ExclArr) then + for J := 0 to ExclArr.Count - 1 do + if SouffleValueToString(ExclArr.Get(J)) = Key then + begin + Excluded := True; + Break; + end; + if not Excluded then + RestObj.SetProperty(Key, UnwrapToGocciaValue(SrcRec.GetOrderedValue(I))); + end; + Exit(ToSouffleValue(RestObj)); + end; + + if ASource.AsReference is TGocciaWrappedValue then + begin + GocciaVal := TGocciaWrappedValue(ASource.AsReference).Value; + if GocciaVal is TGocciaObjectValue then + begin + SrcObj := TGocciaObjectValue(GocciaVal); + RestObj := TGocciaObjectValue.Create; + PropNames := SrcObj.GetOwnPropertyNames; + for I := 0 to High(PropNames) do + begin + Key := PropNames[I]; + Excluded := False; + if Assigned(ExclArr) then + for J := 0 to ExclArr.Count - 1 do + if SouffleValueToString(ExclArr.Get(J)) = Key then + begin + Excluded := True; + Break; + end; + if not Excluded then + RestObj.SetProperty(Key, SrcObj.GetProperty(Key)); + end; + Exit(ToSouffleValue(RestObj)); + end; + end; + + Result := SouffleNil; +end; + +{ Modules } + +function TGocciaRuntimeOperations.ImportModule(const APath: string): TSouffleValue; +var + EngineObj: TGocciaEngine; + Module: TGocciaModule; + Rec: TSouffleRecord; + Pair: TPair; +begin + if not Assigned(FEngine) then + Exit(SouffleNil); + + EngineObj := TGocciaEngine(FEngine); + Module := EngineObj.Interpreter.LoadModule(APath, FSourcePath); + if not Assigned(Module) then + Exit(SouffleNil); + + Rec := TSouffleRecord.Create(Module.ExportsTable.Count); + if Assigned(TSouffleGarbageCollector.Instance) then + TSouffleGarbageCollector.Instance.AllocateObject(Rec); + + for Pair in Module.ExportsTable do + Rec.Put(Pair.Key, ToSouffleValue(Pair.Value)); + + Result := SouffleReference(Rec); +end; + +procedure TGocciaRuntimeOperations.ExportBinding(const AValue: TSouffleValue; + const AName: string); +begin + FExports.AddOrSetValue(AName, AValue); +end; + +{ Async } + +function TGocciaRuntimeOperations.AwaitValue(const AValue: TSouffleValue): TSouffleValue; +var + GocciaVal, Resolved: TGocciaValue; +begin + try + GocciaVal := UnwrapToGocciaValue(AValue); + Resolved := Goccia.Evaluator.AwaitValue(GocciaVal); + Result := ToSouffleValue(Resolved); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; + +function TGocciaRuntimeOperations.WrapInPromise(const AValue: TSouffleValue; + const AIsRejected: Boolean): TSouffleValue; +var + Promise: TGocciaPromiseValue; + GocciaVal: TGocciaValue; +begin + Promise := TGocciaPromiseValue.Create; + GocciaVal := UnwrapToGocciaValue(AValue); + if AIsRejected then + Promise.Reject(GocciaVal) + else + Promise.Resolve(GocciaVal); + if Assigned(TGocciaMicrotaskQueue.Instance) then + TGocciaMicrotaskQueue.Instance.DrainQueue; + Result := WrapGocciaValue(Promise); +end; + +{ Globals } + +function TGocciaRuntimeOperations.GetGlobal(const AName: string): TSouffleValue; +var + Value: TSouffleValue; +begin + if FGlobals.TryGetValue(AName, Value) then + Result := Value + else + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); +end; + +procedure TGocciaRuntimeOperations.SetGlobal(const AName: string; + const AValue: TSouffleValue); +begin + if FConstGlobals.ContainsKey(AName) then + begin + try + ThrowTypeError('Assignment to constant variable ''' + AName + ''''); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; + Exit; + end; + if not FGlobals.ContainsKey(AName) then + begin + try + ThrowReferenceError('Undefined variable: ' + AName); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; + Exit; + end; + FGlobals.AddOrSetValue(AName, AValue); +end; + +function TGocciaRuntimeOperations.HasGlobal(const AName: string): Boolean; +begin + Result := FGlobals.ContainsKey(AName); +end; + +procedure TGocciaRuntimeOperations.DefineGetter(const AObject: TSouffleValue; + const AKey: string; const AGetter: TSouffleValue); +var + Bp: TSouffleBlueprint; + Rec: TSouffleRecord; + Obj: TGocciaObjectValue; + GocciaGetter: TGocciaValue; + Existing: TGocciaPropertyDescriptor; + ExistingSetter: TGocciaValue; +begin + if SouffleIsReference(AObject) and Assigned(AObject.AsReference) then + begin + if AObject.AsReference is TSouffleBlueprint then + begin + Bp := TSouffleBlueprint(AObject.AsReference); + Bp.Getters.Put(AKey, AGetter); + Exit; + end; + if AObject.AsReference is TSouffleRecord then + begin + Rec := TSouffleRecord(AObject.AsReference); + Rec.Getters.Put(AKey, AGetter); + Exit; + end; + end; + + Obj := UnwrapToGocciaValue(AObject) as TGocciaObjectValue; + GocciaGetter := UnwrapToGocciaValue(AGetter); + + ExistingSetter := nil; + Existing := Obj.GetOwnPropertyDescriptor(AKey); + if (Existing <> nil) and (Existing is TGocciaPropertyDescriptorAccessor) then + ExistingSetter := TGocciaPropertyDescriptorAccessor(Existing).Setter; + + Obj.DefineProperty(AKey, TGocciaPropertyDescriptorAccessor.Create( + GocciaGetter, ExistingSetter, [pfEnumerable, pfConfigurable])); +end; + +procedure TGocciaRuntimeOperations.DefineSetter(const AObject: TSouffleValue; + const AKey: string; const ASetter: TSouffleValue); +var + Bp: TSouffleBlueprint; + Rec: TSouffleRecord; + Obj: TGocciaObjectValue; + GocciaSetter: TGocciaValue; + Existing: TGocciaPropertyDescriptor; + ExistingGetter: TGocciaValue; +begin + if SouffleIsReference(AObject) and Assigned(AObject.AsReference) then + begin + if AObject.AsReference is TSouffleBlueprint then + begin + Bp := TSouffleBlueprint(AObject.AsReference); + Bp.Setters.Put(AKey, ASetter); + Exit; + end; + if AObject.AsReference is TSouffleRecord then + begin + Rec := TSouffleRecord(AObject.AsReference); + Rec.Setters.Put(AKey, ASetter); + Exit; + end; + end; + + Obj := UnwrapToGocciaValue(AObject) as TGocciaObjectValue; + GocciaSetter := UnwrapToGocciaValue(ASetter); + + ExistingGetter := nil; + Existing := Obj.GetOwnPropertyDescriptor(AKey); + if (Existing <> nil) and (Existing is TGocciaPropertyDescriptorAccessor) then + ExistingGetter := TGocciaPropertyDescriptorAccessor(Existing).Getter; + + Obj.DefineProperty(AKey, TGocciaPropertyDescriptorAccessor.Create( + ExistingGetter, GocciaSetter, [pfEnumerable, pfConfigurable])); +end; + +procedure TGocciaRuntimeOperations.DefineStaticGetter( + const AObject: TSouffleValue; const AKey: string; + const AGetter: TSouffleValue); +var + Bp: TSouffleBlueprint; +begin + if SouffleIsReference(AObject) and Assigned(AObject.AsReference) and + (AObject.AsReference is TSouffleBlueprint) then + begin + Bp := TSouffleBlueprint(AObject.AsReference); + Bp.StaticGetters.Put(AKey, AGetter); + end; +end; + +procedure TGocciaRuntimeOperations.DefineStaticSetter( + const AObject: TSouffleValue; const AKey: string; + const ASetter: TSouffleValue); +var + Bp: TSouffleBlueprint; +begin + if SouffleIsReference(AObject) and Assigned(AObject.AsReference) and + (AObject.AsReference is TSouffleBlueprint) then + begin + Bp := TSouffleBlueprint(AObject.AsReference); + Bp.StaticSetters.Put(AKey, ASetter); + end; +end; + +{ Property violations } + +procedure TGocciaRuntimeOperations.PropertyWriteViolation( + const AObject: TSouffleValue; const AKey: string); +begin + try + ThrowTypeError( + 'Cannot assign to read only property ''' + AKey + ''''); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; + +procedure TGocciaRuntimeOperations.ThrowTypeErrorMessage( + const AMessage: string); +begin + try + ThrowTypeError(AMessage); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; + +procedure TGocciaRuntimeOperations.PropertyDeleteViolation( + const AObject: TSouffleValue; const AKey: string); +begin + try + ThrowTypeError( + 'Cannot delete property ''' + AKey + ''''); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; + +{ Proxy resolution } + +function TGocciaRuntimeOperations.ResolveProxyGet( + const ATarget: TSouffleHeapObject; const AName: string): TGocciaValue; +var + Arr: TSouffleArray; + Rec: TSouffleRecord; + Index: Integer; + Val: TSouffleValue; + TempArr: TGocciaArrayValue; +begin + if ATarget is TSouffleArray then + begin + Arr := TSouffleArray(ATarget); + if AName = PROP_LENGTH then + Exit(TGocciaNumberLiteralValue.Create(Arr.Count * 1.0)); + if TryStrToInt(AName, Index) and (Index >= 0) and (Index < Arr.Count) then + Exit(UnwrapToGocciaValue(Arr.Get(Index))); + TempArr := ConvertSouffleArrayToGocciaArray(Self, Arr); + Result := TempArr.GetProperty(AName); + end + else if ATarget is TSouffleRecord then + begin + Rec := TSouffleRecord(ATarget); + if Rec.HasGetters and Rec.Getters.Get(AName, Val) then + Exit(UnwrapToGocciaValue( + GetProperty(SouffleReference(ATarget), AName))); + if Rec.Get(AName, Val) then + Exit(UnwrapToGocciaValue(Val)); + if Assigned(Rec.Blueprint) and Rec.Blueprint.Methods.Get(AName, Val) then + Exit(UnwrapToGocciaValue(Val)); + while Assigned(Rec.Delegate) do + begin + if Rec.Delegate is TSouffleRecord then + begin + Rec := TSouffleRecord(Rec.Delegate); + if Rec.HasGetters and Rec.Getters.Get(AName, Val) then + Exit(UnwrapToGocciaValue( + GetProperty(SouffleReference(ATarget), AName))); + if Rec.Get(AName, Val) then + Exit(UnwrapToGocciaValue(Val)); + if Assigned(Rec.Blueprint) and Rec.Blueprint.Methods.Get(AName, Val) then + Exit(UnwrapToGocciaValue(Val)); + end + else + Break; + end; + Result := nil; + end + else + Result := nil; +end; + +procedure TGocciaRuntimeOperations.ResolveProxySet( + const ATarget: TSouffleHeapObject; const AName: string; + const AValue: TGocciaValue); +var + Arr: TSouffleArray; + Rec: TSouffleRecord; + Index: Integer; +begin + if ATarget is TSouffleArray then + begin + Arr := TSouffleArray(ATarget); + if TryStrToInt(AName, Index) and (Index >= 0) then + Arr.Put(Index, ToSouffleValue(AValue)); + end + else if ATarget is TSouffleRecord then + begin + Rec := TSouffleRecord(ATarget); + Rec.Put(AName, ToSouffleValue(AValue)); + end; +end; + +{ Native delegate callbacks } + +procedure SyncSouffleArrayToCache(const AArr: TSouffleArray); forward; + +function NativeArrayPush(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + I: Integer; +begin + if SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray) then + begin + Arr := TSouffleArray(AReceiver.AsReference); + for I := 0 to AArgCount - 1 do + Arr.Push(PSouffleValue(PByte(AArgs) + I * SizeOf(TSouffleValue))^); + SyncSouffleArrayToCache(Arr); + Result := SouffleInteger(Arr.Count); + end + else + Result := SouffleNil; +end; + +function NativeArrayPop(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; +begin + if SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray) then + begin + Arr := TSouffleArray(AReceiver.AsReference); + Result := Arr.Pop; + if SouffleIsHole(Result) then + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + SyncSouffleArrayToCache(Arr); + end + else + Result := SouffleNil; +end; + +function NativeArrayLength(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +begin + if SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray) then + Result := SouffleInteger(TSouffleArray(AReceiver.AsReference).Count) + else + Result := SouffleInteger(0); +end; + +function JoinElementToString(const AElem: TSouffleValue; + const ARuntime: TGocciaRuntimeOperations): string; +var + GocciaVal: TGocciaValue; + StrVal: TGocciaStringLiteralValue; + StrResult: TSouffleValue; +begin + if SouffleIsNil(AElem) then + Exit(''); + if SouffleIsStringValue(AElem) then + Exit(SouffleGetString(AElem)); + if SouffleIsReference(AElem) and Assigned(AElem.AsReference) then + begin + if AElem.AsReference is TSouffleArray then + Exit(JoinArrayElements(TSouffleArray(AElem.AsReference), ',', ARuntime)); + if AElem.AsReference is TSouffleRecord then + begin + StrResult := ARuntime.CoerceValueToString(AElem); + if SouffleIsStringValue(StrResult) then + Exit(SouffleGetString(StrResult)); + Exit('[object Object]'); + end; + end; + + GocciaVal := ARuntime.UnwrapToGocciaValue(AElem); + if (GocciaVal is TGocciaUndefinedLiteralValue) or + (GocciaVal is TGocciaNullLiteralValue) then + Exit(''); + StrVal := Goccia.Values.ToPrimitive.ToECMAString(GocciaVal); + Result := StrVal.Value; +end; + +function JoinArrayElements(const AArr: TSouffleArray; + const ASep: string; const ARuntime: TGocciaRuntimeOperations): string; +var + I: Integer; +begin + Result := ''; + for I := 0 to AArr.Count - 1 do + begin + if I > 0 then + Result := Result + ASep; + Result := Result + JoinElementToString(AArr.Get(I), ARuntime); + end; +end; + +var + GNativeArrayJoinRuntime: TGocciaRuntimeOperations; + +procedure SyncSouffleArrayToCache(const AArr: TSouffleArray); +var + CachedBridge: TObject; + GArr: TGocciaArrayValue; + J: Integer; +begin + if not Assigned(GNativeArrayJoinRuntime) then Exit; + if not GNativeArrayJoinRuntime.FArrayBridgeCache.TryGetValue( + AArr, CachedBridge) then + Exit; + GArr := TGocciaArrayValue(CachedBridge); + GArr.Elements.Clear; + for J := 0 to AArr.Count - 1 do + GArr.Elements.Add( + GNativeArrayJoinRuntime.UnwrapToGocciaValue(AArr.Get(J))); +end; + +function NativeArrayJoin(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + Sep: string; +begin + if SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray) then + begin + Arr := TSouffleArray(AReceiver.AsReference); + Sep := ','; + if (AArgCount > 0) and not SouffleIsNil(AArgs^) then + Sep := GNativeArrayJoinRuntime.CoerceToString(AArgs^); + Result := SouffleString( + JoinArrayElements(Arr, Sep, GNativeArrayJoinRuntime)); + end + else + Result := SouffleString(''); +end; + +function NativeArrayToString(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +begin + Result := NativeArrayJoin(AReceiver, nil, 0); +end; + +function NativeArrayIndexOf(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + I, StartFrom, Len: Integer; + SearchVal, Arg2: TSouffleValue; +begin + Result := SouffleInteger(-1); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if AArgCount < 1 then Exit; + + Arr := TSouffleArray(AReceiver.AsReference); + Len := Arr.Count; + SearchVal := AArgs^; + StartFrom := 0; + if AArgCount > 1 then + begin + Arg2 := PSouffleValue(PByte(AArgs) + SizeOf(TSouffleValue))^; + if Arg2.Kind = svkInteger then + StartFrom := Arg2.AsInteger + else if Arg2.Kind = svkFloat then + StartFrom := Trunc(Arg2.AsFloat); + if StartFrom < 0 then + begin + StartFrom := Len + StartFrom; + if StartFrom < 0 then StartFrom := 0; + end; + end; + if StartFrom >= Len then Exit; + + for I := StartFrom to Len - 1 do + begin + if SouffleIsHole(Arr.Get(I)) then Continue; + if SouffleValuesEqual(Arr.Get(I), SearchVal) then + Exit(SouffleInteger(I)); + end; +end; + +function SouffleSameValueZero(const A, B: TSouffleValue): Boolean; +var + F: Double; +begin + if (A.Kind = svkFloat) and (B.Kind = svkFloat) then + begin + if IsNaN(A.AsFloat) and IsNaN(B.AsFloat) then + Exit(True); + Exit(A.AsFloat = B.AsFloat); + end; + if (A.Kind = svkFloat) and (B.Kind = svkInteger) then + begin + F := B.AsInteger; + Exit(A.AsFloat = F); + end; + if (A.Kind = svkInteger) and (B.Kind = svkFloat) then + begin + F := A.AsInteger; + Exit(F = B.AsFloat); + end; + Result := SouffleValuesEqual(A, B); +end; + +function NativeArrayIncludes(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + I, StartFrom, Len: Integer; + SearchVal, Arg2: TSouffleValue; +begin + Result := SouffleBoolean(False); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if AArgCount < 1 then Exit; + + Arr := TSouffleArray(AReceiver.AsReference); + Len := Arr.Count; + SearchVal := AArgs^; + StartFrom := 0; + if AArgCount > 1 then + begin + Arg2 := PSouffleValue(PByte(AArgs) + SizeOf(TSouffleValue))^; + if Arg2.Kind = svkInteger then + StartFrom := Arg2.AsInteger + else if Arg2.Kind = svkFloat then + StartFrom := Trunc(Arg2.AsFloat); + if StartFrom < 0 then + begin + StartFrom := Len + StartFrom; + if StartFrom < 0 then StartFrom := 0; + end; + end; + if StartFrom >= Len then Exit; + + for I := StartFrom to Len - 1 do + begin + if SouffleIsHole(Arr.Get(I)) then + begin + if SouffleIsNil(SearchVal) and (SearchVal.Flags = GOCCIA_NIL_UNDEFINED) then + Exit(SouffleBoolean(True)); + Continue; + end; + if SouffleSameValueZero(Arr.Get(I), SearchVal) then + Exit(SouffleBoolean(True)); + end; +end; + +function NativeArraySlice(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr, NewArr: TSouffleArray; + Start, Stop, I, Len: Integer; + Arg: TSouffleValue; +begin + Result := SouffleNil; + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + + Arr := TSouffleArray(AReceiver.AsReference); + Len := Arr.Count; + Start := 0; + Stop := Len; + + if AArgCount > 0 then + begin + Arg := AArgs^; + if Arg.Kind = svkInteger then Start := Arg.AsInteger + else if Arg.Kind = svkFloat then Start := Trunc(Arg.AsFloat); + if Start < 0 then Start := Len + Start; + if Start < 0 then Start := 0; + end; + if AArgCount > 1 then + begin + Arg := PSouffleValue(PByte(AArgs) + SizeOf(TSouffleValue))^; + if Arg.Kind = svkInteger then Stop := Arg.AsInteger + else if Arg.Kind = svkFloat then Stop := Trunc(Arg.AsFloat); + if Stop < 0 then Stop := Len + Stop; + if Stop > Len then Stop := Len; + end; + + NewArr := TSouffleArray.Create(Stop - Start); + if Assigned(TSouffleGarbageCollector.Instance) then + TSouffleGarbageCollector.Instance.AllocateObject(NewArr); + for I := Start to Stop - 1 do + NewArr.Push(Arr.Get(I)); + Result := SouffleReference(NewArr); +end; + +function NativeArrayReverse(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + I, Half: Integer; + Tmp: TSouffleValue; +begin + Result := AReceiver; + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Half := Arr.Count div 2; + for I := 0 to Half - 1 do + begin + Tmp := Arr.Get(I); + Arr.Put(I, Arr.Get(Arr.Count - 1 - I)); + Arr.Put(Arr.Count - 1 - I, Tmp); + end; + SyncSouffleArrayToCache(Arr); +end; + +function NativeArrayConcat(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr, SrcArr, NewArr: TSouffleArray; + I, J: Integer; + Arg: TSouffleValue; +begin + Result := SouffleNil; + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + + Arr := TSouffleArray(AReceiver.AsReference); + NewArr := TSouffleArray.Create(Arr.Count); + if Assigned(TSouffleGarbageCollector.Instance) then + TSouffleGarbageCollector.Instance.AllocateObject(NewArr); + + for I := 0 to Arr.Count - 1 do + NewArr.Push(Arr.Get(I)); + + for J := 0 to AArgCount - 1 do + begin + Arg := PSouffleValue(PByte(AArgs) + J * SizeOf(TSouffleValue))^; + if SouffleIsReference(Arg) and Assigned(Arg.AsReference) and + (Arg.AsReference is TSouffleArray) then + begin + SrcArr := TSouffleArray(Arg.AsReference); + for I := 0 to SrcArr.Count - 1 do + NewArr.Push(SrcArr.Get(I)); + end + else + NewArr.Push(Arg); + end; + Result := SouffleReference(NewArr); +end; + +function NativeArrayShift(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + I: Integer; +begin + Result := SouffleNil; + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + if Arr.Count = 0 then Exit; + Result := Arr.Get(0); + for I := 1 to Arr.Count - 1 do + Arr.Put(I - 1, Arr.Get(I)); + Arr.Pop; + SyncSouffleArrayToCache(Arr); +end; + +function NativeArrayUnshift(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + I, J, OldCount: Integer; +begin + Result := SouffleInteger(0); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + if AArgCount < 1 then + Exit(SouffleInteger(Arr.Count)); + OldCount := Arr.Count; + for I := 0 to AArgCount - 1 do + Arr.Push(SouffleNil); + for I := OldCount - 1 downto 0 do + Arr.Put(I + AArgCount, Arr.Get(I)); + for J := 0 to AArgCount - 1 do + Arr.Put(J, PSouffleValue(PByte(AArgs) + J * SizeOf(TSouffleValue))^); + SyncSouffleArrayToCache(Arr); + Result := SouffleInteger(Arr.Count); +end; + +function NativeArrayFill(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + FillVal: TSouffleValue; + I, Start, Stop, Len: Integer; + Arg: TSouffleValue; +begin + Result := AReceiver; + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Len := Arr.Count; + if AArgCount < 1 then + FillVal := SouffleNil + else + FillVal := AArgs^; + Start := 0; + Stop := Len; + if AArgCount > 1 then + begin + Arg := PSouffleValue(PByte(AArgs) + SizeOf(TSouffleValue))^; + if Arg.Kind = svkInteger then Start := Arg.AsInteger + else if Arg.Kind = svkFloat then Start := Trunc(Arg.AsFloat); + if Start < 0 then Start := Len + Start; + if Start < 0 then Start := 0; + end; + if AArgCount > 2 then + begin + Arg := PSouffleValue(PByte(AArgs) + 2 * SizeOf(TSouffleValue))^; + if Arg.Kind = svkInteger then Stop := Arg.AsInteger + else if Arg.Kind = svkFloat then Stop := Trunc(Arg.AsFloat); + if Stop < 0 then Stop := Len + Stop; + if Stop > Len then Stop := Len; + end; + for I := Start to Stop - 1 do + Arr.Put(I, FillVal); + SyncSouffleArrayToCache(Arr); +end; + +function NativeArrayAt(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + Idx: Integer; +begin + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if AArgCount < 1 then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + if AArgs^.Kind = svkInteger then + Idx := AArgs^.AsInteger + else if AArgs^.Kind = svkFloat then + Idx := Trunc(AArgs^.AsFloat) + else + Exit; + if Idx < 0 then Idx := Arr.Count + Idx; + if (Idx >= 0) and (Idx < Arr.Count) then + Result := Arr.Get(Idx); +end; + +function NativeArrayForEach(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + Callback: TSouffleClosure; + I: Integer; +begin + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if (AArgCount < 1) or not SouffleIsReference(AArgs^) or + not (AArgs^.AsReference is TSouffleClosure) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Callback := TSouffleClosure(AArgs^.AsReference); + for I := 0 to Arr.Count - 1 do + begin + if SouffleIsHole(Arr.Get(I)) then Continue; + GNativeArrayJoinRuntime.VM.ExecuteFunction(Callback, + [SouffleNil, Arr.Get(I), SouffleInteger(I), AReceiver]); + end; +end; + +function NativeArrayMap(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr, NewArr: TSouffleArray; + Callback: TSouffleClosure; + I: Integer; +begin + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if (AArgCount < 1) or not SouffleIsReference(AArgs^) or + not (AArgs^.AsReference is TSouffleClosure) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Callback := TSouffleClosure(AArgs^.AsReference); + NewArr := TSouffleArray.Create(Arr.Count); + NewArr.Delegate := GNativeArrayJoinRuntime.VM.ArrayDelegate; + if Assigned(TSouffleGarbageCollector.Instance) then + TSouffleGarbageCollector.Instance.AllocateObject(NewArr); + for I := 0 to Arr.Count - 1 do + begin + if SouffleIsHole(Arr.Get(I)) then + begin + NewArr.Push(SouffleNilWithFlags(GOCCIA_NIL_HOLE)); + Continue; + end; + NewArr.Push(GNativeArrayJoinRuntime.VM.ExecuteFunction(Callback, + [SouffleNil, Arr.Get(I), SouffleInteger(I), AReceiver])); + end; + Result := SouffleReference(NewArr); +end; + +function NativeArrayFilter(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr, NewArr: TSouffleArray; + Callback: TSouffleClosure; + I: Integer; + Elem, TestResult: TSouffleValue; +begin + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if (AArgCount < 1) or not SouffleIsReference(AArgs^) or + not (AArgs^.AsReference is TSouffleClosure) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Callback := TSouffleClosure(AArgs^.AsReference); + NewArr := TSouffleArray.Create(Arr.Count); + NewArr.Delegate := GNativeArrayJoinRuntime.VM.ArrayDelegate; + if Assigned(TSouffleGarbageCollector.Instance) then + TSouffleGarbageCollector.Instance.AllocateObject(NewArr); + for I := 0 to Arr.Count - 1 do + begin + Elem := Arr.Get(I); + if SouffleIsHole(Elem) then Continue; + TestResult := GNativeArrayJoinRuntime.VM.ExecuteFunction(Callback, + [SouffleNil, Elem, SouffleInteger(I), AReceiver]); + if SouffleIsTrue(TestResult) then + NewArr.Push(Elem); + end; + Result := SouffleReference(NewArr); +end; + +function NativeArrayFind(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + Callback: TSouffleClosure; + I: Integer; + Elem, TestResult: TSouffleValue; +begin + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if (AArgCount < 1) or not SouffleIsReference(AArgs^) or + not (AArgs^.AsReference is TSouffleClosure) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Callback := TSouffleClosure(AArgs^.AsReference); + for I := 0 to Arr.Count - 1 do + begin + Elem := Arr.Get(I); + if SouffleIsHole(Elem) then Continue; + TestResult := GNativeArrayJoinRuntime.VM.ExecuteFunction(Callback, + [SouffleNil, Elem, SouffleInteger(I), AReceiver]); + if SouffleIsTrue(TestResult) then + Exit(Elem); + end; +end; + +function NativeArrayFindIndex(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + Callback: TSouffleClosure; + I: Integer; + TestResult: TSouffleValue; +begin + Result := SouffleInteger(-1); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if (AArgCount < 1) or not SouffleIsReference(AArgs^) or + not (AArgs^.AsReference is TSouffleClosure) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Callback := TSouffleClosure(AArgs^.AsReference); + for I := 0 to Arr.Count - 1 do + begin + if SouffleIsHole(Arr.Get(I)) then Continue; + TestResult := GNativeArrayJoinRuntime.VM.ExecuteFunction(Callback, + [SouffleNil, Arr.Get(I), SouffleInteger(I), AReceiver]); + if SouffleIsTrue(TestResult) then + Exit(SouffleInteger(I)); + end; +end; + +function NativeArrayEvery(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + Callback: TSouffleClosure; + I: Integer; + TestResult: TSouffleValue; +begin + Result := SouffleBoolean(True); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if (AArgCount < 1) or not SouffleIsReference(AArgs^) or + not (AArgs^.AsReference is TSouffleClosure) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Callback := TSouffleClosure(AArgs^.AsReference); + for I := 0 to Arr.Count - 1 do + begin + if SouffleIsHole(Arr.Get(I)) then Continue; + TestResult := GNativeArrayJoinRuntime.VM.ExecuteFunction(Callback, + [SouffleNil, Arr.Get(I), SouffleInteger(I), AReceiver]); + if not SouffleIsTrue(TestResult) then + Exit(SouffleBoolean(False)); + end; +end; + +function NativeArraySome(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + Callback: TSouffleClosure; + I: Integer; + TestResult: TSouffleValue; +begin + Result := SouffleBoolean(False); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if (AArgCount < 1) or not SouffleIsReference(AArgs^) or + not (AArgs^.AsReference is TSouffleClosure) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Callback := TSouffleClosure(AArgs^.AsReference); + for I := 0 to Arr.Count - 1 do + begin + if SouffleIsHole(Arr.Get(I)) then Continue; + TestResult := GNativeArrayJoinRuntime.VM.ExecuteFunction(Callback, + [SouffleNil, Arr.Get(I), SouffleInteger(I), AReceiver]); + if SouffleIsTrue(TestResult) then + Exit(SouffleBoolean(True)); + end; +end; + +function NativeArrayReduce(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + Callback: TSouffleClosure; + I, StartIdx: Integer; + Accumulator: TSouffleValue; +begin + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if (AArgCount < 1) or not SouffleIsReference(AArgs^) or + not (AArgs^.AsReference is TSouffleClosure) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Callback := TSouffleClosure(AArgs^.AsReference); + if AArgCount > 1 then + begin + Accumulator := PSouffleValue(PByte(AArgs) + SizeOf(TSouffleValue))^; + StartIdx := 0; + end + else + begin + if Arr.Count = 0 then + begin + try + ThrowTypeError('Reduce of empty array with no initial value'); + except + on E: TGocciaThrowValue do + GNativeArrayJoinRuntime.RethrowAsVM(E); + end; + Exit; + end; + StartIdx := 0; + while (StartIdx < Arr.Count) and SouffleIsHole(Arr.Get(StartIdx)) do + Inc(StartIdx); + if StartIdx >= Arr.Count then + begin + try + ThrowTypeError('Reduce of empty array with no initial value'); + except + on E: TGocciaThrowValue do + GNativeArrayJoinRuntime.RethrowAsVM(E); + end; + Exit; + end; + Accumulator := Arr.Get(StartIdx); + Inc(StartIdx); + end; + for I := StartIdx to Arr.Count - 1 do + begin + if SouffleIsHole(Arr.Get(I)) then Continue; + Accumulator := GNativeArrayJoinRuntime.VM.ExecuteFunction(Callback, + [SouffleNil, Accumulator, Arr.Get(I), SouffleInteger(I), AReceiver]); + end; + Result := Accumulator; +end; + +function NativeArrayReduceRight(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + Callback: TSouffleClosure; + I, StartIdx: Integer; + Accumulator: TSouffleValue; +begin + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if (AArgCount < 1) or not SouffleIsReference(AArgs^) or + not (AArgs^.AsReference is TSouffleClosure) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Callback := TSouffleClosure(AArgs^.AsReference); + if AArgCount > 1 then + begin + Accumulator := PSouffleValue(PByte(AArgs) + SizeOf(TSouffleValue))^; + StartIdx := Arr.Count - 1; + end + else + begin + if Arr.Count = 0 then Exit; + StartIdx := Arr.Count - 1; + while (StartIdx >= 0) and SouffleIsHole(Arr.Get(StartIdx)) do + Dec(StartIdx); + if StartIdx < 0 then Exit; + Accumulator := Arr.Get(StartIdx); + Dec(StartIdx); + end; + for I := StartIdx downto 0 do + begin + if SouffleIsHole(Arr.Get(I)) then Continue; + Accumulator := GNativeArrayJoinRuntime.VM.ExecuteFunction(Callback, + [SouffleNil, Accumulator, Arr.Get(I), SouffleInteger(I), AReceiver]); + end; + Result := Accumulator; +end; + +function NativeArraySort(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + HasComparator: Boolean; + Comparator: TSouffleClosure; + I, J, Len: Integer; + Tmp, CmpResult: TSouffleValue; + CmpVal: Double; + StrA, StrB: string; +begin + Result := AReceiver; + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Len := Arr.Count; + if Len <= 1 then Exit; + HasComparator := (AArgCount > 0) and SouffleIsReference(AArgs^) and + (AArgs^.AsReference is TSouffleClosure); + if HasComparator then + Comparator := TSouffleClosure(AArgs^.AsReference) + else + Comparator := nil; + for I := 0 to Len - 2 do + for J := 0 to Len - 2 - I do + begin + if HasComparator then + begin + CmpResult := GNativeArrayJoinRuntime.VM.ExecuteFunction(Comparator, + [SouffleNil, Arr.Get(J), Arr.Get(J + 1)]); + if CmpResult.Kind = svkInteger then + CmpVal := CmpResult.AsInteger + else if CmpResult.Kind = svkFloat then + CmpVal := CmpResult.AsFloat + else + CmpVal := 0; + end + else + begin + StrA := GNativeArrayJoinRuntime.CoerceToString(Arr.Get(J)); + StrB := GNativeArrayJoinRuntime.CoerceToString(Arr.Get(J + 1)); + if StrA > StrB then CmpVal := 1 + else if StrA < StrB then CmpVal := -1 + else CmpVal := 0; + end; + if CmpVal > 0 then + begin + Tmp := Arr.Get(J); + Arr.Put(J, Arr.Get(J + 1)); + Arr.Put(J + 1, Tmp); + end; + end; + SyncSouffleArrayToCache(Arr); +end; + +function NativeArrayFindLast(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + Callback: TSouffleClosure; + I: Integer; + Elem, TestResult: TSouffleValue; +begin + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if (AArgCount < 1) or not SouffleIsReference(AArgs^) or + not (AArgs^.AsReference is TSouffleClosure) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Callback := TSouffleClosure(AArgs^.AsReference); + for I := Arr.Count - 1 downto 0 do + begin + Elem := Arr.Get(I); + TestResult := GNativeArrayJoinRuntime.VM.ExecuteFunction(Callback, + [SouffleNil, Elem, SouffleInteger(I), AReceiver]); + if SouffleIsTrue(TestResult) then + Exit(Elem); + end; +end; + +function NativeArrayFindLastIndex(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr: TSouffleArray; + Callback: TSouffleClosure; + I: Integer; + TestResult: TSouffleValue; +begin + Result := SouffleInteger(-1); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if (AArgCount < 1) or not SouffleIsReference(AArgs^) or + not (AArgs^.AsReference is TSouffleClosure) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Callback := TSouffleClosure(AArgs^.AsReference); + for I := Arr.Count - 1 downto 0 do + begin + TestResult := GNativeArrayJoinRuntime.VM.ExecuteFunction(Callback, + [SouffleNil, Arr.Get(I), SouffleInteger(I), AReceiver]); + if SouffleIsTrue(TestResult) then + Exit(SouffleInteger(I)); + end; +end; + +function NativeArrayFlat(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; + + procedure FlattenInto(const ASrc: TSouffleArray; const ADest: TSouffleArray; + const ADepth: Integer); + var + I: Integer; + Elem: TSouffleValue; + Inner: TSouffleArray; + begin + for I := 0 to ASrc.Count - 1 do + begin + Elem := ASrc.Get(I); + if SouffleIsHole(Elem) then Continue; + if (ADepth > 0) and SouffleIsReference(Elem) and + Assigned(Elem.AsReference) and (Elem.AsReference is TSouffleArray) then + begin + Inner := TSouffleArray(Elem.AsReference); + FlattenInto(Inner, ADest, ADepth - 1); + end + else + ADest.Push(Elem); + end; + end; + +var + Arr, NewArr: TSouffleArray; + Depth: Integer; + Arg: TSouffleValue; + DepthF: Double; +begin + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Depth := 1; + if AArgCount > 0 then + begin + Arg := AArgs^; + if Arg.Kind = svkInteger then + Depth := Arg.AsInteger + else if Arg.Kind = svkFloat then + begin + DepthF := Arg.AsFloat; + if IsInfinite(DepthF) and (DepthF > 0) then + Depth := MaxInt + else if IsNaN(DepthF) or (DepthF < 0) then + Depth := 0 + else + Depth := Trunc(DepthF); + end; + end; + NewArr := TSouffleArray.Create(Arr.Count); + NewArr.Delegate := GNativeArrayJoinRuntime.VM.ArrayDelegate; + if Assigned(TSouffleGarbageCollector.Instance) then + TSouffleGarbageCollector.Instance.AllocateObject(NewArr); + FlattenInto(Arr, NewArr, Depth); + Result := SouffleReference(NewArr); +end; + +function NativeArrayFlatMap(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr, NewArr: TSouffleArray; + Callback: TSouffleClosure; + I, J: Integer; + Mapped: TSouffleValue; + MappedArr: TSouffleArray; +begin + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if (AArgCount < 1) or not SouffleIsReference(AArgs^) or + not (AArgs^.AsReference is TSouffleClosure) then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Callback := TSouffleClosure(AArgs^.AsReference); + NewArr := TSouffleArray.Create(Arr.Count); + NewArr.Delegate := GNativeArrayJoinRuntime.VM.ArrayDelegate; + if Assigned(TSouffleGarbageCollector.Instance) then + TSouffleGarbageCollector.Instance.AllocateObject(NewArr); + for I := 0 to Arr.Count - 1 do + begin + if SouffleIsHole(Arr.Get(I)) then Continue; + Mapped := GNativeArrayJoinRuntime.VM.ExecuteFunction(Callback, + [SouffleNil, Arr.Get(I), SouffleInteger(I), AReceiver]); + if SouffleIsReference(Mapped) and Assigned(Mapped.AsReference) and + (Mapped.AsReference is TSouffleArray) then + begin + MappedArr := TSouffleArray(Mapped.AsReference); + for J := 0 to MappedArr.Count - 1 do + begin + if not SouffleIsHole(MappedArr.Get(J)) then + NewArr.Push(MappedArr.Get(J)); + end; + end + else + NewArr.Push(Mapped); + end; + Result := SouffleReference(NewArr); +end; + +function NativeArraySplice(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Arr, Removed, TmpArr: TSouffleArray; + Start, DeleteCount, Len, I, InsertCount: Integer; + Arg: TSouffleValue; begin - if SouffleIsNumeric(A) and SouffleIsNumeric(B) then - Result := SouffleBoolean(SouffleAsNumber(A) >= SouffleAsNumber(B)) + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleArray)) then Exit; + if AArgCount < 1 then Exit; + Arr := TSouffleArray(AReceiver.AsReference); + Len := Arr.Count; + + Arg := AArgs^; + if Arg.Kind = svkInteger then Start := Arg.AsInteger + else if Arg.Kind = svkFloat then Start := Trunc(Arg.AsFloat) + else Start := 0; + if Start < 0 then Start := Len + Start; + if Start < 0 then Start := 0; + if Start > Len then Start := Len; + + if AArgCount > 1 then + begin + Arg := PSouffleValue(PByte(AArgs) + SizeOf(TSouffleValue))^; + if Arg.Kind = svkInteger then DeleteCount := Arg.AsInteger + else if Arg.Kind = svkFloat then DeleteCount := Trunc(Arg.AsFloat) + else DeleteCount := 0; + if DeleteCount < 0 then DeleteCount := 0; + if DeleteCount > Len - Start then DeleteCount := Len - Start; + end else - Result := SouffleBoolean(SouffleValueToString(A) >= SouffleValueToString(B)); + DeleteCount := Len - Start; + + InsertCount := AArgCount - 2; + if InsertCount < 0 then InsertCount := 0; + + Removed := TSouffleArray.Create(DeleteCount); + Removed.Delegate := GNativeArrayJoinRuntime.VM.ArrayDelegate; + if Assigned(TSouffleGarbageCollector.Instance) then + TSouffleGarbageCollector.Instance.AllocateObject(Removed); + for I := 0 to DeleteCount - 1 do + Removed.Push(Arr.Get(Start + I)); + + TmpArr := TSouffleArray.Create(Len - DeleteCount + InsertCount); + for I := 0 to Start - 1 do + TmpArr.Push(Arr.Get(I)); + for I := 0 to InsertCount - 1 do + TmpArr.Push(PSouffleValue(PByte(AArgs) + (I + 2) * SizeOf(TSouffleValue))^); + for I := Start + DeleteCount to Len - 1 do + TmpArr.Push(Arr.Get(I)); + + Arr.Clear; + for I := 0 to TmpArr.Count - 1 do + Arr.Push(TmpArr.Get(I)); + SyncSouffleArrayToCache(Arr); + + Result := SouffleReference(Removed); end; -{ Logical / Type } +function NativeRecordHasOwnProperty(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +var + Rec: TSouffleRecord; + Key: string; + GocciaObj: TGocciaValue; +begin + Result := SouffleBoolean(False); + if not SouffleIsReference(AReceiver) or not Assigned(AReceiver.AsReference) then + Exit; + if AArgCount < 1 then + Key := 'undefined' + else + Key := GNativeArrayJoinRuntime.CoerceToString(AArgs^); + if AReceiver.AsReference is TSouffleRecord then + begin + Rec := TSouffleRecord(AReceiver.AsReference); + Result := SouffleBoolean(Rec.Has(Key)); + end + else if AReceiver.AsReference is TGocciaWrappedValue then + begin + GocciaObj := TGocciaWrappedValue(AReceiver.AsReference).Value; + if GocciaObj is TGocciaObjectValue then + Result := SouffleBoolean(TGocciaObjectValue(GocciaObj).HasOwnProperty(Key)); + end; +end; -function TGocciaRuntimeOperations.LogicalNot(const A: TSouffleValue): TSouffleValue; +function NativeRecordToString(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; begin - Result := SouffleBoolean(not SouffleIsTrue(A)); + Result := SouffleString('[object Object]'); end; -function TGocciaRuntimeOperations.TypeOf(const A: TSouffleValue): TSouffleValue; +function NativeRecordValueOf(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +begin + Result := AReceiver; +end; + +function NativeRecordIsPrototypeOf(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; var - GocciaVal: TGocciaValue; - TypeStr: string; + Target: TSouffleValue; + Walk: TSouffleHeapObject; + ReceiverObj: TSouffleHeapObject; + ReceiverGoccia: TGocciaValue; + TargetGoccia: TGocciaValue; + GocciaProto: TGocciaObjectValue; + Proxy: TGocciaSouffleProxy; begin - GocciaVal := UnwrapToGocciaValue(A); - TypeStr := GocciaVal.TypeOf; - Result := SouffleReference(TSouffleString.Create(TypeStr)); + Result := SouffleBoolean(False); + if AArgCount < 1 then Exit; + Target := AArgs^; + if not SouffleIsReference(Target) or not Assigned(Target.AsReference) then Exit; + if not SouffleIsReference(AReceiver) or not Assigned(AReceiver.AsReference) then Exit; + ReceiverObj := AReceiver.AsReference; + + Walk := Target.AsReference.Delegate; + while Assigned(Walk) do + begin + if Walk = ReceiverObj then + Exit(SouffleBoolean(True)); + Walk := Walk.Delegate; + end; + + ReceiverGoccia := nil; + if ReceiverObj is TGocciaWrappedValue then + ReceiverGoccia := TGocciaWrappedValue(ReceiverObj).Value; + + TargetGoccia := nil; + if Target.AsReference is TGocciaWrappedValue then + TargetGoccia := TGocciaWrappedValue(Target.AsReference).Value; + + if Assigned(TargetGoccia) and (TargetGoccia is TGocciaObjectValue) then + begin + GocciaProto := TGocciaObjectValue(TargetGoccia).Prototype; + while Assigned(GocciaProto) do + begin + if Assigned(ReceiverGoccia) and (GocciaProto = ReceiverGoccia) then + Exit(SouffleBoolean(True)); + if GocciaProto is TGocciaSouffleProxy then + begin + Proxy := TGocciaSouffleProxy(GocciaProto); + if Proxy.Target = ReceiverObj then + Exit(SouffleBoolean(True)); + end; + GocciaProto := GocciaProto.Prototype; + end; + end; end; -function TGocciaRuntimeOperations.IsInstance(const A, B: TSouffleValue): TSouffleValue; +function NativeRecordPropertyIsEnumerable(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; var - GocciaObj, GocciaClass: TGocciaValue; + Rec: TSouffleRecord; + Key: string; + Flags: Byte; begin - GocciaObj := UnwrapToGocciaValue(A); - GocciaClass := UnwrapToGocciaValue(B); - if (GocciaObj is TGocciaInstanceValue) and (GocciaClass is TGocciaClassValue) then - Result := SouffleBoolean( - TGocciaInstanceValue(GocciaObj).IsInstanceOf(TGocciaClassValue(GocciaClass))) + Result := SouffleBoolean(False); + if not (SouffleIsReference(AReceiver) and Assigned(AReceiver.AsReference) + and (AReceiver.AsReference is TSouffleRecord)) then Exit; + Rec := TSouffleRecord(AReceiver.AsReference); + if AArgCount < 1 then + Key := 'undefined' else - Result := SouffleBoolean(False); + Key := SouffleValueToString(AArgs^); + if not Rec.Has(Key) then Exit; + Flags := Rec.GetEntryFlags(Key); + Result := SouffleBoolean(Flags and SOUFFLE_PROP_ENUMERABLE <> 0); end; -function TGocciaRuntimeOperations.HasProperty( - const AObject, AKey: TSouffleValue): TSouffleValue; +function NativeRecordToLocaleString(const AReceiver: TSouffleValue; + const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; var - KeyStr: string; - Prop: TGocciaValue; + ToStr: TSouffleValue; begin - KeyStr := SouffleValueToString(AKey); - if SouffleIsReference(AObject) and - (AObject.AsReference is TGocciaWrappedValue) then - begin - Prop := TGocciaWrappedValue(AObject.AsReference).Value.GetProperty(KeyStr); - Result := SouffleBoolean(Assigned(Prop) and - not (Prop is TGocciaUndefinedLiteralValue)); - end + ToStr := GNativeArrayJoinRuntime.GetProperty(AReceiver, 'toString'); + if SouffleIsReference(ToStr) and Assigned(ToStr.AsReference) and + (ToStr.AsReference is TSouffleClosure) then + Result := GNativeArrayJoinRuntime.VM.ExecuteFunction( + TSouffleClosure(ToStr.AsReference), [AReceiver]) + else if SouffleIsReference(ToStr) and Assigned(ToStr.AsReference) and + (ToStr.AsReference is TSouffleNativeFunction) then + Result := TSouffleNativeFunction(ToStr.AsReference).Invoke(AReceiver, nil, 0) else - Result := SouffleBoolean(False); + Result := SouffleString('[object Object]'); end; -function TGocciaRuntimeOperations.ToBoolean(const A: TSouffleValue): TSouffleValue; +const + ARRAY_PROTOTYPE_METHODS: array[0..27] of TSouffleMethodEntry = ( + (Name: 'toString'; Arity: 0; Callback: @NativeArrayToString), + (Name: 'push'; Arity: 1; Callback: @NativeArrayPush), + (Name: 'pop'; Arity: 0; Callback: @NativeArrayPop), + (Name: 'join'; Arity: 1; Callback: @NativeArrayJoin), + (Name: 'indexOf'; Arity: 1; Callback: @NativeArrayIndexOf), + (Name: 'includes'; Arity: 1; Callback: @NativeArrayIncludes), + (Name: 'slice'; Arity: 2; Callback: @NativeArraySlice), + (Name: 'reverse'; Arity: 0; Callback: @NativeArrayReverse), + (Name: 'concat'; Arity: 1; Callback: @NativeArrayConcat), + (Name: 'shift'; Arity: 0; Callback: @NativeArrayShift), + (Name: 'unshift'; Arity: 1; Callback: @NativeArrayUnshift), + (Name: 'fill'; Arity: 1; Callback: @NativeArrayFill), + (Name: 'at'; Arity: 1; Callback: @NativeArrayAt), + (Name: 'forEach'; Arity: 1; Callback: @NativeArrayForEach), + (Name: 'map'; Arity: 1; Callback: @NativeArrayMap), + (Name: 'filter'; Arity: 1; Callback: @NativeArrayFilter), + (Name: 'find'; Arity: 1; Callback: @NativeArrayFind), + (Name: 'findIndex'; Arity: 1; Callback: @NativeArrayFindIndex), + (Name: 'every'; Arity: 1; Callback: @NativeArrayEvery), + (Name: 'some'; Arity: 1; Callback: @NativeArraySome), + (Name: 'reduce'; Arity: 1; Callback: @NativeArrayReduce), + (Name: 'reduceRight'; Arity: 1; Callback: @NativeArrayReduceRight), + (Name: 'sort'; Arity: 0; Callback: @NativeArraySort), + (Name: 'findLast'; Arity: 1; Callback: @NativeArrayFindLast), + (Name: 'findLastIndex'; Arity: 1; Callback: @NativeArrayFindLastIndex), + (Name: 'flat'; Arity: 0; Callback: @NativeArrayFlat), + (Name: 'flatMap'; Arity: 1; Callback: @NativeArrayFlatMap), + (Name: 'splice'; Arity: 2; Callback: @NativeArraySplice) + ); + + RECORD_PROTOTYPE_METHODS: array[0..5] of TSouffleMethodEntry = ( + (Name: 'hasOwnProperty'; Arity: 1; Callback: @NativeRecordHasOwnProperty), + (Name: 'isPrototypeOf'; Arity: 1; Callback: @NativeRecordIsPrototypeOf), + (Name: 'propertyIsEnumerable'; Arity: 1; Callback: @NativeRecordPropertyIsEnumerable), + (Name: 'toString'; Arity: 0; Callback: @NativeRecordToString), + (Name: 'valueOf'; Arity: 0; Callback: @NativeRecordValueOf), + (Name: 'toLocaleString'; Arity: 0; Callback: @NativeRecordToLocaleString) + ); + +procedure TGocciaRuntimeOperations.RegisterDelegates; begin - Result := SouffleBoolean(SouffleIsTrue(A)); + if not Assigned(FVM) then Exit; + + RegisterConstGlobal('undefined', SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED)); + RegisterConstGlobal('NaN', SouffleFloat(NaN)); + RegisterConstGlobal('Infinity', SouffleFloat(Infinity)); + + GNativeArrayJoinRuntime := Self; + + FVM.ArrayDelegate := TSouffleRecord( + BuildDelegate(ARRAY_PROTOTYPE_METHODS)); + FVM.RecordDelegate := TSouffleRecord( + BuildDelegate(RECORD_PROTOTYPE_METHODS)); end; -{ Compound creation } +procedure TGocciaRuntimeOperations.RegisterFormalParameterCount( + const ATemplate: TSouffleFunctionTemplate; const ACount: Integer); +begin + FFormalParameterCounts.AddOrSetValue(ATemplate, ACount); +end; -function TGocciaRuntimeOperations.CreateCompound(const ATypeTag: UInt8): TSouffleValue; -var - GocciaVal: TGocciaValue; +function TGocciaRuntimeOperations.GetFormalParameterCount( + const ATemplate: TSouffleFunctionTemplate): Integer; begin - case ATypeTag of - 1: GocciaVal := TGocciaArrayValue.Create; - else - GocciaVal := TGocciaObjectValue.Create; - end; - Result := WrapGocciaValue(GocciaVal); + if not FFormalParameterCounts.TryGetValue(ATemplate, Result) then + Result := -1; end; -procedure TGocciaRuntimeOperations.InitField(const ACompound: TSouffleValue; - const AKey: string; const AValue: TSouffleValue); +procedure TGocciaRuntimeOperations.RegisterGlobal(const AName: string; + const AValue: TSouffleValue); +begin + FGlobals.AddOrSetValue(AName, AValue); +end; + +procedure TGocciaRuntimeOperations.RegisterConstGlobal(const AName: string; + const AValue: TSouffleValue); +begin + FGlobals.AddOrSetValue(AName, AValue); + FConstGlobals.AddOrSetValue(AName, True); +end; + +function TGocciaRuntimeOperations.RequireIterable( + const AValue: TSouffleValue): TSouffleValue; var - Wrapped: TGocciaValue; - Val: TGocciaValue; + Arr: TSouffleArray; + IterVal, ElemVal: TSouffleValue; + Done: Boolean; + StrVal: string; + I: Integer; begin - if SouffleIsReference(ACompound) and - (ACompound.AsReference is TGocciaWrappedValue) then - begin - Wrapped := TGocciaWrappedValue(ACompound.AsReference).Value; - Val := UnwrapToGocciaValue(AValue); - Wrapped.SetProperty(AKey, Val); + try + if SouffleIsReference(AValue) and Assigned(AValue.AsReference) and + (AValue.AsReference is TSouffleArray) then + Exit(AValue); + + if SouffleIsNil(AValue) then + begin + if AValue.Flags = GOCCIA_NIL_NULL then + ThrowTypeError('Cannot destructure null as it is not iterable') + else + ThrowTypeError('Cannot destructure undefined as it is not iterable'); + end; + + Arr := TSouffleArray.Create(4); + + if SouffleIsStringValue(AValue) then + begin + StrVal := SouffleGetString(AValue); + for I := 1 to Length(StrVal) do + Arr.Push(SouffleString(StrVal[I])); + Exit(SouffleReference(Arr)); + end; + + IterVal := GetIterator(AValue); + repeat + ElemVal := IteratorNext(IterVal, Done); + if not Done then + Arr.Push(ElemVal); + until Done; + Result := SouffleReference(Arr); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); end; end; -procedure TGocciaRuntimeOperations.InitIndex(const ACompound: TSouffleValue; - const AIndex: TSouffleValue; const AValue: TSouffleValue); -var - Wrapped: TGocciaValue; - Val: TGocciaValue; +procedure TGocciaRuntimeOperations.RequireObjectCoercible( + const AValue: TSouffleValue); begin - if SouffleIsReference(ACompound) and - (ACompound.AsReference is TGocciaWrappedValue) then - begin - Wrapped := TGocciaWrappedValue(ACompound.AsReference).Value; - Val := UnwrapToGocciaValue(AValue); - if Wrapped is TGocciaArrayValue then - TGocciaArrayValue(Wrapped).Elements.Add(Val) - else - Wrapped.SetProperty(SouffleValueToString(AIndex), Val); + try + if SouffleIsNil(AValue) and (AValue.Flags = GOCCIA_NIL_UNDEFINED) then + ThrowTypeError('Cannot destructure undefined as it is not an object') + else if SouffleIsNil(AValue) then + ThrowTypeError('Cannot destructure null as it is not an object'); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); end; end; -{ Property access } +function TGocciaRuntimeOperations.CoerceValueToString( + const A: TSouffleValue): TSouffleValue; +begin + try + if SouffleIsStringValue(A) then + Exit(A); + Result := SouffleString(CoerceToString(A)); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + on E: ESouffleThrow do + raise; + end; +end; -function TGocciaRuntimeOperations.GetProperty(const AObject: TSouffleValue; - const AKey: string): TSouffleValue; +function TGocciaRuntimeOperations.SuperMethodGet( + const ASuperBlueprint: TSouffleValue; + const AMethodName: string): TSouffleValue; var - GocciaObj, Prop: TGocciaValue; - Boxed: TGocciaObjectValue; + Bp: TSouffleBlueprint; + Method: TSouffleValue; begin - GocciaObj := UnwrapToGocciaValue(AObject); - Prop := GocciaObj.GetProperty(AKey); + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + if not (SouffleIsReference(ASuperBlueprint) and + Assigned(ASuperBlueprint.AsReference) and + (ASuperBlueprint.AsReference is TSouffleBlueprint)) then + Exit; - if not Assigned(Prop) then + Bp := TSouffleBlueprint(ASuperBlueprint.AsReference); + while Assigned(Bp) do begin - Boxed := GocciaObj.Box; - if Assigned(Boxed) then - Prop := Boxed.GetProperty(AKey); + if Bp.Methods.Get(AMethodName, Method) then + Exit(Method); + if Bp.HasStaticFields and Bp.StaticFields.Get(AMethodName, Method) then + Exit(Method); + Bp := Bp.SuperBlueprint; end; +end; - if Assigned(Prop) then - Result := ToSouffleValue(Prop) - else - Result := SouffleNil; +function TGocciaRuntimeOperations.AddPendingClassDef( + const AClassDef: TGocciaClassDefinition; + const ALine, AColumn: Integer): Integer; +begin + Result := FPendingClassCount; + if FPendingClassCount >= Length(FPendingClasses) then + SetLength(FPendingClasses, FPendingClassCount * 2 + 4); + FPendingClasses[FPendingClassCount].ClassDefinition := AClassDef; + FPendingClasses[FPendingClassCount].Line := ALine; + FPendingClasses[FPendingClassCount].Column := AColumn; + Inc(FPendingClassCount); end; -procedure TGocciaRuntimeOperations.SetProperty(const AObject: TSouffleValue; - const AKey: string; const AValue: TSouffleValue); +function BlueprintToClassValue(const ABlueprint: TSouffleBlueprint; + const ARuntime: TGocciaRuntimeOperations): TGocciaClassValue; var - GocciaObj, Val: TGocciaValue; + SuperClass: TGocciaClassValue; + I: Integer; + Key: string; + MethodVal: TSouffleValue; + Bridge: TGocciaSouffleMethodBridge; begin - if SouffleIsReference(AObject) and - (AObject.AsReference is TGocciaWrappedValue) then + SuperClass := nil; + if Assigned(ABlueprint.SuperBlueprint) then + SuperClass := BlueprintToClassValue(ABlueprint.SuperBlueprint, ARuntime); + Result := TGocciaClassValue.Create(ABlueprint.Name, SuperClass); + for I := 0 to ABlueprint.Methods.Count - 1 do begin - GocciaObj := TGocciaWrappedValue(AObject.AsReference).Value; - Val := UnwrapToGocciaValue(AValue); - GocciaObj.SetProperty(AKey, Val); + Key := ABlueprint.Methods.GetOrderedKey(I); + MethodVal := ABlueprint.Methods.GetOrderedValue(I); + if SouffleIsReference(MethodVal) and + (MethodVal.AsReference is TSouffleClosure) then + begin + Bridge := TGocciaSouffleMethodBridge.Create( + TSouffleClosure(MethodVal.AsReference), ARuntime); + Result.AddMethod(Key, Bridge); + end; end; end; -function TGocciaRuntimeOperations.GetIndex( - const AObject, AKey: TSouffleValue): TSouffleValue; +function CreateBridgedContext( + const ARuntime: TGocciaRuntimeOperations): TGocciaEvaluationContext; +var + BridgeScope: TGocciaScope; + GocciaVal: TGocciaValue; + Pair: TPair; begin - Result := GetProperty(AObject, SouffleValueToString(AKey)); + Result := TGocciaEngine(ARuntime.Engine).Interpreter.CreateEvaluationContext; + if ARuntime.FGlobals.Count = 0 then + Exit; + + BridgeScope := Result.Scope.CreateChild(skBlock); + for Pair in ARuntime.FGlobals do + if not BridgeScope.Contains(Pair.Key) then + begin + GocciaVal := ARuntime.UnwrapToGocciaValue(Pair.Value); + BridgeScope.DefineLexicalBinding(Pair.Key, GocciaVal, dtLet); + end; + Result.Scope := BridgeScope; end; -procedure TGocciaRuntimeOperations.SetIndex(const AObject: TSouffleValue; - const AKey, AValue: TSouffleValue); +procedure SyncArraysBack(const ARuntime: TGocciaRuntimeOperations; + const AScope: TGocciaScope); +var + Pair: TPair; + GocciaVal: TGocciaValue; + SArr: TSouffleArray; + GArr: TGocciaArrayValue; + I: Integer; begin - SetProperty(AObject, SouffleValueToString(AKey), AValue); + for Pair in ARuntime.FGlobals do + begin + if not SouffleIsReference(Pair.Value) then + Continue; + if not (Pair.Value.AsReference is TSouffleArray) then + Continue; + if not AScope.Contains(Pair.Key) then + Continue; + GocciaVal := AScope.GetValue(Pair.Key); + if not (GocciaVal is TGocciaArrayValue) then + Continue; + SArr := TSouffleArray(Pair.Value.AsReference); + GArr := TGocciaArrayValue(GocciaVal); + SArr.Clear; + for I := 0 to GArr.Elements.Count - 1 do + SArr.Push(ARuntime.ToSouffleValue(GArr.Elements[I])); + end; end; -function TGocciaRuntimeOperations.DeleteProperty(const AObject: TSouffleValue; - const AKey: string): TSouffleValue; +procedure RebuildArrayBridgeCache( + const ARuntime: TGocciaRuntimeOperations; + const AScope: TGocciaScope); var - GocciaObj: TGocciaValue; + Pair: TPair; + GocciaVal: TGocciaValue; begin - if SouffleIsReference(AObject) and Assigned(AObject.AsReference) and - (AObject.AsReference is TGocciaWrappedValue) then + for Pair in ARuntime.FGlobals do begin - GocciaObj := TGocciaWrappedValue(AObject.AsReference).Value; - if GocciaObj is TGocciaObjectValue then - begin - TGocciaObjectValue(GocciaObj).DeleteProperty(AKey); - Result := SouffleBoolean(True); - Exit; - end; + if not SouffleIsReference(Pair.Value) then + Continue; + if not (Pair.Value.AsReference is TSouffleArray) then + Continue; + if not AScope.Contains(Pair.Key) then + Continue; + GocciaVal := AScope.GetValue(Pair.Key); + if not (GocciaVal is TGocciaArrayValue) then + Continue; + ARuntime.FArrayBridgeCache.AddOrSetValue( + Pair.Value.AsReference, GocciaVal); end; - Result := SouffleBoolean(False); end; -{ Invocation -- delegates to GocciaScript's existing call mechanism } - -function TGocciaRuntimeOperations.Invoke(const ACallee: TSouffleValue; - const AArgs: PSouffleValue; const AArgCount: Integer; - const AReceiver: TSouffleValue): TSouffleValue; +procedure SyncCachedGocciaToSouffle( + const ARuntime: TGocciaRuntimeOperations); var - GocciaCallee, GocciaReceiver, GocciaResult: TGocciaValue; - Args: TGocciaArgumentsCollection; + Pair: TPair; + SArr: TSouffleArray; + GArr: TGocciaArrayValue; I: Integer; begin - GocciaCallee := UnwrapToGocciaValue(ACallee); - if not GocciaCallee.IsCallable then + for Pair in ARuntime.FArrayBridgeCache do begin - Result := SouffleNil; - Exit; + SArr := TSouffleArray(Pair.Key); + GArr := TGocciaArrayValue(Pair.Value); + SArr.Clear; + for I := 0 to GArr.Elements.Count - 1 do + SArr.Push(ARuntime.ToSouffleValue(GArr.Elements[I])); end; +end; - Args := TGocciaArgumentsCollection.Create; - try - for I := 0 to AArgCount - 1 do - Args.Add(UnwrapToGocciaValue(PSouffleValue(PByte(AArgs) + I * SizeOf(TSouffleValue))^)); - - GocciaReceiver := UnwrapToGocciaValue(AReceiver); - if GocciaCallee is TGocciaFunctionBase then - GocciaResult := TGocciaFunctionBase(GocciaCallee).Call(Args, GocciaReceiver) - else if GocciaCallee is TGocciaClassValue then - GocciaResult := TGocciaClassValue(GocciaCallee).Call(Args, GocciaReceiver) - else - GocciaResult := nil; - - if Assigned(GocciaResult) then - Result := ToSouffleValue(GocciaResult) - else - Result := SouffleNil; - finally - Args.Free; +procedure SyncScopeToGlobals(const ARuntime: TGocciaRuntimeOperations; + const AScope: TGocciaScope); +var + GlobalPair: TPair; + GocciaVal: TGocciaValue; +begin + for GlobalPair in ARuntime.FGlobals do + begin + if not AScope.Contains(GlobalPair.Key) then + Continue; + GocciaVal := AScope.GetValue(GlobalPair.Key); + ARuntime.FGlobals.AddOrSetValue( + GlobalPair.Key, ARuntime.ToSouffleValue(GocciaVal)); end; end; -function TGocciaRuntimeOperations.Construct(const AConstructor: TSouffleValue; - const AArgs: PSouffleValue; const AArgCount: Integer): TSouffleValue; +procedure SyncDefinitionScopesBack( + const ARuntime: TGocciaRuntimeOperations); var - GocciaConstructor, GocciaResult: TGocciaValue; - Args: TGocciaArgumentsCollection; + ScopePair: TPair; +begin + for ScopePair in ARuntime.FClassDefinitionScopes do + SyncScopeToGlobals(ARuntime, TGocciaScope(ScopePair.Value)); +end; + +function TGocciaRuntimeOperations.EvaluateClassByIndex( + const AIndex: Integer): TSouffleValue; +var + Entry: TGocciaPendingClassEntry; Context: TGocciaEvaluationContext; - I: Integer; + EvalScope: TGocciaScope; + ClassValue: TGocciaClassValue; + GocciaValue: TGocciaValue; + Pair: TPair; begin - GocciaConstructor := UnwrapToGocciaValue(AConstructor); + Result := SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED); + if (AIndex < 0) or (AIndex >= FPendingClassCount) then + Exit; + + Entry := FPendingClasses[AIndex]; + Context := CreateBridgedContext(Self); + EvalScope := Context.Scope; + + try + ClassValue := EvaluateClassDefinition( + Entry.ClassDefinition, Context, Entry.Line, Entry.Column); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; + + SyncArraysBack(Self, EvalScope); + FArrayBridgeCache.Clear; - if not GocciaConstructor.IsCallable then + if Assigned(ClassValue) then begin - Result := SouffleNil; - Exit; + FClassDefinitionScopes.AddOrSetValue(ClassValue, EvalScope); + TGocciaGarbageCollector.Instance.AddTempRoot(ClassValue); + try + Result := ToSouffleValue(ClassValue); + FGlobals.AddOrSetValue(Entry.ClassDefinition.Name, Result); + if EvalScope.ContainsOwnLexicalBinding(Entry.ClassDefinition.Name) then + EvalScope.AssignLexicalBinding(Entry.ClassDefinition.Name, ClassValue) + else + EvalScope.DefineLexicalBinding( + Entry.ClassDefinition.Name, ClassValue, dtLet); + finally + TGocciaGarbageCollector.Instance.RemoveTempRoot(ClassValue); + end; end; +end; - Args := TGocciaArgumentsCollection.Create; +function TGocciaRuntimeOperations.FinalizeEnum(const ARecord: TSouffleValue; + const AName: string): TSouffleValue; +var + Rec: TSouffleRecord; + EnumObj: TGocciaEnumValue; + Entries: TGocciaArrayValue; + I: Integer; + Key: string; + Val: TSouffleValue; + GocciaVal: TGocciaValue; + PairArr: TGocciaArrayValue; +begin try - for I := 0 to AArgCount - 1 do - Args.Add(UnwrapToGocciaValue(PSouffleValue(PByte(AArgs) + I * SizeOf(TSouffleValue))^)); + if not SouffleIsReference(ARecord) or + not (ARecord.AsReference is TSouffleRecord) then + Exit(ARecord); - if (GocciaConstructor is TGocciaClassValue) and Assigned(FEngine) then + Rec := TSouffleRecord(ARecord.AsReference); + EnumObj := TGocciaEnumValue.Create(AName); + Entries := TGocciaArrayValue.Create; + EnumObj.Entries := Entries; + + for I := 0 to Rec.Count - 1 do begin - Context := TGocciaEngine(FEngine).Interpreter.CreateEvaluationContext; - GocciaResult := InstantiateClass( - TGocciaClassValue(GocciaConstructor), Args, Context); - end - else if GocciaConstructor is TGocciaFunctionBase then - GocciaResult := TGocciaFunctionBase(GocciaConstructor).Call(Args, nil) - else - GocciaResult := nil; + Key := Rec.GetOrderedKey(I); + Val := Rec.GetOrderedValue(I); + GocciaVal := UnwrapToGocciaValue(Val); - if Assigned(GocciaResult) then - Result := ToSouffleValue(GocciaResult) - else - Result := SouffleNil; - finally - Args.Free; - end; -end; + if not (GocciaVal is TGocciaNumberLiteralValue) and + not (GocciaVal is TGocciaStringLiteralValue) and + not (GocciaVal is TGocciaSymbolValue) then + ThrowTypeError('Enum member ''' + Key + + ''' must be a number, string, or symbol'); -{ Iteration } + EnumObj.DefineProperty(Key, + TGocciaPropertyDescriptorData.Create(GocciaVal, [pfEnumerable])); -function TGocciaRuntimeOperations.GetIterator( - const AIterable: TSouffleValue): TSouffleValue; -begin - Result := AIterable; -end; + PairArr := TGocciaArrayValue.Create; + PairArr.Elements.Add(TGocciaStringLiteralValue.Create(Key)); + PairArr.Elements.Add(GocciaVal); + Entries.Elements.Add(PairArr); + end; -function TGocciaRuntimeOperations.IteratorNext(const AIterator: TSouffleValue; - out ADone: Boolean): TSouffleValue; -begin - ADone := True; - Result := SouffleNil; -end; + EnumObj.PreventExtensions; + InitializeEnumSymbols(EnumObj); -procedure TGocciaRuntimeOperations.SpreadInto( - const ATarget, ASource: TSouffleValue); -begin - // Placeholder for spread implementation -end; + if Assigned(TGocciaGarbageCollector.Instance) then + TGocciaGarbageCollector.Instance.AddTempRoot(EnumObj); -{ Modules } + Result := WrapGocciaValue(EnumObj); + except + on E: TGocciaThrowValue do + RethrowAsVM(E); + end; +end; -function TGocciaRuntimeOperations.ImportModule(const APath: string): TSouffleValue; +procedure TGocciaRuntimeOperations.ExtendedOperation(const ASubOp: UInt8; + var ADest: TSouffleValue; const AOperand, AExtra: TSouffleValue; + const ATemplate: TSouffleFunctionTemplate; + const AOperandIndex: UInt8); begin - Result := SouffleNil; + case ASubOp of + 1: // GOCCIA_EXT_SPREAD_OBJ + SpreadObjectInto(ADest, AOperand); + 2: // GOCCIA_EXT_OBJ_REST + ADest := ObjectRest(AOperand, AExtra); + 3: // GOCCIA_EXT_FINALIZE_ENUM + ADest := FinalizeEnum(ADest, ATemplate.GetConstant(AOperandIndex).StringValue); + 4: // GOCCIA_EXT_DEF_GETTER + DefineGetter(ADest, ATemplate.GetConstant(AOperandIndex).StringValue, AExtra); + 5: // GOCCIA_EXT_DEF_SETTER + DefineSetter(ADest, ATemplate.GetConstant(AOperandIndex).StringValue, AExtra); + 6: // GOCCIA_EXT_DEF_STATIC_GETTER + DefineStaticGetter(ADest, ATemplate.GetConstant(AOperandIndex).StringValue, AExtra); + 7: // GOCCIA_EXT_DEF_STATIC_SETTER + DefineStaticSetter(ADest, ATemplate.GetConstant(AOperandIndex).StringValue, AExtra); + 8: // GOCCIA_EXT_REQUIRE_OBJECT + RequireObjectCoercible(ADest); + 9: // GOCCIA_EXT_EVAL_CLASS + ADest := EvaluateClassByIndex(AOperandIndex); + 10: // GOCCIA_EXT_THROW_TYPE_ERROR + ThrowTypeErrorMessage(ATemplate.GetConstant(AOperandIndex).StringValue); + 11: // GOCCIA_EXT_SUPER_GET + ADest := SuperMethodGet(AExtra, ATemplate.GetConstant(AOperandIndex).StringValue); + 12: // GOCCIA_EXT_SPREAD + SpreadInto(ADest, AOperand); + 13: // GOCCIA_EXT_REQUIRE_ITERABLE + ADest := RequireIterable(ADest); + 14: // GOCCIA_EXT_DEFINE_GLOBAL + FGlobals.AddOrSetValue( + ATemplate.GetConstant(AOperandIndex).StringValue, ADest); + end; end; -procedure TGocciaRuntimeOperations.ExportBinding(const AValue: TSouffleValue; - const AName: string); +procedure TGocciaRuntimeOperations.MarkExternalRoots; +var + GlobalVal: TSouffleValue; + ClosureKey: TSouffleClosure; + BridgeKey: TObject; begin - FExports.AddOrSetValue(AName, AValue); -end; + for GlobalVal in FGlobals.Values do + if SouffleIsReference(GlobalVal) and Assigned(GlobalVal.AsReference) + and not GlobalVal.AsReference.GCMarked then + GlobalVal.AsReference.MarkReferences; -{ Async } + for GlobalVal in FExports.Values do + if SouffleIsReference(GlobalVal) and Assigned(GlobalVal.AsReference) + and not GlobalVal.AsReference.GCMarked then + GlobalVal.AsReference.MarkReferences; -function TGocciaRuntimeOperations.AwaitValue(const AValue: TSouffleValue): TSouffleValue; -begin - Result := AValue; -end; + for ClosureKey in FClosureBridgeCache.Keys do + if not ClosureKey.GCMarked then + ClosureKey.MarkReferences; -{ Globals } + for BridgeKey in FArrayBridgeCache.Keys do + if (BridgeKey is TSouffleHeapObject) and not TSouffleHeapObject(BridgeKey).GCMarked then + TSouffleHeapObject(BridgeKey).MarkReferences; -function TGocciaRuntimeOperations.GetGlobal(const AName: string): TSouffleValue; -var - Value: TSouffleValue; -begin - if FGlobals.TryGetValue(AName, Value) then - Result := Value - else - Result := SouffleNil; + for BridgeKey in FRecordBridgeCache.Keys do + if (BridgeKey is TSouffleHeapObject) and not TSouffleHeapObject(BridgeKey).GCMarked then + TSouffleHeapObject(BridgeKey).MarkReferences; end; -procedure TGocciaRuntimeOperations.SetGlobal(const AName: string; - const AValue: TSouffleValue); +procedure TGocciaRuntimeOperations.MarkWrappedGocciaValues; +var + I: Integer; + Wrapped: TGocciaWrappedValue; + BridgeVal: TObject; begin - FGlobals.AddOrSetValue(AName, AValue); -end; + for I := 0 to FWrappedValues.Count - 1 do + begin + Wrapped := TGocciaWrappedValue(FWrappedValues[I]); + if Assigned(Wrapped.Value) and not Wrapped.Value.GCMarked then + Wrapped.Value.MarkReferences; + end; -procedure TGocciaRuntimeOperations.RegisterGlobal(const AName: string; - const AValue: TSouffleValue); -begin - FGlobals.AddOrSetValue(AName, AValue); + for BridgeVal in FClosureBridgeCache.Values do + if (BridgeVal is TGocciaValue) and not TGocciaValue(BridgeVal).GCMarked then + TGocciaValue(BridgeVal).MarkReferences; + + for BridgeVal in FArrayBridgeCache.Values do + if (BridgeVal is TGocciaValue) and not TGocciaValue(BridgeVal).GCMarked then + TGocciaValue(BridgeVal).MarkReferences; + + for BridgeVal in FRecordBridgeCache.Values do + if (BridgeVal is TGocciaValue) and not TGocciaValue(BridgeVal).GCMarked then + TGocciaValue(BridgeVal).MarkReferences; end; end. diff --git a/units/Goccia.Scope.pas b/units/Goccia.Scope.pas index 874ade2c..ecb81ead 100644 --- a/units/Goccia.Scope.pas +++ b/units/Goccia.Scope.pas @@ -45,6 +45,7 @@ TGocciaScope = class // New Define/Assign pattern procedure DefineLexicalBinding(const AName: string; const AValue: TGocciaValue; const ADeclarationType: TGocciaDeclarationType; const ALine: Integer = 0; const AColumn: Integer = 0); procedure AssignLexicalBinding(const AName: string; const AValue: TGocciaValue; const ALine: Integer = 0; const AColumn: Integer = 0); virtual; + procedure ForceUpdateBinding(const AName: string; const AValue: TGocciaValue); // Helper methods for token-based declarations procedure DefineFromToken(const AName: string; const AValue: TGocciaValue; const ATokenType: TGocciaTokenType); @@ -281,6 +282,18 @@ procedure TGocciaScope.DefineFromToken(const AName: string; const AValue: TGocci DefineLexicalBinding(AName, AValue, DeclarationType); end; +procedure TGocciaScope.ForceUpdateBinding(const AName: string; const AValue: TGocciaValue); +var + LexicalBinding: TLexicalBinding; +begin + if FLexicalBindings.TryGetValue(AName, LexicalBinding) then + begin + LexicalBinding.Value := AValue; + LexicalBinding.Initialized := True; + FLexicalBindings.AddOrSetValue(AName, LexicalBinding); + end; +end; + procedure TGocciaScope.AssignLexicalBinding(const AName: string; const AValue: TGocciaValue; const ALine: Integer = 0; const AColumn: Integer = 0); var LexicalBinding: TLexicalBinding; diff --git a/units/Goccia.TestSetup.pas b/units/Goccia.TestSetup.pas new file mode 100644 index 00000000..2d182a3f --- /dev/null +++ b/units/Goccia.TestSetup.pas @@ -0,0 +1,15 @@ +unit Goccia.TestSetup; + +{$I Goccia.inc} + +interface + +implementation + +uses + Math; + +initialization + SetExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision]); + +end. diff --git a/units/Goccia.Values.ArrayValue.pas b/units/Goccia.Values.ArrayValue.pas index 0076266c..f50d612a 100644 --- a/units/Goccia.Values.ArrayValue.pas +++ b/units/Goccia.Values.ArrayValue.pas @@ -822,6 +822,7 @@ function TGocciaArrayValue.ArrayFlat(const AArgs: TGocciaArgumentsCollection; co var Arr: TGocciaArrayValue; ResultArray: TGocciaArrayValue; + DepthNum: TGocciaNumberLiteralValue; Depth: Integer; begin // Step 1: Let O be ToObject(this value) @@ -839,13 +840,19 @@ function TGocciaArrayValue.ArrayFlat(const AArgs: TGocciaArgumentsCollection; co if not (AArgs.GetElement(0) is TGocciaNumberLiteralValue) then ThrowError('Array.flat expects depth argument to be a number'); - if AArgs.GetElement(0).ToNumberLiteral.IsInfinity then + DepthNum := AArgs.GetElement(0).ToNumberLiteral; + if DepthNum.IsNaN then + Depth := 0 + else if DepthNum.IsInfinity then Depth := MaxInt + else if DepthNum.IsNegativeInfinity then + Depth := 0 + else if DepthNum.Value > MaxInt then + Depth := MaxInt + else if DepthNum.Value < 0 then + Depth := 0 else - Depth := Trunc(AArgs.GetElement(0).ToNumberLiteral.Value); - - if Depth < 0 then - Depth := 0; + Depth := Trunc(DepthNum.Value); end; // Step 5: Let A be ArraySpeciesCreate(O, 0) diff --git a/units/Goccia.Values.AsyncFunctionValue.pas b/units/Goccia.Values.AsyncFunctionValue.pas index 84898fa9..82a0fd1b 100644 --- a/units/Goccia.Values.AsyncFunctionValue.pas +++ b/units/Goccia.Values.AsyncFunctionValue.pas @@ -17,13 +17,11 @@ interface TGocciaAsyncFunctionValue = class(TGocciaFunctionValue) public function Call(const AArguments: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue; override; - function CloneWithNewScope(const ANewScope: TGocciaScope): TGocciaFunctionValue; override; end; TGocciaAsyncArrowFunctionValue = class(TGocciaArrowFunctionValue) public function Call(const AArguments: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue; override; - function CloneWithNewScope(const ANewScope: TGocciaScope): TGocciaFunctionValue; override; end; TGocciaAsyncMethodValue = class(TGocciaMethodValue) @@ -78,17 +76,6 @@ function TGocciaAsyncFunctionValue.Call(const AArguments: TGocciaArgumentsCollec Result := Promise; end; -function TGocciaAsyncFunctionValue.CloneWithNewScope(const ANewScope: TGocciaScope): TGocciaFunctionValue; -var - ClonedStatements: TObjectList; - I: Integer; -begin - ClonedStatements := TObjectList.Create(False); - for I := 0 to FBodyStatements.Count - 1 do - ClonedStatements.Add(FBodyStatements[I]); - Result := TGocciaAsyncFunctionValue.Create(FParameters, ClonedStatements, ANewScope, FName); -end; - { TGocciaAsyncArrowFunctionValue } // ES2026 §27.7.5.1 AsyncFunctionStart(promiseCapability, asyncFunctionBody) @@ -127,18 +114,6 @@ function TGocciaAsyncArrowFunctionValue.Call(const AArguments: TGocciaArgumentsC Result := Promise; end; -function TGocciaAsyncArrowFunctionValue.CloneWithNewScope(const ANewScope: TGocciaScope): TGocciaFunctionValue; -var - ClonedStatements: TObjectList; - I: Integer; -begin - ClonedStatements := TObjectList.Create(False); - for I := 0 to FBodyStatements.Count - 1 do - ClonedStatements.Add(FBodyStatements[I]); - Result := TGocciaAsyncArrowFunctionValue.Create(FParameters, ClonedStatements, ANewScope, FName); - TGocciaFunctionValue(Result).IsExpressionBody := FIsExpressionBody; -end; - { TGocciaAsyncMethodValue } // ES2026 §27.7.5.1 AsyncFunctionStart(promiseCapability, asyncFunctionBody) diff --git a/units/Goccia.Values.ClassHelper.pas b/units/Goccia.Values.ClassHelper.pas index edcc9901..64b701d0 100644 --- a/units/Goccia.Values.ClassHelper.pas +++ b/units/Goccia.Values.ClassHelper.pas @@ -15,34 +15,12 @@ interface TGocciaValueHelper = class helper for TGocciaValue public - // Type coercion helpers - // function ToBooleanLiteral: TGocciaBooleanLiteralValue; - // function ToNumberLiteral: TGocciaNumberLiteralValue; - // function ToStringLiteral: TGocciaStringLiteralValue; - function ToStringObject: TGocciaStringObjectValue; function ToBooleanObject: TGocciaBooleanObjectValue; function ToNumberObject: TGocciaNumberObjectValue; - // Boxing helpers function Box: TGocciaObjectValue; - // Arithmetic helpers - function Add(const AOther: TGocciaValue): TGocciaValue; - function Subtract(const AOther: TGocciaValue): TGocciaValue; - function Multiply(const AOther: TGocciaValue): TGocciaValue; - function Divide(const AOther: TGocciaValue): TGocciaValue; - function Modulo(const AOther: TGocciaValue): TGocciaValue; - function Power(const AOther: TGocciaValue): TGocciaValue; - - function BitwiseAnd(const AOther: TGocciaValue): TGocciaValue; - function BitwiseOr(const AOther: TGocciaValue): TGocciaValue; - function BitwiseXor(const AOther: TGocciaValue): TGocciaValue; - function LeftShift(const AOther: TGocciaValue): TGocciaValue; - function RightShift(const AOther: TGocciaValue): TGocciaValue; - function UnsignedRightShift(const AOther: TGocciaValue): TGocciaValue; - function BitwiseNot: TGocciaValue; - function IsEqual(const AOther: TGocciaValue): TGocciaBooleanLiteralValue; function IsNotEqual(const AOther: TGocciaValue): TGocciaBooleanLiteralValue; function IsLessThan(const AOther: TGocciaValue): TGocciaBooleanLiteralValue; @@ -290,274 +268,6 @@ implementation Result := nil; end; - - - - - function TGocciaValueHelper.Add(const AOther: TGocciaValue): TGocciaValue; - var - LeftStr, RightStr: TGocciaStringLiteralValue; - LeftNum, RightNum: TGocciaNumberLiteralValue; - begin - // ECMAScript Addition: If either operand is string, do string concatenation - if (Self is TGocciaStringLiteralValue) or (AOther is TGocciaStringLiteralValue) then - begin - LeftStr := Self.ToStringLiteral; - RightStr := AOther.ToStringLiteral; - Result := TGocciaStringLiteralValue.Create(LeftStr.Value + RightStr.Value); - end - else - begin - // Numeric addition - LeftNum := Self.ToNumberLiteral; - RightNum := AOther.ToNumberLiteral; - - // Handle special cases - if LeftNum.IsNaN or RightNum.IsNaN then - Result := TGocciaNumberLiteralValue.NaNValue - else if LeftNum.IsInfinity then - begin - if RightNum.IsNegativeInfinity then - Result := TGocciaNumberLiteralValue.NaNValue - else - Result := TGocciaNumberLiteralValue.InfinityValue; - end - else if LeftNum.IsNegativeInfinity then - begin - if RightNum.IsInfinity then - Result := TGocciaNumberLiteralValue.NaNValue - else - Result := TGocciaNumberLiteralValue.NegativeInfinityValue; - end - else if RightNum.IsInfinity then - Result := TGocciaNumberLiteralValue.InfinityValue - else if RightNum.IsNegativeInfinity then - Result := TGocciaNumberLiteralValue.NegativeInfinityValue - else - Result := TGocciaNumberLiteralValue.Create(LeftNum.Value + RightNum.Value); - end; - end; - - function TGocciaValueHelper.Subtract(const AOther: TGocciaValue): TGocciaValue; - var - LeftNum, RightNum: TGocciaNumberLiteralValue; - begin - LeftNum := Self.ToNumberLiteral; - RightNum := AOther.ToNumberLiteral; - - // Handle special cases - if (LeftNum.IsNaN or RightNum.IsNaN) then - Result := TGocciaNumberLiteralValue.NaNValue - else if (LeftNum.IsInfinity and RightNum.IsInfinity) then - Result := TGocciaNumberLiteralValue.NaNValue - else if (LeftNum.IsNegativeInfinity and RightNum.IsNegativeInfinity) then - Result := TGocciaNumberLiteralValue.NaNValue - else if LeftNum.IsInfinity then - Result := TGocciaNumberLiteralValue.InfinityValue - else if LeftNum.IsNegativeInfinity then - Result := TGocciaNumberLiteralValue.NegativeInfinityValue - else if RightNum.IsInfinity then - Result := TGocciaNumberLiteralValue.NegativeInfinityValue - else if RightNum.IsNegativeInfinity then - Result := TGocciaNumberLiteralValue.InfinityValue - else - Result := TGocciaNumberLiteralValue.Create(LeftNum.Value - RightNum.Value); - end; - - function TGocciaValueHelper.Multiply(const AOther: TGocciaValue): TGocciaValue; - var - LeftNum, RightNum: TGocciaNumberLiteralValue; - begin - LeftNum := Self.ToNumberLiteral; - RightNum := AOther.ToNumberLiteral; - - // Handle special cases - if LeftNum.IsNaN or RightNum.IsNaN then - Result := TGocciaNumberLiteralValue.NaNValue - else if ((LeftNum.IsInfinity or LeftNum.IsNegativeInfinity) and (RightNum.Value = 0)) then - Result := TGocciaNumberLiteralValue.NaNValue - else if ((RightNum.IsInfinity or RightNum.IsNegativeInfinity) and (LeftNum.Value = 0)) then - Result := TGocciaNumberLiteralValue.NaNValue - else if LeftNum.IsInfinity then - begin - if RightNum.Value < 0 then - Result := TGocciaNumberLiteralValue.NegativeInfinityValue - else - Result := TGocciaNumberLiteralValue.InfinityValue; - end - else if LeftNum.IsNegativeInfinity then - begin - if RightNum.Value < 0 then - Result := TGocciaNumberLiteralValue.InfinityValue - else - Result := TGocciaNumberLiteralValue.NegativeInfinityValue; - end - else if RightNum.IsInfinity then - begin - if LeftNum.Value < 0 then - Result := TGocciaNumberLiteralValue.NegativeInfinityValue - else - Result := TGocciaNumberLiteralValue.InfinityValue; - end - else if RightNum.IsNegativeInfinity then - begin - if LeftNum.Value < 0 then - Result := TGocciaNumberLiteralValue.InfinityValue - else - Result := TGocciaNumberLiteralValue.NegativeInfinityValue; - end - else - Result := TGocciaNumberLiteralValue.Create(LeftNum.Value * RightNum.Value); - end; - - function TGocciaValueHelper.Divide(const AOther: TGocciaValue): TGocciaValue; - var - LeftNum, RightNum: TGocciaNumberLiteralValue; - begin - LeftNum := Self.ToNumberLiteral; - RightNum := AOther.ToNumberLiteral; - - // Handle special cases - if (LeftNum.IsNaN or RightNum.IsNaN) then - Result := TGocciaNumberLiteralValue.NaNValue - else if (RightNum.Value = 0) then - begin - if (LeftNum.Value = 0) then - Result := TGocciaNumberLiteralValue.NaNValue - else if (LeftNum.Value > 0) then - Result := TGocciaNumberLiteralValue.InfinityValue - else - Result := TGocciaNumberLiteralValue.NegativeInfinityValue; - end - else if LeftNum.IsInfinity then - begin - if (RightNum.IsInfinity or RightNum.IsNegativeInfinity) then - Result := TGocciaNumberLiteralValue.NaNValue - else if (RightNum.Value < 0) then - Result := TGocciaNumberLiteralValue.NegativeInfinityValue - else - Result := TGocciaNumberLiteralValue.InfinityValue; - end - else if LeftNum.IsNegativeInfinity then - begin - if (RightNum.IsInfinity or RightNum.IsNegativeInfinity) then - Result := TGocciaNumberLiteralValue.NaNValue - else if (RightNum.Value < 0) then - Result := TGocciaNumberLiteralValue.InfinityValue - else - Result := TGocciaNumberLiteralValue.NegativeInfinityValue; - end - else if (RightNum.IsInfinity or RightNum.IsNegativeInfinity) then - Result := TGocciaNumberLiteralValue.ZeroValue - else - Result := TGocciaNumberLiteralValue.Create(LeftNum.Value / RightNum.Value); - end; - - function TGocciaValueHelper.Modulo(const AOther: TGocciaValue): TGocciaValue; - var - LeftNum, RightNum: TGocciaNumberLiteralValue; - ModResult: Double; - begin - LeftNum := Self.ToNumberLiteral; - RightNum := AOther.ToNumberLiteral; - - // Handle special cases according to ECMAScript spec - if (LeftNum.IsNaN or RightNum.IsNaN or - LeftNum.IsInfinity or LeftNum.IsNegativeInfinity or - (RightNum.Value = 0)) then - Result := TGocciaNumberLiteralValue.NaNValue - else if (RightNum.IsInfinity or RightNum.IsNegativeInfinity) then - Result := LeftNum - else if (LeftNum.Value = 0) then - Result := LeftNum - else - begin - ModResult := Fmod(LeftNum.Value, RightNum.Value); - Result := TGocciaNumberLiteralValue.Create(ModResult); - end; - end; - - function TGocciaValueHelper.Power(const AOther: TGocciaValue): TGocciaValue; - var - LeftNum, RightNum: TGocciaNumberLiteralValue; - Base, Exponent: Extended; - begin - LeftNum := Self.ToNumberLiteral; - RightNum := AOther.ToNumberLiteral; - - // Handle special cases - if (LeftNum.IsNaN or RightNum.IsNaN) then - Result := TGocciaNumberLiteralValue.NaNValue - else - begin - Base := LeftNum.Value; - Exponent := RightNum.Value; - Result := TGocciaNumberLiteralValue.Create(Math.Power(Base, Exponent)); - end; - end; - - function TGocciaValueHelper.BitwiseAnd(const AOther: TGocciaValue): TGocciaValue; - var - LeftInt, RightInt: Integer; - begin - LeftInt := Trunc(Self.ToNumberLiteral.Value) and $7FFFFFFF; - RightInt := Trunc(AOther.ToNumberLiteral.Value) and $7FFFFFFF; - Result := TGocciaNumberLiteralValue.Create(LeftInt and RightInt); - end; - - function TGocciaValueHelper.BitwiseOr(const AOther: TGocciaValue): TGocciaValue; - var - LeftInt, RightInt: Integer; - begin - LeftInt := Trunc(Self.ToNumberLiteral.Value) and $7FFFFFFF; - RightInt := Trunc(AOther.ToNumberLiteral.Value) and $7FFFFFFF; - Result := TGocciaNumberLiteralValue.Create(LeftInt or RightInt); - end; - - function TGocciaValueHelper.BitwiseXor(const AOther: TGocciaValue): TGocciaValue; - var - LeftInt, RightInt: Integer; - begin - LeftInt := Trunc(Self.ToNumberLiteral.Value) and $7FFFFFFF; - RightInt := Trunc(AOther.ToNumberLiteral.Value) and $7FFFFFFF; - Result := TGocciaNumberLiteralValue.Create(LeftInt xor RightInt); - end; - - function TGocciaValueHelper.LeftShift(const AOther: TGocciaValue): TGocciaValue; - var - LeftInt, RightInt: Integer; - begin - LeftInt := Trunc(Self.ToNumberLiteral.Value) and $7FFFFFFF; - RightInt := Trunc(AOther.ToNumberLiteral.Value) and $1F; // Only use lower 5 bits - Result := TGocciaNumberLiteralValue.Create(LeftInt shl RightInt); - end; - - function TGocciaValueHelper.RightShift(const AOther: TGocciaValue): TGocciaValue; - var - LeftInt, RightInt: Integer; - begin - LeftInt := Trunc(Self.ToNumberLiteral.Value); - RightInt := Trunc(AOther.ToNumberLiteral.Value) and $1F; // Only use lower 5 bits - Result := TGocciaNumberLiteralValue.Create(LeftInt shr RightInt); - end; - - function TGocciaValueHelper.UnsignedRightShift(const AOther: TGocciaValue): TGocciaValue; - var - LeftInt, RightInt: Cardinal; - begin - LeftInt := Cardinal(Trunc(Self.ToNumberLiteral.Value)); - RightInt := Cardinal(Trunc(AOther.ToNumberLiteral.Value)) and $1F; // Only use lower 5 bits - Result := TGocciaNumberLiteralValue.Create(LeftInt shr RightInt); - end; - - function TGocciaValueHelper.BitwiseNot: TGocciaValue; - var - IntValue: Integer; - begin - IntValue := Trunc(Self.ToNumberLiteral.Value); - Result := TGocciaNumberLiteralValue.Create(not IntValue); - end; - function TGocciaValueHelper.IsEqual(const AOther: TGocciaValue): TGocciaBooleanLiteralValue; begin // Strict equality comparison diff --git a/units/Goccia.Values.FunctionValue.Test.pas b/units/Goccia.Values.FunctionValue.Test.pas index b7f9f0e1..3af7e77e 100644 --- a/units/Goccia.Values.FunctionValue.Test.pas +++ b/units/Goccia.Values.FunctionValue.Test.pas @@ -15,6 +15,7 @@ Goccia.AST.Statements, Goccia.Evaluator, Goccia.Scope, + Goccia.TestSetup, Goccia.Token, Goccia.Values.FunctionValue, Goccia.Values.ObjectValue, diff --git a/units/Goccia.Values.FunctionValue.pas b/units/Goccia.Values.FunctionValue.pas index 75149a09..250c7ddd 100644 --- a/units/Goccia.Values.FunctionValue.pas +++ b/units/Goccia.Values.FunctionValue.pas @@ -36,7 +36,6 @@ TGocciaFunctionValue = class(TGocciaFunctionBase) destructor Destroy; override; function Call(const AArguments: TGocciaArgumentsCollection; const AThisValue: TGocciaValue): TGocciaValue; override; - function CloneWithNewScope(const ANewScope: TGocciaScope): TGocciaFunctionValue; virtual; procedure MarkReferences; override; property Parameters: TGocciaParameterArray read FParameters; @@ -49,8 +48,6 @@ TGocciaFunctionValue = class(TGocciaFunctionBase) TGocciaArrowFunctionValue = class(TGocciaFunctionValue) protected procedure BindThis(const ACallScope: TGocciaScope; const AThisValue: TGocciaValue); override; - public - function CloneWithNewScope(const ANewScope: TGocciaScope): TGocciaFunctionValue; override; end; TGocciaMethodValue = class(TGocciaFunctionValue) @@ -79,7 +76,8 @@ implementation Goccia.Logger, Goccia.Values.ArrayValue, Goccia.Values.ClassHelper, - Goccia.Values.Error; + Goccia.Values.Error, + Goccia.Values.ErrorHelper; { TGocciaFunctionValue } @@ -235,8 +233,13 @@ function TGocciaFunctionValue.ExecuteBody(const ACallScope: TGocciaScope; const except on E: TGocciaReturnValue do raise; on E: TGocciaThrowValue do raise; + on E: TGocciaBreakSignal do raise; + on E: TGocciaTypeError do raise; + on E: TGocciaReferenceError do raise; + on E: TGocciaRuntimeError do raise; + on E: TGocciaError do raise; on E: Exception do - raise TGocciaError.Create('Error executing statement: ' + E.Message, 0, 0, '', nil); + ThrowError('Error executing statement: ' + E.Message); end; end; @@ -283,20 +286,6 @@ function TGocciaFunctionValue.Call(const AArguments: TGocciaArgumentsCollection; end; end; -function TGocciaFunctionValue.CloneWithNewScope(const ANewScope: TGocciaScope): TGocciaFunctionValue; -var - ClonedStatements: TObjectList; - I: Integer; -begin - // Create a copy of the statements - we can't share the same reference - // since both functions would try to free the same object - ClonedStatements := TObjectList.Create(False); // Don't own the objects - for I := 0 to FBodyStatements.Count - 1 do - ClonedStatements.Add(FBodyStatements[I]); - - Result := TGocciaFunctionValue.Create(FParameters, ClonedStatements, ANewScope, FName); -end; - function TGocciaFunctionValue.GetFunctionLength: Integer; var I: Integer; @@ -337,18 +326,6 @@ procedure TGocciaArrowFunctionValue.BindThis(const ACallScope: TGocciaScope; con end; end; -function TGocciaArrowFunctionValue.CloneWithNewScope(const ANewScope: TGocciaScope): TGocciaFunctionValue; -var - ClonedStatements: TObjectList; - I: Integer; -begin - ClonedStatements := TObjectList.Create(False); - for I := 0 to FBodyStatements.Count - 1 do - ClonedStatements.Add(FBodyStatements[I]); - - Result := TGocciaArrowFunctionValue.Create(FParameters, ClonedStatements, ANewScope, FName); -end; - { TGocciaMethodValue } constructor TGocciaMethodValue.Create(const AParameters: TGocciaParameterArray; const ABodyStatements: TObjectList; const AClosure: TGocciaScope; const AName: string; const ASuperClass: TGocciaValue = nil); diff --git a/units/Goccia.Values.ObjectValue.Test.pas b/units/Goccia.Values.ObjectValue.Test.pas index ea5afd5e..260fc89b 100644 --- a/units/Goccia.Values.ObjectValue.Test.pas +++ b/units/Goccia.Values.ObjectValue.Test.pas @@ -7,6 +7,7 @@ TestRunner, + Goccia.TestSetup, Goccia.Values.ObjectValue, Goccia.Values.Primitives; diff --git a/units/Goccia.Values.ObjectValue.pas b/units/Goccia.Values.ObjectValue.pas index 97c6f180..014e9d57 100644 --- a/units/Goccia.Values.ObjectValue.pas +++ b/units/Goccia.Values.ObjectValue.pas @@ -49,7 +49,7 @@ TGocciaObjectValue = class(TGocciaValue) function ToBooleanLiteral: TGocciaBooleanLiteralValue; override; function ToNumberLiteral: TGocciaNumberLiteralValue; override; - procedure DefineProperty(const AName: string; const ADescriptor: TGocciaPropertyDescriptor); + procedure DefineProperty(const AName: string; const ADescriptor: TGocciaPropertyDescriptor); virtual; procedure AssignProperty(const AName: string; const AValue: TGocciaValue; const ACanCreate: Boolean = True); virtual; @@ -62,30 +62,30 @@ TGocciaObjectValue = class(TGocciaValue) function GetOwnPropertyDescriptor(const AName: string): TGocciaPropertyDescriptor; virtual; function HasProperty(const AName: string): Boolean; function HasOwnProperty(const AName: string): Boolean; virtual; - function DeleteProperty(const AName: string): Boolean; + function DeleteProperty(const AName: string): Boolean; virtual; - function GetEnumerablePropertyNames: TArray; - function GetEnumerablePropertyValues: TArray; - function GetEnumerablePropertyEntries: TArray>; - function GetAllPropertyNames: TArray; - function GetOwnPropertyNames: TArray; - function GetOwnPropertyKeys: TArray; + function GetEnumerablePropertyNames: TArray; virtual; + function GetEnumerablePropertyValues: TArray; virtual; + function GetEnumerablePropertyEntries: TArray>; virtual; + function GetAllPropertyNames: TArray; virtual; + function GetOwnPropertyNames: TArray; virtual; + function GetOwnPropertyKeys: TArray; virtual; procedure DefineSymbolProperty(const ASymbol: TGocciaSymbolValue; const ADescriptor: TGocciaPropertyDescriptor); procedure AssignSymbolProperty(const ASymbol: TGocciaSymbolValue; const AValue: TGocciaValue); - function GetSymbolProperty(const ASymbol: TGocciaSymbolValue): TGocciaValue; + function GetSymbolProperty(const ASymbol: TGocciaSymbolValue): TGocciaValue; virtual; function GetSymbolPropertyWithReceiver(const ASymbol: TGocciaSymbolValue; const AReceiver: TGocciaValue): TGocciaValue; function GetOwnSymbolPropertyDescriptor(const ASymbol: TGocciaSymbolValue): TGocciaPropertyDescriptor; - function HasSymbolProperty(const ASymbol: TGocciaSymbolValue): Boolean; + function HasSymbolProperty(const ASymbol: TGocciaSymbolValue): Boolean; virtual; function GetEnumerableSymbolProperties: TArray>; function GetOwnSymbols: TArray; - procedure Freeze; - function IsFrozen: Boolean; - procedure Seal; - function IsSealed: Boolean; - procedure PreventExtensions; - function IsExtensible: Boolean; + procedure Freeze; virtual; + function IsFrozen: Boolean; virtual; + procedure Seal; virtual; + function IsSealed: Boolean; virtual; + procedure PreventExtensions; virtual; + function IsExtensible: Boolean; virtual; procedure MarkReferences; override; @@ -108,6 +108,7 @@ implementation Goccia.GarbageCollector, Goccia.Values.ClassHelper, Goccia.Values.ErrorHelper, + Goccia.Values.FunctionBase, Goccia.Values.FunctionValue, Goccia.Values.NativeFunction; @@ -498,8 +499,7 @@ function TGocciaObjectValue.ToNumberLiteral: TGocciaNumberLiteralValue; procedure TGocciaObjectValue.AssignProperty(const AName: string; const AValue: TGocciaValue; const ACanCreate: Boolean = True); var Descriptor: TGocciaPropertyDescriptor; - SetterFunction: TGocciaFunctionValue; - NativeSetterFunction: TGocciaNativeFunctionValue; + Accessor: TGocciaPropertyDescriptorAccessor; Args: TGocciaArgumentsCollection; Proto: TGocciaObjectValue; begin @@ -510,32 +510,17 @@ procedure TGocciaObjectValue.AssignProperty(const AName: string; const AValue: T begin if Descriptor is TGocciaPropertyDescriptorAccessor then begin - if Assigned(TGocciaPropertyDescriptorAccessor(Descriptor).Setter) then + Accessor := TGocciaPropertyDescriptorAccessor(Descriptor); + if Assigned(Accessor.Setter) and Accessor.Setter.IsCallable then begin - if TGocciaPropertyDescriptorAccessor(Descriptor).Setter is TGocciaFunctionValue then - begin - SetterFunction := TGocciaFunctionValue(TGocciaPropertyDescriptorAccessor(Descriptor).Setter); - Args := TGocciaArgumentsCollection.Create; - try - Args.Add(AValue); - SetterFunction.Call(Args, Self); - finally - Args.Free; - end; - Exit; - end - else if TGocciaPropertyDescriptorAccessor(Descriptor).Setter is TGocciaNativeFunctionValue then - begin - NativeSetterFunction := TGocciaNativeFunctionValue(TGocciaPropertyDescriptorAccessor(Descriptor).Setter); - Args := TGocciaArgumentsCollection.Create; - try - Args.Add(AValue); - NativeSetterFunction.Call(Args, Self); - finally - Args.Free; - end; - Exit; + Args := TGocciaArgumentsCollection.Create; + try + Args.Add(AValue); + TGocciaFunctionBase(Accessor.Setter).Call(Args, Self); + finally + Args.Free; end; + Exit; end; ThrowTypeError('Cannot set property ' + AName + ' of #<' + ToStringTag + '> which has only a getter'); end @@ -559,34 +544,18 @@ procedure TGocciaObjectValue.AssignProperty(const AName: string; const AValue: T begin if Descriptor is TGocciaPropertyDescriptorAccessor then begin - if Assigned(TGocciaPropertyDescriptorAccessor(Descriptor).Setter) then + Accessor := TGocciaPropertyDescriptorAccessor(Descriptor); + if Assigned(Accessor.Setter) and Accessor.Setter.IsCallable then begin - if TGocciaPropertyDescriptorAccessor(Descriptor).Setter is TGocciaFunctionValue then - begin - SetterFunction := TGocciaFunctionValue(TGocciaPropertyDescriptorAccessor(Descriptor).Setter); - Args := TGocciaArgumentsCollection.Create; - try - Args.Add(AValue); - SetterFunction.Call(Args, Self); - finally - Args.Free; - end; - Exit; - end - else if TGocciaPropertyDescriptorAccessor(Descriptor).Setter is TGocciaNativeFunctionValue then - begin - NativeSetterFunction := TGocciaNativeFunctionValue(TGocciaPropertyDescriptorAccessor(Descriptor).Setter); - Args := TGocciaArgumentsCollection.Create; - try - Args.Add(AValue); - NativeSetterFunction.Call(Args, Self); - finally - Args.Free; - end; - Exit; + Args := TGocciaArgumentsCollection.Create; + try + Args.Add(AValue); + TGocciaFunctionBase(Accessor.Setter).Call(Args, Self); + finally + Args.Free; end; + Exit; end; - // Getter-only accessor in prototype chain — strict mode TypeError ThrowTypeError('Cannot set property ' + AName + ' of #<' + ToStringTag + '> which has only a getter'); end else if Descriptor is TGocciaPropertyDescriptorData then @@ -669,38 +638,23 @@ procedure TGocciaObjectValue.SetProperty(const AName: string; const AValue: TGoc function TGocciaObjectValue.GetPropertyWithContext(const AName: string; const AThisContext: TGocciaValue): TGocciaValue; var Descriptor: TGocciaPropertyDescriptor; - GetterFunction: TGocciaFunctionValue; - NativeGetterFunction: TGocciaNativeFunctionValue; + Accessor: TGocciaPropertyDescriptorAccessor; Args: TGocciaArgumentsCollection; begin if FProperties.TryGetValue(AName, Descriptor) then begin if Descriptor is TGocciaPropertyDescriptorAccessor then begin - if Assigned(TGocciaPropertyDescriptorAccessor(Descriptor).Getter) then + Accessor := TGocciaPropertyDescriptorAccessor(Descriptor); + if Assigned(Accessor.Getter) and Accessor.Getter.IsCallable then begin - if TGocciaPropertyDescriptorAccessor(Descriptor).Getter is TGocciaFunctionValue then - begin - GetterFunction := TGocciaFunctionValue(TGocciaPropertyDescriptorAccessor(Descriptor).Getter); - Args := TGocciaArgumentsCollection.Create; - try - Result := GetterFunction.Call(Args, AThisContext); - finally - Args.Free; - end; - Exit; - end - else if TGocciaPropertyDescriptorAccessor(Descriptor).Getter is TGocciaNativeFunctionValue then - begin - NativeGetterFunction := TGocciaNativeFunctionValue(TGocciaPropertyDescriptorAccessor(Descriptor).Getter); - Args := TGocciaArgumentsCollection.Create; - try - Result := NativeGetterFunction.Call(Args, AThisContext); - finally - Args.Free; - end; - Exit; + Args := TGocciaArgumentsCollection.Create; + try + Result := TGocciaFunctionBase(Accessor.Getter).Call(Args, AThisContext); + finally + Args.Free; end; + Exit; end; Result := TGocciaUndefinedLiteralValue.UndefinedValue; Exit; @@ -714,6 +668,31 @@ function TGocciaObjectValue.GetPropertyWithContext(const AName: string; const AT if Assigned(FPrototype) then begin + Descriptor := FPrototype.GetOwnPropertyDescriptor(AName); + if Assigned(Descriptor) then + begin + if Descriptor is TGocciaPropertyDescriptorAccessor then + begin + Accessor := TGocciaPropertyDescriptorAccessor(Descriptor); + if Assigned(Accessor.Getter) and Accessor.Getter.IsCallable then + begin + Args := TGocciaArgumentsCollection.Create; + try + Result := TGocciaFunctionBase(Accessor.Getter).Call(Args, AThisContext); + finally + Args.Free; + end; + Exit; + end; + Result := TGocciaUndefinedLiteralValue.UndefinedValue; + Exit; + end + else if Descriptor is TGocciaPropertyDescriptorData then + begin + Result := TGocciaPropertyDescriptorData(Descriptor).Value; + Exit; + end; + end; Result := FPrototype.GetPropertyWithContext(AName, AThisContext); Exit; end; @@ -729,7 +708,7 @@ function TGocciaObjectValue.GetOwnPropertyDescriptor(const AName: string): TGocc function TGocciaObjectValue.HasProperty(const AName: string): Boolean; begin - Result := FProperties.ContainsKey(AName); + Result := HasOwnProperty(AName); if not Result and Assigned(FPrototype) then Result := FPrototype.HasProperty(AName); diff --git a/units/Goccia.Values.Primitives.Test.pas b/units/Goccia.Values.Primitives.Test.pas index 77f9874b..f47844c5 100644 --- a/units/Goccia.Values.Primitives.Test.pas +++ b/units/Goccia.Values.Primitives.Test.pas @@ -8,6 +8,7 @@ TestRunner, + Goccia.TestSetup, Goccia.Values.Primitives; type diff --git a/units/Goccia.Values.Primitives.pas b/units/Goccia.Values.Primitives.pas index 1ef2af21..f0e45252 100644 --- a/units/Goccia.Values.Primitives.pas +++ b/units/Goccia.Values.Primitives.pas @@ -90,12 +90,9 @@ TGocciaBooleanLiteralValue = class(TGocciaValue) property Value: Boolean read FValue; end; - TGocciaNumberSpecialValue = (nsvNone, nsvNaN, nsvNegativeZero, nsvInfinity, nsvNegativeInfinity); - TGocciaNumberLiteralValue = class(TGocciaValue) private FValue: Double; - FSpecialValue: TGocciaNumberSpecialValue; function GetIsNaN: Boolean; inline; function GetIsNegativeZero: Boolean; inline; @@ -112,7 +109,7 @@ TGocciaNumberLiteralValue = class(TGocciaValue) class var FSmallIntCache: array[0..255] of TGocciaNumberLiteralValue; class var FSmallIntCacheInitialized: Boolean; public - constructor Create(const AValue: Double; const ASpecialValue: TGocciaNumberSpecialValue = nsvNone); + constructor Create(const AValue: Double); class function NaNValue: TGocciaNumberLiteralValue; class function NegativeZeroValue: TGocciaNumberLiteralValue; @@ -373,99 +370,71 @@ function TGocciaNumberLiteralValue.IsPrimitive: Boolean; Result := True; end; -constructor TGocciaNumberLiteralValue.Create(const AValue: Double; const ASpecialValue: TGocciaNumberSpecialValue = nsvNone); +constructor TGocciaNumberLiteralValue.Create(const AValue: Double); begin - if Math.IsNaN(AValue) then - begin - FSpecialValue := nsvNaN; - FValue := ZERO_VALUE; - end - else if Math.IsInfinite(AValue) then - begin - if AValue > 0 then - FSpecialValue := nsvInfinity - else - FSpecialValue := nsvNegativeInfinity; - FValue := ZERO_VALUE; - end - else if (AValue = ZERO_VALUE) and (ASpecialValue = nsvNegativeZero) then - begin - FSpecialValue := nsvNegativeZero; - FValue := ZERO_VALUE; - end - else - begin - FSpecialValue := ASpecialValue; - FValue := AValue; - end; + FValue := AValue; end; function TGocciaNumberLiteralValue.GetIsNaN: Boolean; begin - Result := FSpecialValue = nsvNaN; + Result := Math.IsNaN(FValue); end; function TGocciaNumberLiteralValue.GetIsNegativeZero: Boolean; +var + V: Double; + Bits: Int64 absolute V; begin - Result := FSpecialValue = nsvNegativeZero; + V := FValue; + Result := (V = ZERO_VALUE) and (Bits < 0); end; function TGocciaNumberLiteralValue.GetIsInfinity: Boolean; begin - Result := FSpecialValue = nsvInfinity; + Result := Math.IsInfinite(FValue) and (FValue > 0); end; function TGocciaNumberLiteralValue.GetIsNegativeInfinity: Boolean; begin - Result := FSpecialValue = nsvNegativeInfinity; + Result := Math.IsInfinite(FValue) and (FValue < 0); end; function TGocciaNumberLiteralValue.GetIsInfinite: Boolean; begin - Result := (FSpecialValue = nsvInfinity) or (FSpecialValue = nsvNegativeInfinity); + Result := Math.IsInfinite(FValue); end; class function TGocciaNumberLiteralValue.NaNValue: TGocciaNumberLiteralValue; begin if not Assigned(FNanValue) then - begin - FNaNValue := TGocciaNumberLiteralValue.Create(ZERO_VALUE, nsvNaN); - end; - - // Return the cached value + FNaNValue := TGocciaNumberLiteralValue.Create(Math.NaN); Result := FNaNValue; end; class function TGocciaNumberLiteralValue.NegativeZeroValue: TGocciaNumberLiteralValue; +var + NZ: Double; begin if not Assigned(FNegativeZeroValue) then begin - FNegativeZeroValue := TGocciaNumberLiteralValue.Create(ZERO_VALUE, nsvNegativeZero); + NZ := ZERO_VALUE; + NZ := NZ * (-1.0); + FNegativeZeroValue := TGocciaNumberLiteralValue.Create(NZ); end; - - // Return the cached value Result := FNegativeZeroValue; end; class function TGocciaNumberLiteralValue.InfinityValue: TGocciaNumberLiteralValue; begin if not Assigned(FInfinityValue) then - begin - FInfinityValue := TGocciaNumberLiteralValue.Create(ZERO_VALUE, nsvInfinity); - end; - - // Return the cached value + FInfinityValue := TGocciaNumberLiteralValue.Create(Math.Infinity); Result := FInfinityValue; end; class function TGocciaNumberLiteralValue.NegativeInfinityValue: TGocciaNumberLiteralValue; begin if not Assigned(FNegativeInfinityValue) then - begin - FNegativeInfinityValue := TGocciaNumberLiteralValue.Create(ZERO_VALUE, nsvNegativeInfinity); - end; - - // Return the cached value + FNegativeInfinityValue := TGocciaNumberLiteralValue.Create(Math.NegInfinity); Result := FNegativeInfinityValue; end; @@ -513,21 +482,16 @@ function TGocciaNumberLiteralValue.TypeOf: string; function TGocciaNumberLiteralValue.RuntimeCopy: TGocciaValue; begin - // Use SmallInt cache for common integer values (already GC-pinned, zero allocation) - if (FSpecialValue = nsvNone) and (FValue >= 0) and (FValue <= 255) + if (not IsNegativeZero) and (FValue >= 0) and (FValue <= 255) and (Frac(FValue) = 0) then Result := SmallInt(Round(FValue)) else - Result := TGocciaNumberLiteralValue.Create(FValue, FSpecialValue); + Result := TGocciaNumberLiteralValue.Create(FValue); end; function TGocciaNumberLiteralValue.ToBooleanLiteral: TGocciaBooleanLiteralValue; begin - if FSpecialValue = nsvNaN then - Result := TGocciaBooleanLiteralValue.FalseValue - else if (FSpecialValue = nsvInfinity) or (FSpecialValue = nsvNegativeInfinity) then - Result := TGocciaBooleanLiteralValue.TrueValue - else if FValue = ZERO_VALUE then + if Math.IsNaN(FValue) or (FValue = ZERO_VALUE) then Result := TGocciaBooleanLiteralValue.FalseValue else Result := TGocciaBooleanLiteralValue.TrueValue; @@ -540,18 +504,19 @@ function TGocciaNumberLiteralValue.ToNumberLiteral: TGocciaNumberLiteralValue; function TGocciaNumberLiteralValue.ToStringLiteral: TGocciaStringLiteralValue; begin - case FSpecialValue of - nsvNaN: - Result := TGocciaStringLiteralValue.Create('NaN'); - nsvInfinity: - Result := TGocciaStringLiteralValue.Create('Infinity'); - nsvNegativeInfinity: - Result := TGocciaStringLiteralValue.Create('-Infinity'); - nsvNegativeZero: - Result := TGocciaStringLiteralValue.Create('0'); + if Math.IsNaN(FValue) then + Result := TGocciaStringLiteralValue.Create(NAN_LITERAL) + else if Math.IsInfinite(FValue) then + begin + if FValue > 0 then + Result := TGocciaStringLiteralValue.Create(INFINITY_LITERAL) + else + Result := TGocciaStringLiteralValue.Create(NEGATIVE_INFINITY_LITERAL); + end + else if IsNegativeZero then + Result := TGocciaStringLiteralValue.Create('0') else Result := TGocciaStringLiteralValue.Create(FloatToStr(FValue)); - end; end; diff --git a/units/Goccia.Values.TypedArrayValue.pas b/units/Goccia.Values.TypedArrayValue.pas index 7e26b4bb..2dab4990 100644 --- a/units/Goccia.Values.TypedArrayValue.pas +++ b/units/Goccia.Values.TypedArrayValue.pas @@ -174,6 +174,7 @@ function TGocciaTypedArrayValue.ReadElement(const AIndex: Integer): Double; U16: Word; I32: LongInt; U32: LongWord; + I64: Int64; F32: Single; F64: Double; begin @@ -202,12 +203,14 @@ function TGocciaTypedArrayValue.ReadElement(const AIndex: Integer): Double; takInt32: begin Move(FBufferData[Offset], I32, 4); - Result := I32; + I64 := I32; + Result := I64; end; takUint32: begin Move(FBufferData[Offset], U32, 4); - Result := U32; + I64 := U32; + Result := I64; end; takFloat32: begin @@ -294,30 +297,20 @@ procedure TGocciaTypedArrayValue.WriteElement(const AIndex: Integer; const AValu end; end; -procedure WriteFloatSpecial(var AData: TBytes; +procedure WriteFloatDirect(var AData: TBytes; const AOffset: Integer; const AKind: TGocciaTypedArrayKind; - const ASpecial: TGocciaNumberSpecialValue); -const - NAN_F32: LongWord = $7FC00000; - INF_F32: LongWord = $7F800000; - NINF_F32: LongWord = $FF800000; - NAN_F64: Int64 = Int64($7FF8000000000000); - INF_F64: Int64 = Int64($7FF0000000000000); - NINF_F64: Int64 = Int64($FFF0000000000000); + const AValue: Double); +var + F32: Single; begin case AKind of takFloat32: - case ASpecial of - nsvNaN: Move(NAN_F32, AData[AOffset], 4); - nsvInfinity: Move(INF_F32, AData[AOffset], 4); - nsvNegativeInfinity: Move(NINF_F32, AData[AOffset], 4); - end; + begin + F32 := AValue; + Move(F32, AData[AOffset], 4); + end; takFloat64: - case ASpecial of - nsvNaN: Move(NAN_F64, AData[AOffset], 8); - nsvInfinity: Move(INF_F64, AData[AOffset], 8); - nsvNegativeInfinity: Move(NINF_F64, AData[AOffset], 8); - end; + Move(AValue, AData[AOffset], 8); end; end; @@ -330,7 +323,7 @@ procedure TGocciaTypedArrayValue.WriteNumberLiteral(const AIndex: Integer; const if FKind in [takFloat32, takFloat64] then begin Offset := FByteOffset + AIndex * BytesPerElement(FKind); - WriteFloatSpecial(FBufferData, Offset, FKind, nsvNaN); + WriteFloatDirect(FBufferData, Offset, FKind, ANum.Value); end else WriteElement(AIndex, 0); @@ -342,7 +335,7 @@ procedure TGocciaTypedArrayValue.WriteNumberLiteral(const AIndex: Integer; const takFloat32, takFloat64: begin Offset := FByteOffset + AIndex * BytesPerElement(FKind); - WriteFloatSpecial(FBufferData, Offset, FKind, nsvInfinity); + WriteFloatDirect(FBufferData, Offset, FKind, ANum.Value); end; else WriteElement(AIndex, 0); @@ -354,7 +347,7 @@ procedure TGocciaTypedArrayValue.WriteNumberLiteral(const AIndex: Integer; const takFloat32, takFloat64: begin Offset := FByteOffset + AIndex * BytesPerElement(FKind); - WriteFloatSpecial(FBufferData, Offset, FKind, nsvNegativeInfinity); + WriteFloatDirect(FBufferData, Offset, FKind, ANum.Value); end; else WriteElement(AIndex, 0);