AST Identities

This commit is contained in:
Michael Schimmel
2025-11-25 19:41:26 +01:00
parent 0b7a60e338
commit aff4cec7d5
9 changed files with 2334 additions and 2150 deletions
+43 -36
View File
@@ -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;