v1.16.0 - Lua スクリプト for YMM4
標準の Lua には無い簡易な記法を構文拡張へ 6 種類追加したリリースです。三項演算子、nil 条件演算子、ラムダ式、パイプ、補間文字列、範囲判定を書けるようになりました。いずれもスクリプトの実行前に等価な標準 Lua へ変換するため、LuaJIT、MoonSharp、CPU カーネルのどの実行エンジンでも同じ結果になります。変換は字句単位で行い、文字列とコメントの内側は変換しません。入口とする記号は通常の Lua には現れないため、既存のスクリプトの書き方と実行結果に変更はありません。
新機能
三項演算子
条件 ? A : B を追加しました。条件が真のとき A を、偽のとき B を返します。条件と両辺は同じ行に式として書きます。
- (function() if 条件 then return (A) else return (B) end end)() へ展開します。条件は 1 度だけ評価し、選ばれた側の式だけを評価します。
- a and b or c と違い、真の側の値が false や nil であっても、その値をそのまま返します。
- : のあとにもう一度 ? : を書けば、else 側で分岐を続けられます。
- 関数呼び出しの引数の位置にも書けます。? を値の位置に書いたときだけ変換するため、テーブルリテラルやメソッド呼び出し、ラベルなど、通常のコードには影響しません。
nil 条件演算子
t?.x と t?[k] を追加しました。t が nil のときは nil を返し、そうでなければ t.x や t[k] を返します。nil の値へ添字アクセスして実行時エラーになるのを防ぎます。
- 対象を一時変数へ受けて 1 度だけ評価する無名関数へ展開します。(function() local tmp = (t) if tmp == nil then return nil end return tmp.x end)() の形になります。
- a?.b?.c のように連ねれば、途中の値が nil でも安全にたどれます。
- nil 合体演算子と組み合わせて、t?.x ?? 既定値 のように書けます。左辺が nil のときだけ既定値を返します。
ラムダ式
(a, b) => 式 を追加しました。その式を返す無名関数へ展開し、(function(a, b) return 式 end) になります。
- 引数が 1 つのときは括弧を省いて x => 式、引数が無いときは () => 式 と書けます。最後の引数として ... を受け取れます。本体には 1 つの式を書きます。
- table.sort の比較関数のように、関数を引数へ直接渡す場面で使えます。
- 入口は => だけのため、比較演算子の >= や <= には影響しません。
パイプ
x |> f を追加しました。f((x)) へ展開し、左辺を関数の第 1 引数として渡します。
- x |> f(a, b) のように引数を書いたときは、左辺が第 1 引数に入り、残りの引数は後ろへ続いて f((x), a, b) になります。
- |> は左から右へ連ねられます。a |> f |> g は g(f(a)) と同じ順序で評価します。
- math.floor のようなメンバー関数も呼べます。
補間文字列
バッククォートで囲む文字列を追加しました。中に {式} を書くと、その値を tostring で文字列にして差し込みます。
x={a}は ("x=" .. tostring(a)) へ展開します。式を含まないhelloは "hello" に、空の `` は "" になります。- { } の中では拡張構文もそのまま使えます。バックスラッシュによるエスケープは保持します。
- 入口はバッククォートのため、通常の文字列やコメントの中のバッククォートは変換しません。
範囲判定
x in <1..5> を追加しました。x がその範囲に入っているかどうかを判定します。.. は終端を含み、..< は終端を含みません。
- 左辺を一時変数へ受けて 1 度だけ評価する無名関数へ展開し、1 <= x and x <= 5 に相当する判定を返します。<1..<5> は上限を含まず、1 <= x and x < 5 になります。
- 範囲 for の for i in <0..10> とは書く位置が異なるため、for のヘッダーや、ipairs、pairs を使う汎用 for には影響しません。
- 増分を伴う <開始..終了, 増分> の書き方は、範囲判定では扱いません。
互換性・後方互換
- 追加した記法はいずれも、標準の Lua には現れない記号、つまり ? と => と |> とバッククォートと in < の並びを入口にしています。既存のスクリプトはそのまま動作し、実行結果も変わりません。
- 変換はすべての実行エンジンに適用され、LuaJIT、MoonSharp、CPU カーネルのいずれでも同じ結果になります。文字列とコメントの内側は変換しません。
- 分岐の各辺、ラムダの本体、パイプの対象、補間文字列の {式}、範囲判定の両端など、入れ子になった式にも構文拡張の変換を再帰的に適用します。
- v1.15.0 までの仕様に変更はありません。obj.pixelshader をはじめとする既存の API と、実行エンジンの自動振り分けに変更はありません。
内部実装
- 字句解析器に補間文字列のトークン種別を追加し、バッククォートで囲む文字列を 1 つのトークンとして読み取ります。あわせて演算子として => と |> を追加しました。
- 範囲の解析を共通のヘルパーへ切り出しました。開始・終了・増分と、終端を含むかどうかを 1 か所で判定し、範囲 for と範囲判定の両方がこのヘルパーを使います。
- 各記法は、属性で優先順位を与えた変換規則として実装しました。nil 条件演算子の規則は三項演算子より先に走り、?. と ?[ を単独の ? より先に処理します。既存の変換基盤はそのまま流用し、変換文脈に変更はありません。
- ドキュメントは付属の 8 言語、サイトの構文拡張のページ、README を更新しました。
- テストは 3297 件です。v1.15.0 の 3259 件から、三項演算子、nil 条件演算子、ラムダ式、パイプ、補間文字列、範囲判定の各変換と評価、入れ子になった式の変換、比較演算子や for のヘッダーを変換しないこと、対象や左辺を 1 度だけ評価することの検証を追加しました。