💡

ChatGPTを使ってRustで新しいプログラミング言語をつくり始めた話(if分岐)

に公開

前回までのおさらい

前回はPyroに「変数定義」を導入しました。
letprというPyro独自の構文をRustのletに変換できるようにし、変数を使ったプログラムを書けるようにしました。

letpr x = 10
print(x)

今回のテーマ: if分岐

次の一歩として、if分岐を実装しました。
Pyroでは「数値が0以外なら真」というルールで判定しています。
例:

letpr x = 10
if x {
    print("x は 0 以外です")
} else {
    print("x は 0 です")
}

実装のポイント

  1. AST (Stmt) に If を追加
pyro/crates/pyroc/src/lib.rs
 pub enum Stmt {
     Expr(Expr),
     Let { name: String, expr: Expr },
+    If {
+        cond: Expr,
+        then_block: Vec<Stmt>,
+        else_block: Vec<Stmt>,
+    },
 }
  1. パーサに if 文を追加
pyro/crates/pyroc/src/parser.rs
 match self.at() {
-    Tok::Letpr => {
-        // (変数定義の処理)
-    }
-    _ => {
-        // (既存の式処理)
-    }
+    Tok::If => {
+        self.bump(); // "if"
+        let cond = self.parse_expr()?;
+        let then_block = self.parse_block_stmts()?;
+        let else_block = if matches!(self.at(), Tok::Else) {
+            self.bump();
+            self.parse_block_stmts()?
+        } else {
+            vec![]
+        };
+        Ok(Stmt::If { cond, then_block, else_block })
+    }
+    Tok::Letpr => {
+        // (変数定義の処理)
+    }
+    _ => {
+        // (既存の式処理)
+    }
 }
  1. コード生成に if 分岐を追加
pyro/crates/pyrorts/src/lib.rs
 fn generate_stmt(stmt: &Stmt) -> String {
     let mut code = String::new();
     match stmt {
         Stmt::Expr(e) => {
             code.push_str(&format!("println!(\"{{}}\", {});\n", expr_to_rust(e)));
         }
         Stmt::Expr(Expr::Call { callee, args }) if callee == "print" => {
             if let Some(arg) = args.get(0) {
                 code.push_str(&format!("println!(\"{{}}\", {});\n", expr_to_rust(arg)));
             } else {
                 code.push_str("println!(\"<print: missing arg>\");\n");
             }
         }
         Stmt::Expr(_) => { /* no-op */ }
         Stmt::Let { name, expr } => {
             code.push_str(&format!("let {} = {};\n", name, expr_to_rust(expr)));
         }
+        Stmt::If {
+            cond,
+            then_block,
+            else_block,
+        } => {
+            code.push_str(&format!("if ({} as f64) != 0.0 {{\n", expr_to_rust(cond)));
+            for s in then_block {
+                code.push_str(&indent(&generate_stmt(s), 4));
+            }
+            code.push_str("}\n");
+            if !else_block.is_empty() {
+                code.push_str("else {\n");
+                for s in else_block {
+                    code.push_str(&indent(&generate_stmt(s), 4));
+                }
+                code.push_str("}\n");
+            }
+        }
     }
     code
 }

実行例

examples/main.pyro
letpr x = 1
letpr y = 0

if x {
    print("x は真")
} else {
    print("x は偽")
}

if y {
    print("y は真")
} else {
    print("y は偽")
}

実行手順

プロジェクトルートで以下を実行します。

  1. ビルド
cargo build
  1. Rust コードをコンパイル
cargo run -p pyroc-bin -- run

pyro/output/out.rs が生成されます。
生成された Rust コード

pyro/output/out.rs
fn main() {
    let x = 1;
    let y = 0;
    if (x as f64) != 0.0 {
        println!("{}", "x は真");
    } else {
        println!("{}", "x は偽");
    }
    if (y as f64) != 0.0 {
        println!("{}", "y は真");
    } else {
        println!("{}", "y は偽");
    }
}

実行結果:

x は真
y は偽

まとめ

  • Pyro に if 分岐 を追加
  • 0以外を「真」として判定する仕様
  • then/else ブロックを Rust の if/else に変換

これで Pyro は単純な条件分岐を扱えるようになりました 🎉
次は while ループ や 関数定義 に挑戦できそうです。

コラボスタイル Developers

Discussion