|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CONTENTS |
はじめに / セットアップ / CLI / 対応ブロック一覧
実行ランタイムはまだ完成していないため、現在の中心は解析と変換のパイプラインです。 モジュール概要
データフロー詳細
全体フロー
ステージ 1: SB3 ロード ( src/sb3.rs )
主要な公開 API
処理の流れ
エラー報告の仕組み
エラー表示には
ステージ 2: 型定義とデシリアライズ ( src/types/ )
主要な型
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| バリアント | Scratch での表示 | opcode |
|---|---|---|
WhenFlagClicked
|
緑の旗が押されたとき |
EventWhenFlagClicked
|
WhenKeyPressed { key }
|
[キー]
キーが押されたとき
|
EventWhenKeyPressed
|
WhenThisSpriteClicked
|
このスプライトが押されたとき |
EventWhenThisSpriteClicked
|
WhenStageClicked
|
ステージが押されたとき |
EventWhenStageClicked
|
WhenBacdropSwitchesTo { backdrop }
|
背景が
[名前]
に切り替わったとき
|
EventWhenBackdropSwitchesTo
|
WhenGreaterThan { target, value }
|
[音量/タイマー]
が
[値]
より大きくなったとき
|
EventWhenGreaterThan
|
WhenBroadcastReceived { target }
|
[メッセージ]
を受け取ったとき
|
EventWhenBroadcastReceived
|
ControlStartAsClone
|
クローンされたとき |
ControlStartAsClone
|
ProcedureDefinition { prototype }
|
手続き定義(独自ブロック) |
ProceduresDefinition
|
WhenTouchingObject { object }
|
[対象]
に触れたとき
|
EventWhenTouchingObject
|
project_parser
関数は次の手順でスレッドを抽出します。
top_level: true
かつ hat block の opcode を持つブロックを見つける
hat.rs
で判定して
HatStmt
に変換する
next
フィールドをたどって続くブロック列を解析し、
Vec<Stmt>
を作る
Thread { hat, stmts, target_idx }
を生成して結果リストに追加する
パーサーが生成する AST は
parser/types.rs
で定義されています。
Stmt
(文ブロック)
pub enum Stmt {
Motion(MotionStmt), // 動き
Looks(LooksStmt), // 見た目
Sound(SoundStmt), // 音
Event(EventStmt), // イベント
Control(ControlStmt), // 制御
Sensing(SensingStmt), // 調べる
Operator(OperatorStmt), // 演算 (文はほぼない)
DataStmt(DataStmt), // データ
Procedures(ProceduresStmt), // 独自ブロック
PenStmt(PenStmt), // ペン
}
各カテゴリは対応する Scratch ブロックのすべての入力・フィールドを保持します。例えば
MotionStmt::GlideToXY
は秒数・X 座標・Y 座標を
Expr
として保持します。
Expr
(値ブロック / レポーター)
pub enum Expr {
Motion(MotionExpr),
Looks(LooksExpr),
Sound(SoundExpr),
Event(EventExpr),
Control(ControlExpr),
Sensing(SensingExpr),
Operator(OperatorExpr),
Data(DataExpr),
Procedures(ProceduresExpr),
Pen(PenExpr),
Literal(Literal), // 数値・文字列・変数参照などのリテラル
}
Expr
はネストできます。例えば「
(10 + x座標)
歩動かす」は次のように表現されます。
MotionStmt::MoveStep {
steps: Expr::Operator(OperatorExpr::Add {
left: Box::new(Expr::Literal(Literal::Number("10".to_string()))),
right: Box::new(Expr::Motion(MotionExpr::XPosition)),
})
}
Literal
(入力プリミティブ)
Literal
はブロック以外の生の入力値を表します。
pub enum Literal {
String(String), // テキスト入力
Number(String), // 数値入力(文字列として保持)
Variable { target: String }, // 変数参照 (変数 ID)
List { target: String }, // リスト参照 (リスト ID)
Color { color: String }, // 色 (#RRGGBB 形式)
Broadcast { id: String }, // ブロードキャスト参照
Null, // 空入力
}
数値は
String
として保持されており、IR 生成時に
f64
へ変換されます。これは Scratch の数値が浮動小数点演算を前提としているためです。
parser/blocks/
ディレクトリには、カテゴリごとのパーサーが 11 ファイルあります。
| ファイル | 担当カテゴリ | 対応ブロック数(概算) |
|---|---|---|
motion.rs
|
動き | 17 Stmt + 8 Expr |
looks.rs
|
見た目 | 21 Stmt + 5 Expr |
sound.rs
|
音 | 8 Stmt + 4 Expr |
event.rs
|
イベント | 2 Stmt |
control.rs
|
制御 | 15 Stmt + 2 Expr |
sensing.rs
|
調べる | 3 Stmt + 22 Expr |
operator.rs
|
演算 | 18 Expr |
data.rs
|
データ | 11 Stmt + 6 Expr |
procedures.rs
|
独自ブロック | 1 Stmt + 3 Expr |
pen.rs
|
ペン | 13 Stmt + 1 Expr |
ParserError
列挙型でパースエラーを表現します。
pub enum ParserError<'a> {
NotHandledOp(BlockOpCodes), // 未実装 opcode
InvalidValue(&'a str), // 不正な値
UnknownBlock(String), // 未知のブロック ID
InvalidTargetIndex(usize), // 不正なターゲットインデックス
UnexpectedTopLevelPrimitive(String), // 予期しないプリミティブ
Context {
context: String, // コンテキスト情報(ブロック ID など)
source: Box<ParserError<'a>>, // 原因エラー
},
}
Context
バリアントにより、エラーが発生したブロック ID やターゲットインデックスを連鎖的に付加できます。これにより「どのスプライトのどのブロックで失敗したか」が分かります。
コンパイラは
Vec<Thread>
を受け取り、各スレッドを LLVM IR の関数として生成します。
Builders
構造体
compiler/types.rs
では
inkwell
の主要オブジェクトをまとめた
Builders
構造体が定義されています。
pub struct Builders<'ctx> {
pub context: &'ctx Context, // LLVM コンテキスト(型・定数の作成に使用)
pub module: Module<'ctx>, // LLVM モジュール(関数・グローバルの格納先)
pub builder: Builder<'ctx>, // IR 命令を追加するビルダー
// スプライトごとのグローバル変数
// (X 座標、Y 座標、向きなどを float64 のグローバル変数として保持)
}
各
Thread
は次のようにして LLVM 関数に変換されます。
// 関数のシグネチャ: void thread_N(ptr)
let fn_type = context.void_type().fn_type(&[ptr_type.into()], false);
let function = module.add_function(&function_name, fn_type, None);
// エントリブロックを作成してビルダーを配置
let entry = context.append_basic_block(function, "entry");
builder.position_at_end(entry);
// 各 Stmt を IR に変換
for stmt in &thread.stmts {
match stmt {
Stmt::Motion(v) => parse_motion_stmt(builders, v, &function, strings, target_idx),
_ => todo!("やります"), // 未実装
}
}
builder.build_return(None); // void return
各スプライトの状態(X 座標、Y 座標、向きなど)は
f64
型の LLVM グローバル変数として表現されます。例えば、スプライト 0 の X 座標は
@sprite_0_x
というグローバル変数です。
動き系命令(
MotionSetX
など)はこれらのグローバル変数を読み書きする命令(
load
/
store
)として変換されます。
generate_expr_ir
関数が式を LLVM の値に変換します。戻り値の型は
ScratchReturnTypes
列挙型で表現されます。
pub enum ScratchReturnTypes<'ctx> {
Number(FloatValue<'ctx>), // f64 の LLVM 値
String(PointerValue<'ctx>), // 文字列ポインタ
Bool(IntValue<'ctx>), // i1 の LLVM 値(真偽値)
NumberLiteral(f64), // コンパイル時定数(数値)
StringLiteral((String, PointerValue<'ctx>)), // コンパイル時定数(文字列)
BoolLiteral((bool, IntValue<'ctx>)), // コンパイル時定数(真偽値)
}
Literal
バリアントは実際の LLVM 値と Rust 側の定数の両方を保持します。これにより、定数畳み込みなどの最適化が可能になります。
IR 生成後、
default<O3>
パスが適用されます。有効化されている最適化オプション:
cargo build
時に
build.rs
が実行され、
bitcodes/c/
にあるトップレベルの C ソースを LLVM bitcode に変換します。
現在のターゲット:
| C ソース | 生成物 | 役割 |
|---|---|---|
bitcodes/c/dtoa.c
|
bitcodes/bc/dtoa.bc
,
bitcodes/ll/dtoa.ll
|
浮動小数点数を文字列に変換するライブラリ |
bitcodes/c/atod.c
|
bitcodes/bc/atod.bc
,
bitcodes/ll/atod.ll
|
文字列を浮動小数点数へ変換する補助ライブラリ |
bitcodes/c/to_lower.c
|
bitcodes/bc/to_lower.bc
,
bitcodes/ll/to_lower.ll
|
Scratch 文字列の真偽値判定に使う自己完結の補助ライブラリ |
dtoa.c
は将来、生成した IR とリンクして数値→文字列変換(
(0.1 + 0.2)
の結果を表示するときなど)に使用することを想定しています。
atod.c
と
to_lower.c
は、Scratch 文字列の数値化・真偽値化を補助するために使われます。
to_lower.c
は vendored ICU と連携する補助ライブラリです。標準運用では
XYO_ICU_ROOT
で指定した ICU source tree、または
bitcodes/c/lib/icu-prebuilt
の
include/
を使って
to_lower.bc
/
to_lower.ll
を軽量生成し、実際の動作確認は同じ ICU から生成した prebuilt static archive を
tools/check_to_lower_native.sh
から native link します。
to_lower.c
と ICU ソース群を個別に bitcode 化して
llvm-link
で自己完結化する重い経路は、
XYO_EMBED_ICU_BITCODE=1
を付けたときだけ有効です。
ビルドスクリプトは次の手順で動作します:
clang
の実行パスを解決する(
CLANG
→
PATH
上の
clang
→
llvm-config --bindir
/
LLVM_CONFIG_PATH
の順)
clang -emit-llvm -c
と
clang -S -emit-llvm
で
.bc
/
.ll
を生成する
to_lower.c
は通常は vendored ICU ヘッダだけを使って軽量に
.bc
/
.ll
を生成する
XYO_EMBED_ICU_BITCODE=1
のときだけ ICU ソース群も個別に
.bc
化して
llvm-link
で結合する
llvm-dis
で最終的な
.ll
を生成する
bitcodes/bc/
と
bitcodes/ll/
に保存する
run
で残りの opcode に当たると
todo!()
パニックが起きる
ControlStmt
(if/else, repeat, forever など) の IR 生成
DataStmt
(変数操作) の IR 生成
LooksStmt
(見た目変更) の IR 生成(実際のレンダリングは別ライブラリが必要)
run
の未実装分岐に対する安全なフォールバック(パニックを避けてエラー報告する)
clang
や
llc
でリンク・コンパイルするフロー
前のページ: CLI