unit Myc.Ast.Binding; interface uses System.SysUtils, System.Classes, System.Generics.Collections, Myc.Data.Value, Myc.Ast.Nodes, Myc.Ast.Visitor, Myc.Ast.Scope, Myc.Ast.Analyzer, Myc.Ast; type // The binder is a transformer that enriches the AST with semantic information // like resolved addresses, scopes, and tail-call annotations. IAstBinder = interface(IAstVisitor) function Execute(const RootNode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode; end; TAstBinder = class(TAstTransformer, IAstBinder) private type // Helper class to track upvalues for a lambda expression. TUpvalueMapping = class public Map: TDictionary; Nodes: TList; constructor Create; destructor Destroy; override; end; private FCurrentDescriptor: IScopeDescriptor; FUpvalueStack: TStack; FNestedLambdaCount: Integer; FIsTailStack: TStack; FNextIsTail: Boolean; // Contains all variable declarations that are captured by a closure. // This is populated by a pre-pass before the transformation begins. FBoxedDeclarations: THashSet; procedure EnterScope; procedure ExitScope; function IsValidIdentifier(const Name: string): Boolean; protected // Stack management for tail-call state is centralized here. function Accept(const Node: IAstNode): TDataValue; override; public constructor Create(const AInitialScope: IExecutionScope); destructor Destroy; override; function Execute(const RootNode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode; // The binder overrides specific transform methods to enrich the AST. function TransformIdentifier(const Node: IIdentifierNode): IIdentifierNode; override; function TransformVariableDeclaration(const Node: IVariableDeclarationNode): IVariableDeclarationNode; override; function TransformAssignment(const Node: IAssignmentNode): IAssignmentNode; override; function TransformLambdaExpression(const Node: ILambdaExpressionNode): ILambdaExpressionNode; override; function TransformFunctionCall(const Node: IFunctionCallNode): IFunctionCallNode; override; function TransformRecur(const Node: IRecurNode): IRecurNode; override; function TransformBlockExpression(const Node: IBlockExpressionNode): IBlockExpressionNode; override; function TransformIfExpression(const Node: IIfExpressionNode): IIfExpressionNode; override; function TransformTernaryExpression(const Node: ITernaryExpressionNode): ITernaryExpressionNode; override; function TransformBinaryExpression(const Node: IBinaryExpressionNode): IBinaryExpressionNode; override; function TransformUnaryExpression(const Node: IUnaryExpressionNode): IUnaryExpressionNode; override; end; TBoundIdentifierNode = class(TIdentifierNode) private FAddress: TResolvedAddress; public constructor Create(const AUnboundNode: IIdentifierNode; const AAddress: TResolvedAddress); property Address: TResolvedAddress read FAddress; end; // A bound variable declaration node that includes whether the variable is captured (boxed). TBoundVariableDeclarationNode = class(TVariableDeclarationNode) private FIsBoxed: Boolean; public constructor Create(const AIdentifier: IIdentifierNode; AInitializer: IAstNode; AIsBoxed: Boolean); property IsBoxed: Boolean read FIsBoxed; end; TBoundLambdaExpressionNode = class(TLambdaExpressionNode) private FScopeDescriptor: IScopeDescriptor; FUpvalues: TArray; FHasNestedLambdas: Boolean; public constructor Create( const AUnboundNode: ILambdaExpressionNode; const ABody: IAstNode; const AParameters: TArray; const AScopeDescriptor: IScopeDescriptor; const AUpvalues: TArray; AHasNestedLambdas: Boolean ); property ScopeDescriptor: IScopeDescriptor read FScopeDescriptor; property Upvalues: TArray read FUpvalues; property HasNestedLambdas: Boolean read FHasNestedLambdas; end; TBoundFunctionCallNode = class(TFunctionCallNode) private FIsTailCall: Boolean; public constructor Create( const AUnboundNode: IFunctionCallNode; const ACallee: IAstNode; const AArguments: TArray; AIsTailCall: Boolean ); property IsTailCall: Boolean read FIsTailCall; end; implementation uses System.Generics.Defaults, System.Character; type // A custom equality comparer for TResolvedAddress to ensure correct behavior in TDictionary. TResolvedAddressComparer = class(TEqualityComparer) public function Equals(const Left, Right: TResolvedAddress): Boolean; override; function GetHashCode(const Value: TResolvedAddress): Integer; override; end; { TBoundIdentifierNode } constructor TBoundIdentifierNode.Create(const AUnboundNode: IIdentifierNode; const AAddress: TResolvedAddress); begin inherited Create(AUnboundNode.Name); FAddress := AAddress; end; { TBoundVariableDeclarationNode } constructor TBoundVariableDeclarationNode.Create(const AIdentifier: IIdentifierNode; AInitializer: IAstNode; AIsBoxed: Boolean); begin inherited Create(AIdentifier, AInitializer); FIsBoxed := AIsBoxed; end; { TBoundLambdaExpressionNode } constructor TBoundLambdaExpressionNode.Create( const AUnboundNode: ILambdaExpressionNode; const ABody: IAstNode; const AParameters: TArray; const AScopeDescriptor: IScopeDescriptor; const AUpvalues: TArray; AHasNestedLambdas: Boolean ); begin inherited Create(AParameters, ABody); FScopeDescriptor := AScopeDescriptor; FUpvalues := AUpvalues; FHasNestedLambdas := AHasNestedLambdas; end; { TBoundFunctionCallNode } constructor TBoundFunctionCallNode.Create( const AUnboundNode: IFunctionCallNode; const ACallee: IAstNode; const AArguments: TArray; AIsTailCall: Boolean ); begin inherited Create(ACallee, AArguments); FIsTailCall := AIsTailCall; end; { TResolvedAddressComparer } function TResolvedAddressComparer.Equals(const Left, Right: TResolvedAddress): Boolean; begin Result := (Left = Right); end; function TResolvedAddressComparer.GetHashCode(const Value: TResolvedAddress): Integer; begin Result := 17; Result := Result * 23 + Ord(Value.Kind); Result := Result * 23 + Value.ScopeDepth; Result := Result * 23 + Value.SlotIndex; end; { TAstBinder.TUpvalueMapping } constructor TAstBinder.TUpvalueMapping.Create; begin inherited Create; Map := TDictionary.Create(TResolvedAddressComparer.Create); Nodes := TList.Create(); end; destructor TAstBinder.TUpvalueMapping.Destroy; begin Nodes.Free; Map.Free; inherited Destroy; end; constructor TAstBinder.Create(const AInitialScope: IExecutionScope); begin inherited Create; FCurrentDescriptor := AInitialScope.CreateDescriptor; FUpvalueStack := TObjectStack.Create(True); FNestedLambdaCount := 0; FIsTailStack := TStack.Create; FNextIsTail := True; FBoxedDeclarations := nil; end; destructor TAstBinder.Destroy; begin FIsTailStack.Free; FUpvalueStack.Free; FBoxedDeclarations.Free; inherited; end; function TAstBinder.Accept(const Node: IAstNode): TDataValue; begin if (not Assigned(Node)) or Done then exit; FIsTailStack.Push(FNextIsTail); try Result := inherited Accept(Node); finally FNextIsTail := FIsTailStack.Pop; end; end; procedure TAstBinder.EnterScope; begin FCurrentDescriptor := TScope.CreateDescriptor(FCurrentDescriptor); end; function TAstBinder.Execute(const RootNode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode; begin // First pass: Analyze the entire AST to find all variable declarations // that are captured by closures (upvalues). FBoxedDeclarations := TUpvalueAnalyzer.Analyze(RootNode, FCurrentDescriptor.Parent); try // Second pass: Transform the tree, using the information from the analysis pass. EnterScope; try Result := Accept(RootNode).AsIntf; Descriptor := FCurrentDescriptor; finally ExitScope; end; finally // The binder now owns the hash set, which will be freed in the destructor. end; end; procedure TAstBinder.ExitScope; begin FCurrentDescriptor := FCurrentDescriptor.Parent; end; function TAstBinder.IsValidIdentifier(const Name: string): Boolean; var c: Char; begin if Name.IsEmpty then exit(False); c := Name[1]; if not (c.IsLetter or (c = '_')) then exit(False); for c in Name do begin if not (c.IsLetterOrDigit or (c = '_') or (c = '-')) then exit(False); end; Result := True; end; function TAstBinder.TransformIdentifier(const Node: IIdentifierNode): IIdentifierNode; var adr: TResolvedAddress; begin adr := FCurrentDescriptor.FindSymbol(Node.Name); if adr.Kind = akLocalOrParent then begin if (adr.ScopeDepth > 0) and (FUpvalueStack.Count > 0) then begin var upvalue := FUpvalueStack.Peek; // up to outer scope dec(adr.ScopeDepth); var upvalueIndex: Integer; if not upvalue.Map.TryGetValue(adr, upvalueIndex) then begin upvalueIndex := upvalue.Map.Count; upvalue.Map.Add(adr, upvalueIndex); end; Result := TBoundIdentifierNode.Create(Node, TResolvedAddress.Create(akUpvalue, 0, upvalueIndex)); end else Result := TBoundIdentifierNode.Create(Node, adr); end else raise Exception.CreateFmt('Undefined identifier: "%s"', [Node.Name]); end; function TAstBinder.TransformVariableDeclaration(const Node: IVariableDeclarationNode): IVariableDeclarationNode; var initializer: IAstNode; slotIndex: Integer; address: TResolvedAddress; boundIdentifier: IIdentifierNode; isBoxed: Boolean; begin if not IsValidIdentifier(Node.Identifier.Name) then raise Exception.CreateFmt('Invalid identifier name: "%s".', [Node.Identifier.Name]); FNextIsTail := False; initializer := nil; if Node.Initializer <> nil then initializer := Accept(Node.Initializer).AsIntf; slotIndex := FCurrentDescriptor.Define(Node.Identifier.Name); address := TResolvedAddress.Create(akLocalOrParent, 0, slotIndex); boundIdentifier := TBoundIdentifierNode.Create(Node.Identifier, address); // Check if the analysis pass marked this declaration as being captured by a closure. isBoxed := (FBoxedDeclarations <> nil) and FBoxedDeclarations.Contains(Node); // Always create a bound declaration node, passing the IsBoxed flag. Result := TBoundVariableDeclarationNode.Create(boundIdentifier, initializer, isBoxed); end; function TAstBinder.TransformAssignment(const Node: IAssignmentNode): IAssignmentNode; begin FNextIsTail := False; Result := inherited TransformAssignment(Node); end; function TAstBinder.TransformBinaryExpression(const Node: IBinaryExpressionNode): IBinaryExpressionNode; begin FNextIsTail := False; Result := inherited TransformBinaryExpression(Node); end; function TAstBinder.TransformUnaryExpression(const Node: IUnaryExpressionNode): IUnaryExpressionNode; begin FNextIsTail := False; Result := inherited TransformUnaryExpression(Node); end; function TAstBinder.TransformLambdaExpression(const Node: ILambdaExpressionNode): ILambdaExpressionNode; var i: integer; boundParams: TArray; boundBody: IAstNode; lambdaScope: IScopeDescriptor; upvalues: TArray; hasNestedLambdas: Boolean; lastNestedLambdaCount: Integer; begin FUpvalueStack.Push(TUpvalueMapping.Create); try EnterScope; try FCurrentDescriptor.Define(''); SetLength(boundParams, Length(Node.Parameters)); for i := 0 to High(Node.Parameters) do begin var paramNode := Node.Parameters[i]; var slotIndex := FCurrentDescriptor.Define(paramNode.Name); var address := TResolvedAddress.Create(akLocalOrParent, 0, slotIndex); boundParams[i] := TBoundIdentifierNode.Create(paramNode, address); end; lastNestedLambdaCount := FNestedLambdaCount; FNextIsTail := True; boundBody := Accept(Node.Body).AsIntf; hasNestedLambdas := FNestedLambdaCount > lastNestedLambdaCount; lambdaScope := FCurrentDescriptor; finally ExitScope; end; var upvalueMapping := FUpvalueStack.Peek; var sortedPairs := upvalueMapping.Map.ToArray; TArray.Sort>( sortedPairs, TComparer>.Construct( function(const Left, Right: TPair): Integer begin Result := Left.Value - Right.Value; end ) ); SetLength(upvalues, Length(sortedPairs)); for i := 0 to High(sortedPairs) do upvalues[i] := sortedPairs[i].Key; finally FUpvalueStack.Pop; end; inc(FNestedLambdaCount); Result := TBoundLambdaExpressionNode.Create(Node, boundBody, boundParams, lambdaScope, upvalues, hasNestedLambdas); end; function TAstBinder.TransformFunctionCall(const Node: IFunctionCallNode): IFunctionCallNode; var isTailCall: Boolean; callee: IAstNode; args: TArray; begin isTailCall := FIsTailStack.Peek; FNextIsTail := False; callee := Accept(Node.Callee).AsIntf; args := TransformNodes(Node.Arguments); Result := TBoundFunctionCallNode.Create(Node, callee, args, isTailCall); end; function TAstBinder.TransformRecur(const Node: IRecurNode): IRecurNode; begin if not FIsTailStack.Peek then raise Exception.Create('''recur'' can only be used in a tail position.'); FNextIsTail := False; Result := inherited TransformRecur(Node); end; function TAstBinder.TransformBlockExpression(const Node: IBlockExpressionNode): IBlockExpressionNode; var exprs: TArray; i: Integer; isContextTail: Boolean; begin isContextTail := FIsTailStack.Peek; SetLength(exprs, Node.Expressions.Count); for i := 0 to Node.Expressions.Count - 1 do begin FNextIsTail := isContextTail and (i = Node.Expressions.Count - 1); exprs[i] := Accept(Node.Expressions[i]).AsIntf; end; Result := TAst.Block(exprs); end; function TAstBinder.TransformIfExpression(const Node: IIfExpressionNode): IIfExpressionNode; var isContextTail: Boolean; condition, thenBranch, elseBranch: IAstNode; begin isContextTail := FIsTailStack.Peek; FNextIsTail := False; condition := Accept(Node.Condition).AsIntf; FNextIsTail := isContextTail; thenBranch := Accept(Node.ThenBranch).AsIntf; elseBranch := Accept(Node.ElseBranch).AsIntf; if (condition <> Node.Condition) or (thenBranch <> Node.ThenBranch) or (elseBranch <> Node.ElseBranch) then Result := TAst.IfExpr(condition, thenBranch, elseBranch) else Result := Node; end; function TAstBinder.TransformTernaryExpression(const Node: ITernaryExpressionNode): ITernaryExpressionNode; var isContextTail: Boolean; condition, thenBranch, elseBranch: IAstNode; begin isContextTail := FIsTailStack.Peek; FNextIsTail := False; condition := Accept(Node.Condition).AsIntf; FNextIsTail := isContextTail; thenBranch := Accept(Node.ThenBranch).AsIntf; elseBranch := Accept(Node.ElseBranch).AsIntf; if (condition <> Node.Condition) or (thenBranch <> Node.ThenBranch) or (elseBranch <> Node.ElseBranch) then Result := TAst.TernaryExpr(condition, thenBranch, elseBranch) else Result := Node; end; end.