delphidwscript

DWScript: passing a set-of-enumerated-type will pass an array of integer


From a DWScript script, I call a method of an object instance exposed by the Delphi side. The method takes, among others, an argument which is a set of some enumerated data type. This enumerated datatype is exposed from Delphi to the script.

I saw from the error message generated at script compile time that DWScript pass such an argument as an array of integer and that the Delphi side receive an array of variant (TData).

I had to write a wrapper at Delphi side which loops thru the array and rebuild the corresponding set-of variable to pass it to the actual Delphi function. Accessing the array is done using "ProgramInfo.Vars['MsgFlags'].GetData".

This works perfectly well, but is this the correct may to do? Did I miss something?

Script side code:

procedure Test;
begin
    DelphiObject.Demo('Hello', [mffStop, mffClose]);
end;

Delphi side code:

TFlag  = (mmfStop, mffStart, mmfClose);
TFlags = set of TFlag;

// Internal method doing the actual job
procedure TDelphiObject.DemoInternal(
    const MsgText  : String;
    const MsgFlags : TFlags);
begin
    // Some code...
end;

// Wrapper method exposed to script
procedure TDelphiObject.Demo(
    const MsgText : String;
    const MsgFlags : array of integer);
var
    Flags      : TFlags;
    I          : Integer;
begin
    Flags := [];
    for I := Low(MsgFlags) to High(MsgFlags) do
        Flags := Flags + [TFlag(MsgFlags[I])];
    DemoInternal(MsgText, Flags);
end;

Solution

  • I would implement the Delphi side a bit differently (see below), but apart from that your solution appears correct.


    The quirk, as you've correctly observed, is that DWScript represents static sets as arrays. Note however that this is just a limitation of the compiler frontend which hopefully, someday, will be resolved. See DWScript issue #10: Improve implicit casts from static arrays to sets.

    The following script demonstrates in which cases the compiler performs an implicit cast between set and array:

    type
      TMyEnum = (meOne, meTwo);
    type
      TMySet = set of TMyEnum;
    type
      TMyArray = array of TMyEnum;
    
    procedure TestSet(MySet: TMySet);
    begin
      ShowMessage(integer(MySet).toString);
    end;
    
    procedure TestArray(MyArray: TMyArray);
    var
      MySet: TMySet;
    begin
      MySet := [];
      for var i := 0 to MyArray.Length-1 do
        Include(MySet, MyArray[i]);
      ShowMessage(integer(MySet).toString);
    end;
    
    begin
      TestSet([]);
      TestArray([]);
      TestSet([meOne]);
      TestArray([meOne]);
      TestSet([meOne, meTwo]);
      TestArray([meOne, meTwo]);
    
      var VarSet: TMySet = [meOne, meTwo];
      TestSet(VarSet);
      // Syntax Error: Argument 0 expects type "array of TMyEnum" instead of "TMySet"
      // TestArray(VarSet);
    
      var VarArray: TMyArray = [meOne, meTwo];
      TestArray(VarArray);
      // Syntax Error: Argument 0 expects type "array of TMyEnum" instead of "TMySet"
      // TestArray(VarSet);
    
      // Syntax Error: Incompatible types: "TMySet" and "array [0..1] of TMyEnum"  const ConstSet: TMySet = [meOne, meTwo];
      // const ConstSet: TMySet = [meOne, meTwo];
      // TestSet(ConstSet);
      // TestArray(ConstSet);
    
      // Syntax Error: Incompatible types: "array of TMyEnum" and "array [0..1] of TMyEnum"
      // const ConstArray: TMyArray = [meOne, meTwo];
      // TestSet(ConstArray);
      // TestArray(ConstArray);
    end;
    

    The above is purely a script side implementation. When you add a Delphi side implementation into the mix it can get problematic.

    Consider a simplified implementation of the MessageDlg function:

    Delphi side declaration (via TdwsUnit):

    type
      TMsgDlgBtn = (mbYes, mbNo, mbOK, mbCancel, etc...);
      TMsgDlgButtons = set of TMsgDlgBtn;
    
    function MessageDlg(const Msg: string; Buttons: TMsgDlgButtons): integer;
    

    Delphi side implementation:

    Info.ResultAsInteger := MessageDlg(Info.ParamAsString[0], mtInformation, TMsgDlgButtons(Word(Info.ParamAsInteger[1])), -1);
    

    Script side usage:

    begin
      // Implicit cast from array to set fails:
      // Syntax Error: There is no overloaded version of "MessageDlg" that can be called with these arguments
      // MessageDlg('Test', [mbOK]);
    
      var Buttons: TMsgDlgButtons = [mbOK];
      MessageDlg('Test', Buttons);
    end;
    

    Now lets try the same with your solution declaring the set parameter as an array instead:

    Delphi side declaration (via TdwsUnit):

    type
      TMsgDlgBtn = (mbYes, mbNo, mbOK, mbCancel, etc...);
      TMsgDlgButtons = array of TMsgDlgBtn;
    
    function MessageDlg(const Msg: string; Buttons: TMsgDlgButtons): integer;
    

    Delphi side implementation:

    var
      Buttons: TMsgDlgButtons;
      i: integer;
      ButtonArray: IScriptDynArray;
    begin
      ButtonArray := Info.Params[1].ScriptDynArray;
      Buttons := [];
      for i := 0 to ButtonArray.ArrayLength-1 do
        Include(Buttons, TMsgDlgBtn(ButtonArray.AsInteger[i]));
      Info.ResultAsInteger := MessageDlgEx(Info.ParamAsString[0], mtInformation, Buttons, -1);
    end;
    

    Script side usage:

    begin
      MessageDlg('Test', [mbOK]);
    
      var Buttons: TMsgDlgButtons = [mbOK];
      // Note that an implicit cast from set to array is performed
      MessageDlg('Test', Buttons);
    end;
    

    In my own branch of DWScript I've modified the compiler to perform an implicit cast from an array of enum values to a set: DWScript pull request #4: Enhancement to set type. This works beautifully and resolves all the cases above that otherwise fail.