AST Identities
This commit is contained in:
@@ -13,6 +13,7 @@ uses
|
|||||||
Myc.Ast.Scope,
|
Myc.Ast.Scope,
|
||||||
Myc.Ast.Types,
|
Myc.Ast.Types,
|
||||||
Myc.Ast.Compiler.Binder.Upvalues,
|
Myc.Ast.Compiler.Binder.Upvalues,
|
||||||
|
Myc.Ast.Identities, // Wichtig für AsNamed
|
||||||
Myc.Ast;
|
Myc.Ast;
|
||||||
|
|
||||||
type
|
type
|
||||||
@@ -156,7 +157,7 @@ begin
|
|||||||
|
|
||||||
Result := Accept(RootNode);
|
Result := Accept(RootNode);
|
||||||
if not Assigned(Result) then
|
if not Assigned(Result) then
|
||||||
Result := TAst.Block([]);
|
Result := TAst.Block([], nil);
|
||||||
|
|
||||||
Layout := FCurrentBuilder.Build;
|
Layout := FCurrentBuilder.Build;
|
||||||
end;
|
end;
|
||||||
@@ -168,12 +169,10 @@ begin
|
|||||||
if Name.IsEmpty then
|
if Name.IsEmpty then
|
||||||
exit(False);
|
exit(False);
|
||||||
|
|
||||||
// Support standard identifiers and hygienic macro names (e.g. loop#1)
|
|
||||||
for c in Name do
|
for c in Name do
|
||||||
if not (c.IsLetterOrDigit or (c = '_') or (c = '-') or (c = '#')) then
|
if not (c.IsLetterOrDigit or (c = '_') or (c = '-') or (c = '#')) then
|
||||||
exit(False);
|
exit(False);
|
||||||
|
|
||||||
// First char check (cannot be digit unless it's a pure number which is tokenized differently)
|
|
||||||
c := Name[1];
|
c := Name[1];
|
||||||
if not (c.IsLetter or (c = '_') or (c = '#')) then
|
if not (c.IsLetter or (c = '_') or (c = '#')) then
|
||||||
exit(False);
|
exit(False);
|
||||||
@@ -187,7 +186,6 @@ var
|
|||||||
depth: Integer;
|
depth: Integer;
|
||||||
slot: Integer;
|
slot: Integer;
|
||||||
begin
|
begin
|
||||||
// Start search in current builder
|
|
||||||
layout := FCurrentBuilder;
|
layout := FCurrentBuilder;
|
||||||
depth := 0;
|
depth := 0;
|
||||||
|
|
||||||
@@ -238,12 +236,14 @@ function TAstBinder.VisitIdentifier(const Node: IIdentifierNode): IAstNode;
|
|||||||
var
|
var
|
||||||
physAddr: TResolvedAddress;
|
physAddr: TResolvedAddress;
|
||||||
upvalueIndex: Integer;
|
upvalueIndex: Integer;
|
||||||
|
identity: INamedIdentity;
|
||||||
begin
|
begin
|
||||||
|
identity := Node.Identity.AsNamed; // Type-safe extraction of identity
|
||||||
|
|
||||||
if Node.Address.Kind = akUnresolved then
|
if Node.Address.Kind = akUnresolved then
|
||||||
begin
|
begin
|
||||||
if not ResolveSymbol(Node.Name, physAddr) then
|
if not ResolveSymbol(Node.Name, physAddr) then
|
||||||
begin
|
begin
|
||||||
// Log error but don't crash. Return unresolved node (Poison Pill).
|
|
||||||
if Assigned(FLog) then
|
if Assigned(FLog) then
|
||||||
FLog.AddError(Format('Undefined identifier: "%s"', [Node.Name]), Node);
|
FLog.AddError(Format('Undefined identifier: "%s"', [Node.Name]), Node);
|
||||||
Result := Node;
|
Result := Node;
|
||||||
@@ -252,13 +252,12 @@ begin
|
|||||||
|
|
||||||
if physAddr.ScopeDepth > 0 then
|
if physAddr.ScopeDepth > 0 then
|
||||||
begin
|
begin
|
||||||
// It's an upvalue (from a parent scope). Capture it.
|
|
||||||
upvalueIndex := CaptureUpvalue(physAddr);
|
upvalueIndex := CaptureUpvalue(physAddr);
|
||||||
Result := TAst.Identifier(Node.Name, TResolvedAddress.Create(akUpvalue, 0, upvalueIndex), TTypes.Unknown);
|
// Recreate identifier using the SAME identity but new address
|
||||||
|
Result := TAst.Identifier(identity, TResolvedAddress.Create(akUpvalue, 0, upvalueIndex), TTypes.Unknown);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
// It's local.
|
Result := TAst.Identifier(identity, physAddr, TTypes.Unknown);
|
||||||
Result := TAst.Identifier(Node.Name, physAddr, TTypes.Unknown);
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
Result := Node;
|
Result := Node;
|
||||||
@@ -271,24 +270,23 @@ var
|
|||||||
newInit: IAstNode;
|
newInit: IAstNode;
|
||||||
newIdent: IIdentifierNode;
|
newIdent: IIdentifierNode;
|
||||||
isBoxed: Boolean;
|
isBoxed: Boolean;
|
||||||
|
identifier: IIdentifierNode;
|
||||||
|
identIdentity: INamedIdentity;
|
||||||
begin
|
begin
|
||||||
var identifier := Node.Target.AsIdentifier;
|
identifier := Node.Target.AsIdentifier;
|
||||||
|
identIdentity := identifier.Identity.AsNamed;
|
||||||
|
|
||||||
if not IsValidIdentifier(identifier.Name) then
|
if not IsValidIdentifier(identifier.Name) then
|
||||||
begin
|
begin
|
||||||
if Assigned(FLog) then
|
if Assigned(FLog) then
|
||||||
FLog.AddError(Format('Invalid identifier name: "%s".', [identifier.Name]), Node);
|
FLog.AddError(Format('Invalid identifier name: "%s".', [identifier.Name]), Node);
|
||||||
// Continue to try processing the initializer
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Check for duplicates before defining
|
|
||||||
if FCurrentBuilder.FindSlot(identifier.Name) >= 0 then
|
if FCurrentBuilder.FindSlot(identifier.Name) >= 0 then
|
||||||
begin
|
begin
|
||||||
if Assigned(FLog) then
|
if Assigned(FLog) then
|
||||||
FLog.AddError(Format('Variable "%s" is already defined in this scope.', [identifier.Name]), Node);
|
FLog.AddError(Format('Variable "%s" is already defined in this scope.', [identifier.Name]), Node);
|
||||||
|
|
||||||
// Do NOT define the slot to avoid scope corruption.
|
|
||||||
// Visit initializer to catch errors there, then return original node or dummy.
|
|
||||||
if Assigned(Node.Initializer) then
|
if Assigned(Node.Initializer) then
|
||||||
Accept(Node.Initializer);
|
Accept(Node.Initializer);
|
||||||
|
|
||||||
@@ -304,10 +302,13 @@ begin
|
|||||||
else
|
else
|
||||||
newInit := nil;
|
newInit := nil;
|
||||||
|
|
||||||
newIdent := TAst.Identifier(identifier.Name, addr, TTypes.Unknown);
|
// Recreate identifier with bound address using original identity
|
||||||
|
newIdent := TAst.Identifier(identIdentity, addr, TTypes.Unknown);
|
||||||
|
|
||||||
isBoxed := (FBoxedDeclarations <> nil) and FBoxedDeclarations.Contains(Node);
|
isBoxed := (FBoxedDeclarations <> nil) and FBoxedDeclarations.Contains(Node);
|
||||||
|
|
||||||
Result := TAst.VarDecl(newIdent, newInit, TTypes.Unknown, isBoxed);
|
// Recreate variable decl using original identity
|
||||||
|
Result := TAst.VarDecl(Node.Identity, newIdent, newInit, TTypes.Unknown, isBoxed);
|
||||||
|
|
||||||
if Assigned(FFunctionRegistry) and (newInit <> nil) and (newInit.Kind = akLambdaExpression) then
|
if Assigned(FFunctionRegistry) and (newInit <> nil) and (newInit.Kind = akLambdaExpression) then
|
||||||
FFunctionRegistry.Register(addr, newInit.AsLambdaExpression);
|
FFunctionRegistry.Register(addr, newInit.AsLambdaExpression);
|
||||||
@@ -321,11 +322,10 @@ begin
|
|||||||
newIdent := Accept(Node.Target);
|
newIdent := Accept(Node.Target);
|
||||||
newValue := Accept(Node.Value);
|
newValue := Accept(Node.Value);
|
||||||
|
|
||||||
Result := TAst.Assign(newIdent.AsIdentifier, newValue, TTypes.Unknown);
|
Result := TAst.Assign(Node.Identity, newIdent, newValue, TTypes.Unknown);
|
||||||
|
|
||||||
if Assigned(FFunctionRegistry) and (newValue <> nil) and (newValue.Kind = akLambdaExpression) then
|
if Assigned(FFunctionRegistry) and (newValue <> nil) and (newValue.Kind = akLambdaExpression) then
|
||||||
begin
|
begin
|
||||||
// Only register if it resolved correctly
|
|
||||||
if newIdent.AsIdentifier.Address.Kind = akLocalOrParent then
|
if newIdent.AsIdentifier.Address.Kind = akLocalOrParent then
|
||||||
FFunctionRegistry.Register(newIdent.AsIdentifier.Address, newValue.AsLambdaExpression);
|
FFunctionRegistry.Register(newIdent.AsIdentifier.Address, newValue.AsLambdaExpression);
|
||||||
end;
|
end;
|
||||||
@@ -343,15 +343,13 @@ var
|
|||||||
capturedMap: TUpvalueMap;
|
capturedMap: TUpvalueMap;
|
||||||
sortedPairs: TArray<TPair<TResolvedAddress, Integer>>;
|
sortedPairs: TArray<TPair<TResolvedAddress, Integer>>;
|
||||||
upvaluesList: TArray<TResolvedAddress>;
|
upvaluesList: TArray<TResolvedAddress>;
|
||||||
|
|
||||||
startCount: Integer;
|
startCount: Integer;
|
||||||
hasNested: Boolean;
|
hasNested: Boolean;
|
||||||
|
paramIdentity: INamedIdentity;
|
||||||
begin
|
begin
|
||||||
// 1. Count logic to determine if this lambda has nested lambdas
|
|
||||||
startCount := FLambdaCounter;
|
startCount := FLambdaCounter;
|
||||||
Inc(FLambdaCounter); // Count myself
|
Inc(FLambdaCounter);
|
||||||
|
|
||||||
// 2. Push Scope
|
|
||||||
parentBuilder := FCurrentBuilder;
|
parentBuilder := FCurrentBuilder;
|
||||||
FCurrentBuilder := TScope.CreateBuilder(parentBuilder);
|
FCurrentBuilder := TScope.CreateBuilder(parentBuilder);
|
||||||
|
|
||||||
@@ -364,25 +362,21 @@ begin
|
|||||||
SetLength(newParams, Length(Node.Parameters));
|
SetLength(newParams, Length(Node.Parameters));
|
||||||
for i := 0 to High(Node.Parameters) do
|
for i := 0 to High(Node.Parameters) do
|
||||||
begin
|
begin
|
||||||
var paramName := Node.Parameters[i].Name;
|
var paramNode := Node.Parameters[i];
|
||||||
|
var paramName := paramNode.Name;
|
||||||
|
paramIdentity := paramNode.Identity.AsNamed;
|
||||||
|
|
||||||
// Check duplicate parameters
|
|
||||||
if FCurrentBuilder.FindSlot(paramName) >= 0 then
|
if FCurrentBuilder.FindSlot(paramName) >= 0 then
|
||||||
begin
|
begin
|
||||||
if Assigned(FLog) then
|
if Assigned(FLog) then
|
||||||
FLog.AddError(Format('Duplicate parameter name "%s".', [paramName]), Node);
|
FLog.AddError(Format('Duplicate parameter name "%s".', [paramName]), Node);
|
||||||
// Skip defining this parameter to avoid scope crash, but create a dummy binding
|
|
||||||
// so index alignment isn't completely broken if we were to continue harder.
|
|
||||||
// Here we just skip definition.
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
FCurrentBuilder.Define(paramName);
|
FCurrentBuilder.Define(paramName);
|
||||||
|
|
||||||
// Note: We still create a valid (or attempted) identifier node mapping
|
slot := FCurrentBuilder.FindSlot(paramName);
|
||||||
// even if definition failed, to keep AST shape consistent.
|
|
||||||
slot := FCurrentBuilder.FindSlot(paramName); // might return <0 or previous slot if duplicate
|
|
||||||
if slot < 0 then
|
if slot < 0 then
|
||||||
slot := 0; // Fallback for duplicates
|
slot := 0;
|
||||||
|
|
||||||
addr := TResolvedAddress.Create(akLocalOrParent, 0, slot);
|
addr := TResolvedAddress.Create(akLocalOrParent, 0, slot);
|
||||||
|
|
||||||
@@ -390,7 +384,8 @@ begin
|
|||||||
if (parentBuilder.Parent = nil) and (FArgTypes <> nil) and (i < Length(FArgTypes)) then
|
if (parentBuilder.Parent = nil) and (FArgTypes <> nil) and (i < Length(FArgTypes)) then
|
||||||
paramType := FArgTypes[i];
|
paramType := FArgTypes[i];
|
||||||
|
|
||||||
newParams[i] := TAst.Identifier(paramName, addr, paramType);
|
// Rebind parameter identifier using original identity
|
||||||
|
newParams[i] := TAst.Identifier(paramIdentity, addr, paramType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
newBody := Accept(Node.Body);
|
newBody := Accept(Node.Body);
|
||||||
@@ -421,18 +416,30 @@ begin
|
|||||||
FUpvalueStack.Pop;
|
FUpvalueStack.Pop;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// 3. Calculate hasNested
|
|
||||||
// If FLambdaCounter increased by more than just 1 (myself), children were visited.
|
|
||||||
hasNested := FLambdaCounter > (startCount + 1);
|
hasNested := FLambdaCounter > (startCount + 1);
|
||||||
|
|
||||||
Result := TAst.LambdaExpr(newParams, newBody, finalLayout, nil, upvaluesList, hasNested, Node.IsPure);
|
// Rebuild Lambda using original identity
|
||||||
|
Result :=
|
||||||
|
TAst.LambdaExpr(
|
||||||
|
Node.Identity,
|
||||||
|
newParams,
|
||||||
|
newBody,
|
||||||
|
finalLayout,
|
||||||
|
nil, // Descriptor is created in TypeChecker
|
||||||
|
upvaluesList,
|
||||||
|
hasNested,
|
||||||
|
Node.IsPure
|
||||||
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstBinder.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
|
function TAstBinder.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
if Node.Callee.Kind = akKeyword then
|
if Node.Callee.Kind = akKeyword then
|
||||||
begin
|
begin
|
||||||
var memberAccess := TAst.MemberAccess(Accept(Node.Arguments[0]), Node.Callee.AsKeyword);
|
// Transform keyword call (:key obj) into member access (.key obj)
|
||||||
|
// Note: The new MemberAccess node gets the identity of the original FunctionCall
|
||||||
|
// The member keyword retains its own identity.
|
||||||
|
var memberAccess := TAst.MemberAccess(Node.Identity, Accept(Node.Arguments[0]), Node.Callee.AsKeyword);
|
||||||
Result := memberAccess;
|
Result := memberAccess;
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ type
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// Callback used by TMacroExpander to request full compilation and execution of macro arguments.
|
// Callback used by TMacroExpander to request full compilation and execution of macro arguments.
|
||||||
// This abstracts away the specific Binder, TypeChecker, and Evaluator implementations.
|
|
||||||
TMacroEvaluatorProc = reference to function(const Scope: IExecutionScope; const Node: IAstNode): TDataValue;
|
TMacroEvaluatorProc = reference to function(const Scope: IExecutionScope; const Node: IAstNode): TDataValue;
|
||||||
|
|
||||||
// Handles the expansion of the macro body template (Quasiquotes/Unquotes).
|
// Handles the expansion of the macro body template (Quasiquotes/Unquotes).
|
||||||
@@ -146,8 +145,6 @@ end;
|
|||||||
|
|
||||||
function TExpansionVisitor.Gensym(const ABaseName: string): string;
|
function TExpansionVisitor.Gensym(const ABaseName: string): string;
|
||||||
begin
|
begin
|
||||||
// Hygienic macros: Generate unique names for identifiers introduced by the macro
|
|
||||||
// that were not captured from the arguments.
|
|
||||||
if not FRenameMap.TryGetValue(ABaseName, Result) then
|
if not FRenameMap.TryGetValue(ABaseName, Result) then
|
||||||
begin
|
begin
|
||||||
var count := TInterlocked.Add(FGensymCounter, 1);
|
var count := TInterlocked.Add(FGensymCounter, 1);
|
||||||
@@ -187,7 +184,6 @@ begin
|
|||||||
var spliceExpr := node.AsUnquoteSplicing.Expression;
|
var spliceExpr := node.AsUnquoteSplicing.Expression;
|
||||||
var evaluatedSpliceValue: TDataValue;
|
var evaluatedSpliceValue: TDataValue;
|
||||||
|
|
||||||
// GUARDED EVALUATION
|
|
||||||
try
|
try
|
||||||
evaluatedSpliceValue := FMacroEvaluator(FMacroScope, spliceExpr);
|
evaluatedSpliceValue := FMacroEvaluator(FMacroScope, spliceExpr);
|
||||||
except
|
except
|
||||||
@@ -207,7 +203,8 @@ begin
|
|||||||
end
|
end
|
||||||
else if (not evaluatedSpliceValue.IsVoid) then
|
else if (not evaluatedSpliceValue.IsVoid) then
|
||||||
begin
|
begin
|
||||||
newList.Add(TAst.Constant(evaluatedSpliceValue));
|
// Use Splice Node Identity for the new Constant
|
||||||
|
newList.Add(TAst.Constant(evaluatedSpliceValue, node.Identity.Location));
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@@ -230,7 +227,8 @@ begin
|
|||||||
// Apply hygienic renaming
|
// Apply hygienic renaming
|
||||||
if FRenameMap.TryGetValue(Node.Name, newName) then
|
if FRenameMap.TryGetValue(Node.Name, newName) then
|
||||||
begin
|
begin
|
||||||
Result := TAst.Identifier(newName, TTypes.Unknown);
|
// Create new Identifier with new Name but preserve Location from original Node
|
||||||
|
Result := TAst.Identifier(newName, Node.Identity.Location);
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
Result := Node;
|
Result := Node;
|
||||||
@@ -243,19 +241,18 @@ var
|
|||||||
begin
|
begin
|
||||||
newInit := Accept(Node.Initializer);
|
newInit := Accept(Node.Initializer);
|
||||||
|
|
||||||
// Check if target is identifier before trying to rename
|
|
||||||
if Node.Target.Kind = akIdentifier then
|
if Node.Target.Kind = akIdentifier then
|
||||||
begin
|
begin
|
||||||
newName := Gensym(Node.Target.AsIdentifier.Name);
|
newName := Gensym(Node.Target.AsIdentifier.Name);
|
||||||
newTarget := TAst.Identifier(newName, TTypes.Unknown);
|
// Use location from original target
|
||||||
|
newTarget := TAst.Identifier(newName, Node.Target.Identity.Location);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
// Recursively expand unquotes in target position (advanced usage)
|
|
||||||
newTarget := Accept(Node.Target);
|
newTarget := Accept(Node.Target);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TAst.VarDecl(newTarget, newInit, TTypes.Unknown);
|
Result := TAst.VarDecl(Node.Identity, newTarget, newInit, TTypes.Unknown, False);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TExpansionVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
|
function TExpansionVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
|
||||||
@@ -265,17 +262,18 @@ var
|
|||||||
newName: string;
|
newName: string;
|
||||||
i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
// Parameters in a macro body must also be renamed hygienically
|
|
||||||
SetLength(newParams, Length(Node.Parameters));
|
SetLength(newParams, Length(Node.Parameters));
|
||||||
for i := 0 to High(Node.Parameters) do
|
for i := 0 to High(Node.Parameters) do
|
||||||
begin
|
begin
|
||||||
newName := Gensym(Node.Parameters[i].Name);
|
newName := Gensym(Node.Parameters[i].Name);
|
||||||
newParams[i] := TAst.Identifier(newName, TTypes.Unknown);
|
// Use location from original param
|
||||||
|
newParams[i] := TAst.Identifier(newName, Node.Parameters[i].Identity.Location);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
newBody := Accept(Node.Body);
|
newBody := Accept(Node.Body);
|
||||||
|
|
||||||
Result := TAst.LambdaExpr(newParams, newBody, nil, nil, nil, False, Node.IsPure, TTypes.Unknown);
|
// Rebuild structural lambda
|
||||||
|
Result := TAst.LambdaExpr(Node.Identity, newParams, newBody);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TExpansionVisitor.VisitUnquote(const Node: IUnquoteNode): IAstNode;
|
function TExpansionVisitor.VisitUnquote(const Node: IUnquoteNode): IAstNode;
|
||||||
@@ -300,8 +298,6 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Evaluate the expression at compile time.
|
|
||||||
// This executes user code! It must be guarded.
|
|
||||||
try
|
try
|
||||||
value := FMacroEvaluator(FMacroScope, expr);
|
value := FMacroEvaluator(FMacroScope, expr);
|
||||||
except
|
except
|
||||||
@@ -318,14 +314,14 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
if value.Kind in [vkScalar, vkText, vkVoid] then
|
if value.Kind in [vkScalar, vkText, vkVoid] then
|
||||||
Result := TAst.Constant(value)
|
// Create Constant node, inheriting location from the Unquote node
|
||||||
|
Result := TAst.Constant(value, Node.Identity.Location)
|
||||||
else
|
else
|
||||||
raise EMacroException.CreateFmt('Cannot unquote complex runtime value of type %s at compile time.', [value.Kind.ToString]);
|
raise EMacroException.CreateFmt('Cannot unquote complex runtime value of type %s at compile time.', [value.Kind.ToString]);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TExpansionVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode;
|
function TExpansionVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
// ~@ can only be handled within a list context (TransformAndSpliceNodes)
|
|
||||||
raise EMacroException.Create('Unquote-splicing (`~@`) can only appear inside a list form.');
|
raise EMacroException.Create('Unquote-splicing (`~@`) can only appear inside a list form.');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -335,23 +331,20 @@ var
|
|||||||
transformedCallee: IAstNode;
|
transformedCallee: IAstNode;
|
||||||
begin
|
begin
|
||||||
transformedCallee := Self.Accept(Node.Callee);
|
transformedCallee := Self.Accept(Node.Callee);
|
||||||
// Use special splicing logic for argument lists
|
|
||||||
newArgs := TransformAndSpliceNodes(Node.Arguments);
|
newArgs := TransformAndSpliceNodes(Node.Arguments);
|
||||||
Result := TAst.FunctionCall(transformedCallee, newArgs);
|
Result := TAst.FunctionCall(Node.Identity, transformedCallee, newArgs);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TExpansionVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
|
function TExpansionVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
|
||||||
var
|
var
|
||||||
newExprs: TArray<IAstNode>;
|
newExprs: TArray<IAstNode>;
|
||||||
begin
|
begin
|
||||||
// Use special splicing logic for block expressions
|
|
||||||
newExprs := TransformAndSpliceNodes(Node.Expressions);
|
newExprs := TransformAndSpliceNodes(Node.Expressions);
|
||||||
Result := TAst.Block(newExprs);
|
Result := TAst.Block(Node.Identity, newExprs);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TExpansionVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode;
|
function TExpansionVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
// Standard recursion for records
|
|
||||||
Result := inherited VisitRecordLiteral(Node);
|
Result := inherited VisitRecordLiteral(Node);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -366,7 +359,6 @@ begin
|
|||||||
inherited Create;
|
inherited Create;
|
||||||
FInitialScope := AInitialScope;
|
FInitialScope := AInitialScope;
|
||||||
FMacroEvaluator := AMacroEvaluator;
|
FMacroEvaluator := AMacroEvaluator;
|
||||||
// Create a local child registry to handle scoped macro definitions (defmacro inside do)
|
|
||||||
FCurrentMacroRegistry := ARootRegistry.CreateChildRegistry;
|
FCurrentMacroRegistry := ARootRegistry.CreateChildRegistry;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -400,12 +392,11 @@ function TMacroExpander.Execute(const RootNode: IAstNode): IAstNode;
|
|||||||
begin
|
begin
|
||||||
Result := Accept(RootNode);
|
Result := Accept(RootNode);
|
||||||
if not Assigned(Result) then
|
if not Assigned(Result) then
|
||||||
Result := TAst.Block([]);
|
Result := TAst.Block([], nil);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMacroExpander.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
|
function TMacroExpander.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
// Macros defined inside a block should only be visible within that block
|
|
||||||
EnterMacroScope;
|
EnterMacroScope;
|
||||||
try
|
try
|
||||||
Result := inherited VisitBlockExpression(Node);
|
Result := inherited VisitBlockExpression(Node);
|
||||||
@@ -426,11 +417,7 @@ end;
|
|||||||
|
|
||||||
function TMacroExpander.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode;
|
function TMacroExpander.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
// Register the macro in the current scope
|
|
||||||
FCurrentMacroRegistry.Define(Node);
|
FCurrentMacroRegistry.Define(Node);
|
||||||
// Remove the definition from the runtime AST (it's compile-time only)
|
|
||||||
// However, for now we return the node so it can be serialized/inspected,
|
|
||||||
// but the Evaluator usually ignores it (returns Void).
|
|
||||||
Result := Node;
|
Result := Node;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -440,7 +427,6 @@ var
|
|||||||
macroDef: IMacroDefinitionNode;
|
macroDef: IMacroDefinitionNode;
|
||||||
i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
// Check if it is a macro call
|
|
||||||
if Node.Callee.Kind <> akIdentifier then
|
if Node.Callee.Kind <> akIdentifier then
|
||||||
exit(inherited VisitFunctionCall(Node));
|
exit(inherited VisitFunctionCall(Node));
|
||||||
|
|
||||||
@@ -449,41 +435,31 @@ begin
|
|||||||
if macroDef = nil then
|
if macroDef = nil then
|
||||||
exit(inherited VisitFunctionCall(Node));
|
exit(inherited VisitFunctionCall(Node));
|
||||||
|
|
||||||
// --- Macro Expansion Logic ---
|
|
||||||
|
|
||||||
// 1. Create temporary scope for macro arguments (parented to Initial/Global Scope)
|
|
||||||
// This ensures macro execution is hygienic regarding the environment it runs in.
|
|
||||||
var expansionScope := TScope.CreateScope(FInitialScope, nil, nil);
|
var expansionScope := TScope.CreateScope(FInitialScope, nil, nil);
|
||||||
|
|
||||||
var params := macroDef.Parameters;
|
var params := macroDef.Parameters;
|
||||||
if Length(Node.Arguments) <> Length(params) then
|
if Length(Node.Arguments) <> Length(params) then
|
||||||
raise EMacroException.CreateFmt('Macro %s expects %d arguments.', [calleeIdentifier.Name, Length(params)]);
|
raise EMacroException.CreateFmt('Macro %s expects %d arguments.', [calleeIdentifier.Name, Length(params)]);
|
||||||
|
|
||||||
// 2. Populate scope with UN-EVALUATED AST nodes (Code as Data)
|
|
||||||
for i := 0 to High(params) do
|
for i := 0 to High(params) do
|
||||||
expansionScope.Define(params[i].Name, TDataValue.FromIntf<IAstNode>(Node.Arguments[i]));
|
expansionScope.Define(params[i].Name, TDataValue.FromIntf<IAstNode>(Node.Arguments[i]));
|
||||||
|
|
||||||
// 4. Expand the Macro Body
|
|
||||||
// We assume the macro body is a Quasiquote. We expand it using the visitor.
|
|
||||||
var expandedBody := TExpansionVisitor.Expand(expansionScope, macroDef.Body.AsQuasiquote.Expression, FMacroEvaluator);
|
var expandedBody := TExpansionVisitor.Expand(expansionScope, macroDef.Body.AsQuasiquote.Expression, FMacroEvaluator);
|
||||||
|
|
||||||
// 5. Create a MacroExpansionNode to preserve source mapping
|
// The MacroExpansionNode wraps the expansion, preserving the original call's identity (Source Location)
|
||||||
var macroNode := TAst.MacroExpansionNode(Node, expandedBody);
|
// for better error reporting.
|
||||||
|
var macroNode := TAst.MacroExpansionNode(Node.Identity, Node, expandedBody);
|
||||||
|
|
||||||
// 6. Recursively expand the result (the output of a macro might contain more macro calls)
|
|
||||||
Result := Self.Accept(macroNode);
|
Result := Self.Accept(macroNode);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMacroExpander.VisitQuasiquote(const Node: IQuasiquoteNode): IAstNode;
|
function TMacroExpander.VisitQuasiquote(const Node: IQuasiquoteNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
// Quasiquotes in user code are not expanded by the MacroExpander,
|
|
||||||
// they are literals.
|
|
||||||
Result := Node;
|
Result := Node;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMacroExpander.VisitUnquote(const Node: IUnquoteNode): IAstNode;
|
function TMacroExpander.VisitUnquote(const Node: IUnquoteNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
// Unquotes outside of a macro definition/expansion phase are invalid syntax
|
|
||||||
raise EMacroException.Create('Unquote (`~`) can only be used inside a quasiquote.');
|
raise EMacroException.Create('Unquote (`~`) can only be used inside a quasiquote.');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ function TStaticSpecializer.Execute(const RootNode: IAstNode): IAstNode;
|
|||||||
begin
|
begin
|
||||||
Result := Accept(RootNode);
|
Result := Accept(RootNode);
|
||||||
if not Assigned(Result) then
|
if not Assigned(Result) then
|
||||||
Result := TAst.Block([]);
|
Result := TAst.Block([], nil);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TStaticSpecializer.GetStaticRtlFunction(const AName: string; const AArgTypes: TArray<IStaticType>): TSpecializedMethod;
|
function TStaticSpecializer.GetStaticRtlFunction(const AName: string; const AArgTypes: TArray<IStaticType>): TSpecializedMethod;
|
||||||
@@ -150,7 +150,7 @@ begin
|
|||||||
if (newCallee = Node.Callee) and (newArgs = Node.Arguments) then
|
if (newCallee = Node.Callee) and (newArgs = Node.Arguments) then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
Result := TAst.FunctionCall(newCallee, newArgs, Node.StaticType, Node.IsTailCall, nil);
|
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgs, Node.StaticType, Node.IsTailCall, nil);
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ begin
|
|||||||
if (newCallee = Node.Callee) and (newArgs = Node.Arguments) then
|
if (newCallee = Node.Callee) and (newArgs = Node.Arguments) then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
Result := TAst.FunctionCall(newCallee, newArgs, Node.StaticType, Node.IsTailCall, nil);
|
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgs, Node.StaticType, Node.IsTailCall, nil);
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -189,6 +189,7 @@ begin
|
|||||||
// 4a. Cache Hit (Environment)
|
// 4a. Cache Hit (Environment)
|
||||||
Result :=
|
Result :=
|
||||||
TAst.FunctionCall(
|
TAst.FunctionCall(
|
||||||
|
Node.Identity,
|
||||||
newCallee,
|
newCallee,
|
||||||
newArgs,
|
newArgs,
|
||||||
specializedMethod.ReturnType,
|
specializedMethod.ReturnType,
|
||||||
@@ -208,6 +209,7 @@ begin
|
|||||||
|
|
||||||
Result :=
|
Result :=
|
||||||
TAst.FunctionCall(
|
TAst.FunctionCall(
|
||||||
|
Node.Identity,
|
||||||
newCallee,
|
newCallee,
|
||||||
newArgs,
|
newArgs,
|
||||||
specializedMethod.ReturnType,
|
specializedMethod.ReturnType,
|
||||||
@@ -229,7 +231,7 @@ begin
|
|||||||
var lambdaDef := funcDef.AsLambdaExpression;
|
var lambdaDef := funcDef.AsLambdaExpression;
|
||||||
if (Length(lambdaDef.Upvalues) > 0) or (lambdaDef.HasNestedLambdas) then
|
if (Length(lambdaDef.Upvalues) > 0) or (lambdaDef.HasNestedLambdas) then
|
||||||
begin
|
begin
|
||||||
Result := TAst.FunctionCall(newCallee, newArgs, Node.StaticType, Node.IsTailCall, nil);
|
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgs, Node.StaticType, Node.IsTailCall, nil);
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -250,7 +252,7 @@ begin
|
|||||||
FMonomorphCache.Add(key, specializedMethod);
|
FMonomorphCache.Add(key, specializedMethod);
|
||||||
|
|
||||||
// 6c. Return the new node
|
// 6c. Return the new node
|
||||||
Result := TAst.FunctionCall(newCallee, newArgs, returnType, Node.IsTailCall, compiled.Func, compiled.IsPure);
|
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgs, returnType, Node.IsTailCall, compiled.Func, compiled.IsPure);
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -258,7 +260,7 @@ begin
|
|||||||
if (newCallee = Node.Callee) and (newArgs = Node.Arguments) then
|
if (newCallee = Node.Callee) and (newArgs = Node.Arguments) then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
Result := TAst.FunctionCall(newCallee, newArgs, Node.StaticType, Node.IsTailCall, nil);
|
Result := TAst.FunctionCall(Node.Identity, newCallee, newArgs, Node.StaticType, Node.IsTailCall, nil);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TMonoCacheKey }
|
{ TMonoCacheKey }
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ function TAstTCO.Execute(const RootNode: IAstNode): IAstNode;
|
|||||||
begin
|
begin
|
||||||
Result := Accept(RootNode);
|
Result := Accept(RootNode);
|
||||||
if not Assigned(Result) then
|
if not Assigned(Result) then
|
||||||
Result := TAst.Block([]);
|
Result := TAst.Block([], nil);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTCO.Accept(const Node: IAstNode): IAstNode;
|
function TAstTCO.Accept(const Node: IAstNode): IAstNode;
|
||||||
@@ -117,7 +117,8 @@ begin
|
|||||||
if newExprs = Node.Expressions then
|
if newExprs = Node.Expressions then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
Result := TAst.Block(newExprs, Node.StaticType);
|
// CoW: Reuse Identity
|
||||||
|
Result := TAst.Block(Node.Identity, newExprs, Node.StaticType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTCO.VisitIfExpression(const Node: IIfExpressionNode): IAstNode;
|
function TAstTCO.VisitIfExpression(const Node: IIfExpressionNode): IAstNode;
|
||||||
@@ -139,7 +140,8 @@ begin
|
|||||||
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then
|
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
Result := TAst.IfExpr(newCond, newThen, newElse, Node.StaticType);
|
// CoW: Reuse Identity
|
||||||
|
Result := TAst.IfExpr(Node.Identity, newCond, newThen, newElse, Node.StaticType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTCO.VisitTernaryExpression(const Node: ITernaryExpressionNode): IAstNode;
|
function TAstTCO.VisitTernaryExpression(const Node: ITernaryExpressionNode): IAstNode;
|
||||||
@@ -161,7 +163,8 @@ begin
|
|||||||
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then
|
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
Result := TAst.TernaryExpr(newCond, newThen, newElse, Node.StaticType);
|
// CoW: Reuse Identity
|
||||||
|
Result := TAst.TernaryExpr(Node.Identity, newCond, newThen, newElse, Node.StaticType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTCO.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
|
function TAstTCO.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
|
||||||
@@ -183,8 +186,10 @@ begin
|
|||||||
begin
|
begin
|
||||||
// Rebuild using factory.
|
// Rebuild using factory.
|
||||||
// IMPORTANT: Pass Layout AND Descriptor (TCO runs after TypeChecker).
|
// IMPORTANT: Pass Layout AND Descriptor (TCO runs after TypeChecker).
|
||||||
|
// CoW: Reuse Identity
|
||||||
Result :=
|
Result :=
|
||||||
TAst.LambdaExpr(
|
TAst.LambdaExpr(
|
||||||
|
Node.Identity,
|
||||||
newParams,
|
newParams,
|
||||||
newBody,
|
newBody,
|
||||||
Node.Layout,
|
Node.Layout,
|
||||||
@@ -211,7 +216,8 @@ begin
|
|||||||
if newArgs = Node.Arguments then
|
if newArgs = Node.Arguments then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
Result := TAst.Recur(newArgs, Node.StaticType);
|
// CoW: Reuse Identity
|
||||||
|
Result := TAst.Recur(Node.Identity, newArgs, Node.StaticType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTCO.VisitMacroExpansionNode(const Node: IMacroExpansionNode): IAstNode;
|
function TAstTCO.VisitMacroExpansionNode(const Node: IMacroExpansionNode): IAstNode;
|
||||||
@@ -225,7 +231,8 @@ begin
|
|||||||
if newBody = Node.ExpandedBody then
|
if newBody = Node.ExpandedBody then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
Result := TAst.MacroExpansionNode(Node.CallNode, newBody);
|
// CoW: Reuse Identity
|
||||||
|
Result := TAst.MacroExpansionNode(Node.Identity, Node.CallNode, newBody);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTCO.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
|
function TAstTCO.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
|
||||||
@@ -249,8 +256,10 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
// Use factory to create new node, passing the *new* TCO status
|
// Use factory to create new node, passing the *new* TCO status
|
||||||
|
// CoW: Reuse Identity
|
||||||
Result :=
|
Result :=
|
||||||
TAst.FunctionCall(
|
TAst.FunctionCall(
|
||||||
|
Node.Identity,
|
||||||
newCallee,
|
newCallee,
|
||||||
newArgs,
|
newArgs,
|
||||||
Node.StaticType,
|
Node.StaticType,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ uses
|
|||||||
Myc.Ast.Visitor,
|
Myc.Ast.Visitor,
|
||||||
Myc.Ast.Scope,
|
Myc.Ast.Scope,
|
||||||
Myc.Ast.Types,
|
Myc.Ast.Types,
|
||||||
|
Myc.Ast.Identities, // Wichtig für Casts (AsNamed etc.)
|
||||||
Myc.Ast;
|
Myc.Ast;
|
||||||
|
|
||||||
type
|
type
|
||||||
@@ -107,7 +108,6 @@ begin
|
|||||||
begin
|
begin
|
||||||
if not Assigned(ctx.FParent) then
|
if not Assigned(ctx.FParent) then
|
||||||
begin
|
begin
|
||||||
// Should technically not happen if Binder did its job, but safe guard it.
|
|
||||||
Result := TTypes.Unknown;
|
Result := TTypes.Unknown;
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
@@ -180,7 +180,7 @@ function TTypeChecker.Execute(const RootNode: IAstNode; const Layout: IScopeLayo
|
|||||||
begin
|
begin
|
||||||
Result := Accept(RootNode);
|
Result := Accept(RootNode);
|
||||||
if not Assigned(Result) then
|
if not Assigned(Result) then
|
||||||
Result := TAst.Block([]);
|
Result := TAst.Block([], nil);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitConstant(const Node: IConstantNode): IAstNode;
|
function TTypeChecker.VisitConstant(const Node: IConstantNode): IAstNode;
|
||||||
@@ -197,18 +197,21 @@ function TTypeChecker.VisitIdentifier(const Node: IIdentifierNode): IAstNode;
|
|||||||
var
|
var
|
||||||
typ: IStaticType;
|
typ: IStaticType;
|
||||||
adr: TResolvedAddress;
|
adr: TResolvedAddress;
|
||||||
|
identity: INamedIdentity;
|
||||||
begin
|
begin
|
||||||
adr := Node.Address;
|
adr := Node.Address;
|
||||||
|
identity := Node.Identity.AsNamed; // Extract specific identity
|
||||||
|
|
||||||
// If binder failed to resolve, we propagate Unknown type to prevent cascading errors.
|
|
||||||
if adr.Kind = akUnresolved then
|
if adr.Kind = akUnresolved then
|
||||||
begin
|
begin
|
||||||
Result := TAst.Identifier(Node.Name, adr, TTypes.Unknown);
|
// Propagate identity, set type to Unknown
|
||||||
|
Result := TAst.Identifier(identity, adr, TTypes.Unknown);
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
typ := FCurrentContext.LookupType(adr);
|
typ := FCurrentContext.LookupType(adr);
|
||||||
Result := TAst.Identifier(Node.Name, adr, typ);
|
// CoW: Reuse Identity
|
||||||
|
Result := TAst.Identifier(identity, adr, typ);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitRecurNode(const Node: IRecurNode): IAstNode;
|
function TTypeChecker.VisitRecurNode(const Node: IRecurNode): IAstNode;
|
||||||
@@ -220,7 +223,8 @@ begin
|
|||||||
for i := 0 to High(Node.Arguments) do
|
for i := 0 to High(Node.Arguments) do
|
||||||
newArgs[i] := Accept(Node.Arguments[i]);
|
newArgs[i] := Accept(Node.Arguments[i]);
|
||||||
|
|
||||||
Result := TAst.Recur(newArgs, TTypes.Void);
|
// CoW: Reuse Identity
|
||||||
|
Result := TAst.Recur(Node.Identity, newArgs, TTypes.Void);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
|
function TTypeChecker.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
|
||||||
@@ -231,11 +235,14 @@ var
|
|||||||
lambdaNode: ILambdaExpressionNode;
|
lambdaNode: ILambdaExpressionNode;
|
||||||
placeholderType: IStaticType;
|
placeholderType: IStaticType;
|
||||||
i: Integer;
|
i: Integer;
|
||||||
|
identNode: IIdentifierNode;
|
||||||
|
identIdentity: INamedIdentity;
|
||||||
begin
|
begin
|
||||||
adr := Node.Target.AsIdentifier.Address;
|
identNode := Node.Target.AsIdentifier;
|
||||||
|
identIdentity := identNode.Identity.AsNamed;
|
||||||
|
adr := identNode.Address;
|
||||||
initType := TTypes.Unknown;
|
initType := TTypes.Unknown;
|
||||||
|
|
||||||
// If address is unresolved (binder error), we just process the initializer and return.
|
|
||||||
if adr.Kind = akUnresolved then
|
if adr.Kind = akUnresolved then
|
||||||
begin
|
begin
|
||||||
if Assigned(Node.Initializer) then
|
if Assigned(Node.Initializer) then
|
||||||
@@ -271,8 +278,11 @@ begin
|
|||||||
if initType.Kind <> stUnknown then
|
if initType.Kind <> stUnknown then
|
||||||
FCurrentContext.SetType(adr.SlotIndex, initType);
|
FCurrentContext.SetType(adr.SlotIndex, initType);
|
||||||
|
|
||||||
newIdent := TAst.Identifier(Node.Target.AsIdentifier.Name, adr, initType);
|
// Reuse Identity for Identifier
|
||||||
Result := TAst.VarDecl(newIdent.AsIdentifier, newInitializer, initType, Node.IsBoxed);
|
newIdent := TAst.Identifier(identIdentity, adr, initType);
|
||||||
|
|
||||||
|
// Reuse Identity for VarDecl
|
||||||
|
Result := TAst.VarDecl(Node.Identity, newIdent, newInitializer, initType, Node.IsBoxed);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitAssignment(const Node: IAssignmentNode): IAstNode;
|
function TTypeChecker.VisitAssignment(const Node: IAssignmentNode): IAstNode;
|
||||||
@@ -283,12 +293,17 @@ var
|
|||||||
lambdaNode: ILambdaExpressionNode;
|
lambdaNode: ILambdaExpressionNode;
|
||||||
placeholderType: IStaticType;
|
placeholderType: IStaticType;
|
||||||
i: Integer;
|
i: Integer;
|
||||||
|
identNode: IIdentifierNode;
|
||||||
|
identIdentity: INamedIdentity;
|
||||||
begin
|
begin
|
||||||
newIdent := Accept(Node.Target);
|
// Rebuild Identifier with potential type info updates
|
||||||
targetType := newIdent.AsTypedNode.StaticType;
|
identNode := Node.Target.AsIdentifier;
|
||||||
adr := newIdent.AsIdentifier.Address;
|
identIdentity := identNode.Identity.AsNamed;
|
||||||
|
|
||||||
|
newIdent := Accept(Node.Target); // This visits identifier and might update type from context
|
||||||
|
targetType := newIdent.AsTypedNode.StaticType;
|
||||||
|
adr := identNode.Address;
|
||||||
|
|
||||||
// If binder failed, address is unresolved. We still check the value but skip assignment logic.
|
|
||||||
if adr.Kind = akUnresolved then
|
if adr.Kind = akUnresolved then
|
||||||
begin
|
begin
|
||||||
Accept(Node.Value);
|
Accept(Node.Value);
|
||||||
@@ -316,26 +331,25 @@ begin
|
|||||||
newValue := Accept(Node.Value);
|
newValue := Accept(Node.Value);
|
||||||
sourceType := newValue.AsTypedNode.StaticType;
|
sourceType := newValue.AsTypedNode.StaticType;
|
||||||
|
|
||||||
// Only check assignment if both types are known to avoid cascading error reports
|
|
||||||
if (targetType.Kind <> stUnknown) and (sourceType.Kind <> stUnknown) then
|
if (targetType.Kind <> stUnknown) and (sourceType.Kind <> stUnknown) then
|
||||||
begin
|
begin
|
||||||
if not TTypeRules.CanAssign(targetType, sourceType) then
|
if not TTypeRules.CanAssign(targetType, sourceType) then
|
||||||
begin
|
begin
|
||||||
if Assigned(FLog) then
|
if Assigned(FLog) then
|
||||||
FLog.AddError(Format('Cannot assign type %s to %s', [sourceType.ToString, targetType.ToString]), Node);
|
FLog.AddError(Format('Cannot assign type %s to %s', [sourceType.ToString, targetType.ToString]), Node);
|
||||||
// We continue, effectively ignoring the assignment type-wise (variable keeps old type).
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Type Inference for 'Unknown' targets
|
|
||||||
if ((targetType.Kind = stUnknown) or Assigned(placeholderType)) and (sourceType.Kind <> stUnknown) then
|
if ((targetType.Kind = stUnknown) or Assigned(placeholderType)) and (sourceType.Kind <> stUnknown) then
|
||||||
begin
|
begin
|
||||||
FCurrentContext.SetType(adr.SlotIndex, sourceType);
|
FCurrentContext.SetType(adr.SlotIndex, sourceType);
|
||||||
newIdent := TAst.Identifier(newIdent.AsIdentifier.Name, adr, sourceType);
|
// Recreate Identifier with new type and original identity
|
||||||
|
newIdent := TAst.Identifier(identIdentity, adr, sourceType);
|
||||||
targetType := sourceType;
|
targetType := sourceType;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TAst.Assign(newIdent.AsIdentifier, newValue, targetType);
|
// CoW: Reuse Assignment Identity
|
||||||
|
Result := TAst.Assign(Node.Identity, newIdent, newValue, targetType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
|
function TTypeChecker.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
|
||||||
@@ -348,6 +362,8 @@ var
|
|||||||
upvalueAddrs: TArray<TResolvedAddress>;
|
upvalueAddrs: TArray<TResolvedAddress>;
|
||||||
i: Integer;
|
i: Integer;
|
||||||
finalDescriptor: IScopeDescriptor;
|
finalDescriptor: IScopeDescriptor;
|
||||||
|
paramIdent: IIdentifierNode;
|
||||||
|
paramIdentity: INamedIdentity;
|
||||||
begin
|
begin
|
||||||
upvalueAddrs := Node.Upvalues;
|
upvalueAddrs := Node.Upvalues;
|
||||||
SetLength(upvalueTypes, Length(upvalueAddrs));
|
SetLength(upvalueTypes, Length(upvalueAddrs));
|
||||||
@@ -361,23 +377,24 @@ begin
|
|||||||
|
|
||||||
for i := 0 to High(Node.Parameters) do
|
for i := 0 to High(Node.Parameters) do
|
||||||
begin
|
begin
|
||||||
var paramIdent := Node.Parameters[i];
|
paramIdent := Node.Parameters[i];
|
||||||
|
paramIdentity := paramIdent.Identity.AsNamed;
|
||||||
var paramAdr := paramIdent.Address;
|
var paramAdr := paramIdent.Address;
|
||||||
|
|
||||||
paramTypes[i] := TTypes.Unknown;
|
paramTypes[i] := TTypes.Unknown;
|
||||||
|
|
||||||
if paramAdr.Kind <> akUnresolved then
|
if paramAdr.Kind <> akUnresolved then
|
||||||
FCurrentContext.SetType(paramAdr.SlotIndex, paramTypes[i]);
|
FCurrentContext.SetType(paramAdr.SlotIndex, paramTypes[i]);
|
||||||
|
|
||||||
newParams[i] := TAst.Identifier(paramIdent.Name, paramAdr, paramTypes[i]);
|
// CoW: Reuse Parameter Identity
|
||||||
|
newParams[i] := TAst.Identifier(paramIdentity, paramAdr, paramTypes[i]);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
newBody := Accept(Node.Body);
|
newBody := Accept(Node.Body);
|
||||||
bodyType := newBody.AsTypedNode.StaticType;
|
bodyType := newBody.AsTypedNode.StaticType;
|
||||||
methodType := TTypes.CreateMethod(paramTypes, bodyType);
|
methodType := TTypes.CreateMethod(paramTypes, bodyType);
|
||||||
|
|
||||||
// Set self-reference type (slot 0 is <self>)
|
|
||||||
FCurrentContext.SetType(0, methodType);
|
FCurrentContext.SetType(0, methodType);
|
||||||
|
|
||||||
finalDescriptor := TScope.CreateDescriptor(Node.Layout, FCurrentContext.Types);
|
finalDescriptor := TScope.CreateDescriptor(Node.Layout, FCurrentContext.Types);
|
||||||
finally
|
finally
|
||||||
var temp := FCurrentContext;
|
var temp := FCurrentContext;
|
||||||
@@ -385,8 +402,19 @@ begin
|
|||||||
temp.Free;
|
temp.Free;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// CoW: Reuse Lambda Identity
|
||||||
Result :=
|
Result :=
|
||||||
TAst.LambdaExpr(newParams, newBody, Node.Layout, finalDescriptor, Node.Upvalues, Node.HasNestedLambdas, Node.IsPure, methodType);
|
TAst.LambdaExpr(
|
||||||
|
Node.Identity,
|
||||||
|
newParams,
|
||||||
|
newBody,
|
||||||
|
Node.Layout,
|
||||||
|
finalDescriptor,
|
||||||
|
Node.Upvalues,
|
||||||
|
Node.HasNestedLambdas,
|
||||||
|
Node.IsPure,
|
||||||
|
methodType
|
||||||
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
|
function TTypeChecker.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
|
||||||
@@ -449,7 +477,6 @@ begin
|
|||||||
retType := bestSig.ReturnType
|
retType := bestSig.ReturnType
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
// Log error, but don't crash
|
|
||||||
if Assigned(FLog) then
|
if Assigned(FLog) then
|
||||||
begin
|
begin
|
||||||
argsStr := '';
|
argsStr := '';
|
||||||
@@ -460,15 +487,8 @@ begin
|
|||||||
Node
|
Node
|
||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
retType := TTypes.Unknown; // Poison result
|
retType := TTypes.Unknown;
|
||||||
end;
|
end;
|
||||||
end
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
// We have unknown arguments (probably due to upstream errors).
|
|
||||||
// We cannot resolve the signature, so the result is Unknown.
|
|
||||||
// We do NOT log an error here to avoid spam.
|
|
||||||
retType := TTypes.Unknown;
|
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
else if calleeType.Kind <> TStaticTypeKind.stUnknown then
|
else if calleeType.Kind <> TStaticTypeKind.stUnknown then
|
||||||
@@ -478,7 +498,17 @@ begin
|
|||||||
retType := TTypes.Unknown;
|
retType := TTypes.Unknown;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TAst.FunctionCall(newCallee, newArgs, retType, Node.IsTailCall);
|
// CoW: Reuse Call Identity
|
||||||
|
Result :=
|
||||||
|
TAst.FunctionCall(
|
||||||
|
Node.Identity,
|
||||||
|
newCallee,
|
||||||
|
newArgs,
|
||||||
|
retType,
|
||||||
|
Node.IsTailCall,
|
||||||
|
nil, // StaticTarget set by Specializer
|
||||||
|
False // IsTargetPure set by Specializer
|
||||||
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
|
function TTypeChecker.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
|
||||||
@@ -496,7 +526,8 @@ begin
|
|||||||
else
|
else
|
||||||
blockType := TTypes.Void;
|
blockType := TTypes.Void;
|
||||||
|
|
||||||
Result := TAst.Block(newExprs, blockType);
|
// CoW: Reuse Block Identity
|
||||||
|
Result := TAst.Block(Node.Identity, newExprs, blockType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitIfExpression(const Node: IIfExpressionNode): IAstNode;
|
function TTypeChecker.VisitIfExpression(const Node: IIfExpressionNode): IAstNode;
|
||||||
@@ -509,7 +540,6 @@ begin
|
|||||||
newElse := Accept(Node.ElseBranch);
|
newElse := Accept(Node.ElseBranch);
|
||||||
|
|
||||||
conditionType := newCond.AsTypedNode.StaticType;
|
conditionType := newCond.AsTypedNode.StaticType;
|
||||||
|
|
||||||
if (conditionType.Kind <> stUnknown)
|
if (conditionType.Kind <> stUnknown)
|
||||||
and not TTypeRules.CanAssign(TTypes.Ordinal, conditionType)
|
and not TTypeRules.CanAssign(TTypes.Ordinal, conditionType)
|
||||||
and not TTypeRules.CanAssign(TTypes.Boolean, conditionType) then
|
and not TTypeRules.CanAssign(TTypes.Boolean, conditionType) then
|
||||||
@@ -531,7 +561,8 @@ begin
|
|||||||
resultType := TTypes.Unknown;
|
resultType := TTypes.Unknown;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TAst.IfExpr(newCond, newThen, newElse, resultType);
|
// CoW: Reuse If Identity
|
||||||
|
Result := TAst.IfExpr(Node.Identity, newCond, newThen, newElse, resultType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitTernaryExpression(const Node: ITernaryExpressionNode): IAstNode;
|
function TTypeChecker.VisitTernaryExpression(const Node: ITernaryExpressionNode): IAstNode;
|
||||||
@@ -563,7 +594,8 @@ begin
|
|||||||
resultType := TTypes.Unknown;
|
resultType := TTypes.Unknown;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TAst.TernaryExpr(newCond, newThen, newElse, resultType);
|
// CoW: Reuse Ternary Identity
|
||||||
|
Result := TAst.TernaryExpr(Node.Identity, newCond, newThen, newElse, resultType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode;
|
function TTypeChecker.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode;
|
||||||
@@ -616,7 +648,8 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TAst.MemberAccess(newBase, newMember.AsKeyword, elemType);
|
// CoW: Reuse MemberAccess Identity
|
||||||
|
Result := TAst.MemberAccess(Node.Identity, newBase, newMember.AsKeyword, elemType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitIndexer(const Node: IIndexerNode): IAstNode;
|
function TTypeChecker.VisitIndexer(const Node: IIndexerNode): IAstNode;
|
||||||
@@ -653,7 +686,8 @@ begin
|
|||||||
FLog.AddError(Format('Indexer `[]` requires an Ordinal index, but got %s', [indexType.ToString]), Node);
|
FLog.AddError(Format('Indexer `[]` requires an Ordinal index, but got %s', [indexType.ToString]), Node);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TAst.Indexer(newBase, newIndex, elemType);
|
// CoW: Reuse Indexer Identity
|
||||||
|
Result := TAst.Indexer(Node.Identity, newBase, newIndex, elemType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode;
|
function TTypeChecker.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode;
|
||||||
@@ -705,7 +739,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
def := TScalarRecordRegistry.Intern(scalarDefFields);
|
def := TScalarRecordRegistry.Intern(scalarDefFields);
|
||||||
staticType := TTypes.CreateRecord(def);
|
staticType := TTypes.CreateRecord(def);
|
||||||
Result := TAst.RecordLiteral(newFields, def, nil, staticType);
|
Result := TAst.RecordLiteral(Node.Identity, newFields, def, nil, staticType);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
@@ -716,16 +750,19 @@ begin
|
|||||||
|
|
||||||
var genDef := TGenericRecordRegistry.Intern(genDefFields);
|
var genDef := TGenericRecordRegistry.Intern(genDefFields);
|
||||||
staticType := TTypes.CreateGenericRecord(genDef);
|
staticType := TTypes.CreateGenericRecord(genDef);
|
||||||
Result := TAst.RecordLiteral(newFields, nil, genDef, staticType);
|
Result := TAst.RecordLiteral(Node.Identity, newFields, nil, genDef, staticType);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitCreateSeries(const Node: ICreateSeriesNode): IAstNode;
|
function TTypeChecker.VisitCreateSeries(const Node: ICreateSeriesNode): IAstNode;
|
||||||
var
|
var
|
||||||
elemType: IStaticType;
|
elemType: IStaticType;
|
||||||
|
defIdentity: IDefinitionIdentity;
|
||||||
begin
|
begin
|
||||||
try
|
try
|
||||||
elemType := TTypes.FromScalarKind(TScalar.StringToKind(Node.Definition));
|
// Use definition from Identity to ensure SSOT
|
||||||
|
defIdentity := Node.Identity.AsDefinition;
|
||||||
|
elemType := TTypes.FromScalarKind(TScalar.StringToKind(defIdentity.Definition));
|
||||||
except
|
except
|
||||||
on E: Exception do
|
on E: Exception do
|
||||||
begin
|
begin
|
||||||
@@ -735,7 +772,8 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TAst.CreateSeries(Node.Definition, TTypes.CreateSeries(elemType));
|
// CoW: Reuse Definition Identity
|
||||||
|
Result := TAst.CreateSeries(defIdentity, TTypes.CreateSeries(elemType));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode;
|
function TTypeChecker.VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode;
|
||||||
@@ -759,7 +797,6 @@ begin
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
// Only check assignment compatibility if value type is known
|
|
||||||
if (valueType.Kind <> stUnknown) and not TTypeRules.CanAssign(seriesType.ElementType, valueType) then
|
if (valueType.Kind <> stUnknown) and not TTypeRules.CanAssign(seriesType.ElementType, valueType) then
|
||||||
begin
|
begin
|
||||||
if Assigned(FLog) then
|
if Assigned(FLog) then
|
||||||
@@ -781,12 +818,13 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TAst.AddSeriesItem(newSeries.AsIdentifier, newValue, newLookback, TTypes.Void);
|
// CoW: Reuse Add Identity
|
||||||
|
Result := TAst.AddSeriesItem(Node.Identity, newSeries.AsIdentifier, newValue, newLookback, TTypes.Void);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitNop(const Node: INopNode): IAstNode;
|
function TTypeChecker.VisitNop(const Node: INopNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
Result := TAst.Nop(TTypes.Void);
|
Result := TAst.Nop(Node.Identity, TTypes.Void);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTypeChecker.VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode;
|
function TTypeChecker.VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode;
|
||||||
@@ -805,7 +843,8 @@ begin
|
|||||||
FLog.AddError(Format('"length" requires a series, but got %s', [seriesType.ToString]), Node);
|
FLog.AddError(Format('"length" requires a series, but got %s', [seriesType.ToString]), Node);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TAst.SeriesLength(newSeries.AsIdentifier, TTypes.Ordinal);
|
// CoW: Reuse SeriesLength Identity
|
||||||
|
Result := TAst.SeriesLength(Node.Identity, newSeries.AsIdentifier, TTypes.Ordinal);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
+1412
-80
File diff suppressed because it is too large
Load Diff
+55
-75
@@ -8,7 +8,8 @@ uses
|
|||||||
Myc.Data.Value,
|
Myc.Data.Value,
|
||||||
Myc.Ast,
|
Myc.Ast,
|
||||||
Myc.Ast.Types,
|
Myc.Ast.Types,
|
||||||
Myc.Ast.Nodes;
|
Myc.Ast.Nodes,
|
||||||
|
Myc.Ast.Identities; // Needed for identity casting
|
||||||
|
|
||||||
type
|
type
|
||||||
TAstVisitor<T> = class abstract(TInterfacedObject, IAstVisitor)
|
TAstVisitor<T> = class abstract(TInterfacedObject, IAstVisitor)
|
||||||
@@ -36,7 +37,7 @@ type
|
|||||||
function IAstVisitor.VisitAddSeriesItem = DoVisitAddSeriesItem;
|
function IAstVisitor.VisitAddSeriesItem = DoVisitAddSeriesItem;
|
||||||
function IAstVisitor.VisitSeriesLength = DoVisitSeriesLength;
|
function IAstVisitor.VisitSeriesLength = DoVisitSeriesLength;
|
||||||
function IAstVisitor.VisitRecurNode = DoVisitRecurNode;
|
function IAstVisitor.VisitRecurNode = DoVisitRecurNode;
|
||||||
function IAstVisitor.VisitNop = DoVisitNop; // Added Nop
|
function IAstVisitor.VisitNop = DoVisitNop;
|
||||||
|
|
||||||
// Private bridge method implementations
|
// Private bridge method implementations
|
||||||
function DoVisitConstant(const Node: IConstantNode): TDataValue;
|
function DoVisitConstant(const Node: IConstantNode): TDataValue;
|
||||||
@@ -61,7 +62,7 @@ type
|
|||||||
function DoVisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
|
function DoVisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
|
||||||
function DoVisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
|
function DoVisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
|
||||||
function DoVisitRecurNode(const Node: IRecurNode): TDataValue;
|
function DoVisitRecurNode(const Node: IRecurNode): TDataValue;
|
||||||
function DoVisitNop(const Node: INopNode): TDataValue; // Added Nop
|
function DoVisitNop(const Node: INopNode): TDataValue;
|
||||||
|
|
||||||
protected
|
protected
|
||||||
// Visit a node.
|
// Visit a node.
|
||||||
@@ -89,7 +90,7 @@ type
|
|||||||
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): T; virtual; abstract;
|
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): T; virtual; abstract;
|
||||||
function VisitSeriesLength(const Node: ISeriesLengthNode): T; virtual; abstract;
|
function VisitSeriesLength(const Node: ISeriesLengthNode): T; virtual; abstract;
|
||||||
function VisitRecurNode(const Node: IRecurNode): T; virtual; abstract;
|
function VisitRecurNode(const Node: IRecurNode): T; virtual; abstract;
|
||||||
function VisitNop(const Node: INopNode): T; virtual; abstract; // Added Nop
|
function VisitNop(const Node: INopNode): T; virtual; abstract;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TAstTransformer = class abstract(TAstVisitor<IAstNode>)
|
TAstTransformer = class abstract(TAstVisitor<IAstNode>)
|
||||||
@@ -119,7 +120,7 @@ type
|
|||||||
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode; override;
|
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode; override;
|
||||||
function VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode; override;
|
function VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode; override;
|
||||||
function VisitRecurNode(const Node: IRecurNode): IAstNode; override;
|
function VisitRecurNode(const Node: IRecurNode): IAstNode; override;
|
||||||
function VisitNop(const Node: INopNode): IAstNode; override; // Added Nop
|
function VisitNop(const Node: INopNode): IAstNode; override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TAstVisitor = class abstract(TInterfacedObject, IAstVisitor)
|
TAstVisitor = class abstract(TInterfacedObject, IAstVisitor)
|
||||||
@@ -147,7 +148,7 @@ type
|
|||||||
function IAstVisitor.VisitAddSeriesItem = DoVisitAddSeriesItem;
|
function IAstVisitor.VisitAddSeriesItem = DoVisitAddSeriesItem;
|
||||||
function IAstVisitor.VisitSeriesLength = DoVisitSeriesLength;
|
function IAstVisitor.VisitSeriesLength = DoVisitSeriesLength;
|
||||||
function IAstVisitor.VisitRecurNode = DoVisitRecurNode;
|
function IAstVisitor.VisitRecurNode = DoVisitRecurNode;
|
||||||
function IAstVisitor.VisitNop = DoVisitNop; // Added Nop
|
function IAstVisitor.VisitNop = DoVisitNop;
|
||||||
|
|
||||||
// Private bridge method implementations
|
// Private bridge method implementations
|
||||||
function DoVisitConstant(const Node: IConstantNode): TDataValue;
|
function DoVisitConstant(const Node: IConstantNode): TDataValue;
|
||||||
@@ -172,7 +173,7 @@ type
|
|||||||
function DoVisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
|
function DoVisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
|
||||||
function DoVisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
|
function DoVisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
|
||||||
function DoVisitRecurNode(const Node: IRecurNode): TDataValue;
|
function DoVisitRecurNode(const Node: IRecurNode): TDataValue;
|
||||||
function DoVisitNop(const Node: INopNode): TDataValue; // Added Nop
|
function DoVisitNop(const Node: INopNode): TDataValue;
|
||||||
|
|
||||||
protected
|
protected
|
||||||
// Virtual procedures for descendants (Interpreters/Side-effects) to override
|
// Virtual procedures for descendants (Interpreters/Side-effects) to override
|
||||||
@@ -198,7 +199,7 @@ type
|
|||||||
procedure VisitAddSeriesItem(const Node: IAddSeriesItemNode); virtual; abstract;
|
procedure VisitAddSeriesItem(const Node: IAddSeriesItemNode); virtual; abstract;
|
||||||
procedure VisitSeriesLength(const Node: ISeriesLengthNode); virtual; abstract;
|
procedure VisitSeriesLength(const Node: ISeriesLengthNode); virtual; abstract;
|
||||||
procedure VisitRecurNode(const Node: IRecurNode); virtual; abstract;
|
procedure VisitRecurNode(const Node: IRecurNode); virtual; abstract;
|
||||||
procedure VisitNop(const Node: INopNode); virtual; abstract; // Added Nop
|
procedure VisitNop(const Node: INopNode); virtual; abstract;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
@@ -327,7 +328,6 @@ end;
|
|||||||
|
|
||||||
function TAstVisitor<T>.DoVisitNop(const Node: INopNode): TDataValue;
|
function TAstVisitor<T>.DoVisitNop(const Node: INopNode): TDataValue;
|
||||||
begin
|
begin
|
||||||
// Added Nop implementation
|
|
||||||
Result := TDataValue.FromGeneric<T>(VisitNop(Node));
|
Result := TDataValue.FromGeneric<T>(VisitNop(Node));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -337,7 +337,6 @@ var
|
|||||||
hasChanged: Boolean;
|
hasChanged: Boolean;
|
||||||
newNode: IIdentifierNode;
|
newNode: IIdentifierNode;
|
||||||
begin
|
begin
|
||||||
// Implement Copy-on-Write for parameter arrays
|
|
||||||
hasChanged := False;
|
hasChanged := False;
|
||||||
SetLength(Result, Length(Nodes));
|
SetLength(Result, Length(Nodes));
|
||||||
|
|
||||||
@@ -350,7 +349,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
if not hasChanged then
|
if not hasChanged then
|
||||||
Result := Nodes; // Return original array if no changes
|
Result := Nodes;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.AcceptNodes(
|
function TAstTransformer.AcceptNodes(
|
||||||
@@ -359,12 +358,10 @@ function TAstTransformer.AcceptNodes(
|
|||||||
): TArray<IAstNode>;
|
): TArray<IAstNode>;
|
||||||
var
|
var
|
||||||
i: Integer;
|
i: Integer;
|
||||||
newNode: IAstNode; // Changed from TDataValue
|
newNode: IAstNode;
|
||||||
newList: TList<IAstNode>; // Used if nodes are removed (e.g. defmacro)
|
newList: TList<IAstNode>;
|
||||||
hasChanged: Boolean;
|
hasChanged: Boolean;
|
||||||
begin
|
begin
|
||||||
// This implementation already supports CoW and node removal (nil)
|
|
||||||
// We just need to track if the array reference itself needs to change.
|
|
||||||
hasChanged := False;
|
hasChanged := False;
|
||||||
newList := nil;
|
newList := nil;
|
||||||
|
|
||||||
@@ -373,25 +370,24 @@ begin
|
|||||||
if Assigned(AcceptProc) then
|
if Assigned(AcceptProc) then
|
||||||
newNode := AcceptProc(i, Nodes[i])
|
newNode := AcceptProc(i, Nodes[i])
|
||||||
else
|
else
|
||||||
newNode := Accept(Nodes[i]); // Calls new Accept, returns IAstNode (or nil)
|
newNode := Accept(Nodes[i]);
|
||||||
|
|
||||||
if not Assigned(newNode) then // Node was removed
|
if not Assigned(newNode) then
|
||||||
begin
|
begin
|
||||||
hasChanged := True;
|
hasChanged := True;
|
||||||
if newList = nil then // First change
|
if newList = nil then
|
||||||
begin
|
begin
|
||||||
newList := TList<IAstNode>.Create;
|
newList := TList<IAstNode>.Create;
|
||||||
for var j := 0 to i - 1 do
|
for var j := 0 to i - 1 do
|
||||||
newList.Add(Nodes[j]);
|
newList.Add(Nodes[j]);
|
||||||
end;
|
end;
|
||||||
// else: just skip adding it
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
if newNode <> Nodes[i] then // Node was replaced
|
if newNode <> Nodes[i] then
|
||||||
begin
|
begin
|
||||||
hasChanged := True;
|
hasChanged := True;
|
||||||
if newList = nil then // First change
|
if newList = nil then
|
||||||
begin
|
begin
|
||||||
newList := TList<IAstNode>.Create;
|
newList := TList<IAstNode>.Create;
|
||||||
for var j := 0 to i - 1 do
|
for var j := 0 to i - 1 do
|
||||||
@@ -401,45 +397,43 @@ begin
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
if newList <> nil then // No change, but we are already copying
|
if newList <> nil then
|
||||||
newList.Add(Nodes[i]);
|
newList.Add(Nodes[i]);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
if not hasChanged then
|
if not hasChanged then
|
||||||
Result := Nodes // Return original array
|
Result := Nodes
|
||||||
else if newList <> nil then
|
else if newList <> nil then
|
||||||
begin
|
begin
|
||||||
Result := newList.ToArray;
|
Result := newList.ToArray;
|
||||||
newList.Free;
|
newList.Free;
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
Result := []; // All nodes were removed
|
Result := [];
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// --- Base Virtual Implementations (IAstNode-based) ---
|
// --- Base Virtual Implementations ---
|
||||||
// --- Now fully implementing Copy-on-Write (CoW) ---
|
|
||||||
|
|
||||||
function TAstTransformer.VisitConstant(const Node: IConstantNode): IAstNode;
|
function TAstTransformer.VisitConstant(const Node: IConstantNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
Result := Node; // Leaf node, immutable
|
Result := Node;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitIdentifier(const Node: IIdentifierNode): IAstNode;
|
function TAstTransformer.VisitIdentifier(const Node: IIdentifierNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
Result := Node; // Leaf node, immutable (will be replaced by Binder)
|
Result := Node;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitKeyword(const Node: IKeywordNode): IAstNode;
|
function TAstTransformer.VisitKeyword(const Node: IKeywordNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
Result := Node; // Leaf node, immutable
|
Result := Node;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitNop(const Node: INopNode): IAstNode;
|
function TAstTransformer.VisitNop(const Node: INopNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
// Added Nop implementation
|
Result := Node;
|
||||||
Result := Node; // Leaf node, immutable
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitIfExpression(const Node: IIfExpressionNode): IAstNode;
|
function TAstTransformer.VisitIfExpression(const Node: IIfExpressionNode): IAstNode;
|
||||||
@@ -448,13 +442,12 @@ var
|
|||||||
begin
|
begin
|
||||||
newCond := Accept(Node.Condition);
|
newCond := Accept(Node.Condition);
|
||||||
newThen := Accept(Node.ThenBranch);
|
newThen := Accept(Node.ThenBranch);
|
||||||
newElse := Accept(Node.ElseBranch); // Accept handles nil
|
newElse := Accept(Node.ElseBranch);
|
||||||
|
|
||||||
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then
|
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
// Use TAst factory
|
Result := TAst.IfExpr(Node.Identity, newCond, newThen, newElse, Node.StaticType);
|
||||||
Result := TAst.IfExpr(newCond, newThen, newElse, Node.StaticType);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitTernaryExpression(const Node: ITernaryExpressionNode): IAstNode;
|
function TAstTransformer.VisitTernaryExpression(const Node: ITernaryExpressionNode): IAstNode;
|
||||||
@@ -468,8 +461,7 @@ begin
|
|||||||
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then
|
if (newCond = Node.Condition) and (newThen = Node.ThenBranch) and (newElse = Node.ElseBranch) then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
// Use TAst factory
|
Result := TAst.TernaryExpr(Node.Identity, newCond, newThen, newElse, Node.StaticType);
|
||||||
Result := TAst.TernaryExpr(newCond, newThen, newElse, Node.StaticType);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
|
function TAstTransformer.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
|
||||||
@@ -477,7 +469,6 @@ var
|
|||||||
newParams: TArray<IIdentifierNode>;
|
newParams: TArray<IIdentifierNode>;
|
||||||
newBody: IAstNode;
|
newBody: IAstNode;
|
||||||
begin
|
begin
|
||||||
// No longer cast to concrete class, use interface
|
|
||||||
newParams := AcceptParameters(Node.Parameters);
|
newParams := AcceptParameters(Node.Parameters);
|
||||||
newBody := Accept(Node.Body);
|
newBody := Accept(Node.Body);
|
||||||
|
|
||||||
@@ -485,9 +476,9 @@ begin
|
|||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
// Use TAst factory and copy properties via interface getters
|
|
||||||
Result :=
|
Result :=
|
||||||
TAst.LambdaExpr(
|
TAst.LambdaExpr(
|
||||||
|
Node.Identity,
|
||||||
newParams,
|
newParams,
|
||||||
newBody,
|
newBody,
|
||||||
Node.Layout,
|
Node.Layout,
|
||||||
@@ -512,8 +503,8 @@ begin
|
|||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
// Use TAst factory and copy properties via interface getters
|
Result :=
|
||||||
Result := TAst.FunctionCall(newCallee, newArgs, Node.StaticType, Node.IsTailCall, Node.StaticTarget);
|
TAst.FunctionCall(Node.Identity, newCallee, newArgs, Node.StaticType, Node.IsTailCall, Node.StaticTarget, Node.IsTargetPure);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -521,13 +512,12 @@ function TAstTransformer.VisitMacroExpansionNode(const Node: IMacroExpansionNode
|
|||||||
var
|
var
|
||||||
newBody: IAstNode;
|
newBody: IAstNode;
|
||||||
begin
|
begin
|
||||||
// Visit the body and check if it changed
|
|
||||||
newBody := Accept(Node.ExpandedBody);
|
newBody := Accept(Node.ExpandedBody);
|
||||||
if newBody = Node.ExpandedBody then
|
if newBody = Node.ExpandedBody then
|
||||||
exit(Node);
|
exit(Node);
|
||||||
|
|
||||||
// The body changed. Create a NEW wrapper node.
|
// Macro Expansion Nodes are structural, reuse identity
|
||||||
Result := TAst.MacroExpansionNode(Node.CallNode, newBody);
|
Result := TAst.MacroExpansionNode(Node.Identity, Node.CallNode, newBody);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
|
function TAstTransformer.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
|
||||||
@@ -539,8 +529,7 @@ begin
|
|||||||
if newExprs = Node.Expressions then
|
if newExprs = Node.Expressions then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
// Use TAst factory
|
Result := TAst.Block(Node.Identity, newExprs, Node.StaticType);
|
||||||
Result := TAst.Block(newExprs, Node.StaticType);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
|
function TAstTransformer.VisitVariableDeclaration(const Node: IVariableDeclarationNode): IAstNode;
|
||||||
@@ -555,7 +544,7 @@ begin
|
|||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
Result := TAst.VarDecl(newTarget, newInit, Node.StaticType, Node.IsBoxed);
|
Result := TAst.VarDecl(Node.Identity, newTarget, newInit, Node.StaticType, Node.IsBoxed);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -570,17 +559,13 @@ begin
|
|||||||
if (newTarget = Node.Target) and (newValue = Node.Value) then
|
if (newTarget = Node.Target) and (newValue = Node.Value) then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
Result := TAst.Assign(newTarget, newValue, Node.StaticType);
|
Result := TAst.Assign(Node.Identity, newTarget, newValue, Node.StaticType);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode;
|
function TAstTransformer.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
// A macro definition is a compile-time construct.
|
// Macro definitions are usually stripped/ignored in transformers,
|
||||||
// Transformers (Binder, TypeChecker, Lowerer) should not traverse its
|
// but we return them as-is to support partial pipelines.
|
||||||
// children (Name, Parameters, Body) as they are not part of the
|
|
||||||
// standard execution AST.
|
|
||||||
// Serializers (TAstDumper/TJsonAstConverter), which need to traverse,
|
|
||||||
// provide their own override.
|
|
||||||
Result := Node;
|
Result := Node;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -588,37 +573,38 @@ function TAstTransformer.VisitQuasiquote(const Node: IQuasiquoteNode): IAstNode;
|
|||||||
var
|
var
|
||||||
newExpr: IAstNode;
|
newExpr: IAstNode;
|
||||||
begin
|
begin
|
||||||
// Rebuild instead of mutate (Copy-on-Write)
|
|
||||||
newExpr := Accept(Node.Expression);
|
newExpr := Accept(Node.Expression);
|
||||||
if newExpr = Node.Expression then
|
if newExpr = Node.Expression then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
Result := TAst.Quasiquote(newExpr);
|
Result := TAst.Quasiquote(Node.Identity, newExpr);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitUnquote(const Node: IUnquoteNode): IAstNode;
|
function TAstTransformer.VisitUnquote(const Node: IUnquoteNode): IAstNode;
|
||||||
var
|
var
|
||||||
newExpr: IAstNode;
|
newExpr: IAstNode;
|
||||||
begin
|
begin
|
||||||
// Rebuild instead of mutate (Copy-on-Write)
|
|
||||||
newExpr := Accept(Node.Expression);
|
newExpr := Accept(Node.Expression);
|
||||||
if newExpr = Node.Expression then
|
if newExpr = Node.Expression then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
Result := TAst.Unquote(newExpr);
|
Result := TAst.Unquote(Node.Identity, newExpr);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode;
|
function TAstTransformer.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode;
|
||||||
var
|
var
|
||||||
newExpr: IAstNode;
|
newExpr: IAstNode;
|
||||||
begin
|
begin
|
||||||
// Rebuild instead of mutate (Copy-on-Write)
|
// Note: Accept expects IAstNode, IQuasiquoteNode inherits from it.
|
||||||
newExpr := Accept(Node.Expression);
|
newExpr := Accept(Node.Expression);
|
||||||
|
|
||||||
if (newExpr = Node.Expression) then
|
if (newExpr = Node.Expression) then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
Result := TAst.UnquoteSplicing(newExpr.AsQuasiquote);
|
// We need to ensure the transformed expression is still a Quasiquote,
|
||||||
|
// or we have to assume the transformer knows what it's doing.
|
||||||
|
// For safety, we cast back. If transformation changed type, this will fail fast.
|
||||||
|
Result := TAst.UnquoteSplicing(Node.Identity, newExpr.AsQuasiquote);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitIndexer(const Node: IIndexerNode): IAstNode;
|
function TAstTransformer.VisitIndexer(const Node: IIndexerNode): IAstNode;
|
||||||
@@ -631,8 +617,7 @@ begin
|
|||||||
if (newBase = Node.Base) and (newIndex = Node.Index) then
|
if (newBase = Node.Base) and (newIndex = Node.Index) then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
// Use TAst factory
|
Result := TAst.Indexer(Node.Identity, newBase, newIndex, Node.StaticType);
|
||||||
Result := TAst.Indexer(newBase, newIndex, Node.StaticType);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode;
|
function TAstTransformer.VisitMemberAccess(const Node: IMemberAccessNode): IAstNode;
|
||||||
@@ -641,13 +626,12 @@ var
|
|||||||
newMember: IKeywordNode;
|
newMember: IKeywordNode;
|
||||||
begin
|
begin
|
||||||
newBase := Accept(Node.Base);
|
newBase := Accept(Node.Base);
|
||||||
newMember := Accept(Node.Member).AsKeyword; // Keyword ist Blattknoten
|
newMember := Accept(Node.Member).AsKeyword;
|
||||||
|
|
||||||
if (newBase = Node.Base) and (newMember = Node.Member) then
|
if (newBase = Node.Base) and (newMember = Node.Member) then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
// Use TAst factory
|
Result := TAst.MemberAccess(Node.Identity, newBase, newMember, Node.StaticType);
|
||||||
Result := TAst.MemberAccess(newBase, newMember, Node.StaticType);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode;
|
function TAstTransformer.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode;
|
||||||
@@ -661,6 +645,7 @@ begin
|
|||||||
|
|
||||||
for i := 0 to High(Node.Fields) do
|
for i := 0 to High(Node.Fields) do
|
||||||
begin
|
begin
|
||||||
|
// Keys are typically static, but we visit them anyway
|
||||||
newFields[i].Key := Accept(Node.Fields[i].Key).AsKeyword;
|
newFields[i].Key := Accept(Node.Fields[i].Key).AsKeyword;
|
||||||
newFields[i].Value := Accept(Node.Fields[i].Value);
|
newFields[i].Value := Accept(Node.Fields[i].Value);
|
||||||
if (newFields[i].Key <> Node.Fields[i].Key) or (newFields[i].Value <> Node.Fields[i].Value) then
|
if (newFields[i].Key <> Node.Fields[i].Key) or (newFields[i].Value <> Node.Fields[i].Value) then
|
||||||
@@ -671,15 +656,13 @@ begin
|
|||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
// Rebuild the node, preserving its specific type and definitions
|
Result := TAst.RecordLiteral(Node.Identity, newFields, Node.ScalarDefinition, Node.GenericDefinition, Node.StaticType);
|
||||||
// Use TAst factory
|
|
||||||
Result := TAst.RecordLiteral(newFields, Node.ScalarDefinition, Node.GenericDefinition, Node.StaticType);
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitCreateSeries(const Node: ICreateSeriesNode): IAstNode;
|
function TAstTransformer.VisitCreateSeries(const Node: ICreateSeriesNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
Result := Node; // Leaf node, immutable
|
Result := Node;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode;
|
function TAstTransformer.VisitAddSeriesItem(const Node: IAddSeriesItemNode): IAstNode;
|
||||||
@@ -689,13 +672,12 @@ var
|
|||||||
begin
|
begin
|
||||||
newSeries := Accept(Node.Series).AsIdentifier;
|
newSeries := Accept(Node.Series).AsIdentifier;
|
||||||
newValue := Accept(Node.Value);
|
newValue := Accept(Node.Value);
|
||||||
newLookback := Accept(Node.Lookback); // Accept handles nil
|
newLookback := Accept(Node.Lookback);
|
||||||
|
|
||||||
if (newSeries = Node.Series) and (newValue = Node.Value) and (newLookback = Node.Lookback) then
|
if (newSeries = Node.Series) and (newValue = Node.Value) and (newLookback = Node.Lookback) then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
// Use TAst factory
|
Result := TAst.AddSeriesItem(Node.Identity, newSeries, newValue, newLookback, Node.StaticType);
|
||||||
Result := TAst.AddSeriesItem(newSeries, newValue, newLookback, Node.StaticType);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode;
|
function TAstTransformer.VisitSeriesLength(const Node: ISeriesLengthNode): IAstNode;
|
||||||
@@ -707,8 +689,7 @@ begin
|
|||||||
if newSeries = Node.Series then
|
if newSeries = Node.Series then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
// Use TAst factory
|
Result := TAst.SeriesLength(Node.Identity, newSeries, Node.StaticType);
|
||||||
Result := TAst.SeriesLength(newSeries, Node.StaticType);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TAstTransformer.VisitRecurNode(const Node: IRecurNode): IAstNode;
|
function TAstTransformer.VisitRecurNode(const Node: IRecurNode): IAstNode;
|
||||||
@@ -720,8 +701,7 @@ begin
|
|||||||
if newArgs = Node.Arguments then
|
if newArgs = Node.Arguments then
|
||||||
Result := Node
|
Result := Node
|
||||||
else
|
else
|
||||||
// Use TAst factory
|
Result := TAst.Recur(Node.Identity, newArgs, Node.StaticType);
|
||||||
Result := TAst.Recur(newArgs, Node.StaticType);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TAstVisitor }
|
{ TAstVisitor }
|
||||||
|
|||||||
+596
-1817
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,8 @@ type
|
|||||||
private
|
private
|
||||||
FRootLayout: IScopeLayout;
|
FRootLayout: IScopeLayout;
|
||||||
|
|
||||||
function Bind(const Node: IAstNode; out Layout: IScopeLayout): IAstNode;
|
// Updated Bind helper: Returns log to allow assertions on errors
|
||||||
|
function Bind(const Node: IAstNode; out Layout: IScopeLayout; out Log: ICompilerLog): IAstNode;
|
||||||
function Unwrap(const Node: IAstNode): IAstNode;
|
function Unwrap(const Node: IAstNode): IAstNode;
|
||||||
public
|
public
|
||||||
[Setup]
|
[Setup]
|
||||||
@@ -72,14 +73,14 @@ type
|
|||||||
[TestCase('Invalid_StartDigit', '1var')]
|
[TestCase('Invalid_StartDigit', '1var')]
|
||||||
[TestCase('Invalid_Symbol', 'var$name')]
|
[TestCase('Invalid_Symbol', 'var$name')]
|
||||||
[TestCase('Invalid_DotStart', '.var')]
|
[TestCase('Invalid_DotStart', '.var')]
|
||||||
procedure Test_IdentifierValidation_InvalidNames_RaisesException(const Name: string);
|
procedure Test_IdentifierValidation_InvalidNames_LogsError(const Name: string);
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
procedure Test_UnresolvedIdentifier_RaisesException;
|
procedure Test_UnresolvedIdentifier_LogsError;
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[IgnoreMemoryLeaks]
|
[IgnoreMemoryLeaks]
|
||||||
procedure Test_RedefinitionInSameScope_RaisesException;
|
procedure Test_RedefinitionInSameScope_LogsError;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
@@ -94,9 +95,11 @@ begin
|
|||||||
FRootLayout := TScope.CreateRootLayout;
|
FRootLayout := TScope.CreateRootLayout;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTestAstBinder.Bind(const Node: IAstNode; out Layout: IScopeLayout): IAstNode;
|
function TTestAstBinder.Bind(const Node: IAstNode; out Layout: IScopeLayout; out Log: ICompilerLog): IAstNode;
|
||||||
begin
|
begin
|
||||||
Result := TAstBinder.Bind(FRootLayout, Node, Layout);
|
Log := TCompilerLog.Create;
|
||||||
|
// TAstBinder.Bind now takes Log instead of raising exceptions
|
||||||
|
Result := TAstBinder.Bind(FRootLayout, Node, Layout, Log);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTestAstBinder.Unwrap(const Node: IAstNode): IAstNode;
|
function TTestAstBinder.Unwrap(const Node: IAstNode): IAstNode;
|
||||||
@@ -107,8 +110,6 @@ begin
|
|||||||
Result := Node;
|
Result := Node;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// ... (Previous tests for Basic Scope, Parameters, Upvalues remain identical to previous post)
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Fixed Test: Initializer Visibility
|
// Fixed Test: Initializer Visibility
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
@@ -120,15 +121,14 @@ var
|
|||||||
block: IBlockExpressionNode;
|
block: IBlockExpressionNode;
|
||||||
decl: IVariableDeclarationNode;
|
decl: IVariableDeclarationNode;
|
||||||
initIdent: IIdentifierNode;
|
initIdent: IIdentifierNode;
|
||||||
|
log: ICompilerLog;
|
||||||
begin
|
begin
|
||||||
// (do
|
// (do (def a 10) (def b a))
|
||||||
// (def a 10)
|
|
||||||
// (def b a) <-- CHANGED: 'b' initializes from 'a'.
|
|
||||||
// 'def a a' is forbidden in same scope (no shadowing).
|
|
||||||
// )
|
|
||||||
root := TAst.Block([TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(10)), TAst.VarDecl(TAst.Identifier('b'), TAst.Identifier('a'))]);
|
root := TAst.Block([TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(10)), TAst.VarDecl(TAst.Identifier('b'), TAst.Identifier('a'))]);
|
||||||
|
|
||||||
bound := Bind(root, layout);
|
bound := Bind(root, layout, log);
|
||||||
|
Assert.IsFalse(log.HasErrors, 'Binding should succeed without errors.');
|
||||||
|
|
||||||
block := bound.AsBlockExpression;
|
block := bound.AsBlockExpression;
|
||||||
|
|
||||||
// Check definition of 'b'
|
// Check definition of 'b'
|
||||||
@@ -136,8 +136,6 @@ begin
|
|||||||
initIdent := decl.Initializer.AsIdentifier;
|
initIdent := decl.Initializer.AsIdentifier;
|
||||||
|
|
||||||
// 'a' is Slot 0. 'b' is Slot 1.
|
// 'a' is Slot 0. 'b' is Slot 1.
|
||||||
// The initializer 'a' must point to Slot 0.
|
|
||||||
|
|
||||||
Assert.AreEqual<Integer>(0, initIdent.Address.SlotIndex, 'Initializer should resolve to "a" (Slot 0).');
|
Assert.AreEqual<Integer>(0, initIdent.Address.SlotIndex, 'Initializer should resolve to "a" (Slot 0).');
|
||||||
Assert.AreEqual<Integer>(1, decl.Target.AsIdentifier.Address.SlotIndex, 'Target "b" should be at Slot 1.');
|
Assert.AreEqual<Integer>(1, decl.Target.AsIdentifier.Address.SlotIndex, 'Target "b" should be at Slot 1.');
|
||||||
end;
|
end;
|
||||||
@@ -146,21 +144,23 @@ end;
|
|||||||
// Rule Verification: No Redefinition
|
// Rule Verification: No Redefinition
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
procedure TTestAstBinder.Test_RedefinitionInSameScope_RaisesException;
|
procedure TTestAstBinder.Test_RedefinitionInSameScope_LogsError;
|
||||||
var
|
var
|
||||||
root: IAstNode;
|
root: IAstNode;
|
||||||
layout: IScopeLayout;
|
layout: IScopeLayout;
|
||||||
|
log: ICompilerLog;
|
||||||
begin
|
begin
|
||||||
// (do (def a 1) (def a 2)) - Forbidden!
|
// (do (def a 1) (def a 2)) - Forbidden!
|
||||||
root := TAst.Block([TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(1)), TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(2))]);
|
root := TAst.Block([TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(1)), TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(2))]);
|
||||||
|
|
||||||
Assert.WillRaise(procedure begin Bind(root, layout); end, EBinderException, 'Binder must forbid redefinition in the same scope.');
|
Bind(root, layout, log);
|
||||||
|
|
||||||
|
Assert.IsTrue(log.HasErrors, 'Binder must log error for redefinition.');
|
||||||
|
Assert.AreEqual('Variable "a" is already defined in this scope.', log.GetEntries[0].Message);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// ... (Rest of validation tests remain identical)
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Boilerplate for the rest of the tests (copying here for completeness of the unit block)
|
// Basic Scope & Definitions
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
procedure TTestAstBinder.Test_DefineAndResolve_LocalVariable;
|
procedure TTestAstBinder.Test_DefineAndResolve_LocalVariable;
|
||||||
@@ -169,9 +169,13 @@ var
|
|||||||
layout: IScopeLayout;
|
layout: IScopeLayout;
|
||||||
block: IBlockExpressionNode;
|
block: IBlockExpressionNode;
|
||||||
ident: IIdentifierNode;
|
ident: IIdentifierNode;
|
||||||
|
log: ICompilerLog;
|
||||||
begin
|
begin
|
||||||
root := TAst.Block([TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(42)), TAst.Identifier('a')]);
|
root := TAst.Block([TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(42)), TAst.Identifier('a')]);
|
||||||
bound := Bind(root, layout);
|
bound := Bind(root, layout, log);
|
||||||
|
|
||||||
|
Assert.IsFalse(log.HasErrors);
|
||||||
|
|
||||||
block := bound.AsBlockExpression;
|
block := bound.AsBlockExpression;
|
||||||
ident := block.Expressions[1].AsIdentifier;
|
ident := block.Expressions[1].AsIdentifier;
|
||||||
Assert.AreEqual<TAddressKind>(akLocalOrParent, ident.Address.Kind);
|
Assert.AreEqual<TAddressKind>(akLocalOrParent, ident.Address.Kind);
|
||||||
@@ -186,37 +190,53 @@ var
|
|||||||
block: IBlockExpressionNode;
|
block: IBlockExpressionNode;
|
||||||
innerLambda: ILambdaExpressionNode;
|
innerLambda: ILambdaExpressionNode;
|
||||||
innerUsage: IIdentifierNode;
|
innerUsage: IIdentifierNode;
|
||||||
|
log: ICompilerLog;
|
||||||
begin
|
begin
|
||||||
// This is technically shadowing across *nested* scopes (Lambda vs Outer Block).
|
|
||||||
// If you forbid this too, this test needs to fail. Currently assuming strictly "Same Scope" check.
|
|
||||||
root :=
|
root :=
|
||||||
TAst.Block([TAst.VarDecl(TAst.Identifier('x'), TAst.Constant(1)), TAst.LambdaExpr([TAst.Identifier('x')], TAst.Identifier('x'))]);
|
TAst.Block([TAst.VarDecl(TAst.Identifier('x'), TAst.Constant(1)), TAst.LambdaExpr([TAst.Identifier('x')], TAst.Identifier('x'))]);
|
||||||
bound := Bind(root, layout);
|
bound := Bind(root, layout, log);
|
||||||
|
|
||||||
|
Assert.IsFalse(log.HasErrors);
|
||||||
|
|
||||||
block := bound.AsBlockExpression;
|
block := bound.AsBlockExpression;
|
||||||
innerLambda := block.Expressions[1].AsLambdaExpression;
|
innerLambda := block.Expressions[1].AsLambdaExpression;
|
||||||
innerUsage := innerLambda.Body.AsIdentifier;
|
innerUsage := innerLambda.Body.AsIdentifier;
|
||||||
Assert.AreEqual<Integer>(1, innerUsage.Address.SlotIndex); // Parameter x
|
Assert.AreEqual<Integer>(1, innerUsage.Address.SlotIndex); // Parameter x (Slot 1 because <self> is Slot 0)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TTestAstBinder.Test_ScopeIsolation_SiblingScopesCannotSeeEachOther;
|
procedure TTestAstBinder.Test_ScopeIsolation_SiblingScopesCannotSeeEachOther;
|
||||||
var
|
var
|
||||||
root: IAstNode;
|
root: IAstNode;
|
||||||
layout: IScopeLayout;
|
layout: IScopeLayout;
|
||||||
|
log: ICompilerLog;
|
||||||
begin
|
begin
|
||||||
|
// (do (fn [] (def a 1)) a) -> 'a' is inside lambda, not visible outside
|
||||||
root := TAst.Block([TAst.LambdaExpr([], TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(1))), TAst.Identifier('a')]);
|
root := TAst.Block([TAst.LambdaExpr([], TAst.VarDecl(TAst.Identifier('a'), TAst.Constant(1))), TAst.Identifier('a')]);
|
||||||
Assert.WillRaise(procedure begin Bind(root, layout); end);
|
|
||||||
|
Bind(root, layout, log);
|
||||||
|
|
||||||
|
Assert.IsTrue(log.HasErrors, 'Sibling scope access should fail.');
|
||||||
|
Assert.IsTrue(log.GetEntries[0].Message.Contains('Undefined identifier'), 'Expected undefined identifier error.');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// Parameters
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
procedure TTestAstBinder.Test_LambdaParameters_AreBoundToSlotsStartingAtOne;
|
procedure TTestAstBinder.Test_LambdaParameters_AreBoundToSlotsStartingAtOne;
|
||||||
var
|
var
|
||||||
root, bound: IAstNode;
|
root, bound: IAstNode;
|
||||||
layout: IScopeLayout;
|
layout: IScopeLayout;
|
||||||
lambda: ILambdaExpressionNode;
|
lambda: ILambdaExpressionNode;
|
||||||
|
log: ICompilerLog;
|
||||||
begin
|
begin
|
||||||
root := TAst.LambdaExpr([TAst.Identifier('p1')], TAst.Nop);
|
root := TAst.LambdaExpr([TAst.Identifier('p1')], TAst.Nop);
|
||||||
bound := Unwrap(Bind(root, layout));
|
bound := Unwrap(Bind(root, layout, log));
|
||||||
|
|
||||||
|
Assert.IsFalse(log.HasErrors);
|
||||||
|
|
||||||
lambda := bound.AsLambdaExpression;
|
lambda := bound.AsLambdaExpression;
|
||||||
Assert.AreEqual<Integer>(1, lambda.Parameters[0].Address.SlotIndex);
|
Assert.AreEqual<Integer>(1, lambda.Parameters[0].Address.SlotIndex); // Slot 0 is reserved for <self>
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TTestAstBinder.Test_MultipleParameters_AreBoundCorrectly;
|
procedure TTestAstBinder.Test_MultipleParameters_AreBoundCorrectly;
|
||||||
@@ -224,14 +244,22 @@ var
|
|||||||
root, bound: IAstNode;
|
root, bound: IAstNode;
|
||||||
layout: IScopeLayout;
|
layout: IScopeLayout;
|
||||||
lambda: ILambdaExpressionNode;
|
lambda: ILambdaExpressionNode;
|
||||||
|
log: ICompilerLog;
|
||||||
begin
|
begin
|
||||||
root := TAst.LambdaExpr([TAst.Identifier('a'), TAst.Identifier('b')], TAst.Nop);
|
root := TAst.LambdaExpr([TAst.Identifier('a'), TAst.Identifier('b')], TAst.Nop);
|
||||||
bound := Unwrap(Bind(root, layout));
|
bound := Unwrap(Bind(root, layout, log));
|
||||||
|
|
||||||
|
Assert.IsFalse(log.HasErrors);
|
||||||
|
|
||||||
lambda := bound.AsLambdaExpression;
|
lambda := bound.AsLambdaExpression;
|
||||||
Assert.AreEqual<Integer>(1, lambda.Parameters[0].Address.SlotIndex);
|
Assert.AreEqual<Integer>(1, lambda.Parameters[0].Address.SlotIndex);
|
||||||
Assert.AreEqual<Integer>(2, lambda.Parameters[1].Address.SlotIndex);
|
Assert.AreEqual<Integer>(2, lambda.Parameters[1].Address.SlotIndex);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// Upvalues / Closures
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
procedure TTestAstBinder.Test_Capture_ImmediateParent;
|
procedure TTestAstBinder.Test_Capture_ImmediateParent;
|
||||||
var
|
var
|
||||||
root, bound: IAstNode;
|
root, bound: IAstNode;
|
||||||
@@ -239,14 +267,20 @@ var
|
|||||||
block: IBlockExpressionNode;
|
block: IBlockExpressionNode;
|
||||||
lambda: ILambdaExpressionNode;
|
lambda: ILambdaExpressionNode;
|
||||||
bodyIdent: IIdentifierNode;
|
bodyIdent: IIdentifierNode;
|
||||||
|
log: ICompilerLog;
|
||||||
begin
|
begin
|
||||||
root := TAst.Block([TAst.VarDecl(TAst.Identifier('x'), TAst.Constant(99)), TAst.LambdaExpr([], TAst.Identifier('x'))]);
|
root := TAst.Block([TAst.VarDecl(TAst.Identifier('x'), TAst.Constant(99)), TAst.LambdaExpr([], TAst.Identifier('x'))]);
|
||||||
bound := Bind(root, layout);
|
bound := Bind(root, layout, log);
|
||||||
|
|
||||||
|
Assert.IsFalse(log.HasErrors);
|
||||||
|
|
||||||
block := bound.AsBlockExpression;
|
block := bound.AsBlockExpression;
|
||||||
lambda := block.Expressions[1].AsLambdaExpression;
|
lambda := block.Expressions[1].AsLambdaExpression;
|
||||||
bodyIdent := lambda.Body.AsIdentifier;
|
bodyIdent := lambda.Body.AsIdentifier;
|
||||||
|
|
||||||
|
// Inside the lambda, 'x' is accessed via an Upvalue
|
||||||
Assert.AreEqual<TAddressKind>(akUpvalue, bodyIdent.Address.Kind);
|
Assert.AreEqual<TAddressKind>(akUpvalue, bodyIdent.Address.Kind);
|
||||||
Assert.AreEqual<Integer>(0, bodyIdent.Address.SlotIndex);
|
Assert.AreEqual<Integer>(0, bodyIdent.Address.SlotIndex); // First captured value
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TTestAstBinder.Test_Capture_GrandParent_PropagatesThroughIntermediateScope;
|
procedure TTestAstBinder.Test_Capture_GrandParent_PropagatesThroughIntermediateScope;
|
||||||
@@ -255,15 +289,21 @@ var
|
|||||||
layout: IScopeLayout;
|
layout: IScopeLayout;
|
||||||
outerBlock: IBlockExpressionNode;
|
outerBlock: IBlockExpressionNode;
|
||||||
midLambda, innerLambda: ILambdaExpressionNode;
|
midLambda, innerLambda: ILambdaExpressionNode;
|
||||||
|
log: ICompilerLog;
|
||||||
begin
|
begin
|
||||||
root :=
|
root :=
|
||||||
TAst.Block(
|
TAst.Block(
|
||||||
[TAst.VarDecl(TAst.Identifier('top'), TAst.Constant(100)), TAst.LambdaExpr([], TAst.LambdaExpr([], TAst.Identifier('top')))]
|
[TAst.VarDecl(TAst.Identifier('top'), TAst.Constant(100)), TAst.LambdaExpr([], TAst.LambdaExpr([], TAst.Identifier('top')))]
|
||||||
);
|
);
|
||||||
bound := Bind(root, layout);
|
bound := Bind(root, layout, log);
|
||||||
|
|
||||||
|
Assert.IsFalse(log.HasErrors);
|
||||||
|
|
||||||
outerBlock := bound.AsBlockExpression;
|
outerBlock := bound.AsBlockExpression;
|
||||||
midLambda := outerBlock.Expressions[1].AsLambdaExpression;
|
midLambda := outerBlock.Expressions[1].AsLambdaExpression;
|
||||||
innerLambda := midLambda.Body.AsLambdaExpression;
|
innerLambda := midLambda.Body.AsLambdaExpression;
|
||||||
|
|
||||||
|
// Inner lambda accesses 'top' via upvalue
|
||||||
Assert.AreEqual<TAddressKind>(akUpvalue, innerLambda.Body.AsIdentifier.Address.Kind);
|
Assert.AreEqual<TAddressKind>(akUpvalue, innerLambda.Body.AsIdentifier.Address.Kind);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -272,38 +312,58 @@ var
|
|||||||
root, bound: IAstNode;
|
root, bound: IAstNode;
|
||||||
layout: IScopeLayout;
|
layout: IScopeLayout;
|
||||||
lambda: ILambdaExpressionNode;
|
lambda: ILambdaExpressionNode;
|
||||||
|
log: ICompilerLog;
|
||||||
begin
|
begin
|
||||||
root := TAst.LambdaExpr([], TAst.Identifier('<self>'));
|
root := TAst.LambdaExpr([], TAst.Identifier('<self>'));
|
||||||
bound := Unwrap(Bind(root, layout));
|
bound := Unwrap(Bind(root, layout, log));
|
||||||
|
|
||||||
|
Assert.IsFalse(log.HasErrors);
|
||||||
|
|
||||||
lambda := bound.AsLambdaExpression;
|
lambda := bound.AsLambdaExpression;
|
||||||
|
// <self> is always at Slot 0 (Local)
|
||||||
|
Assert.AreEqual<TAddressKind>(akLocalOrParent, lambda.Body.AsIdentifier.Address.Kind);
|
||||||
Assert.AreEqual<Integer>(0, lambda.Body.AsIdentifier.Address.SlotIndex);
|
Assert.AreEqual<Integer>(0, lambda.Body.AsIdentifier.Address.SlotIndex);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// Validation Tests
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
procedure TTestAstBinder.Test_IdentifierValidation_ValidNames(const Name: string);
|
procedure TTestAstBinder.Test_IdentifierValidation_ValidNames(const Name: string);
|
||||||
var
|
var
|
||||||
root: IAstNode;
|
root: IAstNode;
|
||||||
layout: IScopeLayout;
|
layout: IScopeLayout;
|
||||||
|
log: ICompilerLog;
|
||||||
begin
|
begin
|
||||||
root := TAst.VarDecl(TAst.Identifier(Name), nil);
|
root := TAst.VarDecl(TAst.Identifier(Name), nil);
|
||||||
Assert.WillNotRaise(procedure begin Bind(root, layout); end);
|
Bind(root, layout, log);
|
||||||
|
Assert.IsFalse(log.HasErrors, 'Valid name should not produce errors.');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TTestAstBinder.Test_IdentifierValidation_InvalidNames_RaisesException(const Name: string);
|
procedure TTestAstBinder.Test_IdentifierValidation_InvalidNames_LogsError(const Name: string);
|
||||||
var
|
var
|
||||||
root: IAstNode;
|
root: IAstNode;
|
||||||
layout: IScopeLayout;
|
layout: IScopeLayout;
|
||||||
|
log: ICompilerLog;
|
||||||
begin
|
begin
|
||||||
root := TAst.VarDecl(TAst.Identifier(Name), nil);
|
root := TAst.VarDecl(TAst.Identifier(Name), nil);
|
||||||
Assert.WillRaise(procedure begin Bind(root, layout); end);
|
Bind(root, layout, log);
|
||||||
|
|
||||||
|
Assert.IsTrue(log.HasErrors, 'Invalid name should produce error.');
|
||||||
|
Assert.IsTrue(log.GetEntries[0].Message.Contains('Invalid identifier name'));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TTestAstBinder.Test_UnresolvedIdentifier_RaisesException;
|
procedure TTestAstBinder.Test_UnresolvedIdentifier_LogsError;
|
||||||
var
|
var
|
||||||
root: IAstNode;
|
root: IAstNode;
|
||||||
layout: IScopeLayout;
|
layout: IScopeLayout;
|
||||||
|
log: ICompilerLog;
|
||||||
begin
|
begin
|
||||||
root := TAst.Identifier('z');
|
root := TAst.Identifier('z');
|
||||||
Assert.WillRaise(procedure begin Bind(root, layout); end);
|
Bind(root, layout, log);
|
||||||
|
|
||||||
|
Assert.IsTrue(log.HasErrors, 'Unresolved identifier should produce error.');
|
||||||
|
Assert.IsTrue(log.GetEntries[0].Message.Contains('Undefined identifier'));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
Reference in New Issue
Block a user