🐡

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

に公開

はじめに

本記事では、自作プログラミング言語Pyroに変数宣言機能を追加した過程をご紹介いたします。
PyroはPythonライクな構文を持ち、Rustにトランスパイルして実行可能な軽量言語です。
今回はPyro独自のキーワードletprを導入し、変数を扱えるようにしました。

背景

前回の実装でPyroは整数リテラルと四則演算に対応しました。
しかし実際にプログラムを書く際には、値を保持して再利用する仕組みが不可欠です。
そこでPyro専用の変数宣言キーワードとしてletprを導入しました。

例:

examples/hello.pyro
letpr x = 10
letpr y = x * 2
print(y + 5)

実装内容

  1. AST の拡張
    変数宣言を表すノードを Stmt::Let として追加しました。
    以下は差分箇所をコメントで示しています。
./pyroc/src/lib.rs
// --- ここから差分 ---
// 前回までは `Stmt` に `Expr` のみだったが、変数宣言用の `Let` を追加した
#[derive(Debug, Clone)]
pub enum Stmt {
    Expr(Expr),
    Let { name: String, expr: Expr }, // ← New: letpr 用
}
// --- ここまで差分 ---
  1. 字句解析 (Lexer) の拡張
    代入演算子 = をトークンとして追加します。
./crates/pyroc/src/parser.rs
// --- ここから差分 ---
// 新しいトークン '=' を追加
#[derive(Debug, Clone, PartialEq)]
enum Tok {
    Ident(String),
    Str(String),
    Num(f64),
    LParen, RParen, Comma,
    Plus, Minus, Star, Slash,
    Eq,                 // ← New: '='
    Newline, Eof,
}
// --- ここまで差分 ---
// --- ここから差分 ---
// Lexer で '=' をトークン化
Some('=') => Ok(Tok::Eq),
// --- ここまで差分 ---
  1. 構文解析 (Parser) の拡張
    letpr <ident> = <expr> の形を Stmt::Let として構築します。
./crates/pyroc/src/parser.rs
// --- ここから差分 ---
// Parser に letpr 文を追加
if let Tok::Ident(ref s) = self.at() {
    if s == "letpr" {
        self.bump(); // consume 'letpr'

        let name = if let Tok::Ident(n) = self.at().clone() {
            self.bump(); n
        } else {
            return Err("expected identifier after letpr".into());
        };

        if !matches!(self.at(), Tok::Eq) {
            return Err("expected '=' after identifier".into());
        }
        self.bump(); // consume '='

        let expr = self.parse_expr(0)?;
        stmts.push(Stmt::Let { name, expr });
        self.eat_newlines();
        continue;
    }
}
// --- ここまで差分 ---
  1. コード生成の対応
    Rust コード生成 (pyrorts/src/lib.rs) に Stmt::Let を追加し、Rust の let 文に変換します。
./crates/pyrorts/src/lib.rs
// --- ここから差分 ---
// generate に Let を追加し、Rust の let に変換
for stmt in &m.stmts {
    match *stmt {
        Stmt::Let { ref name, ref expr } => {
            body.push_str(&format!("let {} = {};\n", name, expr_to_rust(expr)));
        }
        Stmt::Expr(Expr::Call { ref callee, ref args }) if callee == "print" => {
            if let Some(arg) = args.get(0) {
                let expr_rs = expr_to_rust(arg);
                body.push_str(&format!("println!(\"{{}}\", {});\n", expr_rs));
            }
        }
        Stmt::Expr(_) => { /* no-op */ }
    }
}
// --- ここまで差分 ---

動作確認

Pyro ソースコード

letpr a = 2
letpr b = 3
print(a + b * 4)

トランスパイル

cargo run -p pyroc-bin
rustc out.rs -o out

生成された out.rs:

fn main() {
    let a = 2.0;
    let b = 3.0;
    println!("{}", (a + (b * 4.0)));
}
./out

実行結果

14

まとめと今後の課題

今回の実装で Pyro は 変数宣言と参照 に対応しました。
letpr という独自のキーワードを導入したことで、既存言語と混同しない「Pyro らしさ」も加わっています。

今後は以下の機能拡張を検討しています。

  • 変数の再代入(letpr に続く = を更新用に利用)
  • スコープ管理(ブロック単位の変数寿命)
  • 型の拡張(浮動小数点、文字列の代入など)

言語仕様が一歩ずつ整っていくことで、Pyroは徐々に「小さなプログラミング言語」として成長していきます。

コラボスタイル Developers

Discussion