Ast Binding

This commit is contained in:
Michael Schimmel
2025-09-04 00:19:11 +02:00
parent a5fd079875
commit de052cab64
2 changed files with 328 additions and 4 deletions
+327 -4
View File
@@ -10,6 +10,7 @@ uses
type
// Record acting as a namespace for the factory functions.
TAst = record
// --- Existing factory functions ---
class function Constant(AValue: TScalar): IConstantNode; static;
class function Identifier(AName: string): IIdentifierNode; static;
class function BinaryExpr(
@@ -31,7 +32,7 @@ type
class function Block(const AExpressions: array of IExpressionNode): IBlockExpressionNode; static;
class function VarDecl(const AIdentifier: IIdentifierNode; AInitializer: IExpressionNode): IVariableDeclarationNode; static;
class function Assign(const AIdentifier: IIdentifierNode; const AValue: IExpressionNode): IAssignmentNode; static;
class function AssignResult(const AValue: IExpressionNode): IAssignmentNode; static;
class function AssignResult(const AValue: IExpressionNode): IAssignmentNode; static; deprecated;
class function Indexer(const ABase: IExpressionNode; const AIndex: IExpressionNode): IIndexerNode; static;
class function MemberAccess(const ABase: IExpressionNode; const AMember: IIdentifierNode): IMemberAccessNode; static;
class function CreateSeries(const ADefinition: String): ICreateSeriesNode; static;
@@ -40,8 +41,32 @@ type
const AValue: IExpressionNode;
const ALookback: IExpressionNode = nil
): IAddSeriesItemNode; static;
// Added the new factory function for SeriesLength
class function SeriesLength(const ASeries: IIdentifierNode): ISeriesLengthNode; static;
// --- Static method to run the binder ---
// Traverses the AST and resolves all identifiers. This must be called before evaluating the AST.
class procedure Bind(const ARootNode: IExpressionNode); static;
end;
// TAstTraverser provides a default AST traversal implementation.
TAstTraverser = class abstract(TInterfacedObject, IAstVisitor)
public
function VisitConstant(const Node: IConstantNode): TAstValue; virtual;
function VisitIdentifier(const Node: IIdentifierNode): TAstValue; virtual;
function VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue; virtual;
function VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue; virtual;
function VisitIfExpression(const Node: IIfExpressionNode): TAstValue; virtual;
function VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue; virtual;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue; virtual;
function VisitFunctionCall(const Node: IFunctionCallNode): TAstValue; virtual;
function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue; virtual;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; virtual;
function VisitAssignment(const Node: IAssignmentNode): TAstValue; virtual;
function VisitIndexer(const Node: IIndexerNode): TAstValue; virtual;
function VisitMemberAccess(const Node: IMemberAccessNode): TAstValue; virtual;
function VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue; virtual;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue; virtual;
function VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue; virtual;
end;
implementation
@@ -69,13 +94,22 @@ type
end;
{ TIdentifierNode }
// TIdentifierNode now includes fields for binder annotations.
TIdentifierNode = class(TAstNode, IIdentifierNode)
private
FName: string;
// --- Annotation fields added for the binder ---
FIsResolved: Boolean;
FScopeDepth: Integer;
FSlotIndex: Integer;
function GetName: string;
public
constructor Create(AName: string);
function Accept(const Visitor: IAstVisitor): TAstValue; override;
function Resolve(out ScopeDepth: Integer; out SlotIndex: Integer): Boolean;
property Name: string read FName;
end;
{ TBinaryExpressionNode }
@@ -205,7 +239,6 @@ type
end;
{ TMemberAccessNode }
// Concrete implementation for the member access node.
TMemberAccessNode = class(TAstNode, IMemberAccessNode)
private
FBase: IExpressionNode;
@@ -241,7 +274,6 @@ type
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
// Added concrete class for the series length node.
{ TSeriesLengthNode }
TSeriesLengthNode = class(TAstNode, ISeriesLengthNode)
private
@@ -252,6 +284,40 @@ type
function Accept(const Visitor: IAstVisitor): TAstValue; override;
end;
// --- Binder Implementation ---
TSymbol = record
Name: string;
SlotIndex: Integer;
end;
TSymbolTable = class
private
FParent: TSymbolTable;
FSymbols: TDictionary<string, TSymbol>;
FNextSlotIndex: Integer;
public
constructor Create(AParent: TSymbolTable);
destructor Destroy; override;
function Define(const Name: string): TSymbol;
function Resolve(const Name: string; out Symbol: TSymbol; out Depth: Integer): Boolean;
end;
// TBinder inherits from the base traverser and overrides methods with specific binding logic.
TBinder = class(TAstTraverser)
private
FCurrentScope: TSymbolTable;
procedure EnterScope;
procedure ExitScope;
public
constructor Create;
destructor Destroy; override;
function VisitIdentifier(const Node: IIdentifierNode): TAstValue; override;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue; override;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue; override;
function VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue; override;
end;
{ TConstantNode }
constructor TConstantNode.Create(AValue: TScalar);
@@ -276,6 +342,9 @@ constructor TIdentifierNode.Create(AName: string);
begin
inherited Create;
FName := AName;
FIsResolved := False;
FScopeDepth := -1;
FSlotIndex := -1;
end;
function TIdentifierNode.Accept(const Visitor: IAstVisitor): TAstValue;
@@ -288,6 +357,16 @@ begin
Result := FName;
end;
function TIdentifierNode.Resolve(out ScopeDepth: Integer; out SlotIndex: Integer): Boolean;
begin
Result := FIsResolved;
if Result then
begin
ScopeDepth := FScopeDepth;
SlotIndex := FSlotIndex;
end;
end;
{ TBinaryExpressionNode }
constructor TBinaryExpressionNode.Create(ALeft: IExpressionNode; AOperator: TBinaryOperator; ARight: IExpressionNode);
@@ -650,8 +729,148 @@ begin
Result := FSeries;
end;
{ TSymbolTable }
constructor TSymbolTable.Create(AParent: TSymbolTable);
begin
inherited Create;
FParent := AParent;
FSymbols := TDictionary<string, TSymbol>.Create;
FNextSlotIndex := 0;
end;
destructor TSymbolTable.Destroy;
begin
FSymbols.Free;
inherited;
end;
function TSymbolTable.Define(const Name: string): TSymbol;
begin
Result.Name := Name;
Result.SlotIndex := FNextSlotIndex;
inc(FNextSlotIndex);
FSymbols.Add(Name, Result);
end;
function TSymbolTable.Resolve(const Name: string; out Symbol: TSymbol; out Depth: Integer): Boolean;
var
currentScope: TSymbolTable;
begin
Depth := 0;
currentScope := Self;
while Assigned(currentScope) do
begin
if currentScope.FSymbols.TryGetValue(Name, Symbol) then
Exit(True);
inc(Depth);
currentScope := currentScope.FParent;
end;
Result := False;
end;
{ TBinder }
constructor TBinder.Create;
begin
inherited;
FCurrentScope := TSymbolTable.Create(nil); // Start with a global scope
end;
destructor TBinder.Destroy;
begin
Assert(not Assigned(FCurrentScope.FParent), 'Scope leak in binder.');
FCurrentScope.Free;
inherited;
end;
procedure TBinder.EnterScope;
begin
FCurrentScope := TSymbolTable.Create(FCurrentScope);
end;
procedure TBinder.ExitScope;
var
oldScope: TSymbolTable;
begin
oldScope := FCurrentScope;
FCurrentScope := FCurrentScope.FParent;
oldScope.Free;
end;
function TBinder.VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
begin
EnterScope;
try
// Default traversal of all expressions in the block
inherited VisitBlockExpression(Node);
finally
ExitScope;
end;
end;
function TBinder.VisitIdentifier(const Node: IIdentifierNode): TAstValue;
var
symbol: TSymbol;
depth: Integer;
identNode: TIdentifierNode;
begin
identNode := Node as TIdentifierNode;
if identNode.FIsResolved then
Exit;
if FCurrentScope.Resolve(identNode.Name, symbol, depth) then
begin
identNode.FIsResolved := True;
identNode.FScopeDepth := depth;
identNode.FSlotIndex := symbol.SlotIndex;
end
else
raise Exception.CreateFmt('Undefined identifier: "%s"', [identNode.Name]);
end;
function TBinder.VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue;
var
param: IIdentifierNode;
begin
EnterScope;
try
// 1. Define all parameters in the new scope.
for param in Node.Parameters do
FCurrentScope.Define(param.Name);
// 2. Now visit the parameter identifiers to resolve them to their new definitions,
// and visit the body to resolve its identifiers.
inherited VisitLambdaExpression(Node);
finally
ExitScope;
end;
end;
function TBinder.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue;
begin
// 1. First, visit the initializer expression to resolve its identifiers.
if Assigned(Node.Initializer) then
Node.Initializer.Accept(Self);
// 2. Then, define the new variable in the current scope.
FCurrentScope.Define(Node.Identifier.Name);
// 3. Finally, visit the declaration's own identifier to resolve it.
Node.Identifier.Accept(Self);
end;
{ TAst }
class procedure TAst.Bind(const ARootNode: IExpressionNode);
var
binder: IAstVisitor;
begin
binder := TBinder.Create;
ARootNode.Accept(binder);
end;
class function TAst.AddSeriesItem(
const ASeries: IIdentifierNode;
const AValue: IExpressionNode;
@@ -741,4 +960,108 @@ begin
Result := TVariableDeclarationNode.Create(AIdentifier, AInitializer);
end;
{ TAstTraverser }
function TAstTraverser.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TAstValue;
begin
Node.Series.Accept(Self);
Node.Value.Accept(Self);
if Assigned(Node.Lookback) then
Node.Lookback.Accept(Self);
end;
function TAstTraverser.VisitAssignment(const Node: IAssignmentNode): TAstValue;
begin
Node.Value.Accept(Self);
Node.Identifier.Accept(Self);
end;
function TAstTraverser.VisitBinaryExpression(const Node: IBinaryExpressionNode): TAstValue;
begin
Node.Left.Accept(Self);
Node.Right.Accept(Self);
end;
function TAstTraverser.VisitBlockExpression(const Node: IBlockExpressionNode): TAstValue;
var
expr: IExpressionNode;
begin
for expr in Node.Expressions do
expr.Accept(Self);
end;
function TAstTraverser.VisitConstant(const Node: IConstantNode): TAstValue;
begin
end;
function TAstTraverser.VisitCreateSeries(const Node: ICreateSeriesNode): TAstValue;
begin
end;
function TAstTraverser.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
var
arg: IExpressionNode;
begin
Node.Callee.Accept(Self);
for arg in Node.Arguments do
arg.Accept(Self);
end;
function TAstTraverser.VisitIdentifier(const Node: IIdentifierNode): TAstValue;
begin
end;
function TAstTraverser.VisitIfExpression(const Node: IIfExpressionNode): TAstValue;
begin
Node.Condition.Accept(Self);
Node.ThenBranch.Accept(Self);
if Assigned(Node.ElseBranch) then
Node.ElseBranch.Accept(Self);
end;
function TAstTraverser.VisitIndexer(const Node: IIndexerNode): TAstValue;
begin
Node.Base.Accept(Self);
Node.Index.Accept(Self);
end;
function TAstTraverser.VisitLambdaExpression(const Node: ILambdaExpressionNode): TAstValue;
var
param: IIdentifierNode;
begin
for param in Node.Parameters do
param.Accept(Self);
Node.Body.Accept(Self);
end;
function TAstTraverser.VisitMemberAccess(const Node: IMemberAccessNode): TAstValue;
begin
// Do not visit the member identifier, as it's not a variable in the current scope.
Node.Base.Accept(Self);
end;
function TAstTraverser.VisitSeriesLength(const Node: ISeriesLengthNode): TAstValue;
begin
Node.Series.Accept(Self);
end;
function TAstTraverser.VisitTernaryExpression(const Node: ITernaryExpressionNode): TAstValue;
begin
Node.Condition.Accept(Self);
Node.ThenBranch.Accept(Self);
Node.ElseBranch.Accept(Self);
end;
function TAstTraverser.VisitUnaryExpression(const Node: IUnaryExpressionNode): TAstValue;
begin
Node.Right.Accept(Self);
end;
function TAstTraverser.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TAstValue;
begin
if Assigned(Node.Initializer) then
Node.Initializer.Accept(Self);
Node.Identifier.Accept(Self);
end;
end.