← 仕様書・設計書一覧
KNF インタプリタ仕様書(作成途中)
KNF(.ksc)スクリプトを実行するインタプリタの仕様書です。Op 命令セット(JSON IR)とは別系統の、テキストベーススクリプトを直接実行するランタイムです。
Phase 7-3 まで実装済み(107テスト通過)。一部の再帰テスト・大規模ループテストは未解決。
アーキテクチャ概要
.ksc スクリプト
│
▼
┌───────────┐
│ Tokenizer │ 式文字列をトークン列に分割
└───────────┘
│
▼
┌───────────┐
│ Parser │ 行単位で分類・ブロック構造解析
└───────────┘
│
▼
┌─────────────┐
│ Interpreter │ メインループで逐次実行
│ ├─ Evaluator │ 式の評価(再帰下降パーサー)
│ ├─ GameState │ 変数・スコープ・コールスタック管理
│ └─ Debugger │ デバッグ機能
└─────────────┘
│
▼
┌────────────┐
│ IEngineAPI │ プラットフォーム抽象化(描画・音声・UI)
└────────────┘コアモジュール
Interpreter
メインの実行エンジン。.ksc スクリプトを行単位で解析・実行します。
| メソッド | 説明 |
|---|---|
run(script) | スクリプト全体をパース・実行 |
stop() | 実行を停止 |
jumpTo(label) | 指定ラベルへジャンプ |
getState() | 現在の PC・変数・コールスタック深度を返す |
getDebugger() | デバッガインスタンスを取得 |
Parser
行レベルの構文解析を行います。
| LineType | パターン | 説明 |
|---|---|---|
DialogueStart | #speaker | ダイアログブロック開始 |
DialogueEnd | # | ダイアログブロック終了 |
Label | *label_name | ラベル定義 |
Comment | // ... | コメント |
Empty | (空行) | 空行 |
Expression | その他 | 式・コマンド |
Evaluator
式を評価する再帰下降パーサーです。
| 種別 | 演算子 |
|---|---|
| 算術 | +, -, *, /, % |
| 比較 | ==, !=, >, >=, <, <= |
| 論理 | &&, ||, ! |
| 代入 | =, +=, -=, *=, /= |
| グループ | (, ) |
演算子優先順位(低→高): || → && → ==/!= → >/>=/</<= → +/- → *///% → 単項 → リテラル/変数/関数呼び出し
Tokenizer
式文字列をトークン列に分割するレキサーです。
| TokenType | 例 |
|---|---|
Number | 42, 3.14 |
String | "hello", 'world' |
Boolean | true, false |
Identifier | count, playerName |
Keyword | if, else, def, sub, return, choice |
Operator | +, -, ==, && |
Assign | =, +=, -=, *=, /= |
GameState
ランタイムの全状態を管理します。
| フィールド | 説明 |
|---|---|
variables | グローバル変数(Map) |
localScopes | ローカルスコープのスタック(Map[]) |
callStack | コールスタック(CallFrame[]) |
labelMap | ラベル→行番号マップ |
functions | def 定義(値を返す関数) |
subroutines | sub 定義(値を返さないサブルーチン) |
スコープ解決: getVar はローカルスコープ(後入れ優先)→ グローバルの順で検索。
IEngineAPI
プラットフォーム非依存の描画・音声・UI インターフェースです。
| メソッド | 説明 |
|---|---|
showDialogue(speaker, lines) | ダイアログ表示 |
setBg(name, effect?) | 背景設定 |
showChar(name, pose, position?) | キャラクター表示 |
hideChar(name) | キャラクター非表示 |
moveChar(name, position, time) | キャラクター移動 |
playBgm(name) | BGM 再生 |
stopBgm() | BGM 停止 |
fadeBgm(time) | BGM フェードアウト |
playSe(name) | 効果音再生 |
playTimeline(name) | タイムライン再生 |
showChoice(options) | 選択肢表示(Promise<number>) |
waitForClick() | クリック待ち |
wait(ms) | ミリ秒待機 |
ビルトインコマンド
| コマンド | 引数 | IEngineAPI |
|---|---|---|
bg(name) | 背景名 | setBg |
ch(name, pose, pos?) | キャラ名, ポーズ, 位置 | showChar |
ch_hide(name) | キャラ名 | hideChar |
bgm(name) | BGM名 | playBgm |
bgm_stop() | ― | stopBgm |
se(name) | SE名 | playSe |
wait(ms) | ミリ秒 | wait |
waitclick() | ― | waitForClick |
timeline(name) | タイムライン名 | playTimeline |
jump(label) | ラベル名 | PC を移動 |
call(label) | ラベル名 | コールスタックに積んでジャンプ |
ret() | ― | コールスタックから復帰 |
スクリプト構文
ダイアログ
#太郎
こんにちは!
今日はいい天気ですね。
##speaker で開始、# で終了。間の行がダイアログテキストとして表示されます。
条件分岐
if (flag == 1) {
#太郎
フラグが立っています。
#
} else if (flag == 2) {
#太郎
フラグは2です。
#
} else {
#太郎
フラグは立っていません。
#
}選択肢
choice {
"はい" {
yes_count += 1
}
"いいえ" {
no_count += 1
}
"条件付き" if (flag == 1) {
// flag が 1 の時のみ表示
}
}ユーザー定義関数
def(値を返す):
def add(a, b) {
return a + b
}
result = add(3, 5)sub(値を返さない):
sub greet(name) {
greeting = "Hello, " + name
}
greet("World")再帰呼び出し対応(深度上限: 16)。ローカルスコープで引数を管理。
文字列補間
count = 42
#ナレーター
現在のカウントは{count}です。
計算結果: {count * 2 + 1}
#デバッグ機能
変数ウォッチ
debugger.watchVariable("hp")
// ... 実行 ...
debugger.getVariableHistory("hp")
// → [{ line, oldValue, newValue, timestamp }, ...]ブレークポイント
debugger.addBreakpoint(10) // 行10で停止
debugger.addBreakpoint(20, "hp < 50") // 条件付き
debugger.toggleBreakpoint(10) // 有効/無効切替トレースログ
debugger.enableTrace()
// ... 実行 ...
debugger.getTraceLog()
// → [{ type, line, data }, ...]デバッグイベント
| DebugEventType | 発火タイミング |
|---|---|
VariableChanged | 変数の値が変更された |
Breakpoint | ブレークポイントに到達 |
StepComplete | ステップ実行完了 |
FunctionCall | 関数呼び出し |
FunctionReturn | 関数からの復帰 |
エラーハンドリング
| ErrorType | 説明 |
|---|---|
SyntaxError | 構文エラー |
ReferenceError | 未定義の変数・関数への参照 |
TypeError | 型の不一致 |
RuntimeError | 実行時エラー(ゼロ除算など) |
StackOverflow | 再帰深度超過(上限: 16) |
FileNotFound | ファイルが見つからない |
未定義の変数・関数を参照した際、Levenshtein 距離3以内の候補を「もしかして」として提案します。
実装フェーズ
| Phase | 内容 | 状態 |
|---|---|---|
| Phase 1 | 基本実行(ダイアログ、ビルトインコマンド) | 完了 |
| Phase 2 | ラベル・ジャンプ・call/ret | 完了 |
| Phase 3 | 式評価(算術・比較・論理・代入) | 完了 |
| Phase 4 | if/else/choice | 完了 |
| Phase 5 | ユーザー定義関数 def/sub | 完了(再帰テスト一部未解決) |
| Phase 6 | 文字列補間 | 完了 |
| Phase 7-1 | エラーハンドリング | 完了 |
| Phase 7-2 | デバッグモード | 完了 |
| Phase 7-3 | 統合テスト | 完了(107テスト通過) |
既知の問題
- Phase 5 の再帰テスト(fibonacci 等の二重再帰呼び出し)がハングする
- 大規模ループ + 関数呼び出しのテストがタイムアウトする
- ダイアログ内の文字列補間統合テストが一部スキップ(TODO)
playTimeline()は未実装