Recent

Author Topic: ToStr - Convert any value to String using RTTI  (Read 13647 times)

Thaddy

  • Hero Member
  • *****
  • Posts: 14205
  • Probably until I exterminate Putin.
Re: ToStr - Convert any value to String using RTTI
« Reply #15 on: October 12, 2018, 06:08:27 pm »
Note that both versions should be in:
In case of a var - so not a type - the original works magic.
If you know the type my addition is helpful.
For example a var that is an array.
Specialize a type, not a var.

garlar27

  • Hero Member
  • *****
  • Posts: 652
Re: ToStr - Convert any value to String using RTTI
« Reply #16 on: October 12, 2018, 07:39:22 pm »
I haven't tried it out, but if it works well I'd love to see it included with FPC core.
I don't want to disencourage you, but in my eyes, it is not flexible enough for inclusion in fcl: What if I want floats in scientific notation, integers in hex, a different field separator?
I agree with the formatting thing, and its very easy to change it :
Code: Pascal  [Select][+][-]
  1.    function ToStr(var AX; ATypeInfo: PTypeInfo; FormatSettings: TFormatSettings): AnsiString;
  2.    type
  3.      TArray = array of Byte;
  4.    var
  5.      I: LongInt;
  6.      TypeData: PTypeData;
  7.      //FormatSettings: TFormatSettings;
  8.    begin
  9.      //FillByte(FormatSettings, SizeOf(TFormatSettings), 0);
  10.      //FormatSettings.DecimalSeparator := '.';
  11.      TypeData := GetTypeData(ATypeInfo);
  12.      Result := '"'+ATypeInfo^.Name + '" : '; // I added something here...
  13.      case ATypeInfo^.Kind of
  14.        tkChar: Result += '''' + Char(AX) + '''';
  15.        tkWChar: Result += '''' + AnsiString(WChar(AX)) + '''';
  16.        tkBool: Result += BoolToStr(Boolean(AX), True);
  17.        tkInteger:
  18.        begin
  19.          case TypeData^.OrdType of
  20.            otSByte: Result += IntToStr(ShortInt(AX));
  21.            otUByte: Result += IntToStr(Byte(AX));
  22.            otSWord: Result += IntToStr(SmallInt(AX));
  23.            otUWord: Result += IntToStr(Word(AX));
  24.            otSLong: Result += IntToStr(LongInt(AX));
  25.            otULong: Result += IntToStr(LongWord(AX));
  26.          end;
  27.        end;
  28.        tkInt64: Result += IntToStr(Int64(AX));
  29.        tkQWord: Result += IntToStr(QWord(AX));
  30.        tkFloat:
  31.        begin
  32.          case TypeData^.FloatType of
  33.            ftSingle: Result += FormatFloat('0.###', Single(AX), FormatSettings);
  34.            ftDouble: Result += FormatFloat('0.###', Double(AX), FormatSettings);
  35.            ftExtended: Result += FormatFloat('0.###', Extended(AX), FormatSettings);
  36.            ftComp: Result += FormatFloat('0.###', Comp(AX), FormatSettings);
  37.            ftCurr: Result += FormatFloat('0.###', Currency(AX), FormatSettings);
  38.          end;
  39.        end;
  40.        tkSString: Result += '''' + ShortString(AX) + '''';
  41.        tkAString: Result += '''' + AnsiString(AX) + '''';
  42.        tkWString: Result += '''' + AnsiString(WideString(AX)) + '''';
  43.        tkUString: Result += '''' + AnsiString(UnicodeString(AX)) + '''';
  44.        tkRecord:
  45.        begin
  46.          Result += '(';
  47.          for I := 0 to TypeData^.ManagedFldCount - 1 do
  48.          begin
  49.            if I > 0 then Result += ', ';
  50.            with PManagedField(PByte(@TypeData^.ManagedFldCount) + SizeOf(TypeData^.ManagedFldCount) + (I * SizeOf(TManagedField)))^ do
  51.              Result += ToStr((PByte(@AX) + FldOffset)^, TypeRef);
  52.          end;
  53.          Result += ')';
  54.        end;
  55.        tkArray:
  56.        begin
  57.          Result += '[';
  58.          for I := 0 to TypeData^.ArrayData.ElCount - 1 do
  59.          begin
  60.            if I > 0 then Result += ', ';
  61.            Result += ToStr((PByte(@AX) + (I * (TypeData^.ArrayData.Size div TypeData^.ArrayData.ElCount)))^, TypeData^.ArrayData.ElType);
  62.          end;
  63.          Result += ']';
  64.        end;
  65.        tkDynArray:
  66.        begin
  67.          Result += '[';
  68.          for I := 0 to Length(TArray(AX)) - 1 do
  69.          begin
  70.            if I > 0 then Result += ', ';
  71.            Result += ToStr((PByte(@TArray(AX)[0]) + (I * TypeData^.ElSize))^, TypeData^.ElType2);
  72.          end;
  73.          Result += ']';
  74.        end;
  75.        tkClass: Result += TObject(AX).ToString;
  76.        tkEnumeration: Result += GetEnumName(ATypeInfo, Integer(AX));
  77.        tkSet: Result += SetToString(ATypeInfo, Integer(AX), True).Replace(',', ', ');
  78.        tkVariant: Result += VarToStr(Variant(AX)); //TODO: Write VariantToString for consistency (for string and float point numbers)
  79.        else Result += ATypeInfo^.Name + '@' + HexStr(@AX);
  80.      end;
  81.    end;
  82.    function ToStr(var AX; ATypeInfo: PTypeInfo): AnsiString;
  83.    begin
  84.      Result := ToStr(AX, ATypeInfo, DefaultFormatSettings);
  85.    end;
  86.  

Thaddy

  • Hero Member
  • *****
  • Posts: 14205
  • Probably until I exterminate Putin.
Re: ToStr - Convert any value to String using RTTI
« Reply #17 on: October 12, 2018, 08:33:50 pm »
Close and almost a cigar, but why not:
Code: Pascal  [Select][+][-]
  1.   function ToStr(var AX; ATypeInfo: PTypeInfo; FormatSettings: TFormatSettings = defaultformatsettings): AnsiString;
You can subsequently do the same with my template addition suggestion...

Good suggestion! 8)

Specialize a type, not a var.

Thaddy

  • Hero Member
  • *****
  • Posts: 14205
  • Probably until I exterminate Putin.
Re: ToStr - Convert any value to String using RTTI
« Reply #18 on: October 12, 2018, 09:06:42 pm »
@correa.elias et. al.
This is a very enjoyable subject. I hope this leads to an inclusion in the core sources.
Maybe some shavings here and there, but really a good effort and really good contributions.
Specialize a type, not a var.

correa.elias

  • New Member
  • *
  • Posts: 18
Re: ToStr - Convert any value to String using RTTI
« Reply #19 on: October 12, 2018, 09:42:56 pm »
Quote
I have reviewed the code and it looks very nice.
I have a couple of remarks, though:

1. ManagedFldCount is deprecated (at least in 3.2.0 and trunk).
    You can replace it with TotalFieldCount. (The compiler actually gives two suggestions. I chose this one)
2. I prefer {$mode Delphi}{$coperators on} over mode objfpc (see below code why!)
3. You can get rid of the typeinfo parameter and introduce type safety like so:
Code: Pascal  [Select]

    {$mode delphi}{$coperators on}
     
    function ToStr(var AX; ATypeInfo: PTypeInfo): AnsiString;overload;
    function Tostr<T>(var Value:T):AnsiString;overload;  // <----
     
    implementation
     
    function Tostr<T>(var Value:T):AnsiString;overload;
    begin
      Result :=Tostr(value,typeinfo(t));
    end;


My initial tests show no regressions with these minor changes. 
I will include the generic version, its fantastic! (as long as it compiles for people using the unit in objfpc mode too).
About the name deprecation, i am using 3.0.4 so i cant change it for now.

Quote
I don't want to disencourage you, but in my eyes, it is not flexible enough for inclusion in fcl: What if I want floats in scientific notation, integers in hex, a different field separator?
As suggested by marcov, avra, thaddy and garlar27 its easy to remedy by adding a FormatSettings parameter with a default value for the lazy :D. I will implement this: A FormatSettings record that includes float point, integer, boolean ect formatting and custom delimiters for array and record(currently '[]' and '()'), and the delimiter itself (currently a coma).

Quote
Within that context he did a pretty good job! Within that context it is also valuable, not to the FCL but to the rtl: in sysutils.
Theres other functions that uses typeinfo for string conversion like SetToString and GetEnumName, both in typinfo unit (i mean, its another place where ToStr could reside if its included in core).

Quote
I would still prefer Str() function name instead of ToStr() - and yes, I am aware there is already a Str() procedure.
Its a good name too, and could be used as an overload. I choose 'ToStr' because conversion functions uses it: IntToStr, FloatToStr and BoolToStr. It could be called VarToStr too...

Quote
Added new page http://wiki.freepascal.org/ToStr
Thanks alextp!

Quote
I'd love to see it included with FPC core
It would be great!
« Last Edit: October 12, 2018, 09:52:19 pm by correa.elias »

Thaddy

  • Hero Member
  • *****
  • Posts: 14205
  • Probably until I exterminate Putin.
Re: ToStr - Convert any value to String using RTTI
« Reply #20 on: October 12, 2018, 10:26:25 pm »
I will include the generic version, its fantastic! (as long as it compiles for people using the unit in objfpc mode too).
About the name deprecation, i am using 3.0.4 so i cant change it for now.
Yes, that works..... I understand your problem with FPC 3.0.4. just make a note ( or an ifdef)
[edit]
And var should be const...

Best I have seen in a long time apart from the core developers...- and Maciej.. O:-) - Well done...
There's a big difference between seeing good code and try to improve on it and seeing bad code you need to fix.
I think we all agree...
This is one of those occasions from my point of view.
« Last Edit: October 12, 2018, 10:53:50 pm by Thaddy »
Specialize a type, not a var.

garlar27

  • Hero Member
  • *****
  • Posts: 652
Re: ToStr - Convert any value to String using RTTI
« Reply #21 on: October 12, 2018, 11:10:53 pm »
I would still prefer Str() function name instead of ToStr() - and yes, I am aware there is already a Str() procedure.
I would name it "xToStr" or "AnythingToStr" instead.

[EDIT]
By the way. Is it possible to get the record's field's name?
« Last Edit: October 12, 2018, 11:14:49 pm by garlar27 »

ASerge

  • Hero Member
  • *****
  • Posts: 2223
Re: ToStr - Convert any value to String using RTTI
« Reply #22 on: October 12, 2018, 11:22:30 pm »
Some optimizations (so the file with a different name). Longer source code but shorter generated (about 30% on Win64).
1. Changed var to const.
2. Added simple validation of invalid input parameters.
3. Unnecessary code call is excluded. For example, TFormatSettings is only needed for floating types, or calling GetTypeData only for some cases.
4. Removed hints in clear places.
5. Repeated code (enumeration of array or record elements) is extracted to a separate procedure.

garlar27

  • Hero Member
  • *****
  • Posts: 652
Re: ToStr - Convert any value to String using RTTI
« Reply #23 on: October 13, 2018, 12:04:53 am »
Some optimizations (so the file with a different name). Longer source code but shorter generated (about 30% on Win64).
1. Changed var to const.
2. Added simple validation of invalid input parameters.
3. Unnecessary code call is excluded. For example, TFormatSettings is only needed for floating types, or calling GetTypeData only for some cases.
4. Removed hints in clear places.
5. Repeated code (enumeration of array or record elements) is extracted to a separate procedure.

WOW !!!
That's awesome!!!

JernejL

  • Jr. Member
  • **
  • Posts: 92
Re: ToStr - Convert any value to String using RTTI
« Reply #24 on: October 13, 2018, 09:42:46 am »
If there was ability to get TypeInfo for each array of const or just any parameter of called function, this feature would be much nicier to use:
You could just do print([some_variable])
 

Thaddy

  • Hero Member
  • *****
  • Posts: 14205
  • Probably until I exterminate Putin.
Re: ToStr - Convert any value to String using RTTI
« Reply #25 on: October 13, 2018, 12:17:17 pm »
If there was ability to get TypeInfo for each array of const or just any parameter of called function, this feature would be much nicier to use:
You could just do print([some_variable])
Well, that's not possible. That's why I wrote the generics addition. Tostr<SomeType>(SomeVariable);  // strongly typed.
Specialize a type, not a var.

ASerge

  • Hero Member
  • *****
  • Posts: 2223
Re: ToStr - Convert any value to String using RTTI
« Reply #26 on: October 13, 2018, 05:47:48 pm »
That's why I wrote the generics addition. Tostr<SomeType>(SomeVariable);  // strongly typed.
I think generic is not good here.
Lets see small project:
Code: Pascal  [Select][+][-]
  1. {$MODE OBJFPC}
  2. uses TypInfo;
  3.  
  4. function ToStr2(const AValue; ATypeInfo: PTypeInfo): string;
  5. begin
  6.   Result := '';
  7. end;
  8.  
  9. generic function ToStr<T>(const AValue: T): string; overload;
  10. begin
  11.   Result := ToStr2(AValue, TypeInfo(T));
  12. end;
  13.  
  14. var
  15.   i: Integer = 1;
  16. begin
  17.   ToStr2(i, TypeInfo(i));
  18.   specialize ToStr<Byte>(i);
  19. end.
1. It's compiled with trunk (FPC 3.1.1), but not in current 3.0.4.
2. When trying to rename ToStr2 to ToStr, a compiler error occurs (I know in $mode delphi it's OK).
3. The syntax for generic looks no easier than for a normal function (line 17 and 18).
4. It's not very strict, note that the types of variable and specialization are different in line 18.
5. In a specialization, you always need to write a type (in a normal function, only the variable name) and if the variable type needs to be changed, you need to correct all calls or errors will occur as a point above.
6. With generics the compiler will generate many functions.

With $mode delphi only point 2 will be resolved.

Thaddy

  • Hero Member
  • *****
  • Posts: 14205
  • Probably until I exterminate Putin.
Re: ToStr - Convert any value to String using RTTI
« Reply #27 on: October 13, 2018, 06:53:28 pm »
No in mode delphi you don't ruin your keyboard. I am still curious why people use objfpc mode for generics if even the devs don't. It is not a religion but a relict.
Otherwise you did a good job refactoring the code, btw.
« Last Edit: October 13, 2018, 08:08:34 pm by Thaddy »
Specialize a type, not a var.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: ToStr - Convert any value to String using RTTI
« Reply #28 on: October 15, 2018, 01:39:25 pm »
No in mode delphi you don't ruin your keyboard. I am still curious why people use objfpc mode for generics if even the devs don't. It is not a religion but a relict.
Otherwise you did a good job refactoring the code, btw.
Hellooho? I only use mode ObjFPC. Mode Delphi is only used for testing, cause aside from bugs (like the one with the overload here; did anyone report that already?) mode ObjFPC's generics are superior to the mode Delphi ones as one can use complex inline specializations that currently aren't possible in mode Delphi.

Xor-el

  • Sr. Member
  • ****
  • Posts: 404
Re: ToStr - Convert any value to String using RTTI
« Reply #29 on: October 15, 2018, 02:05:44 pm »
Well, while some specializations/constructs in mode ObjFPC are far superior to mode Delphi, trust me, mode Delphi is far more developer friendly for majority of Pascal developers.

 

TinyPortal © 2005-2018