3.3

Lua がサポートする文は古くからある言語が持つものとよく似ています。例えばブロック・代入・制御構造・関数呼び出し・変数宣言があります。

3.3.1. ブロック

ブロックは文のリストです。一つずつ順番に実行されます:

block ::= {stat}

Lua はセミコロンで表される空文 (empty statement) を持ちます。文をセミコロンで区切ったり、ブロックをセミコロンで始めたり、二つのセミコロンを並べたりといったことが可能です。

stat ::= ‘;

関数呼び出しと代入はどちらも丸括弧で始めることができますが、これにより Lua の文法は曖昧となります。例として次のプログラム片を考えます:

a = b + c
(print or io.write)('done')

Lua の文法がこれを解釈する方法は二つあります:

a = b + c(print or io.write)('done')

a = b + c; (print or io.write)('done')

現在のパーサーはこういった構文を必ず一つ目の方法で解釈し、行の最初にある丸括弧開きを関数呼び出しの引数の始まりとみなします。この曖昧さを取り除くために、丸括弧で始まる文には最初にセミコロンを付けるのが良い習慣です:

;(print or io.write)('done')

ブロックは do...end で明示的に区切って一つの文にできます:

stat ::= do block end

この明示的なブロックは変数宣言のスコープを制御するときに重宝します。またブロックの途中に return を入れるためにブロックが使われることもあります (§ 3.3.4)。

3.3.2. チャンク

Lua ではコンパイル単位をチャンク (chunk) と呼びます。チャンクは構文上はただのブロックです:

chunk ::= block

Lua はチャンクを可変長引数を取る無名関数の本体として扱います (§ 3.4.11)。そのためチャンクはローカル変数を定義でき、引数を受け取ることができ、値を返すことができます。この無名関数は _ENV という外部ローカル変数と同じスコープでコンパイルされる (§ 2.2) ので、_ENV を唯一の外部変数として持ちます。_ENV が使われなくても除外されることはありません。

チャンクはファイルもしくはホストプロラム中に文字列として保存できます。チャンクを実行するとき Lua は最初にそれをロードして実行する前にチャンクのコードを仮想マシン用の命令にコンパイルし、それから仮想マシンのインタープリタを使ってコンパイルしたコードを実行します。

前もってチャンクをバイナリ形式にコンパイルしておくことも可能です。詳細は luac プログラムと string.dump 関数を参照してください。ソースプログラムとコンパイルしたバイナリ形式は交換可能です。Lua はファイルタイプを自動的に検出し、それに沿った動作をします (参照: load)。

3.3.3. 代入

Lua では複数代入が可能です。そのため代入の構文は左辺に変数のリストを取り、右辺に式のリストを取ります。リストの要素は両辺ともコンマで区切られます:

stat ::= varlist ‘=’ explist
varlist ::= var {‘,’ var}
explist ::= exp {‘,’ exp}

式 (exp, explist) は § 3.4 で議論します。

代入が行われるとき、右辺にある値のリストは左辺にある変数の長さに調整 (adjust) されます。つまり値の数が必要より多いときは余計な分が捨てられ、逆に値が必要より少ないときは足りない分が nil で埋められます。式のリストが関数の呼び出しである場合には代入が起こる前にその呼び出しが実行され、全ての返り値が値のリストに入ります (関数呼び出しが括弧で囲われている場合は除きます。参照: § 3.4)。

代入文ではまず全ての式が評価され、それから代入が実行されます。例えば

i = 3
i, a[i] = i+1, 20

を実行すると a[3]20 が代入され、a[4] は影響を受けません。なぜなら a[i]i4 が代入される前に評価されるからです。同様に

x, y = y, x

xy の値を交換し、

x, y, z = y, z, x

x, y, z の値を循環させます。

グローバルな名前への代入 x = val_ENV.x = val という代入と等価です (§ 2.2)。

テーブルフィールドおよびグローバル変数への代入の意味はメタテーブルで変更できます (§ 2.4)。なお突き詰めればグローバル変数もテーブルフィールドです。

3.3.4. 制御構造

制御構造 if, while, repeat はいつも通りの意味とお馴染みの構文を持ちます:

stat ::= while exp do block end
stat ::= repeat block until exp
stat ::= if exp then block {elseif exp then block} [else block] end

Lua には for 文もあり、二つの書き方があります (§ 3.3.5)。

制御構造に含まれる条件式は任意の値を返すことができます。条件式は false または nil に対して偽となり、それ以外の全ての値に対して真となります。特に数値 0 と空文字列は真となります。

repeat...until... ループにおいて内側のブロックが終わるのは until キーワードではなくその後の条件式です。そのため条件式はループブロックで宣言されたローカル変数を参照できます。

goto 文はプログラムの制御をラベルまで移動させます。構文的な理由により、Lua のラベルは文とみなされます:

stat ::= goto Name
stat ::= label
label ::= ‘::’ Name ‘::

ラベルは基本的に定義されたブロックのどこからでも見えるようになりますが、唯一ネストされた関数からは見えません。goto を使えば新たなローカル変数のスコープに入らない限り任意の見えているラベルにジャンプできます。ラベルには既に見えているラベルと同じ名前を付けるべきではありません。同じ名前のラベルが定義されたのが外側のブロックであったとしても、これは避けるべきです。

ラベルと空文は何の動作も表さないので、無文 (void statement) と呼ばれます。

break 文は while, repeat, for ループの実行を終了し、ループ後の文までスキップします:

stat ::= break

break は最も内側のループを終了します。

return 文は関数あるいはチャンクから値を返すのに使われます (チャンクは無名関数として扱われます)。

関数は二つ以上の値を返すことができます。そのため return の構文は次のようになります:

stat ::= return [explist] [‘;’]

return はブロックの最後の文としてのみ書くことができます1。ブロックの途中で return する場合には、do return end として明示的にブロックを作らなければなりません。こうすると return が新しくできたブロックの最後の文となります。

3.3.5. for 文

for 文は二つの形があります: 数値を使った for と一般的な for です。

数値を使った for ループ

数値を使った for ループは制御変数を等差的に増加または減少させながらブロックのコードを反復します。次の構文を持ちます:

stat ::= for Name ‘=’ exp ‘,’ exp [‘,’ exp] do block end

識別子 Name が制御変数の名前であり、制御変数はループ本体 (block) における新しいローカル変数として定義されます。

ループの実行は三つの制御式を一度ずつ評価するところから始まります。この三つの値はそれぞれ初期値、終了値、ステップを表します。ステップが与えられなければ 1 となります。

初期値とステップが両方とも整数なら、ループは整数の制御変数を使って実行されます。このとき終了値が整数である必要はありません。それ以外の場合には、ループは三つの値を浮動小数点数に変換した上で浮動小数点数の制御変数を使って実行されます。

初期化が終わると、Lua は制御変数の値を等差的に変化させながらループの本体を繰り返し実行します。制御変数は初期値からステップの値を等差として変化するので、ステップを負にすれば制御変数の取る値は減少列となります。またステップが 0 のときはエラーが送出されます。ループは制御変数の値が終了値以下である間 (ステップが負なら終了値以上である間) 続きます。もし初期値が最初から終了値より大きかった場合 (ステップが負なら小さかった場合) には、本体は実行されません。

整数のループで制御変数がラップアラウンドすることはありません。オーバーフローが起こるとその時点でループが終了します。

制御変数の値をループで変更すべきではありません。またループの後で制御変数の値が必要な場合には、ループを抜ける前に他の変数に代入してください。

一般的な for ループ

一般的な for 文ではイテレータ (iterator) と呼ばれる関数が使われます。反復ごとにイテレータ関数が呼ばれて新しい値を生成し、生成された値が nil ならそこでループが終了します。一般的な for ループは次の構文をしています:

stat ::= for namelist in explist do block end
namelist ::= Name {‘,’ Name}

次の for 文を考えます:

for var_1, ..., var_n in explist do body end

これは次のように実行されます。

var_i という名前がループ本体にローカルなループ変数を定義します。最初のループ変数 var_1 が制御変数となります。

ループの実行は explist を評価するところから始まり、この評価により次の四つの値が手に入ります: イテレータ関数・状態・制御変数の初期値・終了値です。

そして反復ごとに、Lua はイテレータ関数を状態と制御変数という二つの引数で呼び出します。この呼び出しの返り値は複数代入の規則 (§ 3.3.3) に従ってループ変数 var_1, ..., var_n に代入されます。ここで制御変数 var_1nil となればループはそこで終了し、そうでなければ本体 (body) が実行されてループが次の反復へ進みます。

一般 for ループの終了値はクローズ予約変数 (§ 3.3.8) のように振る舞うので、これを使えばループが終了したときのリソース解放処理を行えます。この点以外で終了値がループに影響することはありません。

制御変数の値をループ中に変更するべきではありません。

3.3.6. 関数呼び出し文

副作用がある関数を呼び出せるように、関数呼び出しは文として実行できます:

stat ::= functioncall

こうした場合の返り値は全て捨てられます。関数呼び出しは § 3.4.10 で説明されます。

3.3.7. ローカル宣言

ローカル変数はブロック中の任意の場所で宣言できます。宣言に初期化を含めることも可能です:

stat ::= local attnamelist [‘=’ explist]
attnamelist ::=  Name attrib {‘,’ Name attrib}

初期化代入が存在する場合には複数代入 (§ 3.3.3) と同じ意味となります。初期化が無ければ全ての変数が nil で初期化されます。

各変数には属性 (attribute) を付与できます。不等号で囲まれた部分が属性の名前です:

attrib ::= [‘<’ Name ‘>’]

付与できる属性は constclose の二つです。const を付けると初期化後に変更できない定数変数が宣言され、close を付けるとクローズ予約変数 (§ 3.3.8) が宣言されます。変数のリストに含められるクローズ予約変数の個数は最大でも一つです。

チャンクはブロックでもある (§ 3.3.2) ので、明示的なブロックに入っていなくてもチャンクの中であればローカル変数を宣言できます。

ローカル変数の可視性に関する規則は § 3.5 で説明されます。

3.3.8. クローズ予約変数

クローズ予約変数 (to-be-closed variable) は定数ローカル変数と同じように振る舞いますが、スコープを外れるときにクローズされる点が異なります。ここでスコープを外れるとは、例えばブロックの最後の文を実行すること、break, goto, return でブロックを抜けること、あるいはエラーで実行が終了することを指します。

変数をクローズするとは、変数の __close メタメソッドを呼び出すことを意味します。このメタメソッドが呼ばれるとき第一引数にはオブジェクト自身が渡され、第二引数にはブロックの実行終了を引き起こしたエラーオブジェクトが (もしあれば) 渡されます。エラーが無ければ第二引数は nil です。

クローズ予約変数に代入される値は __close メタメソッドを持つ値または偽値である必要があります。クローズ予約変数の値が nil または false のときはクローズは行われません。

複数のクローズ予約変数が同じイベントでスコープを外れる場合には、宣言された順番の逆順で変数がクローズされます。

クローズメソッドの実行中にエラーが発生すると、そのエラーは変数が定義された場所にある通常のコードで起きたエラーと同じように処理されます。ただし Lua はその後クローズメソッドをもう一度呼び出す場合があります2

エラーが起きた場合でも呼ばれていないクローズメソッドは呼び出されます。そのクローズメソッドでさらにエラーが起きれば実行が中断されて警告が出ますが、エラーとしては無視されます。報告されるエラーは最初の (クローズメソッドを呼び出すきっかけとなった) エラーだけです。

コルーチンが yield した後 resume されないと、変数の一部はずっとスコープの中に残ることになります (コルーチンの中で作成され、yield した時点でスコープに入っていた変数がそうです)。同様にコルーチンがエラーを出して終了するときはスタックを巻き戻さないので、変数がクローズされません。両方の場合において、ファイナライザあるいは coroutine.close を使えば変数をクローズできます。ただしコルーチンが coroutine.wrap で作成された場合には、coroutine.wrap が返す関数がコルーチンのクローズを行います。


  1. 訳注: この制限は文法の曖昧性を取り除くためにある。https://stackoverflow.com/questions/35060938/ を参照。[return]

  2. 訳注: ソースコードによるとクローズメソッドは基本的にもう一度呼ばれるが、Lua ステートやコルーチンをクローズしているときには呼ばれない。[return]

広告