NeovimのためのLua入門 Lua基礎編
はじめに
NeovimのLua製プラグインが活発なのでLuaを1から学んだことをまとめます。
タイトルにNeovimのためのとありますが、この記事ではまだNeovim関連の所にたどりつけていません。
Neovimのバージョンは0.5(nighty)を使用しています。
Luaとは
プログラムに組み込んで使うことを想定されたスクリプト言語です。
同じスクリプト言語のPython,Rubyと比べて高速に動作するようです。
NeovimではLuaJITが採用されていて、Luaのバージョンは5.1になります。
実行の仕方
NeovimにはLuaJITが組込まれているためNeovim上で実行します。
ExコマンドからLuaを実行できるので、Vim scriptと同じ様に動作確認できます。
Exコマンドで1チャンクずつ実行する場合
ちょっとした確認用に。
:lua a = 1
:lua b = 2
:lua print( a+b )
"3
Luaファイルを実行する場合
ファイルを開いたバッファで:luafile %
を実行します。
お試し期間中はマッピングをしておくと楽かもしれません。
:nnoremap <F5> <cmd>luafile %<CR>
ファイルの拡張子は.luaです。デフォルトでシンタックスハイライトされるので、とりあえずファイルを作っておくと書きやすくなります。
Luaの文法
コメント
1行コメントと複数行コメントがあります。
-- 1行コメント
--[[
複数行
コメント
]]
変数
Luaにはグローバル変数、ローカル変数、テーブルフィールドの3種類の変数があります。
宣言時にlocalと書けばローカル変数になり、それ以外はグローバル変数になります。
テーブルフィールドは後述します。
hoge = 10 -- グローバル変数
local hoge = 9 -- ローカル変数
値と型
Luaは動的言語なので、変数が型を持つのではなく値が型を持ちます。
型はnil, boolean, number, string, function, userdata, thread, tableの8つがあります。
nil型
一般的なnilです。
boolean型
trueとfalseの値を持ちます。
number型
整数と実数は区別されず、実体は実数です。
n = 10
n = 3.5
n = 0xff
string型
'
か"
で囲います。
str = 'クォートは'
str = "どちらでもいい"
クォート内では、\
でエスケープできます。よく使いそうなのはこちら。
-
\n
改行 -
\t
タブ -
\\
\ -
\"
" -
\'
'
エスケープシーケンスが解釈されず改行もそのまま代入できる構文もあります。(javascriptでいうテンプレートリテラル)
括弧のすぐ後ろの改行は無視されます。
str = [[
エスケープのいらない
複数行な
文字列
]]
文字列の連結には..
を使います。数値を渡した場合は文字列に変換されます。
str = 'abc' .. 10 -- 'abc10'
userdata型
C言語と連携するための物なので今回はスルーします。
thread型
実行されているスレッドを表します。非同期処理のための物です。
今回はこれもスルーします。
table型
連想配列です。nilを除く任意の値をキーにできます。
Lua唯一のデータ構造で、配列も辞書もオブジェクトも全部これです。
[]
を使って各要素にアクセスします。シンタックスシュガーとして.
も使えます。
その他の言語と違い、添字は1から始まります。
array = {3, 6, 9}
print( array[1] ) -- 3
table = {
hoge = 'hoge',
huga = 'huga'
}
-- 下2つは等価
table['hoge']
table.hoge
型の変換
Luaは一般的な動的言語の様に数値と文字列を自動的に変換します。変換できない場合はエラーになります。
明示的に変換をする場合は、組込み関数のtostring()
, string.format()
, tonumber()
を使用します。
print( 1 + '1' ) -- 2
print( 1 + '1a' ) -- エラー
代入
多重代入ができます。
a, b, c = 1, 2, 3
-- 変数より値が多いと余分な値は捨てられる
a, b, c = 1, 2, 3, 4, 5
-- 変数が値より少ないとnilで埋められる
a, b, c, d = 1, 2, 3
print(d) -- nil
代入文はすべての式を評価してから代入されます。
i = 3
a = {}
i, a[i] = i+1, 20
print( a[3] ) -- 20
上記はiに4が代入される前にa[i]が評価されるためa[3]に20が代入されます。
その特徴から、値の交換に一時変数は必要ありません。
x, y = 10, 50
x, y = y, x
制御構文
if文
条件式ではfalse
とnil
は偽、それ以外は真になります。つまり、数値の0、空文字列は真です。
if exp then
block
elseif exp then
block
else
block
end
while文
一般的なwhile文です。
条件式が真の間、ループします。
-- 5回繰り返す
i=0
while i<5 do
print (i)
i = i+1
end
repeat-until文
条件式が偽の間ループします
ブロック内部は最低でも1回は実行されます。
-- 5回繰り返す
i=0
repeat
print(i)
i=i+1
until i>=5
for文
for文には数値用と汎用の2種類があります。
ループ変数はどちらもループ内のローカル変数です。
数値用はrange forのような物です。
1つ目の数値から始まり、2つ目の数値に達するまで繰り返します。
3つ目を指定すればその数ずつ進み、指定しなければ1ずつ進みます。
-- 出力は0 2 4 6 8 10
for i=0, 10, 2 do
print(i)
end
汎用はイテレータ関数を使用し、主にテーブルの内容を取り出すことに使われます。
-- 配列の場合
array = {1, 2, 3, 4, 5}
for i, value in ipairs(array) do
print('i:', i, 'value:', value)
end
-- i: 1 value: 1
-- i: 2 value: 2
-- i: 3 value: 3
-- i: 4 value: 4
-- i: 5 value: 5
-- テーブルの場合
table = {
x = 5,
y = 9,
z = 2
}
for key, value in pairs(table) do
print(key, value)
end
-- y 9
-- x 5
-- z 2
break文
repeat, while, forの中ではbreakが使えます。continueはありません。
Luaではブロックの最後でしか使うことができません。
途中で使いたい場合はdo end
を使い、明示的にブロックを作ります。
大抵はif文の最後に出てくるのであまり気にしなくてもよさそうです。
for key, value in pairs(table) do
break -- ここだとエラー
do break end -- 途中で使うにはこうする
if value == 0 then
break -- だいたいここ
end
print(key, value)
break -- ブロックの最後ならOK
end
算術演算子
一般的な記号が使えます。
a + b -- 加算
a - b -- 減算
a * b -- 乗算
a / b -- 除算
a % b -- 剰余
a ^ b -- 累算
-a -- 符号反転
関係演算子
否定に!
ではなく~
を使うくらいで一般的なものです。
これらの演算子は、常にfalse
かtrue
の値を返します。
a == b
a ~= b
a < b
a > b
a <= b
a >= b
比較時に数値と文字列は暗黙的な変換はされません。0 == '0'
はfalse
になります。
テーブル、ユーザーデータ、スレッド、関数は参照を比較します。
論理演算子
and
, or
, not
の3種類です。
and
は最初の引数がfalse
かnil
ならその値を返し、そうでなければ二番目の引数を返します。
or
は最初の引数がfalse
かnil
でなければその値を返し、そうでなければ二番目の引数を返します。
どちらも短絡評価され、二番目のオペランドは必要なときだけ評価されます。
not
は常にfalse
かtrue
を返します。
10 or 20 --> 10
10 or error() --> 10
nil or "a" --> "a"
nil and 10 --> nil
false and error() --> false
false and nil --> false
false or nil --> nil
10 and 20 --> 20
長さ演算子
#
を使います。文字列の長さはバイト列です。
テーブルの場合は途中の値にnil
があると、そこまでの長さになります。
#'abc' -- 3
#'あいうえお' -- 15
#{1,2,3,4,5} -- 5
#{nil, nil} -- 0
優先順位
各演算子の優先順位です。
括弧を使って優先順位を変えることができます。..
と^
は右結合でそれ以外は左結合です。
↑低い
or
and
< > <= >= ~= ==
..
+ -
* / %
not # - (符号反転)
^
↓高い
関数
function
を使って定義します。
reutrn
はbreak
と同様にブロックの最後でしか使えません。
local function f(a, b) -- ローカル関数も定義できる
return a + b
end
関数呼び出しの際に引数を多く渡した場合、余った分は無視されます。
可変個な引数を受け取りたい場合は...
を使います。
アクセスする方法は...
そのものを変数のように扱うというおもしろいものです。
function f(a)
return a
end
print( f(1,2,3) ) -- 1
function f(a, b, ...)
local n = {...}
return n[2]
end
print( f(1,2,5,6,7) ) -- 6
コロンを付ける呼び方
v.name(v, ...)
のシンタックスシュガーとしてv:name(...)
といった構文が用意されています。
自分自身を最初の引数に渡すことでオブジェクト指向っぽいことができ、Neovimではvim.opt
等で使われています。
-- 等価
vim.opt.number:get()
vim.opt.number.get(vim.opt.number)
括弧の省略
Luaでは引数が1つの場合、関数呼び出しの括弧を省略することができます。
-- 1つのテーブル
f({ a = 1 })
f{ a = 1 }
-- 1つの文字列
f([[
vim
]])
f[[
vim
]]
-- 1つの文字列
f("vim")
f"vim"
スコープ
変数のスコープはブロックごとに作られます。
x = 10 -- グローバル変数
do -- 新しいブロック
local x = x -- 新しい変数 x、値は 10
print(x) --> 10
x = x+1
do -- 別のブロック
local x = x+1 -- 別の新しい x
print(x) --> 12
end
print(x) --> 11
end
print(x) --> 10 (グローバル変数)
モジュール
モジュールを利用するにはrequire()
を使います。
Lua 5.1の情報を見ているとmodule()
もセットで出てきますが、5.2で非推奨になり今では使われていません。
それではモジュールの作成はどうするのかというと、ファイルの最後でテーブルを返す方法が一般的なようです。
-- hoge.lua
local M = {}
M.func = function()
return "Hi!"
end
return M
-- 利用側.lua
local hoge = require( "hoge" )
hoge.func() -- Hi!
標準ライブラリ
標準ライブラリはCで書かれているので、基本的には標準ライブラリにあるものを使ったほうが良いです。
数が多いのでリファレンスマニュアルを参照してください。
Neovimの標準ライブラリ
Luaの標準ライブラリは上記の通りですが、NeovimのLua用の標準ライブラリもあります(:h lua-stblib
)[1]。
vim
モジュールとして用意されています。
常に有効になっているためrequire("vim")
と明示的に読み込む必要はありません。
:lua print( vim.tbl_count({ 1,2 }) )
"2
Lua自体の便利関数だけでなく、LuaからNeovimの機能を使用できるAPIも含まれています。
Vim scriptと相互にデータのやり取りもできます。夢が広がりますね。
おわりに
これでLuaをなんとなく書けるようになったと思います。
次回はNeovim側の内容に入ります。
参考
- Lua 5.1 Reference Manual(日本語訳)
- Beginning Lua
- Lua 言語仕様:可変長引数
- Lua のモジュール
- Lua .(ドット)による関数呼び出しと:(コロン)による関数呼び出し
-
検索すると出てくるlua-stdlibとは別物です。 ↩︎
Discussion