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); // Defines a variable at a given address and stores it directly in a shared cell (boxing). procedure DefineBoxed(const Address: TResolvedAddress; 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; {$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 read GetSymbols; end; TScope = record class function CreateScope( const Parent: IExecutionScope; const Descriptor: IScopeDescriptor; const CapturedUpvalues: TArray ): IExecutionScope; static; class function CreateDescriptor(const Parent: IScopeDescriptor): IScopeDescriptor; static; end; implementation uses System.Generics.Defaults, System.SyncObjs; type TExecutionScope = class(TInterfacedObject, IExecutionScope) private type TValueCell = class(TInterfacedObject, IValueCell) private FValue: TDataValue; function GetValue: TDataValue; procedure SetValue(const AValue: TDataValue); public constructor Create(const AValue: TDataValue); end; TScopeItem = record Value: TDataValue; IsBoxed: Boolean; end; private FParent: IExecutionScope; FDescriptor: IScopeDescriptor; FValues: TArray; FCapturedUpvalues: TArray; FNames: TDictionary; FNameStrings: TList; FNameToIndex: TDictionary; procedure DumpScope(const ABuilder: TStringBuilder; AIndent: Integer); procedure NeedNameToIndex; function GetNameID(const Name: String): Integer; function GetParent: IExecutionScope; function GetNameToIndex: TDictionary; 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); destructor Destroy; override; procedure Clear; function Dump: string; procedure Define(const Name: string; const Value: TDataValue); procedure DefineBoxed(const Address: TResolvedAddress; const Value: TDataValue); function Capture(const Address: TResolvedAddress): IValueCell; function CreateDescriptor: IScopeDescriptor; property Names: TDictionary read FNames; property NameStrings: TList read FNameStrings; property NameToIndex: TDictionary read GetNameToIndex; property Parent: IExecutionScope read FParent; end; TScopeDescriptor = class(TInterfacedObject, IScopeDescriptor) private FParent: IScopeDescriptor; FSymbols: TDictionary; function GetParent: IScopeDescriptor; function GetSlotCount: Integer; function GetSymbols: TDictionary; 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 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 } constructor TExecutionScope.Create( AParent: IExecutionScope; const ADescriptor: IScopeDescriptor; const ACapturedUpvalues: TArray ); begin inherited Create; FParent := AParent; FDescriptor := ADescriptor; FValues := []; FCapturedUpvalues := ACapturedUpvalues; if FParent is TExecutionScope then begin FNames := (FParent as TExecutionScope).FNames; FNameStrings := (FParent as TExecutionScope).FNameStrings; end else begin FNames := TDictionary.Create; FNameStrings := TList.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; var targetScope: TExecutionScope; i: Integer; 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.'); Result := FCapturedUpvalues[Address.SlotIndex]; end; akLocalOrParent: begin targetScope := Self; for i := 1 to Address.ScopeDepth do begin targetScope := targetScope.Parent as TExecutionScope; Assert(Assigned(targetScope), 'Invalid scope depth during capture.'); end; Assert((Address.SlotIndex >= 0) and (Address.SlotIndex < Length(targetScope.FValues)), 'Invalid slot index during capture.'); // The check-and-update for JIT-boxing must be atomic. TMonitor.Enter(targetScope); try var item := targetScope.FValues[Address.SlotIndex]; if item.IsBoxed then begin // The variable is already boxed, so just return its cell. Result := item.Value.AsIntf; end else begin // This is an unboxed variable. Box it "just-in-time" and update the scope. Result := TValueCell.Create(item.Value); targetScope.FValues[Address.SlotIndex].Value := TDataValue.FromIntf(Result); targetScope.FValues[Address.SlotIndex].IsBoxed := True; end; finally TMonitor.Exit(targetScope); end; end; else raise EInvalidOpException.Create('Cannot capture 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 := Value; FValues[index].IsBoxed := False; FNameToIndex.Add(id, index); end; procedure TExecutionScope.DefineBoxed(const Address: TResolvedAddress; const Value: TDataValue); var targetScope: TExecutionScope; i: Integer; begin // This method is only for local variables (ScopeDepth=0). Assert(Address.ScopeDepth = 0, 'DefineBoxed can only be used on the current scope.'); targetScope := Self; Assert((Address.SlotIndex >= 0) and (Address.SlotIndex < Length(targetScope.FValues)), 'Invalid slot index during DefineBoxed.'); targetScope.FValues[Address.SlotIndex].Value := TDataValue.FromIntf(TValueCell.Create(Value)); targetScope.FValues[Address.SlotIndex].IsBoxed := True; end; procedure TExecutionScope.DumpScope(const ABuilder: TStringBuilder; AIndent: Integer); var pair: TPair; indentStr, boxedStr: string; sortedPairs: TArray>; item: TScopeItem; val: TDataValue; begin NeedNameToIndex; indentStr := ''.PadLeft(AIndent); if FNameToIndex.Count > 0 then begin sortedPairs := FNameToIndex.ToArray; TArray.Sort>( sortedPairs, TComparer> .Construct(function(const Left, Right: TPair): Integer begin Result := Left.Value - Right.Value; end) ); for pair in sortedPairs do begin item := FValues[pair.Value]; if item.IsBoxed then begin boxedStr := ' (Boxed)'; val := (item.Value.AsIntf).Value; end else begin boxedStr := ''; val := item.Value; end; ABuilder.AppendLine(indentStr + Format(' [%d] %s: %s%s', [pair.Value, FNameStrings[pair.Key], val.ToString, boxedStr])); end; end else ABuilder.AppendLine(indentStr + ' (empty)'); 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; begin NeedNameToIndex; Result := FNameToIndex; end; function TExecutionScope.GetValues(const Address: TResolvedAddress): TDataValue; var item: TScopeItem; 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.'); 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 for GetValues.'); end; Assert((Address.SlotIndex >= 0) and (Address.SlotIndex < Length(targetScope.FValues)), 'Invalid slot index for GetValues.'); item := targetScope.FValues[Address.SlotIndex]; if item.IsBoxed then Result := (item.Value.AsIntf).Value else Result := item.Value; 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.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); var targetScope: TExecutionScope; i: Integer; 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.'); FCapturedUpvalues[Address.SlotIndex].Value := Value; end; akLocalOrParent: begin targetScope := Self; for i := 1 to Address.ScopeDepth do begin targetScope := targetScope.Parent as TExecutionScope; Assert(Assigned(targetScope), 'Invalid scope depth for SetValues.'); end; Assert((Address.SlotIndex >= 0) and (Address.SlotIndex < Length(targetScope.FValues)), 'Invalid slot index for SetValues.'); // Use a reference/pointer to modify the array element in place var pItem: ^TScopeItem := @targetScope.FValues[Address.SlotIndex]; if pItem.IsBoxed then (pItem.Value.AsIntf).Value := Value else pItem.Value := Value; end; else raise EInvalidOpException.Create('Cannot set value for an unresolved address.'); end; end; { TScopeDescriptor } // ... (Implementation unchanged and correct) ... constructor TScopeDescriptor.Create(const AParent: IScopeDescriptor); begin inherited Create; FParent := AParent; FSymbols := TDictionary.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 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; begin Result := FSymbols; end; procedure TScopeDescriptor.PopulateFromScope(Scope: TExecutionScope); begin 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 ): IExecutionScope; begin Result := TExecutionScope.Create(Parent, Descriptor, CapturedUpvalues); end; end.