Refactor Compiler Pipeline: Decouple Scope Layout from Runtime Descriptor

- **Architecture:** Split `IScopeDescriptor` into `IScopeLayout` (Binder/Structure) and immutable `IScopeDescriptor` (Runtime/Types).
- **Binder:** Now produces `IScopeLayout` via `IScopeBuilder`. Restored Upvalue tracking and Lambda nesting detection.
- **TypeChecker:** Introduced `TTypeContext` to track types during traversal. Now produces the final `IScopeDescriptor`.
- **Evaluator:** Adapted to new `ILambdaExpressionNode` structure.
- **Fixes:** Resolved `Scope depth mismatch` in TypeChecker and `AccessViolation` in Evaluator due to lost parent scopes.
This commit is contained in:
Michael Schimmel
2025-11-21 12:01:14 +01:00
parent ae10f4eee0
commit 58c44079f7
18 changed files with 1236 additions and 1194 deletions
File diff suppressed because one or more lines are too long
+2 -3
View File
@@ -5,11 +5,9 @@ uses
System.StartUpCopy,
FMX.Forms,
MainForm in 'MainForm.pas' {Form1},
Myc.Ast.Evaluator in '..\Src\AST\Myc.Ast.Evaluator.pas',
Myc.Ast.Nodes in '..\Src\AST\Myc.Ast.Nodes.pas',
Myc.Ast.Scope in '..\Src\AST\Myc.Ast.Scope.pas',
Myc.Data.Value in 'Myc.Data.Value.pas',
Myc.Ast.Debugger in '..\Src\AST\Myc.Ast.Debugger.pas',
Myc.Ast.Visitor in '..\Src\AST\Myc.Ast.Visitor.pas',
Myc.Ast.RTL in '..\Src\AST\Myc.Ast.RTL.pas',
Myc.Ast.Dumper in '..\Src\AST\Myc.Ast.Dumper.pas',
@@ -26,8 +24,9 @@ uses
Myc.Ast.Compiler.Lowering in '..\Src\AST\Myc.Ast.Compiler.Lowering.pas',
Myc.Ast.Compiler.TypeChecker in '..\Src\AST\Myc.Ast.Compiler.TypeChecker.pas',
Myc.Ast.Compiler.Macros in '..\Src\AST\Myc.Ast.Compiler.Macros.pas',
Myc.Ast.Compiler.Specializer in '..\Src\AST\Myc.Ast.Compiler.Specializer.pas',
Myc.Ast.Environment in '..\Src\AST\Myc.Ast.Environment.pas',
Myc.Ast.Compiler.Specializer in '..\Src\AST\Myc.Ast.Compiler.Specializer.pas';
Myc.Ast.Debugger in '..\Src\AST\Myc.Ast.Debugger.pas';
{$R *.res}
+2 -9
View File
@@ -135,11 +135,9 @@
<DCCReference Include="MainForm.pas">
<Form>Form1</Form>
</DCCReference>
<DCCReference Include="..\Src\AST\Myc.Ast.Evaluator.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Nodes.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Scope.pas"/>
<DCCReference Include="Myc.Data.Value.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Debugger.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Visitor.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.RTL.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Dumper.pas"/>
@@ -156,8 +154,9 @@
<DCCReference Include="..\Src\AST\Myc.Ast.Compiler.Lowering.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Compiler.TypeChecker.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Compiler.Macros.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Environment.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Compiler.Specializer.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Environment.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Debugger.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
@@ -213,12 +212,6 @@
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="Win64\Debug\ASTPlayground.rsm" Configuration="Debug" Class="DebugSymbols">
<Platform Name="Win64">
<RemoteName>ASTPlayground.rsm</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="Win64\Release\ASTPlayground.exe" Configuration="Release" Class="ProjectOutput">
<Platform Name="Win64">
<RemoteName>ASTPlayground.exe</RemoteName>
+43 -24
View File
@@ -115,6 +115,7 @@ type
FWorkspace: TAuraWorkspace;
FScriptUpdate: Boolean;
FTriggerTest: TAstEnvironment;
procedure WorkspaceMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
// Helper function to encapsulate the Compile -> Evaluate pattern
@@ -152,7 +153,15 @@ procedure TForm1.InnerLambdaButtonClick(Sender: TObject);
var
mainBlock: IAstNode;
resultValue: TDataValue;
resInt: Int64;
begin
Memo1.Lines.Clear;
Memo1.Lines.Add('--- Nested Lambda & Upvalue Mutation Test ---');
Memo1.Lines.Add('Scenario: outer() -> defines x=10');
Memo1.Lines.Add(' inner() -> calls innermost()');
Memo1.Lines.Add(' innermost() -> captures x, adds 5');
Memo1.Lines.Add('Expected Result: 15');
mainBlock :=
TAst.Block(
[
@@ -200,6 +209,19 @@ begin
resultValue := ExecuteAst(mainBlock);
if (resultValue.Kind = vkScalar) and (resultValue.AsScalar.Kind = TScalar.TKind.Ordinal) then
begin
resInt := resultValue.AsScalar.Value.AsInt64;
Memo1.Lines.Add(Format('Actual Result: %d', [resInt]));
if resInt = 15 then
Memo1.Lines.Add('SUCCESS: Deeply nested closure modified captured variable correctly.')
else
Memo1.Lines.Add(Format('FAILURE: Upvalue logic broken. Expected 15, got %d.', [resInt]));
end
else
Memo1.Lines.Add('FAILURE: Result is not an integer scalar.');
Assert(TScalar.FromInt64(15) = resultValue.AsScalar, 'The final result should be 15, but is ' + resultValue.AsScalar.ToString);
UpdateScript;
end;
@@ -220,11 +242,13 @@ begin
if CompilerStageBox.ItemIndex > 1 then
begin
var desc: IScopeDescriptor;
Result := FEnvironment.Environment.Bind(Result, desc);
var layout: IScopeLayout;
// Bind now returns the bound AST and provides the layout
Result := FEnvironment.Environment.Bind(Result, layout);
if CompilerStageBox.ItemIndex > 2 then
Result := FEnvironment.Environment.Specialize(Result, desc);
// Specialize no longer needs descriptor
Result := FEnvironment.Environment.Specialize(Result);
end;
end;
end;
@@ -247,6 +271,7 @@ begin
FEnvironment.SetStandardMode;
try
// Wrap in Lambda to compile
var funcDef := TAst.LambdaExpr([], ANode);
FCurrExec := FEnvironment.Compile(funcDef).Func;
Result := FCurrExec([]);
@@ -381,11 +406,11 @@ begin
'''
(defmacro stopwatch [body]
`(do
(def start-time (timestamp))
(def result ~body)
(print "(calculated in " (- (timestamp) start-time) " ms)" )
result
))
(def start-time (timestamp))
(def result ~body)
(print "(calculated in " (- (timestamp) start-time) " ms)" )
result
))
''')
.AsMacroDefinition
);
@@ -402,6 +427,7 @@ var
funcAst: IAstNode;
funcValue: TDataValue;
converter: IJsonAstConverter;
addr: TResolvedAddress; // Variable für das Ergebnis von Resolve
begin
// Load definitions from JSON and populate the global scope.
if not TFile.Exists(UserLibName) then
@@ -419,7 +445,7 @@ begin
try
Memo1.Lines.Add(Format('--- Loading User Library from %s ---', [ExtractFileName(UserLibName)]));
var scopeDescr := FEnvironment.RootScope.Descriptor;
// Populate the list view with loaded functions and macros.
RTLListView.Items.BeginUpdate;
try
@@ -431,8 +457,10 @@ begin
// First, deserialize the AST node from JSON.
funcAst := converter.Deserialize(pair.JsonValue as TJSONObject);
var sym := scopeDescr.FindSymbol(pair.JsonString.Value);
if sym.Address.Kind = akUnresolved then
// KORREKTUR: Statt Descriptor.FindSymbol nutzen wir Scope.Resolve
addr := FEnvironment.RootScope.Resolve(pair.JsonString.Value);
if addr.Kind = akUnresolved then
begin
// Distinguish between loading a macro and loading a function.
if funcAst.Kind = akMacroDefinition then
@@ -451,6 +479,7 @@ begin
end
else
Memo1.Lines.Add(Format('Symbol "%s" already defined', [pair.JsonString.Value]));
// Add the new function/macro to the RTL list view.
RTLListView.Items.Add.Text := pair.JsonString.Value;
end;
@@ -615,7 +644,7 @@ begin
try
FCurrUnboundAst := converter.Deserialize(jsonObj);
// Run the full pipeline
// Run the full pipeline via environment
FCurrExec := FEnvironment.Compile(FCurrUnboundAst).Func;
Memo1.Lines.Add('AST deserialized and bound successfully from JSON.');
@@ -674,13 +703,13 @@ begin
Memo1.Lines.Clear;
Memo1.Lines.Add('--- AST Dump ---');
if not Assigned(FCurrExec) then // Check FCurrExec
if not Assigned(FCurrExec) then
begin
Memo1.Lines.Add('No *compiled* AST has been generated yet. Click a test button first.');
exit;
end;
// Dump the compiled AST from the executable
// Dump the compiled AST from the visualizer helper
TAstDumper.Dump(CompileAstStage, Memo1.Lines);
end;
@@ -688,7 +717,6 @@ procedure TForm1.FibonacciButtonClick(Sender: TObject);
var
result: TDataValue;
sw: TStopwatch;
fibDataValue: TDataValue;
begin
Memo1.Lines.Clear;
@@ -1319,15 +1347,6 @@ begin
exit;
FWorkspace.Build(CompileAstStage, TPointF.Create(X, Y));
//
// if FCurrExec <> nil then
// begin
// FWorkspace.Build(FCurrExec.CompiledAst, TPointF.Create(X, Y));
// end
// else if FCurrUnboundAst <> nil then
// begin
// FWorkspace.Build(FCurrUnboundAst, TPointF.Create(X, Y));
// end;
end;
end.