2012/01/14

Pascal Script と FastScript の速度比較

仕事で使っている自作の社内ソフトで、汎用性のない特殊な処理とかマクロ的なことをさせたい時は、当然ながらメニューを追加してソースを書いてやってるんですが、これをスクリプトで出来るようにしたらソースも汚れないし、他のユーザーが「ちょっとこう言うことをしたい」と行ってきた場合に、ソースを追加してコンパイルした実行ファイルを渡したりしていたけど、それをスクリプトのソースだけ書いて渡せばいいんでないかと。もしかしたら便利に使えるようになるかもと思い試してみました。

かなり昔にInnerfuse Pascal Script 2と言うやつを使った事があったのでその流れをくむRemObjects社のPascal Scriptと、Delphi XE2からバンドルされているFastReport社のFastScriptをとりあえず試してみました。

前は単純なことしかできなかったけど、最近ではアプリで作ったクラスへのアクセスはもちろん、TFormとかにもアクセス出来るみたいです。とりあえず作ったクラスにアクセスして集計とかやるのがおもな目的になるのでTFormへのアクセスとかそこら辺のことはまったくやってません。

Pascal Script
Innerfuse Pascal Script と言われていたやつで、作者が RemObjects 社を作ってからは Pascal Script と言う名称になってます。無料です。

自前のクラスへのアクセスはちょっと面倒な感じがします。たぶん使うクラスを分離して付属のツール(PSUnitImporter.exe)でそのクラスのラッパーみたいなクラスが生成されるのでそれをusesしてプラグインして追加とかなり面倒そう。1/21 使わない方法もありました。また、コンパイル時と実行時で2回登録しないといけない仕様は昔から変わってないようです。

※ここには載せてませんが、TestUnit.pas をインポーターにかけたら uPSI_TestUnit.pas が出来ます。リストの下にサンプルソースがあります。

FastScript かなり使いやすくて気に入ったんですが残念ながらトライアルです。実行するたびにダイアログボックスが開きます。また他の言語も使えます。

Pascal Script より 3-5 倍は早いです。またクラスへのアクセスは RTTI を使うことにより、少ない記述で済みます。
  fsScript.AddClass(TData, 'TData');

だけで、クラスのプロパティーにアクセス出来ます。もちろん登録してやればメソッドにもアクセス可能です。
unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.DateUtils,
  fs_iinterpreter, fs_ipascal, uPSComponent,
  TestUnit,
  uPSI_TestUnit, uPSComponent_Default;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    ListBox: TListBox;
    fsScript: TfsScript;
    fsPascal1: TfsPascal;
    PSScript: TPSScript;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure PSScriptCompile(Sender: TPSScript);
  public
    constructor Create (AOwner: TComponent); OVERRIDE;
    function  OutputFS (Instance: TObject; ClassType: TClass; const MethodName: string; var Params: variant) : variant;
    procedure OutputPS (s: string);
    function  GetDataFS  (Instance: TObject; ClassType: TClass; const MethodName: string; var Params: variant) : variant;
    function  GetDataPS : TData;
  private
    fData: TData;
  end;

var Form1: TForm1;

implementation

{$R *.dfm}

constructor TForm1.Create(AOwner: TComponent);
var p: TPSPlugin;
begin
  inherited Create(AOwner);
  fData := TData.Create;
  p := TPSImport_TestUnit.Create(Self);
  TPSPluginItem(PSScript.Plugins.Add).Plugin := p;
end;

procedure TForm1.Button1Click(Sender: TObject);
var d: TData;
begin
  fsScript.Clear;
  fsScript.Lines.Text := Memo1.Text;
  fsScript.Parent := fsGlobalUnit;
  fsScript.AddMethod('procedure Log(s: String)', OutputFS);
  fsScript.AddMethod('function GetData : TData', GetDataFS);
  fsScript.AddClass(TData, 'TData');
  if fsScript.Compile then begin
    ListBox.Items.Add('=== FastScript ===');
    fsScript.Execute;
    ListBox.Items.Add(IntToStr(fData.Value));
  end else begin
    OutputPS(fsScript.ErrorMsg);
    OutputPS(fsScript.ErrorPos);
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var a: integer;
    d: TData;
begin
  PSScript.Script.Text := Memo1.Text;
  if PSScript.Compile then begin
    ListBox.Items.Add('=== Pascal Script ===');
    PSScript.Execute;
    ListBox.Items.Add(IntToStr(fData.Value));
  end else begin
    for a := 0 to PSScript.CompilerMessageCount - 1 do
      ListBox.Items.Add(PSScript.CompilerMessages[a].MessageToString);
  end;
end;

procedure TForm1.PSScriptCompile(Sender: TPSScript);
begin
  PSScript.AddMethod(Self, @TForm1.OutputPS,  'procedure Log(s: string);');
  PSScript.AddMethod(Self, @TForm1.GetDataPS, 'function GetData : TData');
end;

function TForm1.OutputFS(Instance: TObject; ClassType: TClass; const MethodName: string; var Params: variant) : variant;
begin
  OutputPS(Params[0]);
end;

procedure TForm1.OutputPS(s: string);
begin
  ListBox.Items.Add(s + Format(' (%.1f)', [GetTickCount / 1000]));
end;

function TForm1.GetDataFS(Instance: TObject; ClassType: TClass; const MethodName: string; var Params: variant) : variant;
begin
  GetDataFS := NativeInt(fData);
end;

function TForm1.GetDataPS : TData;
begin
  GetDataPS := fData;
end;

end.
サンプルプロジェクト: ダウンロード
※コンパイルには FastScript と Pascal Script が必要。実行ファイル付。
p.s.
FastScript の構文をハイライトで表示してくれるメモコントロールの TfsSyntaxMemo はまったく日本語に対応してませんでした。まぁ別にかまわんけど...

0 件のコメント:

コメントを投稿