Luaを使ってみる

 

はじめに

Luaとはスクリプト言語の一種で、他言語に組み込むことで威力を発揮します。
ここでは、Delphiから呼び出すことを前提にちびちびと書き足していきたいと思います。

Luaバイナリの入手先および日本語のマニュアル

Delphi向けLuaライブラリの選択肢

どれを使おうか悩み中

基礎の基礎

Hello World!

Delphiに組み込む前に単体での入門から。

print ("Hello World!")

以上の内容をhello.luaというファイル名で保存。
コマンドプロンプト上で実行。

>lua52 hello.lua
Hello World!

以下の6通りの記述は、すべて同じ結果となります。

print ("Hello World!")
print ('Hello World!')
print "Hello World!"
print 'Hello World!'
print ([[Hello World!]])
print [[Hello World!]]

文字列の途中に改行を加えたい場合、”\n”を用います。

print ("Hello\nWorld!")
print ('Hello\nWorld!')
print ([[Hello\nWorld!]])

上記のうち、上の2行は同じ動作をしますが、最後の1行は違います。

Hello
World!
Hello
World!
Hello\nWorld!

最後の1行は、文字列をそのままその通りに扱います。例えば以下のようになります。

print ([[Hello
World!]])
Hello
World!
コメント

1行コメントと複数行コメントがあります。

--1行コメント
--[[
複数行
コメントは
このように
書きます
]]

VerySimple.Lua

Readme.txt内のサンプル(加工済み)
program Readme;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  VerySimple.Lua,
  VerySimple.Lua.Lib;

type
  TMyLua = class(TVerySimpleLua)
  published
    function HelloWorld(LuaState: lua_State): Integer;
  end;

function TMyLua.HelloWorld(LuaState: lua_State): Integer;
var
  ArgCount: Integer;
  I: integer;
begin
  ArgCount := Lua_GetTop(LuaState);
  writeln('Delphi: Hello World');
  writeln('Arguments: ', ArgCount);
  for I := 1 to ArgCount do
    writeln('Arg1', I, ': ', Lua_ToInteger(LuaState, I));
  // Clear stack
  Lua_Pop(LuaState, Lua_GetTop(LuaState));
  // Push return values
  Lua_PushInteger(LuaState, 101);
  Lua_PushInteger(LuaState, 102);
  Result := 2;
end;

var
  MyLua: TVerySimpleLua;
begin
  try
    MyLua := TMyLua.Create;
    MyLua.LibraryPath := '..\..\'+LUA_LIBRARY;
    MyLua.DoFile('..\..\Helloworld.lua');
    MyLua.Free;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

Helloworld.lua

print("LuaDelphi Test");
p1,p2 = HelloWorld(1,2,3)
print "Results:";
print (p1);
print (p2);

結果表示

LuaDelphi Test
Delphi: Hello World
Arguments: 3
Arg11: 1
Arg12: 2
Arg13: 3
Results:
101
102

DelphiLua

README.md内のサンプル(加工済み)
program README;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Lua.Api,
  Lua;

var
  lua: TLua;
  returnValue: Integer;
begin
  try
    lua := TLua.Create;
    try
      lua.SetGlobalVariable('A', 5);
      lua.SetGlobalVariable('B', 10);
      lua.LoadFromString('C = A + B');
      returnValue := lua.GetGlobalVariable('C').AsInteger;
      Writeln('C = '+IntToStr(returnValue));
    finally
      Readln;
      FreeAndNil(lua);
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

「C = 15」と表示されれば成功です。

スクリプト関数の呼び出し

まずは、比較用にDelphiでの記述。自作アプリから該当箇所のみ抜粋。正規表現を使うのでusesにPerlRegExを追加。

//Delphi版のGetProgress
function GetProgress1(const S: string): string;
var
  RegExp: TPerlRegEx;
begin
  RegExp := TPerlRegEx.Create;
  try
    RegExp.Subject := UTF8String(S);
    RegExp.RegEx   := UTF8String('^created');
    if RegExp.Match then begin
      Result := '100';
    end else begin
      RegExp.RegEx := UTF8String('(\d+)% done');
      if RegExp.Match then begin
        Result := UnicodeString(RegExp.Groups[1]);
      end else begin
        Result := '';
      end;
    end;
  finally
    RegExp.Free;
  end;
end;

DelphiからLuaスクリプトを呼び出します。usesにLua.ApiとLuaが必要。

//Lua版のGetProgress呼び出し準備
var
  FLua: TLua;

procedure TWavPackGUIMain.FormCreate(Sender: TObject);
begin
  FLua := TLua.Create;
  FLua.LoadFromFile(ExtractFilePath(ParamStr(0))+'GetProgress_WavPackEnc.lua',False);
end;

procedure TWavPackGUIMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  FLua.Free;
end;

//Lua版のGetProgressを呼び出す
function GetProgress2(S: string): string;
begin
  Result := FLua.Call('GetProgress',[S]).ToString;
end;
--Lua版のGetProgress
function GetProgress(s) 
  if string.match(s, '^created') ~= nil then return "100";
  else return string.match(s, '(%d+)%% done'); end;
end

文字列を読み込んで進捗度を返す関数です。
演算子 ~= は等値比較 (==) の否定です。
行頭がcreatedなら100、それ以外なら%の直前の数字を返します。
関数名やスクリプト名を見ればわかるように、WavPack用になってます。
WavPackの表示が少々変わってもスクリプト側で吸収できることが期待されます。

スタックの取り扱い

GetProgressは入出力が単純でした。これより多くの入出力を得ようと思った場合、スタックという機能を用います。

procedure dumpStack(L: lua_State; M: TMemo);
var
  T: Integer; //lua_type
  I: Integer;
  stackSize: Integer;
begin
  //スタックに積まれている数を取得する
  stackSize := lua_gettop(L);
  for I := stackSize downto 1 do begin
    T := lua_type(L,i);
    M.Text := M.Text + 'Stack['+Format('%2d-%10s',[i,lua_typename(L,T)])+'] : ';
    case T of
      LUA_TNUMBER: begin
        M.Text := M.Text + FormatFloat('0.000000',lua_tonumber(L, i));
      end;
      LUA_TBOOLEAN: begin
        if 0 <> lua_toboolean(L, i) then begin
          M.Text := M.Text + 'true';
        end else begin
          M.Text := M.Text + 'false';
        end;
      end;
      LUA_TSTRING: begin
        M.Text := M.Text + lua_tostring(L, i);
      end;
      LUA_TNIL: ;
      else begin
        M.Text := M.Text + lua_typename(L, T);
      end;
    end;
    M.Text := M.Text + #13#10;
  end;
  M.Text := M.Text + #13#10;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  L: TLua;
begin
  L := TLua.Create;
  try
    lua_pushboolean(L.State, 1);    //true をpush
    dumpStack(L.State, Memo1);
    lua_pushnumber(L.State, 10.5);  //10.5 をpush
    dumpStack(L.State, Memo1);
    lua_pushinteger(L.State, 3);    //3 をpush
    dumpStack(L.State, Memo1);
    lua_pushnil(L.State);           //nil をpush
    dumpStack(L.State, Memo1);
    lua_pushstring(L.State, 'Hello world'); //hello world をpush
    dumpStack(L.State, Memo1);
  finally
    L.Free;
  end;
end;

結果表示。

Stack[ 1-   boolean] : true

Stack[ 2-    number] : 10.500000
Stack[ 1-   boolean] : true

Stack[ 3-    number] : 3.000000
Stack[ 2-    number] : 10.500000
Stack[ 1-   boolean] : true

Stack[ 4-       nil] : 
Stack[ 3-    number] : 3.000000
Stack[ 2-    number] : 10.500000
Stack[ 1-   boolean] : true

Stack[ 5-    string] : Hello world
Stack[ 4-       nil] : 
Stack[ 3-    number] : 3.000000
Stack[ 2-    number] : 10.500000
Stack[ 1-   boolean] : true

スタックに値をpushするたびにスタックの内容を表示しています。lua_toboolean関数はLuaの条件判定と同様、false と nil 以外のすべてのLuaの値に対して真を返します。



[Top]

Copyright © 2021 y.ohm / Yoshimasa Ohmuro / 大室 喜正
e-mail : y.ohm@ohmix.net
PGP Public Key : DH/DSS