Luaを言語Lと連携させる明白な方法は、Lua APIをLで実装することですが、これは実装者にとって大変であり、Lプログラマには冗長な構文という負担を強います。より単純な解決策は、lua_open
、lua_close
、lua_dobuffer
、およびlua_register
のみをLで実装し、lua_register
を拡張して言語間のマーシャリングを行うことです。追加機能はLuaで提供できます。
Lua APIを使用すると、Luaの状態をCから完全に制御できます。しかし、別の言語Lと連携したい場合はどうでしょうか?LがCと連携できると仮定すると、明白なアプローチは、C APIをLに反映することです。ただし、Lua APIは非常に大きく、いくつかの隠れた微妙さがあるため、これはかなり困難な見通しです。また、Luaの拡張ライブラリやツールを作成するには良い手段ですが、Lプログラマにとって便利な構文には自然につながりません。Luaマニュアル(4.0版の26ページの5.12節を参照)では、Luaステートメントが
a,b = f("how", t.x, 4)
Lua APIへの10回の呼び出しになることを示しています。
より簡単な方法があります。Luaの最初のルール「Luaでやる」を使うことです。
lua_dostring(S, "a,b = f(\"how\", t.x, 4)");
ここで、S
はコードが実行される状態です。実際、lua_dostring
でできないことは、Luaから値を取得することと、LuaがLを呼び出すことを許可することだけです。これらは両方ともlua_register
で実現できます。
したがって、言語間連携に必要なのは、lua_dostring
とlua_register
、さらにLua状態を作成および破棄するためのlua_open
とlua_close
です。また、プリコンパイルされたコードも処理できるため、lua_dostring
よりもlua_dobuffer
を使用する方が優れています。
しかし、待ってください!C APIでは、lua_register
は登録される関数の引数または結果の型について何も述べていません。これらは、Luaスタックの検査と操作によって処理する必要があります。これに対する粗暴だが単純な解決策は、lua_register
に引数と戻り値の型と数を指定させ、Lに自然にマッピングされる型のみを許可することです。
薄いAPIの最終的な関数リストは次のとおりです。
lua_open
とlua_close
lua_dobuffer
lua_register
(適切に特殊化された)LuaをEPOC、PDAなどのモバイルデバイス用のSymbianのOSに移植する際、Eikon GUIなどのOS機能へのフックを提供したいと考えました。EPOCはC++ベースであり、有望に見えますが、スペースの理由から、そのライブラリにはシンボル情報が含まれていないため、名前によるランタイム動的リンクは不可能です。toluaに頼りたくなかったので、代わりにLuaをEPOCのインタプリタ型BASICのようなRAD言語であるOPLにバインドすることにしました。OPLには、広範なOPX(C++で実装されたOPLライブラリ)を含むEPOCの優れたサポートがあり、名前でプロシージャを動的に呼び出すことができます。
OPLには、16ビットおよび32ビット整数、64ビット浮動小数点数、および文字列の4つの基本型があります。16ビット整数は%
、32ビット整数は&
、文字列は$
で示され、浮動小数点数は何も示されません。OPLは、Cのような関数プロトタイプをサポートしています。たとえば、
foo&:(a,b%,c$)
foo
は関数の名前です。&
は、32ビット整数を返すことを示しています(すべてのOPL関数は値を返します。明示的なRETURN
ステートメントがない場合は、デフォルトでゼロまたは空の文字列になります)。コロンは、foo
が関数であることを示します。次に、オプションの引数リストが続きます。この場合、3つの引数があります。浮動小数点数a
、16ビット整数b%
、および文字列c$
です。(文字列は最大255文字までです。このAPIでは、長い文字列をLuaと直接交換することはできません。)
したがって、次のOPL関数を提供する小さなOPXを作成しました。
LuaOpen&:
新しい状態へのポインタを返しますLuaClose:(state&)
指定された状態を閉じますLua&:(state&,chunk$)
指定された状態内で指定されたチャンク(プリコンパイルされている可能性がありますが、最大255バイトであるため、これはおそらく役に立ちません)を実行しますLuaRegister:(state&,func$,name$)
指定されたLua状態内のname$
というLua名で、プロトタイプがfunc$
で指定されたOPL関数を登録しますLua&:
は、適切(Lua&:
はLuaを実行する関数である)であり、4つのうちで最も広く使用される可能性のあるプロシージャの名前として、短くて良い名前であるため、LuaDoBuffer&:
よりも良い名前のように思われました。LuaRegister:
によって登録されたOPL関数がLuaから呼び出されると、引数は自動的にOPL型に変換され、結果の型は変換されて戻されます。整数引数が範囲内にあるかどうかを確認するのはプログラマの責任です。一見、このインターフェースは非常に制限されているように見えるかもしれません。たとえば、Lua式を評価してその結果をOPLに返す簡単な方法はありませんし、OPLでLuaテーブルをトラバースすることもできません。これは意図的なものです。これらの機能を追加するとAPIが複雑になり、それらを省略すると、プログラマはOPLをLuaにライブラリルーチンを提供するだけで使用するようになります。結局のところ、LuaをOPLにリンクする主な動機は、最初にLua用の多くのC++ライブラリを記述する必要なしにEPOCにアクセスできるようにすることでした。
ただし、場合によっては、アプリケーションドメインのプロパティ(SQLやPrologなど)のために、他の言語でアプリケーションの多くを記述したい場合があります。また、Luaをアプリケーション拡張言語として意図された用途から、アプリケーションが記述されるメイン言語へと昇格させているようです。
実際には、ここには矛盾はありません。Luaをアプリケーション拡張言語としてだけでなく、他の言語で記述されたプログラムの一部を一緒にバインドするグルー言語として考えてください。アプリケーション機能のコアは、多くの場合、他の言語L、おそらく速度のためにC、または特定のドメイン固有の言語で実装されます。このコアをライブラリとして構造化することにより、Lプログラマはそれらを結びつけることを心配することなく、Lでアプリケーションプリミティブの提供に集中できます。Lはこれに適していない可能性があります。その後、アプリケーションは一連のライブラリの上にLuaのレイヤーとして実装できます。これにより、Lでドメイン固有のプリミティブをプログラミングするという異なる懸念事項を、特定のアプリケーションを構成することから分離できます。これにより、アプリケーションの記述が容易になり、LuaコードとLコードの両方の再利用が促進されます。
Lua APIの他の部分をLで実装することが本当に必要な場合は、パフォーマンス上の理由でない限り、必要な機能はLコールバックを使用してLuaで実装できます。実際、Lua APIの完全なLua実装を作成し、薄いAPIによってLuaがインターフェースされた任意の言語で動作させることが可能でしょう。
Luaは、標準C APIのほとんどがサブセットである非常にシンプルなAPIを使用して、他の言語に接続できます。ターゲット言語がCと連携できる場合、実装は迅速であり、Luaとターゲット言語の混合でアプリケーションを記述するために必要なすべての機能を提供します。薄いAPIにおけるいくつかの見かけ上の制限は、実際にはより再利用可能なコードの記述に役立ちます。