ASObjStore.pas

File name
C:\Users\Andreas Rejbrand\Documents\Dev\AlgoSim\ASObjStore.pas
Date exported
Time exported
Formatting processor
TPascalFormattingProcessor
unit ASObjStore;

{ **************************************************************************** }
{ Rejbrand AlgoSim Object Manager                                              }
{ Copyright © 2018 Andreas Rejbrand                                            }
{ https://english.rejbrand.se/                                                 }
{ **************************************************************************** }

{$WARN SYMBOL_PLATFORM OFF}
{$WARN DUPLICATE_CTOR_DTOR OFF}

interface

uses
  SysUtils, Types, Classes, Graphics, Generics.Defaults,
  Generics.Collections, ASNum, ASPixmap, ASTable, ASObjects, ASStructs,
  ASKernelDefs, GenHelpers;

type
  TVariableMetadata = record
    Name: string;
    Description: string;
    Time: TDateTime;
    Attributes: TIdentAttribs;
  end;
  TAlgosimVariable = class
  strict private
    FName: string;
    FDescription: string;
    FTime: TDateTime;
    FObject: TAlgosimObject;
    FAttributes: TIdentAttribs;
    procedure SetValue(const Value: TAlgosimObject);
    procedure SetDescription(const Value: string);
    procedure SetName(const Value: string);
    function GetValue: TAlgosimObject; inline;
    function GetProtected: Boolean; inline;
    procedure SetProtected(AProtected: Boolean); inline;
    function GetSystem: Boolean; inline;
    procedure SetSystem(ASystem: Boolean); inline;
  private
    procedure ProtectionError;
  public
    constructor Create; overload;

    /// <summary>Creates a new AlgoSim variable encapsulating a specified
    ///  AlgoSim object.</summary>
    /// <param name="AObject">The AlgoSim object to store in this variable.
    ///  The variable gains ownership of the object.</param>
    /// <param name="AName">The name of the variable. This SHOULD be a valid
    ///  AlgoSim identifier.</param>
    /// <param name="ADescription">The description of the variable.</param>
    /// <param name="AProtected">Indicates if the variable is protected.</param>
    constructor Create(AObject: TAlgosimObject;
      const AName: string; const ADescription: string = '';
      AProtected: Boolean = False; ASystem: Boolean = False); overload;

    destructor Destroy; override;

    /// <summary>The name of the variable.</summary>
    property Name: string read FName write SetName;

    /// <summary>The description of the variable.</summary>
    property Description: string read FDescription write SetDescription;

    /// <summary>The time at which the variable was last assigned a value.</summary>
    property Time: TDateTime read FTime;

    /// <summary>The value of the variable.</summary>
    /// <remarks>When read, a copy of the value is created, and the caller
    ///  gains ownership of the new value. When set, the variable gains
    ///  ownership of the value, even if an exception is raised. Any previous
    ///  value is automatically freed.</remarks>
    property Value: TAlgosimObject read GetValue write SetValue;

    /// <summary>A reference to the stored value.</summary>
    property ObjRef: TAlgosimObject read FObject;

    property Attributes: TIdentAttribs read FAttributes write FAttributes;

    /// <summary>Indicates if the variable is protected.</summary>
    property IsProtected: Boolean read GetProtected write SetProtected;

    property IsSystem: Boolean read GetSystem write SetSystem;

    /// <summary>Creates an AlgoSim variable structure object from this
    ///  variable. The structure's value is a copy of the variable's value.
    ///  </summary>
    /// <returns>A new AlgoSim variable structure. The caller gains ownership
    ///  of the structure.</returns>
    function CreateStructure: TAlgosimStructure;

    function CreateMetadataStructure: TAlgosimStructure;

    function Metadata: TVariableMetadata;

    /// <summary>Extracts the object from the variable, which then will
    ///  contain (and own) no value.</summary>
    /// <returns>The variable's object. The caller gains ownership of it.
    ///  </returns>
    function Extract: TAlgosimObject;

  end;

  TLValuePathItem = TSubscript;

  TLValueData = class(TList<TLValuePathItem>)
  end;

  /// <summary>A collection of AlgoSim variables, which are owned by the store.
  ///  </summary>
  TAlgosimObjStore = class
  private
    Objs: TObjectDictionary<string, TAlgosimVariable>;
  public
    function TryGetVariable(const AName: string;
      out AVariable: TAlgosimVariable): Boolean;
    procedure SetVariable(const AName: string;
      AValue: TAlgosimObject; const ADescription: string = '';
      AProtected: Boolean = False);
    procedure ForceSetVariable(const AName: string;
      AValue: TAlgosimObject; const ADescription: string = '';
      AProtected: Boolean = False; ASystem: Boolean = False);
    function GetVariableList: TArray<string>;
    procedure Clear;
    constructor Create; virtual;
    destructor Destroy; override;
  end;

  TStoreStackRes = record
    Store: TAlgosimObjStore;
    Success: Boolean;
    procedure Clear;
  end;

  /// <summary>A stack of AlgoSim object stores, which are not owned by the
  ///  stack.</summary>
  TObjStoreStack = class
  strict private
    FStores: TList<TAlgosimObjStore>;
    function GetStore(Index: Integer): TAlgosimObjStore; inline;
    function GetCount: Integer; inline;
    function GetObjRefOrValue(const ALValueData: TLValueData;
      out AValue: TAlgosimObject; AAsValue: Boolean): TStoreStackRes;
  public

    constructor Create; overload; virtual;

    /// <summary>Creates a new AlgoSim object store stack from a given list of
    ///  stores.</summary>
    /// <param name="AStores">An open array of AlgoSim object stores. The stores
    ///  are kept as references: they may be modified by the stack, but they
    ///  will not be freed by it.</param>
    constructor Create(const AStores: array of TAlgosimObjStore); overload; virtual;

    /// <summary>Pushes a new AlgoSim object store to the stack.</summary>
    /// <param name="AStore">The AlgoSim object store to push to the stack.
    ///  The store is kept as a reference: it may be modified by the stack,
    ///  but it will not be freed by it.</param>
    /// <returns>The index of <c>AStore</c> in the stack.</returns>
    function Push(AStore: TAlgosimObjStore): Integer;

    /// <summary>Returns a reference to the topmost element in the stack and
    ///  removes it from the stack, or <c>nil</c> if the stack is empty.</summary>
    function Pop: TAlgosimObjStore;

    /// <summary>Returns a reference to the topmost element in the stack, or
    ///  <c>nil</c> if the stack is empty.</summary>
    function Top: TAlgosimObjStore; inline;

    /// <summary>Returns a reference to the bottom-most element in the stack, or
    ///  <c>nil</c> if the stack is empty.</summary>
    function Bottom: TAlgosimObjStore; inline;

    /// <summary>Clears the stack (but doesn't free any object store).</summary>
    procedure Clear;

    destructor Destroy; override;

    /// <summary>Tries to find a variable in the stack.</summary>
    /// <param name="AName">The name of the variable to search for.</param>
    /// <param name="AVariable">Receives the first variable named <c>AName</c>
    ///  that is found in the stack, searching from the top to the bottom, or is
    ///  left undefined if no matching variable is found. If a match is found,
    ///  this will be a reference to the variable, which contains a reference to
    ///  the variable's object.</param>
    /// <returns>The object store in which the variable is found, or <c>nil</c>
    ///  if no matching variable is found in the stack.</returns>
    function TryGetVariable(const AName: string;
      out AVariable: TAlgosimVariable): TAlgosimObjStore;

    /// <summary>Retrieves a copy of a variable's value in the stack.</summary>
    /// <param name="AName">The name of the variable to search for.</param>
    /// <param name="AValue">Receives a copy of the value of the first
    ///  variable named <c>AName</c> that is found in the stack, searching from
    ///  the top to the bottom.</param>
    /// <returns>The object store in which the variable is found.</returns>
    /// <exception cref="EUnknownIdentifier">Raised if the variable isn't found
    ///  in the stack.</exception>
    function GetValue(const AName: string;
      out AValue: TAlgosimObject): TAlgosimObjStore; overload;

    /// <summary>Retrieves a copy of an addressable value in the stack.</summary>
    /// <param name="ALValueData">The lvalue path of the value to search for.
    ///  The base of this path denotes a variable name, and the stack is searched
    ///  from top to bottom for this name. The first matching variable will be
    ///  used to search for the named location described by the rest of the
    ///  path.</param>
    /// <param name="AValue">Receives a copy of the value of the first
    ///  addressable item named <c>AName</c> that is found in the stack,
    ///  searching from the top to the bottom.</param>
    /// <returns>The object store in which the variable is found.</returns>
    /// <exception cref="EUnknownIdentifier">Raised if the variable isn't found
    ///  in the stack.</exception>
    function GetValue(const ALValueData: TLValueData;
      out AValue: TAlgosimObject): TStoreStackRes; overload;

    /// <summary>Retrieves a reference to a variable's value in the stack.</summary>
    /// <param name="AName">The name of the variable to search for.</param>
    /// <param name="AValue">Receives a reference to the value of the first
    ///  variable named <c>AName</c> that is found in the stack, searching from
    ///  the top to the bottom.</param>
    /// <returns>The object store in which the variable is found.</returns>
    /// <exception cref="EUnknownIdentifier">Raised if the variable isn't found
    ///  in the stack.</exception>
    function GetObjRef(const AName: string;
      out AValue: TAlgosimObject): TAlgosimObjStore; overload;

    /// <summary>Retrieves a reference to an addressable value in the stack.</summary>
    /// <param name="ALValueData">The lvalue path of the value to search for.
    ///  The base of this path denotes a variable name, and the stack is searched
    ///  from top to bottom for this name. The first matching variable will be
    ///  used to search for the named location described by the rest of the
    ///  path.</param>
    /// <param name="AValue">Receives a reference to the value of the first
    ///  reference-addressable item named <c>AName</c> that is found in the
    ///  stack, searching from the top to the bottom.</param>
    /// <returns>The object store in which the variable is found.</returns>
    /// <exception cref="EUnknownIdentifier">Raised if the variable isn't found
    ///  in the stack.</exception>
    function GetObjRef(const ALValueData: TLValueData;
      out AValue: TAlgosimObject): TStoreStackRes; overload;

    /// <summary>Removes the first variable found with a given name,
    ///  searching the stack from top to bottom.</summary>
    /// <param name="AName">The name of the variable to search for.</param>
    /// <returns>The object store in which the variable is found, or <c>nil</c>
    ///  if no matching variable is found in the stack.</returns>
    /// <exception cref="EVariableProtectionException">Raised if the found variable
    ///  is protected; in this case, it is not removed from its store.</exception>
    function TryRemoveVariable(const AName: string): TAlgosimObjStore;

    /// <summary>Sets a variable. If a variable named <c>AName</c> is found in
    ///  the stack, searching from top to bottom, it is updated with the new
    ///  value. Otherwise, a new variable is created in the top-most store with
    ///  the given value.</summary>
    /// <param name="AName">The name of the variable to set. If no variable
    ///  with this name is found in the stack, it is first validated before
    ///  a new variable with this name is created in the top-most store.
    ///  </param>
    /// <param name="AValue">The value to set the variable's value to. The stack
    ///  gains ownership of this value, even if an exception is raised. If no
    ///  exception is raised, ownership is transfered to the variable in which
    ///  the value will belong.</param>
    /// <exception cref="EVariableProtectionException">Raised if the found variable
    ///  is protected; in this case, its value is not updated.</exception>
    /// <exception cref="EInvalidIdentName">Raised if the variable name is not
    ///  valid.</exception>
    function SetVariable(const AName: string;
      AValue: TAlgosimObject): TAlgosimObjStore; overload;

    /// <summary>Sets the value of an addressable location in the stack.</summary>
    /// <param name="ALValueData">The lvalue path of the location to search for.
    ///  The base of this path denotes a variable name, and the stack is searched
    ///  from top to bottom for this name. The first matching variable will be
    ///  used to search for the named location described by the rest of the
    ///  path.</param>
    /// <param name="AValue">The value to set the location's value to. The stack
    ///  gains ownership of this value, even if an exception is raised. If no
    ///  exception is raised, ownership is transfered to the variable in which
    ///  the value will belong.</param>
    /// <returns>The object store in which the variable is found.</returns>
    /// <exception cref="EExpressionException">Raised if the lvalue path is
    ///  invalid.</exception>
    /// <exception cref="EUnknownIdentifier">Raised if the lvalue path consists
    ///  of multiple parts and the base part of the lvalue path is a variable
    ///  which is not found in the stack.</exception>
    /// <exception cref="EVariableProtectionException">Raised if the found variable
    ///  is protected; in this case, its value is not updated.</exception>
    /// <exception cref="EAlgosimObjectException">Raised if a part in the lvalue
    ///  path denotes a structure but the object found at this path in the stack
    ///  is of any other AlgoSim type.</exception>
    /// <exception cref="EStructureException">Raised if a structure member that
    ///  is described by the lvalue path isn't found in the actual structure in
    ///  the stack.</exception>
    /// <remarks>If the lvalue path consists of only a single part (the base
    ///  part) and this part is a variable name (which is has to be), the
    ///  work is delegated to the SetVariable(string, object) method.</remark>
    function SetVariable(const ALValueData: TLValueData;
      AValue: TAlgosimObject): TAlgosimObjStore; overload;

    function GetVariableList: TArray<string>;

    /// <summary>The array of stores in the stack.</summary>
    property Store[Index: Integer]: TAlgosimObjStore read GetStore; default;

    /// <summare>The number of stores in the stack.</summary>
    property Count: Integer read GetCount;

  end;

implementation

uses
  Math, StrUtils;

{ TAlgosimVariable }

constructor TAlgosimVariable.Create;
begin
  FTime := Now;
end;

constructor TAlgosimVariable.Create(AObject: TAlgosimObject;
  const AName, ADescription: string; AProtected, ASystem: Boolean);
begin
  Create;
  FName := AName;
  FDescription := ADescription;
  FObject := AObject;
  if AProtected then
    Include(FAttributes, iaProtected);
  if ASystem then
    Include(FAttributes, iaSystem);
end;

function TAlgosimVariable.CreateStructure: TAlgosimStructure;
begin
  Result := ASOVariable(FName, FDescription, FTime, FObject, IsProtected);
end;

function TAlgosimVariable.CreateMetadataStructure: TAlgosimStructure;
begin
  Result := ASOVariableMetadata(FName, FDescription, FTime, IsProtected);
end;

destructor TAlgosimVariable.Destroy;
begin
  FObject.Free;
  inherited;
end;

function TAlgosimVariable.Extract: TAlgosimObject;
begin
  TMover<TAlgosimObject>.Move(Result, FObject);
end;

function TAlgosimVariable.GetProtected: Boolean;
begin
  Result := iaProtected in FAttributes;
end;

function TAlgosimVariable.GetSystem: Boolean;
begin
  Result := iaSystem in FAttributes;
end;

function TAlgosimVariable.GetValue: TAlgosimObject;
begin
  Result := FObject.Clone;
end;

function TAlgosimVariable.Metadata: TVariableMetadata;
begin
  Result.Name := FName;
  Result.Description := FDescription;
  Result.Time := FTime;
  Result.Attributes := FAttributes;
end;

procedure TAlgosimVariable.ProtectionError;
begin
  raise EVariableProtectionException.CreateFmt(SVarProtectionError, [FName]);
end;

procedure TAlgosimVariable.SetDescription(const Value: string);
begin
  if IsProtected then
    ProtectionError;
  FDescription := Value;
end;

procedure TAlgosimVariable.SetName(const Value: string);
begin
  if IsProtected then
    ProtectionError;
  FName := Value;
end;

procedure TAlgosimVariable.SetProtected(AProtected: Boolean);
begin
  if AProtected then
    Include(FAttributes, iaProtected)
  else
    Exclude(FAttributes, iaProtected)
end;

procedure TAlgosimVariable.SetSystem(ASystem: Boolean);
begin
  if ASystem then
    Include(FAttributes, iaSystem)
  else
    Exclude(FAttributes, iaSystem)
end;

procedure TAlgosimVariable.SetValue(const Value: TAlgosimObject);
begin

  if IsProtected then
  begin
    Value.Free;
    ProtectionError;
    Exit;
  end;

  FTime := Now;
  FreeAndNil(FObject);
  FObject := Value;

end;

{ TAlgosimObjStore }

procedure TAlgosimObjStore.Clear;
begin
  Objs.Clear;
end;

constructor TAlgosimObjStore.Create;
begin
  Objs := TObjectDictionary<string, TAlgosimVariable>.Create([doOwnsValues]);
end;

destructor TAlgosimObjStore.Destroy;
begin
  Objs.Free;
  inherited;
end;

function TAlgosimObjStore.GetVariableList: TArray<string>;
begin
  Result := Objs.Keys.ToArray;
end;

procedure TAlgosimObjStore.SetVariable(const AName: string;
  AValue: TAlgosimObject; const ADescription: string = '';
  AProtected: Boolean = False);
var
  Variable: TAlgosimVariable;
begin
  if Objs.TryGetValue(AName, Variable) then
  begin
    Variable.Value := AValue;
    Variable.Description := ADescription;
    Variable.IsProtected := AProtected;
  end
  else
    Objs.Add(AName, TAlgosimVariable.Create(AValue, AName, ADescription, AProtected));
end;

procedure TAlgosimObjStore.ForceSetVariable(const AName: string;
  AValue: TAlgosimObject; const ADescription: string;
  AProtected, ASystem: Boolean);
var
  Variable: TAlgosimVariable;
begin
  if Objs.TryGetValue(AName, Variable) then
  begin
    Variable.IsProtected := False;
    Variable.Value := AValue;
    Variable.Description := ADescription;
    Variable.IsProtected := AProtected;
    Variable.IsSystem := ASystem;
  end
  else
    Objs.Add(AName, TAlgosimVariable.Create(AValue, AName, ADescription, AProtected, ASystem));
end;

function TAlgosimObjStore.TryGetVariable(const AName: string;
  out AVariable: TAlgosimVariable): Boolean;
begin
  Result := Objs.TryGetValue(AName, AVariable);
end;

{ TObjStoreStack }

constructor TObjStoreStack.Create;
begin
  FStores := TList<TAlgosimObjStore>.Create;
end;

procedure TObjStoreStack.Clear;
begin
  FStores.Clear;
end;

constructor TObjStoreStack.Create(const AStores: array of TAlgosimObjStore);
var
  i: Integer;
begin
  Create;
  FStores.Capacity := Max(FStores.Capacity, Length(AStores));
  for i := 0 to High(AStores) do
    FStores.Add(AStores[i]);
end;

destructor TObjStoreStack.Destroy;
begin
  FStores.Free;
  inherited;
end;

function TObjStoreStack.GetCount: Integer;
begin
  Result := FStores.Count;
end;

function TObjStoreStack.GetStore(Index: Integer): TAlgosimObjStore;
begin
  Result := FStores[Index];
end;

function TObjStoreStack.Pop: TAlgosimObjStore;
begin
  if FStores.Count > 0 then
  begin
    Result := FStores[FStores.Count - 1];
    FStores.Count := FStores.Count - 1;
  end
  else
    Result := nil;
end;

function TObjStoreStack.Push(AStore: TAlgosimObjStore): Integer;
begin
  Result := FStores.Add(AStore);
end;

function TObjStoreStack.TryRemoveVariable(const AName: string): TAlgosimObjStore;
var
  Variable: TAlgosimVariable;
begin
  Result := TryGetVariable(AName, Variable);
  if Assigned(Result) then
    if Variable.IsProtected then
      Variable.ProtectionError
    else
      Result.Objs.Remove(AName);
end;

function TObjStoreStack.SetVariable(const AName: string;
  AValue: TAlgosimObject): TAlgosimObjStore;
var
  Variable: TAlgosimVariable;
begin
  Result := TryGetVariable(AName, Variable);
  if Assigned(Result) then
    Variable.Value := AValue
  else
  begin
    try
      Result := Top;
      if Result = nil then
        raise EVariableException.Create(SObjectStoreStackEmpty);
      CheckIdentName(AName);
    except
      AValue.Free;
      raise;
    end;
    Top.Objs.Add(AName, TAlgosimVariable.Create(AValue, AName));
  end;
end;

function TObjStoreStack.SetVariable(const ALValueData: TLValueData;
  AValue: TAlgosimObject): TAlgosimObjStore;
var
  HighIndex: Integer;
  BaseIdent: string;
  Variable: TAlgosimVariable;
  i: Integer;
  ref: TAlgosimObject;
  CurPath: string;
  mbrname: string;
  mbridx: Integer;
  OwnsValue: Boolean;
begin

  OwnsValue := True;
  try

    if ALValueData = nil then
      raise EExpressionException.Create(SUnassignedLValueData);

    if ALValueData.Count = 0 then
      raise EExpressionException.Create(SEmptyLValueData);

    HighIndex := ALValueData.Count - 1;

    if (ALValueData[HighIndex].Kind <> skIdentifier) or ALValueData[HighIndex].Ident.IsEmpty then
      raise EExpressionException.Create(SInvalidLValueDataRoot);

    BaseIdent := ALValueData[HighIndex].Ident;

    if ALValueData.Count = 1 then
    begin
      OwnsValue := False;
      Exit(SetVariable(BaseIdent, AValue));
    end;

    Result := TryGetVariable(BaseIdent, Variable);

    if Result = nil then
      raise EUnknownIdentifier.CreateFmt(SUnknownIdentifier, [BaseIdent]);

    if Variable.IsProtected then
      Variable.ProtectionError;

    ref := Variable.ObjRef;
    CurPath := BaseIdent;

    for i := HighIndex - 1 downto 0 do
      if ALValueData[i].Kind = skIdentifier then
      begin

        if not (ref is TAlgosimStructure) then
          raise EAlgosimObjectException.CreateFmt(SObjectNotAStructure, [CurPath]);

        mbrname := ALValueData[i].Ident;
        mbridx := TAlgosimStructure(ref).IndexOfName(mbrname);
        if mbridx = -1 then
          raise EStructureException.CreateFmt(SNamedStructNoMemberFound,
            [CurPath, mbrname]);

        if i = 0 then
        begin
          OwnsValue := False;
          TAlgosimStructure(ref).Values[mbridx] := AValue;
          Exit;
        end;

        ref := TAlgosimStructure(ref).Members[mbridx].Value;
        CurPath := CurPath + ALValueData[i].ToString;

      end

      else
      begin

        if i = 0 then
        begin
          OwnsValue := False;
          ref.SetSubscript(ALValueData[i], AValue);
          Exit;
        end;

        ref := ref.GetSubscriptedRef(ALValueData[i]);
        CurPath := CurPath + ALValueData[i].ToString;

      end;

  finally
    if OwnsValue then AValue.Free;
  end;

end;

function TObjStoreStack.Top: TAlgosimObjStore;
begin
  if FStores.Count > 0 then
    Result := FStores[FStores.Count - 1]
  else
    Result := nil;
end;

function TObjStoreStack.Bottom: TAlgosimObjStore;
begin
  if FStores.Count > 0 then
    Result := FStores[0]
  else
    Result := nil;
end;

function TObjStoreStack.GetObjRef(const AName: string;
  out AValue: TAlgosimObject): TAlgosimObjStore;
var
  i: Integer;
  Variable: TAlgosimVariable;
begin
  for i := FStores.Count - 1 downto 0 do
    if FStores[i].Objs.TryGetValue(AName, Variable) then
    begin
      AValue := Variable.ObjRef;
      Exit(FStores[i]);
    end;
  raise EUnknownIdentifier.CreateFmt(SUnknownIdentifier, [AName]);
end;

function TObjStoreStack.GetObjRef(const ALValueData: TLValueData;
  out AValue: TAlgosimObject): TStoreStackRes;
begin
  Result := GetObjRefOrValue(ALValueData, AValue, False);
end;

function TObjStoreStack.GetObjRefOrValue(const ALValueData: TLValueData;
  out AValue: TAlgosimObject; AAsValue: Boolean): TStoreStackRes;
var
  HighIndex: Integer;
  BaseIdent: string;
  Variable: TAlgosimVariable;
  i: Integer;
  ref: TAlgosimObject;
begin

  Result.Clear;

  if ALValueData = nil then
    raise EExpressionException.Create(SUnassignedLValueData);

  if ALValueData.Count = 0 then
    raise EExpressionException.Create(SEmptyLValueData);

  HighIndex := ALValueData.Count - 1;

  if (ALValueData[HighIndex].Kind <> skIdentifier) or ALValueData[HighIndex].Ident.IsEmpty then
    raise EExpressionException.Create(SInvalidLValueDataRoot);

  BaseIdent := ALValueData[HighIndex].Ident;

  Result.Store := TryGetVariable(BaseIdent, Variable);

  if Result.Store = nil then
    raise EUnknownIdentifier.CreateFmt(SUnknownIdentifier, [BaseIdent]);

  if Variable.IsProtected and not AAsValue then
  begin
    Result.Success := False;
    Exit;
  end;

  ref := Variable.ObjRef;

  for i := HighIndex - 1 downto 0 do
    begin

      if (i = 0) and AAsValue then
      begin
        AValue := ref.GetSubscriptedValue(ALValueData[i]);
        Result.Success := True;
        Exit;
      end;

      Result.Success := ref.TryGetSubscriptedRef(ALValueData[i], ref);
      if not Result.Success then
      begin
        AValue := nil;
        Exit;
      end;

    end;

  if AAsValue then
    AValue := ref.Clone
  else
    AValue := ref;

  Result.Success := True;

end;

function TObjStoreStack.GetValue(const AName: string;
  out AValue: TAlgosimObject): TAlgosimObjStore;
begin
  Result := GetObjRef(AName, AValue);
  AValue := AValue.Clone;
end;

function TObjStoreStack.GetValue(const ALValueData: TLValueData;
  out AValue: TAlgosimObject): TStoreStackRes;
begin
  Result := GetObjRefOrValue(ALValueData, AValue, True);
end;

function TObjStoreStack.GetVariableList: TArray<string>;
var
  D: TDictionary<string, Pointer>;
  L: TList<string>;
  S: string;
  i: Integer;
begin
  D := TDictionary<string, Pointer>.Create;
  try
    L := TList<string>.Create;
    try
      for i := FStores.Count - 1 downto 0 do
        for s in FStores[i].GetVariableList do
          if not D.ContainsKey(s) then
          begin
            L.Add(s);
            D.Add(s, nil);
          end;
      Result := L.ToArray;
      TArray.Sort<string>(Result);
    finally
      L.Free;
    end;
  finally
    D.Free;
  end;
end;

function TObjStoreStack.TryGetVariable(const AName: string;
  out AVariable: TAlgosimVariable): TAlgosimObjStore;
var
  i: Integer;
begin
  for i := FStores.Count - 1 downto 0 do
    if FStores[i].Objs.TryGetValue(AName, AVariable) then
      Exit(FStores[i]);
  Result := nil;
end;

{ TStoreStackRes }

procedure TStoreStackRes.Clear;
begin
  Self.Store := nil;
  Self.Success := False;
end;

end.