Lazarus

Programming => General => Topic started by: MountainQ on April 10, 2021, 10:47:20 pm

Title: assigning to Array of const
Post by: MountainQ on April 10, 2021, 10:47:20 pm
Greetings;

using 'array of const' can result in compact and readable code, just consider format().
I am wondering whether there is way to reverse that functionality; thereby assigning values to various types.
Something like:
Code: Pascal  [Select][+][-]
  1. var
  2.   SA: TStringArray;
  3.   s: string;
  4.   f: float;
  5.   k: integer;
  6. begin
  7.   SA := TStringArray.Create('some', '12.3', '45');
  8.   MagicFunction(SA, [s, f, k]);
  9. end;
  10.  
Where 'MagicFunction' loops through the arguments and converts the strings according to the required type. This problem came about when reading and writing text-based files that initialize some values.
I unsuccessfully played around with 'array of const'; the name does suggest this is not possible.
Does anybody knows for sure; are there alternative ideas?

Thanks


Title: Re: assigning to Array of const
Post by: MarkMLl on April 10, 2021, 10:52:03 pm
Show us your attempt at MagicFunction(), and the compiler's opinion of it.

MarkMLl
Title: Re: assigning to Array of const
Post by: jamie on April 11, 2021, 01:27:39 am
Greetings;

using 'array of const' can result in compact and readable code, just consider format().
I am wondering whether there is way to reverse that functionality; thereby assigning values to various types.
Something like:
Code: Pascal  [Select][+][-]
  1. var
  2.   SA: TStringArray;
  3.   s: string;
  4.   f: float;
  5.   k: integer;
  6. begin
  7.   SA := TStringArray.Create('some', '12.3', '45');
  8.   MagicFunction(SA, [s, f, k]);
  9. end;
  10.  
Where 'MagicFunction' loops through the arguments and converts the strings according to the required type. This problem came about when reading and writing text-based files that initialize some values.
I unsuccessfully played around with 'array of const'; the name does suggest this is not possible.
Does anybody knows for sure; are there alternative ideas?

Thanks

Looks like you are doing something like have been lately..

The const array is a makeup of TVArRec, you can find that in the RTL help..

it contains data type and the item itself for the common types. with that you can Cast over the array to determine what type is sitting there when examining it.

for example

 PVarRec(@ArrayEntry[?])^.vType; and that will tell you what type is sitting there atm..

As for determining what is in the incoming text stream you need to write a function to determine the type of field it is and then use the various functions to convert it to that native type.

 So in your case, if you examine for Numbers only then its an integer. if it's numbers and a "." only then it's a float , otherwise it's a string.

 in my case, where  I am reading Iges fields I had to examine the first chars up to the letter H to determine if it was a string field  and if not, then test for a numbers only with a . ….
after you read in each segment you have functions like StrToFloatDef, StrToIntDef where as if there is no number that and just a ,, for example you can default it..

P.S.
  There is no DefaultString function fpc , I had to make one myself.
Title: Re: assigning to Array of const
Post by: MountainQ on April 11, 2021, 09:42:26 am
Thanks for the quick replies; my thoughts were along those lines:
Code: Pascal  [Select][+][-]
  1. procedure MagicFct(SA: TStringArray; vars: array of const);
  2. var
  3.   i: integer;
  4. begin
  5.   for i := 0 to Length(vars)-1 do
  6.     case vars[i].vtype of
  7.       vtAnsiString:
  8.           AnsiString(vars[i].VAnsiString^) := SA[i]; // raises SIGSEV
  9.       vtInteger:
  10.           vars[i].VInteger := StrToInt(Sa[i]);
  11.       vtExtended:
  12.           vars[i].VExtended^ := StrToFloat(Sa[i]);
  13.     else
  14.       raise Exception.Create('NotImplemented: '+IntToStr(vars[i].vtype));
  15.     end;
  16. end;
  17.  
  18.  
  19.  
  20. var
  21.   SA: TStringArray;
  22.   s: string;
  23.   f: double;
  24.   k: integer;
  25. begin
  26.   SA := TStringArray.Create('123', '45.6');
  27.   s := '';
  28.   k := 0;
  29.   f := 0;
  30.   writeln(Int64(@f)); // <- actually neccessary
  31.   MagicFct(SA, [k, f]);
  32.   writeln('and then ', k, f);
  33. end;                              
  34.  
I am unsing FPC 3.2.0 (Lazarus Build 2.0.12 on Windows 10).
The code does not work; (including the string raises SIGSEV). I get the type 'double' to work if I include a line (as seen) that reads the address of the variable.

@MarkMLl: the compiler is fine with all that

At this point my knowledge ends; as I do not know the inner workings of array of const; if it represents just a copy of the variables that are provided then the it is supposed to not work.
Best to all.
Title: Re: assigning to Array of const
Post by: speter on April 11, 2021, 10:00:59 am
Do you really need the "array of const"? If you knew that you will have (for example) an integer, a single and finally a string; your task would be far simpler...

cheers
S. ;)
Title: Re: assigning to Array of const
Post by: MountainQ on April 11, 2021, 10:13:42 am
@speter
Of course it is not absolutely necessary to have this construct; however to me it seems to fairly elegant in certain situations.
In my case I read and write to an init file.
Being able to distribute the values to the respective variables using an array of const would make the code more readable (at least in my opinion) and less error-prone. For example I can copy that array of const to a function that reverses that operation at the end of the program to write to init file (that last part is easy to implement).
Title: Re: assigning to Array of const
Post by: MarkMLl on April 11, 2021, 10:20:03 am
The code does not work; (including the string raises SIGSEV). I get the type 'double' to work if I include a line (as seen) that reads the address of the variable.

Start off with the simplest cases: distinguishing between an integer, a real and a single char, try to avoid pointers.

Without trying to duplicate your code, I think your problem isn't the  const array  so much as your attempted access to string storage... there's "magic" associated with strings and dynamic arrays which is best not interfered with.

MarkMLl
Title: Re: assigning to Array of const
Post by: y.ivanov on April 11, 2021, 10:55:20 am
Quote
Re: assigning to Array of const

You obviously can't. Please, consult the language docs for the meaning of 'array of const'. They're introduced for a sake of having a variable-length arguments.

However, you should consider something like a reference to an array of TVarRec if it is a parameter, or return a dynamically allocated one as a result of a function.

Regards, 
Title: Re: assigning to Array of const
Post by: wp on April 11, 2021, 10:57:05 am
Code: Pascal  [Select][+][-]
  1. procedure MagicFct(SA: TStringArray; vars: array of const);
  2. var
  3.   i: integer;
  4. begin
  5.   for i := 0 to Length(vars)-1 do
  6.     case vars[i].vtype of
  7.       vtAnsiString:
  8.           AnsiString(vars[i].VAnsiString^) := SA[i]; // raises SIGSEV
  9.       vtInteger:
  10.           vars[i].VInteger := StrToInt(Sa[i]);
  11.       vtExtended:
  12.           vars[i].VExtended^ := StrToFloat(Sa[i]);
  13.     else
  14.       raise Exception.Create('NotImplemented: '+IntToStr(vars[i].vtype));
  15.     end;
  16. end;
  17.  
  18. var
  19.   SA: TStringArray;
  20.   s: string;
  21.   f: double;
  22.   k: integer;
  23. begin
  24.   SA := TStringArray.Create('123', '45.6');
  25.   s := '';
  26.   k := 0;
  27.   f := 0;
  28.   writeln(Int64(@f)); // <- actually neccessary
  29.   MagicFct(SA, [k, f]);
  30.   writeln('and then ', k, f);
  31. end;                              
  32.  

But k and f are "constants" for the MagicFct, you cannot anything to the "vars" parameter of the MagicFct. - I don't know what you want to achieve. Why don't you write it in the simple way?
Code: Pascal  [Select][+][-]
  1.   k := StrToInt('123');
  2.   f :=  StrToFloat('45.6');
Title: Re: assigning to Array of const
Post by: MountainQ on April 11, 2021, 11:21:59 am
Thanks to all for the kind responses.
I would like to add that this is not an urgent issue as there are obvious workarounds.
The topic came up when I was getting ever-changing specs for an init-file.
Explicitly assigning the values to the variables is straightforward yet somewhattedious.
As it is easily possible to gather the values from various variables using a function that uses 'array of const', I was wondering if it is possible to do the opposite.
My guess is that it is not possible as the data in the array of const is a copy of the content of the variables; except for pointers.
Hope this clarifies my intention.
Title: Re: assigning to Array of const
Post by: MarkMLl on April 11, 2021, 12:10:02 pm
Thanks to all for the kind responses.
I would like to add that this is not an urgent issue as there are obvious workarounds.
The topic came up when I was getting ever-changing specs for an init-file.
Explicitly assigning the values to the variables is straightforward yet somewhattedious.
As it is easily possible to gather the values from various variables using a function that uses 'array of const', I was wondering if it is possible to do the opposite.
My guess is that it is not possible as the data in the array of const is a copy of the content of the variables; except for pointers.
Hope this clarifies my intention.

Remember that FPC has adequate handling of .ini files itself. I've built on this in the past, but not found it necessary to redo from scratch.

This doesn't work:

Code: Pascal  [Select][+][-]
  1. program test;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. var
  6.   a, b, c, x: integer;
  7.  
  8.  
  9. function f(const s: string; out z: array of const): integer;
  10.  
  11. begin
  12.   result := Length(z);
  13.   if result = 3 then begin
  14.     a := 1;
  15.     b := 2;
  16.     c := 3
  17.   end
  18. end;
  19.  
  20.  
  21. begin
  22.   x := f('Test', [a, b, c]) // <===== Expects a variable
  23. end.
  24.  

This works:

Code: Pascal  [Select][+][-]
  1. program test;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. var
  6.   a, b, c, x: integer;
  7.  
  8. type
  9.   intArray= array of integer;
  10.  
  11. var
  12.   q: intArray;
  13.  
  14.  
  15. function f(const s: string; out z: intArray): integer;
  16.  
  17. begin
  18.   result := Length(z);
  19.   if result = 3 then begin
  20.     a := 1;
  21.     b := 2;
  22.     c := 3
  23.   end
  24. end;
  25.  
  26.  
  27. begin
  28.   SetLength(q, 3);
  29.   x := f('Test', q)
  30. end.
  31.  

You might be able to build on that using variants or whatever you need.

MarkMLl
Title: Re: assigning to Array of const
Post by: jamie on April 11, 2021, 12:30:45 pm
The tvarrec was meant for internal use but still can be used externally of corse.

 At this juncture I think you should be using variants instead.

 Variants store the actual value in them and are compiler friendly so you need to directly interact with the record, basically all the overloads are already created.

 Variants are of type tvardAta
Title: Re: assigning to Array of const
Post by: MarkMLl on April 11, 2021, 12:44:16 pm
The tvarrec was meant for internal use but still can be used externally of corse.

 At this juncture I think you should be using variants instead.

 Variants store the actual value in them and are compiler friendly so you need to directly interact with the record, basically all the overloads are already created.

@Jamie, I think you mean "DON'T need to directly...".

Apart from that I generally agree. Variants have a few rough edges (I've got into fairly deep water trying to implement an APL-style reduce on dynamic-arrays-of-dynamic-arrays-of-variants) but fewer than "const array" AKA varargs which are very much a hack to allow Object Pascal to implement printf()-like behaviour.

If you get too deep into varargs you'll hit architecture-defined behaviour, that's particularly the case if you try to define a Pascal function that looks exactly like a C function.

MarkMLl
Title: Re: assigning to Array of const
Post by: y.ivanov on April 11, 2021, 01:03:15 pm
I would take another approach. Either way, it's about introspection, so instead of using array of const, a specs class can be defined and it's rtti used to extract the values from the ini.

Thus changes can be maintained into the published part of the class.

Goes like this:

Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. uses
  4.   SysUtils, IniFiles, TypInfo, StrUtils;
  5.  
  6. type
  7.  
  8.   { TSpecs }
  9.  
  10.   TSpecs = class
  11.   private
  12.     FIntSpec: Integer;
  13.     FStrSpec: String;
  14.   published
  15.     property IntSpec: Integer read FIntSpec write FIntSpec;
  16.     property StrSpec: String read FStrSpec write FStrSpec;
  17.     // ...
  18.   end;
  19.  
  20. procedure ReadSpec(F: TIniFile; Sect: String; Specs: TSpecs);
  21. var
  22.   C, I: Integer;
  23.   PL: PPropList;
  24.  
  25.   procedure SetPropWithText(AObject: TObject; API: PPropInfo; AText: String);
  26.   begin
  27.     case API^.PropType^.Kind of
  28.       tkChar: SetOrdProp(AObject, API, Ord(IfThen(AText <> '', AText, #0)[1]));
  29.       tkAString: SetStrProp(AObject, API, AText);
  30.       tkInteger: SetOrdProp(AObject, API, StrToIntDef(AText, 0));
  31.       tkInt64, tkQWord: SetInt64Prop(AObject, API, StrToInt64Def(AText, 0));
  32.       tkBool: SetOrdProp(AObject, API, Ord(AText <> '0'));
  33.       tkFloat: SetFloatProp(AObject, API, StrToFloatDef(AText, 0.0));
  34.       tkEnumeration, tkSet: SetOrdProp(AObject, API, StrToIntDef(AText, 0));
  35.     end;
  36.   end;
  37.  
  38. begin
  39.   C := GetPropList(Specs, PL);
  40.   try
  41.     for I := 0 to Pred(C) do
  42.       if F.ValueExists(Sect, PL^[I]^.Name) then
  43.         SetPropWithText(Specs, PL^[I], F.ReadString(Sect, PL^[I]^.Name, ''));
  44.   finally
  45.     FreeMem(PL);
  46.   end;
  47. end;
  48.  
  49. var
  50.   Ini: TIniFile;
  51.   Specs: TSpecs;
  52.  
  53. begin
  54.   Ini := TIniFile.Create('specs.ini');
  55.   Specs := TSpecs.Create;
  56.   try
  57.     ReadSpec(Ini, 'Default', Specs);
  58.     // ...
  59.     // Use Specs.IntSpec, Specs.StrSpec
  60.     // ...
  61.   finally
  62.     Ini.Free;
  63.     Specs.Free;
  64.   end;
  65. end.
  66.  

Regards,
Title: Re: assigning to Array of const
Post by: MountainQ on April 12, 2021, 09:23:11 am
Thanks to all for replies; I will have another look at the problem.
Luckily, it is mostly curiosity-driven.
@y.ivanov: I agree that introspection will be required.
Currently my thoughts are along the lines of typed pointers; e.g. pinteger.
If I come up with something useful, I will post it.
Best to everyone
Title: Re: assigning to Array of const
Post by: MortenB on April 12, 2021, 09:13:47 pm
I made myself something similar a few years back...
Just to give you an idea of something that actually work.
Array Of Const is quite useful as an unknown count of unknown parameter types to a procedure or a function.
This is just ONE of many procedures I made.
Maybe you can get some inspiration from it :)

Code: Pascal  [Select][+][-]
  1. PROCEDURE TShelves.TAdd.ColumnRow(RowData : ARRAY OF CONST; LocalType:BYTE); // No errorchecking. Only adds according to type. Also increase the size of the type's array.
  2.   VAR
  3.     ix : INTEGER ;   xx : EXTENDED;   asx : ^AnsiString;
  4.                                       wsx : ^WideString;
  5.     OldRowPointer : INTEGER;
  6.   BEGIN
  7. {
  8. AllVTypes : ARRAY[0..18] OF AnsiString =('vtInteger','vtBoolean'  ,'vtChar'      ,'vtExtended' ,'vtString'    ,'vtPointer' ,'vtPChar',
  9.                                          'vtObject' ,'vtClass'    ,'vtWideChar'  ,'vtPWideChar','vtAnsiString','vtCurrency',
  10.                                          'vtVariant','vtInterface','vtWideString','vtInt64'    ,'vtQWord'     ,'vtUniCodeString');   }
  11.     WITH pTheColumn^ DO BEGIN
  12.       OldRowPointer := RowCount;
  13.       CASE RowData[0].VType OF
  14.         0: BEGIN
  15.              ix := RowData[0].VInteger;
  16.              AddWholeNumbersToColumn(ix,LocalType);
  17.              IF ErrorMessage = '' THEN INC(RowCount);
  18.            END;
  19.         1:   {  1 } BEGIN SetLength(IfBoolean    , RowCount+1); IfBoolean    [RowCount] := RowData[0].VBoolean; INC(RowCount); END;// vtBoolean  ... Boolean
  20.         3: BEGIN
  21.              xx := RowData[0].VExtended^;  // Probably not needed, but shorter to write in below lines!
  22.              CASE LocalType OF // vtExtended ... Double, TDate, TTime, Real, Extended
  23.                 9 : BEGIN SetLength(IfDouble     , RowCount+1); IfDouble     [RowCount] := xx; INC(RowCount); END;// Double
  24.                10 : BEGIN SetLength(IfExtended   , RowCount+1); IfExtended   [RowCount] := xx; INC(RowCount); END;// Extended
  25.                12 : BEGIN SetLength(IfTime       , RowCount+1); IfTime       [RowCount] := xx; INC(RowCount); END;// TTime
  26.                13 : BEGIN SetLength(IfDate       , RowCount+1); IfDate       [RowCount] := xx; INC(RowCount); END;// TDate
  27.                14 : BEGIN SetLength(IfDateTime   , RowCount+1); IfDateTime   [RowCount] := xx; INC(RowCount); END;// TDateTime
  28.              END;
  29.            END;
  30.         4:   { 15 } BEGIN SetLength(IfShortString, RowCount+1); IfShortString[RowCount] := RowData[0].VString^; INC(RowCount); END;// vtString   ... ShortString
  31.        11:   { 16 } BEGIN
  32.                       asx := @RowData[0].VAnsiString;
  33.                       AddAnsiStringToColumn(asx^,LocalType);
  34.                       IF ErrorMessage = '' THEN INC(RowCount);
  35.                     END;
  36.        12:   { 11 } BEGIN SetLength(IfCurrency   , RowCount+1); IfCurrency   [RowCount] := RowData[0].VCurrency^; INC(RowCount); END;// vtCurrency;
  37.        15:   { 17 } BEGIN
  38.                           wsx := @RowData[0].VWideString;
  39.                           SetLength(IfWideString , RowCount+1); IfWideString [RowCount] := wsx^; INC(RowCount); // vtWideString
  40.                     END;
  41.        16:   {  8 } BEGIN SetLength(IfInt64      , RowCount+1); IfInt64      [RowCount] := RowData[0].VInt64^; INC(RowCount); END;// vtInt64
  42.        17:   {  5 } BEGIN SetLength(IfQWord      , RowCount+1); IfQWord      [RowCount] := RowData[0].VQWord^; INC(RowCount); END;// QWord
  43.       END;
  44.       IF RowCount = OldRowPointer THEN ErrorMessage :='Error! Column-Data was not added.'
  45.     END;
  46.   END;
  47.  
TinyPortal © 2005-2018