Node immutability improved

This commit is contained in:
Michael Schimmel
2025-11-05 18:13:43 +01:00
parent 0915d6d90d
commit 1f0eef7698
11 changed files with 409 additions and 308 deletions
+31 -37
View File
@@ -71,7 +71,8 @@ uses
Myc.Data.Keyword,
Myc.Data.Decimal,
Myc.Data.Series,
Myc.Data.Scalar.JSON;
Myc.Data.Scalar.JSON,
Myc.Ast.Types; // Added for VisitRecordLiteral
// Helper type for TCO via trampolining.
type
@@ -177,25 +178,21 @@ end;
function TEvaluatorVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue;
var
boundNode: TLambdaExpressionNode; // Changed
capturedCells: TArray<IValueCell>;
i: Integer;
sourceAddresses: TArray<TResolvedAddress>;
closureScope: IExecutionScope;
visitorFactory: TEvaluatorFactory;
begin
// Cast to the bound node to access binder-specific information.
boundNode := Node as TLambdaExpressionNode; // Changed
// Create the closure by capturing the value cells of all upvalues from the current scope.
sourceAddresses := boundNode.Upvalues;
// Access binder-specific information via the interface
sourceAddresses := Node.Upvalues;
SetLength(capturedCells, Length(sourceAddresses));
for i := 0 to High(sourceAddresses) do
capturedCells[i] := FScope.Capture(sourceAddresses[i]);
// Memory optimization: a lambda's scope does not need to be kept alive as a parent
// if it contains no nested lambdas that might need to capture from it later.
if boundNode.HasNestedLambdas then
if Node.HasNestedLambdas then
closureScope := FScope
else
closureScope := nil;
@@ -203,8 +200,8 @@ begin
// Get a factory to create the correct visitor (e.g., debug or production) for the lambda body.
visitorFactory := CreateVisitorFactory();
var scopeDescriptor := boundNode.ScopeDescriptor;
var params := boundNode.Parameters;
var scopeDescriptor := Node.ScopeDescriptor;
var params := Node.Parameters;
var cNode: ILambdaExpressionNode := Node;
// [unsafe] prevents a reference cycle since the closure captures itself for 'recur'.
@@ -234,7 +231,7 @@ begin
for i := 0 to High(ArgValues) do
begin
// Parameters in a bound lambda must be bound identifiers.
adr.SlotIndex := params[i].AsIdentifier.Address.SlotIndex; // Changed
adr.SlotIndex := params[i].Address.SlotIndex;
lambdaScope[adr] := ArgValues[i];
end;
@@ -274,7 +271,6 @@ function TEvaluatorVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDa
var
calleeValue: TDataValue;
argValues: TArray<TDataValue>;
boundNode: TFunctionCallNode; // Changed
begin
calleeValue := Node.Callee.Accept(Self);
if calleeValue.Kind <> vkMethod then
@@ -285,8 +281,7 @@ begin
for var i := 0 to High(argNodes) do
argValues[i] := argNodes[i].Accept(Self);
boundNode := Node as TFunctionCallNode; // Changed
if boundNode.IsTailCall then
if Node.IsTailCall then
begin
// This is a tail call. Return a thunk to be processed by the trampoline.
Result := TDataValue.FromGeneric<TThunk>(TThunk.Create(calleeValue, argValues, false));
@@ -334,7 +329,7 @@ var
itemValue, lookbackValue, seriesVar: TDataValue;
lookback: Int64;
begin
seriesVar := FScope[Node.Series.AsIdentifier.Address]; // Changed
seriesVar := FScope[Node.Series.Address];
itemValue := Node.Value.Accept(Self);
lookback := -1;
@@ -368,7 +363,7 @@ begin
// Evaluate the new value.
Result := Node.Value.Accept(Self);
// Assign it. The scope's SetValues implementation now handles boxing correctly.
FScope[Node.Identifier.AsIdentifier.Address] := Result; // Changed
FScope[Node.Identifier.Address] := Result;
end;
function TEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TDataValue;
@@ -405,7 +400,7 @@ end;
function TEvaluatorVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
begin
// The scope's GetValues implementation now handles unboxing automatically.
Result := FScope[Node.AsIdentifier.Address];
Result := FScope[Node.Address];
end;
function TEvaluatorVisitor.VisitIndexer(const Node: IIndexerNode): TDataValue;
@@ -492,22 +487,23 @@ end;
function TEvaluatorVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue;
var
i: Integer;
staticType: IStaticType;
begin
// Check which type the binder created
if Node is TGenericRecordLiteralNode then // Changed
staticType := Node.StaticType; // Get the type set by TypeChecker
if staticType.Kind = stGenericRecord then
begin
// --- NEW GENERIC PATH ---
var boundNode := Node as TGenericRecordLiteralNode; // Changed
var genFields: TArray<TPair<IKeyword, TDataValue>>;
SetLength(genFields, Length(boundNode.Fields));
SetLength(genFields, Length(Node.Fields));
// Evaluate all field values
for i := 0 to High(boundNode.Fields) do
for i := 0 to High(Node.Fields) do
begin
genFields[i] :=
TPair<IKeyword, TDataValue>.Create(
boundNode.Fields[i].Key.Value,
boundNode.Fields[i].Value.Accept(Self) // Evaluate expression
Node.Fields[i].Key.Value,
Node.Fields[i].Value.Accept(Self) // Evaluate expression
);
end;
@@ -515,21 +511,21 @@ begin
var dynRec := TDynamicRecord.Create(genFields);
Result := TDataValue.FromGenericRecord(dynRec);
end
else if Node is TRecordLiteralNode then // Changed
else if staticType.Kind = stRecord then
begin
// --- EXISTING SCALAR PATH ---
var boundNode := Node as TRecordLiteralNode; // Changed
var values: TArray<TScalar.TValue>;
SetLength(values, Length(boundNode.Fields));
SetLength(values, Length(Node.Fields));
for i := 0 to High(boundNode.Fields) do
for i := 0 to High(Node.Fields) do
begin
var valData := boundNode.Fields[i].Value.Accept(Self);
// Binder guarantees these are vkScalar
var valData := Node.Fields[i].Value.Accept(Self);
// TypeChecker guarantees these are vkScalar
values[i] := valData.AsScalar.Value;
end;
var rec := TScalarRecord.Create(boundNode.Definition, values);
// Get the definition from the type
var rec := TScalarRecord.Create(staticType.Definition, values);
Result := TDataValue.FromRecord(rec);
end
else
@@ -539,7 +535,6 @@ end;
function TEvaluatorVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
var
address: TResolvedAddress;
boundNode: TVariableDeclarationNode; // Changed
begin
// First, evaluate the initializer to get the variable's initial value.
if Assigned(Node.Initializer) then
@@ -547,12 +542,11 @@ begin
else
Result := TDataValue.Void;
// After binding, all declaration nodes are TVariableDeclarationNode
boundNode := Node as TVariableDeclarationNode;
address := boundNode.Identifier.AsIdentifier.Address;
// Access properties via interface
address := Node.Identifier.Address;
// Check the IsBoxed flag set by the binder.
if boundNode.IsBoxed then
if Node.IsBoxed then
begin
// This is a captured variable. Use the clean scope method to create the box.
Assert(address.ScopeDepth = 0);
@@ -641,7 +635,7 @@ var
seriesValue: TDataValue;
len: Int64;
begin
seriesValue := FScope[Node.Series.AsIdentifier.Address]; // Changed
seriesValue := FScope[Node.Series.Address];
case seriesValue.Kind of
vkSeries: len := seriesValue.AsSeries.Count;