色々と調査した結果ビルド芸関連の記事が頻繁にアクセスされていることがわかったので、久々にやっていきたいと思います。
ビルド芸は結構色んな知識が得られるので嫌いじゃないんですが、いかんせん変なところで詰まったり結局それができて何が嬉しいんだみたいなことになったりしがちなので、
今回は次につながるステップの一つとしてあの言語をビルドしていきたいと思います。まあタイトルでバレてるんですけども。
Lua
概要
プログラミング言語「Lua」。高い移植性と組込みの容易さから、様々なソフトウェアのプラグインとして採用されているスクリプト言語です。 名前の由来はポルトガル語の「月」からきているんだとか。なんかちょっとおしゃれです。
個人的には、AviUtlやnginx、そのほかAdobe製のソフトウェアで見かける印象が強いです。ゲーム制作にはあまり明るくないのですが、イベントスクリプトを記述するための言語として広く採用されていたりする(The Engine Survey: General results - SATORI)んだとか。
コードはLua VMと呼ばれる環境上で実行されます。ソースをそのまま食わせることも、独自のバイトコードに"コンパイル" することも可能です。
なので、種類としてはインタプリタ型に属する…のかな? ただ The LuaJIT Project なるものが存在するらしいです。この辺りは詳しい方にお任せします。
記法
基本的な記法はこんな感じ。 FizzBuzzは要件がシンプルなだけに何も考えないで書くと変なミスしますね。おおこわいこわい。
-- -- FizzBuzz -- -- 最大値を取得 io.write("Limit: ") local limit = tonumber(io.read()) --カウントアップ for n = 1, limit do if n % 3 == 0 and n % 5 == 0 then print("FizzBuzz") elseif n % 3 == 0 then print("Fizz") elseif n % 5 == 0 then print("Buzz") else print(n) end end
Pythonっぽいなーとか思ってましたが、Wikipediaさん曰くPascalに類似しているとのこと。
Pascalは未履修なのでよくわかりません、すみません…
本題
別に言語紹介は今回の本題ではないので、早速ビルド芸の方を始めたいと思います。
先述の通りLuaはソフトウェアへの組込みを想定して設計されているため、ビルド方法は公式で詳しく解説されています。
正直これが全てと言っても過言ではないのですが、まあ、はい…
やっていきましょう。 ソースを落として展開します。
% wget https://www.lua.org/ftp/lua-5.4.6.tar.gz % tar xzf lua-5.4.6.tar.gz
tree
するとわかるのですが、めちゃくちゃ軽いです。ソース以外の全てのファイルを含めても100ファイルを切ります。
数字としてイメージしづらいと思うので、過去にビルドしてきたものたちと比較してみました。組込み前提とはいえ、この軽さは凄まじい。
…冷静に考えるとこのグラフは一体なんなんでしょうか。
なにはともあれ、ソースの用意はできました。早速ビルドしましょう。
% make
…ビルド芸につきものなのが、このmake
の完了を待つ時間。まあ、ゆっくりいきましょう。私は今推しを抱えてこの記事を書いています。
せっかくですしアニメでも観ましょうか。先日発表された蓮ノ空とアイカツのコラボの話を友人に投げたところ謎の交換条約が成立し、友人はアニガサキを、私は『響け!ユーフォニアム』を履修することになりました。
うん、そうしましょう。せつ菜ちゃんもその方が退屈しないと思います(まあ一応IT活用チームの一員ではありますが)。
…おや? ターミナルが止まっています。なんでしょう。またbrewが古いとか言い出したんでしょうか。
…ん?
% echo $? 0
なんとビルドが終了していました。
速い、速すぎる。なんだお前。
流石に気になったので計測してみました。
% make clean % time make (省略) make 6.14s user 1.83s system 93% cpu 8.492 total %
なんと10秒を切っています。速っや。これではアニメどころではありません。
make test
に至っては一瞬で終わってしまいました。あ、そう…
ビルドが完了したら次はインストールです。make install
するとシステムに直接インストールされますが、make local
するとソースファイル直下に install
ディレクトリが生成され、そこにバイナリが展開されます。
今回はシステムでも使いたいので、両方実行しました。
試してみる
早速試してみましょう。先ほどのFizzBuzzのソースを用意し(main.lua
)、コンパイルしてみます。
% luac -o main.out main.lua
luac
によって生成されるバイトコードは Lua bytecode と呼ばれる独自形式のようです。
% file main.out main.out: Lua bytecode, version 5.4
実行します。
% lua main.out Limit: 20 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz
いい感じにFizzBuzzできました。
他言語に組み込んでみる
先述の通り、Luaは既存のソフトウェアへ容易に組み込むことが可能です。こちらも軽く試してみましょう。今回はこちらのページを参考に実装しています。
はじめに、ファイル test.lua
を用意しました。
-- -- call Lua from C -- io.write("Hello! I'm Lua.\n") local n = 10 print("The factorial of " .. n .. " is " .. factorial(n) .. ".") function sum(limit) local total = 0 for i = 1, limit do total = total + i end return total end
関数 factorial
はLuaの標準関数には存在しないので、このコードはこのままでは実行できません。
次に、このようなCソースを準備します。
クリックして展開
// // // #include <lua/lauxlib.h> #include <lua/lua.h> #include <lua/lualib.h> #include <stdio.h> const char* fileName = "test.lua"; // 階乗 int l_factorial(lua_State*); int main(int argc, char const* argv[]) { // Luaの状態を新しく生成し、保持 lua_State* Lua = luaL_newstate(); // 標準ライブラリの関数を使えるようにする luaL_openlibs(Lua); // Cの関数をLuaに登録 lua_register(Lua, "factorial", l_factorial); /* ---------- */ // ソースファイルを読み込む int err = luaL_loadfile(Lua, fileName); if (err != 0) { printf("An error occured while loading source file: %s\n", lua_tostring(Lua, -1)); return err; } // 読み込んだLuaソースを実行する ルートに記述されたステートメントはここで実行される err = lua_pcall(Lua, 0, 0, 0); if (err != 0) { printf("An error occured while running script: %s\n", lua_tostring(Lua, -1)); return err; } /* ---------- */ // 0. Luaで定義した関数を呼び出す 関数名と引数を用意 const char* funcName = "sum"; int param = 10; // 1. グローバルスコープで定義された関数オブジェクトを名前から引き、"Luaスタック" に積む lua_getglobal(Lua, funcName); // 2. 引数値を積む lua_pushnumber(Lua, param); // 3. pcallを呼び出して実行する 引数は(状態, 引数の数, 戻り値の数, エラー格納先) err = lua_pcall(Lua, 1, 1, 0); if (err != 0) { printf("An error occured while calling function: %s\n", lua_tostring(Lua, -1)); return err; } // 4. 値を確認して取り出す if (!lua_isnumber(Lua, -1)) { printf("Unexpected return value type: %s\n", lua_typename(Lua, lua_type(Lua, -1))); return 1; } int result = lua_tonumber(Lua, -1); printf("Function invocation result: %s(%d) = %d\n", funcName, param, result); // 5. スタックからポップ(して値を破棄)する lua_pop(Lua, 1); // 状態を終了し、閉じる lua_close(Lua); return 0; } int l_factorial(lua_State* Lua) { // Luaスタックから引数値を取得 int n = luaL_checkinteger(Lua, -1); // 計算 int total = n; for (int i = n - 1; i > 0; i--) { total *= i; } // 計算結果を積み、「関数の戻り値の数」を返す lua_pushinteger(Lua, total); return 1; }
ここではLuaを準備し、関数l_factorial
を factorial
という名前で登録しています。これにより、Luaスクリプト側から関数 factorial
を呼ぶことができるようになります。
次にソースファイル test.lua
を開き、スクリプトとして読み込みます。このタイミングで test.lua
が実行され、メッセージの表示および関数 sum
の定義が行われます。
最後に関数 sum
をCから呼び出し、結果を出力します。
コンパイル時は lua
をリンクしましょう。
% gcc main.c -llua
実行するとこんな感じ。
% ./a.out Hello! I'm Lua. The factorial of 10 is 3628800. Function invocation result: sum(10) = 55
Luaで記述した関数をCから、Cで記述した関数をLuaからそれぞれ呼び出すことができています。
実に簡単です。
うまくいきすぎてつまんない!
……特に何の問題も起こらないので面白みがありませんね。
これではビルド芸ではなくただのビルド日記です。
もっとこう、心躍る展開が欲しい。
Swiftから呼び出す
ということでSwiftから呼び出してみましょう。
ここから先は長くなるというか普通に有用な気がしてきたので、Zennにちゃんとした記事として投稿したいと思います。投稿次第こちらにもリンクする予定です。
実装としては完了している(v0.1.0リリース済)ので、一応repoのURLを貼っておきます。
というわけで、基本的なビルド方法とCとの連携例は以上です。お疲れ様でした。
追記 (20240318): やっとZennが生えました。
fflush(stdout)