Edited at

トランスコンパイラ LuneScript で Lua の開発をもっと楽に!!

2018/12/29 全面的に更新しました。

Lua は非常にコンパクトな言語でありながら、高い潜在能力を持つ言語です。

プログラムに組み込まれる言語としては、最も使い易い言語の一つと言っても良いと思います。

ただ「プログラムに組み込まれる言語としては使い易い」とはいえ、イマドキの言語と比べると、いろいろと気になるところがあるのも事実です。

一方で、Lua をイマドキの言語に近づけるための機能進化は、「コンパクト」という Lua の大きな特徴の一つとトレードオフになる可能性があります。

そこで、 Lua 自体には手を加えずに、Lua の気になる箇所をカバー出来るトランスコンパイラ LuneScript を紹介します。


LuneScript とは

LuneScript とは、前述の通り Lua の気になる点をカバーする言語で、LuneScript で開発したコードを Lua のコードに変換することが可能なトランスコンパイラです。

LuneScript は、次の特徴を持ちます。


  • Lua と C の syntax を基調としているため、学習コストが低い。

  • 静的型付け言語であるため、型チェックにより単純なミスをコンパイル時に発見可能。

  • 型推論により、型宣言の手間を最小化。

  • NULL 安全 (null safety)。

  • generics により、型情報を保ったままの処理が可能。

  • 言語の文法としてクラス定義を対応。

  • マクロ により、ポリモーフィズム等の動的処理に頼らないデザインを実現可能。

  • JSON と互換なデータ表現をサポート。

  • トランスコンパイルした Lua コードは、外部ライブラリを前提とせずに単体で動作可能。

  • トランスコンパイルした Lua コードは、LuneScript で書いた処理そのままが出力されるので、性能劣化がない。

  • 既存の Lua の外部モジュールを LuneScript から利用可能。

  • LuneScript は Lua 上で動作し、Lua 標準モジュール以外を必要としないため、導入が簡単。


  • Lua5.1, Lua5.2, 5.3 をサポート。 (追記: 12/18)



  • LuneScript はセルフホスティングで開発している。

  • emacs でのコード補完に対応

  • glue コードの自動生成に対応


LuneScript の使用方法

LuneScript は github で開発しています。

https://github.com/ifritJP/LuneScript

導入方法は次を参照してください。


コマンド

LuneScript を導入すると、lnsc コマンドがインストールされます。

lnsc コマンドの使用方法については、次の記事を参考にしてください。


Lua バージョン間のクロスコンパイル

LuneScript は Lua のバージョン間クロスコンパイルをサポートします。次の記事を参考にしてください。


LuneScript の仕様

ここでは LuneScript の仕様について説明します。

補足記事は、ここにリンクを追加していきます。


値と型

LuneScript で扱う値と型については次の記事を参考にしてください。


コメント

コメントは C++ スタイルを採用。一行コメント // 、 複数行コメント /* */ を指定可能。

// 行末までコメント

/* ここから〜
ここまでコメント*/


演算子

原則的に、演算子 は Lua と同じものを利用する。

Lua5.3 の //(切り捨て除算) は、1行コメントとなるので注意すること。

なお LuneScript では、整数同士の / は自動的に切り捨て除算となる。


変数宣言

LuneScript の変数については次の記事を参考にしてください。


一般制御文

LuneScript の制御文については次を参考にしてください。


関数宣言

LuneScript の関数については、次を参考にしてください。


nilable

LuneScript は nil 安全 (NULL 安全) な言語です。

LuneScript の nil 安全を実現する nilable については、次を参考にしてください。


クラス

LuneScript はオブジェクト指向プログラミングのためのクラスをサポートします。

LuneScript のクラスは、次の制約を持ちます。


  • 多重継承はサポートしない。

  • generics はサポートしない。

  • 全てがオーバーライド可能なメソッドとなる。


    • オーバーライドの抑制はできない。



  • 継承間で引数の異なる同名メソッドは定義できない。


    • ただし、コンストラクタは例外で同じ名前( __init )。



次の記事を参考にしてください。


プロトタイプ宣言

LuneScript は、スクリプトの上から順に解析する。

スクリプトで参照するシンボルは、事前に定義されている必要がある。例えばクラス TEST 型の変数を宣言するには、事前にクラス TEST を定義する必要がある。

また、交互に参照するクラスを定義するには、どちらかをプロトタイプ宣言する必要がある。

次は、 ClassA, ClassB がそれぞれを参照する時の例である。

class Super {

}
pub proto class ClassB extend Super;
class ClassA {
let val: ClassB;
}
pub class ClassB extend Super{
let val: ClassA;
}

proto は上記のように宣言する。

プロトタイプ宣言と実際の定義において、pub や extend など同じものを宣言しなければならない。


Mapping

LuneScript のクラスインスタンスは、Map オブジェクトとの相互変換が可能である。

これを Mapping と呼ぶ。

Mapping については次を参考にしてください。


Generics

LuneScript は Generics をサポートします。

詳しくは次を参照してください。

https://qiita.com/dwarfJP/private/50b119fd082fcc212c48


nil 条件演算子

nilable の値を簡単に扱う方法として、 nil 条件演算子をサポートしています。


モジュール

LuneScript のモジュール管理については、次を参考にしてください。


ビルド

LuneScript を使用したプロジェクトをビルドする方法については、次を参考にしてください。


_lune.lua モジュール

前述している通り LuneScript で Lua へトランスコンパイルしたファイルは、Lua コマンドでそのまま実行できます。この時、外部モジュールを必要としません。

これは、トランスコンパイルした Lua コード内に、処理に必要なコードを全て含めていることを示します。

例えば次の処理コードをトランスコンパイルすると、

fn func( val:int! ):int {

return 1 + unwrap val default 0;
}

Lua コードは次のようにだいぶ長くなります。

 1  --mini.lns

2 local _moduleObj = {}
3 local __mod__ = 'mini'
4 if not _ENV._lune then
5 _lune = {}
6 end
7 function _lune.unwrap( val )
8 if val == nil then
9 __luneScript:error( 'unwrap val is nil' )
10 end
11 return val
12 end
13 function _lune.unwrapDefault( val, defval )
14 if val == nil then
15 return defval
16 end
17 return val
18 end
19
20 local function func( val )
21 return 1 + _lune.unwrapDefault( val, 0)
22 end
23
24 return _moduleObj

この 4 〜 18 行目が unwrap に必要な処理となります。なお、このコードは全ての Lua ファイルに出力されます。

このコード自体は共通処理であるため、トランスコンパイルする際に -r オプションを指定することで、別モジュールとして require して共通処理をまとめることができます。

具体的には次のように -r オプションを指定します。

$ lua lune/base/base.lua -r src.lns save

この -r オプションを指定した場合、上記のコードは次のように変換され、かなりスッキリします。

--mini.lns

local _moduleObj = {}
local __mod__ = 'mini'
_lune = require( "lune.base._lune" )
local function func( val )
return 1 + _lune.unwrapDefault( val, 0)
end

return _moduleObj

なお、require( "lune.base._lune" ) が挿入されるため、このモジュールがロード出来るようにセットしておく必要があります。トランスコンパイラが動作する環境であれば意識する必要はありませんが、変換後の Lua ソースをどこか別の環境で実行するような場合は注意が必要です。


マクロ

LuneScript は簡易的なマクロを採用する。


マクロの意義

マクロは通常の関数と比べて幾つかの制限がある。またマクロで行なえう処理は、オブジェクト指向を駆使することで実現できることが多い。

では、マクロを使う意義は何か?

それは、「マクロを使うことで静的に動作が確定する」ことである。

同じ処理をオブジェクト指向で実現した場合、動的な処理となってしまう。一方、マクロで実現すれば、静的な処理となる。

これの何が嬉しいのか?

それは、静的型付け言語が動的型付け言語よりも優れている点と同じである。

静的に決まる情報を静的に処理することで、静的に解析できる。

例えば、オブジェクト指向の関数オーバーライドの大部分は、マクロを利用することで静的に解決することができる。動的な関数オーバーライドではなく、静的な関数呼び出しにすることで、ソースコードを追い易くなる。

無闇にマクロを多用するは良くないが、安易に関数オーバーライドなどの動的処理にするのも理想ではない。

動的処理とマクロは適宜使い訳が必要である。


マクロ定義

マクロ定義については次の記事を参考にしてください。