unit FpDebugFpcConvVariantNormalizer;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FpDebugValueConvertors, FpDebugDebuggerBase,
  FpDebugStringConstants, LazDebuggerValueConverter, LazDebuggerIntf, FpDbgInfo,
  FpDbgUtil, FpErrorMessages, FpDbgDwarf, FpdMemoryTools, DbgIntfBaseTypes;

type

  (**** Display variant as single/simple value ****)


  { TFpDbgValueConverterVariantNormalizer }

  TFpDbgValueConverterVariantNormalizer = class(TFpDbgValueConverter)
  protected
    //function GetSettingsFrame: ILazDbgValueConverterSettingsFrameIntf; override;  // TConverterSettingsFrameBase
  public
    class function GetName: String; override;
    function GetRegistryEntry: TLazDbgValueConvertRegistryEntryClass; override;
    function NeedConversionLimit: Boolean; override;
    function CanHandleValue(ASourceValue: TFpValue; AnFpDebugger: TFpDebugDebuggerBase): Boolean; override;
    function ConvertValue(ASourceValue: TFpValue;
                          AnFpDebugger: TFpDebugDebuggerBase;
                          AnExpressionScope: TFpDbgSymbolScope;
                          var AnResData: IDbgWatchDataIntf
                         ): TFpValue; override;
  end;

  { TFpDbgValueConverterVariantNormalizerRegistryEntry }

  TFpDbgValueConverterVariantNormalizerRegistryEntry = class(TFpDbgValueConverterRegistryEntry)
  public
    class function GetConvertorClass: TClass; override;
  end;


implementation

{ TFpDbgValueConverterVariantNormalizer }

class function TFpDbgValueConverterVariantNormalizer.GetName: String;
begin
  Result := drsConverterNormalizeVariant;
end;

function TFpDbgValueConverterVariantNormalizer.GetRegistryEntry: TLazDbgValueConvertRegistryEntryClass;
begin
  Result := TFpDbgValueConverterVariantNormalizerRegistryEntry;
end;

function TFpDbgValueConverterVariantNormalizer.NeedConversionLimit: Boolean;
begin
  Result := False;
end;

function TFpDbgValueConverterVariantNormalizer.CanHandleValue(
  ASourceValue: TFpValue; AnFpDebugger: TFpDebugDebuggerBase): Boolean;
var
  m, m2: TFpValue;
begin
  Result := (ASourceValue.Kind = skRecord) and (AnFpDebugger.DbgController.CurrentProcess <> nil);
  if (not Result) then
    exit;

  if AnFpDebugger.DbgController.CurrentProcess.Mode = dm32 then
    Result := (ASourceValue.DataSize.Size = 16)
  else
    Result := (ASourceValue.DataSize.Size = 24);

  if (not Result) then
    exit;

  if (ASourceValue.MemberCount = 1) then begin
    m := ASourceValue.Member[0];
    Result := (m <> nil);
    if not Result then begin
      exit;
      m.ReleaseReference;
    end;

    m2 := m.Member[-1];
    m.ReleaseReference;
    Result := (m2 <> nil) and (m2.FieldFlags * [svfInteger, svfCardinal, svfOrdinal] <> []);
    m2.ReleaseReference;

    exit;
  end
  else
  begin
    m := ASourceValue.MemberByName['vtype'];
    Result := (m <> nil);
    m.ReleaseReference;
    exit;
  end;

  Result := False;
end;

function TFpDbgValueConverterVariantNormalizer.ConvertValue(
  ASourceValue: TFpValue; AnFpDebugger: TFpDebugDebuggerBase;
  AnExpressionScope: TFpDbgSymbolScope; var AnResData: IDbgWatchDataIntf
  ): TFpValue;

  procedure ReturnSourceValue;
  begin
    Result.ReleaseReference;
    ASourceValue.AddReference;
    Result := ASourceValue;
  end;

  procedure ReturnNil;
  begin
    Result.ReleaseReference;
    Result := nil;
  end;

  procedure HandleAnsiString;
  var
    Addr: TFpDbgMemLocation;
    sz: Integer;
    SLen: Int64;
    Str: string;
  begin
    if (Result = nil) then begin
      ReturnSourceValue;
      exit;
    end;
    if (Result.Kind <> skPointer) then
      exit;

    Addr := Result.DerefAddress;
    if not IsTargetNotNil(Addr) then
      exit;

    sz := AnExpressionScope.LocationContext.SizeOfAddress;
    {$PUSH}{$R-}{$Q-}
    Addr.Address := Addr.Address - sz;
    {$POP}
    AnExpressionScope.LocationContext.ReadSignedInt(Addr, SizeVal(sz), SLen);
    if IsError(AnExpressionScope.LocationContext.LastMemError) then begin
      AnResData.CreateError('<Error reading string>');
      ReturnNil;
      exit;
    end;

    {$PUSH}{$R-}{$Q-}
    Addr.Address := Addr.Address + sz;
    {$POP}
    if SLen > AnExpressionScope.LocationContext.MemManager.MemLimits.MaxStringLen then
      SLen := AnExpressionScope.LocationContext.MemManager.MemLimits.MaxStringLen;
    SetLength(Str, SLen);
    if SLen > 0 then
      AnExpressionScope.LocationContext.ReadMemory(Addr, SizeVal(SLen), @Str[1]);
    if IsError(AnExpressionScope.LocationContext.LastMemError) then
      AnResData.CreateError('<Error reading string>')
    else
      AnResData.CreateString(Str);
    ReturnNil;
  end;

var
  m, m2, vtype: TFpValue;
  discr: Int64;
  i: Integer;
  t: TFpSymbol;
  OldResData: IDbgWatchDataIntf;
begin
  Result := nil;

  OldResData := AnResData;
  AnResData := AnResData.CreateVariantValue();
  t := ASourceValue.TypeInfo;
  if (t <> nil) then
    OldResData.SetTypeName(t.Name);


  if ASourceValue.MemberCount = 1 then begin
    // dwarf 3
    m := ASourceValue.Member[0];
    if m = nil then begin
      ReturnSourceValue;
      exit;
    end;

    vtype := m.Member[-1];
    if vtype = nil then begin
      m.ReleaseReference;
      ReturnSourceValue;
      exit;
    end;

    discr := vtype.AsInteger;
    try
      for i := 0 to m.MemberCount - 1 do begin
        m2 := m.Member[i];
        try
          if (m2 <> nil) and
             (m2.DbgSymbol is TFpSymbolDwarfTypeVariant) and
             (TFpSymbolDwarfTypeVariant(m2.DbgSymbol).MatchesDiscr(discr)) and
             (m2.MemberCount = 1)
          then begin
            Result := m2.Member[0];

            case discr of
              0: if Result = nil then AnResData.CreatePrePrinted('<Empty>');
              1: if Result = nil then AnResData.CreatePrePrinted('<Null>');
              256: HandleAnsiString;
              otherwise
                if Result = nil then ReturnSourceValue;
            end;

            exit;
          end;
        finally
          m2.ReleaseReference;
        end;
      end;
    finally
      m.ReleaseReference;
      vtype.ReleaseReference;
    end;
  end
  else begin
    // dwarf 2
    vtype := ASourceValue.MemberByName['vtype'];
    try
      i := vtype.AsInteger;
      case i of
           0: begin
               AnResData.CreatePrePrinted('<Empty>');
               exit;
             end;
           1: begin
               AnResData.CreatePrePrinted('<Null>');
               exit;
             end;
           2: Result := ASourceValue.MemberByName['VSMALLINT'];
           3: Result := ASourceValue.MemberByName['VINTEGER'];
           4: Result := ASourceValue.MemberByName['VSINGLE'];
           5: Result := ASourceValue.MemberByName['VDOUBLE'];
           6: Result := ASourceValue.MemberByName['VCURRENCY'];
           7: Result := ASourceValue.MemberByName['VDATE'];
           8: Result := ASourceValue.MemberByName['VOLESTR'];
           9: Result := ASourceValue.MemberByName['VDISPATCH'];
          10: Result := ASourceValue.MemberByName['VERROR'];
          11: Result := ASourceValue.MemberByName['VBOOLEAN'];
          //12: varVariant
          13: Result := ASourceValue.MemberByName['VUNKNOWN'];
          //14: varDecimal
          //15??
          16: Result := ASourceValue.MemberByName['VSHORTINT'];
          17: Result := ASourceValue.MemberByName['VBYTE'];
          18: Result := ASourceValue.MemberByName['VWORD'];
          19: Result := ASourceValue.MemberByName['VLONGWORD'];
          20: Result := ASourceValue.MemberByName['VINT64'];
          21: Result := ASourceValue.MemberByName['VQWORD'];
          22: Result := ASourceValue.MemberByName['VWORD'];
          //
          36: Result := ASourceValue.MemberByName['VRECORD'];
          //72
         256: Result := ASourceValue.MemberByName['VSTRING'];
         257: Result := ASourceValue.MemberByName['VANY'];
         258: Result := ASourceValue.MemberByName['VUSTRING'];
         //258: varUString
        8192: Result := ASourceValue.MemberByName['VARRAY'];
       16384: Result := ASourceValue.MemberByName['VPOINTER'];
      end;

      if Result = nil then begin
        ReturnSourceValue;
      end
      else
      case i of
        256: begin
            HandleAnsiString;
            exit;
          end;
      end;
    finally
      vtype.ReleaseReference;
    end;
    exit;
  end;

  if Result = nil then
    ReturnSourceValue;
end;

{ TFpDbgValueConverterVariantNormalizerRegistryEntry }

class function TFpDbgValueConverterVariantNormalizerRegistryEntry.GetConvertorClass: TClass;
begin
  Result := TFpDbgValueConverterVariantNormalizer;
end;

initialization
  ValueConverterRegistry.Add(TFpDbgValueConverterVariantNormalizerRegistryEntry);

end.

