{ $Id: gdbtypeinfo.pp 61084 2019-04-30 11:39:43Z martin $ }
{                        ----------------------------------------------
                            GDBTypeInfo.pp  -  Debugger helper class 
                         ----------------------------------------------

 @created(Wed Mar 29th WET 2003)
 @lastmod($Date: 2019-04-30 13:39:43 +0200 (Di, 30 Apr 2019) $)
 @author(Marc Weustink <marc@@dommelstein.net>)

 This unit contains a helper class for decoding PType output.


 ***************************************************************************
 *                                                                         *
 *   This source is free software; you can redistribute it and/or modify   *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This code is distributed in the hope that it will be useful, but      *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
 *                                                                         *
 ***************************************************************************
}
unit GDBTypeInfo;
{$mode objfpc}{$H+}
interface

uses
  Classes, SysUtils, math,
  // LCL
  LclProc,
  // LazUtils
  LazLoggerBase, LazStringUtils,
  // DebuggerIntf
  DbgIntfBaseTypes, DbgIntfDebuggerBase,
  // LazDebuggerGdbmi
  DebugUtils, GDBMIMiscClasses;

(*
  ptype = {
     family = "class" | "record" | "enum" | "set" | "procedure" | "function" | "simple" | "pointer"
    [ ancestor = "...", ]
    [ private = "[" ( "{" name = "...", type = ptype "}" )* "}," ]
    [ protected = "[" ( "{" name = "...", type = ptype "}" )* "}," ]
    [ public = "[" ( "{" name = "...", type = ptype "}" )* "},"]
    [ published = "[" ( "{" name = "...", type = ptype "}" )* "}," ]
    [ members = "[" ( "..." )* "]," | "[" ( "{" name = "...", type = "..." "}" )* "]," ]
    [ args = "[" ( "..." )* "]," ]
    [ result = "..." ]
    [ name = "..." ]
    [ type = "..." ]


  For example results, comparision between similar types, different GDB versions
  or stabs vs dwarf, see the folder "test/gdb responses/"

*)
type

  TGDBPTypeResultFlag =
    (ptprfParamByRef,
     ptprfPointer,
     ptprfNoStructure,     // for Class or Record: no full class declaration, type ends after class keyword; DWARF "whatis TFoo"
                           // includes "record {...}"
     ptprfDynArray,
     ptprfNoBounds,        // no bounds for array found
     ptprfEmpty,
     ptprfDeclarationInBrackets  // e.g ^(array ...) / "&^()" is/are not included in BaseDeclaration
    );
  TGDBPTypeResultFlags = set of TGDBPTypeResultFlag;
  TGDBPTypeResultKind =
    (ptprkNotEvaluated, ptprkError, ptprkSimple, ptprkClass, ptprkRecord,
     ptprkEnum, ptprkSet, ptprkArray, ptprkProcedure, ptprkFunction);

  TGDBPTypeResult = record
    GdbDescription: string;
    Flags: TGDBPTypeResultFlags;
    Kind: TGDBPTypeResultKind;
    Name, BaseName: TPCharWithLen; // BaseName is without ^&
    BoundLow, BoundHigh: TPCharWithLen;
    Declaration, BaseDeclaration: TPCharWithLen; // BaseDeclaration only for Array and Set types, see note on ptprfDeclarationInBrackets
    PointerCount: Integer;
    // type of array entry, or set-enum
    SubName, BaseSubName: TPCharWithLen;
    SubFlags: TGDBPTypeResultFlags;
    SubKind: TGDBPTypeResultKind;
    // multi-dim array
    NestArrayCount: Integer;
    NestArray: array of record  // reverse order, last entry is first nest level
      Flags: TGDBPTypeResultFlags;
      BoundLow, BoundHigh: TPCharWithLen;
      PointerCount: Integer;
    end;
  end;

  TGDBCommandRequestType = (gcrtPType, gcrtEvalExpr);

  PGDBPTypeRequest = ^TGDBPTypeRequest;
  TGDBPTypeRequest = record
    Request: string;
    ReqType: TGDBCommandRequestType;
    Result: TGDBPTypeResult;
    Error: string;
    Next: PGDBPTypeRequest;
  end;


  (* List:      "ACount", "+", "1"
     Array:     "Item[1][2]"
     Cast/Call: "Foo(Bar)"
  *)

  TGDBExprTextOption = (
    toWithStringFix,   // Adjust index for string (1 based)
    toSkipArrayIdx     // Replace array index with low bound (for ptype)
  );
  TGDBExprTextOptions = set of TGDBExprTextOption;

  { TGDBExpressionPart }

  TGDBExpressionPart = class
  protected
    FText: TPCharWithLen;
    function GetParts({%H-}Index: Integer): TGDBExpressionPart; virtual;
    function GetTextFixed({%H-}AOpts: TGDBExprTextOptions=[]): String; virtual;
    function GetText: String;
    function GetTextStrFixed: String;
    function ParseExpression(AText: PChar; ATextLen: Integer): TGDBExpressionPart;
    procedure Init; virtual;
    procedure InitReq(var AReqPtr: PGDBPTypeRequest; var AReqVar: TGDBPTypeRequest;
                      AReqText: String; AType: TGDBCommandRequestType = gcrtPType);
  public
    function NeedValidation(var AReqPtr: PGDBPTypeRequest): Boolean; virtual;
    function MayNeedStringFix: Boolean; virtual;
    function MayNeedTypeCastFix: Boolean; virtual;
  public
    constructor Create;
    function IsNamedOperator: Boolean;
    function PartCount: Integer; virtual;
    property Parts[Index: Integer]: TGDBExpressionPart read GetParts;
    property Text: String read GetText;
    property TextStrFixed: String read GetTextStrFixed;
    property TextEx[AOpts: TGDBExprTextOptions]: String read GetTextFixed;
  end;

  { TGDBExpression }

  TGDBExpression = class(TGDBExpressionPart)
  private
    FTextStr: String;
  protected
    FExpressionPart: TGDBExpressionPart;
    function GetTextFixed(AOpts: TGDBExprTextOptions=[]): String; override;
    function GetParts(Index: Integer): TGDBExpressionPart; override;
  public
    constructor CreateSimple(AText: PChar; ATextLen: Integer);
    constructor Create(AText: PChar; ATextLen: Integer); virtual; overload;
    constructor Create(ATextStr: String); overload;
    destructor Destroy; override;
    function PartCount: Integer; override;
    function IsCommaSeparated: Boolean;
  end;

  { TGDBExpressionPartNumber }

  TGDBExpressionPartNumber = class(TGDBExpression)
  protected
    function GetTextFixed(AOpts: TGDBExprTextOptions=[]): String; override;
  public
    constructor Create(AText: PChar; ATextLen: Integer); override; overload;
  end;

  { TGDBExpressionPartBracketed }

  TGDBExpressionPartBracketed = class(TGDBExpression)
  protected
    function GetTextFixed(AOpts: TGDBExprTextOptions=[]): String; override;
    function GetPlainText(AOpts: TGDBExprTextOptions=[]): String;
  public
    constructor Create(AText: PChar; ATextLen: Integer); override; overload;
  end;

  { TGDBExpressionPartListBase }

  TGDBExpressionPartListBase = class(TGDBExpressionPart)
  private
    FList: TFPList;
  protected
    function GetParts(Index: Integer): TGDBExpressionPart; override;
    function GetTextFixed(AOpts: TGDBExprTextOptions=[]): String; override;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
    procedure ClearShared;
    function  Add(APart: TGDBExpressionPart):Integer;
    procedure Insert(AIndex: Integer; APart: TGDBExpressionPart);
    procedure Delete(AIndex: Integer);
    function  PartCount: Integer; override;
  end;

  TGDBExpressionPartList = class(TGDBExpressionPartListBase)
  public
    function AddList(APartList: TGDBExpressionPartList):Integer;
  end;

  { TGDBExpressionPartCommaList }

  TGDBExpressionPartCommaList = class(TGDBExpressionPartList)
  protected
    function GetTextFixed(AOpts: TGDBExprTextOptions=[]): String; override;
  end;

  { TGDBExpressionPartArrayIdx }

  TGDBExpressionPartArrayIdx = class(TGDBExpressionPartBracketed)
  private
    FArrayPTypeNestIdx: integer;
    FArrayPTypePointerIdx: integer;
    FPTypeIndexReq: TGDBPTypeRequest;
    FVarParam: Boolean;
    FPTypeReq: TGDBPTypeRequest;
    FPTypeDeRefReq: TGDBPTypeRequest;
    function GetArrayPTypeIsDeRef: boolean;
    function GetArrayPTypeIsPointer: boolean;
    function GetArrayPTypeResult: TGDBPTypeResult;
  protected
    procedure Init; override;
    procedure InitReq(var AReqPtr: PGDBPTypeRequest; AReqText: String); overload;
    procedure InitDeRefReq(var AReqPtr: PGDBPTypeRequest; AReqText: String);
    procedure InitIndexReq(var AReqPtr: PGDBPTypeRequest);
    function GetTextFixed(AOpts: TGDBExprTextOptions=[]): String; override;
    property VarParam: Boolean read FVarParam write FVarParam;
    property PTypeReq: TGDBPTypeRequest read FPTypeReq write FPTypeReq;
    property PTypeDeRefReq: TGDBPTypeRequest read FPTypeDeRefReq write FPTypeDeRefReq;
    property PTypeIndexReq: TGDBPTypeRequest read FPTypeIndexReq write FPTypeIndexReq;
    property ArrayPTypeResult: TGDBPTypeResult read GetArrayPTypeResult;
    property ArrayPTypeIsDeRef: boolean read GetArrayPTypeIsDeRef;
    property ArrayPTypeIsPointer: boolean read GetArrayPTypeIsPointer;
    property ArrayPTypeNestIdx: integer read FArrayPTypeNestIdx write FArrayPTypeNestIdx;
    property ArrayPTypePointerIdx: integer read FArrayPTypePointerIdx write FArrayPTypePointerIdx;
    // for comma separated
    function CreateExpressionForSubIndex(AIndex: Integer): TGDBExpressionPartArrayIdx;
  end;

  { TGDBExpressionPartArray }

  TGDBExpressionPartArray = class(TGDBExpressionPartListBase)
  private
    FNeedTypeCast: Boolean;
    FMaybeString: Boolean;
    function GetIndexParts(Index: Integer): TGDBExpressionPartArrayIdx;
  protected
    function GetTextFixed(AOpts: TGDBExprTextOptions=[]): String; override;
    function GetTextToIdx(AIdx: Integer; AOpts: TGDBExprTextOptions=[]): String;
    function IndexCount: Integer;
    property IndexPart[Index: Integer]: TGDBExpressionPartArrayIdx read GetIndexParts;
  public
    constructor Create(ALeadExpresion: TGDBExpressionPart);
    function AddIndex(APart: TGDBExpressionPartArrayIdx):Integer;
    function NeedValidation(var AReqPtr: PGDBPTypeRequest): Boolean; override;
    function MayNeedStringFix: Boolean; override;
    property NeedTypeCast: Boolean read FNeedTypeCast write FNeedTypeCast;
  end;

  { TGDBExpressionPartCastCall }
  TTypeCastFixFlag = (tcfUnknown, tcfEvalNeeded, tcfNoFixNeeded, tcfFixNeeded);

  TGDBExpressionPartCastCall = class(TGDBExpressionPartListBase)
  private
    FIsFunction: Boolean;
    FIsTypeCast: Boolean;
    FPTypeReq: TGDBPTypeRequest;
    FTypeCastFixFlag: TTypeCastFixFlag;
  protected
    procedure Init; override;
    function GetTextFixed(AOpts: TGDBExprTextOptions=[]): String; override;
    property PTypeReq: TGDBPTypeRequest read FPTypeReq write FPTypeReq;
  public
    constructor Create(ALeadExpresion: TGDBExpressionPart);
    function AddBrackets(APart: TGDBExpressionPart):Integer;
    function NeedValidation(var AReqPtr: PGDBPTypeRequest): Boolean; override;
    function MayNeedTypeCastFix: Boolean; override;
    property IsFunction: Boolean read FIsFunction;
    property IsTypeCast: Boolean read FIsTypeCast;
  end;


  { TGDBPTypeRequestCacheEntry }

  TGDBPTypeRequestCacheEntry = class
  protected
    FRequest: TGDBPTypeRequest;
    FStackFrame: Integer;
    FThreadId: Integer;
  public
    property ThreadId: Integer read FThreadId;
    property StackFrame: Integer read FStackFrame;
    property Request: TGDBPTypeRequest read FRequest;
  end;

const
  TGDBPTypeReqCacheListCount = 64; // minimum 8
type
  TGDBPTypeRequestCache = class
  private
    FLists: Array[0..TGDBPTypeReqCacheListCount - 1] of TFPList;
    function GetRequest(Index: Integer): TGDBPTypeRequest;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
    function IndexOf(AThreadId, AStackFrame: Integer; ARequest: TGDBPTypeRequest): Integer; virtual;
    procedure Add(AThreadId, AStackFrame: Integer; ARequest: TGDBPTypeRequest);
    property Request[Index: Integer]: TGDBPTypeRequest read GetRequest;
  end;

  { TGDBTypes }

  TGDBTypes = class(TDBGTypes)
  public
    constructor CreateFromCSV(AValues: String);
  end;

  { TGDBType }

  TGDBTypeCreationFlag = (gtcfClassIsPointer,
                          gtcfFullTypeInfo,
                          gtcfSkipTypeName,
                          gtcfExprIsType,
                          gtcfExprEvaluate,
                          gtcfExprEvalStrFixed,      // Evaluate with string fix, if needed; only if gtcfExprEvaluate is set
                          gtcfAutoCastClass,         // Find real class of instance, and use, instead of declared class of variable
                          gtcfForceArrayEval         // Used by RepeatCount, in case of "SomePointer[i]"
                         );
  TGDBTypeCreationFlags = set of TGDBTypeCreationFlag;

  TGDBTypeProcessState =
    (gtpsInitial, gtpsInitialSimple,
     gtpsSimplePointer,
     gtpsClass, gtpsClassAutoCast, gtpsClassPointer, gtpsFinishProcessClass, gtpsClassAncestor,
     gtpsArray,
     gtpsEvalExpr, gtpsEvalExprRepeated,
     gtpsEvalExprArray, gtpsEvalExprDynArray, gtpsEvalExprDynArrayGetData,
     gtpsFinished
    );

  TGDBTypeProcessRequest =
    (gptrPTypeExpr, gptrWhatisExpr, gptrPTypeOfWhatis,
     gptrPTypeExprDeRef, gptrPTypeExprDeDeRef,  // "Foo^", "Foo^^"  for Foo=Object, or &Object
     gptrEvalExpr, gptrEvalExprDeRef, gptrEvalExprCast,
     gptrEvalExpr2, gptrEvalExprDeRef2, gptrEvalExprCast2, // used by MaybeString
     gptrPtypeCustomAutoCast, gptrPtypeCustomAutoCast2,
     gptrInstanceClassName,
     gptrEvalCustomEval
    );
  TGDBTypeProcessRequests = set of TGDBTypeProcessRequest;

  TGDBType = class(TDBGType)
  private
    FInternalTypeName: string;
  private
    FEvalStarted: Boolean;
    FExpression, FPTypeExpression, FOrigExpression: string;
    FHasStringExprEvaluatedAsText: Boolean;
    FCreationFlags: TGDBTypeCreationFlags;
    FMaybeShortString: Boolean;

    // Value-Eval
    FExprEvaluatedAsText: String;
    FHasExprEvaluatedAsText: Boolean;
    FExprEvaluateFormat: TWatchDisplayFormat;
    FRepeatCount: Integer;

    // Sub-Types (FNext is managed by creator / linked list)
    FFirstProcessingSubType, FNextProcessingSubType: TGDBType;
    FRepeatFirstIndex: Integer;
    FStringExprEvaluatedAsText: String;
    FTypeInfoAncestor: TGDBType;

    FArrayIndexValues: Array of TGDBType;
    FArrayIndexValueLimit: Integer;
    FRepeatCountEval: TGDBType;

    // Gdb-Requests
    FEvalError: boolean;
    FEvalRequest, FLastEvalRequest: PGDBPTypeRequest;

    FProcessState: TGDBTypeProcessState;
    FProccesReuestsMade: TGDBTypeProcessRequests;
    FReqResults: Array [TGDBTypeProcessRequest] of TGDBPTypeRequest;

    FParsedExpression: TGDBExpression;

    FHasAutoTypeCastFix: Boolean;
    FAutoTypeCastName: String;

    procedure AddTypeReq(var AReq :TGDBPTypeRequest; const ACmd: string = '');
    procedure AddSubType(ASubType :TGDBType);
    function GetIsFinished: Boolean;
    function RequireRequests(ARequired: TGDBTypeProcessRequests; ACustomData: String = ''): Boolean;
    function IsReqError(AReqType: TGDBTypeProcessRequest; CheckResKind: Boolean = True): Boolean;
  protected
    procedure Init; override;
    function DebugString: String;
    property RepeatFirstIndex: Integer read FRepeatFirstIndex write FRepeatFirstIndex;
  public
    constructor CreateForExpression(const AnExpression: string;
                                    const AFlags: TGDBTypeCreationFlags;
                                    AFormat: TWatchDisplayFormat = wdfDefault;
                                    ARepeatCount: Integer = 0);
    destructor Destroy; override;
    function ProcessExpression: Boolean;
    property EvalRequest: PGDBPTypeRequest read FEvalRequest;
    property EvalError: boolean read FEvalError;
    property IsFinished: Boolean read GetIsFinished;

    property HasExprEvaluatedAsText: Boolean read FHasExprEvaluatedAsText;
    property ExprEvaluatedAsText: String read FExprEvaluatedAsText;

    // Expression with index fixed by -1 for string access
    property HasStringExprEvaluatedAsText: Boolean read FHasStringExprEvaluatedAsText;
    property StringExprEvaluatedAsText: String read FStringExprEvaluatedAsText;
  public
    // InternalTypeName: include ^ for TObject, if needed
    property InternalTypeName: string read FInternalTypeName;
  end;


function CreatePTypeValueList(AResultValues: String): TStringList;
function ParseTypeFromGdb(const ATypeText: string): TGDBPTypeResult;
function GDBMIMaybeApplyBracketsToExpr(e: string): string;

function dbgs(AFlag: TGDBPTypeResultFlag): string; overload;
function dbgs(AFlags: TGDBPTypeResultFlags): string; overload;
function dbgs(AFlag: TGDBTypeCreationFlag): string; overload;
function dbgs(AFlags: TGDBTypeCreationFlags): string; overload;
function dbgs(AState: TGDBTypeProcessState): string; overload;
function dbgs(AKind: TGDBPTypeResultKind): string; overload;
function dbgs(AReqType: TGDBCommandRequestType): string; overload;
function dbgs(AReq: TGDBPTypeRequest): string; overload;
function dbgs(AReqType: TGDBTypeProcessRequest): string; overload;
function dbgs(AReqTypes: TGDBTypeProcessRequests): string; overload;

implementation

const
  GdbCmdPType = 'ptype ';
  GdbCmdWhatIs = 'whatis ';
  GdbCmdEvaluate = '-data-evaluate-expression ';
var
  DBGMI_TYPE_INFO, DBG_WARNINGS: PLazLoggerLogGroup;

function GDBMIMaybeApplyBracketsToExpr(e: string): string;
var
  i: Integer;
  f: Boolean;
begin
  Result := e;
  if (e='') or ( (e[1] = '(') and (e[length(e)] = ')') ) then exit;
  f := False;
  i := length(e);
  while (i > 0) and (not f) do begin
    f := f or not(e[i] in ['a'..'z', 'A'..'Z', '0'..'9', '_']);
    dec(i);
  end;
  if f then
    Result := '(' + Result + ')';
end;

function CreatePTypeValueList(AResultValues: String): TStringList;
var
  S, Line: String;
  Lines: TStringList;

  procedure DoRecord;
  var
    n: Integer;
    S, Members: String;
  begin
    Result.Add('family=record');
    Members := '';

    //concatinate all lines and skip last end
    S := '';
    for n := 0 to Lines.Count - 2 do
      S := S + Lines[n];

    while S <> '' do
    begin
      if Members <> '' then Members := Members + ',';
      Members := Members + '{name=' + GetPart(['    '], [' '], S);
      Members := Members + ',type=' + GetPart([' : '], [';'], S) + '}';
    end;
    Result.Add('members=[' + Members + ']');
  end;

  procedure DoEnum;
  var
    n: Integer;
    S: String;
  begin
    Result.Add('family=enum');

    S := GetPart(['('], [], Line);
    //concatinate all lines
    for n := 0 to Lines.Count - 1 do
      S := S + Lines[n];

    S := GetPart([], [')'], S);
    Result.Add('members=[' + StringReplace(S, ' ', '', [rfReplaceAll]) + ']');
  end;

  procedure DoProcedure;
  var
    n: Integer;
    S: String;
  begin
    Result.Add('family=procedure');

    S := GetPart(['('], [''], Line);
    //concatinate all lines
    for n := 0 to Lines.Count - 1 do
      S := S + Lines[n];

    S := GetPart([''], [')'], S);
    Result.Add('args=[' + StringReplace(S, ', ', ',', [rfReplaceAll]) + ']');
  end;

  procedure DoFunction;
  var
    n: Integer;
    S, Args: String;
  begin
    Result.Add('family=function');

    S := GetPart(['('], [], Line);
    //concatinate all lines
    for n := 0 to Lines.Count - 1 do
      S := S + Lines[n];

    Args := GetPart([], [')'], S);
    S := GetPart([' : '], [], S);
    Result.Add('args=[' + StringReplace(Args, ', ', ',', [rfReplaceAll]) + ']');
    Result.Add('result=' + S);
  end;

  procedure DoClass;
  begin
    Result.Add('family=class');
    Result.Add('ancestor=' + GetPart([': public '], [' '], Line));
  end;

begin
  Result := TStringList.Create;
  if AResultValues = '' then Exit;

  Lines := TStringList.Create;
  try
    Lines.Text := AResultValues;
    if Lines.Count = 0 then Exit;
    Line := Lines[0];
    Lines.Delete(0);

    S := GetPart(['type = '], [' '], Line);
    if S = '' then Exit;
    if Pos(' = class ', Line) > 0
    then DoClass
    else if S[1] = '^'
    then begin
      Result.Add('family=pointer');
      Result.Add('type=' + GetPart(['^'], [' ='], S));
    end
    else if S = 'set'
    then begin
      Result.Add('family=set');
      Result.Add('type=' + Copy(Line, 5, Length(Line)));
    end
    else if S = 'procedure'
    then DoProcedure
    else if S = 'function'
    then DoFunction
    else if Pos(' = (', Line) > 0
    then DoEnum
    else if Pos(' = record', Line) > 0
    then DoRecord
    else begin
      Result.Add('family=simple');
      Result.Add('type=' + S);
    end;

  finally
    Lines.Free;
  end;
end;

function ParseTypeFromGdb(const ATypeText: PChar; const ATypeTextLen: Integer): TGDBPTypeResult;
var
  i: Integer;
  CurPtr, LineEndPtr, EndPtr, BaseDeclPtr, DeclPtr, DeclEndPtr: PChar;
  HelpPtr, HelpPtr2: PChar;
  SubRes: TGDBPTypeResult;

  procedure SkipSpaces(var p: PChar); inline;
  begin
    while (p^ = ' ') do inc(p);
  end;

  function CheckKeyword: TGDBPTypeResultKind;
  begin
    Result := ptprkSimple;
    // Might be: "set of ", "class", "record =", "array [", '<invalid unnamed"...,
    case CurPtr^ of
      's', 'S': begin
          if (LineEndPtr - CurPtr >= 6 )
          and  (UpperCase(copy(CurPtr, 1, 7)) = 'SET OF ')
          then
            Result := ptprkSet;
        end;
      'r', 'R': begin
          if (LineEndPtr - CurPtr >= 5 )
          and (UpperCase(copy(CurPtr, 1, 6)) = 'RECORD')
          and ((CurPtr+6)^ in [' ', ')', #13, #0])
          then
            Result := ptprkRecord;
        end;
      'c', 'C': begin
          if (LineEndPtr - CurPtr >= 4 )
          and (UpperCase(copy(CurPtr, 1, 5)) = 'CLASS')
          and ((CurPtr+5)^ in [' ', ')', #13, #0])
          then
            Result := ptprkClass;
        end;
      'a', 'A': begin
          if (LineEndPtr - CurPtr >= 5 )
          and (UpperCase(copy(CurPtr, 1, 6)) = 'ARRAY ')
          then
            Result := ptprkArray;
        end;
      '<': begin
          if (LineEndPtr - CurPtr >= 35 )
          and (copy(CurPtr, 1, 36) = '<invalid unnamed pascal type code 8>')
          then
            Result := ptprkSet;
        end;
      'p', 'P': begin
          if (LineEndPtr - CurPtr >= 8 )
          and (UpperCase(copy(CurPtr, 1, 9)) = 'PROCEDURE')
          and ((CurPtr+9)^ in [' ', '(', ')', #13, #0])
          then
            Result := ptprkProcedure;
        end;
      'f', 'F': begin
          if (LineEndPtr - CurPtr >= 7 )
          and (UpperCase(copy(CurPtr, 1, 8)) = 'FUNCTION')
          and ((CurPtr+8)^ in [' ', '(', ')', #13, #0])
          then
            Result := ptprkFunction;
        end;

    end;
  end;

  function CheckIsEnum: Integer;
  var
    p: PChar;
  begin
    Result := -1;
    if CurPtr^ <> '(' then exit;

    p := CurPtr;
    while not(p^ in [')', #0]) do inc(p);
    if (p <= LineEndPtr) and (p^ = ')') then
      Result := p - CurPtr + 1;
  end;

  procedure SetPCharLen(var ATarget: TPCharWithLen; AStartPtr, AEndPtr: PChar);
  begin
    ATarget.Ptr := AStartPtr;
    ATarget.Len := AEndPtr - AStartPtr + 1;
  end;

begin
  try
  Result.Flags := [];
  Result.Kind := ptprkError;
  Result.Name.Ptr := nil;
  Result.Name.Len := 0;
  Result.BaseName.Ptr := nil;
  Result.BaseName.Len := 0;
  Result.Declaration.Ptr := nil;
  Result.Declaration.Len := 0;
  Result.BaseDeclaration.Ptr := nil;
  Result.BaseDeclaration.Len := 0;
  Result.PointerCount := 0;
  Result.BoundLow.Ptr := nil;
  Result.BoundLow.Len := 0;
  Result.BoundHigh.Ptr := nil;
  Result.BoundHigh.Len := 0;
  Result.SubName.Ptr := nil;
  Result.SubName.Len := 0;
  Result.BaseSubName.Ptr := nil;
  Result.BaseSubName.Len := 0;
  Result.SubFlags := [];
  Result.SubKind := ptprkError;
  Result.NestArrayCount := 0;
  If (ATypeText = nil) or (ATypeTextLen = 0) then exit;

  (* type = [&^][name]
     type = [&^][name] = class|record : public
     type = [&^][name] = (a,b,c)
     type = [&^]array ...
     type = [&^]set of [name] = (a,b)
     type = [&^](.....)
  *)



  CurPtr := ATypeText;
  EndPtr := ATypeText + ATypeTextLen-1;

  while (EndPtr > CurPtr) and (EndPtr^ in [#10, #13, ' ']) do dec (EndPtr);

  LineEndPtr := EndPtr;
  //limit LineEndPtr to first \n
  HelpPtr := CurPtr;
  while (true) do begin
    if HelpPtr > LineEndPtr - 1 then break;
    if (HelpPtr[0] in [#10, #13])
    then begin
      LineEndPtr := HelpPtr-1;
      while (LineEndPtr > CurPtr) and (LineEndPtr^ in [#10, #13, ' ']) do dec (LineEndPtr);
      break;
    end;
    inc(HelpPtr);
  end;


  BaseDeclPtr := CurPtr;
  DeclPtr := BaseDeclPtr;
  DeclEndPtr := LineEndPtr;

  // Leading ^&
  while True do begin
    case CurPtr^ of
      '^': begin
          include(Result.Flags, ptprfPointer);
          inc(Result.PointerCount);
        end;
      '&': include(Result.Flags, ptprfParamByRef);
      else break;
    end;
    inc(CurPtr);
  end;
  SkipSpaces(CurPtr); // shouldn'tever happen
  BaseDeclPtr := CurPtr;

  if CurPtr > LineEndPtr then begin
    include(Result.Flags, ptprfEmpty);
    exit;
  end;

  // entite type in brackest (), eg ^(array...)
  if CurPtr^ = '(' then begin
    Include(Result.Flags, ptprfDeclarationInBrackets);
    inc(CurPtr);
    SkipSpaces(CurPtr); // shouldn'tever happen
    BaseDeclPtr := CurPtr;
    DeclPtr := CurPtr; // not possible to capture with one line, as closing bracket may be on other line
    if DeclEndPtr^ = ')' then dec(DeclEndPtr);
    if LineEndPtr^ = ')' then dec(LineEndPtr);
    if EndPtr^ = ')' then dec(EndPtr);
  end;

  SetPCharLen(Result.BaseDeclaration, BaseDeclPtr, DeclEndPtr);
  SetPCharLen(Result.Declaration,     DeclPtr,     DeclEndPtr);

  if CurPtr^ = '=' then begin
    // skip ' = '
    inc(CurPtr);
    SkipSpaces(CurPtr);
  end
  else begin
    // process part before ' = '
    Result.Kind := CheckKeyword;
    if Result.Kind = ptprkSimple
    then begin
      // we may have   type = NAME = ....
      HelpPtr := CurPtr;
      while (HelpPtr <= LineEndPtr) and  not (HelpPtr^ in [#0..#31, ' ']) do inc(HelpPtr);
      HelpPtr2 := HelpPtr;  // HelpPtr2 = after [name]
      SkipSpaces(HelpPtr2);

      if (HelpPtr2^ = '=') or // TYPE WITH = (EQUAL)
         ((HelpPtr^ in [#0, #10, #13]) or (HelpPtr > LineEndPtr))
      then begin
        // Type without space, use as name
        SetPCharLen(Result.Name,     DeclPtr, HelpPtr-1);
        SetPCharLen(Result.BaseName, BaseDeclPtr, HelpPtr-1);

        if (HelpPtr^ in [#0, #10, #13]) or (HelpPtr > LineEndPtr) then exit;

        CurPtr := HelpPtr2 + 1; // after ' = '
        SkipSpaces(CurPtr);
        BaseDeclPtr := CurPtr;  // Declaration after ' = '
        DeclPtr := CurPtr;
      end
      else begin
        // Type is a declaration with spaces
        // (base)declaration is already set
        exit;
      end;
    end;
  end;

  // after ' = '

  i := CheckIsEnum;
  if i > 0 then begin
    Result.Kind := ptprkEnum;
    SetPCharLen(Result.BaseDeclaration, CurPtr, CurPtr+i-1);
    SetPCharLen(Result.Declaration,     CurPtr, CurPtr+i-1);
    exit;
  end;

  Result.Kind := CheckKeyword;
  if Result.Kind = ptprkSimple then begin
    Result.Kind := ptprkError;
    debugln('** WARNING: ptype info format error: ' + ATypeText);
    exit;
  end;

  // now we should be AT a keyword, we may have a name set already // Enum are handled already too
  while LineEndPtr^ = ' ' do dec(LineEndPtr);
  case Result.Kind of
    ptprkClass: begin
        HelpPtr := CurPtr + 5;
        SkipSpaces(HelpPtr);
        if HelpPtr^ in [#10, #13] then include(Result.Flags, ptprfNoStructure);
        SetPCharLen(Result.Declaration, DeclPtr, LineEndPtr);
      end;
    ptprkRecord: begin
        HelpPtr := CurPtr + 6;
        SkipSpaces(HelpPtr);
        if HelpPtr^ in ['{'] then begin
          include(Result.Flags, ptprfNoStructure);
          SetPCharLen(Result.Declaration, DeclPtr, CurPtr + 5);
        end
        else
          SetPCharLen(Result.Declaration, DeclPtr, LineEndPtr);
      end;
    ptprkSet: begin
        if CurPtr^ <> '<' then begin;
          SetPCharLen(Result.Declaration, DeclPtr, LineEndPtr);
          //CurPtr := Result.BaseDeclaration.Ptr + 3;
          CurPtr := CurPtr + 6;
          SkipSpaces(CurPtr);
          if (CurPtr^ = '=') then begin  // has enum, no name,
            CurPtr := CurPtr + 1;
            SkipSpaces(CurPtr);
          end;
          SetPCharLen(Result.SubName, CurPtr, LineEndPtr);
          while (CurPtr^ in ['^', '&']) and (CurPtr < LineEndPtr) do inc(CurPtr); // should not happen
          SetPCharLen(Result.BaseSubName, CurPtr, LineEndPtr);
          Result.SubKind := ptprkSimple;
        end
        else begin
          Result.Declaration.Ptr := nil;
          Result.Declaration.Len := 0;
          Result.BaseDeclaration.Ptr := nil;
          Result.BaseDeclaration.Len := 0;
        end;
      end;
    ptprkArray: begin
        SetPCharLen(Result.Declaration, DeclPtr, LineEndPtr);
        SetPCharLen(Result.BaseDeclaration, BaseDeclPtr, LineEndPtr);
        CurPtr := CurPtr + 5;
        SkipSpaces(CurPtr);
        include(Result.Flags, ptprfNoBounds);
        include(Result.Flags, ptprfDynArray);
        if CurPtr^ = '[' then begin
          inc(CurPtr);
          HelpPtr := CurPtr;
          while (HelpPtr^ in ['-', '0'..'9']) and (HelpPtr < LineEndPtr - 3) do inc (HelpPtr);
          if (HelpPtr > CurPtr) and (HelpPtr^ = '.') and  ((HelpPtr+1)^ = '.') then begin
            HelpPtr2 := HelpPtr + 2;
            while (HelpPtr2^ in ['-', '0'..'9']) and (HelpPtr2 < LineEndPtr - 1) do inc (HelpPtr2);
            if (HelpPtr2 > HelpPtr) and (HelpPtr2^ = ']') then begin
              exclude(Result.Flags, ptprfNoBounds);
              Result.BoundLow.Ptr := CurPtr;
              Result.BoundLow.Len := HelpPtr - CurPtr;
              Result.BoundHigh.Ptr := HelpPtr + 2;
              Result.BoundHigh.Len := HelpPtr2 - (HelpPtr + 2);
              if (HelpPtr2 - CurPtr <> 5) or (strlcomp(Result.BoundLow.Ptr, PChar('0..-1'), 5) <> 0) then
                exclude(Result.Flags, ptprfDynArray);
              CurPtr := HelpPtr2 + 1;
            end;
          end;
        end;

        SkipSpaces(CurPtr);
        if (CurPtr^ in ['o', 'O']) and ((CurPtr+1)^ in ['f', 'F']) then begin
          CurPtr := CurPtr + 2;
          SkipSpaces(CurPtr);

          SubRes := ParseTypeFromGdb(CurPtr, EndPtr - CurPtr + 1);
          if SubRes.Kind = ptprkArray then begin
            Result.SubName        := SubRes.SubName;
            Result.BaseSubName    := SubRes.BaseSubName;
            Result.SubFlags       := SubRes.SubFlags;
            Result.SubKind        := SubRes.SubKind;
            Result.NestArrayCount := SubRes.NestArrayCount + 1;
            Result.NestArray      := SubRes.NestArray;
            if length(Result.NestArray) < Result.NestArrayCount
            then SetLength(Result.NestArray, Result.NestArrayCount + 3);
            Result.NestArray[SubRes.NestArrayCount].Flags        := SubRes.Flags;
            Result.NestArray[SubRes.NestArrayCount].PointerCount := SubRes.PointerCount;
            Result.NestArray[SubRes.NestArrayCount].BoundLow     := SubRes.BoundLow;
            Result.NestArray[SubRes.NestArrayCount].BoundHigh    := SubRes.BoundHigh;
          end else begin
            Result.SubName        := SubRes.Name;
            Result.BaseSubName    := SubRes.BaseName;
            Result.SubFlags       := SubRes.Flags;
            Result.SubKind        := SubRes.Kind;
          end;


          //SetPCharLen(Result.SubName, CurPtr, LineEndPtr);
          //while (CurPtr^ in ['^', '&']) and (CurPtr < LineEndPtr) do inc(CurPtr);
          //SetPCharLen(Result.BaseSubName, CurPtr, LineEndPtr);
        end;
      end;
    ptprkProcedure, ptprkFunction: begin
        SetPCharLen(Result.Declaration, DeclPtr, LineEndPtr);
      end;
  end;
  finally
    DebugLn(DBGMI_TYPE_INFO, ['ParseTypeFromGdb: Flags=', dbgs(Result.Flags), ' Kind=', dbgs(Result.Kind), ' Name="', PCLenToString(Result.Name),'"' ]);
  end;
end;

function ParseTypeFromGdb(const ATypeText: string): TGDBPTypeResult;
var
  i: SizeInt;
begin
  i := pos('type = ', ATypeText);
  if i < 1
  then Result := ParseTypeFromGdb(PChar(ATypeText), length(ATypeText))
  else Result := ParseTypeFromGdb((@ATypeText[i])+7, length(ATypeText)-6-i);
  Result.GdbDescription := ATypeText;
end;

function dbgs(AFlag: TGDBPTypeResultFlag): string;
begin
  writestr(Result, AFlag);
end;

function dbgs(AFlags: TGDBPTypeResultFlags): string;
var
  i: TGDBPTypeResultFlag;
begin
  Result:='';
  for i := low(TGDBPTypeResultFlags) to high(TGDBPTypeResultFlags) do
    if i in AFlags then begin
      if Result <> '' then Result := Result + ', ';
      Result := Result + dbgs(i);
    end;
  if Result <> '' then Result := '[' + Result + ']';
end;

function dbgs(AFlag: TGDBTypeCreationFlag): string;
begin
  writestr(Result, AFlag);
end;

function dbgs(AFlags: TGDBTypeCreationFlags): string;
var
  i: TGDBTypeCreationFlag;
begin
  Result:='';
  for i := low(TGDBTypeCreationFlags) to high(TGDBTypeCreationFlags) do
    if i in AFlags then begin
      if Result <> '' then Result := Result + ', ';
      Result := Result + dbgs(i);
    end;
  if Result <> '' then Result := '[' + Result + ']';
end;

function dbgs(AState: TGDBTypeProcessState): string;
begin
  writestr(Result, AState);
end;

function dbgs(AKind: TGDBPTypeResultKind): string;
begin
  writestr(Result, AKind);
end;

function dbgs(AReqType: TGDBCommandRequestType): string;
begin
  WriteStr(Result, AReqType);
end;

function dbgs(AReq: TGDBPTypeRequest): string;
begin
  Result := 'Req="'+AReq.Request+'" type='+dbgs(AReq.ReqType)
    +' HasNext='+dbgs(AReq.Next <> nil)
    ;
end;

function dbgs(AReqType: TGDBTypeProcessRequest): string;
begin
  WriteStr(Result, AReqType);
end;

function dbgs(AReqTypes: TGDBTypeProcessRequests): string;
var
  i: TGDBTypeProcessRequest;
begin
  Result:='';
  for i := low(TGDBTypeProcessRequests) to high(TGDBTypeProcessRequests) do
    if i in AReqTypes then begin
      if Result <> '' then Result := Result + ', ';
      Result := Result + dbgs(i);
    end;
  if Result <> '' then Result := '[' + Result + ']';
end;

//TGDBTypeProcessRequests

{ TGDBExpressionPartCommaList }

function TGDBExpressionPartCommaList.GetTextFixed(AOpts: TGDBExprTextOptions
  ): String;
var
  i: Integer;
begin
  Result := '';
  if PartCount = 0 then
    exit;
  Result := Parts[0].GetTextFixed(AOpts);
  for i := 1 to PartCount - 1 do
    Result := Result + ',' + Parts[i].GetTextFixed(AOpts);
end;

{ TGDBExpressionPartArrayIdx }

function TGDBExpressionPartArrayIdx.GetArrayPTypeIsDeRef: boolean;
begin
  Result := (FPTypeReq.Result.Kind <> ptprkArray);
end;

function TGDBExpressionPartArrayIdx.GetArrayPTypeIsPointer: boolean;
begin
  if FArrayPTypeNestIdx < 0 then begin
    if ArrayPTypeIsDeRef
    then Result := True
    else Result := ptprfPointer in FPTypeReq.Result.Flags;
  end
  else begin
    Result := ptprfPointer in ArrayPTypeResult.NestArray[FArrayPTypeNestIdx].Flags;
  end;
end;

function TGDBExpressionPartArrayIdx.GetArrayPTypeResult: TGDBPTypeResult;
begin
  Result := FPTypeReq.Result;
  if (Result.Kind <> ptprkArray) then
    Result := FPTypeDeRefReq.Result;
end;

procedure TGDBExpressionPartArrayIdx.Init;
begin
  inherited Init;
  FPTypeReq.Result.Kind := ptprkNotEvaluated;
  FPTypeDeRefReq.Result.Kind := ptprkNotEvaluated;
  FPTypeIndexReq.Result.Kind := ptprkNotEvaluated;
  FVarParam := False;
  FArrayPTypeNestidx := -1;
  FArrayPTypePointerIdx := 0;
end;

procedure TGDBExpressionPartArrayIdx.InitReq(var AReqPtr: PGDBPTypeRequest; AReqText: String);
begin
  InitReq(AReqPtr, FPTypeReq, AReqText, gcrtPType);
end;

procedure TGDBExpressionPartArrayIdx.InitDeRefReq(var AReqPtr: PGDBPTypeRequest;
  AReqText: String);
begin
  InitReq(AReqPtr, FPTypeDeRefReq, AReqText, gcrtPType);
end;

procedure TGDBExpressionPartArrayIdx.InitIndexReq(var AReqPtr: PGDBPTypeRequest);
begin
  InitReq(AReqPtr, FPTypeIndexReq,
          GdbCmdEvaluate + Quote(GetPlainText([toSkipArrayIdx])), gcrtEvalExpr);
end;

function TGDBExpressionPartArrayIdx.GetTextFixed(AOpts: TGDBExprTextOptions
  ): String;
begin
  if toWithStringFix in AOpts then begin
    if FExpressionPart = nil
    then Result := PCLenPartToString(FText, 1, FText.Len-2)
    else Result := FExpressionPart.TextEx[AOpts];
    Result := FText.Ptr^ + Result + '-1' + (FText.Ptr + FText.Len-1)^;
  end
  else
     Result := inherited GetTextFixed(AOpts);
end;

function TGDBExpressionPartArrayIdx.CreateExpressionForSubIndex(AIndex: Integer): TGDBExpressionPartArrayIdx;
begin
  Result := TGDBExpressionPartArrayIdx.Create
            (FText.Ptr^ + Parts[AIndex].GetText + (FText.Ptr + FText.Len-1)^);
end;

{ TGDBExpressionPartList }

function TGDBExpressionPartList.AddList(APartList: TGDBExpressionPartList): Integer;
var
  i: Integer;
begin
  Result := -1;
  if APartList.PartCount = 0 then exit;
  Result := FList.add(APartList.Parts[0]);
  for i := 1 to APartList.PartCount - 1 do
    FList.add(APartList.Parts[i]);
end;

{ TGDBExpressionPartArray }

function TGDBExpressionPartArray.GetIndexParts(Index: Integer): TGDBExpressionPartArrayIdx;
begin
  Result := TGDBExpressionPartArrayIdx(Parts[Index+1]);
  Assert(not Result.IsCommaSeparated, 'GetIndexParts not IsCommaSeparated');
end;

function TGDBExpressionPartArray.GetTextFixed(AOpts: TGDBExprTextOptions
  ): String;
begin
  Result := GetTextToIdx(IndexCount-1, AOpts);
end;

function TGDBExpressionPartArray.GetTextToIdx(AIdx: Integer;
  AOpts: TGDBExprTextOptions): String;
// toSkipArrayIdx: replace all indexes with 0. For ptype the position does not matter

  function GetPointerCast(AnIdxPart: TGDBExpressionPartArrayIdx; out PointerCnt: Integer): String;
  var
    PTRes: TGDBPTypeResult;
    i: Integer;
  begin
    Result := '';
    PointerCnt := 0;
    if not AnIdxPart.ArrayPTypeIsPointer then exit;
    PTRes := AnIdxPart.ArrayPTypeResult;
    if PTRes.SubName.Len = 0 then exit;

    i := PTRes.NestArrayCount - 1;
    if i >= 0 then begin
      while (i >= 0) and (ptprfPointer in PTRes.NestArray[i].Flags) do dec(i);
      if i >= 0 then exit; // cant cast, if contains static array
      PointerCnt := PTRes.NestArrayCount+1;
      Result := StringOfChar('^', PointerCnt) + PCLenToString(PTRes.SubName);
      exit;
    end;

    PointerCnt := PTRes.PointerCount;
    // If PTRes is the result of an extra de-ref in the ptype, then we need to add that pointer back
    if AnIdxPart.ArrayPTypeIsDeRef then
      inc(PointerCnt);
    Result := StringOfChar('^', PointerCnt) + PCLenToString(PTRes.SubName);
  end;

var
  i, j, PCastCnt: Integer;
  IdxPart: TGDBExpressionPartArrayIdx;
  PTResult: TGDBPTypeResult;
  NeedTCast: Boolean;
  s, LowVal: String;
begin
  Result := Parts[0].TextEx[AOpts];
  PCastCnt := 0;

  if AIdx < 0 then exit;

  for i := 0 to AIdx do begin
    IdxPart := TGDBExpressionPartArrayIdx(IndexPart[i]);
    PTResult := IdxPart.ArrayPTypeResult;
    if toSkipArrayIdx in AOpts then
      LowVal := '[' + IntToStr(PCLenToInt(PTResult.BoundLow)) + ']';

    if PCastCnt > 0 then dec(PCastCnt);

    if not (PTResult.Kind = ptprkArray)
    then begin
      // maybe pointer with index access
      if toSkipArrayIdx in AOpts
      then Result := Result + LowVal
      else
      if (toWithStringFix in AOpts) and (i = IndexCount - 1)
      then Result := Result + IdxPart.TextStrFixed
      else Result := Result + IdxPart.Text;
      continue;
    end;

    if ((PTResult.NestArrayCount > 0) and (IdxPart.ArrayPTypeNestIdx <> PTResult.NestArrayCount-1)) or
       (IdxPart.ArrayPTypePointerIdx > 0)
    then begin
      // nested array / no named type known
      if (PCastCnt = 0) and IdxPart.ArrayPTypeIsPointer
      then Result := Result + '^';
      if toSkipArrayIdx in AOpts
      then Result := Result + LowVal
      else Result := Result + IdxPart.Text;
      continue;
    end;


    NeedTCast := FNeedTypeCast and (i = IndexCount-1);

    if IdxPart.ArrayPTypeIsPointer
    then begin
      //dyn array
      s := '';
      if IdxPart.VarParam then
        s := GetPointerCast(IdxPart, j);
      if s <> '' // IdxPart.VarParam and (PTResult.SubName.Len > 0)                        // var param can only be set for the un-inxed variable
      then begin
        // fpc 2.4.4 Var-param dynarray
        // var param are marked with a "&" in fpc 2.4. They are a semi automatic pointer.
        // Any such var param, that points to an internal pointer type (e.g dyn array) must be typecasted, to trigger the semi automatic pointer of the var-param
        // For single dyn array: ^Foo(var)[1]
        // For nested dyn array: ^^Foo(var)[1][2]  // the ^ in front of the index must be skipped, as the dyn array was casted into a pointer
        Result := s + '(' + Result + ')';
        NeedTCast := False;
        PCastCnt := j;
      end
      else
      if (PCastCnt = 0) then
        Result := Result + '^';
    end;

    if toSkipArrayIdx in AOpts
    then Result := Result + LowVal
    else Result := Result + IdxPart.Text;

    if NeedTCast and (PTResult.SubName.Len > 0)
    then
      Result := PCLenToString(PTResult.SubName) + '(' + Result + ')';

  end;

end;

function TGDBExpressionPartArray.IndexCount: Integer;
begin
  Result := PartCount - 1;
end;

constructor TGDBExpressionPartArray.Create(ALeadExpresion: TGDBExpressionPart);
begin
  inherited Create;
  FNeedTypeCast := False;
  Add(ALeadExpresion);
end;

function TGDBExpressionPartArray.AddIndex(APart: TGDBExpressionPartArrayIdx): Integer;
var
  j: Integer;
begin
  if APart.IsCommaSeparated then begin
    For j := 0 to APart.PartCount-1 do
      Result := Add(APart.CreateExpressionForSubIndex(j));
    APart.Free;
  end
  else
    Result := Add(APart);
end;

function TGDBExpressionPartArray.NeedValidation(var AReqPtr: PGDBPTypeRequest): Boolean;
var
  i, j: Integer;
  IdxPart, IdxPart2: TGDBExpressionPartArrayIdx;
  PTReq, PTDeRefReq: TGDBPTypeRequest;
  ArrRes: TGDBPTypeResult;
  ResultList: TGDBMINameValueList;
  s: String;
begin
  Result := False;
  // Index
  for i := 1 to PartCount - 1 do
    if Parts[i].NeedValidation(AReqPtr) then
      Result := True;

  if Parts[0].NeedValidation(AReqPtr) // Array-Variable
  then begin
    Result := True;
    exit;
  end;
  if Result then exit;

  i := 0;
  while i < IndexCount do begin
    // IdxPart is the NEXT index. We evaluate the expression BEFORE IdxPart
    IdxPart := IndexPart[i];
    PTReq := IdxPart.PTypeReq;

    if PTReq.Result.Kind = ptprkError
    then begin
      // "Parts[i]" Check if the part before IndexParts[i] needs typecastfixing
      if (pos('address 0x0', PTReq.Error) > 0) and Parts[i].MayNeedTypeCastFix
      then begin
        Result := Parts[i].NeedValidation(AReqPtr);
        PTReq.Result.Kind := ptprkNotEvaluated; // Reset the request
        IdxPart.PTypeReq := PTReq;
      end;

      exit; // If Result = False; // no way to find more info
            // Todo, simply to next entry, and check for "pointer math on incomplete type"
    end;

    if PTReq.Result.Kind = ptprkNotEvaluated
    then begin
      (* ptype ArrayBaseWithoutIndex *)
      IdxPart.VarParam := False;
      // InitReq sets: PTReq.Result.Kind = ptprkError;
      IdxPart.InitReq(AReqPtr, GdbCmdPType + GetTextToIdx(i-1, [toSkipArrayIdx]));
      Result := True;
      exit;
    end
    else
    if (not IdxPart.VarParam) and (ptprfParamByRef in PTReq.Result.Flags) // seen an "&" in the gdb result
    then begin
      (* ptype ArrayBaseWithoutIndex^ *)
      // FPC 2.2.4 encoded "var param" in a special way, and we need an extra deref)
      IdxPart.VarParam := True;
      IdxPart.InitReq(AReqPtr, GdbCmdPType + GDBMIMaybeApplyBracketsToExpr(GetTextToIdx(i-1, [toSkipArrayIdx])) + '^');
      Result := True;
      exit;
    end;

    (* With Dwarf gdb may return "type = ^TFoo" for an array
       And the for the derefferenced expr "type = array of TFoo"
    *)
    PTDeRefReq := IdxPart.PTypeDeRefReq;
    if (PTReq.Result.Kind <> ptprkArray) and
       (ptprfPointer in PTReq.Result.Flags) and
       (PTDeRefReq.Result.Kind = ptprkNotEvaluated)
    then begin
      (* ptype ArrayBaseWithoutIndex^ or ptype ArrayBaseWithoutIndex^^ *)
      if IdxPart.VarParam
      then IdxPart.InitDeRefReq(AReqPtr, GdbCmdPType + GDBMIMaybeApplyBracketsToExpr(GetTextToIdx(i-1, [toSkipArrayIdx])) + '^^')
      else IdxPart.InitDeRefReq(AReqPtr, GdbCmdPType + GDBMIMaybeApplyBracketsToExpr(GetTextToIdx(i-1, [toSkipArrayIdx])) + '^');
      Result := True;
      exit;
    end;

    // we may have nested array (dyn array only):
    // - ^^(array ...)
    // - array ... of array
    // A combination of both is not expected

    ArrRes := IdxPart.ArrayPTypeResult;
    if (ArrRes.Kind = ptprkArray) and (ArrRes.NestArrayCount > 0) then begin
      j := ArrRes.NestArrayCount;
      while j > 0 do begin
        inc(i);
        dec(j);
        if i >= IndexCount then break;
        IdxPart2 := IndexPart[i];
        IdxPart2.PTypeReq      := IdxPart.PTypeReq;
        IdxPart2.PTypeDeRefReq := IdxPart.PTypeDeRefReq;
        IdxPart2.ArrayPTypeNestIdx := j;
      end;
    end

    else
    if (ArrRes.Kind = ptprkArray) and (ArrRes.PointerCount > 1) then begin
      j := ArrRes.PointerCount - 1;
      while j > 0 do begin
        inc(i);
        dec(j);
        if i >= IndexCount then break;
        IdxPart2 := IndexPart[i];
        IdxPart2.PTypeReq      := IdxPart.PTypeReq;
        IdxPart2.PTypeDeRefReq := IdxPart.PTypeDeRefReq;
        IdxPart2.ArrayPTypePointerIdx := j;
      end;
    end;

    inc(i);
  end;

  if IndexCount=0 then exit;

  // check if we may access a char in a string
  IdxPart := IndexPart[IndexCount-1];
  PTReq := IdxPart.PTypeReq;
  if (PTReq.Result.Kind = ptprkSimple) and
     not(IdxPart.PTypeDeRefReq.Result.Kind = ptprkArray)
  then begin
    s := LowerCase(PCLenToString(PTReq.Result.BaseName));
    if (ptprfPointer in PTReq.Result.Flags) and
       ( ( s = 'char') or (s = 'character') or (s = 'wchar') or (s = 'widechar') )
    then begin
      if IsNumber(IdxPart.GetPlainText)
      then begin
        FMaybeString := True;
      end
      else begin
        PTReq := IdxPart.PTypeIndexReq;
        if PTReq.Result.Kind = ptprkNotEvaluated
        then begin
          IdxPart.InitIndexReq(AReqPtr);
          Result := True;
          exit;
        end;

        if (PTReq.Result.Kind = ptprkSimple)
        then begin
          ResultList := TGDBMINameValueList.Create(PTReq.Result.GdbDescription);
          FMaybeString := IsNumber(ResultList.Values['value']);
          ResultList.Free;
        end;
      end;
    end;
  end;

end;

function TGDBExpressionPartArray.MayNeedStringFix: Boolean;
begin
  Result := FMaybeString;
  if not Result then
    Result := inherited MayNeedStringFix;
end;

{ TGDBExpressionPartCastCall }

procedure TGDBExpressionPartCastCall.Init;
begin
  inherited Init;
  FPTypeReq.Result.Kind := ptprkNotEvaluated;
end;

function TGDBExpressionPartCastCall.GetTextFixed(AOpts: TGDBExprTextOptions
  ): String;
begin
  Result := inherited GetTextFixed(AOpts);
  if FTypeCastFixFlag = tcfFixNeeded then
    Result := '^'+Result;
end;

function TGDBExpressionPartCastCall.NeedValidation(var AReqPtr: PGDBPTypeRequest): Boolean;
begin
  Result := inherited NeedValidation(AReqPtr);

  if IsFunction or (FTypeCastFixFlag <> tcfEvalNeeded) then
    exit;

  if FPTypeReq.Result.Kind = ptprkNotEvaluated then begin
    InitReq(AReqPtr, FPTypeReq, GdbCmdPType + Parts[0].GetTextFixed([toSkipArrayIdx]) , gcrtPType);
    Result := True;
    exit;
  end;

  if (FPTypeReq.Result.Kind = ptprkError) or (FPTypeReq.Error <> '') then begin
    FTypeCastFixFlag := tcfNoFixNeeded;
    exit;
  end;

  if FPTypeReq.Result.Kind = ptprkClass then begin
    FTypeCastFixFlag := tcfFixNeeded;
    FIsTypeCast := True;
    exit;
  end;

  if FPTypeReq.Result.Kind = ptprkRecord then begin // Includes pointer to array
    FTypeCastFixFlag := tcfNoFixNeeded; // TODO: Maybe it is needed?
    FIsTypeCast := True;
    exit;
  end;

  if FPTypeReq.Result.Kind in [ptprkProcedure, ptprkFunction] then begin
    FTypeCastFixFlag := tcfNoFixNeeded;
    FIsFunction := True;
    exit;
  end;

  FTypeCastFixFlag := tcfNoFixNeeded;
end;

constructor TGDBExpressionPartCastCall.Create(ALeadExpresion: TGDBExpressionPart);
var
  i, l: Integer;
  s: String;
begin
  inherited Create;
  Add(ALeadExpresion);
  FIsFunction := False;
  FIsTypeCast := False;
  FTypeCastFixFlag := tcfUnknown;
  s := ALeadExpresion.GetText;
  i := 1;
  l := Length(s);
  while (i <= l) and (s[i] in [' ', #9]) do inc(i);
  if i < l then begin
    while (i <= l) and (s[i] in ['a'..'z', 'A'..'Z', '0'..'9', '_']) do inc(i);
    while (i <= l) and (s[i] in [' ', #9]) do inc(i);
    FIsFunction := i < l;  // Contains chars that are not allowed in type identifiers (like foo.bar())
  end;

end;

function TGDBExpressionPartCastCall.AddBrackets(APart: TGDBExpressionPart): Integer;
begin
  Result := Add(APart);
end;

function TGDBExpressionPartCastCall.MayNeedTypeCastFix: Boolean;
begin
  Result := inherited MayNeedTypeCastFix;
  if IsFunction then
    exit;

  if not(FTypeCastFixFlag in [tcfUnknown, tcfEvalNeeded]) then
    exit;

  Result := True;
  FTypeCastFixFlag := tcfEvalNeeded;
end;

{ TGDBExpressionPartNumber }

function TGDBExpressionPartNumber.GetTextFixed(AOpts: TGDBExprTextOptions): String;
begin
  if AOpts=[] then ;
  Result := FTextStr;
end;

constructor TGDBExpressionPartNumber.Create(AText: PChar; ATextLen: Integer);
var
  i: QWord;
  e: word;
begin
  if AText^ = '%' then begin
    Val(Copy(AText, 1, ATextLen), i, e);
    if e <> 0 then begin
      FTextStr := Copy(AText, 1, ATextLen); // pass error to gdb
      exit;
    end;
    FTextStr := '('+IntToStr(Int64(i))+')'; // brackets so no decimal dot can follow
    exit;
  end;

  if AText^ = '$' then
    FTextStr := '0x'+Copy(AText, 2, ATextLen-1)
  else
  if AText^ = '&' then
    FTextStr := '(0'+ Copy(AText, 2, ATextLen-1)+')'
  else begin // strip leading zeros
    while (AText^ = '0') and (ATextLen > 1) do begin
      inc(AText);
      dec(ATextLen);
    end;
    FTextStr := Copy(AText, 1, ATextLen);
  end;

end;

{ TGDBExpressionPartBracketed }

function TGDBExpressionPartBracketed.GetTextFixed(AOpts: TGDBExprTextOptions
  ): String;
begin
  if FExpressionPart = nil
  then Result := inherited GetTextFixed(AOpts)
  else Result := FText.Ptr^ + FExpressionPart.GetTextFixed(AOpts) + (FText.Ptr + FText.Len-1)^;
end;

function TGDBExpressionPartBracketed.GetPlainText(AOpts: TGDBExprTextOptions
  ): String;
begin
  if FExpressionPart = nil
  then Result := PCLenPartToString(FText, 1, FText.Len-2)
  else Result := FExpressionPart.TextEx[AOpts];
end;

constructor TGDBExpressionPartBracketed.Create(AText: PChar; ATextLen: Integer);
begin
  CreateSimple(AText, ATextLen);
  FExpressionPart := ParseExpression(FText.Ptr+1, FText.Len-2);
end;

{ TGDBExpressionPart }

function TGDBExpressionPart.GetTextFixed(AOpts: TGDBExprTextOptions): String;
begin
  Result := PCLenToString(FText);
end;

function TGDBExpressionPart.GetText: String;
begin
  Result := GetTextFixed([]);
end;

function TGDBExpressionPart.ParseExpression(AText: PChar; ATextLen: Integer): TGDBExpressionPart;
const
  // include "." (dots). currently there is no need to break expressions like "foo.bar"
  // Include "^" (deref)
  // do NOT include "@", it is applied after []() resolution
  WordChar = ['a'..'z', 'A'..'Z', '0'..'9', '_', '#', '^', '.'];
var
  CurPtr, EndPtr: PChar;
  CurPartPtr: PChar;

  procedure SkipSpaces;
  begin
    while (CurPtr < EndPtr) and (CurPtr^ in [#9, ' ']) do inc(CurPtr);
  end;

  procedure ScanToWordEnd;
  var
    c: Char;
    f: Boolean;
  begin
    // include "." (dots). currently there is no need to break expressions like "foo.bar"
    // Include "^" (deref)
    while (CurPtr < EndPtr) do begin
      c := CurPtr^;
      if (c in WordChar) then begin
        inc(CurPtr);
      end
      else if (c in [' ', #9]) then begin
        f := ((CurPtr-1)^ in ['.', '^']);
        SkipSpaces;
        if not(f  or  ((CurPtr < EndPtr) and (CurPtr^ in ['.', '^'])) ) then
          break;
      end
      else
        break;
    end;
  end;

  procedure ScanToWordStart;
  begin
    while (CurPtr < EndPtr) and not( (CurPtr^ in WordChar) or (CurPtr^  = ',') )
    do inc(CurPtr);
  end;

  function ScanToCallCastEnd: Boolean;
  var
    i: Integer;
  begin
    i := 0;
    while (CurPtr < EndPtr) do begin
      case CurPtr^ of
        '(': inc(i);
        ')': begin
            dec(i);
            inc(CurPtr);
            if i = 0
            then break
            else continue;
          end;
      end;
      inc(CurPtr);
    end;
    Result := i = 0;
  end;

  function ScanToIndexEnd: Boolean;
  var
    i: Integer;
  begin
    i := 0;
    while (CurPtr < EndPtr) do begin
      case CurPtr^ of
        '[': inc(i);
        ']': begin
            dec(i);
            inc(CurPtr);
            if i = 0
            then break
            else continue;
          end;
      end;
      inc(CurPtr);
    end;
    Result := i = 0;
  end;

  procedure AddExpPart(aList: TGDBExpressionPartList);
  var
    NewList: TGDBExpressionPartList;
  begin
    if aList.PartCount = 0 then exit;
    if (aList.PartCount = 1) and (Result = nil) then begin
      Result := aList.Parts[0];
      aList.ClearShared;
      exit;
    end;

    If Result = nil
    then Result := TGDBExpressionPartList.Create
    else
    if not (Result is TGDBExpressionPartList)
    then begin
      NewList := TGDBExpressionPartList.Create;
      NewList.Add(Result);
      Result := NewList;
    end;

    TGDBExpressionPartList(Result).AddList(aList);
    aList.ClearShared;
  end;

  function MoveListToCopy(aList: TGDBExpressionPartList): TGDBExpressionPart;
  begin
    if aList.PartCount = 1
    then begin
      Result := aList.Parts[0];
    end
    else begin
      Result := TGDBExpressionPartList.Create;
      TGDBExpressionPartList(Result).AddList(aList);
    end;
    aList.ClearShared;
  end;

var
  CurList: TGDBExpressionPartList;
  CurArray: TGDBExpressionPartArray;
  CurCast: TGDBExpressionPartCastCall;
  FCommaList: TGDBExpressionPartCommaList;
  CurWord: TGDBExpression;
begin
  Result := nil;
  FCommaList := nil;
  CurPtr := AText;
  EndPtr := AText + ATextLen;


  while (CurPtr < EndPtr) and not(CurPtr^ in ['[', '(', ',', '%', '&', '$', '0', '''', '"']) do begin
    if CurPtr^ = '''' then begin
      inc(CurPtr);
      while (CurPtr < EndPtr) and (CurPtr^ <> '''') do
        inc(CurPtr);
    end
    else
    if CurPtr^ = '"' then begin
      inc(CurPtr);
      while (CurPtr < EndPtr) and (CurPtr^ <> '"') do
        inc(CurPtr);
    end;
    (* uppercase due to https://sourceware.org/bugzilla/show_bug.cgi?id=17835
       gdb 7.7 and 7.8 fail to find members, if lowercased
       Alternative prefix with "self." if gdb returns &"Type TCLASSXXXX has no component named EXPRESSION.\n"
    *)
    if (CurPtr < EndPtr) and (CurPtr^ in ['a'..'z']) then
      CurPtr^ := UpperCase(CurPtr^)[1];
    inc(CurPtr);
  end;
  if CurPtr = EndPtr then
    exit; // no fixup needed

  CurPtr := AText;
  CurList:= TGDBExpressionPartList.Create;

  while CurPtr < EndPtr do begin

    if (CurPtr^ = ',')
    then begin
      if FCommaList = nil then
        FCommaList := TGDBExpressionPartCommaList.Create;
      AddExpPart(CurList);
      FCommaList.Add(Result);
      Result := nil;
      inc(CurPtr);
    end
    else
    if (CurPtr^ in ['%', '&', '$'])
       or ((CurPtr^ = '0') and ((CurPtr+1)^ in ['0'..'9'])) // octal for gdb
       // no need to handle decimal or 0xabcd
    then begin
      CurPartPtr := CurPtr;
      if CurPtr^ in ['0'..'9'] then begin
        while CurPtr^ in ['0'..'9'] do inc(CurPtr);
        if (CurPtr^ = '.') and ((CurPtr+1)^ <> '.') then begin
          // decimal, no need to convert
          inc(CurPtr);
          while CurPtr^ in ['0'..'9'] do inc(CurPtr);
          CurWord := TGDBExpression.CreateSimple(CurPartPtr, CurPtr - CurPartPtr);
        end
        else begin
          CurWord := TGDBExpressionPartNumber.Create(CurPartPtr, CurPtr - CurPartPtr);
        end;
      end
      else begin
        inc(CurPtr);
        case CurPartPtr^ of
          '$': while CurPtr^ in ['a'..'z', 'A'..'Z', '0'..'9'] do inc(CurPtr);
          '&': while CurPtr^ in ['0'..'7'] do inc(CurPtr);
          '%': while CurPtr^ in ['0'..'1'] do inc(CurPtr);
        end;
        CurWord := TGDBExpressionPartNumber.Create(CurPartPtr, CurPtr - CurPartPtr);
      end;
      CurList.Add(CurWord);
    end
    else
    if CurPtr^ in WordChar
    then begin
      CurPartPtr := CurPtr;
      ScanToWordEnd;
      CurWord := TGDBExpression.CreateSimple(CurPartPtr, CurPtr - CurPartPtr);
      CurList.Add(CurWord);
      if (CurPtr^ in WordChar) or CurWord.IsNamedOperator then // 2 words => named operator (and/or)
        AddExpPart(CurList);
    end
    else
    if (CurList.PartCount > 0) and (CurPtr^ = '[')
    then begin
      CurArray := TGDBExpressionPartArray.Create(MoveListToCopy(CurList));
      CurList.Add(CurArray);
      while (CurPtr^ = '[') do begin
        CurPartPtr := CurPtr;
        if not ScanToIndexEnd then break; // broken expression, do not attempt to do anything
        CurArray.AddIndex(TGDBExpressionPartArrayIdx.Create(CurPartPtr, CurPtr - CurPartPtr));
        SkipSpaces;
      end;
      if (CurPtr < EndPtr ) and (CurPtr^ in ['.', '^', '(']) then
        CurArray.NeedTypeCast := True;
    end
    else
    if (CurList.PartCount > 0) and (CurPtr^ = '(')
    then begin
      CurCast := TGDBExpressionPartCastCall.Create(MoveListToCopy(CurList));
      CurList.Add(CurCast);
      CurPartPtr := CurPtr;
      if not ScanToCallCastEnd then break; // broken expression, do not attempt to do anything
      CurCast.AddBrackets(TGDBExpressionPartBracketed.Create(CurPartPtr, CurPtr - CurPartPtr));
    end
    else begin
      CurPartPtr := CurPtr;
      ScanToWordStart;
      CurList.Add(TGDBExpression.CreateSimple(CurPartPtr, CurPtr - CurPartPtr));
      AddExpPart(CurList);
    end;

  end;

  AddExpPart(CurList);
  CurList.Free;

  if FCommaList <> nil then begin
    if Result <> nil then
      FCommaList.Add(Result);
    Result := FCommaList;
  end;


  if CurPtr < EndPtr then debugln(['Scan aborted: ', PCLenToString(FText)]);
  if CurPtr < EndPtr then FreeAndNil(Result);
end;

procedure TGDBExpressionPart.Init;
begin
  //
end;

procedure TGDBExpressionPart.InitReq(var AReqPtr: PGDBPTypeRequest;
  var AReqVar: TGDBPTypeRequest; AReqText: String; AType: TGDBCommandRequestType);
begin
  AReqVar.Request := AReqText;
  AReqVar.Error := '';
  AReqVar.ReqType := AType;
  AReqVar.Next := AReqPtr;
  AReqVar.Result.Kind := ptprkError;
  AReqPtr := @AReqVar;
end;

function TGDBExpressionPart.NeedValidation(var AReqPtr: PGDBPTypeRequest): Boolean;
var
  i: Integer;
begin
  Result := False;
  for i := 0 to PartCount - 1 do
    if Parts[i].NeedValidation(AReqPtr) then
      Result := True;
end;

function TGDBExpressionPart.MayNeedStringFix: Boolean;
var
  i: Integer;
begin
  Result := False;
  for i := 0 to PartCount - 1 do
    if Parts[i].MayNeedStringFix then
      Result := True;
end;

function TGDBExpressionPart.MayNeedTypeCastFix: Boolean;
var
  i: Integer;
begin
  Result := False;
  for i := 0 to PartCount - 1 do
    if Parts[i].MayNeedTypeCastFix then
      Result := True;
end;

constructor TGDBExpressionPart.Create;
begin
  Init;
end;

function TGDBExpressionPart.IsNamedOperator: Boolean;
var
  s: String;
begin
  s := LowerCase(Trim(GetText));
  Result := (s = 'not') or (s = 'or') or (s = 'xor') or (s = 'and');
end;

function TGDBExpressionPart.GetTextStrFixed: String;
begin
  Result := GetTextFixed([toWithStringFix]);
end;

function TGDBExpressionPart.GetParts(Index: Integer): TGDBExpressionPart;
begin
  Result := nil;
end;

function TGDBExpressionPart.PartCount: Integer;
begin
  Result := 0;
end;

{ TGDBExpressionPartListBase }

function TGDBExpressionPartListBase.GetParts(Index: Integer): TGDBExpressionPart;
begin
  Result := TGDBExpressionPart(FList[Index]);
end;

function TGDBExpressionPartListBase.GetTextFixed(AOpts: TGDBExprTextOptions
  ): String;
var
  i: Integer;
begin
  Result := '';
  for i := 0 to PartCount - 1 do
    Result := Result + Parts[i].GetTextFixed(AOpts);
end;

constructor TGDBExpressionPartListBase.Create;
begin
  inherited Create;
  FList := TFPList.Create;
end;

destructor TGDBExpressionPartListBase.Destroy;
begin
  Clear;
  FreeAndNil(FList);
  inherited Destroy;
end;

procedure TGDBExpressionPartListBase.Clear;
begin
  while FList.Count > 0 do begin
    TGDBExpressionPart(Flist[0]).Free;
    FList.Delete(0);
  end;
end;

procedure TGDBExpressionPartListBase.ClearShared;
begin
  FList.Clear;
end;

function TGDBExpressionPartListBase.Add(APart: TGDBExpressionPart): Integer;
begin
  Result := FList.Add(APart);
end;

procedure TGDBExpressionPartListBase.Insert(AIndex: Integer; APart: TGDBExpressionPart);
begin
  FList.Insert(AIndex, APart);
end;

procedure TGDBExpressionPartListBase.Delete(AIndex: Integer);
begin
  FList.Delete(AIndex);
end;

function TGDBExpressionPartListBase.PartCount: Integer;
begin
  Result := FList.Count;
end;

{ TGDBExpression }

function TGDBExpression.GetTextFixed(AOpts: TGDBExprTextOptions): String;
begin
  if FExpressionPart = nil
  then Result := inherited GetTextFixed(AOpts)
  else Result := FExpressionPart.GetTextFixed(AOpts);
end;

function TGDBExpression.GetParts(Index: Integer): TGDBExpressionPart;
begin
  Result := nil;
  if FExpressionPart = nil then exit;
  if FExpressionPart is TGDBExpressionPartList
  then Result := FExpressionPart.Parts[Index]
  else Result := FExpressionPart;
end;

constructor TGDBExpression.CreateSimple(AText: PChar; ATextLen: Integer);
begin
  inherited Create;
  // not to be parsed
  FExpressionPart := nil;
  FText.Ptr := AText;
  FText.Len := ATextLen;
end;

constructor TGDBExpression.Create(AText: PChar; ATextLen: Integer);
begin
  CreateSimple(AText, ATextLen);
  FExpressionPart := ParseExpression(FText.Ptr, FText.Len);
end;

constructor TGDBExpression.Create(ATextStr: String);
begin
  FTextStr := ATextStr;
  UniqueString(FTextStr);
  Create(PChar(FTextStr), length(FTextStr));
end;

destructor TGDBExpression.Destroy;
begin
  FreeAndNil(FExpressionPart);
  inherited Destroy;
end;

function TGDBExpression.PartCount: Integer;
begin
  Result := 0;
  if FExpressionPart = nil then exit;
  if FExpressionPart is TGDBExpressionPartList
  then Result := FExpressionPart.PartCount
  else Result := 1;
end;

function TGDBExpression.IsCommaSeparated: Boolean;
begin
  Result := (FExpressionPart <> nil) and (FExpressionPart is TGDBExpressionPartCommaList);
end;

{ TGDBPTypeRequestCache }

function TGDBPTypeRequestCache.GetRequest(Index: Integer): TGDBPTypeRequest;
begin
  Result := TGDBPTypeRequestCacheEntry(FLists[Index mod TGDBPTypeReqCacheListCount][Index div TGDBPTypeReqCacheListCount]).FRequest;
end;

constructor TGDBPTypeRequestCache.Create;
begin
end;

destructor TGDBPTypeRequestCache.Destroy;
var
  i: Integer;
begin
  Clear;
  inherited Destroy;
  for i := 0 to high(FLists) do
    FreeAndNil(FLists[i]);
end;

procedure TGDBPTypeRequestCache.Clear;
var
  i: Integer;
begin
  for i := 0 to high(FLists) do
    if FLists[i] <> nil then
      while FLists[i].Count > 0 do begin
        TGDBPTypeRequestCacheEntry(FLists[i][0]).Free;
        FLists[i].Delete(0);
      end;
end;

function TGDBPTypeRequestCache.IndexOf(AThreadId, AStackFrame: Integer;
  ARequest: TGDBPTypeRequest): Integer;
var
  e: TGDBPTypeRequestCacheEntry;
  s: String;
  HashVal: Integer;
begin
  s := UpperCase(ARequest.Request);
  // There are usually a couple of dozen entry total. Even if most are the same len the search will be quick
  // Including stackframe, means nested procedures go in different lists.
  HashVal := Length(s) mod (TGDBPTypeReqCacheListCount div 8) * 8
             + AStackFrame mod 4 * 2
             + ord(ARequest.ReqType);
  Result := -1;
  if FLists[HashVal] = nil then
    exit;
  Result := FLists[HashVal].Count - 1;
  while Result >= 0 do begin
    e := TGDBPTypeRequestCacheEntry(FLists[HashVal][Result]);
    if (e.ThreadId = AThreadId) and (e.StackFrame = AStackFrame) and
       (e.Request.Request = s) and
       (e.Request.ReqType = ARequest.ReqType)
    then begin
      Result := Result * TGDBPTypeReqCacheListCount + HashVal;
      exit;
    end;
    dec(Result);
  end;
end;

procedure TGDBPTypeRequestCache.Add(AThreadId, AStackFrame: Integer;
  ARequest: TGDBPTypeRequest);
var
  e: TGDBPTypeRequestCacheEntry;
  HashVal: Integer;
begin
  e := TGDBPTypeRequestCacheEntry.Create;
  e.FThreadId := AThreadId;
  e.FStackFrame := AStackFrame;
  e.FRequest := ARequest;
  e.FRequest.Request := UpperCase(e.FRequest.Request);
  e.FRequest.Next := nil;
  HashVal := Length(ARequest.Request) mod (TGDBPTypeReqCacheListCount div 8) * 8
             + AStackFrame mod 4 * 2
             + ord(ARequest.ReqType);
  if FLists[HashVal] = nil then
    FLists[HashVal] := TFPList.Create;
  FLists[HashVal].Add(e);
end;

{ TGDBPType }

procedure TGDBType.AddTypeReq(var AReq: TGDBPTypeRequest; const ACmd: string = '');
begin
  AReq.Result.Kind := ptprkError;
  AReq.Request := ACmd;
  AReq.Error := '';
  AReq.Next := FEvalRequest;
  FEvalRequest := @AReq;
  if FLastEvalRequest = nil then
    FLastEvalRequest := @AReq;
end;

procedure TGDBType.AddSubType(ASubType: TGDBType);
begin
  if ASubType.ProcessExpression then
    exit;
  ASubType.FNextProcessingSubType := FFirstProcessingSubType;
  FFirstProcessingSubType := ASubType;
end;

function TGDBType.GetIsFinished: Boolean;
begin
  Result := FProcessState = gtpsFinished;
end;

function TGDBType.RequireRequests(ARequired: TGDBTypeProcessRequests; ACustomData: String = ''): Boolean;

  function GetReqText(AReq: TGDBTypeProcessRequest): String;
  begin
    case areq of
      gptrPTypeExpr:        Result := GdbCmdPType + FPTypeExpression;
      gptrWhatisExpr:       Result := GdbCmdWhatIs + FPTypeExpression;
      gptrPTypeOfWhatis:    Result := GdbCmdPType + PCLenToString(FReqResults[gptrWhatisExpr].Result.BaseName);
      gptrPTypeExprDeRef:   Result := GdbCmdPType + GDBMIMaybeApplyBracketsToExpr(FPTypeExpression) + '^';
      gptrPTypeExprDeDeRef: Result := GdbCmdPType + GDBMIMaybeApplyBracketsToExpr(FPTypeExpression) + '^^';
      gptrEvalExpr:       Result := GdbCmdEvaluate+Quote(FExpression);
      gptrEvalExprDeRef:  Result := GdbCmdEvaluate+Quote(FExpression+'^');
      gptrEvalExprCast:   Result := GdbCmdEvaluate+Quote(InternalTypeName+'('+FExpression+')');
      gptrEvalExpr2:      Result := GdbCmdEvaluate+Quote(ACustomData);
      gptrEvalExprDeRef2: Result := GdbCmdEvaluate+Quote(ACustomData+'^');
      gptrEvalExprCast2:  Result := GdbCmdEvaluate+Quote(InternalTypeName+'('+ACustomData+')');
      gptrPtypeCustomAutoCast, gptrPtypeCustomAutoCast2:
                         Result := GdbCmdPType + ACustomData;
      gptrInstanceClassName: Result := GdbCmdEvaluate+Quote('(^^^char('+FExpression+')^+3)^');
      gptrEvalCustomEval:   Result := GdbCmdEvaluate+Quote(ACustomData);
    end;
  end;

var
  NeededReq: TGDBTypeProcessRequests;
  i: TGDBTypeProcessRequest;
begin
  NeededReq := ARequired - FProccesReuestsMade;
  Result := NeededReq = [];
  if Result then exit;
  //DebugLn(DBGMI_TYPE_INFO, ['TGDBType.ProcessExpression: Adding Req ', dbgs(NeededReq), ', CD=', ACustomData]);

  if (gptrPTypeOfWhatis in NeededReq) and not (gptrWhatisExpr in FProccesReuestsMade)
  then begin
    Exclude(NeededReq, gptrPTypeOfWhatis);
    Include(NeededReq, gptrWhatisExpr);
  end;

  FProccesReuestsMade := FProccesReuestsMade + NeededReq;
  for i := low(TGDBTypeProcessRequest) to high(TGDBTypeProcessRequest) do
    if i in NeededReq then begin
      AddTypeReq(FReqResults[i], GetReqText(i));
      if i in [gptrEvalExpr, gptrEvalExprDeRef, gptrEvalExprCast, gptrInstanceClassName, gptrEvalCustomEval]
      then FReqResults[i].ReqType := gcrtEvalExpr
      else FReqResults[i].ReqType := gcrtPType;
    end;
end;

function TGDBType.IsReqError(AReqType: TGDBTypeProcessRequest; CheckResKind: Boolean = True): Boolean;
begin
  Result := (not (AReqType in FProccesReuestsMade))
         or (FReqResults[AReqType].Error <> '')
         or (CheckResKind and (FReqResults[AReqType].Result.Kind = ptprkError));
end;

procedure TGDBType.Init;
begin
  inherited Init;
  FProcessState := gtpsFinished;
  FParsedExpression := nil;
  FMaybeShortString := False;
end;

function TGDBType.DebugString: String;
begin
  Result := Format('Expr="%s", Flags=%s, State=%s', [FExpression, dbgs(FCreationFlags), dbgs(FProcessState)]);
end;

constructor TGDBType.CreateForExpression(const AnExpression: string;
  const AFlags: TGDBTypeCreationFlags; AFormat: TWatchDisplayFormat; ARepeatCount: Integer);
begin
  Create(skSimple, ''); // initialize
  FInternalTypeName := '';
  FEvalError := False;
  FExpression := AnExpression;
  FOrigExpression := FExpression;
  FCreationFlags := AFlags;
  FExprEvaluateFormat := AFormat;
  FEvalStarted := False;
  FEvalRequest := nil;
  FFirstProcessingSubType := nil;
  FNextProcessingSubType := nil;
  FProcessState := gtpsInitial;
  FHasExprEvaluatedAsText := False;
  FHasAutoTypeCastFix := False;
  FAutoTypeCastName := '';
  FArrayIndexValueLimit := 5;
  FRepeatCountEval := nil;
  FRepeatCount := ARepeatCount;
  FRepeatFirstIndex := 0;
end;

destructor TGDBType.Destroy;
var
  i: Integer;
begin
  inherited Destroy;
  FreeAndNil(FTypeInfoAncestor);
  for i := 0 to Length(FArrayIndexValues) - 1 do
    FArrayIndexValues[i].Free;
  FArrayIndexValues := nil;
  FreeAndNil(FParsedExpression);
  FreeAndNil(FRepeatCountEval);
end;

function TGDBType.ProcessExpression: Boolean;
var
  Lines: TStringList;
  procedure ProcessInitial; forward;
  procedure ProcessInitialSimple; forward;
  procedure ProcessSimplePointer; forward;
  procedure EvaluateExpression; forward;


  function ClearAmpersand(s: string): string;
  var i: Integer;
  begin
    Result := s;
    i := pos('&', Result);
    if i > 0 then delete(Result, i, 1);
  end;

  procedure SetTypNameFromReq(AReqType: TGDBTypeProcessRequest;
    AnUseBaseName: Boolean = False; ADefaultName: String = '');
  begin
    if IsReqError(AReqType) or (FReqResults[AReqType].Result.BaseName.Len = 0)
    then AReqType := gptrPTypeExpr;

    if AnUseBaseName
    then FTypeName := PCLenToString(FReqResults[AReqType].Result.BaseName)
    else FTypeName := ClearAmpersand(PCLenToString(FReqResults[AReqType].Result.Name));

    if FTypeName = ''
    then FTypeName := ADefaultName;
    FInternalTypeName := FTypeName;
  end;

  Procedure InitLinesFrom(AReq: TGDBPTypeRequest);
  begin
    FreeAndNil(Lines);
    Lines := TStringList.Create;
    Lines.Text := AReq.Result.GdbDescription;
  end;

  procedure DoEnum;
  var
    S: String;
  begin
    FKind := skEnum;

    S := PCLenToString(FReqResults[gptrPTypeExpr].Result.Declaration);
    S := GetPart(['('], [')'], S);
    if (S = '') or (S = '...') then
      exit;

    FMembers := TStringList.Create;
    FMembers.Text := StringReplace(S, ' ', #13#10, [rfReplaceAll]);
  end;

  procedure DoProcedure;
  var
    S: String;
  begin
    FKind := skProcedure;

    S := PCLenToString(FReqResults[gptrPTypeExpr].Result.Declaration);
    S := GetPart(['('], [')'], S);
    if (S = '') then
      exit;

    FArguments := TGDBTypes.CreateFromCSV(S);
  end;

  procedure DoFunction;
  var
    S: String;
  begin
    FKind := skFunction;

    S := PCLenToString(FReqResults[gptrPTypeExpr].Result.Declaration);
    S := GetPart(['('], [')'], S);
    if (S = '') then
      exit;

    FArguments := TGDBTypes.CreateFromCSV(S);

    S := PCLenToString(FReqResults[gptrPTypeExpr].Result.Declaration);
    FResult := TGDBType.Create(skSimple, GetPart([' : '], [], S));
  end;

  procedure DoSet;
  var
    S: String;
  begin
    FKind := skSet;

    S := PCLenToString(FReqResults[gptrPTypeExpr].Result.Declaration);
    S := GetPart(['('], [')'], S);
    if (S = '') or (S = '...') then
      exit;

    FMembers := TStringList.Create;
    FMembers.Text := StringReplace(StringReplace(S, ',', #13#10, [rfReplaceAll]), ' ', '', [rfReplaceAll]);
  end;

  {%region    * Record * }
  procedure DoRecord;
  var
    n: Integer;
    S, S1, S2: String;
    Field: TDBGField;
  begin
    FFields := TDBGFields.Create;
    InitLinesFrom(FReqResults[gptrPTypeExpr]);

    //concatenate all lines and skip last end
    for n := 1 to Lines.Count - 2 do begin
      S := Lines[n];
      S1 := Trim(GetPart([' '], [':'], S));
      S2 := Trim(GetPart([':'], [';'], S));
      Field := TDBGField.Create(
        S1,
        TGDBType.Create(skSimple, S2),
        flPublic
      );
      FFields.Add(Field);
    end;

    FMaybeShortString := (FFields.Count = 2) and // shortstring have 2 fields: length and st
       (lowercase(FFields[0].Name) = 'length') and
       (lowercase(FFields[1].Name) = 'st');

    if (FTypeName = 'Variant') or
       (FTypeName = 'VARIANT') then
      FKind := skVariant
    else
    if (FTypeName = 'ShortString') or
       (FTypeName = 'SHORTSTRING') or
       (FTypeName = '&ShortString')
    then begin
      if (gtcfExprEvaluate in FCreationFlags) then
        FMaybeShortString := True // will be checked later
       else
        FKind := skSimple
     end
    else
      FKind := skRecord;


  end;
  {%endregion    * Record * }

  {%region    * Class * }
  procedure DoClass;
  var
    n, i, j, l: Integer;
    S, S2: String;

    Name: String;
    DBGType: TDBGType;
    Location: TDBGFieldLocation;
    Flags: TDBGFieldFlags;
  begin
    include(FAttributes, saInternalPointer);
    FKind := skClass;
    InitLinesFrom(FReqResults[gptrPTypeExpr]);
    FFields := TDBGFields.Create;
    if Lines.Count < 1 then exit;
    s := Lines[0];
    FAncestor := GetPart([': public '], [' '], s);

    Location := flPublished;
    n := 0;
    while n <  Lines.Count - 2 do
    begin
      inc(n);
      S := Lines[n];
      if S = '' then Continue; // TODO: clear location (private, peblic)
      if S = 'end' then break;
      if S = '  private' then Location := flPrivate
      else if S = '  protected' then Location := flProtected
      else if S = '  public' then Location := flPublic
      else if S = '  published' then Location := flPublished
      else begin
        Flags := [];
        if Pos('  procedure ', S) > 0
        then begin
          Name := GetPart(['procedure '], [' ', ';'], S);
          DBGType := TGDBType.Create(
            skProcedure,
            TGDBTypes.CreateFromCSV(GetPart(['('], [')'], S))
          );
          if GetPart(['; '], [';'], S) = 'virtual'
          then Flags := [ffVirtual];
        end
        else if Pos('  destructor  ~', S) > 0
        then begin
          Name := GetPart(['destructor  ~'], [' ', ';'], S);
          DBGType := TGDBType.Create(
            skProcedure,
            TGDBTypes.CreateFromCSV(GetPart(['('], [')'], S))
          );
          if GetPart(['; '], [';'], S) = 'virtual'
          then Flags := [ffVirtual];
          Include(Flags, ffDestructor);
        end
        else if Pos('  constructor ', S) > 0
        then begin
          Name := GetPart(['constructor '], [' ', ';'], S);
          DBGType := TGDBType.Create(
            skFunction,
            TGDBTypes.CreateFromCSV(GetPart(['('], [')'], S)),
            TGDBType.Create(skSimple, GetPart([' : '], [';'], S))
          );
          if GetPart(['; '], [';'], S) = 'virtual'
          then Flags := [ffVirtual];
          Include(Flags, ffConstructor);
        end
        else if Pos('  function ', S) > 0
        then begin
          Name := GetPart(['function  '], [' ', ';'], S);
          DBGType := TGDBType.Create(
            skFunction,
            TGDBTypes.CreateFromCSV(GetPart(['('], [')'], S)),
            TGDBType.Create(skSimple, GetPart([' : '], [';'], S))
          );
          if GetPart(['; '], [';'], S) = 'virtual'
          then Flags := [ffVirtual];
        end
        else begin
          Name := GetPart(['    '], [' '], S);
          S2 := GetPart([' : '], [';'], S);
          l := Length(S2);
          j := 1;
          while true do begin
            while (j <= l) and (S2[j] in ['^','(', ' ']) do inc(j);
            if (lowercase(copy(S2, j, 6)) = 'array ') then begin
              inc(j, 5+3);
              while (j <= l) and
                    not ( (S2[j-3] = ' ') and (S2[j-2] in ['o','O']) and (S2[j-1] in ['f','F']) and (S2[j] = ' ') )
              do
                inc(j);
              continue;
            end;
            break;
          end;
          if (lowercase(copy(S2, j, 7)) = 'record ') and
             not( (copy(S2, j+7, 1) = ';') or (copy(S2, j+7, 6) = '{...};') )
          then begin
            i := 1;
            while (n <  Lines.Count - 2) and (i > 0) do
            begin
              inc(n);
              S := Lines[n];
              if S = '' then Continue;
              j := pos(': record ', S);
              if (j > 0) and not( (copy(S, j+9, 1) = ';') or (copy(S, j+9, 6) = '{...};') )
              then
                inc(i);
              S := Trim(S);
              if (pos('end;', S) = 1) or (pos('end)', S) = 1) then dec(i);
              S2 := S2 + ' ' + S;
            end;
          end;
          DBGType := TGDBType.Create(skSimple, S2);
        end;
        FFields.Add(TDBGField.Create(Name, DBGType, Location, Flags, FTypeName));
      end;
    end;
  end;

  procedure ProcessClassPointer;
  begin
    FProcessState := gtpsClassPointer;
    if not RequireRequests([gptrWhatisExpr]) then
      exit;

      FKind := skPointer;
      SetTypNameFromReq(gptrWhatisExpr);
      Result := True;
      // ====> DONE
  end;

  procedure ProcessClassAncestor;
  var
    i: Integer;
  begin
    FProcessState := gtpsClassAncestor;

    If FTypeInfoAncestor = nil then begin
      FTypeInfoAncestor := TGDBType.CreateForExpression(FAncestor,
        FCreationFlags*[gtcfClassIsPointer, gtcfFullTypeInfo, gtcfSkipTypeName] + [gtcfExprIsType]
      );
      AddSubType(FTypeInfoAncestor);
    end;
    if not FTypeInfoAncestor.IsFinished then
      exit;

    // add ancestor
    if FTypeInfoAncestor.FFields <> nil then
      for i := 0 to FTypeInfoAncestor.FFields.Count - 1 do
        FFields.Add(FTypeInfoAncestor.FFields[i]);
    Result := True;
  end;

  procedure FinishProcessClass;
  begin
    FProcessState := gtpsFinishProcessClass;
    if (gtcfFullTypeInfo in FCreationFlags) and  not (gtcfExprIsType in FCreationFlags) then
      if not RequireRequests([gptrWhatisExpr]) then
        exit;

    // Handle Error in ptype^ as normal class
    // May need a whatis, if aliased names are needed "type TFooAlias = type TFoo"
    SetTypNameFromReq(gptrWhatisExpr, True);
    DoClass;
    if (gtcfFullTypeInfo in FCreationFlags) and (FAncestor <> '')
    then ProcessClassAncestor
    else Result := True; // ====> DONE
  end;

  procedure ProcessClass;
  var
    t: TGDBTypeProcessRequest;
    ResultList: TGDBMINameValueList;
    s: String;
    i: Integer;
  begin
    FProcessState := gtpsClass;

    if (gtcfExprIsType in FCreationFlags) then begin
      SetTypNameFromReq(gptrPTypeExpr, True);
      DoClass;
      if (gtcfFullTypeInfo in FCreationFlags) and (FAncestor <> '')
      then ProcessClassAncestor
      else Result := True; // ====> DONE
      exit;
    end;

    if saRefParam in FAttributes
    then t := gptrPTypeExprDeDeRef  // &Class (var param; dwarf)
    else t := gptrPTypeExprDeRef;   // Class

    if not RequireRequests([gptrPTypeExpr, t])
    then exit;

    if IsReqError(t)
    then debugln('Failed "ptype expr^[^]" request for class expression');

    if (not IsReqError(t)) and (ptprfPointer in FReqResults[t].Result.Flags)
    then begin
      ProcessClassPointer;
      exit;
    end
    else begin
      // before type cast
      FTypeDeclaration := PCLenToString(FReqResults[gptrPTypeExpr].Result.BaseName);

      if (gtcfAutoCastClass in FCreationFlags) then begin
        if not RequireRequests([gptrInstanceClassName]) then
          exit;
        if not IsReqError(gptrInstanceClassName) then begin
          ResultList := TGDBMINameValueList.Create(FReqResults[gptrInstanceClassName].Result.GdbDescription);
          s := ParseGDBString(ResultList.Values['value']);
          ResultList.Free;
          if s <> ''
          then i := ord(s[1])
          else i := 1;
          if i <= length(s)-1 then begin
            FAutoTypeCastName := copy(s, 2, i);
            RequireRequests([gptrPtypeCustomAutoCast], FAutoTypeCastName);
            FProcessState := gtpsClassAutoCast;
            exit;
          end;
          // continue without type cast
        end;
      end;

      FinishProcessClass;
    end;
  end;

  procedure ProcessClassAutoCast;
  var
    s: String;
  begin
    if IsReqError(gptrPtypeCustomAutoCast) or
       (not(FReqResults[gptrPtypeCustomAutoCast].Result.Kind = ptprkClass)) or
       (LowerCase(FAutoTypeCastName) = LowerCase(PCLenToString(FReqResults[gptrPTypeExpr].Result.BaseName))) // don't typecast to itself
    then begin
      FinishProcessClass; // normal class finish
      exit;
    end;

    s := FAutoTypeCastName + '(' + FExpression + ')';
    if not RequireRequests([gptrPtypeCustomAutoCast2], s)
    then exit;

    if FHasAutoTypeCastFix
    then s := '^' + s;

    if IsReqError(gptrPtypeCustomAutoCast2) and (not FHasAutoTypeCastFix)
    then begin
      s := '^' + s;
      FHasAutoTypeCastFix := True;
      exclude(FProccesReuestsMade, gptrPtypeCustomAutoCast2);
      RequireRequests([gptrPtypeCustomAutoCast2], s);
      exit;
    end;

    if IsReqError(gptrPtypeCustomAutoCast2) or
       not(FReqResults[gptrPtypeCustomAutoCast2].Result.Kind = ptprkClass)
    then begin
      FinishProcessClass; // normal class finish
      exit;
    end;

    FExpression := s;
    FPTypeExpression := FExpression; // TODO: keep FPTypeExpression

    FReqResults[gptrPTypeExpr] := FReqResults[gptrPtypeCustomAutoCast2];
    exclude(FProccesReuestsMade, gptrWhatisExpr);
    FinishProcessClass;
  end;
  {%endregion    * Class * }

  {%region    * Array * }
  procedure ProcessArray;
  var
    PTypeResult: TGDBPTypeResult;
  begin
    FProcessState := gtpsArray;

    PTypeResult := FReqResults[gptrPTypeExpr].Result;
    // In DWARF, some Dynamic Array, are pointer to there base type
    if (ptprfPointer in PTypeResult.Flags) and (PTypeResult.Kind =ptprkSimple)
    then begin
      if not RequireRequests([gptrPTypeExprDeRef])
      then exit;
      if (not IsReqError(gptrPTypeExprDeRef)) then
      PTypeResult := FReqResults[gptrPTypeExprDeRef].Result;
      // This implies it is an internal pointer
      if (ptprfDynArray in PTypeResult.Flags)
      then include(FAttributes, saInternalPointer);
    end;

    if (PTypeResult.Flags * [ptprfDynArray, ptprfPointer] =  [ptprfDynArray, ptprfPointer])
    then include(FAttributes, saInternalPointer);

    if (saInternalPointer in FAttributes) then begin
      if not RequireRequests([gptrPTypeExprDeRef])
      then exit;
    end;

    if (saInternalPointer in FAttributes) and (not IsReqError(gptrPTypeExprDeRef)) then
      PTypeResult := FReqResults[gptrPTypeExprDeRef].Result
    else
      PTypeResult := FReqResults[gptrPTypeExpr].Result;

    if ptprfPointer in PTypeResult.Flags then begin
      ProcessSimplePointer;
      exit;
    end;

    FKind := skSimple;
    if (ptprfDynArray in PTypeResult.Flags)
    then include(FAttributes, saDynArray)
    else include(FAttributes, saArray);

    if not(gtcfSkipTypeName in FCreationFlags) then begin
      if not RequireRequests([gptrWhatisExpr])
      then exit;
      SetTypNameFromReq(gptrWhatisExpr, True);
    end;

    FTypeDeclaration := ClearAmpersand(PCLenToString(PTypeResult.Declaration));
    Result := True;
    // ====> DONE
  end;
  {%endregion    * Array * }

  {%region    * Simple * }
  procedure ProcessSimplePointer;
  begin
    FProcessState := gtpsSimplePointer;
    // there may be multiply levels of pointer, get the name of this pointer
    if not RequireRequests([gptrPTypeExpr, gptrWhatisExpr])
    then exit;

    FKind := skPointer;
    if not IsReqError(gptrWhatisExpr) and (FReqResults[gptrWhatisExpr].Result.Kind = ptprkSimple)
    then begin
      // Whatis result is ok
      if (ptprfParamByRef in FReqResults[gptrWhatisExpr].Result.Flags) then
        include(FAttributes, saRefParam);
      SetTypNameFromReq(gptrWhatisExpr);
    end
    else begin
      // Whatis result failed
      SetTypNameFromReq(gptrPTypeExpr);
    end;
    Result := True;
    // ====> DONE
  end;
  {%endregion    * Simple * }

  {%region    * EvaluateExpression * }
  function GetParsedFromResult(AGdbDesc, AField: String): String;
  var
    ResultList: TGDBMINameValueList;
  begin
    ResultList := TGDBMINameValueList.Create(AGdbDesc);
    Result := ResultList.Values[AField];
    //FTextValue := DeleteEscapeChars(FTextValue);
    ResultList.Free;
  end;
  procedure ParseFromResult(AGdbDesc, AField: String);
  begin
    FExprEvaluatedAsText := GetParsedFromResult(AGdbDesc, AField);
    FHasExprEvaluatedAsText := True;
  end;
  procedure ParseFromResultForStrFixed(AGdbDesc, AField: String);
  begin
    FStringExprEvaluatedAsText := GetParsedFromResult(AGdbDesc, AField);
    FHasStringExprEvaluatedAsText := True;
  end;

  procedure EvaluateExpressionDynArrayGetData;
  var
    i, m: Integer;
    s: String;
  begin
    FProcessState := gtpsEvalExprDynArrayGetData;

    if (FLen <= 0) or (FArrayIndexValueLimit <= 0) then begin
      if FLen > 0 then
        FExprEvaluatedAsText := '(...)'
      else
        FExprEvaluatedAsText := '()';
      FHasExprEvaluatedAsText := True;
      Result := True;
      exit;
    end;

    if (Length(FArrayIndexValues) > 0) then begin
      FExprEvaluatedAsText := '';
      FFields := TDBGFields.Create;
      for i := 0 to Length(FArrayIndexValues) - 1 do begin
        s := FArrayIndexValues[i].ExprEvaluatedAsText;
        if (pos(',', s) > 0) and not(s[1] in ['(', '[', '{', '"', '''', '#']) then
          s := '{'+s+'}';
        if i > 0 then
          FExprEvaluatedAsText := FExprEvaluatedAsText + ', ';
        FExprEvaluatedAsText := FExprEvaluatedAsText + s;

        if FRepeatFirstIndex <> 0
        then m := FRepeatFirstIndex
        else m := FBoundLow;
        FFields.Add(TDBGField.Create(IntToStr(m + i), FArrayIndexValues[i], flPublic));
      end;
      if Length(FArrayIndexValues) < FLen then
        FExprEvaluatedAsText := FExprEvaluatedAsText + ', ...';
      FExprEvaluatedAsText := '(' + FExprEvaluatedAsText + ')';

      SetLength(FArrayIndexValues, 0);
      FHasExprEvaluatedAsText := True;
      Result := True;
      exit;
    end;

    if (FExprEvaluatedAsText <> '') and
       (FExprEvaluatedAsText[1] = '{') and   // gdb returned array data
       not(gtcfForceArrayEval in FCreationFlags)
    then begin
      if (FLen = 0) or
         ((Length(FExprEvaluatedAsText) > 1) and (FExprEvaluatedAsText[2] <> '}') )
      then begin
        Result := True;
        exit;
      end;
    end;

    // Get Data
    m := Min(Max(FArrayIndexValueLimit, FRepeatCount), FLen);
    SetLength(FArrayIndexValues, m);
    for i := 0 to m-1 do begin
      FArrayIndexValues[i] := TGDBType.CreateForExpression(FExpression+'['+IntToStr(FRepeatFirstIndex + i)+']',
        FCreationFlags + [gtcfExprEvaluate] - [gtcfForceArrayEval]);
      if i <= 1
      then FArrayIndexValues[i].FArrayIndexValueLimit := FArrayIndexValueLimit - 2
      else FArrayIndexValues[i].FArrayIndexValueLimit := FArrayIndexValueLimit - 3;
      AddSubType(FArrayIndexValues[i]);
    end;
  end;

  procedure EvaluateExpressionDynArray;
  begin
    FProcessState := gtpsEvalExprDynArray;
    if FExprEvaluateFormat <> wdfDefault then begin;
      Result := True;
      exit;
    end;


    FBoundLow :=  -1;
    FBoundHigh := -1;
    FLen := -1;

    if (FArrayIndexValueLimit < 0) then begin
      FExprEvaluatedAsText := '(...)';
      FHasExprEvaluatedAsText := True;
      Result := True;
      exit;
    end;

    if not RequireRequests([gptrEvalCustomEval], '^^longint('+FExpression+')[-1]') then exit;
    if not IsReqError(gptrEvalCustomEval, False) then begin
      FBoundLow :=  0;
      FBoundHigh  := StrToIntDef(GetParsedFromResult(FReqResults[gptrEvalCustomEval].Result.GdbDescription, 'value'), -1);
      FLen := FBoundHigh + 1;
    end;

    if (saInternalPointer in FAttributes) then begin
      if not RequireRequests([gptrEvalExprDeRef]) then exit;
      if not IsReqError(gptrEvalExprDeRef, False) then begin
        ParseFromResult(FReqResults[gptrEvalExprDeRef].Result.GdbDescription, 'value');
        EvaluateExpressionDynArrayGetData;
        exit;
      end;
    end;

    if (saRefParam in FAttributes) then begin
      if not RequireRequests([gptrEvalExprCast]) then exit;
      if not IsReqError(gptrEvalExprCast, False) then begin
        ParseFromResult(FReqResults[gptrEvalExprCast].Result.GdbDescription, 'value');
        EvaluateExpressionDynArrayGetData;
        exit;
      end;
    end;

    if not RequireRequests([gptrEvalExpr]) then exit;
    if not IsReqError(gptrEvalExpr, False) then begin
      ParseFromResult(FReqResults[gptrEvalExpr].Result.GdbDescription, 'value');
      EvaluateExpressionDynArrayGetData;
      exit;
    end;

    if FLen > 0 then begin
      EvaluateExpressionDynArrayGetData;
      exit;
    end;

    // TODO: set Validity = error
    ParseFromResult(FReqResults[gptrEvalExpr].Result.GdbDescription, 'msg');
    Result := True;
  end;

  procedure EvaluateExpressionArray;
  var
    PTypeResult: TGDBPTypeResult;
  begin
    FProcessState := gtpsEvalExprArray;
    if FExprEvaluateFormat <> wdfDefault then begin;
      Result := True;
      exit;
    end;

    PTypeResult := FReqResults[gptrPTypeExpr].Result;
    FBoundLow :=  PCLenToInt(PTypeResult.BoundLow);
    FBoundHigh := PCLenToInt(PTypeResult.BoundHigh);
    FLen := PCLenToInt(PTypeResult.BoundHigh) - PCLenToInt(PTypeResult.BoundLow) + 1;

    if (gtcfForceArrayEval in FCreationFlags) then begin
      EvaluateExpressionDynArrayGetData;
      exit;
    end;

    if (saInternalPointer in FAttributes) then begin
      if not RequireRequests([gptrEvalExprDeRef]) then exit;
      if not IsReqError(gptrEvalExprDeRef, False) then begin
        ParseFromResult(FReqResults[gptrEvalExprDeRef].Result.GdbDescription, 'value');
        Result := True;
        exit;
      end;
    end;

    if (saRefParam in FAttributes) then begin
      if not RequireRequests([gptrEvalExprCast]) then exit;
      if not IsReqError(gptrEvalExprCast, False) then begin
        ParseFromResult(FReqResults[gptrEvalExprCast].Result.GdbDescription, 'value');
        Result := True;
        exit;
      end;
    end;

    if not RequireRequests([gptrEvalExpr]) then exit;
    if not IsReqError(gptrEvalExpr, False) then begin
      ParseFromResult(FReqResults[gptrEvalExpr].Result.GdbDescription, 'value');
      Result := True;
      exit;
    end;

    // TODO: set Validity = error
    ParseFromResult(FReqResults[gptrEvalExpr].Result.GdbDescription, 'msg');
    Result := True;
  end;

  procedure EvaluateExpressionRepeated;
  var
    ExpArray: TGDBExpressionPartArray;
    s: String;
    Idx: Int64;
    Error: word;
    i: Integer;
  begin
    FProcessState := gtpsEvalExprRepeated;

    if (FRepeatCount < 1) then begin
      Result := True;
      exit;
    end;

    if (FRepeatCount < 1) or (FParsedExpression.PartCount <> 1) or
       not (FParsedExpression.Parts[0] is TGDBExpressionPartArray)
    then begin
      FRepeatCount := 0;
      EvaluateExpression;
      exit;
    end;

    if FRepeatCountEval <> nil then begin
      if not FRepeatCountEval.HasExprEvaluatedAsText then begin
        FRepeatCount := 0;
        EvaluateExpression;
        exit;
      end;
      FExprEvaluatedAsText := FRepeatCountEval.ExprEvaluatedAsText;
      FFields.Free;
      FFields := TDBGFields.Create;
      for i := 0 to FRepeatCountEval.Fields.Count - 1 do
        FFields.Add(FRepeatCountEval.Fields[i]);
      FHasExprEvaluatedAsText := True;
      FreeAndNil(FRepeatCountEval);
      Result := True;
      exit;
    end;

    ExpArray := TGDBExpressionPartArray(FParsedExpression.Parts[0]);
	if ExpArray.IndexCount < 1 then begin
      FRepeatCount := 0;
      EvaluateExpression;
      exit;
    end;

    s := ExpArray.IndexPart[ExpArray.IndexCount - 1].GetPlainText;
    if not RequireRequests([gptrEvalExpr2], Quote('('+s+')+0')) then exit;

    if IsReqError(gptrEvalExpr2, False) then begin
      FRepeatCount := 0;
      EvaluateExpression;
      exit;
    end;

    s := GetParsedFromResult(FReqResults[gptrEvalExpr2].Result.GdbDescription, 'value');
    Val(s, Idx, Error);

    if Error <> 0  then begin
      FRepeatCount := 0;
      EvaluateExpression;
      exit;
    end;

    FRepeatCountEval := TGDBType.CreateForExpression(
      ExpArray.GetTextToIdx(ExpArray.IndexCount-2),
      FCreationFlags + [gtcfExprEvaluate, gtcfForceArrayEval],
      FExprEvaluateFormat,
      FRepeatCount
    );
    FRepeatCountEval.RepeatFirstIndex := Idx;
    AddSubType(FRepeatCountEval);

  end;

  procedure EvaluateExpression;
  begin
    FProcessState := gtpsEvalExpr;

    if not(gtcfExprEvaluate in FCreationFlags) then begin
      Result := True;
      exit;
    end;

    if (FRepeatCount > 1) and (FParsedExpression.PartCount = 1) and
       (FParsedExpression.Parts[0] is TGDBExpressionPartArray) and
       not(gtcfForceArrayEval in FCreationFlags)
    then begin
      exclude(FProccesReuestsMade, gptrEvalExpr2);
      EvaluateExpressionRepeated;
      exit;
    end;

    if saDynArray in FAttributes then begin
      EvaluateExpressionDynArray;
      exit;
    end;
    if saArray in FAttributes then begin
      EvaluateExpressionArray;
      exit;
    end;

    if FExprEvaluateFormat <> wdfDefault then begin;
      Result := True;
      exit;
    end;

    if (gtcfForceArrayEval in FCreationFlags) then begin
      FBoundLow :=  FRepeatFirstIndex;
      FBoundHigh := FRepeatFirstIndex + FRepeatCount - 1;
      FLen := FRepeatCount;
      EvaluateExpressionDynArrayGetData;
      exit;
    end;

    // TODO: stringFixed need to know about:
    // - AutoTypeCast

    if (saInternalPointer in FAttributes) then begin
      if not RequireRequests([gptrEvalExprDeRef]) then exit;
      if not IsReqError(gptrEvalExprDeRef, False) then begin
        ParseFromResult(FReqResults[gptrEvalExprDeRef].Result.GdbDescription, 'value');

        if (gtcfExprEvalStrFixed in FCreationFlags) and
           (FParsedExpression <> nil) and FParsedExpression.MayNeedStringFix
        then begin
          if not RequireRequests([gptrEvalExprDeRef2], FParsedExpression.TextStrFixed) then exit;
          ParseFromResultForStrFixed(FReqResults[gptrEvalExprDeRef2].Result.GdbDescription, 'value');
        end;

        Result := True;
        exit;
      end;
    end;

    if (saRefParam in FAttributes) then begin
      if not RequireRequests([gptrEvalExprCast]) then exit;
      if not IsReqError(gptrEvalExprCast, False) then begin
        ParseFromResult(FReqResults[gptrEvalExprCast].Result.GdbDescription, 'value');

        if (gtcfExprEvalStrFixed in FCreationFlags) and
           (FParsedExpression <> nil) and FParsedExpression.MayNeedStringFix
        then begin
          if not RequireRequests([gptrEvalExprCast2], FParsedExpression.TextStrFixed) then exit;
          ParseFromResultForStrFixed(FReqResults[gptrEvalExprCast2].Result.GdbDescription, 'value');
        end;

        Result := True;
        exit;
      end;
    end;

      if not RequireRequests([gptrEvalExpr]) then exit;
      if not IsReqError(gptrEvalExpr, False) then begin
        ParseFromResult(FReqResults[gptrEvalExpr].Result.GdbDescription, 'value');

        if (gtcfExprEvalStrFixed in FCreationFlags) and
           (FParsedExpression <> nil) and FParsedExpression.MayNeedStringFix
        then begin
          if not RequireRequests([gptrEvalExpr2], FParsedExpression.TextStrFixed) then exit;
          ParseFromResultForStrFixed(FReqResults[gptrEvalExpr2].Result.GdbDescription, 'value');
        end;

        Result := True;
      exit;
    end;

      // TODO: set Validity = error
      ParseFromResult(FReqResults[gptrEvalExpr].Result.GdbDescription, 'msg');
      Result := True;
    end;
  {%endregion    * EvaluateExpression * }

  procedure ProcessInitialSimple;
  var
    i: Integer;
    PTypeResult: TGDBPTypeResult;
  begin
    FProcessState := gtpsInitialSimple;

    // TODO: ptype may be known by FParsedExpression
    if not RequireRequests([gptrPTypeExpr]) //+wi)
    then exit;

    if IsReqError(gptrPTypeExpr) then begin
       //Cannot access memory at address 0x0
      if (pos('address 0x0', FReqResults[gptrPTypeExpr].Error) > 0) and
         FParsedExpression.MayNeedTypeCastFix
      then begin
        exclude(FProccesReuestsMade, gptrPTypeExpr);
        ProcessInitial;
        exit;
      end;

      FEvalError := True;
      exit;
    end;
    PTypeResult := FReqResults[gptrPTypeExpr].Result;

    if (ptprfParamByRef in PTypeResult.Flags) then
      include(FAttributes, saRefParam);

    // In DWARF, some Dynamic Array, are pointer to there base type
    if (ptprfPointer in PTypeResult.Flags) and (PTypeResult.Kind =ptprkSimple)
    then begin
      if not RequireRequests([gptrPTypeExprDeRef])
      then exit;
      if (not IsReqError(gptrPTypeExprDeRef)) and
         (FReqResults[gptrPTypeExprDeRef].Result.Kind = ptprkArray)
      then begin
        ProcessArray;
        exit;
      end;
    end;

    case PTypeResult.Kind of
      //ptprkError: ;
      //ptprkSimple: ;
      ptprkClass: begin
          ProcessClass;
          exit;
        end;
      //ptprkRecord: ;
      //ptprkEnum: ;
      //ptprkSet: ;
      ptprkArray: begin
          ProcessArray;
          exit;
      end;
      //ptprkProcedure: ;
      //ptprkFunction: ;
    end;

    if (ptprfPointer in PTypeResult.Flags)
    and ( (PTypeResult.Kind in [ptprkSimple, ptprkRecord, ptprkEnum, ptprkSet])
          or ( (gtcfClassIsPointer in FCreationFlags) and
               (PTypeResult.Kind in [ptprkProcedure, ptprkFunction])  )
        )
    then begin
      ProcessSimplePointer;
      exit;
    end;

    if (ptprfParamByRef in PTypeResult.Flags)
    and not (PTypeResult.Kind in [ptprkError])
    then begin
      // could be a pointer // need ptype of whatis
      if not RequireRequests([gptrWhatisExpr])
      then exit;

      if (FReqResults[gptrWhatisExpr].Result.BaseName.Len > 0)
      then begin
        if not RequireRequests([gptrPTypeOfWhatis])
        then exit;

        if (not IsReqError(gptrPTypeOfWhatis, False))
        and (ptprfPointer in FReqResults[gptrPTypeOfWhatis].Result.Flags) then begin
          // pointer
          FKind := skPointer;
          SetTypNameFromReq(gptrWhatisExpr);
          Result := True;
          // ====> DONE
          exit;
        end;
      end;
    end;

    case PTypeResult.Kind of
      ptprkError: begin
          // could be empty pointer @ArgProcedure
          Result := True; // nothing to be done, keep simple type, no name
        end;
      ptprkSimple: begin
          // may only need whatis, if current name isn't usable?
          if not RequireRequests([gptrWhatisExpr])
          then exit;

          SetTypNameFromReq(gptrWhatisExpr, True);
          FKind := skSimple;
          Result := True;
          // ====> DONE
        end;
      ptprkClass: begin
          Assert(False, 'GDBTypeInfo Class: Should be handled before');
          ProcessClass;
        end;
      ptprkRecord: begin
          SetTypNameFromReq(gptrWhatisExpr, True);
          DoRecord;
          Result := True;
          // ====> DONE
        end;
      ptprkEnum: begin
          SetTypNameFromReq(gptrWhatisExpr, True);
          FTypeDeclaration := ClearAmpersand(PCLenToString(PTypeResult.Declaration));
          DoEnum;
          Result := True;
          // ====> DONE
        end;
      ptprkSet: begin
          if not RequireRequests([gptrWhatisExpr])
          then exit;

          SetTypNameFromReq(gptrWhatisExpr, True);
          // TODO: resolve enum-name (set of SomeEnum) if mode-full ?
          FTypeDeclaration := ClearAmpersand(PCLenToString(PTypeResult.Declaration));
          i := pos('set of  = ', FTypeDeclaration);
          if  i > 0 then delete(FTypeDeclaration, i+7, 3);
          DoSet;
          Result := True;
          // ====> DONE
        end;
      ptprkArray: begin
          Assert(False, 'GDBTypeInfo Array: Should be handled before');
          ProcessArray;
        end;
      ptprkProcedure: begin
          // under stabs, procedure/function are always pointer // pointer to proc/func return empty type
          if (gtcfClassIsPointer in FCreationFlags) // Dwarf
          and (ptprfPointer in PTypeResult.Flags)
          then begin
            ProcessSimplePointer;
            exit;
          end;

          if not RequireRequests([gptrWhatisExpr])
          then exit;

          SetTypNameFromReq(gptrWhatisExpr, True, 'procedure');
          DoProcedure;
          Result := True;
          // ====> DONE
        end;
      ptprkFunction: begin
          // under stabs, procedure/function are always pointer // pointer to proc/func return empty type
          if (gtcfClassIsPointer in FCreationFlags) // Dwarf
          and (ptprfPointer in PTypeResult.Flags)
          then begin
            ProcessSimplePointer;
            exit;
          end;

          if not RequireRequests([gptrWhatisExpr])
          then exit;

          SetTypNameFromReq(gptrWhatisExpr, True, 'function');
          DoFunction;
          Result := True;
          // ====> DONE
      end;
    end;
  end;

  procedure ProcessInitial;
  begin
    FProcessState := gtpsInitial;
    if FExpression = '' then begin;
      FPTypeExpression := FExpression;
      ProcessInitialSimple;
      exit;
    end;

    if FParsedExpression = nil
    then FParsedExpression := TGDBExpression.Create(FExpression);
    // Does not set FLastEvalRequest
    if FParsedExpression.NeedValidation(FEvalRequest)
    then exit;

    FExpression := FParsedExpression.Text;
    FPTypeExpression := FParsedExpression.TextEx[[toSkipArrayIdx]];

    ProcessInitialSimple;
  end;

  procedure MergeSubProcessRequests;
  var
    SubType: TGDBType;
  begin
    SubType := FFirstProcessingSubType;
    while SubType <> nil do begin
      if (FEvalRequest =  nil)
      then FEvalRequest := SubType.FEvalRequest
      else if FLastEvalRequest <> nil
      then FLastEvalRequest^.Next := SubType.FEvalRequest
      else begin
        // Find last req
        FLastEvalRequest := FEvalRequest;
        while (FLastEvalRequest^.Next <> nil) do
          FLastEvalRequest := FLastEvalRequest^.Next;
        FLastEvalRequest^.Next := SubType.FEvalRequest;
      end;
      FLastEvalRequest := SubType.FLastEvalRequest;
      SubType := SubType.FNextProcessingSubType;
    end;
  end;

  function ProcessSubProcessRequests: Boolean;
  var
    SubType, PrevSubType: TGDBType;
  begin
    DebugLnEnter(DBGMI_TYPE_INFO, ['>>Enter Sub-Requests']);
    PrevSubType := nil;
    SubType := FFirstProcessingSubType;
    while SubType <> nil do begin
      if SubType.ProcessExpression then begin
        if PrevSubType = nil
        then FFirstProcessingSubType := SubType.FNextProcessingSubType
        else PrevSubType.FNextProcessingSubType := SubType.FNextProcessingSubType;
      end
      else
        PrevSubType := SubType;
      SubType := SubType.FNextProcessingSubType;
    end;

    Result := FFirstProcessingSubType = nil;
    DebugLnExit(DBGMI_TYPE_INFO, ['>>Leave Sub-Request']);
  end;

var
  OldProcessState: TGDBTypeProcessState;
  OldReqMade: TGDBTypeProcessRequests;
  s: string;
begin
  Result := False;
  FEvalRequest := nil;
  FLastEvalRequest := nil;
  Lines := nil;
  DebugLnEnter(DBGMI_TYPE_INFO, ['>>Enter: TGDBType.ProcessExpression: ', DebugString]);
  try


  if FFirstProcessingSubType <> nil then begin
    if not ProcessSubProcessRequests then begin
      MergeSubProcessRequests;
      exit;
    end;
  end;

  OldProcessState := FProcessState;
  OldReqMade := FProccesReuestsMade;

  case FProcessState of
    gtpsInitial:            ProcessInitial;
    gtpsInitialSimple:      ProcessInitialSimple;
    gtpsSimplePointer:      ProcessSimplePointer;
    gtpsClass:              ProcessClass;
    gtpsClassAutoCast:      ProcessClassAutoCast;
    gtpsClassPointer:       ProcessClassPointer;
    gtpsFinishProcessClass: FinishProcessClass;
    gtpsClassAncestor:      ProcessClassAncestor;
    gtpsArray:              ProcessArray;
    gtpsEvalExpr:           EvaluateExpression;
    gtpsEvalExprRepeated:   EvaluateExpressionRepeated;
    gtpsEvalExprArray:      EvaluateExpressionArray;
    gtpsEvalExprDynArray:   EvaluateExpressionDynArray;
    gtpsEvalExprDynArrayGetData: EvaluateExpressionDynArrayGetData;
  end;

  FreeAndNil(Lines);
  if Result and not(FEvalStarted)
  then begin
    Result := False;
    FEvalStarted := True;
    EvaluateExpression;
  end;

  if Result
  then begin
    if FHasExprEvaluatedAsText and FMaybeShortString and
       (length(FExprEvaluatedAsText) > 0) and
       (FExprEvaluatedAsText[1] in ['''', '#']) // not a record struct
    then begin
      FTypeName := 'ShortString';
      FKind := skSimple;
      FreeAndNil(FFields);
    end;
    if Value.AsString = '' then
      Value.AsString := ExprEvaluatedAsText;

    FProcessState := gtpsFinished;
  end;

  if FFirstProcessingSubType <> nil then
    MergeSubProcessRequests
  else
  if (FProcessState = OldProcessState) and (FProccesReuestsMade = OldReqMade)
  and (not Result) and (FEvalRequest = nil)
  then begin
    debugln(DBG_WARNINGS, ['ERROR: detected state loop in ProcessExpression']);
    Result := True;
  end;
  finally
    WriteStr(s, FProcessState);
    DebugLnExit(DBGMI_TYPE_INFO, ['<<Exit:  TGDBType.ProcessExpression: state = ', s, '  Result=', dbgs(Result),
                 ' Kind=', dbgs(Kind), ' Attr=', dbgs(Attributes), ' Typename="', TypeName, '" InternTpName="', FInternalTypeName,'" TypeDeclaration="', TypeDeclaration, '"']);
  end;
end;

{ TGDBPTypes }

constructor TGDBTypes.CreateFromCSV(AValues: String);
var
  GDBType: TGDBType;
begin
  Create;
  while AValues <> '' do
  begin
    GDBType := TGDBType.Create(skSimple, GetPart([], [', '], AValues));
    FList.Add(GDBType);
    {if Length(AValues) >= 2 then} Delete(AValues, 1, 2);
  end;
end;

initialization
  DBGMI_TYPE_INFO := DebugLogger.RegisterLogGroup('DBGMI_TYPE_INFO' {$IFDEF DBGMI_TYPE_INFO} , True {$ENDIF} );
  DBG_WARNINGS := DebugLogger.FindOrRegisterLogGroup('DBG_WARNINGS' {$IFDEF DBG_WARNINGS} , True {$ENDIF} );

end.
