0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LuaへのトランスパイラYuescriptの紹介

Posted at
  • Yuescript は lua に変換するトランスコンパイラ。
    Lua へのトランスパイラ集にあった。
  • 他言語にある内包表記、クラス型 OOP、分解代入ほか、?.(null チェックしてアクセス)など便利な記法を取り入れる
    独自の prelude は持ってない(すべて Lua 本体にある機能に変換する)
    型の導入、他言語の哲学の導入はしていない。
  • インデントでブロックを作り最後の式を評価して返す
    複文を横に並べて書けない

インストールと使い方

  • sudo luarocks install yuescriptでインストール。
    yue -c -l --target=5.1 program.yueで program.lua を生成(シンプル!)
  • 周辺ツールは文法の色付けのみ。(トランスパイルして lua のものを使えればつかう)
  • shebangを書いてコマンドにできないっぽい。

よく使う機能

クラス型 OOP

.moon
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 は不完全なものでよくなる
.moon
    tween = with tween.new(@@【新たな数字が大きくなる時間○秒】, anim, {scale: 1}, 'outBack')
        .clock = -delay

パイプ |>

  • 記述順と動作順を同じにできる
  • パイプの右側は関数呼び出しのみ。よって関数呼び出しのカッコを省略することも可能
  • テーブル生成子や内包表記につなげたいとき、それを関数にすれば良い(=> {@, @})

テンプレート文字列"text#{exp}"

  • ダブルクォート文字列が複数行と式埋め込みに対応。トランスパイル時に分割される。式にはtostring()が付く
    [spec?] 埋め込む式は複数行になってはいけない

?で存在チェック

loveのイベントを現在フォーカスを持つウィジットに渡すために、
他の言語ではinterfaceや空の実装を用意して何もしないメソッドを呼ぶだろうところ、
yueでは同名のメソッドが存在するときのみ呼ぶという形になった

ui.moon
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) -- 反転

おもしろい機能

仮引数の初期値

  • トランスパイル時に関数本体に入るので
    • 毎回初期値が評価される
    • 右から詰める必要もない
    • より左の仮引数の値を使える(...も可能か?)
2048.moon
-- 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重の囲みになる)

.moon
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')])}"
      
    • 決まった形のデータに展開する

      .moon
      export 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わからない
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?