Ast Binding

This commit is contained in:
Michael Schimmel
2025-09-04 01:41:09 +02:00
parent de052cab64
commit 9c90a92b04
6 changed files with 394 additions and 382 deletions
+105 -20
View File
@@ -12,7 +12,8 @@ type
TExecutionScope = class(TInterfacedObject, IExecutionScope)
private
FParent: IExecutionScope;
FVariables: TDictionary<string, TAstValue>;
FValues: TArray<TAstValue>;
FNameToIndex: TDictionary<string, Integer>;
procedure DumpScope(const ABuilder: TStringBuilder; AIndent: Integer);
protected
// IExecutionScope
@@ -20,27 +21,37 @@ type
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);
procedure AssignValue(const Name: string; const Value: TAstValue); overload;
function Dump: string;
// --- New index-based access methods ---
procedure Define(const Name: string; const Value: TAstValue);
function GetValue(Depth, Index: Integer): TAstValue;
procedure AssignValue(Depth, Index: Integer; const Value: TAstValue); overload;
public
constructor Create(AParent: IExecutionScope = nil);
destructor Destroy; override;
property NameToIndex: TDictionary<string, Integer> read FNameToIndex;
end;
implementation
uses
System.Generics.Defaults; // For TComparer
{ TExecutionScope }
constructor TExecutionScope.Create(AParent: IExecutionScope);
begin
inherited Create;
FParent := AParent;
FVariables := TDictionary<string, TAstValue>.Create;
FValues := [];
FNameToIndex := TDictionary<string, Integer>.Create;
end;
destructor TExecutionScope.Destroy;
begin
FVariables.Free;
FNameToIndex.Free;
inherited Destroy;
end;
@@ -50,32 +61,75 @@ begin
end;
procedure TExecutionScope.AssignValue(const Name: string; const Value: TAstValue);
var
index: Integer;
begin
if FVariables.ContainsKey(Name) then
FVariables.AddOrSetValue(Name, Value)
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.AssignValue(Depth, Index: Integer; const Value: TAstValue);
var
targetScope: IExecutionScope;
i: Integer;
begin
targetScope := Self;
for i := 1 to Depth do
begin
if Assigned(targetScope) then
targetScope := targetScope.Parent
else
// This should not happen if the binder works correctly.
raise EInvalidOpException.Create('Invalid scope depth during assignment.');
end;
// We must cast back to the implementation to modify the private array.
(targetScope as TExecutionScope).FValues[Index] := Value;
end;
procedure TExecutionScope.Clear;
begin
FVariables.Clear;
if Assigned(FParent) then
FParent.Clear;
FValues := [];
FNameToIndex.Clear;
end;
procedure TExecutionScope.Define(const Name: string; const Value: TAstValue);
var
index: Integer;
begin
// A variable can only be defined once per scope.
if FNameToIndex.ContainsKey(Name) then
raise Exception.CreateFmt('Variable "%s" is already defined in this scope.', [Name]);
index := Length(FValues);
SetLength(FValues, index + 1);
FValues[index] := Value;
FNameToIndex.Add(Name, index);
end;
procedure TExecutionScope.DumpScope(const ABuilder: TStringBuilder; AIndent: Integer);
var
pair: TPair<string, TAstValue>;
pair: TPair<string, Integer>;
indentStr: string;
sortedPairs: TArray<TPair<string, Integer>>;
begin
indentStr := ''.PadLeft(AIndent);
if (FVariables.Count > 0) then
if (FNameToIndex.Count > 0) then
begin
for pair in FVariables do
ABuilder.AppendLine(indentStr + Format(' %s: %s', [pair.Key, pair.Value.ToString]));
// Copy pairs to an array and sort it by index for consistent output
sortedPairs := FNameToIndex.ToArray;
TArray.Sort<TPair<string, Integer>>(
sortedPairs,
TComparer<TPair<string, Integer>>
.Construct(function(const Left, Right: TPair<string, Integer>): Integer begin Result := Left.Value - Right.Value; end)
);
for pair in sortedPairs do
ABuilder.AppendLine(indentStr + Format(' [%d] %s: %s', [pair.Value, pair.Key, FValues[pair.Value].ToString]));
end
else
begin
@@ -85,9 +139,7 @@ begin
if Assigned(FParent) then
begin
ABuilder.AppendLine(indentStr + '[Parent Scope]');
// As FParent is now an interface, we must cast it back to the class to call the private DumpScope.
// This indicates that DumpScope should perhaps be part of the interface or handled differently.
// For now, we use a cast to keep the functionality.
// This cast is necessary for this debug helper to access implementation details.
(FParent as TExecutionScope).DumpScope(ABuilder, AIndent + 2);
end;
end;
@@ -107,17 +159,50 @@ begin
end;
function TExecutionScope.FindValue(const Name: string; out Value: TAstValue): Boolean;
var
index: Integer;
begin
Result := FVariables.TryGetValue(Name, Value);
if not Result and Assigned(FParent) then
if FNameToIndex.TryGetValue(Name, index) then
begin
Result := FParent.FindValue(Name, Value);
Value := FValues[index];
Result := True;
Exit;
end;
if Assigned(FParent) then
Result := FParent.FindValue(Name, Value)
else
Result := False;
end;
function TExecutionScope.GetValue(Depth, Index: Integer): TAstValue;
var
targetScope: IExecutionScope;
i: Integer;
begin
targetScope := Self;
for i := 1 to Depth do
begin
if Assigned(targetScope) then
targetScope := targetScope.Parent
else
// This should not happen if the binder works correctly.
raise EInvalidOpException.Create('Invalid scope depth during value retrieval.');
end;
// We must cast back to the implementation to access the private array.
Result := (targetScope as TExecutionScope).FValues[Index];
end;
procedure TExecutionScope.SetValue(const Name: string; const Value: TAstValue);
var
index: Integer;
begin
FVariables.AddOrSetValue(Name, Value);
// This method is for pre-binder setup (e.g. native functions) or initial definition.
if FNameToIndex.TryGetValue(Name, index) then
FValues[index] := Value
else
Define(Name, Value);
end;
end.