コンテンツにスキップ

利用者:Nami-ja/Help:Lua

このヘルプページはウィキペディアに実装されているLua (mw:Extension:Scribunto英語)) を用いてモジュール名前空間にLuaソースを書くためのヘルプ文書です。なお、この文書は現時点では「個人的な備忘録のために作成された私論」の範囲を出るものではありません。内容は多くの誤りを含む可能性があります。

Luaは10年以上前からインターネット上で公開されている言語ですが、MediaWiki上の運用については2016年4月現在日本語文献はほぼ皆無と言っても良い現状であり、ウィキペディア英語版の初心者向け英語解説ページを聖典のように扱う日々がしばらく続くと思われます。

ウィキペディア上に置くモジュールはMediawikiテンプレートと同様に「自分以外の他人がいじくる前提」で作成する必要があるため、ある程度凝ったソースを入れるのならばそれなりに別の誰かがソースを見ても理解できるようにコメントアウトした説明文章を入れておく必要性が高いです。自分がウィキペディアにあと何年関われるか、5年後、20年後、50年、100年と時間が経過した後も自分の作ったソースがウィキペディア上に残ることを大前提に考えて書いて下さい。

極度に効率を突き詰めて練りに練った究極のソースよりも、誰でもがいじりやすい、仕様変更が容易なソースの方が多くの編集者が関わる合同プロジェクトであるウィキペディアにとっては有用です。究極ソースが必要になったならば、そのような合意を経て複数の編集者が関わって作り上げることでしょう。

このヘルプ文書の問題点

この文書はC#PHPjavascriptのどれかを自分でオリジナルのソースが書ける程度には習得しており、またMediaWikiテンプレートの作成にかなり手馴れている前提で多くの事柄が書かれているので、完全な初心者向けではないかもしれません(2016年8月現在)。

分からない事柄や単語にぶつかったら適時ググッてみることもあらかじめお勧めしておきます。

モジュール作成可能の目安

[編集]

2016年8月現在、ウィキペディア日本語版のLuaモジュールは「MediaWikiテンプレートの上位にある特別なもの」という位置づけが為されており、「MediaWikiテンプレート単体で実行した方が(動作が)高速なもの」、「Luaモジュールを使用することで逆に動作が遅くなるもの」については作成が認められていません。もしこのようなモジュールを作成した場合はWP:DEL#Z、有用性の低いモジュールとして削除依頼が提出される可能性があります。

なお、MediaWikiテンプレートとLuaモジュールの作成難易度を単純に比較した場合、MediaWikiテンプレート特有の動作を理解した上でテンプレートを動かすための特別な知識が求められるテンプレートよりも既存のプログラム言語と大幅に互換性があり、構文構築に際してテンプレートとは比較にならないほど自由にソースを書けるLuaモジュールの方が作成難易度は低いほか、MediaWikiテンプレートに関する深い知識を必要としないため長期的観点から見るとこの目安は年月を経て現在とは違った形になる可能性があります(私論)。

このほか、ページ内でのテンプレートの反復使用回数がテンプレート制限値に触れる、触れやすい使われ方をしているなどの上記以外の特別な理由がある場合({{Cite web}}⇔{{Cite web2}}(モジュール版))や、仕様変更に際してモジュール化することにより仕様変更に係る労力をより簡便化する({{Twitter}}⇔モジュール:Twitter)などの理由でも合意が得られればモジュール化が為される場合があります。

Luaの特徴

[編集]
利点
  1. Luaは高速性に優れた言語で、通常のMediawikiテンプレート単体では処理が複雑すぎたり、複数回のループや判定が必要で重くなりがちなテンプレートソース処理の一部または全部をモジュール化することで簡略化、高速化する利点を持ちます。
  2. 改行やタブ文字などのソースを見やすくするための文字を表示文言とは別に自由に配置できるので、ソースの見通しが良くなり仕様変更が行い易くなる利点があります。
  3. MediaWikiテンプレートでは有限かつ非常に処理を遅くするループ構文を無限引数化、軽量化することが出来ます。
  4. 結果を実行させるために書くソース構文がある程度他プログラム言語と互換性を持つことから、MediaWiki特有の(プログラム言語として考えると多くの欠陥と難点を持つ)テンプレート構文を勉強する必要性が低く、編集者が全てボランティアであり編集可能時間がそれぞれの個人的事情で有限であるマンパワーリソースの観点で考えるとテンプレートとモジュールで同じ結果を得る場合、モジュールでソースを書いた方が必要リソースを節約可能な側面を持ちます。
難点
  1. 必ずテンプレート名前空間⇒モジュール名前空間の順で呼び出されて実行されること、元々MediaWiki上で動かすために最適化されているMediaWikiテンプレート構文と違い「外部Exitensionを利用して無理やり動かしている」関係上、テンプレート単体で動かした方が簡単なソースだった場合は逆に処理が煩雑化・複雑化し重く、遅くなる欠点を持ちます。
  2. やろうと思えば作者の知識範囲内でいくらでもソース短縮化、複雑化出来てしまうことから、Mediawikiテンプレートの比でないレベルのプロ職人しか触れない「芸術レベル」まで凝ったソースを書くことが可能で、誰もが仕様変更出来なくなってしまう欠点もあります。
  3. 元々MediaWiki上で動くように最適化されているMediaWikiテンプレート構文と違い、Extensionモジュールにより擬似的にプログラムとして走らせている関係上、MediaWiki上で最適に動くように設計されているMediaWiki構文に対して最適動作をさせづらい、解消しがたい構造欠陥を持ちます。
数値変数、文字列変数、配列が存在しない

全ての変数は一個のテーブルとして扱われるため、数値変換関数のtonumber()、文字列変換関数のtostring()またはmw.allToString()、配列はテーブルを利用して擬似配列として扱います。テーブルを作る要素は{}で括ります。また、Luaの配列テーブルは必ず 1 から開始されます。単独の文字列または数値変数であっても、Luaは1個のテーブル(t = { hoge })として認識しています。

ループ構文、判定文の連続は処理速度が落ちる

処理が複雑でないモジュールではあまり神経質になる必要はないのですが、for文、if文を大量に使用し複数回数の連続ループ判定文のような似たような処理の連続は、処理を連続しない内容のものと比較すると確実に負荷が増え、処理速度が落ちます。

これを回避するために、例えば受け取った引数の1つ1つが空か判定するためにif args[i] == ''などというループ判定文を繰り返すのではなく、単にt = ( { args[1], args[2] } )として内容を全て合体し処理をまとめるなどいろいろ知恵を絞って工夫するとソースの見通しが良くなり、処理の軽量化が図れます。

書き方

[編集]

変数

[編集]

Luaの変数には数値変数、文字列変数の区別がなく、システム的に予約されている特定文字列を除けば何でも自由に格納することができます。ただし、変数名の文頭1文字目は必ず半角英数字でなければなりません。

変数は常に1つの配列テーブルとして扱われており、配列は必ず1から開始されます。なので単純に

local t = '123'

と変数tを設定した場合でも、Lua的にはt = ['123']という、配列番号1つのテーブルとして認識しています。

配列名には半角英数字以外にも2バイト文字を用いることが出来ます。また、空の変数テーブルを作成する際には単にt = {}として空の配列を宣言します。

配列例1配列例2
local t = {}
t[1] = 1
t[2] = 2
t[case] = case
t['ほげ'] = hoge
local t = {
	1 = 1,
	2 = 2,
	case = case,
	['ほげ'] = hoge
}
格納文字列の変換

格納する数値または文字列については相互に変換することが出来、数値変換にはtonumber()を、文字列変換にはtostring()を用います。

このほか、大文字化にstring.upper()、小文字化にstring.lower()が使用できます。

変数に使用できない単語

以下はシステム的に予約されており、変数として使用することが出来ません。

and 	elseif 	if 	not 	true 	break 	end 	in 	or 	until
do 	false 	local 	repeat 	while 	else 	for 	nil 	return 	function 	then

文字列操作、文末コロン、コメントアウト

[編集]
文字列を変数に入れる
文字列を記述する際は「''」(シングルクォーテーション2つ)または「""」(ダブルクォーテーション2つ)で括ります。スタイルシート前後の「""」の文字列とかぶるとめんどくさいことになるので、「''」の方を推奨しておきます。
変数を繋ぐ、くっつける(連結)
変数の内容を繋ぐには連結演算子「..」(ドット2つ)を変数の前後に置いてくっつけます。
t = '123' ..'456' ..'789'

このtの中身は「123456789」となります。

文末コロン「;」
C#、PHPなどでお馴染みの「;」ですが、Luaでもこれはチャンクと呼ばれる行の要素終了を表す記号として扱われています。が。特に注記して文末に置かなくてもエラーを吐きませんので、全て省略可能です。なお、あってもなくてもモジュールの実行に何の影響も及ぼしませんので、これを除去するためだけにモジュールを編集する必要性もありません。
コメントアウト
行頭を--で開始するとその行はコメントアウト行となり実行されません。複数行に跨る場合は--[[でコメントアウト開始、]]でコメントアウト終了を表します。
困ったことに
--[[ コメントアウト開始
t = '<td style="text-align: right">[[ほげほげ]]';
]] -- コメントアウト終了しようとしている行
と書くと、末尾の]]ではなくほげほげ]]のMediawiki記法部分に反応してそこでコメントアウトが終了してしまい、文末]]を生の命令文と解釈してエラーを吐いてしまいます。
そのため、このような局面では
--[=[ コメントアウト開始
t = '<td style="text-align: right">[[ほげほげ]]';
]=] -- コメントアウト終了しようとしている行
のように、コメントアウト記号の--[[]]の間に=を挟んでコメントアウトを成立させます。なお、=の数はいくつ挟んでも良いので例えば
--[======[ コメントアウト開始
--[=[ ここはコメントアウトになります ]=]
]======] -- コメントアウト終了しようとしている行
という風にコメントアウト文字列を含む行もまとめてコメントアウト(コメントアウトの入れ子構造)することができます。

演算子

[編集]

Luaの演算子はだいたい他の言語と同じです。ノットイコール(不等演算子)が違う程度?

算術演算子
A + B 加算(A + B)
A - B 減算(A - B)
- A 正負反転(-A)
A * B 乗算(A × B)
A / B 除算(A ÷ B)
A ^ B 累乗(AB
A % B 剰余(AをBで割った余り)
比較演算子
A == B A は B と 等しい(A = B)
A ~= B A は B と 異なる(A ≠ B)
A > B A は B より多い(A > B)
A < B A は B より少ない(A < B)
A >= B A は B より多いか等しい(A ≧ B)
A <= B A は B より少ないか等しい(A ≦ B)
論理演算子
A and B A および B
A or B A または B
not A A でない
連結演算子
A .. B A と B を 連結する

繰り返す(ループ)

[編集]

ループ命令にはfor(foreach), while, repeat が使用可能です。

for

[編集]
local p = {}
local t = '' -- 最終返り値に利用するためのローカル変数

function p.main(frame)
	for i = 1, 3, 1 do -- iを1から開始、20回目までループ、1ずつ加算
		t = t ..i ..'回目<br />' -- ローカル変数 t にループ回数数値 + 文字列をくっつける
	end
	return t -- ループ中に実行された変数 t の最終結果を返す
end
return p

この例では表示は

1回目
2回目
3回目

…となりますが、実際の変数 t の内容は1回目<br />2回目<br />3回目<br />となっています。改行コードなどもエスケープシーケンスとして代入出来ますが、実行結果ソースを見ることはほとんどないのであまり必要とされません。

また、ループ回数はテンプレートから受け取った引数を全て含めたargsを渡す(頭に#をつけると内包テーブル数を数値変換)ことでも指定することが出来ます。

for i = 1, #args do -- iを1から開始、argsを数値変換してその回数分ループ
	t = t ..i ..'回目<br />'
end

また、forreach構文も使用出来ます。

for key, val in pairs(args) do -- forreach文に相当
	t = t ..key ..'回目<br />'
end

ちなみにpairs()ipairs()は意味合いがちょっと違います。

while

[編集]

whileは条件がTrueの間ずっとループします。条件設定間違えてうっかり無限ループをやらかしたことがある人は素直に挙手。

while  i <= 3 -- iが10より小さい間ずっとループ
	t = t ..i ..'回目<br />' -- ローカル変数 t にループ回数数値 + 文字列をくっつける
	i = i + 1; -- 条件判定用の変数 i を 1 ずつ加算する
end

repeat

[編集]

repeat文はループの終端にあるuntil文で指定した式の値がfalseの間だけループされます。

repeat
	a = a + 1;
until i == z;

break

[編集]

ループ中に処理を抜けるための文としてbreak文があります。使用に際して注意するのは、break文はブロックの終端にしか書けないため、if文と組み合わせるなどして

if a == b then
	break
end

のように必ずbreakの後にendを置かなければエラーが出ます。強制的にその場所でループを終了させたい場合は

do
	break
end

のように書きます。

疑似Switch

[編集]

LuaにはSwitch構文が存在しないので、簡単な判定であればif else thenを重ねることになります。ですが、お馴染みのテーブルを用いてソースをスッキリさせることもできます。

以下はそれぞれ全く同じように動作します。

MediaWiki TemplateLua (if文)Lua (テーブル)
{{switch:{{{case}}}
	|case1=hoge
	|case2=ほげほげ
	|ほげ=hogehoge
	|#default=Example
}}
if arg.case == 'case1' then
	return 'hoge'
elseif arg.case == 'case2' then
	return 'ほげほげ'
elseif arg.case == 'ほげ' then
	return 'hogehoge'
else
	return 'example'
end
local switch = ({
	case1 = 'hoge',
	case2 = 'ほげほげ',
	['ほげ'] = 'hogehoge',
})[case]

if switch then
	return switch
else
	return 'example'
end

個人的な所感ですが、if文など判定文を重ねると、負荷がけっこー重くなるっぽいので避けた方がいい気がしてます。ソースもどんどん読みづらくなってくし、何よりうつくしくない。

Luaモジュール内部でのMediaWiki関数の使用

[編集]

Luaモジュール内部でテンプレートを使用しようとするとき、モジュール内のテキストに{{テンプレート}}を書いてもテンプレートとして実行されずそのまま単なる文字列として出力されてしまいます。Luaモジュール内部でのテンプレート、タグその他メディアウィキのマジックワード機能を使用したいときは下記を参考とされて下さい(英語)

動作が遅くなることを厭わないのならば、frame:preprocess('{{実行したいテンプレートやマジックワード}}')を使うことでパーサー関数をそのまま実行することができます。

実行テスト

[編集]

Luaモジュールは単体では何も動作しない、プログラムを直書きしたモジュール名前空間の1ページとしてMediaWiki上に置かれます。これを呼び出す際はモジュール以外の名前空間上に

  • {{#invoke:モジュール名 | ファンクション名(あれば) | 引数(あれば)}}

と記述することで、Luaモジュールに書かれたソースを実行することが出来ます。

例を挙げると、例えばモジュール:HelloWorldを呼び出す際は

  • {{#invoke:HelloWorld|hello}}

と記述してやることで

  • Hello World!

という実行結果を得ることが出来ます。どうです、そろそろHello Worldに親しみを感じて来たでしょう?

自分でソースを書いてみる

[編集]

Luaモジュールはモジュール名前空間以外では動作しませんので、モジュールのテスト専用ページとしてモジュール:サンドボックスが用意されています。モジュール:サンドボックスに直接書くのではなく、モジュール:サンドボックスの更に下位に自分の名前を使ったサブページを置いて使用して下さい(モジュール:サンドボックス/貴方の利用者名)。

モジュール:サンドボックス/以下のサブページ一覧

モジュール名前空間ではページを作成すると必ず/docのドキュメント空間がページ上に呼び出されるようになっており、ここにモジュール呼び出し・実行文をあらかじめ書いて置くことで、モジュールの変更と実行結果を同じページ上で一度に見ることが出来ます。

エラー表示

[編集]

もしモジュールソースの書き方が誤っていた場合、保存ボタンを押しても保存されず、ページ上部に赤文字でエラーメッセージが表示されます。親切なんだか不親切なんだか分からない仕様です。ここはぐっと堪えてエラーメッセージから原因を探って下さい。ごく簡単にはエラーメッセージをググってみるのがお勧めですが、まぁ大抵ググっても(一般的なLuaとMediaWiki上のLuaモジュールでは挙動に多少違いがあることから)さっぱり原因特定に至らないことが多く理由がよくわかりませんのでリファレンスマニュアルの英語と格闘して下さい。

まぁたぶん、よく見るであろう「Nilなんちゃらうんぬんかんぬん」は大抵「○○テーブルが定義されてないので戻り値がNil(空っぽ)だよ」というエラーです。

デバッグ

[編集]

Luaモジュールもテンプレートと同様にいったん保存し実行を経て動作確認をする方法もありますが、別途テンプレートから呼び出すために双方を逐次書き換えていては履歴が莫大な数になり変更履歴が追いづらくなります。

ですので、プレビュー中に保存ボタンの下に表示されるデバッグコンソールにある程度簡単な引数を与えることでプレビュー時点でのソースを簡易実行する機能が備わっています。デバッグコンソールでは専用の命令文であるp.log()を使用して結果を表示させます。例えば下記のソース、

local p = {}

function p.main( frame )
    return "Hello world" --
end

return p

これをデバッグコンソールで確認したい場合はデバッグコンソールにp.log(p.main())と入力しエンターするとfunction p.mainの内容が表示されます。少し複雑にして、p.log()のかっこ内で変数を与える処理をして実行することもできます。これは保存毎に消去されてしまいますので、別にテキストエディタを使用すると良いでしょう。

ウィキペディアプロジェクトのひとつとして公式のモジュールテスト専用サイト(バグチェック・報告専用)である https://test2.wikipedia.org/wiki/Main_Page が存在しています(英語)

https://test.wikipedia.org/wiki/Main_Page (英語)も参照

このほか、個人でレンタルサーバーやローカルPC内でMediaWiki + MySQL + Extension:Scribunto (Lua) + Extension:CodeEditor の環境を作り擬似的にテスト環境を作る方法もあります。

出力

[編集]

先ほどのモジュール:HelloWorldのページ内容を書き出すと以下のように記述されています。

--」から始まる文字は実行されないコメントを表しています。

local p = {} -- 最初に設定するテーブルブロックです。

function p.main( frame ) -- function関数で設定するブロックです。
    return "Hello world" -- これがこのmain (frame )関数の実行結果です。
end -- p.main関数の終了を示します。

return p -- このreturnでテーブルp(内部関数を内包)の内容を全て戻すことになります。
解説
  • local p = {}
ローカル関数 pテーブルを初期化。{ ... }{ a, b, c }などの予約も出来ます。
{}で囲うと配列テーブル変数となり、内容はp[1]p[2]などの数値配列または文字配列テーブルが作られます。
local p = ''」と書くと単独の文字列変数(空文字列代入)となります。これらのテーブルまたは文字列変数宣言を怠って式などで使用するとNilエラーが発生します。
先頭のlocalを除去して単にp = と書くとグローバル変数となり、複数のブロックで共通変数として利用出来ます。
  • function p.main( frame )
function宣言から始まり、末文endで終了する範囲内は関数ブロックとして扱われます。
このソースではfunctionのpテーブルにくっつけるmain関数ブロック(p.main (frame) )として書かれていますので、最初に宣言したpテーブルのp[1]の中に入っていることになります。
  • return "Hello world"
main関数ブロック内で実施した戻し文になります。MediaWikiのLuaではprint('文字列')が使えないので、代わりにreturn文を使用して代用しています。文字列のほか、変数も返り値として使えます。
  • return p
内包するテーブル(関数ブロック)で宣言されたreturn文で指定されている内容などを全て呼び出し元へ返します。

応用例

[編集]

モジュール内で別のモジュールを呼び出す

[編集]

既に存在している別のモジュールを呼び出すことが出来ます。呼び出す際には「require」を使います。

function p.main(frame)
	local args = require('Module:HelloWorld')
end

1つのモジュール内に複数の関数ブロックを置く

[編集]
local p = {}

function p.main( frame )
    return "Hello world"
end

function p.super( frame )
    return "Hello world super"
end

function p.special( frame )
    return "Hello world special"
end

return p

このようなソースをテンプレートから呼び出し関数を指定して呼び出した場合、例えば{{#invoke:HelloWorld|super}}とすると「Hello world super」を、{{#invoke:HelloWorld|special}}とすると「Hello world special」を表示します。

このように、1つのモジュール内に複数の実行結果を記述することも出来ます。また、Functionから始まる関数ブロックは他のfunctionブロックに干渉しません。他の関数ブロックにも同じ変数を利用したい場合はグローバル変数を使ったりなんだりしてごそごそ頑張ります(作者が)。

このような構造は、「微妙に差異のある複数のテンプレートの動作本体を単一のモジュールとしてまとめる」際に役に立ちます。例としては、既存の3つのテンプレート、{{Main}}、{{See}}、{{See also}}を単一モジュールにまとめたモジュール:Mainがあります。

引数を渡す

[編集]

MeidaWikiテンプレートを介してLuaモジュールへ引数を渡すことが出来ます。

例1 変数名なし
  • テンプレート側の記述

{{#invoke:ほげほげ|main|ほげほげa|ほげほげb|ほにゃらら}}

  • Lua側の記述
local p = {}

function p.main( frame )
  -- 引数を受け取る
  local hoge_a = frame.args[1];
  local hoge_b = frame.args[2];
  local honya = frame.args[3];

  -- 受け取った引数を繋げて投げ戻す
  return 'うひゃっほう' ..hoge_a ..'&nbsp;-&nbsp;' ..hoge_b ..'&nbsp;-&nbsp;' ..honya ..'<br />'
end

return p
結果

うひゃっほうほげほげa - ほげほげb - ほにゃらら

例2 変数名付き
  • テンプレート側の記述

{{#invoke:ほげほげ|main|a=ほげほげaaa|b=ほげほげbbb|h=ほにゃらら}}

  • Lua側の記述
local p = {}

function p.main( frame )
  -- 引数を受け取る
  local hoge_a = frame.args.a;
  local hoge_b = frame.args.b;
  local honya = frame.args.h;

  -- 受け取った引数を繋げて投げ戻す
  return 'もけけけ' ..hoge_a ..'&nbsp;-&nbsp;' ..hoge_b ..'&nbsp;-&nbsp;' ..honya ..'<br />'
end

return p
結果

もけけけほげほげaaa - ほげほげbbb - ほにゃらら

1バイト文字を扱う場合のみframe.args.??の書き方が使えます。2バイト文字(日本語ほか)変数を扱う場合はframe.args['名称']になります。

現時点でのLuaモジュールの問題点

[編集]

MediaWikiではExtension:Scribuntoを通してLuaの高速かつ軽快な動作特徴をテンプレートに応用し利点を甘受してサーバーの負荷を軽減しつつテンプレート動作を軽快にすることが出来ますが、MediaWiki上で動作させるにあたっていくつかの機能が実装されていません。

代表的なものとしてはC言語を勉強したなら最初にお目にかかった命令文であろう「Hello World!」を表示させるために使用したPrint()のほか、Unicode文字の互換性に起因するいくつかの機能が使用できません。これらはmw:Extension:Scribunto/Lua_reference_manual#Differences_from_standard_Lua(英語)にまとめられています。

関連項目

[編集]
よく利用する汎用モジュール

モジュールの中で別のモジュールを呼び出すことができます。ただし、呼び出すモジュールが高負荷だった場合、呼び出し元のモジュールも呼び出したモジュールの動作負荷の影響を受けて重くなります。

カテゴリ

一応モジュールページにもカテゴリが用意されていますが、モジュールページ単体にカテゴリを書いてもエラーを吐くこと、あまりモジュールドキュメントが作成されないこと(モジュール本体中にコメントアウト説明文章を直接書く手法が主流)が原因でカテゴリに分類されているモジュールはごく最近作られた比較的新しいものばかりになっており、大多数のモジュールはカテゴリ分類されていません。

参考文献

[編集]

外部リンク

[編集]