- Yuescript は lua に変換するトランスコンパイラ。
Lua へのトランスパイラ集にあった。 - 他言語にある内包表記、クラス型 OOP、分解代入ほか、
?.
(null チェックしてアクセス)など便利な記法を取り入れる
独自の prelude は持ってない(すべて Lua 本体にある機能に変換する)
型の導入、他言語の哲学の導入はしていない。 - インデントでブロックを作り最後の式を評価して返す
複文を横に並べて書けない
インストールと使い方
-
sudo luarocks install yuescript
でインストール。
yue -c -l --target=5.1 program.yue
で program.lua を生成(シンプル!) - 周辺ツールは文法の色付けのみ。(トランスパイルして lua のものを使えればつかう)
- shebangを書いてコマンドにできないっぽい。
よく使う機能
クラス型 OOP
class ゲッソー extends _Enemy
new: (x, y)=>
super(x+1, y+8, 14, 16, Sprite({{0x14a, 0x15a, ox: -1, oy: -8, z: 1}, {0x14b, 0x15b}}))
@gravity, @sprites.freq, @vx = 0, 0.8, 0
Scene.current.world\add(@, @x, @y, @w, @h)
update: (dt)=>
super(dt)
if @time > @sprites.freq*2 then @time, @direction = @time-@sprites.freq*2, if Scene.current.player.x < @x then 1 else 3
if @time % (@sprites.freq*2) < @sprites.freq then @vy, @vx = -0.4, _Enemy.Speed*2
else @vy, @vx = 0.4, 0
Playerにぶつかった: ジュゲム.__base.Playerにぶつかった
-
クラス宣言型の単一継承、privateはない。
-
インスタンス変数やメソッドは、クラス宣言にインデントをつけた書き方のほか、(最近の言語のように)クラス宣言の外側から追加する方法がある
-
クラス変数は
@
または@@
をつけて宣言、使うときは@@
を付ける -
クラス内クラスを作りたいとき、インデントをつけた書き方ではうまく展開されない。外側から追加する方法は大丈夫
呼び出すとき@@ClassName()
とすると、第一引数にクラスが渡ってしまうので@@.ClassName()
とする必要がある。 -
インスタンス変数は素直にテーブルのキーと値になるが、メソッドはindexメタメソッドで探す位置にある。
-
__class
で同一クラスか比較できたり、__class.__name
で型名を取ったり、__base.メソッド名
を得たりする。
newは__init
という名前になる
生成は型名()
-
インタプリタなのでクラス自体も実行中、値として存在する
-
issubclassが必要
.moon-- 引数は両方class export issubclass = (A, B)-> assert(A.__init) assert(B.__init) A == B or (A.__parent and issubclass(A.__parent, B))
内包表記
cells = [ [0 for i=1,4] for j=1,4]
- forをネストさせる時順序に注意。外側から書く
- 関数の最後の行でない場所に、受け手のない内包表記を書いた場合、正しく展開されない
- 関数の最後の行が、内包表記の場合、受け手があるものとして return される
- [bug?] 内包表記をパイプで関数に繋げた場合、受け手として認識されない?→luacheck が empty if branch と報告するので気付く
分解代入
- テーブルを分解して変数に代入する機能
{name: name, price: price} = data
配列の場合{a1, a2} = array
- for文でローカル変数に使うときが便利
- [bug] yue v23 現在、多値代入と分解代入を組み合わせると、① 左辺、② 添字、③ 代入の計算順序が狂う。
{x, y}, list = lume.first(list), lume.slice(list, 2)
breaks Before the assignment rule.
local *
で前方宣言を自動でする
- つまりクラスや関数の記述順序を前後させられる
if for while switch do などが式になれる
- (if then else) を3項演算子として使える
- for は明確に受け手があるときのみ式になる(関数の最後ではreturnが必要)
- ネストした for 文は必ず2次元の配列を返す(1次元に出来ない)。対して、for が2つある内包表記は1次元の配列を作れる
do 式で組み立て
- スコープを作るので自然な(短い)変数名が使える
withでオブジェクト生成後に変更を加える
- constructor は不完全なものでよくなる
tween = with tween.new(@@【新たな数字が大きくなる時間○秒】, anim, {scale: 1}, 'outBack')
.clock = -delay
パイプ |>
- 記述順と動作順を同じにできる
- パイプの右側は関数呼び出しのみ。よって関数呼び出しのカッコを省略することも可能
- テーブル生成子や内包表記につなげたいとき、それを関数にすれば良い
(=> {@, @})
テンプレート文字列"text#{exp}"
- ダブルクォート文字列が複数行と式埋め込みに対応。トランスパイル時に分割される。式には
tostring()
が付く
[spec?] 埋め込む式は複数行になってはいけない
?で存在チェック
loveのイベントを現在フォーカスを持つウィジットに渡すために、
他の言語ではinterfaceや空の実装を用意して何もしないメソッドを呼ぶだろうところ、
yueでは同名のメソッドが存在するときのみ呼ぶという形になった
love.keypressed = (key, scancode, isrepeat)-> Scene.current\keypressed?(key, scancode, isrepeat)
love.keyreleased = (key, scancode)-> Scene.current\keyreleased?(key, scancode)
love.mousepressed = (x, y, button, istouch, presses)-> Scene.current\mousepressed?(x, y, button, istouch, presses)
love.mousereleased = (x, y, button, istouch , presses)-> Scene.current\mousereleased?(x, y, button, istouch, presses)
love.mousemoved = (x, y, dx, dy, istouch )-> Scene.current\mousemoved?(x, y, dx, dy, istouch)
love.wheelmoved = (dx, dy)-> Scene.current\wheelmoved?(dx, -dy) -- 反転
おもしろい機能
仮引数の初期値
- トランスパイル時に関数本体に入るので
- 毎回初期値が評価される
- 右から詰める必要もない
- より左の仮引数の値を使える(
...
も可能か?)
-- spawn: (n = if math.random(100)<10 then 4 else 2)=>
spawn: (n = lume.weightedchoice({[2]: 90, [4]: 10}))=>
try -- すべて埋まっているときにrandomchoiceがエラーを返すため
{x, y} = [{i, j} for j, line in ipairs(@field) for i, cell in ipairs(line) when cell == 0] |> lume.randomchoice -- 空いてるマスを集めて1つ選ぶ
@field[y][x] = n
if の条件式の場所で変数宣言と初期化/または再代入文
- [注意] 外側に同じ名前の変数がある時、変数宣言にならずに再代入になる
- lua だから多値にも対応、一番左のものをifの条件の対象とする
- if not のときは
unless
を使う - and/or の右側には書けず。
- [注意] and/or で複数の条件式を書けない。and で合わせたものが代入されてしまう
- while の条件式ではできない
マクロでデータ成形
macroの実行はシステムのlua(5.4)で行われる。
必要なライブラリはマクロのなかで読む
戻り値は一つ、多値はできない。戻り値は文字列にして返す(文字列という値を返したい時2重の囲みになる)
export macro DATE = -> os.date("'%Y-%m-%d'")
関数型しかないため引数0個の時呼び出しカッコを省略して変数のように使える
数字と文字列の自動変換があるため数字も渡せる
-
引数の扱い
- 複数の引数を渡すことが可能
- 引数は文字列として渡る、シングルクォート文字列、ダブルクォート文字列も周囲のクォート文字含めて渡る
例外としてダブルカリー文字列が、先頭、末尾の改行とスペースをトリムして渡る(←これはやりすぎ。luaに合わせて!スペースが使えない!)
-
使いみち
-
トランスパイル時計算
luaでできることは何でもできる(shellを呼ぶことも) mml2oggやzigをコンパイルすることもできた(ただしコンパイルキャッシュ機構が必要)
データをluaのソース形式にすると実行時に変換する部分がなくなり速い。luajit -b
すると更に速い.moon-- 先頭、末尾のスペースが渡ってこないことに注意 export macro to2d = (data, nwidth)-> import 'lib.lume' import 'lib.inspect' -- "#{inspect([ [(if 0x20==c then 0 else c-0x30) for _,c in utf8.codes(row\sub(1,nwidth))] for row in *lume.split(data,'\n')])}" "#{inspect([ [(if 0x20==c then 0 else c) for _, c in utf8.codes(row\sub(1, nwidth))] for row in *lume.split(data,'\n')])}"
-
決まった形のデータに展開する
.moonexport macro palette = (s)-> import 'lib.lume' import 'lib.inspect' "with #{s\gsub('\'(#%x%x%x%x%x%x)\'', (c)->inspect{lume.color(c)})}\n\t.<> = #{s}"
-
-
その他
-
[request] テーブルのキーの順序を保存するマクロがほしい
.moon-- <>._orderedkeysをつけるマクロ、簡易的なもの(完全なものはparseが必要?) export macro orderedkeys = (dict)-> import 'inspect' import 'lume' import 'yue' keys = lume.keys load(yue.to_lua dict)() "{#{dict\sub(2,-2)}, <>: {_orderedkeys: #{inspect [m for m in dict\gmatch("[%{,]%s?(.-): ") when m in keys]}} }"
-
Unicode 識別子
- 日本語の変数名関数名ファイル名(=モジュール名)が使える
- 全角記号やπや㍍なども使える(lpegでspaceを␣と表記できる)
- lua5.2からパースが通らなかったが、
['+']
と表記すれば使える機能だった。 - マングルされないためには
export 型名 = class 型名
と2回型名を書く必要がある
マングルされているとimportで見つからない
import 'combine' as {combn: 順列, permute: 組み合わせ}
close アトリビュート(luajit でも使える)
- スコープを抜けた時実行されるものを順序を前後して書いて置ける
- 書き方:
close _ = {<close>: => donothing()}
- 一方、const アトリビュートは①テーブルの中身をいじれる。もう一度constと書くと再代入できる。ため役に立つとは思えない
危険な機能
local が初期値になったので省略できることの弊害
lua では変数宣言、関数宣言に local と付けなければ global になってしまい、よく付け忘れられてしまっていた
Yue では local がデフォルトになったためそれを省略できるようになった。(local/const と書くこともできる)
global/export のときのみ書いたほうが書き忘れがない
しかし、lua のすべてに local と書いていたものを省略する感じで使うと
ファイルローカルと関数ローカルに同名の変数があったばあいlocalとつけていたときは別変数だったが、同一になってしまう
これはif文の条件式の部分での変数宣言でも同じ、複数の変数を宣言したときでも外側に同じ名前の変数が1つあればその変数だけ、新しく宣言と初期化にならない
つまり、その文だけを見た時『変数を宣言して初期値を代入している』のか、『外側の変数に再代入をしている』のか区別がつきにくくなった。(特にコピペ移植で)
他の言語が var とか let とかを書かせる理由がわかった。
関数呼び出しのカッコ省略
- 省略したほうが見やすくなるところは使う
- [[注意]] 関数名の後にスペースを付けないと lua の括弧省略記法(文字列/テーブル1つのみ渡す形)になる。2つ目以降の引数が渡らない
-
type x == 'number'
は間違いでカッコが必要
予約語が増えているので lua のつもりで書いたら parse 通らない
- as を変数名に使うと字下げがおかしいという変なエラーになる
- 一方、
self
は予約語ではない(1引数関数を=>
と書くこともできる)
その他
- 数値の間に
_
を挟める - not equalが
!=
-
a += 1
がある(多値代入にはできない) - in 演算子
-
??
演算子はorのnil専用だが、使い分ける場面がない -
--target=5.1
したとき整数除算//
は使えない(使えたほうが便利) - テーブルリテラルの別の表記方法がある(が余り使ってない)
- 配列を
[1,]
と表記できるが中身が1つの時カンマが必要(pythonのタプルのよう) - テーブル内では
=
を:
に書き換え、メソッド呼び出しを:
から\
に書き換えが必要 - テーブルの行末のカンマなくても良い、関数引数のカンマはなくてはいけない?
- テーブル展開
...
はテーブルリテラルの中だけで使えて、テーブルの部分更新に使える - メタテーブルのキーを他の値と並べて書ける(活用例はない)
- switch文/式
- 一行に書かない限りelseifとelse if は同じ意味になる
- try/catch
- スライスは、①for文で②ipairsの代わりにアスタリスクを使ったときだけ使える
- 仮引数名をインスタンス変数にすることができる。その時の仮引数名は@を除いたもの
- exportアトリビュート
- [request?] 関数内の記述順序を前後させたい markdownのfootnoteのような
- 後置if/forは前に書いたものと全く同じ。スコープも作るし
- [注意]
y -2
は関数呼び出しと認識される。2項演算子式と認識させるには、スペースなしか、両方あり、にする - backcallわからない