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