Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 のフィールド

フィールド型内容書き手と読み手
actorstable<string, Actor>アクター名 → アクターオブジェクト初期値は @pasta_config の actor。pasta.actor の get_or_create が追加する。ACT の生成時に act.actors として渡る
actor_spotstable<string, integer>アクター名 → スポット番号初期値は @pasta_config の actor.*.spot。pasta.shiori.act の build が sakura_builder に渡し、組立が spot・clear_spot のトークンで書き換える
appearancetable外見の状態 { actors, spots, owners, last_spots }(アクターの既知状態・スポットの表示中状態・スポットの直前の発話アクター・アクターの前回の発話スポット)。セッション中だけ保持し、永続化しないpasta.shiori.act の build が sakura_builder に渡す(アピアランスの観測と復旧)
scenestable<string, SceneTable>グローバルシーン名 → シーンテーブルpasta.scene が作り、get_all_scenes が返す
counterstable<string, number>シーンの基本名 → 最後に振った番号pasta.scene の get_or_increment_counter
global_wordstable<string, table>単語キー → 値リストの配列pasta.word の create_global のビルダー
local_wordstable<string, table>グローバルシーン名 → { 単語キー → 値リストの配列 }pasta.word の create_local のビルダー
actor_wordstable<string, table>アクター名 → { 単語キー → 値リストの配列 }pasta.word の create_actor のビルダー
app_ctxtableセッション中の汎用の表ACT の生成時に同じ表が act.app_ctx に入る
co_scenethread|nil中断中の継続待ちのコルーチン。台詞を出さない割り込みでは残り、台詞を出した割り込み・指名した再生・エラーで捨てられるランタイムの中では pasta.shiori.event の set_co_scene だけが書き、EVENT.drive が種類と台詞の印で呼ぶ。STORE.reset も nil に戻す(継続トーク(チェイントーク)と co_scene の更新)
co_preempttable<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_scenestring|nil最後に init_scene したシーンテーブルの __global_name__ACT_IMPL.init_scene だけが書く(Call から戻っても戻さない)。OnChoiceSelectEx の既定ハンドラが、Reference2 から選択肢を出したグローバルシーンが得られないときの、選択 ID の検索の親に使う
kick_pendingstring|nil保留中のキック対象のシーン名KICK.install が書き、KICK.try_dispatch が消費する(キックの保留と起動)
kick_forcebooleanキックの割り込み許可(既定 false)KICK.install が立て、仮想イベントの dispatch の入口が 1 回で下ろす
auto_talk_pausedboolean自動トークを止めている印(既定 false)。回数は数えず、永続化しないGLOBAL.pause_auto_talk が立て、GLOBAL.resume_auto_talk と STORE.reset が下ろす。仮想イベントの dispatch・ACT_IMPL.yield・つつき撫での既定ハンドラが読む(自動トークを止めている間)
co_callbackthread|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 そのもの
saverequire("pasta.save") の結果。全 ACT で同じ表(SAVE モジュールの内部)
app_ctxSTORE.app_ctx と同じ表
varACT ごとの新しい空の表
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 つだけである。

  1. scene.__global_name__ があれば、STORE.last_global_scene に記録する。pasta.scene が作るシーンテーブルはすべて __global_name__ を持つため、生成コードからの呼び出しでは常に記録される。
  2. self.current_scene = scene とする。
  3. scene_name・local_scene_name・line_map を同名のフィールドに書き(省略した引数は nil)、self.scene_names[scene] = scene_name とする。
  4. FAILURE.bind(self) で、実行中のスレッドにこの ACT を対応させる(失敗の報告)。
  5. 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 は使わない。

  1. name が文字列でなければ nil を返す。
  2. @pasta_search を require し(呼び出し時に読み込む)、SEARCH:search_scene(name, global_scene_name) を呼ぶ。global_scene_name が nil ならグローバルシーンだけを探してローカル名 "__start__" を、文字列ならそのグローバルシーンの中のローカルシーンだけを探す。
  3. 見つからなければ nil。見つかった (グローバルシーン名, ローカル名) で SCENE.get を引き、関数が無ければ nil を返す。
  4. 次の結果オブジェクトを返す。
--- @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)
2xyLuaJIT 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 へ向かう一方通行の依存だけは崩してはなりませんね。