luaを学びなおしたので、そのまとめ

更新日:2020年12月28日

はじめに

かなり昔にluaを使おうと思い、調べたことをこちらのページにメモしながら、色々やってみました。
しかしそれは一時的なもので、長い間放置していました。
なので、再度整理をしようということで、このページを作成しています。
luaのバージョン5.4.2を対象とします。

目標としては
・luaで基本的なスクリプトをかけるようになること
・VisualSutdioで作成しているC++のプログラムと連携できるようになること
とします。

luaには、標準でいくらかの関数が提供されています。
詳細はhttps://www.lua.org/manual/5.4/contents.html#contentsを参照

ソースコードの文字コードはUTF-8で記載することとします。
Windowsのコマンドプロンプトでluaのインタープリタを実行した場合、コマンドプロンプトの文字コードがSJISなので文字が化けます。
そのため、以下のようなコードを書いたバッチを用意し、luaインタープリタを実行する前に実行しておくと便利です。
最初の1行目でluaのインタープリタのexeを保存しているディレクトリに、パスを通します(「D:\app\lua\5.4.2」は私の環境でのluaインタープリタ保存先)。
2行目でコマンドプロンプトの文字コードをUTF-8に変更しています。

	set PATH=%PATH%;D:\app\lua\5.4.2
	chcp 65001
	

環境構築

公式サイト(https://www.lua.org/)のdownloadからソースをダウンロードします。
これを書いている時点での最新版は5.4.2です。

linuxなどの環境向けにMakefileがありますが、windows環境で、かつVisualStudioでビルドしたいので、ひと手間必要です。
ビルドのやり方はソースを解凍した中のdoc/readme.htmlの中の「Building Lua on other systems」に書いてあります。
なので、これを参考にしつつ、DLLとコンパイラとインタプリタを作成します。

VisualStudioでビルドを行う

以前は、VisualStudio用のプロジェクトが付属していたのですが、それがなくなりました。
なので、自力でプロジェクトを作ります。

以下の方法は1例です。
私はこういう風にやったよ・・・というだけです。
作成するものとしては
  ・luaコンパイラ(ソースコードからバイトコードに変換する)
  ・luaインタプリタ(ソースコード、もしくはバイトコードを実行する)
  ・DLL(VisualStudioのC++プロジェクトから使用する用)
です。

以下に手順を記載します。
なお、ターゲットはx86です(x64は試したことがありません)。
そして、試した環境としてはVisualStudio2019です。

(1)VisualStudioでプロジェクトを新規作成する

VisualStudioを起動し、新規にプロジェクトを作成します。
最初に作るプロジェクトは「共有アイテム プロジェクト」を指定し、名前を「luaCommon」とします(ここを最終的な成果物の出力先とします)。

さらにプロジェクトを作成します(プロジェクトを作るときは「空のプロジェクト」を指定し、作成後に、プロジェクトのプロパティの「構成の種類」から以下の表で示すものに変更します)。
作成するものを以下の表にまとめました。

作成するプロジェクト 構成の種類 説明
luaCommon 共有アイテムプロジェクト プロジェクト作成時に作成したもの。これは、「共有アイテム」という特殊なプロジェクトとなる。
lua アプリケーション(.exe) luaインタープリタを作成するプロジェクト。
luac アプリケーション(.exe) luaコンパイラを作成するプロジェクト。
luaLib ダイナミックライブラリ(.dll) luaのダイナミックリンクライブラリを作成するプロジェクト。
こちらは、自分で作成するC++アプリとluaを連携させるときに使用します。
luaLibStatic スタティックライブラリ(.lib) luaのスタティックリンクライブラリを作成するプロジェクト。
「lua」「luac」が参照するライブライです(作成されたものを自分で使用することもできますが、今回は使用しません。)。

(2)プロジェクトにソースへのリンクを追加

(1)で作成したプロジェクトにソースを追加します。
VisualStudioは賢い(?)ので、プロジェクトにファイルをドラッグ&ドロップすると、そのファイルへのリンクをプロジェクト内に作ってくれます。

なので、ダウンロードしてきたLuaのソースを、以下のように各プロジェクトにドラッグ&ドロップします。

プロジェクト ドラッグ&ドロップするソースファイル
lua lua.c
luac luac.c
luaLib 「lua.c」「luac.c」以外全部の「*.c」ファイル
luaLibStatic 「lua.c」「luac.c」以外全部の「*.c」ファイル

全部ドラッグ&ドロップすると、以下のような感じになります。

(3)プロジェクトの設定を行う1

プロジェクトの依存関係を設定します(ソリューションを右クリックして「プロパティ」を開くと、共通のプロパティという項目があるので、そこからどのプロジェクトがどのプロジェクトに依存するかを設定する。ビルド順序に影響がある・・・はず。)。
「lua」プロジェクトの依存先を「luaLibStatic」のチェックをONにする。
「luac」プロジェクトの依存先を「luaLibStatic」のチェックをONにする。

次に、参照するlibを設定する。
「lua」プロジェクトと「luac」プロジェクトに対し、以下の設定を行う。
・「VC++のディレクトリ」の「ライブラリディレクトリ」に「$(ProjectDir)..\luaCommon\Debug\」を追加
 (※「luaLib」や「luaLibStatic」の成果物(libやDLL)は「luaCommon」プロジェクトの中に出力されるため)
 「構成」がReleaseの場合、「$(ProjectDir)..\luaCommon\Release\」にするのを忘れないでください(Releaseなのにデバッグ版のlibをリンクしてしまうため)。
  
・リンカーの「追加の依存ファイル」に「luaStaticLib.lib」を追加
  

(4)プロジェクトの設定を行う2

「luaLib」プロジェクトの「プリプロセッサの定義」に「LUA_BUILD_AS_DLL」を追記します。
DLLを作るためには、この定義を追加する必要があります(なので、DLLを作成するプロジェクトである「luaLib」のみ設定すればOK)。

(5)ビルドを実行する

上記の手順を実行し、ビルドを実行すると、「luaCommon」プロジェクトの中(Debugでビルドしたのならば、「Debug」ディレクトリ、Releaseでビルドしたのならば「Release」ディレクトリの中)に以下のファイルが出力されます。


必要なものは、赤枠で囲ったもののみです(上記のキャプチャは、Debugでビルドした結果)。
「luaLibStatic.lib」(luaのスタティックリンクライブラリ)は、lua.exeやluac.exeを作るときに使用されていますが、今回のluaの勉強では使いません。

(6)VisualStudioで開発するときについて

luaのソースから、ヘッダファイルだけ全部コピーし、、どこか適当なディレクトリにコピーしておきます。
そして、VisualStudioで開発するときは、プロジェクトのそのディレクトリへのパスを通して、参照できるようにします。
libについては、どこか適当なところにおいて、これもパスを通して、プロジェクトから見えるようにしたあと、「リンカー」の「入力」の中にある「追加の依存ファイル」に追加します。
DLLは、開発プロジェクトの「Debug」ディレクトリの中にコピーしてほおりこんでおけばよいと思います。

ソースコードの文字コードについて

UTF-8で記載するのが無難です。
SJISにすると、いわゆる「ダメ文字」問題が発生します。
文字列を「[[」「]]」で囲むなど回避方法があるものの、UTF-8でソースを記載するのが無難です。
なので、本ページでもソースコードの文字コードは全部UTF-8です。

コメント文の書き方について

いくつかコメントの記載方法があります。
いくつかサンプルを記載します。


--コメントはこのように記載します。

--[[
	これで複数の行のコメントが可能です。
--]]

--![[
	print("「!」を入れるとコメント化が解除されます")
--]]

--[==[
	これもコメント化の方法の一部です。
	「==」を入れ込むと、コメントを含むすべてのコードをコメントアウトできます。
	「==」を「===」に増やしてネストさせることもできますが、使用することは稀と思われるので省略します。
--]==]

実行結果

「!」を入れるとコメント化が解除されます

if, while, repeat, forの書き方

ローカル変数として宣言したい場合「local xxxx」と記載します。
型については、特に記載しません(型の概念が緩いので)。
localを記載しないと、全部グローバル変数になりますので、注意が必要です。

ソースではif/while/repeat/forのサンプルを記載しました。


-- ifの書き方
print("■ifの書き方")

local numValue = 1000
if numValue == 200 then
	print("numValueは200です")
elseif numValue == 300 then
	print("numValueは300です")
elseif numValue == 400 then
	print("numValueは300です")
else
	print("numValueは"..numValue)	-- luaでは「,,」を使うと文字列を連結できる
end

if numValue ~= 200 then
	print("numValueは200ではありません(luaでは「!=」を「~=」と記載します)")
end


-- whileの書き方
print("■whileの書き方")

local whileCounter = 5
while whileCounter > 0 do
	print("whileループのカウンター:"..whileCounter)
	whileCounter = whileCounter - 1
end


-- repeatの書き方
print("■repeatの書き方")

local repeatCounter = 5
repeat
	print("repeatループのカウンター:"..repeatCounter)
	repeatCounter = repeatCounter - 1
until repeatCounter < 0


-- forの書き方(1)
print("■forの書き方(1)")
--初期値0で、1回のループで+2し、6になるまでループする・・・という記載
for forCounter=0,6,2 do
	print("(1)forループのカウンター:"..forCounter)
end

-- forCounterはfor文の中でのみ有効となる。
-- 以下を実行しようとしても、forCounterは新たに定義された変数と同じ扱いとなりnil値となる。
-- そのため実行するとエラーが発生する(nil値を文字列連結しようとするとエラーになる)
--print("forCounter:"..forCounter)


-- forの書き方(2)
print("■forの書き方(2)")
-- 初期値0で、1回のループ毎にforCounterをインクリメントし、5になるまでループする・・・という記載
for forCounter=0,5 do
	print("(2)forループのカウンター:"..forCounter)
end

実行結果

■ifの書き方
numValueは1000
numValueは200ではありません(luaでは「!=」を「~=」と記載します)
■whileの書き方
whileループのカウンター:5
whileループのカウンター:4
whileループのカウンター:3
whileループのカウンター:2
whileループのカウンター:1
■repeatの書き方
repeatループのカウンター:5
repeatループのカウンター:4
repeatループのカウンター:3
repeatループのカウンター:2
repeatループのカウンター:1
repeatループのカウンター:0
■forの書き方(1)
(1)forループのカウンター:0
(1)forループのカウンター:2
(1)forループのカウンター:4
(1)forループのカウンター:6
■forの書き方(2)
(2)forループのカウンター:0
(2)forループのカウンター:1
(2)forループのカウンター:2
(2)forループのカウンター:3
(2)forループのカウンター:4
(2)forループのカウンター:5

型について(nil型、文字列型、数値型、ブーリアン)

luaでは変数に対し型を定義しません。
設定する値で自動で決まります(また演算子によって、勝手に文字列型から数値に変換してくれたり・・・などする)
デフォルトでは整数や浮動小数点は64bit型となります(※luaのコンパイル時の設定で32bitに変更することも可能)。

また変数のスコープですが、localとつけるとローカル変数、つけない場合はグローバル変数となります。
未定義の変数名を使った場合、エラーになりません。
ミスタイプで変数名を誤ると、新しい変数が生まれ、nil値がセットされるので、バグの温床になります(どうすればこれを防げる?)。


-- nil型とは、NULLのようなもので、唯一「nil」という値のみ存在する。
-- 値がまだ何もセットされていない・・・ということを示すのに使う
local notValueSet = nil
if notValueSet == nil then
	print("notValueSet is nil")
else
	print("notValueSet is not nil")
end


-- 文字列型
local msg = "あいうえお"
print("msg : "..msg)


-- 整数(64ビットsinged)
local num1 = 1000
-- 16進数で記載
local num2 = 0x7fffffffffffffff
-- 浮動小数点(倍精度浮動小数点数(64ビット))
local num3 = 100.00000000001
-- boolean型(セットする値としては、falseもしくはnilの場合はfalse、それ以外はすべてtrueという扱いになる)
local torf = false

print("num1 : "..num1)
print("num2 : "..num2)
local showMsg = string.format("num3 : %5.20f", num3) -- string.formatは関数(説明は別のところで・・・)
print("num3 : "..num3)

-- boolean型を文字列連結するとエラーになるのでif文で判定する
if (torf) then
	print("torf : true")
else
	print("torf : false")
end


local numStr = "123"
numStr = numStr + "100"    -- 両方文字列型であるが、数値に変換できるので「123 + 100」の演算となる
numStr = numStr .. "999"	-- 「..」を使った場合は単なる文字列連結になる
print("numStr  :"..numStr)

-- また明示的な型変換をするのに、tonumber関数とtostring関数があります
-- tonumber関数は数値に変換不可の場合nulを返します
local cantConvertNum = "aaaaaaa"
if tonumber(cantConvertNum) == nil then
	print("notConvertNum is not number")
else
	print("notConvertNum is number")
end

実行結果

notValueSet is nil
msg : あいうえお
num1 : 1000
num2 : 9223372036854775807
num3 : 100.00000000001
torf : false
numStr  :223999
notConvertNum is not number

tableについて(1)

Luaにおいては、データの格納にテーブルを使用します。
tableは連想配列です。
そして、ほかの言語で言う「配列」は、luaにおいては、テーブルで実現します(なので、配列という機能が独立して存在しているわけではない)。
配列の添え字は1からスタートします。
配列の長さを取得するには「#」で取得しますが、これは万能ではなく、1から始まって、nilに突き当たるまでの要素の数を返します。
なので、配列のindexに歯抜けがある場合には、正確な値を返さないことに、注意が必要です。
要素を全部取得するには、ソースの一番下に書いてあるforループで回すしかありません。

luaでは、なんでもかんでもtableを使います。
talbeには、なんでも入ります(数値、文字列、関数etc・・・)。
以下の例でも、使い方を全部紹介しきれていません。
慣れが必要ですね。


-- 以下で、空のテーブルが作成されます(名前はなんでもOK)。
local sampleTable = {}


-- sampleTableに配列の添え字でアクセスするデータを追加
sampleTable[-5] = "配列の-100番目です"
sampleTable[-1] = "配列の-1番目です"
sampleTable[0] = "配列の0番目です"
sampleTable[1] = "配列の1番目です"
sampleTable[2] = "配列の2番目です"
sampleTable[5] = "配列の10番目です"
sampleTable[6] = "配列の100番目です"


local arrayItemCount = #sampleTable
print("#で取得した配列の要素数 : "..arrayItemCount)

-- 配列の要素を、添え字を使って出力する
print("\n".."■配列の要素を、添え字を使って出力する")
for i=-5, 6 do
	if sampleTable[i] ~= nill then
		print(i..":"..sampleTable[i])
	else
		print(i..":はnilです")
	end
end


-- 配列の要素を全部表示する
print("\n".."■配列の要素を全部表示する")
for key,value in pairs(sampleTable) do
	print("key:"..key.."/"..value)
end

実行結果

#で取得した配列の要素数 : 2

■配列の要素を、添え字を使って出力する
-5:配列の-100番目です
-4:はnilです
-3:はnilです
-2:はnilです
-1:配列の-1番目です
0:配列の0番目です
1:配列の1番目です
2:配列の2番目です
3:はnilです
4:はnilです
5:配列の10番目です
6:配列の100番目です

■配列の要素を全部表示する
key:1/配列の1番目です
key:2/配列の2番目です
key:0/配列の0番目です
key:-5/配列の-100番目です
key:5/配列の10番目です
key:6/配列の100番目です
key:-1/配列の-1番目です

tableについて(2)

テーブルに、設定する値があらかじめ決まっている場合は、今回のサンプルで示すように、定義と同時に値のセットも可能です。


-- 以下で、空のテーブルが作成されます(名前はなんでもOK)。
local sampleTable = {}


--[[
-- テーブルに要素の追加(以下の2つはどちらも、テーブルに要素の追加です。どちらの書き方でもOK)
sampleTable.aiueo1 = "aiueo1のValue"
sampleTable["aiueo2"] = "aiueo2のValue"

sampleTable.array1 = {
	"array1要素1",
	"array1要素2",
	"array1要素3"
}
sampleTable.array2 = {
	"array2要素1",
	"array2要素2"
}
--]]

-- 上記のように1個づつテーブルに値のセットもできますが、以下のように記載することも可能
local sampleTable = {
	aiueo1 = "aiueo1のValue",
	aiueo2 = "aiueo2のValue",
	array1 = {
		"array1要素1",
		"array1要素2",
		"array1要素3"
	},
	array2 = {
		"array2要素1",
		"array2要素2"
	}
}


-- 配列の要素を全部表示する
print("\n".."■配列の要素を全部表示する")
for key,value in pairs(sampleTable) do

	local typeName = type(value)  --type関数は、要素の型を文字列で返してくれる
	
	if (typeName == "number" or typeName == "string") then
		print("key:"..key.."/"..value)
	elseif (typeName == "table") then
		print("key:"..key.."(テーブル型)")
		for key2, value2 in pairs(value) do
			print("  key2:"..key2.."/"..value2)
		end
	end
end

実行結果

■配列の要素を全部表示する
key:aiueo2/aiueo2のValue
key:array2(テーブル型)
  key2:1/array2要素1
  key2:2/array2要素2
key:array1(テーブル型)
  key2:1/array1要素1
  key2:2/array1要素2
  key2:3/array1要素3
key:aiueo1/aiueo1のValue

type関数による型判別

type関数の引数に変数を渡すと、その変数の型を文字列で返してくれます。
戻り値は以下の通りです。

引数に指定した変数にセットされている値 戻り値
nil値 "nil"
数値 "number"
文字列 "string"
TrueもしくはFalse "boolean"
テーブル "table"
コルーチン "thread"
ユーザーデータ "userdata"

local sampleTable = {
	"あいうえお",
	100,
	200,
	{
		100,200,300,400,500
	},
	false,
}

for key,value in pairs(sampleTable) do
	local typeName = type(value)
	
	print("☆"..typeName)
	if typeName == "string" or typeName == "number" then
		print("key : "..key.." / ".."value : "..value)
	elseif typeName == "table" then
		print("key : "..key.." table型でした")
		for i=1,#value do
			print("  "..i..":"..value[i])
		end
	elseif typeName == "boolean" then
		if value then
			print("key : "..key.."はboolean型でtrue")
		else
			print("key : "..key.."はboolean型でfalse")
		end
	end
	
end

実行結果

☆string
key : 1 / value : あいうえお
☆number
key : 2 / value : 100
☆number
key : 3 / value : 200
☆table
key : 4 table型でした
  1:100
  2:200
  3:300
  4:400
  5:500
☆boolean
key : 5はboolean型でfalse

関数(1)

luaで特徴的なのは、戻り値に複数の値を返すことができます。
戻り値に複数の値を返すパターンと、可変引数について、今回のサンプルコードで取り扱います。
戻り値に複数の値を返すことができますが、数に制限があります。
1000個までは保証しますが、これを超えた場合、何個までいけるかはシステム依存という仕様になっています。

関数のオーバーロードは、できないと思われます。
luaの関数は「function f1(a, b)」という関数が定義されると、これの呼び出しは、
  f1()
  f1(10, 20)
  f1(10, 20, 30)
いずれもOKです。 「f1()」で呼び出した場合、aとbにはnil値がセットされ呼び出されます。
「f1(10, 20, 30)」で呼び出した場合、余計な「30」は破棄され、a=10, b=20で関数が呼び出されます。
仮に
  function f1() return 100 end
  function f1() return 200 end
と定義された場合に、f1()と関数呼び出しした場合、戻り値は200が返ってきます。
そのため、同じ名前の関数を定義した場合、エラーにならず単純にあと勝ちになるので、注意が必要です。


-- 関数は以下のように記載する
local f1 = function(arg1, arg2)
	print("■f1関数")
	print(arg1..arg2)
end

-- そして、以下のようにも書ける
local function f2(arg1, arg2)
	print("■f2関数")
	print(arg1..arg2)
end

-- 戻り値を返す関数
local function f3()
	print("■f3関数")
	return "これもうわかんねぇな"
end

-- 「,」で区切ることにより、複数の戻り値を返すことも可能
local function f4()
	print("■f4関数")
	return "ああ^~~", "えっ、それは・・・", "当たり前だよなぁ?"
end

-- 可変引数も「...」を使うことで可能
local function f5(...)
	print("■f5関数")
	-- 「...」が可変引数を示す。
	-- 可変引数をテーブルにいれて、テーブルとして使えるようにする
	local args = {...}
	
	for key,value in pairs(args) do
		print(key.."/"..value)
	end
	
end

-- 関数を呼び出す
f1(1,2)
f2(1,2)
local f3Return = f3()
print("f3Return : "..f3Return)
local f4Return1, f4Return2, f4Return3 = f4()
print("f4Return1 : "..f4Return1.." / ".."f4Return2 : "..f4Return2.." / ".."f4Return3 : "..f4Return3)
f5("はぇ~", "やったぜ。", "悲しいなぁ")

--関数が返す複数の値返しますが、これを引数に渡すことも可能
print("■関数の複数の戻り値を使い、複数の引数を受け取る関数を呼び出す")
f1(f4())

-- type関数に渡すと、"function"という文字列が返ってくる
print("■type関数の引数に関数型を渡す")
print("f1 type : "..type(f1))
print("f2 type : "..type(f2))

実行結果

■f1関数
12
■f2関数
12
■f3関数
f3Return : これもうわかんねぇな
■f4関数
f4Return1 : ああ^~~ / f4Return2 : えっ、それは・・・ / f4Return3 : 当たり前だよなぁ?
■f5関数
1/はぇ~
2/やったぜ。
3/悲しいなぁ
■関数の複数の戻り値を使い、複数の引数を受け取る関数を呼び出す
■f4関数
■f1関数
ああ^~~えっ、それは・・・
■type関数の引数に関数型を渡す
f1 type : function
f2 type : function

関数(2)

再帰を記載するときの注意点について、記載します。
f1のようなものを「真正末尾再帰」というらしい。
f1のようなパターン(returnの中には1つの関数呼び出ししか記載しない)では、無限に再帰呼び出しを繰り返すことができます(関数呼び出し時にスタックに積んで・・・という作業をやらないらしい)。
一方、f2のようなパターン(return の中に関数呼び出し以外の処理が入っている)は、再帰呼び出しの数に制限があります。

ただ、何にせよ、再帰をforループなどに置き換えることを検討すべきです(という個人的な思い)。


-- こちらの再帰呼び出しはスタックオーバーフローしない
function f1(num)
	if (num%100000 == 0) then
		print("f1 numの値:"..num)
	end
	if (num <= 0) then
		return num
	end
	
	-- 以下のように「return」+関数呼び出し・・・だけの記載の場合、スタック消費が無いので、無限にループできる
	return f1(num - 1)
end


-- こちらの再帰呼び出しはスタックオーバーフローする
function f2(num)
	if (num%100000 == 0) then
		print("f2 numの値:"..num)
	end
	if (num <= 0) then
		return num
	end
	
	-- 以下のように「return」+関数呼び出し・・・以外の記載をすると、スタックを消費するので、呼び出しすぎるとスタックオーバーフローする
	return num + f2(num - 1)
end


local result1 = f1(1000000)
print("result1 : "..result1)

local result2 = f2(10000000)
print("result2 : "..result2)

実行結果

(「study8.lua」というのは、私がこのソースを作ったときのファイル名です)

f1 numの値:1000000
f1 numの値:900000
f1 numの値:800000
f1 numの値:700000
f1 numの値:600000
f1 numの値:500000
f1 numの値:400000
f1 numの値:300000
f1 numの値:200000
f1 numの値:100000
f1 numの値:0
result1 : 0
f2 numの値:10000000
f2 numの値:9900000
f2 numの値:9800000
f2 numの値:9700000
f2 numの値:9600000
lua: study8.lua:31: stack overflow
stack traceback:
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        ...     (skipping 499974 levels)
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:31: in function 'f2'
        study8.lua:38: in main chunk
        [C]: in ?

コルーチン

luaにおけるコルーチンは、resumeで関数をスタートして関数内のyiledで制御を呼び出し元に返す、再度resumeで以前yieldで制御を返した時点から再度処理を再開・・・、といった処理を途中で止める、ということができるものです。
マルチスレッドで動くわけではありません。
なので、使いどころはかなり限定されると思いますが・・・。

「coroutine.create」関数の引数に関数を渡すと、thread型の値が返ります(名前はスレッドなのですが、別スレッドで動く処理になるわけではありません。)。
そして、「coroutine.resume」で関数を実行します。
初回のresume関数の呼び出しでは、第二引数以降が、関数の引数として渡される様子です。
f1関数の中でyieldを呼ぶと、呼び出し元に制御が返ります。
その時の、yield関数に引数をセットすると、その値は、resume関数の戻り値として受け取ることができます。
再度resume関数を呼び出して、f1関数を再開させますが、この時、resume関数の引数をセットすると、f1関数の中のyield関数の戻り値として受け取ることができます。
resume/yield関数の引数・戻り値で、相互に値を受け渡すことができるようになっています。
f1関数が最後まで達している状態で、resumeを呼び出すと、一番最後のように、yieldの最初の戻り値がfalseになります(まだ処理がある場合は、trueが返る)。
そして、二個目の戻り値には、エラーメッセージがセットされる様子です。


local function f1(val)
	print("f1が呼び出されました1:"..val)
	local yieldValA1, yieldValA2 = coroutine.yield(val + 1, "1回目のyieldです", "AAAAAAAA")
	print("f1が呼び出されました2:"..val, yieldValA1, yieldValA2)
	local yieldValB1, yieldValB2  = coroutine.yield(val + 2, "2回目のyieldです")
	print("f1が呼び出されました3:"..val, yieldValB1, yieldValB2)
	return val + 1000
end


local coroutineThread = coroutine.create(f1)

local returnValA1, returnValA2, returnValA3, returnValA4 = coroutine.resume(coroutineThread, 100)
print("■returnValA : ", returnValA1, returnValA2, returnValA3, returnValA4)

local returnValB1, returnValB2, returnValB3 = coroutine.resume(coroutineThread, 200, "yieldの戻り値1")
print("■returnValB : ", returnValB1, returnValB2, returnValB3)

local returnValC1, returnValC2  = coroutine.resume(coroutineThread, 300, "yieldの戻り値2")
print("■returnValC : ", returnValC1, returnValC2)

local returnValD1, returnValD2 = coroutine.resume(coroutineThread, 400)
print("■returnValD : ", returnValD1, returnValD2)

実行結果

f1が呼び出されました1:100
■returnValA :  true    101     1回目のyieldです        AAAAAAAA
f1が呼び出されました2:100       200     yieldの戻り値1
■returnValB :  true    102     2回目のyieldです
f1が呼び出されました3:100       300     yieldの戻り値2
■returnValC :  true    1100
■returnValD :  false   cannot resume dead coroutine

メタメソッド

テーブルに対し、演算子のオーバーロードのようなことができます。
テーブルに対し、メタテーブルをセットすることで実現します。
セットされているメタテーブルを取得するにはgetmetatableメソッドを使い、メタテーブルをセットするにはsetmetatableメソッドを使います。
また、文字列型の変数は特別で、メタメソッドが定義されています(サンプルソースでは、valStrに設定されているメタメソッドを表示しています)

以下に、オーバーロード可能なメソッドの一覧を記載します。
サンプルソースでは__addの例のみを記載しています。
他のメタメソッドは、私自身、試したことが無く、マニュアルを参考に記載しました。

メタメソッド名 演算子
__add + A+Bという式の場合、AかBのどちらかが数値として評価できない場合、__addのメタメソッドの呼び出しが行われます。
Aのメタメソッドの__addを呼び出しますが、Aに__addが定義されていない場合、Bの__addを呼び出します。
__sub - __addと同じ動作をします。
__mul *
__div /
__mod %
__pow ^
__unm -(単項マイナス演算)
__idiv //(切り捨て演算)
__band &(論理積演算) 引数(演算する対象)のいずれかが整数として評価できない場合にのみ呼び出され、__addと同じ動作をする。
__bor |(論理和演算)
__bxor ~(排他的論理和)
__bnot ~(単項の時。否定)
__shl <<(左シフト)
__shr >>(右シフト)
__concat ..(連結) 引数(演算する対象)のいずれかが数値としても文字列としても評価できない場合にのみ呼び出され、__addと同じ動作をします。
__len #(長さ演算) 引数(演算する対象)が文字列ではない場合にのみ呼び出されます。
__eq ==(等値比較) 引数(演算する対象)が両方ともテーブル、もしくはフルユーザー型の場合、かつ、両者を生の値として==で比較した際、trueにならない場合に呼び出されます。
__lt < 引数(演算する対象)のいずれかが数値としても文字列としても評価できない場合にのみ呼び出され、__addと同じ動作をします。
__le <= __ltと__eqを組み合わせた処理が行われる(詳細は理解するのが面倒になったので省略)。
__index table[key](添え字アクセス) 自身がテーブルではない場合、もしくは、自身がテーブル型で指定されたindexのデータがnilの場合に呼び出されます。
__newindex table[key] = value(値の代入) 自身がテーブルではない場合、もしくは、自身がテーブル型で指定されたindexのデータがnilの場合での、値の代入をする場合に呼び出されます。
__call function(args)(関数呼び出し) 自身が関数ではないときに、関数の呼び出しを行った場合に呼び出されます。

function showMetaTable(metatable)
	if metatable == nil then
		print("metatableはnilです")
		return
	end
	for key,val in pairs(metatable) do
		print(key, val)
	end
end


local valStr = "あいうえお"
local valStrMetatable = getmetatable(valStr)
print("■文字列型はメタテーブルが設定されている")
showMetaTable(valStrMetatable)



-- メタテーブルを設定するテーブルを作成
local dataTable = {
	100, 200, 300, 400, 500
}

-- セットするメタテーブルを作成
local dataTableMetatableNew = {
	__add = function(a, b) 
		a[#a+1] = b 	-- 配列の末尾に要素を1つ追加
		return a
	end
}

print("\n■dataTableにメタテーブルを設定する前")
showMetaTable(getmetatable(dataTable))
print("■dataTableにメタテーブルを設定した結果")
setmetatable(dataTable, dataTableMetatableNew)
showMetaTable(getmetatable(dataTable))

-- メタテーブルを使い、「+」演算子に別の意味を持たせたので、試しに使ってみる
dataTable = dataTable + 1
for i=1,#dataTable do
	print(i.." : ".. dataTable[i])
end

実行結果

■文字列型はメタテーブルが設定されている
__mod   function: 007EE5F0
__pow   function: 007EE670
__add   function: 007EE470
__mul   function: 007EE570
__idiv  function: 007EE770
__sub   function: 007EE4F0
__div   function: 007EE6F0
__unm   function: 007EE7F0
__index table: 00B902A8

■dataTableにメタテーブルを設定する前
metatableはnilです
■dataTableにメタテーブルを設定した結果
__add   function: 00B53C10
1 : 100
2 : 200
3 : 300
4 : 400
5 : 500
6 : 1

クロージャ

スコープ外となる変数に対する参照を、うまく動かすためにクロージャが実装されています。
もちろん、メモリは消費し続けるので、扱いには注意が必要です。

サンプルのソースにおける、f1関数の中のnum, num2, num3は処理としては特に意味は無いのですが、f1関数の戻り値として関数を返しています。
この戻り値とする関数は、f1の中のlocal変数を使っています。
本来ならば、この関数を実行するタイミングで、f1のローカル関数はメモリ上に存在しないのでエラーとなるのですが、これの救済策としてクロージャが導入されており、num, num2, num3はメモリに残ったままにする仕様です。
local定義していたnum, num2, num3はスコープを抜けたらメモリが解放されるはずですが、解放されなくなります(サンプルソースで言う「funcTable」が不要になったら、すべてのメモリは解放されますが)。
なので、メモリに残ったままになるため、使いすぎるとメモリ不足でエラーとなります(サンプルソースは、メモリ不足で処理が落ちるようにしています)。
文句ばかり言っていますが、実装するアルゴリズムによっては、クロージャの動きがとても都合がよいケースがあり、そういうケースにおいては、とても助かる機能です。


function f1()
	local num = 100
	local num2 = 200
	local num3 = {}
	
	for i=0,1000 do
		num3[i] = i 
	end
	
	-- 戻り値としてfunctionを返す(処理内部ではf1が持つローカル変数を参照している)
	return 
		function()
			local calcResult = 100 * num * num2 * num3[num]  -- この処理に特に意味はない
			return calcResult
		end
end


local funcTable = {}

for i=0,0xffffff do
	funcTable[i] = f1()
	
	if i%10000 == 0 then
		print("ループ回数 : "..i, funcTable[i]())
	end
end

実行結果

ループ回数 : 0  200000000
ループ回数 : 10000      200000000
ループ回数 : 20000      200000000
ループ回数 : 30000      200000000
ループ回数 : 40000      200000000
ループ回数 : 50000      200000000
ループ回数 : 60000      200000000
ループ回数 : 70000      200000000
ループ回数 : 80000      200000000
ループ回数 : 90000      200000000
ループ回数 : 100000     200000000
ループ回数 : 110000     200000000
ループ回数 : 120000     200000000
lua: not enough memory

「環境(_ENV)」「グローバル環境(_G)」について

_ENVは5.2から導入されたらしい(なので、5.1の時代は無かった)。
_ENVは_Gと同じような存在だが、_Gは過去のバージョンとの互換性のために残されているっぽい(おそらく)。

「_G」は、ようするに「_ENV._G」という解釈でよい様子。
そして、デフォルトの状態では_Gも_ENVも指しているテーブルは同じとなる。

luaの言語仕様として、「a = 10」は「_ENV.a = 10」に置き換えらるようになっています。
そして、グローバル変数(localで定義していないもの。これは関数もテーブルも全部含む。)は_ENVの中に追加されます。
_ENVの中身は「a = 10」を実行する前と後で変化します。
「a = 10」実行後、_ENVには「a」という要素が追加されます(なので、グローバルな変数は_ENVの中身を見ればわかる)。

処理毎に、環境を変更したい場合があります。
その切り替えというものが、簡単に実現可能です。
サンプルソースのf1はテーブルを引数にとり、それをlocal _ENVにセットしています。
このようにすることで、言語仕様として「a=10」は「_ENV.a=10」に置き換えられますが、この時の_ENVは何が使われるかというと、localで定義した_ENVが使用されます(変数アクセスの優先順位は、手前から同じ名前にマッチしたものを使うので)。
これで、引数で渡されたテーブルが_ENV扱いとなります。
なので、localをつけないで定義した変数、関数などは_ENVにセットされます。
このようなことが、実現できます。


function showEtc()
	print("_Gの値 --------------------------")
	for key,value in pairs(_G) do
		print("    "..key, value)
	end
	print("_ENVの値 --------------------------")
	for key,value in pairs(_ENV) do
		print("    "..key, value)
	end
end


print("■変更前の_Gと_ENV")
print("_G   : "..tostring(_G))
print("_ENV : "..tostring(_ENV))
print("■_Gと_ENVの内容確認")
showEtc()
print("--------------------------------\n")


-- 引数で渡されたテーブルを環境にセットして動く関数
function f1(specialEnv)
	-- _ENVを引数で渡されたテーブルにセットする
	-- localとやっているのがポイント。なので、この関数内部だけ_ENVが置き換わる。
	local _ENV = specialEnv
	
	exEnv.print("■変更後の_Gと_ENV")
	exEnv.print("_G  :"..exEnv.tostring(_G))
	exEnv.print("_ENV:"..exEnv.tostring(_ENV))
	
	-- 関数を定義してみる
	function aaaaaaaaaaaaaaaaaalocalFunc1()
		return 100
	end
	-- 変数を定義してみる
	aaaaaaaaaaaaaaaaaAiueo=10000
end


-- 独自の環境用のテーブル
local sampleEnv = {
	exEnv = _ENV,  -- 今の環境をexEnvとして持たせておく(そうしないと、基本的な関数すら無いので)
}

-- f1を呼び出す
f1(sampleEnv)


print("■関数呼び出しから戻ってきた時の_Gと_ENV")
print("_G   : "..tostring(_G))
print("_ENV : "..tostring(_ENV))

print("■sampleEnvの内容 -------------------------------")
for key,value in pairs(sampleEnv) do
	print(key, value)
end

実行結果

■変更前の_Gと_ENV
_G   : table: 01388168
_ENV : table: 01388168
■_Gと_ENVの内容確認
_Gの値 --------------------------
    coroutine   table: 0138E6E0
    _G  table: 01388168
    assert      function: 00B09C90
    print       function: 00B08310
    warn        function: 00B083E0
    math        table: 0138EDE8
    getmetatable        function: 00B08950
    package     table: 0138EA50
    os  table: 0138EC58
    io  table: 0138E7A8
    utf8        table: 0138ED98
    require     function: 0138E898
    rawlen      function: 00B08C00
    tonumber    function: 00B08530
    string      table: 0138ECD0
    load        function: 00B098C0
    select      function: 00B09D10
    dofile      function: 00B09B60
    ipairs      function: 00B09520
    type        function: 00B09280
    setmetatable        function: 00B08A00
    rawequal    function: 00B08B20
    showEtc     function: 01392528
    arg table: 013936A0
    debug       table: 01393510
    tostring    function: 00B0A240
    table       table: 0138E708
    loadfile    function: 00B09680
    error       function: 00B088E0
    next        function: 00B09310
    pcall       function: 00B09E90
    pairs       function: 00B09400
    xpcall      function: 00B0A040
    collectgarbage      function: 00B08F50
    _VERSION    Lua 5.4
    rawset      function: 00B08E30
    rawget      function: 00B08D00
_ENVの値 --------------------------
    coroutine   table: 0138E6E0
    _G  table: 01388168
    assert      function: 00B09C90
    print       function: 00B08310
    warn        function: 00B083E0
    math        table: 0138EDE8
    getmetatable        function: 00B08950
    package     table: 0138EA50
    os  table: 0138EC58
    io  table: 0138E7A8
    utf8        table: 0138ED98
    require     function: 0138E898
    rawlen      function: 00B08C00
    tonumber    function: 00B08530
    string      table: 0138ECD0
    load        function: 00B098C0
    select      function: 00B09D10
    dofile      function: 00B09B60
    ipairs      function: 00B09520
    type        function: 00B09280
    setmetatable        function: 00B08A00
    rawequal    function: 00B08B20
    showEtc     function: 01392528
    arg table: 013936A0
    debug       table: 01393510
    tostring    function: 00B0A240
    table       table: 0138E708
    loadfile    function: 00B09680
    error       function: 00B088E0
    next        function: 00B09310
    pcall       function: 00B09E90
    pairs       function: 00B09400
    xpcall      function: 00B0A040
    collectgarbage      function: 00B08F50
    _VERSION    Lua 5.4
    rawset      function: 00B08E30
    rawget      function: 00B08D00
--------------------------------

■変更後の_Gと_ENV
_G  :nil
_ENV:table: 013977B0
■関数呼び出しから戻ってきた時の_Gと_ENV
_G   : table: 01388168
_ENV : table: 01388168
■sampleEnvの内容 -------------------------------
aaaaaaaaaaaaaaaaaalocalFunc1    function: 01389E60
exEnv   table: 01388168
aaaaaaaaaaaaaaaaaAiueo  10000

モジュールのロード

「study13Mod.lua」というファイル(実行するluaスクリプトと同じディレクトリにあるファイル)を読み込み、それを実行するサンプルです。
検索方法をカスタムする方法もありますが、これについては、ここでは触れません。

モジュールとして独立させている「study13Mod.lua」ですが、luaにおいては、ソースコードの塊はチャンクと呼びます。
なので「study13Mod.lua」もluaから見るとチャンクです。
そして、チャンクは関数と同じような扱いがなされます。
そのため、引数もあれば、戻り値もあります。
「study13Mod.lua」では、それを利用し、_ENVを汚さず、関数の定義を行うようにしています。


-- 同ディレクトリ内にある「study13Mod.lua」を読み込む
local sampleMod = require("study13Mod")


local f1Result = sampleMod.f1()
local f2Result = sampleMod.f2(1000)

print("f1Result : "..f1Result)
print("f2Result : "..f2Result)

「study13Mod.lua」の内容


-- モジュールのロード時の引数(=モジュール名)がセットされる
local moduleName = ...

-- 関数をローカルで定義する
local function f1()
	return moduleName
end

local function f2(val)
	if val==1 then
		return "わいわいチャーハン"
	else
		return "ホイホイチャーハン"
	end
end

-- 当モジュールの戻り値として、以下のテーブルを返す
return {
	f1 = f1,
	f2 = f2
}

実行結果

f1Result : study13Mod
f2Result : ホイホイチャーハン

コロン構文

本サンプルだけではあまり意味が無いのですが、これは、クラスを作成する時の足掛かりとなります。
t:f関数は「t.f = function(self) print(self.msg) end」と同じです。
「:」を使うことで、selfという名前の変数に自身が属するテーブル(サンプルでは「t」)がセットされるようになります。
クラスを作らない場合、このような書き方はしないと思いますが、クラスを作る上では便利な書き方となります。


local t = { msg="ゆがみねぇな" }

function t:f() 
	print(self.msg)
end

t:f()

実行結果

ゆがみねぇな

クラス(1)

luaには言語レベルでのクラスの実装はありません。
なので、クラスっぽく扱うためのテクニック的な話です。
コロン構文を使うことで、クラスっぽくソースを記述することができます。

特殊な部分は、CData:newの関数の部分です。
これは結局のところ、空のテーブルを作って返しているだけです。
ただ__indexにself(CDataテーブル)というメタテーブルをセットした、テーブルを戻り値にセットしています。
このメタテーブルをセットすることでほかの言語で言うクラスっぽく動いているように見せることができます。

cdataObj1:setWidth(100)を呼び出す処理を考えてみます。
cdataObj1は、ただの空のテーブルです(しかし、メタテーブルがセットされている)。
cdataObj1:setWidth(100)を呼び出すと、cdataObj1自身には「setWidth(100)」という関数は存在しないので、__indexにセットされているCDataが呼ばれます。
CDataには、setWidthという関数があるのでそれが呼び出しされます。
setWidth関数の呼び出しコロン構文になっており、この関数内のselfにセットされている値はcdataObj1となります。
その結果、「self.width = val」は、「cdataObj1.width = val」とやっていることと同じとなります。
cdataObj1はwidthという項目を持っていませんが、エラーとはならず、widthという項目がcdataObj1に追加され、値はvalで指定した値となります・・・。
という感じで、動いている・・・はずです・・・。

また、new関数に引数をとれるようにしているのは
local cdataObjX = CData:new({ num=100, msg="ひぎぃ!"})
というように、cdataObjXに持たせる内容を初期設定できるようにするためです。
後で足すこともできますが、newするときに同時にセットできたほうが便利だからです。(利便性のため)


-- テーブルの内容を表示するメソッド(確認用)
local function showItem(t)
	for key, value in pairs(t) do
		print("key/value : ", key, value)
	end
end


-- CDataというクラス扱いにするテーブルを作成
CData = { }

-- メンバ関数(インスタンスを生成する関数)
function CData:new(o)
	o = o or { }	-- oがnilなら新規にテーブルを作成し、引数が指定されている場合、それをそのまま使用する
	self.__index = self
	setmetatable(o, self)
	return o
end
-- メンバ関数(メンバ変数widthに値をセットする関数)
function CData:setWidth(val)
	self.width = val
end
-- メンバ関数(メンバ変数heightに値をセットする関数)
function CData:setHeight(val)
	self.height = val
end
-- メンバ変数の値を表示する関数
function CData:show()
	print("width/height : ", self.width, self.height)
end


print("■CDataが持っている項目を表示")
showItem(CData)

print("■クラスのインスタンスを作成し、メソッドを呼び出す")
local cdataObj1 = CData:new()
cdataObj1:setWidth(100)
cdataObj1:setHeight(200)

local cdataObj2 = CData:new()
cdataObj2:setWidth(1000)
cdataObj2:setHeight(2000)

cdataObj1:show()
cdataObj2:show()

実行結果

■CDataが持っている項目を表示
key/value :     show    function: 00C24838
key/value :     setWidth        function: 00BE3EC0
key/value :     setHeight       function: 00BE3F20
key/value :     new     function: 00C249F8
■クラスのインスタンスを作成し、メソッドを呼び出す
width/height :  100     200
width/height :  1000    2000

クラス(2)

クラスの継承・・・のようなことをやってみます。
CDataを基底クラスとし、CDataExというクラスを作りました。
子クラスを作る際、一回インスタンスを作らねばならない様子です。
なので、C++などにある純粋仮想関数という概念は無い様子。

getLenghtをオーバーライドしてみました。
しかし、親クラスのメソッドを呼ぶには、サンプルで示したような書き方じゃないとだめっぽいです(「self:getLenght()」とすると無限ループして不正終了する)。


-- 基底クラス扱いとするテーブル
CData = { }

function CData:new(o)
	o = o or { }
	setmetatable(o, self)
	self.__index = self
	return o
end

function CData:getLenght()
	return self.lenght
end

function CData:setLenght(val)
	self.lenght = val
end


-- CDataを継承したCDataExクラス・・・とするテーブルを作成
CDataEx = CData:new()
-- 新しいメンバ関数を追加
function CDataEx:getMsg()
	return self:getLenght().."が、すごく・・・大きいです・・・"
end
-- getLenghtをオーバーライドする
function CDataEx:getLenght()
	-- 親クラスのgetLenghtを呼び出そうと、以下を呼ぶと無限ループする。
	--local baseLen = self:getLenght()
	-- そのため、直接呼び出す(単にlenghtの値が欲しいだけなら「self.lenght」と記載すればOK)
	local baseLen = CData.getLenght(self)
	return baseLen * 100
end


local cdataExObj1 = CDataEx:new()
cdataExObj1:setLenght(1000)

local cdataExObj2 = CDataEx:new()
cdataExObj2:setLenght(2000)

local msg1 = cdataExObj1:getMsg()
local msg2 = cdataExObj2:getMsg()

print("result1 : "..msg1)
print("result2 : "..msg2)

実行結果

result1 : 100000が、すごく・・・大きいです・・・
result2 : 200000が、すごく・・・大きいです・・・

弱いテーブル

弱いテーブルを作ってみます。
弱いテーブルからガベージコレクションされるのは、オブジェクトだけです。数値などはガーベジコレクションの対象外です。
文字列は値なので、ガベージコレクション対象外となります。
__modeに何をセットしていたとしても、cacheTableのkeyもしくはvalueが、ガベージコレクションにより回収された場合、テーブル(サンプルのソースでいうとcacheTable)の項目一覧から要素が削除されます。


function showItem(t)
	for key,value in pairs(t) do
		print("key/value : ", key, value)
	end
end

-- 何かのキャッシュをするテーブルとする
local cacheTable = {}

local cacheTableMeta = {
	__mode="k"	-- __modeには"k"(keyが弱い参照), "v"(valueが弱い参照), "kv"(keyもvalueも弱い参照)が指定できる。
}
setmetatable(cacheTable, cacheTableMeta)



local tmp1Data = { 
	-- 何かのデータ・・・
}
local tmp2Data = { 
	-- 何かのデータ・・・
}

local tmp1DataValue = {
	-- 何かのキャッシュのようなデータ・・・
}
local tmp2DataValue = {
	-- 何かのキャッシュのようなデータ・・・
}

-- cacheTableに値をセット
cacheTable[tmp1Data] = tmp1DataValue
cacheTable[tmp2Data] = tmp2DataValue


-- 何かの処理の結果tmp1Dataのデータが必要無くなりnilがセットされた・・・
tmp1Data = nil

-- 現在のcacheTableを表示
print("■ガベージコレクション実行前のcacheTable")
showItem(cacheTable)

-- ガベージコレクションを実行
collectgarbage()

print("■ガベージコレクション実行後のcacheTable")
showItem(cacheTable)

実行結果

■ガベージコレクション実行前のcacheTable
key/value :     table: 01194778 table: 01194818
key/value :     table: 011947A0 table: 01194840
■ガベージコレクション実行後のcacheTable
key/value :     table: 011947A0 table: 01194840

エラーハンドリング

luaスクリプトにエラー(文法エラーなども含めたその他もろもろ)が発生すると、その時点で処理が終了します。
しかし、終了したくない時もあります。
そういったケースのため、関数を実行し、エラーがあった場合は処理を呼び出し元に戻すことができる仕組みがあります。
pcall関数やxpcall関数を使用すると、引数で指定した関数を実行し、実行した結果がエラー無く終わったか、エラーがあったか教えてくれます。
ここではxpcall関数の使用例を示します(xpcallは、エラー発生時のエラーハンドリング関数を指定できます)。

サンプルソース中にもありますが、error関数を使うと故意にエラーを発生させることができます。


function f1(a, b)
	print("f1 called", a, b)
	
	-- 故意にエラーを発生させる
	error("エラーです!")
end

function errHandler(x)
	local msg = "エラーが発生しました : "..x
	return msg
end

-- xpcall関数の第一引数に呼び出す対象の関数、第二引数にエラーハンドラの関数、第三引数以降にf1の引数をセットする
local callResult, errorMsg = xpcall(f1, errHandler, 1, 2)

print("callResult : ", callResult, errorMsg)

-- callResultがtrueの場合はエラーが発生していない状態
if callResult then
	print("f1の処理は正常に終了しました")
else
	print("f1の処理内部でエラーが発生しました")
end

実行結果

f1 called       1       2
callResult :    false   エラーが発生しました : study18.lua:8: エラーです!
f1の処理内部でエラーが発生しました

ページの一番上へ