Lua ランタイム内部モジュール
部品カタログを広げますわよ。細部にこそ品格が宿るもの――pasta.* の内部モジュールを一つずつ手に取って、フィールドの意味、関数の役割、互いの依存の避け方まで確かめますの。
中でも pasta.store は、ほかの pasta.* を一切 require しない一匹狼と伺っております。
一匹狼だなんて、ずいぶんな言われようですわね。だからこそ、どのモジュールからも循環なく呼べる要の子ですのに。
この章は、Lua ランタイムの内部モジュールのモジュール単位のリファレンスである。モジュール間の関係と実行の流れは ランタイム実行モデル で扱う。ゴースト作者が scripts/ から呼ぶ API(ACT のトーク系メソッド・WORD・GLOBAL・SAVE の使い方)は スクリプト用ランタイム API で扱う。
この章が扱うモジュールのソースの所在は ランタイム実行モデル を参照する。
STORE パターン
pasta.store は、ランタイムが共有するデータを 1 つの表に集めて保持するモジュールである。他の pasta.* モジュールを require しないため、どのモジュールからも循環なく require できる。
local STORE = require("pasta.store")
STORE のフィールド
| フィールド | 型 | 内容 | 書き手と読み手 |
|---|---|---|---|
actors | table<string, Actor> | アクター名 → アクターオブジェクト | 初期値は @pasta_config の actor。pasta.actor の get_or_create が追加する。ACT の生成時に act.actors として渡る |
actor_spots | table<string, integer> | アクター名 → スポット番号 | 初期値は @pasta_config の actor.*.spot。pasta.shiori.act の build が sakura_builder に渡し、組立が spot・clear_spot のトークンで書き換える |
appearance | table | 外見の状態 { actors, spots, owners, last_spots }(アクターの既知状態・スポットの表示中状態・スポットの直前の発話アクター・アクターの前回の発話スポット)。セッション中だけ保持し、永続化しない | pasta.shiori.act の build が sakura_builder に渡す(アピアランスの観測と復旧) |
scenes | table<string, SceneTable> | グローバルシーン名 → シーンテーブル | pasta.scene が作り、get_all_scenes が返す |
counters | table<string, number> | シーンの基本名 → 最後に振った番号 | pasta.scene の get_or_increment_counter |
global_words | table<string, table> | 単語キー → 値リストの配列 | pasta.word の create_global のビルダー |
local_words | table<string, table> | グローバルシーン名 → { 単語キー → 値リストの配列 } | pasta.word の create_local のビルダー |
actor_words | table<string, table> | アクター名 → { 単語キー → 値リストの配列 } | pasta.word の create_actor のビルダー |
app_ctx | table | セッション中の汎用の表 | ACT の生成時に同じ表が act.app_ctx に入る |
co_scene | thread|nil | 中断中の継続待ちのコルーチン。台詞を出さない割り込みでは残り、台詞を出した割り込み・指名した再生・エラーで捨てられる | ランタイムの中では pasta.shiori.event の set_co_scene だけが書き、EVENT.drive が種類と台詞の印で呼ぶ。STORE.reset も nil に戻す(継続トーク(チェイントーク)と co_scene の更新) |
co_preempt | table<thread, true> | 開発者が指名した再生(キック・シーン再生)のコルーチンの印(はっきり捨てる)。キーを弱い参照にした表で、捨てたコルーチンは GC で消える | pasta.shiori.event.kick の wrap_local_func・wrap_reload_func と、pasta.shiori.event.scene_play の OnPastaScene・OnPastaSceneAt が付ける。読むのは pasta.shiori.event の drive_kind だけ。STORE.reset が作り直す |
last_global_scene | string|nil | 最後に init_scene したシーンテーブルの __global_name__ | ACT_IMPL.init_scene だけが書く(Call から戻っても戻さない)。OnChoiceSelectEx の既定ハンドラが、Reference2 から選択肢を出したグローバルシーンが得られないときの、選択 ID の検索の親に使う |
kick_pending | string|nil | 保留中のキック対象のシーン名 | KICK.install が書き、KICK.try_dispatch が消費する(キックの保留と起動) |
kick_force | boolean | キックの割り込み許可(既定 false) | KICK.install が立て、仮想イベントの dispatch の入口が 1 回で下ろす |
auto_talk_paused | boolean | 自動トークを止めている印(既定 false)。回数は数えず、永続化しない | GLOBAL.pause_auto_talk が立て、GLOBAL.resume_auto_talk と STORE.reset が下ろす。仮想イベントの dispatch・ACT_IMPL.yield・つつき撫での既定ハンドラが読む(自動トークを止めている間) |
co_callback | thread|nil | コールバック待ちとして登録したコルーチンの印 | pasta.store は初期化しない(未設定のときは nil)。CALLBACK.consume_staged が書き、直後の set_co_scene(どちらも EVENT.drive の中)が nil に戻す。CALLBACK.reset も nil に戻す(コールバック待ちとの関係) |
単語の 3 つのフィールドの値の形は finalize_scene の「単語収集データ構造」で扱う。
@pasta_config からの初期化
pasta.store は、最初に require されたときに @pasta_config を pcall(require, "@pasta_config") で読み、actor が表なら次の 2 つを行う。
STORE.actorsにCONFIG.actorの表そのものを入れる(複製しない)。以後get_or_createが足すアクターも同じ表に入る。CONFIG.actorの各要素のうち、表でありspotが数値のものについて、STORE.actor_spots[アクター名] = spotとする。
@pasta_config を取得できない環境(@pasta_config を登録しない単体テストなど)でも、pcall で保護しているため読み込みは失敗せず、両フィールドは空の表のままになる。ランタイムの構築では @pasta_config の登録が pasta.store の最初の require より先に行われる(VM の構築とモジュール登録)。CONFIG.actor の要素にアクターのメタテーブルを付けるのは pasta.actor である(アクターオブジェクト)。
reset()
STORE.reset() は、STORE の状態を初期値に戻す。ランタイムのモジュールは呼ばず、テストが状態を消すために使う。
STORE.reset()
co_sceneは、coroutine.closeがあり中断中なら閉じてからnilにする。LuaJIT 2.1 にはcoroutine.closeが無いため、ランタイムでは閉じる処理は実行されず、参照を外すだけになる(LuaJIT 2.1 の制約)。actors・actor_spots・scenes・app_ctx・counters・global_words・local_words・actor_words・appearanceを新しい空の表(appearanceは空の 4 つの表を持つ表)にし、last_global_scene・kick_pendingをnil、kick_force・auto_talk_pausedをfalseにし、co_preemptをキーを弱い参照にした新しい表にする。仮想イベントの時計は決め直さない。@pasta_configからの初期化はやり直さない。actors・actor_spotsは空の表になる。co_callbackは変えない。コールバックの状態を消すのはCALLBACK.reset()である。- フィールドの表を新しい表に置き換えるため、すでに作られた ACT が持つ
act.app_ctx・act.actorsは古い表を指したまま残る。
循環参照回避の原則
共有するデータは STORE に集め、他のモジュールが STORE を require する一方向の依存を保つ。STORE 自身は他の pasta.* モジュールを require しない。
-- 正しい依存の向き: 他のモジュール → STORE
-- pasta.store
local STORE = {}
STORE.actors = {}
return STORE
-- pasta.actor
local STORE = require("pasta.store")
STORE.actors["さくら"] = { name = "さくら" }
例外は Rust 製のモジュール @pasta_config だけであり、pcall を通して require する(前節)。@pasta_config は Lua のモジュールを require しないため、循環は生じない。モジュール全体の require の関係は Lua 側のモジュールの関係 で扱う。
ACT の内部
ACT は、シーン関数が第 1 引数で受け取るオブジェクトである。pasta.act が基本の ACT を、pasta.shiori.act が SHIORI 用の ACT を作る。SHIORI のイベントごとに SHIORI_ACT.new(STORE.actors, req) で新しい ACT が作られる(イベントからシーンへ)。この節では ACT の構造と、メソッド・アクタープロキシの解決、init_scene とシーン文脈の復元、名前の解決のメソッド、Call のキーと失敗表記、生成コード用のメソッドと動的参照のキーの変換を扱う。トーク系メソッドなど個々のメソッドの使い方は スクリプト用ランタイム API で、トークンの蓄積と組立は トーク出力とアピアランス で扱う。
ACT オブジェクトの構造
ACT.new(actors) は次のフィールドを持つ表を作り、メタテーブル ACT_IMPL を付ける。ここでは各フィールドの作られ方を示す。スクリプトからの使い方は ACT のフィールド が正である。
| フィールド | 値 |
|---|---|
actors | 引数の actors(nil なら空の表)。SHIORI 用の ACT では STORE.actors そのもの |
save | require("pasta.save") の結果。全 ACT で同じ表(SAVE モジュールの内部) |
app_ctx | STORE.app_ctx と同じ表 |
var | ACT ごとの新しい空の表 |
token | 積んだトークンの配列。build が新しい空の配列に置き換える |
current_scene | 実行中のシーンテーブル。初期値は nil で、init_scene が設定し、call_restore・restore_scene が呼ぶ前の値に戻す(シーン文脈の復元) |
scene_name | 実行中のグローバルシーンの、辞書に書いた名前(string|nil)。初期値は nil で、init_scene の第 2 引数を控える |
local_scene_name | 実行中のローカルシーンの、辞書に書いた名前(string|nil)。初期値は nil で、init_scene の第 3 引数を控える |
line_map | 実行中のシーンの、生成コードの行と .pasta の行の対応(string|nil)。初期値は nil で、init_scene の第 4 引数を読み解かずに控える |
scene_names | シーンテーブル → グローバルシーンの書いた名前。ACT ごとの新しい空の表で、init_scene が書き足し、restore_scene が引く |
SHIORI_ACT.new(actors, req) は ACT.new(actors) で作った表に、_spot_newlines・_choice_newlines・_after_choice_newlines(pasta.config から読む [ghost] の spot_newlines・choice_newlines・after_choice_newlines。既定は 1.5・1.5・1.0)と req(SHIORI のリクエストの表)を足し、メタテーブルを SHIORI_ACT_IMPL に付け替える。act.req のフィールドは act.req が正である。
シーンのコルーチンに渡った ACT は、そのコルーチンが終わるまで使われ続ける(再開のループ)。Call で呼んだ先のシーン関数も同じ ACT を受け取るため、var と token は呼び出しの連鎖の全体で共有される。
メソッドとアクタープロキシの解決(__index)
ACT のフィールドに無いキーの参照は、メタテーブルの __index 関数が解決する。
act[key](ACT のフィールドに無いとき)
SHIORI 用の ACT: rawget(SHIORI_ACT_IMPL, key) があればそれ
無ければ ACT_IMPL.__index(act, key) へ
ACT_IMPL.__index:
1. ACT_IMPL[key] があればそれ(メソッド)
2. act.actors[key] があれば ACTOR.create_proxy(アクター, act)(アクタープロキシ)
3. nil
- 解決の優先順はフィールド → メソッド → アクター名である。この優先順により、メソッド名やフィールド名と同じ名前のアクターがプロキシにならないこと(利用者から見た振る舞い)は アクタープロキシ が正である。
- アクタープロキシは参照のたびに新しく作られ、キャッシュされない(PROXY パターン)。
- 生成コードはアクターをこの経路で引かず、
act:actor_proxy("名前")を呼ぶ(生成コード用のメソッド)。actor_proxyはact.actors[名前]を直接引くため、メソッド名・フィールド名と同じ名前のアクターもプロキシになる。 SHIORI_ACT_IMPL自身にも__index = ACT.IMPLのメタテーブルが付いており、SHIORI_ACT_IMPL.talkのように実装表から直接引いてもACT_IMPLのメソッドが得られる。
継承(ACT.IMPL)
pasta.act は実装表を ACT.IMPL として、pasta.shiori.act は SHIORI_ACT.IMPL として公開する。pasta.shiori.act は ACT.IMPL を継承し、build をさくらスクリプト文字列を返す処理に差し替え(トーク出力とアピアランス)、get_property・set_property・transfer_date_to_var・transfer_req_to_var を加える。差し替えた build の中では、親の処理を ACT.IMPL.build(self) で呼ぶ。
init_scene
ACT_IMPL.init_scene(self, scene, scene_name, local_scene_name, line_map) は、シーン関数の実行を始めるときに ACT を初期化し、永続化データの表とローカル変数の表を返す。
--- @param scene SceneTable シーンテーブル(生成コードの SCENE)
--- @param scene_name string|nil グローバルシーンの書いた名前(無ければ nil)
--- @param local_scene_name string|nil ローカルシーンの書いた名前(無ければ nil)
--- @param line_map string|nil 行の対応(読み解かずに控えるだけ)
--- @return table save 永続化データの表(act.save)
--- @return table var ローカル変数の表(act.var)
function ACT_IMPL.init_scene(self, scene, scene_name, local_scene_name, line_map) end
function SCENE.__start__(act, ...)
local args = { ... }
local save, var = act:init_scene(SCENE, "天気", nil, [[dic\天気.pasta|12:3,16:4]])
-- 以降のアクション
end
処理は次の 5 つだけである。
scene.__global_name__があれば、STORE.last_global_sceneに記録する。pasta.sceneが作るシーンテーブルはすべて__global_name__を持つため、生成コードからの呼び出しでは常に記録される。self.current_scene = sceneとする。scene_name・local_scene_name・line_mapを同名のフィールドに書き(省略した引数はnil)、self.scene_names[scene] = scene_nameとする。FAILURE.bind(self)で、実行中のスレッドにこの ACT を対応させる(失敗の報告)。self.save, self.varを返す。新しい表は作らない。
- トランスパイラは、すべてのシーン関数の先頭に
local save, var = act:init_scene(SCENE, 書いた名前…, 行の対応)を生成する(生成される Lua コードの形)。作者が Lua で書いたシーン関数と、引数を渡さない古いキャッシュでは、3 つのフィールドはnilになる。 scene_name・local_scene_name・line_mapは、止まるエラーの表記にだけ使う。出力・名前の検索には使わず、止まるエラーが起きない要求では定数を受け取って控える以外の仕事をしない。止まったときにpasta.shiori.stopが、名前から止まった場所(*天気・雨の日)を作り、行の対応を最後の|で切って読み解いて、変換した Lua の行を.pastaの行に直す(止まるエラーの控えと応答)。名前がnilのときは、current_scene.__global_name__から通し番号を落とした名前を使う。エラー文のチャンクが作者の.luaか変換した Lua のキャッシュかは、Rust のモジュール探索関数が読み込んだファイルを書き足す表@pasta_chunks(チャンク名 → ゴーストのフォルダーから見たパス、キャッシュはtrue、見分けられない重複はfalse)で見分ける。current_sceneは、単語参照・Call・式関数の名前の解決のうち、L1(current_scene[key]の完全一致)と L2(current_scene.__global_name__をスコープとするローカル辞書の前方一致)が使う(ローカル優先の検索順)。init_sceneを呼ばない関数の中では、current_sceneは直前にinit_sceneしたシーンのままである。init_sceneはcurrent_scene・書いた名前と行の対応・last_global_sceneを上書きするだけで、自分では元へ戻さない。呼び出し元へ戻ったときにcurrent_sceneなどを戻すのは呼ぶ側である(次節)。last_global_sceneは戻さない。
シーン文脈の復元(call_restore・restore_scene)
current_scene の書き手は init_scene と、呼ぶ側の call_restore・restore_scene である。呼ばれた側のシーン関数が init_scene で自分のシーンを設定し、呼ぶ側が、戻った後に呼ぶ前の値に書き戻す。利用者から見た振る舞いは Call から戻った後の名前解決 と call_restore が正である。
function ACT_IMPL.restore_scene(self, scene, ...)
self.current_scene = scene
self.scene_name = scene and self.scene_names[scene]
self.local_scene_name = nil
self.line_map = nil
return ...
end
function ACT_IMPL.call_restore(self, global_scene_name, key, attrs, ...)
local scene = self.current_scene
local scene_name, local_scene_name, line_map = self.scene_name, self.local_scene_name, self.line_map
return (function(...)
self.current_scene = scene
self.scene_name, self.local_scene_name, self.line_map = scene_name, local_scene_name, line_map
return ...
end)(self:call(global_scene_name, key, attrs, ...))
end
call_restoreは、呼ぶ前のcurrent_scene・scene_name・local_scene_name・line_mapの 4 つをそのまま戻す。DSL の途中の Call から戻った後の行で止まると、止まるエラーの表記は呼び出し元のローカルシーンの名前と.pastaの行まで示せる。restore_sceneは、呼ぶ前のローカルシーンと行の対応を知らない。グローバルシーンの書いた名前だけをscene_names[scene]から戻し、local_scene_name・line_mapをnilにする。関数から戻った後に止まると、表記は*天気のようにグローバルシーンまでになり、.pastaの行も示さないが、呼んだ先のシーンの名前は出さない。- どちらも
STORE.last_global_sceneは触らない。残りの引数をそのまま返すため、呼んだ関数の複数の戻り値も保たれる。 - 途中の Call(ローカルシーンの最後の項目でない Call)の生成コードは
act:call_restore(…)である(生成される Lua コードの形)。call_restoreの中のself:call(…)は末尾位置ではないため、呼び出しのたびにフレームが 1 つ増える。呼ばれた側の中でreturn act:call(…)がつながっても深くならず、その連鎖から戻った時点で、外側のcall_restoreが 1 回だけ復元する。 - 復元は、
act:callの中で何が起きたか(シーン関数・Lua の関数を呼んだ、見つからなかった、キーがnil・「呼ばない」印だった)にかかわらず同じ経路で行う。呼ばれた側がyieldで中断した場合は、コルーチンの再開後に戻った時点で行う。 - 呼ばれた側が Lua のエラーで抜けた場合は復元しない。エラーはコルーチンごと失敗し(止まるエラー。止まるエラーの表記)、その ACT は止まった場所の名前を作るのに使われた後に捨てられる。
- 関数を呼ぶ ACT のメソッドも同じ形で復元する。
pasta.actの局所関数call_expr(expr_fn・expr_fn_var)・global_fn・word(見つかったのが関数のとき)は、local scene = self.current_sceneの後にreturn self:restore_scene(scene, h(self, ...))とする(wordは引数を渡さずh(self))。pasta.actorのcall_exprとプロキシのwordは、self.act.current_sceneを保存してdrop_self(self, self.act:restore_scene(scene, h(…)))とする。pasta.actorはpasta.actをrequireせず、プロキシが持つself.actのメソッドを呼ぶ。 - 末尾の Call(
return act:call(…))は復元しない。戻る行が無いため、呼ばれた側のシーンが実行中のまま、その Call を含むシーン関数が終わる。そのシーン関数をcall_restoreで呼んだ側があれば、その側が復元する。
Call のキーと失敗表記(call_key・failure)
call_key・failure の引数・戻り値・警告の文言は call_key・failure が、失敗表記の文言は 実行時の失敗の出力 が正である。ここでは実装の内部の構成だけを扱う。
- 動的コールの値の判定(
nilの判定を含む)は、生成コードではなく実行時のcall_keyが行う。生成コードは式の値をtostringせずにそのまま渡す(生成される Lua コードの形)。キーの式はact:call・act:call_restoreの第 2 引数のため、引数の式より先に評価される。 - 検索キーにできない値のとき、
call_keyは警告と失敗表記を出してから、pasta.actの局所の表SKIP_CALL(「呼ばない」印)を返す。式が関数呼び出し 1 つ(descがありvar_pathが無い)で、その関数の参照が報告済み(FAILURE.take_fn_failed)のときは、重ねて報告せずに印だけを返す。ACT_IMPL.callはkey == SKIP_CALLなら検索も警告もせずにnilを返す。印はモジュールの外へ公開されず、文字列やfalseと一致しない。 var_pathがあるときの警告は、WORD.dynamic_key(value, var_path, "act:call")を呼んで出させる(戻り値は使わない)。var_pathが無いときの警告のvalue=の表記は、ACT.num・ACT.strと同じ局所関数arith_value_textが作る。ACT_IMPL.callのkey == nilの分岐は、Lua から直接呼んだ場合のためのもので、対象の無い「名前にならない値」(説明は値なし)をFAILURE.reportで報告してnilを返す。生成コードの動的コールはcall_keyを通るため、nilのキーでact:callを呼ぶことはない。ACT_IMPL.callは、見つかった値が関数でないとき(見つからないときを含む)、「シーンが見つからない」(対象は>と書いた名前)をhandler not foundの警告文とともにFAILURE.reportで報告し、その戻り値のnilを返す。failure(self, text, warning)は、FAILURE.report(self, { label = text, warning = warning })を呼んでnilを返す。辞書の失敗と同じ出口を通るため、textはエスケープされ、[logging] balloon_warningに従う。失敗表記はraw_scriptのトークンとして積まれ、talk_to_scriptを通らないため、句読点のウェイトも budoux の改行も入らない。raw_scriptのグループ化と組立(保留中の段落区切りの改行が失敗表記の前には出ないことを含む)は グループ化トークン・さくらスクリプトの組立 で扱う。
名前の解決のメソッド
ACT の word・expr_fn・expr_fn_var・find_scene・call・call_restore と find_handler・find_act_handler の引数・戻り値・警告は 検索と呼び出し(word(name, var_path)・expr_fn_var(value, var_path, …) を含む)が、検索の各段の意味は ローカル優先の検索順 が正である。ここでは実装の内部の構成だけを扱う。
expr_fn(self, key, ...)とexpr_fn_var(self, value, var_path, ...)は、pasta.actの局所関数call_expr(self, key, skip_methods, ...)を共有する。expr_fnはcall_expr(self, key, nil, ...)、expr_fn_varはキーに直した後にcall_expr(self, キー, true, ...)を呼ぶ。call_exprはfind_handler("expr", key, skip_methods)が関数なら、h(self, ...)を呼んでrestore_sceneでcurrent_sceneを呼ぶ前の値に戻してから戻り値を返し、それ以外は「報告済み」の印(FAILURE.set_fn_failed)を真にし、接頭辞act:expr_fnの警告(handler not found)とともに「関数が見つからない」をFAILURE.reportで報告してnilを返す。関数が見つかったときは、関数が戻った後に印を偽にする。アクタープロキシはpasta.actorに同じ形の別の局所関数を持つ(PROXY_IMPL のメソッド)。skip_methodsを真にするのは、var_pathを受け取ったwordとexpr_fn_var(生成コードの動的参照)だけである。find_scene・callと、var_pathの無いword・expr_fnはskip_methodsを渡さない。find_handlerはfind_act_handlerに引数をそのまま渡す。
生成コード用のメソッド(actor_proxy・global_fn)
アクション行のアクター(act:actor_proxy("名前"))と @*名前(…)(act:global_fn("名前", …))の生成コードが呼ぶメソッドである。引数・戻り値・警告の文言は アクター・グローバル関数 が、生成コードの形は 生成される Lua コードの形 が正である。ここでは実装の内部の構成だけを扱う。
actor_proxy(self, name)は、self.actors[name]があればACTOR.create_proxy(アクター, self)を返す。無ければself.tokenを末尾から見て、最初に当たる話者のトークン(talk・sakura_script・actor付きのraw_script)のactor.nameがnameと同じならそのactorの表を再利用してプロキシを作る。そうでなければ{ name = name }(メタテーブルなし)を作り、FAILURE.mark_unregistered_actorで未登録の印を付け、「未登録アクター」をFAILURE.reportで報告して目印(actor付きのraw_scriptトークン)を積んでからプロキシを作る。再利用によって同じ未登録の名前の連続する発言は同じ表を持ち、buildのグループ化(表の同一性で判定する)で 1 つのグループになる(グループ化トークン)。- その場限りのアクターは
STORE.actors・self.actors・STORE.actor_spotsのどれにも書かれず、ACT にもフィールドを足さない。状態はself.tokenの中にしか無いため、build・yieldでトークンが空になると、次の発言で目印がまた付く。nameだけの表のため、プロキシの検索の A1 はnameにしか一致せず、A2 のアクター単語も無い(PROXY_IMPL のメソッド)。 global_fn(self, name, ...)はGLOBAL[name]が関数なら、f(self, ...)を呼んでrestore_sceneでcurrent_sceneを戻してから戻り値をすべて返し、関数でなければ「報告済み」の印を真にし、「関数が見つからない」をFAILURE.reportで報告してnilを返す。名前の解決の 5 段の検索(find_act_handler)は通らない。関数の中で起きたエラーは捕まえない。
被演算子の変換(ACT.num・ACT.str)
式の算術と連結の生成コードが被演算子に使う PASTA.num・PASTA.str の実体である。引数・戻り値・警告の文言は PASTA.num・PASTA.str が、生成コードの形は 生成される Lua コードの形 が正である。ここでは実装の内部の構成だけを扱う。
num(op, v, desc)・str(v, desc)はpasta.actのモジュールの関数(ACT.num・ACT.str)で、ACT_IMPLには置かれない。そのため ACT の__indexとfind_act_handlerの 3 段目には現れない(メソッドとアクタープロキシの解決)。selfを取らず、失敗の報告のほかは ACT の状態を読み書きしない。- 値の判定は
typeで行う。numはnumberをそのまま、stringをtonumberで、strはstringをそのまま、numberをtostringで変換する。nilは警告せずに0・""を返し、それ以外の値(numではtonumberで数値にできない文字列も含む)は、実行中の act(FAILURE.running_act())へ「数にできない値」・「文字列にできない値」をFAILURE.reportで報告して(警告 1 行。act が見つからなければ警告だけ)0・""を返す。表にtostringしないため、__tostringなどのメタメソッドは呼ばれない。警告のvalue=の表記は、call_keyと同じ局所関数arith_value_textが作る。 pasta(pastaモジュール)は、PASTAのメタテーブルの__indexでnum・strを初めて参照したときにrequire("pasta.act")し、参照したキーの関数(ACT.numかACT.str)をrawsetでPASTAに載せる。以後の参照はメタテーブルを通らず、PASTA.numとACT.numは同じ関数である。require "pasta"の時点ではpasta.actを読み込まないため、pastaを読み込んでもpasta.actの読み込みの順序は変わらない。
動的参照のキー(WORD.dynamic_key)
WORD.dynamic_key の変換規則と警告は WORD.dynamic_key(value, var_path, via, act) が正である。この関数は、pasta.act(word・expr_fn_var)と pasta.actor(同名のプロキシのメソッド)の両方が使うため、どちらも require しない pasta.word に置かれている。pasta.word が require するのは pasta.store と pasta.failure だけであり、pasta.act・pasta.actor が pasta.word を require しても循環は生じない。呼び出し側は 4 番目の引数に失敗表記を積む act を渡す(pasta.act は self、pasta.actor は self.act)。
失敗の報告(pasta.failure)
pasta.failure は、実行時の失敗の報告の唯一の出口である。失敗の種類・表記・出る位置・重ねない規則は 実行時の失敗の出力 が正である。ここでは実装の内部の構成だけを扱う。
FAILURE.report(act, record)は、record.warningがあれば@pasta_logのwarnで 1 行出し、FAILURE.enabled()が真ならFAILURE.formatで作った表記を{ type = "raw_script", text = 表記, actor = record.actor }としてact.tokenに積んでnilを返す。偽のときは、未登録アクターの目印(record.actorあり)だけを空文字列のtextで積む。actがnilなら警告だけを出す。FAILURE.notation(record)は警告を出して表記の文字列(偽ならnil)を返し、トークンには積まない。- 記録(
FailureRecord)はlabel(種類の名前。FAILURE.LABELの値)・subject(対象)・detail(説明)・warning(ログの警告文)・actor(未登録アクターの目印のときのアクター)を持つ。報告のたびに作って捨てる。 FAILURE.format(label, subject, detail)は中身をFAILURE.escape(\→\\、%→\%、CR・LF → 空白)にかけてから【】で囲む。対象の名前はFAILURE.var(var.x→$x・save.x→$*x・args[1]→$0)とFAILURE.fn(@f()→@f()など)で辞書の書き方に直す。知らない形はそのまま返す。FAILURE.enabled()は報告のたびにpasta.configのget("logging", "balloon_warning", true)を読み、真偽値でなければtrueを返す。@pasta_logとpasta.configは報告の時点でrequireするため、pasta.failureは読み込みの時点では他のモジュールをrequireしない。そのためpasta.act・pasta.actor・pasta.word・pasta.shiori.sakura_builderのどれからrequireしても循環は生じない。- 状態は弱参照の表 3 つで持ち、永続化しない。
RUNNING(スレッド → 実行中の act。init_sceneがFAILURE.bindで書き、ACT.num・ACT.strがFAILURE.running_actで引く)、FN_FAILED(act → 直前の関数の参照が報告済みか。set_fn_failed・take_fn_failed)、UNREGISTERED(アクターの表 → 未登録の印。mark_unregistered_actor・is_unregistered_actor。sakura_builderのemit_actor_switchが、印のあるアクターでは立ち位置の警告を出さないために読む)である。
PROXY パターン
アクタープロキシは、act:actor_proxy("さくら") を呼んだとき、または act.さくら のようにアクター名で ACT を参照したときに作られる小さな表である。アクターと ACT への参照を持ち、アクターを添えて ACT のメソッドに委ねる。生成コードはアクターの発言と、アクター修飾付きの単語参照・関数呼び出しを、actor_proxy で得たプロキシで書く。
act:actor_proxy("さくら"):talk("こんにちは")
act:actor_proxy("さくら"):talk(act:actor_proxy("さくら"):word("名前")) -- アクター単語から探す
act:actor_proxy("さくら"):talk((act:actor_proxy("さくら"):expr_fn("関数名", 引数))) -- アクター修飾付きの関数呼び出し
act:actor_proxy("さくら"):talk(act:actor_proxy("さくら"):word(var.x, "var.x")) -- 動的単語参照(変数の値をキーにする)
プロキシの実装(PROXY_IMPL)とアクターオブジェクトは、どちらも pasta.actor にある。
アクターオブジェクト
アクターオブジェクトは name(アクター名)と spot(nil または数値)を持つ表で、メタテーブル ACTOR_IMPL を持つ。
| 関数・メソッド | 内容 |
|---|---|
ACTOR.get_or_create(name) | STORE.actors[name] が無ければ { name = name, spot = nil } を作ってメタテーブルを付け、登録する。あればそれを返す。同じ名前には常に同じオブジェクトを返す。pasta の create_actor はこの関数そのもの |
ACTOR_IMPL.create_word(self, key) | アクター単語のビルダーを返す。生成コードの ACTOR:create_word(キー):entry(値, …) が使う |
ACTOR.create_proxy(actor, act) | アクタープロキシを作る(次節) |
create_wordが返すビルダー(ACTOR_WORD_BUILDER_IMPL)は、内部にWORD.create_actor(アクター名, キー)のビルダーを_word_builderとして持ち、entry(...)を値が 1 つ以上あるときだけそれに渡して自身を返す。登録先はSTORE.actor_wordsだけであり、アクターオブジェクトのフィールドには値を設定しない。pasta.actorは読み込み時に、STORE.actorsの要素のうち表であるもの(@pasta_configのactorから来たアクター)に、nameが無ければ表のキーをnameとして補い、メタテーブルACTOR_IMPLを付ける。そのためpasta.tomlの[actor.名前]に書いたキー(spotなど)は、アクターオブジェクトのフィールドとして見える。
プロキシの構造と生成
ACTOR.create_proxy(actor, act) は { actor = アクター, act = ACT } にメタテーブル PROXY_IMPL(__index は PROXY_IMPL 自身)を付けて返す。ACT の actor_proxy と __index が、呼び出し・参照のたびにこの関数を呼ぶ(生成コード用のメソッド・メソッドとアクタープロキシの解決)。プロキシは状態を持たず、同じアクターのプロキシを何度作っても振る舞いは変わらない。
PROXY_IMPL のメソッド
| メソッド | 処理 | 戻り値 |
|---|---|---|
talk(self, text, var_name) | self.act:talk(self.actor, text, var_name) | nil(メソッドチェーンはできない) |
sakura_script(self, text) | self.act:sakura_script(self.actor, text) | nil |
find_actor_handler(self, mode, key, skip_methods) | mode が "word" でなければ nil。self.actor[key](skip_methods が真なら rawget(self.actor, key))が nil でなければそれ(A1)。次に @pasta_search を pcall(require, …) で得られれば SEARCH:search_word(key, "__actor_" .. アクター名 .. "__")(A2) | 見つかった値、または nil |
find_handler(self, mode, key, skip_methods) | find_actor_handler で見つからなければ self.act:find_act_handler(mode, key, skip_methods) に委ねる。skip_methods は両方に渡す | 見つかった値、または nil |
word(self, name, var_path) | var_path が nil なら、name が nil か空文字列のとき nil を返す。var_path があれば WORD.dynamic_key(name, var_path, "proxy:word", self.act) でキーにし(nil ならそこで nil を返す)、skip_methods を真にする。そのうえで find_handler と同じ順序で、find_actor_handler("word", …)(アクターの段)、見つからなければ self.act:find_act_handler("word", …)(ACT の段)を引く(ACT の word と同じ規則。word(name, var_path))。結果が関数なら、アクターの段で見つかったときは h(self)(引数はプロキシ)、ACT の段で見つかったときは h(self.act)(引数は ACT)を呼び、戻り値を drop_self で正規化して返す。それ以外の値なら tostring(h)。見つからなければ「単語が見つからない」を FAILURE.report(self.act, …) で報告する(警告は via=proxy(アクター名)) | 単語の文字列、関数の戻り値(正規化後)、または nil |
expr_fn(self, key, ...) | 局所関数 call_expr(self, key, nil, ...) | 関数の戻り値、または nil |
expr_fn_var(self, value, var_path, ...) | value を WORD.dynamic_key(value, var_path, "proxy:expr_fn", self.act) でキーにし(nil ならそこで nil を返す)、call_expr(self, キー, true, ...) | 関数の戻り値、または nil |
call_exprはpasta.actorの局所関数で(pasta.actの同名の局所関数とは別)、find_handler("expr", key, skip_methods)の結果が関数ならh(self.act, ...)(第 1 引数は ACT)を呼び、self.act:restore_sceneでcurrent_sceneを呼ぶ前の値に戻し、戻り値をdrop_selfで正規化して返す。プロキシのwordも、関数を呼んだ後に同じくcurrent_sceneを戻す。関数でなければ「関数が見つからない」をFAILURE.report(self.act, …)で報告する(警告はproxy:expr_fn - handler not found)。「報告済み」の印の読み書きは ACT のcall_exprと同じである。"expr"モードではfind_actor_handlerがnilを返すため、見つかる関数は常に ACT の段のものである。drop_self(self, r, ...)はpasta.actorの局所関数で、先頭の戻り値rがself.actまたはself(プロキシ)と==で等しければnilを返し、そうでなければr, ...を複数の戻り値も含めてそのまま返す。ACT・プロキシは__eqを持たないため、比較は同一性である。正規化はプロキシのword・expr_fn・expr_fn_varだけが行い、ACT のword・expr_fn・expr_fn_var・global_fnは関数の戻り値をそのまま返す。- A1 は通常の添字参照であるため、アクターオブジェクトのフィールド(
name・spot・pasta.tomlの[actor.名前]のキー)と、メタテーブル経由のcreate_wordも一致の対象になる。skip_methodsが真のとき(動的参照)はrawgetで引くため、アクターオブジェクト自身のフィールドだけが対象になり、create_wordなどのメソッドには一致しない。 - A2 が渡すスコープ名は元のアクター名から組み立てるが、
search_wordの入口でサニタイズされてから照合されるため、記号を含むアクター名(さくら・改)でも、register_actorがサニタイズした名前で登録したキー(:__actor_さくら_改__:…)に一致する(照合規則の共有)。 - 検索の全体の順序(A1 → A2 → L1〜L5)と各段の意味は ローカル優先の検索順 で扱う。
wordが見つけた関数に渡す第 1 引数は、アクターの段で見つかったときはプロキシ、ACT の段で見つかったときは ACT である。expr_fn・expr_fn_varが見つけた関数には常に ACT を渡す。ACT のword・expr_fn・expr_fn_var・callが見つけた関数にも ACT を渡す。利用者から見た規則は 関数スコープの展開先 が正である。
SCENE モジュール
pasta.scene は、シーンテーブルの作成と登録、検索結果からシーン関数への解決、シーン関数のコルーチン化を担うモジュールである。
local SCENE = require("pasta.scene")
シーンテーブル
シーンテーブルは、グローバルシーン 1 つにつき 1 つ作られ、STORE.scenes[グローバルシーン名] に入る。
STORE.scenes = {
["メイン_1"] = { -- シーンテーブル
__global_name__ = "メイン_1",
__start__ = function(act, ...) … end, -- グローバルシーンの本体
["選択肢_1"] = function(act, ...) … end, -- ローカルシーン
-- メタテーブル: __index = SCENE_TABLE_IMPL(create_word を持つ)
},
}
- グローバルシーン名(登録名)は、照合用の名前・区切りの
_・通し番号の 3 つからなる(base_name .. "_" .. 番号)。照合用の名前はトランスパイラが渡す基本名(sanitize_nameでサニタイズ済みのシーン名)で、通し番号はcreate_sceneが照合用の名前ごとに 1 から振った番号である(1 つ目の*メインはメイン_1、1 つ目の*挨拶・朝は挨拶_朝_1)。基本名は宣言名(pasta.toml の別名表で置き換えた後の名前)から作られるため、既定の別名表のもとで*OnTalkの無いゴーストの 1 つ目の*会話はOnTalk_1になる(登録と生成)。区切りがあるため、*A1の 1 つ目(A1_1)と*Aの 11 個目(A_11)は別の登録名になる。Rust 側で同じ形を作るのはpasta_coreのSceneRegistry::registered_nameである。 - ローカルシーンのキー(生成コードの関数名)も同じ形の登録名である。照合用の名前は
・の後に書いたローカルシーン名をサニタイズしたもので、通し番号は同じグローバルシーンの中で照合用の名前ごとに 1 から数える(・選択肢の 1 つ目は選択肢_1。・挨拶・1と・挨拶_1はどちらも照合用の名前が挨拶_1なので挨拶_1_1・挨拶_1_2)。無名の開始シーンは__start__である。 - 登録名を(名前, 通し番号)に分けるのは Rust の
SceneRegistry::split_registered_nameだけであり、Lua 側には分ける関数は無い。最後の_の後ろが 1 文字以上の半角数字(0〜9)で、_の前が空でないときだけ分ける(挨拶_朝_1→ (挨拶_朝, 1))。それ以外(__start__・加算ループ・_1・全角数字で終わる名前・usizeに収まらない数字列)は分けず、名前の全体を通し番号なしとして扱う。Lua ブロックで直接定義した関数も同じ規則で分けるため、step_2は(step, 2)になる。分けた結果を使うのは、検索キーの作成(検索キーの形式)、辞書確定の登録順(辞書確定)、デバッガの突き合わせ(シーン identity 索引)である。 __global_name__以外のキーはすべてシーン関数として扱われ、辞書確定で(グローバルシーン名, キー)の組として集められる(finalize_scene)。- メタテーブルの
__indexがSCENE_TABLE_IMPLを指すため、scene:create_word(キー)はWORD.create_local(scene.__global_name__, キー)のビルダーを返す。生成コードのSCENE:create_word(キー):entry(値, …)がこれを使う。通常の添字参照(ACT の L1 の完全一致を含む)でも、キーcreate_wordはこのメソッドに一致する。動的参照の L1 はrawgetで引くため、シーンテーブル自身のキー(__global_name__とシーン関数)だけが対象になり、create_wordには一致しない。
SCENE の関数
| 関数 | 内容 |
|---|---|
create_scene(base_name, local_name, scene_func) | get_or_increment_counter(base_name) で番号を得て、グローバルシーン名 base_name .. "_" .. 番号 を作る。local_name と scene_func が両方あれば register で登録する。そのグローバルシーン名のシーンテーブルを(無ければ作って)返す。pasta の create_scene はこの関数そのもの |
get_or_increment_counter(base_name) | STORE.counters[base_name] を 1 増やして返す(最初は 1) |
register(global_name, local_name, scene_func) | シーンテーブルが無ければ作り、[local_name] = scene_func とする |
create_global_table(global_name) | シーンテーブルが無ければ作って返す |
get_global_table(global_name) | STORE.scenes[global_name] を返す |
get(global_name, local_name) | シーン関数を返す。無ければ nil |
get_start(global_name) | get(global_name, "__start__") |
get_global_name(scene_table) | scene_table.__global_name__ を返す |
get_all_scenes() | STORE.scenes そのもの(複製しない)を返す |
search(name, global_scene_name, attrs) | 前方一致の検索でシーンを探し、結果オブジェクトを返す(後述) |
co_exec(act, name, global_scene_name, attrs) | act:find_scene で探したシーン関数をコルーチンに包んで返す(後述) |
co_exec_exact(name, local_name) | 完全一致の検索で探したグローバルシーンかローカルシーンをコルーチンに包んで返す(後述) |
co_exec_registered(global_name, local_name) | 登録名の組で引いたシーン関数をコルーチンに包んで返す(後述) |
- 生成コードは
create_sceneを基本名だけで呼び(local SCENE = PASTA.create_scene("メイン"))、返ったシーンテーブルにfunction SCENE.__start__(act, ...)の形で関数を直接定義する。register・create_global_tableなどはpasta.sceneの内部とテストが使う。 - 番号は基本名ごとに全モジュールを通して振られる。
pasta.scene_dicがモジュールを名前順にrequireするため、同じ基本名のシーンの番号はその順(モジュールの中では出現順)で決まる。
search と結果オブジェクト
SCENE.search(name, global_scene_name, attrs) は次の順に処理する。attrs は使わない。
nameが文字列でなければnilを返す。@pasta_searchをrequireし(呼び出し時に読み込む)、SEARCH:search_scene(name, global_scene_name)を呼ぶ。global_scene_nameがnilならグローバルシーンだけを探してローカル名"__start__"を、文字列ならそのグローバルシーンの中のローカルシーンだけを探す。- 見つからなければ
nil。見つかった(グローバルシーン名, ローカル名)でSCENE.getを引き、関数が無ければnilを返す。 - 次の結果オブジェクトを返す。
--- @class SceneSearchResult
--- @field global_name string グローバルシーン名
--- @field local_name string ローカル名(グローバルシーンなら "__start__")
--- @field func function シーン関数
-- メタテーブルの __call により result(...) は result.func(...) と同じ
前方一致の候補の集め方と選び方は シーン・単語レジストリとシーン検索 で扱う。ACT の名前の解決(L2・L5)は結果オブジェクトの func を使う。
co_exec
SCENE.co_exec(act, name, global_scene_name, attrs) は、act:find_scene(name, global_scene_name, attrs) でシーン関数を探し、関数でなければ nil を、関数ならそれを包んだコルーチンを返す。act:find_scene は global_scene_name と attrs を使わず find_handler("scene", name) に委ねるため、co_exec で親のグローバルシーンを指定してローカルシーンを引くことはできない。第 1 引数の act は名前の解決にだけ使い、シーン関数には coroutine.resume で渡された ACT が渡る。コルーチンの包み方と再開は シーンコルーチンの作り方 で扱う。
co_exec_exact
SCENE.co_exec_exact(name, local_name) は、完全一致の検索でシーンを探し、co_exec_registered でコルーチンにして返す。act を取らず、act:find_scene の名前の解決(act のメソッド・GLOBAL テーブルなど)を通らない。
local_nameがnilのとき:@pasta_searchのSEARCH:search_scene_exact(name, nil)でグローバルシーンを探す(search_scene_exact)。local_nameを渡したとき:SEARCH:search_local_scene_exact(name, local_name)で、nameのグローバルシーンの中のlocal_nameのローカルシーンを探す(search_local_scene_exact)。2 つの引数はどちらも作者が書く名前である。
戻り値は、見つかればコルーチン 1 つである。見つからないか、引いた値が関数でなければ、nil と見つからなかった側("global" か "local")の 2 値を返す。local_name が nil のときは常に "global" である。local_name を渡したときは、グローバルシーンが無ければ "global"、グローバルシーンはあるがローカルシーンが無いか関数でなければ "local" である。
つつき・撫での既定ハンドラ(pasta.shiori.event.mouse)は、local_name を渡さずにショートカットの 1〜8 段の名前を探す(つつき・撫でのショートカット)。OnPastaScene の既定ハンドラ(pasta.shiori.event.scene_play)は、Reference0 と Reference1(空の文字列なら nil)を渡し、戻り値の 2 つ目で失敗の表記を選ぶ(OnPastaScene)。
co_exec_registered
SCENE.co_exec_registered(global_name, local_name) は、登録名の組で SCENE.get(global_name, local_name) を引き、関数なら co_exec と同じ包み方のコルーチンを返す。関数でなければ nil を返す。検索をしないため、シャッフル&順次消費の記録は進まない。local_name に "__start__" を渡すと、グローバルシーンの本体を引く。co_exec_exact は、検索で得た登録名の組をこの関数に渡す。OnPastaSceneAt の既定ハンドラは、位置から引いた登録名の組を渡す(OnPastaSceneAt)。
DSL の Lua ブロックとの接続
トランスパイラは、グローバルシーンとその配下に書かれた Lua ブロックを、そのグローバルシーンの do … end の中、すべてのシーン関数の定義の後に出力する(生成される Lua コードの形)。この位置ではローカル変数 SCENE がそのシーンテーブルを指すため、Lua ブロックで定義した関数はシーンテーブルに入る。
function SCENE.関数名(act, ...)
local save, var = act:init_scene(SCENE)
act.さくら:talk("セリフ")
end
- こうして定義した関数は、生成されたシーン関数と同じくシーンテーブルのキーになり、辞書確定でローカルシーンとして集められる。
- DSL の
@関数名(…)はact:expr_fn("関数名", …)などに、>関数名はact:call(…)・act:call_restore(…)になり、どちらも L1 の完全一致(current_scene[key])でこの関数に一致する。 - 関数の中で
init_sceneを呼ぶとcurrent_sceneとlast_global_sceneがこのシーンテーブルになり、save・varが得られる(init_scene)。途中の Call・式の関数呼び出しから呼ばれた場合は、戻った後にcurrent_scene(と書いた名前・行の対応)だけが呼ぶ前の値に戻る(シーン文脈の復元)。書き方の利用者向けの説明は スクリプト用ランタイム API で扱う。
SAVE モジュールの内部
pasta.save は、@pasta_persistence の load() の結果をそのまま返すモジュールである。実装は次のとおりである。
local persistence = require("@pasta_persistence")
local save = persistence.load()
return save
load()が呼ばれるのは、pasta.saveが最初にrequireされたときの 1 回だけである。結果の表はpackage.loaded["pasta.save"]に入り、以後のrequireは同じ表を返す。ACT.newが ACT ごとにrequire("pasta.save")をact.saveに入れるため、全 ACT とinit_sceneの戻り値saveは同じ表を指す。- ランタイムの破棄時に保存されるのは
package.loaded["pasta.save"]の表である。読み書きの時点と保存の処理は 永続化 で、@pasta_persistenceの API と設定は @pasta_persistence で扱う。キーの命名規約とアクセスの方法は スクリプト用ランタイム API で扱う。
finalize_scene
finalize_scene は、Lua 側に登録されたシーンと単語から @pasta_search を作り直す関数である。この節では Lua 側の窓口と、Rust 側に渡す収集データの構造を扱う。Rust 側での収集・レジストリの構築・@pasta_search の登録は 辞書確定 で扱う。
require("pasta").finalize_scene()
Lua 側の窓口
pasta(pastaモジュール)は、何もしないスタブのPASTA.finalize_sceneを持つ。- ランタイムの構築は、
main・pasta.shiori.entry・pasta.scene_dicを読み込む前に、Rust のregister_finalize_sceneでこのスタブを Rust の関数に置き換える。pastaがまだ読み込まれていなければ、その場でrequireしてから置き換える。 - そのため、ランタイムの上で
finalize_sceneを呼ぶと常に Rust 側の処理が動く。Rust を通さずにpastaを読み込む環境(単体テストなど)では、スタブが呼ばれて何も起きない。 @pasta_searchは VM の構築時にも、トランスパイル時のレジストリから一度登録されている。finalize_sceneはそれを、Lua 側の登録から作った検索表で置き換える。
呼び出しタイミング
finalize_scene を呼ぶのは、ローダが生成する pasta.scene_dic(scene_dic.lua)の末尾である。pasta.scene_dic は全シーンモジュールを名前順に require してから finalize_scene を呼ぶ。
-- scene_dic.lua(自動生成)
require("pasta.scene.main")
require("pasta.scene.sub")
require("pasta").finalize_scene()
- シーンモジュールの実行(生成コードの
create_actor・create_scene・create_wordと、シーンテーブルへの関数定義)で、Lua 側のテーブルが埋まる。finalize_sceneはその後に 1 回だけ呼ばれる。 mainはpasta.scene_dicより前に読み込まれるため、mainでWORD.create_globalなどを使って登録した単語も収集の対象になる。finalize_sceneより後に登録したシーン・単語は、もう一度finalize_sceneを呼ぶまで検索の対象にならない。scene_dic.luaの生成は トランスパイル結果キャッシュ で扱う。
シーン収集データ構造
Rust 側は pasta.scene の get_all_scenes() を呼び、STORE.scenes を受け取る。
{
["メイン_1"] = {
__global_name__ = "メイン_1",
__start__ = function(act, ...) … end,
["選択肢_1"] = function(act, ...) … end,
},
}
- 外側のキーはグローバルシーン名、内側はシーンテーブル(シーンテーブル)である。
- Rust 側での集め方(
__global_name__以外のキーを組にすること、関数を写さないこと)は 辞書確定 で扱う。
単語収集データ構造
Rust 側は pasta.word の get_all_words() を呼ぶ。この関数は、STORE の 3 つの表をまとめた新しい表を返す。
{
global = { -- STORE.global_words
["キー"] = { { "値1", "値2" }, { "値3" } },
},
["local"] = { -- STORE.local_words
["メイン_1"] = { -- グローバルシーン名
["キー"] = { { "ローカル値" } },
},
},
actor = { -- STORE.actor_words
["さくら"] = { -- アクター名
["キー"] = { { "アクター値" } },
},
},
}
- 単語キーの値は「値リストの配列」である。ビルダーの
entry(...)を 1 回呼ぶごとに、その引数を値リスト 1 つとして配列の末尾に足す。引数が 0 個のentry()は何も足さない。 - Rust 側での集め方(値リスト 1 つにつき 1 つの収集項目を作り、収集した表でスコープが決まること)は 辞書確定 で扱う。
localは Lua の予約語であるため、表のキーは["local"]と書く。
単語ビルダーの内部
pasta.word のビルダー(WORD_BUILDER_IMPL)は、登録先の表 _registry と単語キー _key だけを持つ。
| 生成 | 登録先(_registry) |
|---|---|
WORD.create_global(key)(別名 WORD.create_word・pasta の create_word) | STORE.global_words |
WORD.create_local(scene_name, key)(scene:create_word(key) が使う) | STORE.local_words[scene_name](無ければ作る) |
WORD.create_actor(actor_name, key)(アクターの create_word が使う) | STORE.actor_words[actor_name](無ければ作る) |
- ビルダーの生成時に
_registry[key]が無ければ空の配列を作る。そのため、entryを呼ばずに作っただけのビルダーでも、値リストを 1 つも持たないキーが残る。 entry(...)はtable.insert(_registry[_key], { ... })を行って自身を返す。同じキーのビルダーを何度作っても、同じ配列に値リストが足されていく。get_global_words()・get_local_words(scene_name)・get_actor_words(actor_name)は STORE の該当する表をそのまま返す。
ユーティリティモジュール
pasta.buf
pasta.buf は、文字列を連結するためのバッファを作るモジュールである。LuaJIT の String Buffer Library(string.buffer)があればそれを使い、無ければ同じメソッドを持つ最小実装を使う。pasta.shiori.sakura_builder がさくらスクリプトの組立に使う(さくらスクリプトの組立)。
local buf = require("pasta.buf")
local b = buf.new()
b:put("Hello"):put(", "):put("World")
local s = b:tostring() -- "Hello, World"
| API | 内容 |
|---|---|
buf.new() | バッファを作る。LuaJIT のときは string.buffer の new そのもの、それ以外は new_fallback |
buf.backend | 採用したバックエンド("luajit" か "fallback") |
buf.new_fallback() | 最小実装のバッファを明示的に作る(環境に依らないテストのため) |
buffer:put(s) | 文字列を追記して自身を返す |
buffer:tostring() | 追記した順に連結した文字列を返す。バッファの中身は消えない |
- バックエンドの選択は、モジュールの読み込み時に 1 回だけ
pcall(require, "string.buffer")で行う。requireに失敗しても、newが関数でなくても、例外は送出せず最小実装を選ぶ。 - LuaJIT のときは
string.bufferのオブジェクトをラップせず生成関数を直接束ねるため、間接呼び出しの負担は無い。呼び出し側はput・tostring以外のメソッドに依存しない。 - 最小実装は内部の配列
_partsに追記し、tostringでtable.concatする。
pasta.lua_version
pasta.lua_version は、実行中のランタイムの種類(標準 Lua か LuaJIT か)と版を 1 つの整数で返す。
local lua_version = require("pasta.lua_version")
local v = lua_version.get() -- このランタイム(LuaJIT 2.1)では 221
if v >= 200 then
-- LuaJIT 固有の機能(string.buffer など)を使える
end
| 戻り値 | 意味 |
|---|---|
1xy | 標準 Lua x.y(例: 154 は Lua 5.4、155 は Lua 5.5) |
2xy | LuaJIT x.y(例: 220 は LuaJIT 2.0、221 は LuaJIT 2.1) |
- 種類は
rawget(_G, "jit")の有無で判定する。LuaJIT でも_VERSIONは"Lua 5.1"を返すため、種類の判定には使わない。 - LuaJIT の版は
jit.version_num(例20100)から求め、数値でなければjit.version("LuaJIT 2.1.…")のmajor.minorを解析する。どちらも得られなければ221を返す。 - 標準 Lua の版は
_VERSIONのmajor.minorを解析し、得られなければ151を返す。 - 例外を送出せず、常に整数を返す。
部品の顔ぶれ、いくつ覚えられたかしら……。一度で覚えきれなくても構いませんわ。必要なときに、またこの棚へ戻っていらっしゃい。
部品を足す折にも、pasta.store へ向かう一方通行の依存だけは崩してはなりませんね。