Recent

Author Topic: Using variable content as a name/command (macro substitution)  (Read 11104 times)

antovski

  • Newbie
  • Posts: 4
Using variable content as a name/command (macro substitution)
« on: November 22, 2017, 09:13:55 am »
Hi all

In the past I was working with Visual FoxPro and there was one useful feature called 'macro substitution',
which provides ability to use the content of the variable as a command or name. I will try to explain this with simple example:

Code: Pascal  [Select][+][-]
  1. [code]
  2.   Procedure Test;
  3.   var
  4.     s:String;
  5.   begin
  6.     s:='January';
  7.     // now I have to use the value of s (January) to call the procedure called January. In FoxPro I had to use &<variable_name>, so...
  8.    
  9.     &s;  // this have to call procedure 'January'
  10.   end;
  11.  
  12.   Procedure January;
  13.   begin
  14.     // some code
  15.   end;
  16. [code=pascal]

Is anyone knows if this is possible in Free Pascal, some way how to achieve this?

I know I can test the value with IF or CASE, but if I have hundred of values this method will be much faster and more effective then to
check condition hundred of times with IF or CASE.

Thank you.

Thaddy

  • Hero Member
  • *****
  • Posts: 14197
  • Probably until I exterminate Putin.
Re: Using variable content as a name/command (macro substitution)
« Reply #1 on: November 22, 2017, 09:18:56 am »
You mean something like this?
Code: Pascal  [Select][+][-]
  1. {$macro on}
  2. {$define s := 'Januari'}
  3. begin
  4. writeln(s)
  5. end.

or this?
Code: Pascal  [Select][+][-]
  1. {$macro on}
  2. {$define s := Januari}
  3. procedure Januari;
  4. begin
  5.   writeln('februari - one')
  6. end;
  7. begin
  8.  s
  9. end.
« Last Edit: November 22, 2017, 09:22:52 am by Thaddy »
Specialize a type, not a var.

antovski

  • Newbie
  • Posts: 4
Re: Using variable content as a name/command (macro substitution)
« Reply #2 on: November 22, 2017, 09:26:23 am »
Generally yes, but I need this in run-time, for any variable which I will define. I'm relatively new in Free Pascal, but I think that this macro definition is done in compile time and it is fixed, or am I wrong?
Anyway, I will test your example and I'll let you know :). Thx

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: Using variable content as a name/command (macro substitution)
« Reply #3 on: November 22, 2017, 09:36:58 am »
fpc is not a scripting language it does not provide and interpreter to translate a string to a function call so by default the answer is you can't do that. Now depending on your use cases and if you can accept some restrictions then it is possible to do so in two different ways.
1) use the pascal Script controls that come with lazarus to build a runtime environment that you can call any function defined in a string
 or
2) build your own Dictionary container that maps strings to function calls.

The second solution is a bit restrictive in the sense that you need to know the exact signature of the procedures/functions you want to call. Both solutions need you to properly register a list of procedures/functions that can be called in this manner.
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

KemBill

  • Jr. Member
  • **
  • Posts: 74
Re: Using variable content as a name/command (macro substitution)
« Reply #4 on: November 22, 2017, 09:56:52 am »
If you just want to call a procedure by it's name, try somthing like this (not tested)
Code: Pascal  [Select][+][-]
  1. type
  2.   Tprocedure = procedure of object;
  3.  
  4.   TMyClass = class
  5.   published
  6.     procedure proc1; //Januari, whatever you want
  7.     procedure proc2;
  8.     procedure proc3;
  9.   public
  10.     function Call(MethodName): boolean;
  11.   end;
  12.  
  13. function TMyClass.Call(MethodName): boolean;
  14. var m: TMethod;
  15. begin
  16.   result := false;
  17.   m.Code := Self.MethodAddress(MethodName);
  18.   if m.Code <> nil then
  19.   begin
  20.     m.Data := pointer(Self);
  21.     Tprocedure (m);
  22.     result := true;
  23.   end;
  24. end;
  25.  
  26. {...}
  27.  
  28. //use it like this
  29. var MyClass: TMyClass;
  30. begin
  31.   MyClass := TMyClass.Create;
  32.   MyClass.Call('proc1');
  33.   MyClass.Call('proc');
  34.   MyClass.free;
  35. end.
  36.  

Thaddy

  • Hero Member
  • *****
  • Posts: 14197
  • Probably until I exterminate Putin.
Re: Using variable content as a name/command (macro substitution)
« Reply #5 on: November 22, 2017, 10:35:36 am »
Generally yes, but I need this in run-time, for any variable which I will define. I'm relatively new in Free Pascal, but I think that this macro definition is done in compile time and it is fixed, or am I wrong?
Anyway, I will test your example and I'll let you know :). Thx
As taazz explained: FPC is not a scripting language.....
But using the current advanced RTTI it is possible to invoke a procedure at runtime. I am not a fan of this, so I discourage it. Use simply one of the scripting libraries that are available (including plain SQL).
Specialize a type, not a var.

antovski

  • Newbie
  • Posts: 4
Re: Using variable content as a name/command (macro substitution)
« Reply #6 on: November 22, 2017, 10:51:49 am »
You mean something like this?
Code: Pascal  [Select][+][-]
  1. {$macro on}
  2. {$define s := 'Januari'}
  3. begin
  4. writeln(s)
  5. end.

or this?
Code: Pascal  [Select][+][-]
  1. {$macro on}
  2. {$define s := Januari}
  3. procedure Januari;
  4. begin
  5.   writeln('februari - one')
  6. end;
  7. begin
  8.  s
  9. end.


Thaddy, your second example works, but the problem is that I have to define variable and assign value in run-time based on string.
I need some string value assigned to some variable (based on some result in run-time) which will be used as a command.

To simplify my question and avoid confusion with procedure call, in the following example, those 'Writeln()' commands have to give identical result,
so the string value should not be considered as a string:

Code: Pascal  [Select][+][-]
  1. [code]
  2. Program Test;
  3. uses Sysutils;
  4. begin
  5.   {$macro on}
  6.   {$define s1:=Now}
  7.   {$define s2:='Now'}
  8.   Writeln('Now is '+DateTimeToStr(Now));
  9.   Writeln('Now is '+DateTimeToStr(s1));     // this works, but I have to assign to s1 string value
  10.   Writeln('Now is '+DateTimeToStr(s2));     // this doesn't work
  11. end.
  12. [code=pascal]

Any ideas?
Thx.

kupferstecher

  • Hero Member
  • *****
  • Posts: 583
Re: Using variable content as a name/command (macro substitution)
« Reply #7 on: November 22, 2017, 11:20:23 am »
Can you explain more detailed what you try to achieve? Perhaps there are other solutions.

Something else than you asked, but in case it helps: You can call a procedure through a variable.

Code: Pascal  [Select][+][-]
  1. var
  2.   s1: function: TDateTime;
  3. begin
  4.   s1:= @Now;        //Assign Function "now" to the function variable;
  5.   Writeln('Now is '+DateTimeToStr(s1));      //Call 's1', i.e. call 'now'
  6. end;

antovski

  • Newbie
  • Posts: 4
Re: Using variable content as a name/command (macro substitution)
« Reply #8 on: November 22, 2017, 12:10:46 pm »
Sure, I will give you my exact problem...
I'm trying to make some simple CNC simulator which will execute the G-code and draw the result on the screen.
Line of commands is stored in some string variable which can look like this

  G21 G90 G54

Now, I have to store the first command in variable and to execute it, then to take the second command and execute etc...
I can do this with IF or CASE statement in this way

  var:='G21';  // this will be taken from the variable which contain the whole string with more G-codes

  IF var='G21' then some_proc1;
  IF var='G28' then some_proc2;
  IF var='G30' then some_proc3;

The problem is that there are about hundred different commands and testing each time with IF or CASE statement is kind of inefficient.

What I'm actually asking is if it's possible to use the content of the variable as a name.
For example:

  var:='G21';

  var;  // the content have to be threat as a name and should call the procedure named G21. If you red my first post, in FoxPro I would use "DO &var" which will be interpreted as "DO G21"

  procedure G21;
  begin

  end;

In this case I would be able to call the right procedure directly, without checking hundred of times through IF or CASE statement.

I hope now it is more clear, every suggestion is welcomed.
Thx to all.
 

munair

  • Hero Member
  • *****
  • Posts: 798
  • compiler developer @SharpBASIC
    • SharpBASIC
Re: Using variable content as a name/command (macro substitution)
« Reply #9 on: November 22, 2017, 12:56:38 pm »
I would probably define those commands as constants (even if there a few hundred of them) and compare the selected command like
Code: Pascal  [Select][+][-]
  1. const
  2.   G21 = 'G21';
  3.   G28 = 'G28';
  4.   G30 = 'G30';
  5. ...
  6. if C = G21 then
  7. ...
  8. else if C = G28 then
  9. ...
  10. else if C = G30 then
  11. ...
  12. ;
It may be a bit of work to set it up, but it's probably faster to use constants with if-conditions than to compare strings using Case.
« Last Edit: November 22, 2017, 01:03:41 pm by Munair »
keep it simple

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Using variable content as a name/command (macro substitution)
« Reply #10 on: November 22, 2017, 01:41:46 pm »
You could do something along these lines:
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms;
  9.  
  10. type
  11.  
  12.   T1StringParamProc = procedure(const s: String) of object;
  13.  
  14.   TProcArray = array of T1StringParamProc;
  15.  
  16.   TForm1 = class(TForm)
  17.     procedure FormCreate(Sender: TObject);
  18.   private
  19.     FProcArray: TProcArray;
  20.     procedure CallProcedures(const aLineOfCommands: String);
  21.     procedure Proc21(const s: String);
  22.     procedure Proc90(const s: String);
  23.     procedure Proc99(const s: String);
  24.     procedure Proc54(const s: String);
  25.     procedure SetupProcArray;
  26.   public
  27.  
  28.   end;
  29.  
  30. var
  31.   Form1: TForm1;
  32.  
  33. implementation
  34.  
  35. {$R *.lfm}
  36.  
  37. procedure TForm1.FormCreate(Sender: TObject);
  38. const
  39.    LineOfCommands = 'G21 G90 G54 G99';
  40. begin
  41.   SetupProcArray;
  42.   CallProcedures(LineOfCommands);
  43. end;
  44.  
  45. procedure TForm1.CallProcedures(const aLineOfCommands: String);
  46. var
  47.   sl: TStringList;
  48.   s: String;
  49.   i: LongInt;
  50. begin
  51.   if Length(aLineOfCommands) > 0 then begin
  52.     sl:=TStringList.Create;
  53.     try
  54.       sl.StrictDelimiter:=True;
  55.       sl.Delimiter:=' ';
  56.       sl.DelimitedText:=aLineOfCommands;
  57.       for s in sl do begin
  58.         i:=StrToIntDef(Copy(s, 2, 2), -1);
  59.         if (i > -1) and Assigned(FProcArray[i]) then
  60.           FProcArray[i](s);
  61.       end;
  62.     finally
  63.       sl.Free;
  64.     end;
  65.   end;
  66. end;
  67.  
  68. procedure TForm1.Proc21(const s: String);
  69. begin
  70.   Writeln('Proc21, "',s,'"');
  71. end;
  72.  
  73. procedure TForm1.Proc90(const s: String);
  74. begin
  75.   Writeln('Proc90, "',s,'"');
  76. end;
  77.  
  78. procedure TForm1.Proc99(const s: String);
  79. begin
  80.   Writeln('Proc99, "',s,'"');
  81. end;
  82.  
  83. procedure TForm1.Proc54(const s: String);
  84. begin
  85.   Writeln('Proc54, "',s,'"');
  86. end;
  87.  
  88. procedure TForm1.SetupProcArray;
  89. begin
  90.   SetLength(FProcArray, 100);
  91.   FProcArray[21]:=@Proc21;
  92.   FProcArray[90]:=@Proc90;
  93.   FProcArray[99]:=@Proc99;
  94.   FProcArray[54]:=@Proc54;
  95. end;
  96.  
  97. end.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11382
  • FPC developer.
Re: Using variable content as a name/command (macro substitution)
« Reply #11 on: November 22, 2017, 02:52:44 pm »
Or, more procedural and using TDictionary, a more Delphi/fpc Trunk style of coding:

Code: Pascal  [Select][+][-]
  1. {$mode delphi}
  2.  
  3. uses
  4.    generics.collections,sysutils;
  5.  
  6. procedure g11;
  7. begin
  8.  writeln({$i %CURRENTROUTINE%});
  9. end;
  10.  
  11. procedure g43;
  12. begin
  13.  writeln({$i %CURRENTROUTINE%});
  14. end;
  15. procedure g234;
  16. begin
  17.  writeln({$i %CURRENTROUTINE%});
  18. end;
  19.  
  20. procedure g64;
  21. begin
  22.  writeln({$i %CURRENTROUTINE%});
  23. end;
  24.  
  25. Type  
  26.   TStrProcDictionary = TDictionary<string,TProcedure>;
  27.   TIntProcDictionary = TDictionary<Integer,TProcedure>;
  28.  
  29. var
  30.   sdict: TStrProcDictionary;
  31.   idict: TIntProcDictionary;
  32.    x : TPair<Integer,Tprocedure>;
  33.  
  34. procedure call(i:integer);overload;
  35. var t :TProcedure;
  36. begin
  37.   if idict.trygetvalue(i,t) then
  38.      t
  39.    else
  40.      writeln(' procedure ',i,' not found');
  41. end;
  42.  
  43. procedure call(s:string);overload;
  44. var t :TProcedure;
  45. begin
  46.   if sdict.trygetvalue(s,t) then
  47.      t
  48.    else
  49.      writeln(' procedure ',s,' not found');
  50. end;
  51.  
  52. begin
  53.   sdict:=TStrProcDictionary.create;
  54.   idict:=TIntProcDictionary.create;
  55.  
  56.   // number to procedure
  57.   idict.add(11,g11);
  58.   idict.add(43,g43);
  59.   idict.add(234,g234);
  60.   idict.add(64,g64);
  61.  
  62.   for x in idict do
  63.     sdict.add('g'+inttostr(x.key),x.value);
  64.  
  65.    call(32);          
  66.    call(43);
  67.    call('g32');
  68.    call('g43');
  69. end.
  70.  

If all functions are of the form <letter><number> you can register all procedures with their numbers in several idicts and then fill a central gdict with them as show in the above example

Nitorami

  • Sr. Member
  • ****
  • Posts: 481
Re: Using variable content as a name/command (macro substitution)
« Reply #12 on: November 22, 2017, 05:16:12 pm »
If your codes are all simple strings such as "G21" then a solution might be to make a simple hash function which translates the string to a byte, and then use this as an index to an array of functions.

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Using variable content as a name/command (macro substitution)
« Reply #13 on: November 23, 2017, 12:05:07 am »
Build a string table of all the G commands.

make a parser that searches this table for the command and the found index will be a command
 number..

Use another procedure to execute this command where it could have several case statements that
examines this command..

 The Constant string table looks like this.

Const GCMD:Arrary[0..NumberOfCmds-1] of String = ('GD1',GD2','XYZ5','And happy thanks Giving', 'And A better New Year');

The search function is simple of course.

 Result :=0;
While (Result < NumberOfCmds)and(CompareStr(InputStr,GCMD[Result])<>0) do Inc(Result);
 If Result > NumberOfCmds Then result := -1;

---- Later On ---

Case Result of
 0:
 1:
 2:
etc....

You should use a parser because that will capture the Text for the Cmd and also can be used to capture the
parameters after the command etc.


The only true wisdom is knowing you know nothing

RAW

  • Hero Member
  • *****
  • Posts: 868
Re: Using variable content as a name/command (macro substitution)
« Reply #14 on: November 23, 2017, 03:56:10 pm »
Quote
I know I can test the value with IF or CASE, but if I have hundred of values this method will be much faster and more effective then to
check condition hundred of times with IF or CASE.
There must be a reason why someone invented ARRAYS and LISTS (TList, TStringList, TObjectList, TDictionary, fpList, fpHashList... etc.).
Why don't you load your GCodes from a *.txt file to an ARRAY or use a DATABASE if you don't want to store everything inside the RAM.
GCodes inside of a String... ?

BTW:
I like the way from @KemBill, but without the class overkill, just one procedure... very easy....
But "MethodAddress" uses a For-Loop and this should be faster than a TDictionary ????
Code: Pascal  [Select][+][-]
  1. UNIT Unit1;
  2.  {$MODE OBJFPC}{$H+}{$J-}
  3.  
  4. Interface
  5.  USES
  6.   Classes, SysUtils, Forms, Controls, Dialogs;
  7.  
  8.  TYPE
  9.   TProc = Procedure Of Object;
  10.  
  11.  TYPE
  12.   TForm1 = Class(TForm)
  13.    Procedure FormCreate (Sender: TObject);
  14.    Procedure FormClick  (Sender: TObject);
  15.    Procedure GCodeExec  (ProcName: ShortString);
  16.  
  17.    Procedure G21;
  18.    Procedure G28;
  19.    Procedure G30;
  20.  
  21.     PRIVATE
  22.      strGCode: ShortString;
  23.   End;
  24.  
  25.  VAR
  26.   Form1: TForm1;
  27.  
  28. Implementation
  29.  {$R *.LFM}
  30.  
  31.  
  32. Procedure TForm1.GCodeExec(ProcName: ShortString);
  33.  Var
  34.   M: TMethod;
  35.  Begin
  36.   M.Code:= Self.MethodAddress(ProcName);
  37.  
  38.   If Assigned(M.Code)
  39.   Then
  40.    Begin
  41.     M.Data:= Pointer(Self);
  42.     TProc(M);
  43.    End;
  44.  End;
  45.  
  46.  
  47. Procedure TForm1.FormCreate(Sender: TObject);
  48.  Begin
  49.   strGCode:= 'G28';
  50.  End;
  51.  
  52.  
  53. Procedure TForm1.FormClick(Sender: TObject);
  54.  Begin
  55.   GCodeExec(strGCode);
  56.  End;
  57.  
  58.  
  59. Procedure TForm1.G21;
  60.  Begin
  61.   ShowMessage('G21');
  62.  End;
  63.  
  64.  
  65. Procedure TForm1.G28;
  66.  Begin
  67.   ShowMessage('G28');
  68.  End;
  69.  
  70.  
  71. Procedure TForm1.G30;
  72.  Begin
  73.   ShowMessage('G30');
  74.  End;
  75.  
  76. END.

@Munair: Please ask YODA if your way is "Keep it simple". I think your way is a pain and a growing pain in the future...  :)
And is that faster than a Dictionary or a HashList ???
Windows 7 Pro (x64 Sp1) & Windows XP Pro (x86 Sp3).

 

TinyPortal © 2005-2018