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

に公開

はじめに

「自分の言語をつくる」――プログラミングに携わっていると、一度は考えたことがある人も多いと思います。
今回、Rustを用いて新しいプログラミング言語を設計・実装し始めました。
目指すのは次のような言語です:

  • Pythonに近い記法で扱いやすい
  • 高速に動作する
  • Web開発に特化した機能を標準で備える

言語名は Pyro(パイロ) としました。

前提条件

cargoがインストールされている
Rustが動く環境である

現時点でできること

現状はまだ最小限の機能しか実装していません。

以下のようなコード:

examples/app.pyro
print("Hello, Pyro!")

上記を.pyroファイルとして書き、コンパイル → 実行すると、Rustに変換されて Hello, Pyro! が出力される状態です。

プロジェクト構成
Cargoのワークスペースを利用して、以下のような構成にしました。

pyro_lang/
  ├─ crates/
  │   ├─ pyroc      # コンパイル処理
  │   ├─ pyro-bin      # CLI本体
  │   └─ pyrorts   # Pyro -> Rust 変換ライブラリ
  └─ examples/
      └─ app.pyro

処理の流れはシンプルです。

  1. CLI(pyro)が .pyro ファイルを読み込む
  2. pyrorts がASTに変換し、Rustコードを生成
  3. 生成されたRustコードをCargoでビルド・実行

コード断片:パーサと文字列エスケープ

まだprint専用ですが、行を解析してASTを構築しています。

./crates/pyrorts/src/lib.rs
use pyroc::{Module, Stmt, Expr};

pub fn generate(m: &Module) -> String {
    let mut body = String::new();
    for stmt in &m.stmts {
        match stmt {
            Stmt::Expr(Expr::Call { callee, args }) if callee == "print" => {
                if let Some(Expr::Str(s)) = args.get(0) {
                    // フォーマット文字列は通常の文字列で、内容はエスケープして挿入
                    body.push_str(&format!("println!(\"{}\");\n", escape_rust(s)));
                } else {
                    body.push_str("println!(\"<print: non-string not supported yet>\");\n");
                }
            }
            _ => body.push_str("// (unsupported stmt)\n"),
        }
    }
    format!("fn main(){{\n{}\n}}\n", indent(&body, 4))
}

fn indent(s: &str, n: usize) -> String {
    let pad = " ".repeat(n);
    s.lines()
        .map(|ln| format!("{}{}", pad, ln))
        .collect::<Vec<_>>()
        .join("\n")
}

fn escape_rust(s: &str) -> String {
    let mut out = String::new();
    for ch in s.chars() {
        match ch {
            '\\' => out.push_str("\\\\"),
            '"'  => out.push_str("\\\""),
            '\n' => out.push_str("\\n"),
            '\t' => out.push_str("\\t"),
            _    => out.push(ch),
        }
    }
    out
}



Pyro ソースを読み取って Rust コードに変換する CLI 実行バイナリを作成します。

./crates/pyroc-bin/src/main.rs
use pyroc::{Module, Stmt, Expr};
use pyrorts::generate;

fn main() {
    let src = std::fs::read_to_string("examples/hello.pyro").expect("failed to read source");
    // 仮のASTを直接作る(パーサーは未接続)
    let m = Module { stmts: vec![
        Stmt::Expr(Expr::Call { callee: "print".into(), args: vec![Expr::Str(src.trim().into())] })
    ]};
    let out = generate(&m);
    std::fs::write("out.rs", out).expect("failed to write out.rs");
    println!("Transpiled to out.rs");
}



Pyro のソースコードをパースして AST(抽象構文木)を作るモジュールの作成します。

./crates/pyroc/src/lib.rs
pub mod parser;

#[derive(Debug, Clone)]
pub enum Expr {
    Str(String),
    Ident(String),
    Call { callee: String, args: Vec<Expr> },
}

#[derive(Debug, Clone)]
pub enum Stmt {
    Expr(Expr),
}

#[derive(Debug, Clone)]
pub struct Module {
    pub stmts: Vec<Stmt>,
}

この部分を実装するまでに、unterminated double quote string エラーに何度も遭遇しました。Rustの文字列リテラルと、Rustコード生成用のリテラル表現の二重管理は、つまずきやすいポイントだと思います。

実行例

$ cargo build
$ cargo run -p pyroc-bin
$ cargo new host
$ cp out.rs host/src/main.rs
$ cd host && cargo run



以下が出力されます。

warning: virtual workspace defaulting to `resolver = "1"` despite one or more workspace members being on edition 2024 which implies `resolver = "3"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2024 resolver, specify `workspace.resolver = "3"` in the workspace root's manifest
note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
   Compiling host v0.1.0 (/Users/ユーザー名/Desktop/pyro_lang_test/host)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s
     Running `/Users/ユーザ名/Desktop/pyro_lang_test/target/debug/host`
print("Hello, こんちわ!")



PyroコードがRustコードに変換され、最終的にRustとして実行されます。

今後の展望

今後は以下の機能を順次実装する予定です:

  1. 変数定義(let)
  2. 四則演算
  3. 関数定義・呼び出し
  4. print の一般化(式全体を扱えるようにする)
  5. Webルーティング構文の導入(例:@app.post("/users"))

特に「Webアプリケーションを言語そのものから記述できる」仕組みを重視しています。

まとめ

Rustで新しいプログラミング言語「Pyro」を設計・実装し始めた
現状は print("Hello, Pyro!") のみ対応
Rustコード生成を通じて動作確認できる環境が整った
今後は構文・型・Web向け機能を拡張していく予定
まだ実験的な段階ですが、少しずつ「言語」としての形を整えていきます。

次回以降は 変数定義と四則演算 を実装した段階で記事にまとめる予定です。

コラボスタイル Developers

Discussion