🎑

NeovimのためのLua入門 Lua基礎編

2020/10/20に公開

はじめに

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文

条件式ではfalsenilは偽、それ以外は真になります。つまり、数値の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    -- 符号反転

関係演算子

否定に!ではなく~を使うくらいで一般的なものです。
これらの演算子は、常にfalsetrueの値を返します。

a == b
a ~= b
a < b
a > b
a <= b
a >= b

比較時に数値と文字列は暗黙的な変換はされません。0 == '0'falseになります。

テーブル、ユーザーデータ、スレッド、関数は参照を比較します。

論理演算子

and, or, notの3種類です。
andは最初の引数がfalsenilならその値を返し、そうでなければ二番目の引数を返します。
orは最初の引数がfalsenilでなければその値を返し、そうでなければ二番目の引数を返します。
どちらも短絡評価され、二番目のオペランドは必要なときだけ評価されます。
notは常にfalsetrueを返します。

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を使って定義します。
reutrnbreakと同様にブロックの最後でしか使えません。

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側の内容に入ります。

参考

脚注
  1. 検索すると出てくるlua-stdlibとは別物です。 ↩︎

Discussion