519 lines
17 KiB
ObjectPascal
519 lines
17 KiB
ObjectPascal
unit Myc.Ast.Scope;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.SysUtils,
|
|
System.Generics.Collections,
|
|
System.Classes,
|
|
Myc.Data.Value,
|
|
Myc.Ast.Nodes;
|
|
|
|
type
|
|
IExecutionScope = interface;
|
|
IScopeDescriptor = interface;
|
|
|
|
IValueCell = interface
|
|
{$region 'private'}
|
|
function GetValue: TDataValue;
|
|
procedure SetValue(const AValue: TDataValue);
|
|
{$endregion}
|
|
property Value: TDataValue read GetValue write SetValue;
|
|
end;
|
|
|
|
IExecutionScope = interface
|
|
{$region 'private'}
|
|
function GetParent: IExecutionScope;
|
|
function GetValues(const Address: TResolvedAddress): TDataValue;
|
|
procedure SetValues(const Address: TResolvedAddress; const Value: TDataValue);
|
|
{$endregion}
|
|
|
|
procedure Define(const Name: string; const Value: TDataValue);
|
|
function Dump: string;
|
|
procedure Clear;
|
|
function Capture(const Address: TResolvedAddress): IValueCell;
|
|
|
|
function CreateDescriptor: IScopeDescriptor;
|
|
|
|
property Values[const Address: TResolvedAddress]: TDataValue read GetValues write SetValues; default;
|
|
property Parent: IExecutionScope read GetParent;
|
|
end;
|
|
|
|
// Describes the layout of a scope: variable names and their slot indices.
|
|
// This is generated by the binder and used to create TExecutionScope instances at runtime.
|
|
IScopeDescriptor = interface
|
|
{$region 'private'}
|
|
function GetParent: IScopeDescriptor;
|
|
function GetSlotCount: Integer;
|
|
function GetSymbols: TDictionary<string, Integer>;
|
|
{$endregion}
|
|
function CreateScope(const AParent: IExecutionScope): IExecutionScope;
|
|
function Define(const Name: string): Integer;
|
|
function FindSymbol(const Name: string): TResolvedAddress;
|
|
property Parent: IScopeDescriptor read GetParent;
|
|
property SlotCount: Integer read GetSlotCount;
|
|
property Symbols: TDictionary<string, Integer> read GetSymbols;
|
|
end;
|
|
|
|
TScope = record
|
|
class function CreateScope(
|
|
const Parent: IExecutionScope;
|
|
const Descriptor: IScopeDescriptor;
|
|
const CapturedUpvalues: TArray<IValueCell>
|
|
): IExecutionScope; static;
|
|
class function CreateDescriptor(const Parent: IScopeDescriptor): IScopeDescriptor; static;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.Generics.Defaults;
|
|
|
|
type
|
|
TExecutionScope = class(TInterfacedObject, IExecutionScope)
|
|
type
|
|
TValueCell = class(TInterfacedObject, IValueCell)
|
|
private
|
|
FValue: TDataValue;
|
|
function GetValue: TDataValue;
|
|
procedure SetValue(const AValue: TDataValue);
|
|
public
|
|
constructor Create(const AValue: TDataValue);
|
|
end;
|
|
|
|
// Corrected TValueRef: Holds a stable reference to the scope and address, not the raw array.
|
|
TValueRef = class(TInterfacedObject, IValueCell)
|
|
private
|
|
FScope: IExecutionScope;
|
|
FAddress: TResolvedAddress;
|
|
function GetValue: TDataValue;
|
|
procedure SetValue(const AValue: TDataValue);
|
|
public
|
|
constructor Create(const AScope: IExecutionScope; const AAddress: TResolvedAddress);
|
|
end;
|
|
|
|
private
|
|
FParent: IExecutionScope;
|
|
FDescriptor: IScopeDescriptor;
|
|
FValues: TArray<TDataValue>;
|
|
FCapturedUpvalues: TArray<IValueCell>;
|
|
FNames: TDictionary<string, Integer>;
|
|
FNameStrings: TList<string>;
|
|
FNameToIndex: TDictionary<Integer, Integer>;
|
|
|
|
procedure DumpScope(const ABuilder: TStringBuilder; AIndent: Integer);
|
|
procedure NeedNameToIndex;
|
|
function GetNameID(const Name: String): Integer;
|
|
function GetParent: IExecutionScope;
|
|
function GetNameToIndex: TDictionary<Integer, Integer>;
|
|
function GetValues(const Address: TResolvedAddress): TDataValue;
|
|
procedure SetValues(const Address: TResolvedAddress; const Value: TDataValue);
|
|
public
|
|
constructor Create(AParent: IExecutionScope; const ADescriptor: IScopeDescriptor; const ACapturedUpvalues: TArray<IValueCell>);
|
|
destructor Destroy; override;
|
|
procedure Clear;
|
|
function Dump: string;
|
|
procedure Define(const Name: string; const Value: TDataValue);
|
|
function Capture(const Address: TResolvedAddress): IValueCell;
|
|
function CreateDescriptor: IScopeDescriptor;
|
|
property Names: TDictionary<string, Integer> read FNames;
|
|
property NameStrings: TList<string> read FNameStrings;
|
|
property NameToIndex: TDictionary<Integer, Integer> read GetNameToIndex;
|
|
property Parent: IExecutionScope read FParent;
|
|
end;
|
|
|
|
TScopeDescriptor = class(TInterfacedObject, IScopeDescriptor)
|
|
private
|
|
FParent: IScopeDescriptor;
|
|
FSymbols: TDictionary<string, Integer>;
|
|
function GetParent: IScopeDescriptor;
|
|
function GetSlotCount: Integer;
|
|
function GetSymbols: TDictionary<string, Integer>;
|
|
public
|
|
constructor Create(const AParent: IScopeDescriptor);
|
|
destructor Destroy; override;
|
|
class function CreateDescriptor(const Scope: IExecutionScope): IScopeDescriptor; static;
|
|
function Define(const Name: string): Integer;
|
|
function FindSymbol(const Name: string): TResolvedAddress;
|
|
function CreateScope(const Parent: IExecutionScope): IExecutionScope;
|
|
procedure PopulateFromScope(Scope: TExecutionScope);
|
|
property Symbols: TDictionary<string, Integer> read FSymbols;
|
|
end;
|
|
|
|
{ TExecutionScope.TValueCell }
|
|
|
|
constructor TExecutionScope.TValueCell.Create(const AValue: TDataValue);
|
|
begin
|
|
inherited Create;
|
|
FValue := AValue;
|
|
end;
|
|
|
|
function TExecutionScope.TValueCell.GetValue: TDataValue;
|
|
begin
|
|
Result := FValue;
|
|
end;
|
|
|
|
procedure TExecutionScope.TValueCell.SetValue(const AValue: TDataValue);
|
|
begin
|
|
FValue := AValue;
|
|
end;
|
|
|
|
{ TExecutionScope.TValueRef }
|
|
|
|
constructor TExecutionScope.TValueRef.Create(const AScope: IExecutionScope; const AAddress: TResolvedAddress);
|
|
begin
|
|
inherited Create;
|
|
FScope := AScope;
|
|
FAddress := AAddress;
|
|
end;
|
|
|
|
function TExecutionScope.TValueRef.GetValue: TDataValue;
|
|
begin
|
|
// Delegate the call to the scope, which correctly handles parent traversal.
|
|
Result := FScope[FAddress];
|
|
end;
|
|
|
|
procedure TExecutionScope.TValueRef.SetValue(const AValue: TDataValue);
|
|
begin
|
|
// Delegate the call to the scope.
|
|
FScope[FAddress] := AValue;
|
|
end;
|
|
|
|
{ TExecutionScope }
|
|
|
|
constructor TExecutionScope.Create(
|
|
AParent: IExecutionScope;
|
|
const ADescriptor: IScopeDescriptor;
|
|
const ACapturedUpvalues: TArray<IValueCell>
|
|
);
|
|
begin
|
|
inherited Create;
|
|
FParent := AParent;
|
|
FDescriptor := ADescriptor;
|
|
|
|
FValues := [];
|
|
FCapturedUpvalues := ACapturedUpvalues; // Store upvalues
|
|
|
|
if FParent is TExecutionScope then
|
|
begin
|
|
FNames := (FParent as TExecutionScope).FNames;
|
|
FNameStrings := (FParent as TExecutionScope).FNameStrings;
|
|
end
|
|
else
|
|
begin
|
|
FNames := TDictionary<string, Integer>.Create;
|
|
FNameStrings := TList<string>.Create;
|
|
end;
|
|
|
|
if ADescriptor <> nil then
|
|
SetLength(FValues, ADescriptor.SlotCount);
|
|
end;
|
|
|
|
destructor TExecutionScope.Destroy;
|
|
begin
|
|
Clear;
|
|
if not (FParent is TExecutionScope) then
|
|
begin
|
|
FNameStrings.Free;
|
|
FNames.Free;
|
|
end;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TExecutionScope.GetParent: IExecutionScope;
|
|
begin
|
|
Result := FParent;
|
|
end;
|
|
|
|
procedure TExecutionScope.Clear;
|
|
begin
|
|
FValues := [];
|
|
FreeAndNil(FNameToIndex);
|
|
end;
|
|
|
|
function TExecutionScope.Capture(const Address: TResolvedAddress): IValueCell;
|
|
begin
|
|
case Address.Kind of
|
|
akUpvalue:
|
|
begin
|
|
Assert(Assigned(FCapturedUpvalues), 'Attempt to access an upvalue in a scope with no closure context.');
|
|
Assert(
|
|
(Address.SlotIndex >= 0) and (Address.SlotIndex < Length(FCapturedUpvalues)),
|
|
'Invalid upvalue index during value retrieval.'
|
|
);
|
|
Result := FCapturedUpvalues[Address.SlotIndex];
|
|
end;
|
|
akLocalOrParent:
|
|
begin
|
|
// Corrected Implementation: Create a TValueRef that holds the current scope
|
|
// and the full address. The scope's indexer will handle the parent traversal correctly
|
|
// and is robust against array reallocations.
|
|
Result := TValueRef.Create(Self, Address);
|
|
end;
|
|
else
|
|
raise EInvalidOpException.Create('Cannot get value for an unresolved address.');
|
|
end;
|
|
end;
|
|
|
|
function TExecutionScope.CreateDescriptor: IScopeDescriptor;
|
|
begin
|
|
Result := TScopeDescriptor.CreateDescriptor(Self);
|
|
end;
|
|
|
|
procedure TExecutionScope.Define(const Name: string; const Value: TDataValue);
|
|
var
|
|
id: Integer;
|
|
index: Integer;
|
|
begin
|
|
NeedNameToIndex;
|
|
|
|
id := GetNameID(Name);
|
|
|
|
if FNameToIndex.ContainsKey(id) 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(id, index);
|
|
end;
|
|
|
|
procedure TExecutionScope.DumpScope(const ABuilder: TStringBuilder; AIndent: Integer);
|
|
var
|
|
pair: TPair<Integer, Integer>;
|
|
indentStr: string;
|
|
sortedPairs: TArray<TPair<Integer, Integer>>;
|
|
begin
|
|
NeedNameToIndex;
|
|
|
|
indentStr := ''.PadLeft(AIndent);
|
|
if FNameToIndex.Count > 0 then
|
|
begin
|
|
sortedPairs := FNameToIndex.ToArray;
|
|
TArray.Sort<TPair<Integer, Integer>>(
|
|
sortedPairs,
|
|
TComparer<TPair<Integer, Integer>>
|
|
.Construct(function(const Left, Right: TPair<Integer, Integer>): Integer begin Result := Left.Value - Right.Value; end)
|
|
);
|
|
|
|
for pair in sortedPairs do
|
|
// Access the value inside the cell for printing.
|
|
ABuilder.AppendLine(indentStr + Format(' [%d] %s: %s', [pair.Value, FNameStrings[pair.Key], FValues[pair.Value].ToString]));
|
|
end
|
|
else
|
|
begin
|
|
ABuilder.AppendLine(indentStr + ' (empty)');
|
|
end;
|
|
|
|
if Assigned(FParent) then
|
|
begin
|
|
ABuilder.AppendLine(indentStr + '[Parent Scope]');
|
|
(FParent as TExecutionScope).DumpScope(ABuilder, AIndent + 2);
|
|
end;
|
|
end;
|
|
|
|
function TExecutionScope.Dump: string;
|
|
var
|
|
builder: TStringBuilder;
|
|
begin
|
|
builder := TStringBuilder.Create;
|
|
try
|
|
builder.AppendLine('[Current Scope]');
|
|
DumpScope(builder, 0);
|
|
Result := builder.ToString.TrimRight;
|
|
finally
|
|
builder.Free;
|
|
end;
|
|
end;
|
|
|
|
function TExecutionScope.GetNameToIndex: TDictionary<Integer, Integer>;
|
|
begin
|
|
NeedNameToIndex;
|
|
Result := FNameToIndex;
|
|
end;
|
|
|
|
function TExecutionScope.GetValues(const Address: TResolvedAddress): TDataValue;
|
|
begin
|
|
case Address.Kind of
|
|
akUpvalue:
|
|
begin
|
|
Assert(Assigned(FCapturedUpvalues), 'Attempt to access an upvalue in a scope with no closure context.');
|
|
Assert(
|
|
(Address.SlotIndex >= 0) and (Address.SlotIndex < Length(FCapturedUpvalues)),
|
|
'Invalid upvalue index during value retrieval.'
|
|
);
|
|
Result := FCapturedUpvalues[Address.SlotIndex].Value;
|
|
end;
|
|
akLocalOrParent:
|
|
begin
|
|
var targetScope := Self;
|
|
for var i := 1 to Address.ScopeDepth do
|
|
begin
|
|
targetScope := targetScope.Parent as TExecutionScope;
|
|
Assert(Assigned(targetScope), 'Invalid scope depth during value retrieval.');
|
|
end;
|
|
Assert(
|
|
(Address.SlotIndex >= 0) and (Address.SlotIndex < Length(targetScope.FValues)),
|
|
'Invalid scope index during value retrieval.'
|
|
);
|
|
Result := targetScope.FValues[Address.SlotIndex];
|
|
end;
|
|
else
|
|
raise EInvalidOpException.Create('Cannot get value for an unresolved address.');
|
|
end;
|
|
end;
|
|
|
|
function TExecutionScope.GetNameID(const Name: String): Integer;
|
|
begin
|
|
if not FNames.TryGetValue(Name, Result) then
|
|
begin
|
|
Result := FNameStrings.Add(Name);
|
|
FNames.Add(Name, Result);
|
|
end;
|
|
end;
|
|
|
|
procedure TExecutionScope.NeedNameToIndex;
|
|
begin
|
|
if FNameToIndex = nil then
|
|
begin
|
|
FNameToIndex := TDictionary<Integer, Integer>.Create;
|
|
if FDescriptor <> nil then
|
|
begin
|
|
for var item in FDescriptor.Symbols do
|
|
FNameToIndex.AddOrSetValue(GetNameId(item.Key), item.Value);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TExecutionScope.SetValues(const Address: TResolvedAddress; const Value: TDataValue);
|
|
begin
|
|
case Address.Kind of
|
|
akUpvalue:
|
|
begin
|
|
Assert(Assigned(FCapturedUpvalues), 'Attempt to access an upvalue in a scope with no closure context.');
|
|
Assert(
|
|
(Address.SlotIndex >= 0) and (Address.SlotIndex < Length(FCapturedUpvalues)),
|
|
'Invalid upvalue index during value retrieval.'
|
|
);
|
|
FCapturedUpvalues[Address.SlotIndex].Value := Value;
|
|
end;
|
|
akLocalOrParent:
|
|
begin
|
|
var targetScope := Self;
|
|
for var i := 1 to Address.ScopeDepth do
|
|
begin
|
|
targetScope := targetScope.Parent as TExecutionScope;
|
|
Assert(Assigned(targetScope), 'Invalid scope depth during value retrieval.');
|
|
end;
|
|
Assert(
|
|
(Address.SlotIndex >= 0) and (Address.SlotIndex < Length(targetScope.FValues)),
|
|
'Invalid scope index during value retrieval.'
|
|
);
|
|
targetScope.FValues[Address.SlotIndex] := Value;
|
|
end;
|
|
else
|
|
raise EInvalidOpException.Create('Cannot get value for an unresolved address.');
|
|
end;
|
|
end;
|
|
|
|
{ TScopeDescriptor }
|
|
|
|
constructor TScopeDescriptor.Create(const AParent: IScopeDescriptor);
|
|
begin
|
|
inherited Create;
|
|
FParent := AParent;
|
|
FSymbols := TDictionary<string, Integer>.Create;
|
|
end;
|
|
|
|
destructor TScopeDescriptor.Destroy;
|
|
begin
|
|
FSymbols.Free;
|
|
inherited;
|
|
end;
|
|
|
|
class function TScopeDescriptor.CreateDescriptor(const Scope: IExecutionScope): IScopeDescriptor;
|
|
begin
|
|
if Scope is TExecutionScope then
|
|
begin
|
|
var res := TScopeDescriptor.Create(CreateDescriptor(Scope.Parent));
|
|
res.PopulateFromScope(Scope as TExecutionScope);
|
|
Result := res;
|
|
end
|
|
else
|
|
Result := TScopeDescriptor.Create(nil);
|
|
end;
|
|
|
|
function TScopeDescriptor.CreateScope(const Parent: IExecutionScope): IExecutionScope;
|
|
begin
|
|
// Creates a runtime scope instance based on this descriptor's layout.
|
|
Result := TExecutionScope.Create(Parent, Self, nil);
|
|
end;
|
|
|
|
function TScopeDescriptor.Define(const Name: string): Integer;
|
|
begin
|
|
Result := FSymbols.Count;
|
|
FSymbols.Add(Name, Result);
|
|
end;
|
|
|
|
function TScopeDescriptor.FindSymbol(const Name: string): TResolvedAddress;
|
|
var
|
|
currentDescriptor: TScopeDescriptor;
|
|
begin
|
|
Result.Kind := akUnresolved;
|
|
Result.ScopeDepth := 0;
|
|
currentDescriptor := Self;
|
|
while currentDescriptor <> nil do
|
|
begin
|
|
if currentDescriptor.FSymbols.TryGetValue(Name, Result.SlotIndex) then
|
|
begin
|
|
Result.Kind := akLocalOrParent;
|
|
exit;
|
|
end;
|
|
inc(Result.ScopeDepth);
|
|
currentDescriptor := currentDescriptor.FParent as TScopeDescriptor;
|
|
end;
|
|
end;
|
|
|
|
function TScopeDescriptor.GetParent: IScopeDescriptor;
|
|
begin
|
|
Result := FParent;
|
|
end;
|
|
|
|
function TScopeDescriptor.GetSlotCount: Integer;
|
|
begin
|
|
Result := FSymbols.Count;
|
|
end;
|
|
|
|
function TScopeDescriptor.GetSymbols: TDictionary<string, Integer>;
|
|
begin
|
|
Result := FSymbols;
|
|
end;
|
|
|
|
procedure TScopeDescriptor.PopulateFromScope(Scope: TExecutionScope);
|
|
begin
|
|
// This method is likely used to create a descriptor from an existing, dynamically populated scope.
|
|
// Note: This relies on internal details of TExecutionScope (NameToIndex, NameStrings).
|
|
for var pair in Scope.NameToIndex do
|
|
begin
|
|
var name := Scope.NameStrings[pair.Key];
|
|
if not FSymbols.ContainsKey(name) then
|
|
FSymbols.Add(name, pair.Value);
|
|
end;
|
|
end;
|
|
|
|
class function TScope.CreateDescriptor(const Parent: IScopeDescriptor): IScopeDescriptor;
|
|
begin
|
|
Result := TScopeDescriptor.Create(Parent);
|
|
end;
|
|
|
|
class function TScope.CreateScope(
|
|
const Parent: IExecutionScope;
|
|
const Descriptor: IScopeDescriptor;
|
|
const CapturedUpvalues: TArray<IValueCell>
|
|
): IExecutionScope;
|
|
begin
|
|
Result := TExecutionScope.Create(Parent, Descriptor, CapturedUpvalues);
|
|
end;
|
|
|
|
end.
|