ChatGPTを使ってRustで新しいプログラミング言語をつくり始めた話
はじめに
「自分の言語をつくる」――プログラミングに携わっていると、一度は考えたことがある人も多いと思います。
今回、Rustを用いて新しいプログラミング言語を設計・実装し始めました。
目指すのは次のような言語です:
- Pythonに近い記法で扱いやすい
- 高速に動作する
- Web開発に特化した機能を標準で備える
言語名は Pyro(パイロ) としました。
前提条件
cargoがインストールされている
Rustが動く環境である
現時点でできること
現状はまだ最小限の機能しか実装していません。
以下のようなコード:
print("Hello, Pyro!")
上記を.pyroファイルとして書き、コンパイル → 実行すると、Rustに変換されて Hello, Pyro! が出力される状態です。
プロジェクト構成
Cargoのワークスペースを利用して、以下のような構成にしました。
pyro_lang/
├─ crates/
│ ├─ pyroc # コンパイル処理
│ ├─ pyro-bin # CLI本体
│ └─ pyrorts # Pyro -> Rust 変換ライブラリ
└─ examples/
└─ app.pyro
処理の流れはシンプルです。
- CLI(pyro)が .pyro ファイルを読み込む
- pyrorts がASTに変換し、Rustコードを生成
- 生成されたRustコードをCargoでビルド・実行
コード断片:パーサと文字列エスケープ
まだprint専用ですが、行を解析してASTを構築しています。
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 実行バイナリを作成します。
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(抽象構文木)を作るモジュールの作成します。
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として実行されます。
今後の展望
今後は以下の機能を順次実装する予定です:
- 変数定義(let)
- 四則演算
- 関数定義・呼び出し
- print の一般化(式全体を扱えるようにする)
- Webルーティング構文の導入(例:@app.post("/users"))
特に「Webアプリケーションを言語そのものから記述できる」仕組みを重視しています。
まとめ
Rustで新しいプログラミング言語「Pyro」を設計・実装し始めた
現状は print("Hello, Pyro!") のみ対応
Rustコード生成を通じて動作確認できる環境が整った
今後は構文・型・Web向け機能を拡張していく予定
まだ実験的な段階ですが、少しずつ「言語」としての形を整えていきます。
次回以降は 変数定義と四則演算 を実装した段階で記事にまとめる予定です。
Discussion