unit Myc.Ast.Scope; interface uses System.SysUtils, System.Generics.Collections, System.Generics.Defaults, System.Classes, Myc.Data.Value, Myc.Ast.Types; type IExecutionScope = interface; IScopeDescriptor = interface; // Defines how an identifier's address was resolved. TAddressKind = (akUnresolved, akLocalOrParent, akUpvalue); TResolvedAddress = record Kind: TAddressKind; ScopeDepth: Integer; SlotIndex: Integer; constructor Create(AKind: TAddressKind; AScopeDepth, ASlotIndex: Integer); class operator Initialize(out Dest: TResolvedAddress); public class operator Equal(const A: TResolvedAddress; B: TResolvedAddress): Boolean; end; TResolvedAddressComparer = class(TEqualityComparer) public function Equals(const Left, Right: TResolvedAddress): Boolean; override; function GetHashCode(const Value: TResolvedAddress): Integer; override; end; // A resolved symbol, containing its runtime address and static type. TResolvedSymbol = record Address: TResolvedAddress; StaticType: IStaticType; constructor Create(const AAddress: TResolvedAddress; const AStaticType: IStaticType); class operator Initialize(out Dest: TResolvedSymbol); end; IValueCell = interface {$region 'private'} function GetValue: TDataValue; procedure SetValue(const AValue: TDataValue); {$endregion} property Value: TDataValue read GetValue write SetValue; end; // Describes the layout of a scope: variable names, their slot indices and macros. IScopeDescriptor = interface {$region 'private'} function GetParent: IScopeDescriptor; function GetSlotCount: Integer; function GetSymbols: TDictionary; function GetType(SlotIndex: Integer): IStaticType; {$endregion} function CreateScope(const AParent: IExecutionScope): IExecutionScope; function Define(const Name: string; const AType: IStaticType = nil): Integer; procedure UpdateType(SlotIndex: Integer; const AType: IStaticType); function FindSymbol(const Name: string): TResolvedSymbol; property Parent: IScopeDescriptor read GetParent; property SlotCount: Integer read GetSlotCount; property Symbols: TDictionary read GetSymbols; end; IExecutionScope = interface {$region 'private'} function GetParent: IExecutionScope; function GetDescriptor: IScopeDescriptor; // (* ADDED *) function GetValues(const Address: TResolvedAddress): TDataValue; procedure SetValues(const Address: TResolvedAddress; const Value: TDataValue); {$endregion} function Define(const Name: string; const Value: TDataValue; const AStaticType: IStaticType = nil): TResolvedAddress; procedure DefineBoxed(SlotIndex: Integer; const Value: TDataValue); function Dump: string; procedure Clear; function Capture(const Address: TResolvedAddress): IValueCell; function GetNameID(const Name: String): Integer; function Resolve(const Name: string): TResolvedAddress; // (* REMOVED: function CreateDescriptor: IScopeDescriptor; *) property Values[const Address: TResolvedAddress]: TDataValue read GetValues write SetValues; default; property Parent: IExecutionScope read GetParent; property Descriptor: IScopeDescriptor read GetDescriptor; // (* ADDED *) 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.Hash, System.SyncObjs; type TScopeDescriptor = class(TInterfacedObject, IScopeDescriptor) private FParent: IScopeDescriptor; FSymbols: TDictionary; FSlotTypes: TArray; function GetParent: IScopeDescriptor; function GetSlotCount: Integer; function GetSymbols: TDictionary; function GetType(SlotIndex: Integer): IStaticType; public constructor Create(const AParent: IScopeDescriptor); destructor Destroy; override; // (* REMOVED: class function CreateDescriptor(const Scope: IExecutionScope): IScopeDescriptor; *) function Define(const Name: string; const AType: IStaticType): Integer; procedure UpdateType(SlotIndex: Integer; const AType: IStaticType = nil); function FindSymbol(const Name: string): TResolvedSymbol; function CreateScope(const Parent: IExecutionScope): IExecutionScope; property Symbols: TDictionary read FSymbols; end; 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; function GetContent: TDataValue; inline; 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 GetParent: IExecutionScope; function GetDescriptor: IScopeDescriptor; // (* ADDED *) 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 GetNameID(const Name: String): Integer; function Resolve(const Name: string): TResolvedAddress; function Dump: string; function Define(const Name: string; const Value: TDataValue; const AStaticType: IStaticType = nil): TResolvedAddress; procedure DefineBoxed(SlotIndex: Integer; const Value: TDataValue); function Capture(const Address: TResolvedAddress): IValueCell; // (* REMOVED: function CreateDescriptor: IScopeDescriptor; *) property Names: TDictionary read FNames; property NameStrings: TList read FNameStrings; property NameToIndex: TDictionary read GetNameToIndex; property Parent: IExecutionScope read FParent; property ValuesArray: TArray read FValues; end; { TResolvedAddressComparer } function TResolvedAddressComparer.Equals(const Left, Right: TResolvedAddress): Boolean; begin Result := (Left = Right); end; function TResolvedAddressComparer.GetHashCode(const Value: TResolvedAddress): Integer; var adr: TResolvedAddress; begin adr := Value; Result := THashBobJenkins.GetHashValue(adr.Kind, SizeOf(TAddressKind), 0); Result := THashBobJenkins.GetHashValue(adr.ScopeDepth, SizeOf(Integer), Result); Result := THashBobJenkins.GetHashValue(adr.SlotIndex, SizeOf(Integer), Result); end; { TResolvedAddress } constructor TResolvedAddress.Create(AKind: TAddressKind; AScopeDepth, ASlotIndex: Integer); begin Kind := AKind; ScopeDepth := AScopeDepth; SlotIndex := ASlotIndex; end; class operator TResolvedAddress.Equal(const A: TResolvedAddress; B: TResolvedAddress): Boolean; begin Result := (A.Kind = B.Kind) and (A.ScopeDepth = B.ScopeDepth) and (A.SlotIndex = B.SlotIndex); end; class operator TResolvedAddress.Initialize(out Dest: TResolvedAddress); begin Dest.Kind := akUnresolved; Dest.ScopeDepth := -1; Dest.SlotIndex := -1; end; { TResolvedSymbol } constructor TResolvedSymbol.Create(const AAddress: TResolvedAddress; const AStaticType: IStaticType); begin Address := AAddress; StaticType := AStaticType; end; class operator TResolvedSymbol.Initialize(out Dest: TResolvedSymbol); begin Dest.StaticType := TTypes.Unknown; 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; // (* MODIFIED: Clean logic for Descriptor initialization *) if Assigned(ADescriptor) then FDescriptor := ADescriptor else if Assigned(AParent) then begin // Create a NEW empty descriptor that is a CHILD of the parent's descriptor. // Use the Descriptor property of the parent scope. FDescriptor := TScopeDescriptor.Create(AParent.Descriptor); end else // Create a new root descriptor (parent = nil) FDescriptor := TScopeDescriptor.Create(nil); 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 Assigned(ADescriptor) 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; function TExecutionScope.GetDescriptor: IScopeDescriptor; begin Result := FDescriptor; 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)); Result := FCapturedUpvalues[Address.SlotIndex]; end; akLocalOrParent: begin targetScope := Self; for i := 1 to Address.ScopeDepth do targetScope := targetScope.Parent as TExecutionScope; TMonitor.Enter(targetScope); try var item := targetScope.FValues[Address.SlotIndex]; if item.IsBoxed then Result := item.Value.AsIntf else begin 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.Define(const Name: string; const Value: TDataValue; const AStaticType: IStaticType = nil): TResolvedAddress; var id: Integer; index: Integer; staticType: IStaticType; begin NeedNameToIndex; id := GetNameID(Name); if FNameToIndex.ContainsKey(id) then raise Exception.CreateFmt('Variable "%s" is already defined in this scope.', [Name]); if Assigned(AStaticType) then staticType := AStaticType else staticType := TTypes.Unknown; index := Length(FValues); SetLength(FValues, index + 1); FValues[index].Value := Value; FValues[index].IsBoxed := False; FNameToIndex.Add(id, index); // Update the descriptor var descSlot := FDescriptor.Define(Name, staticType); Assert(descSlot = index, 'Scope descriptor slot mismatch during Define'); Result.Kind := akLocalOrParent; Result.ScopeDepth := 0; Result.SlotIndex := index; end; procedure TExecutionScope.DefineBoxed(SlotIndex: Integer; const Value: TDataValue); begin FValues[SlotIndex].Value := TDataValue.FromIntf(TValueCell.Create(Value)); FValues[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)); Result := FCapturedUpvalues[Address.SlotIndex].Value; end; akLocalOrParent: begin var targetScope := Self; for var i := 1 to Address.ScopeDepth do targetScope := targetScope.Parent as TExecutionScope; 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; function TExecutionScope.Resolve(const Name: string): TResolvedAddress; var nameID: Integer; slotIndex: Integer; currentScope: IExecutionScope; depth: Integer; begin currentScope := Self; depth := 0; nameID := GetNameID(Name); while Assigned(currentScope) do begin var execScopeImpl := (currentScope as TExecutionScope); if execScopeImpl.NameToIndex.TryGetValue(nameID, slotIndex) then begin Result.Kind := akLocalOrParent; Result.ScopeDepth := depth; Result.SlotIndex := slotIndex; exit; end; inc(depth); currentScope := currentScope.Parent; end; Result := Default(TResolvedAddress); 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)); FCapturedUpvalues[Address.SlotIndex].Value := Value; end; akLocalOrParent: begin targetScope := Self; for i := 1 to Address.ScopeDepth do targetScope := targetScope.Parent as TExecutionScope; 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; function TExecutionScope.TScopeItem.GetContent: TDataValue; begin Result := Value; if IsBoxed then Result := Result.AsIntf.Value; end; { TScopeDescriptor } constructor TScopeDescriptor.Create(const AParent: IScopeDescriptor); begin inherited Create; FParent := AParent; FSymbols := TDictionary.Create; FSlotTypes := []; end; destructor TScopeDescriptor.Destroy; begin FSlotTypes := nil; FSymbols.Free; inherited; end; function TScopeDescriptor.CreateScope(const Parent: IExecutionScope): IExecutionScope; begin Result := TExecutionScope.Create(Parent, Self, nil); end; function TScopeDescriptor.Define(const Name: string; const AType: IStaticType): Integer; begin Result := FSymbols.Count; FSymbols.Add(Name, Result); SetLength(FSlotTypes, Result + 1); FSlotTypes[Result] := if AType <> nil then AType else TTypes.Unknown; end; procedure TScopeDescriptor.UpdateType(SlotIndex: Integer; const AType: IStaticType = nil); begin if (SlotIndex < 0) or (SlotIndex >= Length(FSlotTypes)) then raise EArgumentOutOfRangeException.Create('Invalid SlotIndex in UpdateType.'); FSlotTypes[SlotIndex] := If AType <> nil then AType else TTypes.Unknown; end; function TScopeDescriptor.FindSymbol(const Name: string): TResolvedSymbol; var currentDescriptor: IScopeDescriptor; slotIndex: Integer; begin Result.StaticType := TTypes.Unknown; Result.Address.Kind := akUnresolved; Result.Address.ScopeDepth := 0; currentDescriptor := Self; while Assigned(currentDescriptor) do begin if currentDescriptor.Symbols.TryGetValue(Name, slotIndex) then begin Result.Address.Kind := akLocalOrParent; Result.Address.SlotIndex := slotIndex; Result.StaticType := currentDescriptor.GetType(slotIndex); exit; end; inc(Result.Address.ScopeDepth); currentDescriptor := currentDescriptor.Parent; 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; function TScopeDescriptor.GetType(SlotIndex: Integer): IStaticType; begin if (SlotIndex < 0) or (SlotIndex >= Length(FSlotTypes)) then raise EArgumentOutOfRangeException.Create('Invalid SlotIndex in GetType.'); Result := FSlotTypes[SlotIndex]; end; { TScope } class function TScope.CreateDescriptor(const Parent: IScopeDescriptor): IScopeDescriptor; begin // Factory for a new, empty descriptor 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.