unit Myc.Ast.Scope; interface uses System.SysUtils, System.Generics.Collections, System.Classes, Myc.Ast.Nodes; type TExecutionScope = class(TInterfacedObject, IExecutionScope) private FParent: IExecutionScope; FVariables: TDictionary; procedure DumpScope(const ABuilder: TStringBuilder; AIndent: Integer); protected // IExecutionScope function GetParent: IExecutionScope; 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); function Dump: string; public constructor Create(AParent: IExecutionScope = nil); destructor Destroy; override; end; implementation { TExecutionScope } constructor TExecutionScope.Create(AParent: IExecutionScope); begin inherited Create; FParent := AParent; FVariables := TDictionary.Create; end; destructor TExecutionScope.Destroy; begin FVariables.Free; inherited Destroy; end; function TExecutionScope.GetParent: IExecutionScope; begin Result := FParent; end; procedure TExecutionScope.AssignValue(const Name: string; const Value: TAstValue); begin if FVariables.ContainsKey(Name) then FVariables.AddOrSetValue(Name, Value) else if Assigned(FParent) then FParent.AssignValue(Name, Value) else raise Exception.CreateFmt('Cannot assign to undeclared variable "%s".', [Name]); end; procedure TExecutionScope.Clear; begin FVariables.Clear; if Assigned(FParent) then FParent.Clear; end; procedure TExecutionScope.DumpScope(const ABuilder: TStringBuilder; AIndent: Integer); var pair: TPair; indentStr: string; begin indentStr := ''.PadLeft(AIndent); if (FVariables.Count > 0) then begin for pair in FVariables do ABuilder.AppendLine(indentStr + Format(' %s: %s', [pair.Key, pair.Value.ToString])); end else begin ABuilder.AppendLine(indentStr + ' (empty)'); end; 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. (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.FindValue(const Name: string; out Value: TAstValue): Boolean; begin Result := FVariables.TryGetValue(Name, Value); if not Result and Assigned(FParent) then begin Result := FParent.FindValue(Name, Value); end; end; procedure TExecutionScope.SetValue(const Name: string; const Value: TAstValue); begin FVariables.AddOrSetValue(Name, Value); end; end.