22.2. Lua と連携 編
LuneScript は Lua のコードを実行できます。
しかし、Lua コードを実行するには幾つかの点で注意が必要です。
以降では、 LuneScript 上で Lua コードを実行する際の注意点を説明します。
Lua コードの実行
LuneScript では、
_load()
関数などを利用することで LuneScript 内から Lua のコードを実行できます。
_load()
関数は、 lua の load()
関数と基本的に同じ仕様です。
以下にサンプルを示します。
// @lnsFront: ok
let code = ```
return { val1 = 10, val2 = 20 }
```;
let loaded, err = _load( code, nil );
when! loaded {
if! let obj = loaded( ## ) {
forsort val, key in obj@@Map<str,int> {
print( key, val + 100 );
}
}
} else {
print( err );
}
このサンプルは、次の値を出力します。
val1 110
val2 120
これは、Lua のコード return { val1 = 10, val2 = 20 }
が返す Lua のテーブルを、
forsort で列挙して print( key, val + 100 );
で出力した結果です。
上記サンプルは、以下の LuneScript のコードとほぼ同じと言えます。
// @lnsFront: ok
fn func():Map<str,int> {
return { "val1":10, "val2":20 };
}
forsort val, key in func() {
print( key, val + 100 );
}
このように、 LuneScript と Lua は連携して動作することが出来ます。
Lua → LuneScript のデータ変換
LuneScript から Lua のコードを実行したとき、 その Lua コードの実行結果は Luaval 型になります。
Luaval 型は、Lua コード内のデータを保持する型で、
LuneScript の型 T に相当する Lua コード内のデータは Luaval<T>
となります。
例えば上記サンプルの { val1 = 10, val2 = 20 }
は、
LuneScritp の Map<str,int> に相当し、 Luaval<Map<str,int>>
となります。
なお、 LuneScript から Lua にトランスコンパイルする場合、 Luaval<T> は T と完全に一致した型 になります。 LuneScript から Lua にトランスコンパイルする場合、 例え Luaval<T> と明示的に宣言しても内部的に T として変換されるため、 Luaval<T> を意識する必要はありません。
LuneScript から Lua 以外の言語(現在は go に変換可能)に変換する場合、 Luaval<T> と T は明確に扱いが異なるため、 Lua 以外に変換する場合は Luaval<T> を意識する必要があります。
トランスコンパイルのオプションに "–valid-luaval" を指定 することで、 Lua にトランスコンパイルする際も、Luaval<T> と T を分けて管理します。
上記サンプルは、次のように Luaval<Map<str,int>>
の引数を持つ
func()
を使用するように書き換えることが出来ます。
// @lnsFront: ok
fn func(map:Luaval<&Map<str,int>>) {
forsort val, key in map {
print( key, val + 100 );
}
}
let code = ```
return { val1 = 10, val2 = 20 }
```;
let loaded, err = _load( code, nil );
when! loaded {
if! let obj = loaded( ## ) {
func( obj@@Map<str,int> );
}
} else {
print( err );
}
また上記のサンプルの様に Map の Luaval 型のデータに対しても、
LuneScript の Map と同様に
foreach や []
を使用した要素アクセスなどで、Map 内のデータを参照できます。
ただし参照は出来ますが、 変更は出来ません。
相互変換可能な型
Lua コード内のデータの型が次の場合、 Luaval 型とはならずにそのままの型になります。
- int, real, bool, str
- 上記の nilable
str に関しては、 文字列長に比例したオーバーヘッド が掛かります。
Luaval 型のキャスト
Luaval 型のキャストには制限があります。
上記サンプルは、次のように func の引数に obj@@Map<str,int>
を渡しています。
// @lnsFront: skip
if! let obj = loaded( ## ) {
func( obj@@Map<str,int> );
}
これは obj を Map<str,int> 型にキャストする演算ですが、 ここで obj の型は Luaval<stem> 型であり、 それを Map<str,int> にキャストすると、 そのキャスト後の型は Luaval<Map<str,int>> になります。
ある型 T1 から T2 型にキャストが可能だった時、 Luaval<T1> から T2 へのキャストを指示した場合、 そのキャスト結果は Luaval<T2> になります。
また、次のキャストは出来ません。
- T1 型から Luaval<T1> 型へのキャスト
- Luaval<T1> 型から T1 型へのキャスト
具体的には Map<str,int> から Luaval<Map<str,int>> へのキャストは出来ません。
ただし、 stem 型は例外的に Luaval との相互キャストが可能です。
なお、次のように stem 型を経由することで、 Luaval 型から非 Luaval 型へのキャストが可能ですが、
Luaval => stem => Luaval
本来とは異なる型にキャストした時の動作は 未定義 です。
expandLuavalMap
Lua コード内の collection のデータは、 Luaval として扱います。
たとえば、 Map 型のデータは Luaval<Map> です。 Map 型と Luaval<Map> 型のデータは互換性がないため、代入などは出来ません。
この collection の Luaval 型のデータを LuneScript の値として展開する方法として、 次の関数を提供しています。
fn expandLuavalMap( Luaval<stem>! ) : stem!;
この関数を使用すると、次のような処理が掛けます。
// @lnsFront: ok
fn func(map:Luaval<&Map<str,int>>) {
forsort val, key in map {
print( key, val + 100 );
}
}
fn func2(map:&Map<str,int>) {
forsort val, key in map {
print( key, val + 100 );
}
}
let code = ```
return { val1 = 10, val2 = 20 }
```;
let loaded, err = _load( code, nil );
when! loaded {
if! let obj = loaded( ## ) {
func( obj@@Map<str,int> );
if! let map = expandLuavalMap( obj ) {
func2( map@@Map<str,int> );
}
}
} else {
print( err );
}
このサンプルでは、 Luaval<&Map<str,int>> を列挙する func()
関数と、
&Map<str,int> を列挙する func2()
関数があります。
func2() をコールする前に expandLuavalMap()
で Luaval 型のデータを展開し、
それを Map<str,int> にキャストするこおで Luaval<Map<str,int>> ではなく、
Map<str,int> として処理しています。
なお expandLuavalMap()
は、
引数で与えられた Luaval 型のデータを展開したクローンを作成します。
Luaval 型の型変換
ある nilable 型の T! を保持する Luaval 型は、 Luaval<T>! になります。 Luaval<T!> にはなりません。
また、 Luaval<T> の Immutable は Luaval<&T> になります。
Luaval 型の関数、Luaval 型オブジェクトのメソッド
関数型の Luaval は、その引数、戻り値も Luaval 型になります。
例えば次のサンプルの func 関数の引数 proc は、 Luaval<Process> 型の From であり、 この Form の引数は Luaval<&List<int>> 、 戻り値は Luaval<&List<int>> になります。
// @lnsFront: skip
form Process( val:&List<int> ) : &Map<int>;
fn func( proc:Luaval<Process> ) {
let list = proc( [ 1, 2, 3 ] );
}
LuneScript → Lua のデータ変換
Lua の関数に LuneScript の値を渡す時は、Luaval 型の値を渡す必要があります。
ただし、 Lua の関数の引数が次の値の場合、 Luava 型ではなくそのままの型になります。
- int, real, bool, str
- 上記を要素に持つ List などのコレクション型
- 上記の nilable
次にサンプルを示します。
// @lnsFront: ok
let code = ```
return function( tbl )
local total = 0
for key, val in pairs( tbl ) do
total = total + val
end
return total
end
```;
let loaded, err = _load( code, nil );
when! loaded {
if! let obj = loaded( ## ) {
let map = { "val1":1, "val2":10 };
print( (obj@@form)( map ) ); // Lua の関数コール
}
} else {
print( err );
}
このサンプルは、 引数で与えられた Lua のテーブルの要素の値の合計を計算する関数をコールします。
このサンプルでは、 Map<str,int> 型のデータ map を Lua の関数の引数に指定して実行しています。
このとき、内部的に Map<str,int> 型のデータが Lua のテーブルに変換されています。