Files
MycLib/Src/AST/Myc.Ast.Scope.pas
T
2025-09-15 12:12:23 +02:00

291 lines
9.1 KiB
ObjectPascal

unit Myc.Ast.Scope;
interface
uses
System.SysUtils,
System.Generics.Collections,
System.Classes,
Myc.Data.Value,
Myc.Ast.Nodes;
type
TExecutionScope = class(TInterfacedObject, IExecutionScope)
type
TValueCell = class(TInterfacedObject, IValueCell)
private
FValue: TDataValue;
function GetValue: TDataValue; inline;
procedure SetValue(const AValue: TDataValue); inline;
public
constructor Create(const AValue: TDataValue);
end;
private
FParent: IExecutionScope;
FDescriptor: IScopeDescriptor;
FValues: TArray<TDataValue>;
FCapturedUpvalues: TArray<IValueCell>;
FNameToIndex: TDictionary<string, Integer>;
procedure DumpScope(const ABuilder: TStringBuilder; AIndent: Integer);
procedure NeedNameToIndex;
function GetParent: IExecutionScope;
function GetNameToIndex: TDictionary<string, Integer>;
function GetValues(const Address: TResolvedAddress): TDataValue;
procedure SetValues(const Address: TResolvedAddress; const Value: TDataValue);
public
constructor Create(
AParent: IExecutionScope = nil;
const ADescriptor: IScopeDescriptor = nil;
const ACapturedUpvalues: TArray<IValueCell> = nil
);
destructor Destroy; override;
procedure Clear;
function Dump: string;
procedure Define(const Name: string; const Value: TDataValue);
function Capture(const Address: TResolvedAddress): IValueCell;
property NameToIndex: TDictionary<string, Integer> read GetNameToIndex;
property Parent: IExecutionScope read FParent;
end;
implementation
uses
System.Generics.Defaults;
{ 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 }
constructor TExecutionScope.Create(
AParent: IExecutionScope;
const ADescriptor: IScopeDescriptor;
const ACapturedUpvalues: TArray<IValueCell>
);
var
slotCount: Integer;
i: Integer;
begin
inherited Create;
FParent := AParent;
FDescriptor := ADescriptor;
FValues := [];
FCapturedUpvalues := ACapturedUpvalues; // Store upvalues
if ADescriptor <> nil then
begin
slotCount := ADescriptor.SlotCount;
SetLength(FValues, slotCount);
end;
end;
destructor TExecutionScope.Destroy;
begin
Clear;
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
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 := TValueCell.Create(targetScope.FValues[Address.SlotIndex]);
end;
else
raise EInvalidOpException.Create('Cannot get value for an unresolved address.');
end;
end;
procedure TExecutionScope.Define(const Name: string; const Value: TDataValue);
var
index: Integer;
begin
NeedNameToIndex;
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, Integer>;
indentStr: string;
sortedPairs: TArray<TPair<string, Integer>>;
begin
NeedNameToIndex;
indentStr := ''.PadLeft(AIndent);
if FNameToIndex.Count > 0 then
begin
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
// Access the value inside the cell for printing.
ABuilder.AppendLine(indentStr + Format(' [%d] %s: %s', [pair.Value, 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<string, 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;
procedure TExecutionScope.NeedNameToIndex;
begin
if FNameToIndex = nil then
begin
FNameToIndex := TDictionary<string, Integer>.Create;
if FDescriptor <> nil then
begin
for var item in FDescriptor.Symbols do
FNameToIndex.AddOrSetValue(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;
end.