This commit is contained in:
Michael Schimmel
2025-09-04 21:17:23 +02:00
parent bb74d408da
commit 4a8075fecf
5 changed files with 76 additions and 155 deletions
+5 -13
View File
@@ -122,16 +122,10 @@ end;
function TForm1.ExecuteAst(const ANode: IExpressionNode; const AParentScope: IExecutionScope): TAstValue; function TForm1.ExecuteAst(const ANode: IExpressionNode; const AParentScope: IExecutionScope): TAstValue;
var var
scriptDescriptor: IScopeDescriptor;
scriptScope: IExecutionScope; scriptScope: IExecutionScope;
begin begin
// STEP 1: BIND and get the descriptor for the script's local variables. scriptScope := TAst.Bind(ANode, AParentScope);
scriptDescriptor := TAst.Bind(ANode, AParentScope);
// STEP 2: CREATE the correctly-sized execution scope for the script.
scriptScope := scriptDescriptor.Instantiate(AParentScope);
// STEP 3: EVALUATE using the new, correctly-sized scope.
var visitor := CreateVisitor(scriptScope); var visitor := CreateVisitor(scriptScope);
Result := ANode.Accept(visitor); Result := ANode.Accept(visitor);
end; end;
@@ -512,7 +506,7 @@ begin
); );
FLastAst := setupAst; FLastAst := setupAst;
var scope := TAst.Bind(setupAst, FGScope).Instantiate(FGScope); var scope := TAst.Bind(setupAst, FGScope);
setupAst.Accept(CreateVisitor(scope)); setupAst.Accept(CreateVisitor(scope));
// Declare the series variable in the scope BEFORE binding the call AST // Declare the series variable in the scope BEFORE binding the call AST
@@ -523,7 +517,7 @@ begin
var callAst := TAst.FunctionCall(TAst.Identifier('maCrossStrategy'), [currentSeriesIdent]); var callAst := TAst.FunctionCall(TAst.Identifier('maCrossStrategy'), [currentSeriesIdent]);
// Bind the call AST once, before the loop. // Bind the call AST once, before the loop.
scope := TAst.Bind(callAst, scope).Instantiate(scope); scope := TAst.Bind(callAst, scope);
var visitor := CreateVisitor(scope); var visitor := CreateVisitor(scope);
// 4. Simulation Loop // 4. Simulation Loop
@@ -535,7 +529,7 @@ begin
var recDef := TRttiAstHelper.JsonToRecordDefinition(TRttiAstHelper.RecordDefinitionToJson<TOHLCV>); var recDef := TRttiAstHelper.JsonToRecordDefinition(TRttiAstHelper.RecordDefinitionToJson<TOHLCV>);
var series := TAstValue.FromRecordSeries(TScalarRecordSeries.Create(recDef)); var series := TAstValue.FromRecordSeries(TScalarRecordSeries.Create(recDef));
var seriesAddress := currentSeriesIdent.Resolve; var seriesAddress := currentSeriesIdent.Address;
var nw := Now; var nw := Now;
var ohlcvRec: TOHLCV; var ohlcvRec: TOHLCV;
@@ -611,7 +605,7 @@ begin
] ]
); );
TriggerScope := TAst.Bind(blk, FGScope).Instantiate(FGScope); TriggerScope := TAst.Bind(blk, FGScope);
blk.Accept(CreateVisitor(TriggerScope)); blk.Accept(CreateVisitor(TriggerScope));
@@ -628,7 +622,6 @@ begin
callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(TScalar.FromInt64(1))]); callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(TScalar.FromInt64(1))]);
FLastAst := callAst; FLastAst := callAst;
// Bind and execute the call against the persistent scope
var X := ExecuteAst(callAst, TriggerScope); var X := ExecuteAst(callAst, TriggerScope);
Memo1.Lines.Add(Format('Tick(1)! New value of X: %s', [X.ToString])); Memo1.Lines.Add(Format('Tick(1)! New value of X: %s', [X.ToString]));
@@ -642,7 +635,6 @@ begin
callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(TScalar.FromInt64(2))]); callAst := TAst.FunctionCall(TAst.Identifier('tickHandler'), [TAst.Constant(TScalar.FromInt64(2))]);
FLastAst := callAst; FLastAst := callAst;
// Bind and execute the call against the persistent scope
var X := ExecuteAst(callAst, TriggerScope); var X := ExecuteAst(callAst, TriggerScope);
Memo1.Lines.Add(Format('Tick(2)! New value of X: %s', [X.ToString])); Memo1.Lines.Add(Format('Tick(2)! New value of X: %s', [X.ToString]));
+15 -23
View File
@@ -88,8 +88,8 @@ type
// The signature for a native Delphi function callable from the script. // The signature for a native Delphi function callable from the script.
TNativeFunction = function(const Args: TArray<TAstValue>): TAstValue; TNativeFunction = function(const Args: TArray<TAstValue>): TAstValue;
// Concrete implementation of the IEvaluatorClosure interface for script-defined functions. // Concrete implementation of the TAstValue.IClosure interface for script-defined functions.
TClosureValue = class(TInterfacedObject, IEvaluatorClosure) TClosureValue = class(TInterfacedObject, TAstValue.IClosure)
private private
FBody: IExpressionNode; FBody: IExpressionNode;
FClosureScope: IExecutionScope; FClosureScope: IExecutionScope;
@@ -97,15 +97,14 @@ type
function GetBody: IExpressionNode; function GetBody: IExpressionNode;
function GetParameters: TArray<IIdentifierNode>; function GetParameters: TArray<IIdentifierNode>;
function GetClosureScope: IExecutionScope; function GetClosureScope: IExecutionScope;
function GetLambdaNode: ILambdaExpressionNode;
public public
constructor Create(const ALambdaNode: ILambdaExpressionNode; const AClosureScope: IExecutionScope); constructor Create(const ALambdaNode: ILambdaExpressionNode; const AClosureScope: IExecutionScope);
property ClosureScope: IExecutionScope read GetClosureScope; property ClosureScope: IExecutionScope read GetClosureScope;
property LambdaNode: ILambdaExpressionNode read GetLambdaNode; property LambdaNode: ILambdaExpressionNode read FLambdaNode;
end; end;
// Concrete implementation of IEvaluatorClosure for native Delphi functions. // Concrete implementation of TAstValue.IClosure for native Delphi functions.
TNativeClosure = class(TInterfacedObject, IEvaluatorClosure) TNativeClosure = class(TInterfacedObject, TAstValue.IClosure)
private private
FMethod: TNativeFunction; FMethod: TNativeFunction;
function GetBody: IExpressionNode; function GetBody: IExpressionNode;
@@ -196,7 +195,7 @@ end;
procedure RegisterNativeFunctions(const AScope: IExecutionScope); procedure RegisterNativeFunctions(const AScope: IExecutionScope);
var var
closure: IEvaluatorClosure; closure: TAstValue.IClosure;
begin begin
// Use 'Define' to clearly state that we are adding a new variable // Use 'Define' to clearly state that we are adding a new variable
// to the global scope before the binder runs. // to the global scope before the binder runs.
@@ -224,11 +223,6 @@ begin
Result := FClosureScope; Result := FClosureScope;
end; end;
function TClosureValue.GetLambdaNode: ILambdaExpressionNode;
begin
Result := FLambdaNode;
end;
function TClosureValue.GetParameters: TArray<IIdentifierNode>; function TClosureValue.GetParameters: TArray<IIdentifierNode>;
begin begin
Result := FLambdaNode.Parameters; Result := FLambdaNode.Parameters;
@@ -294,7 +288,7 @@ var
lookback: Int64; lookback: Int64;
begin begin
// The target series must have been resolved by the binder. // The target series must have been resolved by the binder.
seriesVar := FScope[Node.Series.Resolve]; seriesVar := FScope[Node.Series.Address];
itemValue := Node.Value.Accept(Self); itemValue := Node.Value.Accept(Self);
lookback := -1; lookback := -1;
@@ -345,7 +339,7 @@ end;
function TEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue; function TEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TAstValue;
begin begin
Result := Node.Value.Accept(Self); Result := Node.Value.Accept(Self);
FScope[Node.Identifier.Resolve] := Result; FScope[Node.Identifier.Address] := Result;
end; end;
function TEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TAstValue; function TEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TAstValue;
@@ -378,7 +372,7 @@ end;
function TEvaluatorVisitor.VisitIdentifier(const Node: IIdentifierNode): TAstValue; function TEvaluatorVisitor.VisitIdentifier(const Node: IIdentifierNode): TAstValue;
begin begin
Result := FScope[Node.Resolve]; Result := FScope[Node.Address];
end; end;
function TEvaluatorVisitor.VisitIndexer(const Node: IIndexerNode): TAstValue; function TEvaluatorVisitor.VisitIndexer(const Node: IIndexerNode): TAstValue;
@@ -475,7 +469,7 @@ begin
else else
value := TAstValue.Void; value := TAstValue.Void;
FScope[Node.Identifier.Resolve] := value; FScope[Node.Identifier.Address] := value;
Result := value; Result := value;
end; end;
@@ -488,10 +482,8 @@ end;
function TEvaluatorVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue; function TEvaluatorVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TAstValue;
var var
calleeValue: TAstValue; calleeValue: TAstValue;
closure: IEvaluatorClosure; closure: TAstValue.IClosure;
i: Integer; i: Integer;
callScope: IExecutionScope;
descriptor: IScopeDescriptor;
begin begin
calleeValue := Node.Callee.Accept(Self); calleeValue := Node.Callee.Accept(Self);
@@ -514,20 +506,20 @@ begin
raise EArgumentException raise EArgumentException
.CreateFmt('Argument count mismatch: expected %d, got %d', [Length(closure.Parameters), Node.Arguments.Count]); .CreateFmt('Argument count mismatch: expected %d, got %d', [Length(closure.Parameters), Node.Arguments.Count]);
descriptor := (closure as TClosureValue).LambdaNode.ScopeDescriptor; var descriptor := (closure as TClosureValue).LambdaNode.ScopeDescriptor;
if not Assigned(descriptor) then if not Assigned(descriptor) then
raise EParserError.Create('Lambda has no scope descriptor. Did the binder run?'); raise EParserError.Create('Lambda has no scope descriptor. Did the binder run?');
var callScope := descriptor.CreateScope(closure.ClosureScope);
var adr: TResolvedAddress; var adr: TResolvedAddress;
adr.ScopeDepth := 0; adr.ScopeDepth := 0;
adr.SlotIndex := 0; adr.SlotIndex := 0;
callScope := descriptor.Instantiate(closure.ClosureScope);
callScope[adr] := calleeValue; callScope[adr] := calleeValue;
for i := 0 to Node.Arguments.Count - 1 do for i := 0 to Node.Arguments.Count - 1 do
begin begin
adr.SlotIndex := closure.Parameters[i].Resolve.SlotIndex; adr.SlotIndex := closure.Parameters[i].Address.SlotIndex;
callScope[adr] := Node.Arguments[i].Accept(Self); callScope[adr] := Node.Arguments[i].Accept(Self);
end; end;
+30 -37
View File
@@ -44,36 +44,30 @@ type
ICreateSeriesNode = interface; ICreateSeriesNode = interface;
IAddSeriesItemNode = interface; IAddSeriesItemNode = interface;
ISeriesLengthNode = interface; ISeriesLengthNode = interface;
IEvaluatorClosure = interface;
IExecutionScope = interface; IExecutionScope = interface;
IScopeDescriptor = interface; IScopeDescriptor = interface;
// --- Concrete Type Definitions --- // --- Concrete Type Definitions ---
TResolvedAddress = record
ScopeDepth: Integer;
SlotIndex: Integer;
end;
IEvaluatorClosure = interface
{$region 'private'}
function GetBody: IExpressionNode;
function GetClosureScope: IExecutionScope;
function GetParameters: TArray<IIdentifierNode>;
{$endregion}
property Body: IExpressionNode read GetBody;
property ClosureScope: IExecutionScope read GetClosureScope;
property Parameters: TArray<IIdentifierNode> read GetParameters;
end;
TAstValue = record TAstValue = record
private
type type
IClosure = interface
{$region 'private'}
function GetBody: IExpressionNode;
function GetClosureScope: IExecutionScope;
function GetParameters: TArray<IIdentifierNode>;
{$endregion}
property Body: IExpressionNode read GetBody;
property ClosureScope: IExecutionScope read GetClosureScope;
property Parameters: TArray<IIdentifierNode> read GetParameters;
end;
TVal<T> = class(TInterfacedObject) TVal<T> = class(TInterfacedObject)
Value: T; Value: T;
constructor Create(const AValue: T); constructor Create(const AValue: T);
end; end;
private
var var
FKind: TAstValueKind; FKind: TAstValueKind;
FScalar: TScalar; FScalar: TScalar;
@@ -84,14 +78,14 @@ type
class operator Initialize(out Dest: TAstValue); class operator Initialize(out Dest: TAstValue);
class function Void: TAstValue; inline; static; class function Void: TAstValue; inline; static;
class function FromScalar(const AValue: TScalar): TAstValue; inline; static; class function FromScalar(const AValue: TScalar): TAstValue; inline; static;
class function FromClosure(const AValue: IEvaluatorClosure): TAstValue; inline; static; class function FromClosure(const AValue: TAstValue.IClosure): TAstValue; inline; static;
class function FromText(const AValue: String): TAstValue; inline; static; class function FromText(const AValue: String): TAstValue; inline; static;
class function FromSeries(const [ref] AValue: TScalarSeries): TAstValue; static; inline; class function FromSeries(const [ref] AValue: TScalarSeries): TAstValue; static; inline;
class function FromRecordSeries(const [ref] AValue: TScalarRecordSeries): TAstValue; static; inline; class function FromRecordSeries(const [ref] AValue: TScalarRecordSeries): TAstValue; static; inline;
class function FromRecord(const [ref] AValue: TScalarRecord): TAstValue; static; inline; class function FromRecord(const [ref] AValue: TScalarRecord): TAstValue; static; inline;
class function FromMemberSeries(const [ref] AValue: TScalarMemberSeries): TAstValue; static; inline; class function FromMemberSeries(const [ref] AValue: TScalarMemberSeries): TAstValue; static; inline;
function AsScalar: TScalar; inline; function AsScalar: TScalar; inline;
function AsClosure: IEvaluatorClosure; inline; function AsClosure: TAstValue.IClosure; inline;
function AsText: String; inline; function AsText: String; inline;
function AsRecordSeries: TVal<TScalarRecordSeries>; inline; function AsRecordSeries: TVal<TScalarRecordSeries>; inline;
function AsRecord: TScalarRecord; inline; function AsRecord: TScalarRecord; inline;
@@ -102,25 +96,24 @@ type
property Kind: TAstValueKind read GetKind; property Kind: TAstValueKind read GetKind;
end; end;
TResolvedAddress = record
ScopeDepth: Integer;
SlotIndex: Integer;
end;
IExecutionScope = interface IExecutionScope = interface
{$region 'private'} {$region 'private'}
function GetParent: IExecutionScope; function GetParent: IExecutionScope;
function GetValues(const Address: TResolvedAddress): TAstValue; function GetValues(const Address: TResolvedAddress): TAstValue;
procedure SetValues(const Address: TResolvedAddress; const Value: TAstValue); procedure SetValues(const Address: TResolvedAddress; const Value: TAstValue);
{$endregion} {$endregion}
procedure Define(const Name: string; const Value: TAstValue);
function Dump: string;
procedure Clear; procedure Clear;
// --- Legacy name-based access ---
function FindValue(const Name: string; out Value: TAstValue): Boolean;
procedure SetValue(const Name: string; const Value: TAstValue);
procedure AssignValue(const Name: string; const Value: TAstValue); overload;
// --- New index-based access ---
procedure Define(const Name: string; const Value: TAstValue);
function Dump: string;
property Values[const Address: TResolvedAddress]: TAstValue read GetValues write SetValues; default; property Values[const Address: TResolvedAddress]: TAstValue read GetValues write SetValues; default;
property Parent: IExecutionScope read GetParent; property Parent: IExecutionScope read GetParent;
end; end;
@@ -130,8 +123,7 @@ type
function GetSlotCount: Integer; function GetSlotCount: Integer;
function GetSymbols: TDictionary<string, Integer>; function GetSymbols: TDictionary<string, Integer>;
{$endregion} {$endregion}
function Instantiate(const AParent: IExecutionScope): IExecutionScope; function CreateScope(const AParent: IExecutionScope): IExecutionScope;
procedure PopulateFromScope(const AScope: IExecutionScope);
property Parent: IScopeDescriptor read GetParent; property Parent: IScopeDescriptor read GetParent;
property SlotCount: Integer read GetSlotCount; property SlotCount: Integer read GetSlotCount;
property Symbols: TDictionary<string, Integer> read GetSymbols; property Symbols: TDictionary<string, Integer> read GetSymbols;
@@ -173,11 +165,12 @@ type
IIdentifierNode = interface(IExpressionNode) IIdentifierNode = interface(IExpressionNode)
{$region 'private'} {$region 'private'}
function GetIsResolved: Boolean; function GetIsResolved: Boolean;
function GetAddress: TResolvedAddress;
function GetName: string; function GetName: string;
{$endregion} {$endregion}
function Resolve: TResolvedAddress;
property IsResolved: Boolean read GetIsResolved;
property Name: string read GetName; property Name: string read GetName;
property IsResolved: Boolean read GetIsResolved;
property Address: TResolvedAddress read GetAddress;
end; end;
IBinaryExpressionNode = interface(IExpressionNode) IBinaryExpressionNode = interface(IExpressionNode)
@@ -337,11 +330,11 @@ begin
Dest.FInterface := nil; Dest.FInterface := nil;
end; end;
function TAstValue.AsClosure: IEvaluatorClosure; function TAstValue.AsClosure: TAstValue.IClosure;
begin begin
if (FKind <> avkClosure) then if (FKind <> avkClosure) then
raise EInvalidCast.Create('Cannot read value as a Closure.'); raise EInvalidCast.Create('Cannot read value as a Closure.');
Result := IEvaluatorClosure(FInterface); Result := TAstValue.IClosure(FInterface);
end; end;
function TAstValue.AsMemberSeries: TVal<TScalarMemberSeries>; function TAstValue.AsMemberSeries: TVal<TScalarMemberSeries>;
@@ -388,7 +381,7 @@ begin
Result := (FInterface as TVal<String>).Value; Result := (FInterface as TVal<String>).Value;
end; end;
class function TAstValue.FromClosure(const AValue: IEvaluatorClosure): TAstValue; class function TAstValue.FromClosure(const AValue: TAstValue.IClosure): TAstValue;
begin begin
Result.FKind := avkClosure; Result.FKind := avkClosure;
Result.FInterface := AValue; Result.FInterface := AValue;
+10 -57
View File
@@ -17,19 +17,13 @@ type
procedure DumpScope(const ABuilder: TStringBuilder; AIndent: Integer); procedure DumpScope(const ABuilder: TStringBuilder; AIndent: Integer);
function GetValues(const Address: TResolvedAddress): TAstValue; function GetValues(const Address: TResolvedAddress): TAstValue;
procedure SetValues(const Address: TResolvedAddress; const Value: TAstValue); procedure SetValues(const Address: TResolvedAddress; const Value: TAstValue);
protected
// IExecutionScope
function GetParent: IExecutionScope; function GetParent: IExecutionScope;
public
constructor Create(AParent: IExecutionScope = nil; const ADescriptor: IScopeDescriptor = nil);
destructor Destroy; override;
procedure Clear; procedure Clear;
function FindValue(const Name: string; out Value: TAstValue): Boolean;
procedure SetValue(const Name: string; const Value: TAstValue);
procedure AssignValue(const Name: string; const Value: TAstValue); overload;
function Dump: string; function Dump: string;
procedure Define(const Name: string; const Value: TAstValue); procedure Define(const Name: string; const Value: TAstValue);
public
constructor Create(AParent: IExecutionScope = nil);
constructor CreateFromDescriptor(const AParent: IExecutionScope; const ADescriptor: IScopeDescriptor);
destructor Destroy; override;
property NameToIndex: TDictionary<string, Integer> read FNameToIndex; property NameToIndex: TDictionary<string, Integer> read FNameToIndex;
property Parent: IExecutionScope read FParent; property Parent: IExecutionScope read FParent;
property Values: TArray<TAstValue> read FValues; property Values: TArray<TAstValue> read FValues;
@@ -42,20 +36,18 @@ uses
{ TExecutionScope } { TExecutionScope }
constructor TExecutionScope.Create(AParent: IExecutionScope); constructor TExecutionScope.Create(AParent: IExecutionScope = nil; const ADescriptor: IScopeDescriptor = nil);
begin begin
inherited Create; inherited Create;
FParent := AParent; FParent := AParent;
FValues := []; FValues := [];
FNameToIndex := TDictionary<string, Integer>.Create; FNameToIndex := TDictionary<string, Integer>.Create;
end; if ADescriptor <> nil then
begin
constructor TExecutionScope.CreateFromDescriptor(const AParent: IExecutionScope; const ADescriptor: IScopeDescriptor); for var item in ADescriptor.Symbols do
begin FNameToIndex.AddOrSetValue(item.Key, item.Value);
inherited Create; SetLength(FValues, ADescriptor.SlotCount);
FParent := AParent; end;
FNameToIndex := TDictionary<string, Integer>.Create(ADescriptor.Symbols);
SetLength(FValues, ADescriptor.SlotCount);
end; end;
destructor TExecutionScope.Destroy; destructor TExecutionScope.Destroy;
@@ -69,18 +61,6 @@ begin
Result := FParent; Result := FParent;
end; end;
procedure TExecutionScope.AssignValue(const Name: string; const Value: TAstValue);
var
index: Integer;
begin
if FNameToIndex.TryGetValue(Name, index) then
FValues[index] := Value
else if Assigned(FParent) then
FParent.AssignValue(Name, Value)
else
raise Exception.CreateFmt('Cannot assign to undeclared variable "%s".', [Name]);
end;
procedure TExecutionScope.Clear; procedure TExecutionScope.Clear;
begin begin
FValues := []; FValues := [];
@@ -145,23 +125,6 @@ begin
end; end;
end; end;
function TExecutionScope.FindValue(const Name: string; out Value: TAstValue): Boolean;
var
index: Integer;
begin
if FNameToIndex.TryGetValue(Name, index) then
begin
Value := FValues[index];
Result := True;
Exit;
end;
if Assigned(FParent) then
Result := FParent.FindValue(Name, Value)
else
Result := False;
end;
function TExecutionScope.GetValues(const Address: TResolvedAddress): TAstValue; function TExecutionScope.GetValues(const Address: TResolvedAddress): TAstValue;
var var
targetScope: TExecutionScope; targetScope: TExecutionScope;
@@ -179,16 +142,6 @@ begin
Result := targetScope.Values[Address.SlotIndex]; Result := targetScope.Values[Address.SlotIndex];
end; end;
procedure TExecutionScope.SetValue(const Name: string; const Value: TAstValue);
var
index: Integer;
begin
if FNameToIndex.TryGetValue(Name, index) then
FValues[index] := Value
else
Define(Name, Value);
end;
procedure TExecutionScope.SetValues(const Address: TResolvedAddress; const Value: TAstValue); procedure TExecutionScope.SetValues(const Address: TResolvedAddress; const Value: TAstValue);
var var
targetScope: IExecutionScope; targetScope: IExecutionScope;
+16 -25
View File
@@ -46,11 +46,8 @@ type
): IAddSeriesItemNode; static; ): IAddSeriesItemNode; static;
class function SeriesLength(const ASeries: IIdentifierNode): ISeriesLengthNode; static; class function SeriesLength(const ASeries: IIdentifierNode): ISeriesLengthNode; static;
// --- Static method to run the binder --- class function CreateDescriptor(const Scope: IExecutionScope): IScopeDescriptor; static;
// Traverses the AST and resolves all identifiers. Returns a scope descriptor for the script's top-level variables. class function Bind(const RootNode: IExpressionNode; const ParentScope: IExecutionScope): IExecutionScope; static;
class function CreateScopeDescriptor(const Parent: IScopeDescriptor): IScopeDescriptor; static;
class function CreateBinding(const Scope: IExecutionScope): IScopeDescriptor; static;
class function Bind(const RootNode: IExpressionNode; const ParentScope: IExecutionScope): IScopeDescriptor; static;
end; end;
// TAstTraverser provides a default AST traversal implementation. // TAstTraverser provides a default AST traversal implementation.
@@ -89,8 +86,9 @@ type
destructor Destroy; override; destructor Destroy; override;
function Define(const Name: string): Integer; function Define(const Name: string): Integer;
function FindSymbol(const Name: string; out Index: Integer; out Depth: Integer): Boolean; function FindSymbol(const Name: string; out Index: Integer; out Depth: Integer): Boolean;
function Instantiate(const AParent: IExecutionScope): IExecutionScope; function CreateScope(const AParent: IExecutionScope): IExecutionScope;
procedure PopulateFromScope(const AScope: IExecutionScope); procedure PopulateFromScope(const AScope: IExecutionScope);
property Symbols: TDictionary<string, Integer> read FSymbols;
end; end;
{ TAstNode } { TAstNode }
@@ -119,14 +117,15 @@ type
FResolvedAddress: TResolvedAddress; FResolvedAddress: TResolvedAddress;
function GetName: string; function GetName: string;
function GetIsResolved: Boolean; inline; function GetIsResolved: Boolean; inline;
function GetAddress: TResolvedAddress; inline;
public public
constructor Create(AName: string); constructor Create(AName: string);
function Accept(const Visitor: IAstVisitor): TAstValue; override; function Accept(const Visitor: IAstVisitor): TAstValue; override;
function Resolve: TResolvedAddress;
property IsResolved: Boolean read GetIsResolved; property IsResolved: Boolean read GetIsResolved;
property Name: string read FName; property Name: string read FName;
property Address: TResolvedAddress read GetAddress;
end; end;
{ TBinaryExpressionNode } { TBinaryExpressionNode }
@@ -371,16 +370,13 @@ begin
Result := FSymbols; Result := FSymbols;
end; end;
function TScopeDescriptor.Instantiate(const AParent: IExecutionScope): IExecutionScope; function TScopeDescriptor.CreateScope(const AParent: IExecutionScope): IExecutionScope;
begin begin
Result := TExecutionScope.CreateFromDescriptor(AParent, Self); Result := TExecutionScope.Create(AParent, Self);
end; end;
procedure TScopeDescriptor.PopulateFromScope(const AScope: IExecutionScope); procedure TScopeDescriptor.PopulateFromScope(const AScope: IExecutionScope);
begin begin
if not Assigned(AScope) then
Exit;
for var pair in (AScope as TExecutionScope).NameToIndex do for var pair in (AScope as TExecutionScope).NameToIndex do
if not FSymbols.ContainsKey(pair.Key) then if not FSymbols.ContainsKey(pair.Key) then
FSymbols.Add(pair.Key, pair.Value); FSymbols.Add(pair.Key, pair.Value);
@@ -429,7 +425,7 @@ begin
Result := FName; Result := FName;
end; end;
function TIdentifierNode.Resolve: TResolvedAddress; function TIdentifierNode.GetAddress: TResolvedAddress;
begin begin
if not IsResolved then if not IsResolved then
raise EInvalidOpException.Create('Identifier not bound'); raise EInvalidOpException.Create('Identifier not bound');
@@ -810,7 +806,7 @@ end;
constructor TBinder.Create(const AInitialScope: IExecutionScope); constructor TBinder.Create(const AInitialScope: IExecutionScope);
begin begin
inherited Create; inherited Create;
FCurrentDescriptor := TAst.CreateBinding(AInitialScope); FCurrentDescriptor := TAst.CreateDescriptor(AInitialScope);
end; end;
procedure TBinder.EnterScope; procedure TBinder.EnterScope;
@@ -869,7 +865,7 @@ end;
{ TAst } { TAst }
class function TAst.Bind(const RootNode: IExpressionNode; const ParentScope: IExecutionScope): IScopeDescriptor; class function TAst.Bind(const RootNode: IExpressionNode; const ParentScope: IExecutionScope): IExecutionScope;
var var
binder: TBinder; binder: TBinder;
visitor: IAstVisitor; visitor: IAstVisitor;
@@ -884,7 +880,7 @@ begin
// Traverse the AST. This annotates all nodes AND populates the new descriptor. // Traverse the AST. This annotates all nodes AND populates the new descriptor.
RootNode.Accept(visitor); RootNode.Accept(visitor);
// Return the completed descriptor for the script's scope. // Return the completed descriptor for the script's scope.
Result := binder.FCurrentDescriptor; Result := binder.CurrentDescriptor.CreateScope(ParentScope);
finally finally
// Restore the binder's internal state. // Restore the binder's internal state.
binder.ExitScope; binder.ExitScope;
@@ -930,22 +926,17 @@ begin
Result := TBinaryExpressionNode.Create(ALeft, AOperator, ARight); Result := TBinaryExpressionNode.Create(ALeft, AOperator, ARight);
end; end;
class function TAst.CreateBinding(const Scope: IExecutionScope): IScopeDescriptor; class function TAst.CreateDescriptor(const Scope: IExecutionScope): IScopeDescriptor;
begin begin
if Assigned(Scope) then if Scope is TExecutionScope then
begin begin
Result := CreateScopeDescriptor(CreateBinding(Scope.Parent)); Result := TScopeDescriptor.Create(CreateDescriptor(Scope.Parent));
Result.PopulateFromScope(Scope); (Result as TScopeDescriptor).PopulateFromScope(Scope);
end end
else else
Result := TScopeDescriptor.Create(nil); Result := TScopeDescriptor.Create(nil);
end; end;
class function TAst.CreateScopeDescriptor(const Parent: IScopeDescriptor): IScopeDescriptor;
begin
Result := TScopeDescriptor.Create(Parent);
end;
class function TAst.FunctionCall(const ACallee: IExpressionNode; const AArguments: array of IExpressionNode): IFunctionCallNode; class function TAst.FunctionCall(const ACallee: IExpressionNode; const AArguments: array of IExpressionNode): IFunctionCallNode;
begin begin
Result := TFunctionCallNode.Create(ACallee, AArguments); Result := TFunctionCallNode.Create(ACallee, AArguments);