Upgrade to Pro — share decks privately, control downloads, hide ads and more …

明日の私を救う proc_macro

Avatar for kiokuless kiokuless
September 24, 2025
5

明日の私を救う proc_macro

Avatar for kiokuless

kiokuless

September 24, 2025
Tweet

Transcript

  1. 導入 #[test] fn test_adc_immediate() { let mut cpu = CPU::new();

    // LDA #$50 ; A = 0x50 // ADC #$30 ; A = A + 0x30 = 0x80 let program = vec![0xa9, 0x50, 0x69, 0x30]; cpu.load_and_run(program); assert_eq!(cpu.register_a, 0x80); assert_eq!(cpu.get_carry(), 0); assert_eq!(cpu.get_negative(), 1); } 明日の私を救う proc_macro - kiokuless 6
  2. バイト列で命令思い出すの辛い let program = vec![ // LDA #$50 ; A

    = 0x50 0xa9, 0x50, // ADC #$30 ; A = A + 0x30 = 0x80 0x69, 0x30 ]; ファミコンで使われた 6502 の機械語はとても簡単な構造 命令は 1-3 バイトで決まっている オペコードを見るとオペランドの見るべきバイト数が定まる コメントや関数名でまあわかるけど・・・ -> なんか イケてない 明日の私を救う proc_macro - kiokuless 7
  3. 実現したいこと let program = asm![ LDA imm 50; // 厳密に言うと6502

    " 風" の命令になっている ADC imm 30; CLC ]; 明日の私を救う proc_macro - kiokuless 8
  4. 実現したいこと let program = asm![ LDA imm 50; // 厳密に言うと6502

    " 風" の命令になっている ADC imm 30; CLC ]; LDA で即値 50 を A レジスタに代入 ADC で即値 30 を A レジスタに += CLC で carry フラグをオフにする( 命令トークンの長さは 1-3) 明日の私を救う proc_macro - kiokuless 9
  5. 実現したいこと let program = asm![ LDA imm 50; // 厳密に言うと6502

    " 風" の命令になっている ADC imm 30; CLC ]; LDA で即値 50 を A レジスタに代入 ADC で即値 30 を A レジスタに += CLC で carry フラグをオフにする( 命令トークンの長さは 1-3) => めっちゃ見やすくなった 明日の私を救う proc_macro - kiokuless 10
  6. 最初は macro_rules! で... macro_rules! asm { ($($tokens:tt);*) => { vec![

    $(instruction!($($tokens)*)),* ] }; } macro_rules! instruction { // パースして構造体に変換... } ここで大ハマり 明日の私を救う proc_macro - kiokuless 11
  7. 問題は ; (セミコロン) macro_rules! instruction { ($($token:tt)*) => { ...

    }; } LDA 0x80; NOP; みたいなコードをパースしようとすると... 明日の私を救う proc_macro - kiokuless 12
  8. 問題は ; (セミコロン) macro_rules! instruction { ($($token:tt)*;) => { ...

    }; } LDA 0x80; NOP; みたいなコードをパースしようとすると... コンパイラが混乱! 「 ; って、token-tree の一部?それともマクロの区切り文字?」 => パースの曖昧性が発生する 明日の私を救う proc_macro - kiokuless 14
  9. 対処法? [] / () / {} で命令毎にを囲む asm![ (LDA 0x80)

    (CLC) (NOP) // ... ] 明日の私を救う proc_macro - kiokuless 16
  10. 対処法? [] / () / {} で命令毎にを囲む asm![ (LDA 0x80)

    (CLC) (NOP) // ... ] => 絶対にやりたくない こんなのやるくらいなら人間デコンパイラになるほうがマシ 明日の私を救う proc_macro - kiokuless 17
  11. proc_macro に手を出す [lib] proc_macro = true 注意: マクロの定義/ 実装をするだけの Cargo

    workspace を別に用意 する必要がある 明日の私を救う proc_macro - kiokuless 21
  12. 実装と結果 pub struct AsmInput { pub asm: Vec<InstructionInput>, } //

    セミコロン区切りで残りの命令を解析 while input.peek(Token![;]) { input.parse::<Token![;]>()?; // セミコロンを消費 // セミコロンの後に何もない場合は終了 if input.is_empty() { break; } // 細かい1 命令単位のパースは InstructionInput に任せる asm.push(input.parse::<InstructionInput>()?); } 明日の私を救う proc_macro - kiokuless 22
  13. 結果は... Before: vec![0xA9, 0x01, 0x8D, 0x00, 0x02] // ??? After:

    asm!( LDA imm 0x01; // 即値1 をA レジスタに読み込み STA abs 0x0200 // $0200 番地に保存 -> エンディアンも気にしなくていいぞ!!! ) 明日の私を救う proc_macro - kiokuless 23
  14. proc_macro の威力はこれだけじゃない Rust 上で手書きだと面倒なオブジェクト、なんでも簡潔に書けるよう になります AST (抽象構文木) ast!(if x >

    0 { return x } else { return -x }) グラフ構造 graph!(A -> B -> C; A -> C; B -> D) 明日の私を救う proc_macro - kiokuless 26