Memo

メモ > 技術 > プログラミング言語: Rust

入門
Rustプログラミング言語 https://www.rust-lang.org/ja とほほのRust入門 - とほほのWWW入門 https://www.tohoho-web.com/ex/rust.html プログラミング言語「Rust」とは? "Hello, World!"で基本を押さえる:基本からしっかり学ぶRust入門(1)(1/3 ページ) - @IT https://atmarkit.itmedia.co.jp/ait/articles/2107/28/news010.html プログラミング言語Rustのススメ - Qiita https://qiita.com/elipmoc101/items/3c8b6d8332a9019e578c Rust入門 https://zenn.dev/mebiusbox/books/22d4c1ed9b0003 組込みエンジニアのためのRustのよくわからない記号まとめ - Qiita https://qiita.com/yagisawa/items/281478ca05514d4ed49e Rustで作るテトリス入門 https://zenn.dev/kumavale/books/30efec2e1d3428 Rustソースコードのざっくりとした歩き方 - Speaker Deck https://speakerdeck.com/tako8ki/rustsosukotonosatukuritositabu-kifang-3f597e58-222e-4c70-9b1b-e644ea... これからRustを勉強したいエンジニアにおすすめしたい参考書や教材を紹介します https://zenn.dev/pharmax/articles/44d730172f6b61 そもそもプログラミング経験自体無い人がRustを学ぶとき,どんな順序が良いのか https://zenn.dev/toga/articles/rust-for-beginner これまでと違う学び方をしたら挫折せずにRustを学べた話 / Programming Rust techramen24conf LT - Speaker Deck https://speakerdeck.com/twada/programming-rust-techramen24conf-lt Rustで勘違いしていたこと3選 【100 Exercises To Learn Rust 完走記事】 #Rust - Qiita https://qiita.com/namn1125/items/2a59f486cce30fd2b889 GopherがRust入門したので違いをまとめてみた https://zenn.dev/skanehira/articles/2024-08-12-go-rust-pros-cons もしもいま、Rustをイチから学び直すとしたら? Rust入門書著者・matsu7874さんに聞く学習ロードマップ - Findy Engineer Lab https://findy-code.io/engineer-lab/techtensei-matsu7874 ■紹介や事例 「Rust」はなぜクラウドネイティブ開発者の間で大人気なのか?:「Rust」のメリットとデメリット【前編】 - TechTargetジャパン システム開発 https://techtarget.itmedia.co.jp/tt/news/2109/06/news04.html 難しくても「Rust」を学びたくなってしまう理由:「Rust」のメリットとデメリット【後編】 - TechTargetジャパン システム開発 https://techtarget.itmedia.co.jp/tt/news/2109/13/news04.html なぜわざわざ学習コストを払ってまでRustを採用するのか? Webエンジニア目線でRustを考察:WebエンジニアからみたRust(1) - @IT https://atmarkit.itmedia.co.jp/ait/articles/2109/30/news009.html Rust開発時の便利ツールたち https://zenn.dev/toru3/articles/14312f4dbf18b6 Rust初心者殺しの文法10選 - Qiita https://qiita.com/muumu/items/8cdcc79fa881912adf51 他言語ユーザがRust言語をガチめに使っての雑感 - 分散KVSを書いてみて - - Qiita https://qiita.com/ryo_grid/items/9a82d7230fbc4a0875c1 プログラミング言語「Rust」が「C」「C++」の後継になるこれだけの根拠:AWSやMicrosoftも支援する「最も愛される言語」 - TechTargetジャパン システム開発 https://techtarget.itmedia.co.jp/tt/news/1912/05/news04.html 「Rustでやると知らないうちに詰む設計」を避けるためのTipsを集めてみる https://zenn.dev/qnighy/scraps/93071f7c813cb9 なぜRustなの?と言われた時のために https://zenn.dev/khale/articles/rust-beginners-catchup Rust のここが好き、10選 https://zenn.dev/sawatani/articles/4d6349e6b795cd Rustの良質な学習リソースをまとめる - BioErrorLog Tech Blog https://www.bioerrorlog.work/entry/rust-good-learning-resources なぜRustはメモリ安全なのかをC言語のコードと考える - Qiita https://qiita.com/ohakutsu/items/5d29001f79d42d63e886 満を持して始めるRust - Speaker Deck https://speakerdeck.com/estie/man-wochi-siteshi-merurust sudoとsuがRustで書き直される。メモリ安全性向上へ - PC Watch https://pc.watch.impress.co.jp/docs/news/1498034.html Rustプログラムのデバッグ辛すぎ問題 #Rust - Qiita https://qiita.com/k0kubun/items/766dc15773ec73925163 プログラミング言語Rustになぜ注目するのか #Rust - Qiita https://qiita.com/comware_takatsuru/items/9607df56a5803f68588e
インストール
■Windowsにインストール Rust をインストール - Rustプログラミング言語 https://www.rust-lang.org/ja/tools/install rustup.rs - The Rust toolchain installer https://rustup.rs/ 上のページから rustup-init.exe をダウンロードして実行する コマンドプロンプトが立ち上がり、以下のメッセージが表示される
The Cargo home directory is located at: C:\Users\refirio\.cargo This can be modified with the CARGO_HOME environment variable. The cargo, rustc, rustup and other commands will be added to Cargo's bin directory, located at: C:\Users\refirio\.cargo\bin This path will then be added to your PATH environment variable by modifying the HKEY_CURRENT_USER/Environment/PATH registry key. You can uninstall at any time with rustup self uninstall and these changes will be reverted. Current installation options: default host triple: x86_64-pc-windows-msvc default toolchain: stable (default) profile: default modify PATH variable: yes 1) Proceed with standard installation (default - just press enter) 2) Customize installation 3) Cancel installation >1 … 「1」と入力してEnterを入力 info: profile set to 'default' info: default host triple is x86_64-pc-windows-msvc info: syncing channel updates for 'stable-x86_64-pc-windows-msvc' info: latest update on 2024-06-13, rust version 1.79.0 (129f3b996 2024-06-10) info: downloading component 'cargo' info: downloading component 'clippy' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' 57.7 MiB / 57.7 MiB (100 %) 25.1 MiB/s in 2s ETA: 0s info: downloading component 'rustfmt' info: installing component 'cargo' 6.4 MiB / 6.4 MiB (100 %) 5.5 MiB/s in 1s ETA: 0s info: installing component 'clippy' info: installing component 'rust-docs' 15.4 MiB / 15.4 MiB (100 %) 1.5 MiB/s in 19s ETA: 0s info: installing component 'rust-std' 18.3 MiB / 18.3 MiB (100 %) 4.8 MiB/s in 3s ETA: 0s info: installing component 'rustc' 57.7 MiB / 57.7 MiB (100 %) 4.9 MiB/s in 11s ETA: 0s info: installing component 'rustfmt' info: default toolchain set to 'stable-x86_64-pc-windows-msvc' stable-x86_64-pc-windows-msvc installed - rustc 1.79.0 (129f3b996 2024-06-10) Rust is installed now. Great! To get started you may need to restart your current shell. This would reload its PATH environment variable to include Cargo's bin directory (%USERPROFILE%\.cargo\bin). Press the Enter key to continue. … Enterを入力
1分程度でインストールが完了する ■コンパイル C:\Users\refirio\Rust 内に hello フォルダを作成し、そこで作業するものとする main.rs を作成して以下を記述する
fn main() { println!("Hello, world!"); }
コマンドプロンプトから、以下のとおりコンパイルして実行する(「.\main」としても実行できる。PowerShellの場合、「./main」として実行する) なおコンパイルすると、main.exe と main.pdb が作成される >cd C:\Users\refirio\Rust\hello >rustc main.rs >main Hello, world! ■プロジェクト ※上の手順で作成した「hello」フォルダは削除しておくものとする 以下のコマンドでプロジェクトを作成できる ソースコードは src フォルダ内に main.rs として格納される(内容は上の「コンパイル」のものと同じ) >cd C:\Users\refirio\Rust >cargo new hello --bin … プロジェクトを作成 >cd hello 以下のコマンドでビルドできる プログラムは target\debug 内に hello.exe として作成される >cargo build … コンパイルだけ行う場合 >cargo run … コンパイルして実行する場合 >cargo build --release … リリース用にコンパイルする場合 以下のようにして実行できる >target\debug\hello もしくは、以下のようにして実行できる >cd target\debug >hello ■メモ VSCodeだと、特に拡張機能をインストールしなくても色分け表示された
基本の文法
以下を参考に、基本の文法を確認する プログラミング言語「Rust」とは? "Hello, World!"で基本を押さえる:基本からしっかり学ぶRust入門(1)(1/3 ページ) - @IT https://atmarkit.itmedia.co.jp/ait/articles/2107/28/news010.html とほほのRust入門 - とほほのWWW入門 https://www.tohoho-web.com/ex/rust.html プログラミング言語Rustのススメ - Qiita https://qiita.com/elipmoc101/items/3c8b6d8332a9019e578c Rust入門 https://zenn.dev/mebiusbox/books/22d4c1ed9b0003 ■基本のコード ・C言語などと同様、main関数がプログラムのエントリーポイントとなる ・「println!」というのはマクロで、マクロは名称の後に「!」を付けて使用する Rustでは引数の数が可変である関数を定義できないため、マクロを使って定義されている ・「print!」というマクロもあり、この場合は末尾に改行が付かない
fn main() { println!("Hello, world!"); }
■コメント
fn main() { println!("Hello, world!"); /* println!("Hello, world!"); */ //println!("Hello, world!"); }
■変数 Rustの変数は、デフォルトでイミュータブル(変更できない)となる 変数名には、小文字のスネークケースを使うといい 「{}」を使用して変数の内容を表示でき、「{:?}」を使用して配列の内容を表示できる
fn main() { let x = 100; //x = 200; println!("値 {} は変数xに拘束されています。", x); }
なお Rust 1.58 から、フォーマット文字列内で変数名を直接参照できる (「vec!」については、後述の「ベクター」を参照) Rust 1.58からフォーマット文字列で変数の直接参照が可能に! - dotTrail https://dottrail.codemountains.org/rust-format-strings/
fn main() { let x = 100; println!("値 {x} は変数xに拘束されています。"); let values = vec![1, 2, 3, 4]; println!("values: {values:?}"); }
変数の変更(「mut」の指定が必要)
fn main() { let mut x = 100; x = 200; println!("値 {} は変数xに拘束されています。", x); }
変数の宣言と初期化を分ける(初回の代入は許されている)
fn main() { let x; x = 100; println!("値 {} は変数xに拘束されています。", x); }
変数は型を明示することができる 異なる型同士で計算する場合、as を使って型を合わせる
fn main() { let x: i32 = 100; let y: f32 = 0.5; let result = (x as f32) * y; println!("計算結果は {} です。", result); }
変数の型は、「u16」「i32」「f64」などがある(符号なし整数、符号あり整数、浮動小数点) 例えば「u8」で宣言した変数には0〜255を格納できるが、ここに300を格納しようとすると「error: literal out of range for `u8`」のようにエラーになる 型の一覧は、以下などの記事が参考になる Rustの数値型をマスターしたい(Part1) #Rust - Qiita https://qiita.com/LynxLevin/items/6cf47f5f68abe573f135 型が指定されていなければ、一例だがデフォルトタイプにより以下のように解釈される
let x = 10; // i32 let y = 3.14; // f64
基本的には「i32」を使うことが勧められている 「整数を扱うならi32を使う、小数を扱うならf64を使う」としておけば良さそう(そもそも、型推論に任せれば良さそう) 32bitが安定して早いらしい Data Types - The Rust Programming Language https://doc.rust-lang.org/book/ch03-02-data-types.html > So how do you know which type of integer to use? If you’re unsure, Rust’s defaults are generally good places to start: integer types default to i32. ■定数 変数には任意の式を代入できるが、定数には定数式しか代入できない 定数名には、大文字のスネークケースを使うといい
fn main() { const TAX_RATE: f32 = 1.1; let price: i32 = 100; let total_price = (price as f32 * TAX_RATE) as i32; println!("100円の税込み価格は {} 円です。", total_price); }
■文字列 後述の「文字列型」を参照 ■タプル 複数の値をまとめて扱うことができる それぞれの値に名前は無い 要素数は固定で、インデックスは0から始まる数値となる 「t[i]」のように、インデックスに変数を使用できない タプル内の一部の型を、後から変更することはできない
fn main() { let t = (2, 3.14, 0); let (a, b, c) = t; println!("a={}, b={}, c={}", a, b, c); let x = t.0; let y = t.1; let z = t.2; println!("x={}, y={}, z={}", x, y, z); }
■配列 配列のサイズは固定で、コンパイル時に決まっている必要がある 要素数を可変させたい場合、ベクターを使う必要がある 型の異なる要素を含むことはできない
fn main() { let a = [1, 2, 3, 4, 5]; let x = a[0]; let y = a[1]; let z = a[2]; println!("x={}, y={}, z={}", x, y, z); }
■条件分岐 条件は、必ず「論理値を返すbool型」の結果にする必要がある
fn main() { let age = 20; if age >= 25 { println!("選挙権と被選挙権があります。"); } else if age >= 18 { println!("選挙権があります。"); } else { println!("選挙権がありません。"); } }
値を返す条件式 セミコロンの有無に注意
fn main() { let age = 20; let s = if age >= 25 { "選挙権と被選挙権があります。" } else if age >= 18 { "選挙権があります。" } else { "選挙権がありません。" }; println!("{}", s); }
switch文に似た分岐として、match文がある
fn main() { let size = 'S'; match size { 'Z' => println!("{}はアルファベット最後の文字です。", size), // 単一値のマッチング 'S' | 'M' | 'L' => println!("{}はサイズを表すアルファベットです。", size), // 複数値のマッチング '0'..='9' | 'A'..='F' => println!("{}は16進数で使う文字です。", size), // 範囲を指定したマッチング _ => println!("{}はいずれでもない文字です。", size), // いずれにもマッチしなかった場合 }; }
以下のようにすると、値を返すことができる
fn main() { let size = 'S'; let explain = match size { 'Z' => "アルファベット最後の文字", // 単一値のマッチング 'S' | 'M' | 'L' => "サイズを表すアルファベット", // 複数値のマッチング '0'..='9' | 'A'..='F' => "16進数で使う文字", // 範囲を指定したマッチング _ => "いずれでもない文字", // いずれにもマッチしなかった場合 }; println!("{}は{}です。", size, explain); }
■繰り返し whileによる繰り返し
fn main() { let max = 10; let mut count = 1; let mut sum = 0; while count <= max { sum += count; count += 1; } println!("{}までの合計は{}です。", max, sum); }
loopによる繰り返し 最もシンプルに繰り返しができる
fn main() { loop { // 永遠に続くのでCtrl+Cを入力して止める println!("永遠に続くループ。"); } }
forによる繰り返し C/C++などで伝統的に使われている初期化、条件、繰り返し前処理のような構文は無い(そのような構文はwhileを使って記述する)
fn main() { let scores = [90, 80, 85, 70, 95]; for score in scores.iter() { println!("点数は{}点です。", score); } }
iterでイテレータを扱うことはできる イテレータ|Rust のイテレータとクロージャを理解して rayon で並列処理する https://zenn.dev/suzuki_hoge/books/2024-04-rust-for-rayon-a450094c0faf3c/viewer/2-iterator iter()とinto_iter()の違いとちょっとした落とし穴 #Rust - Qiita https://qiita.com/harvath/items/b79eaf61e73e79e3fc0f ■関数
fn sample() { println!("シンプルな関数です。"); } fn main() { println!("関数を呼び出します。"); sample(); println!("関数を呼び出しました。"); }
引数のある関数
fn sum(x: i32, y: i32) { println!("合計は{}です。", x + y); } fn main() { println!("関数を呼び出します。"); sum(3, 2); println!("関数を呼び出しました。"); }
戻り値のある関数
fn sum(x: i32, y: i32) -> i32 { //return x + y; // returnで返す x + y // 最後に評価された式で返す } fn main() { println!("関数を呼び出します。"); let a = sum(3, 2); println!("合計は{}です。", a); println!("関数を呼び出しました。"); }
■関数合成 関数を動的に作成し、そこに引数を渡すなどして処理できる
fn add(a: i32) -> impl Fn(i32) -> i32 { move |b| a + b } fn main() { let add_five = add(5); // add(5)が「5を足す関数」を返す let result1 = add_five(3); // add_five内で「5 + 3 = 8」が計算される println!("{}", result1); // 出力: 8 let add_seven = add(7); // add(7)が「7を足す関数」を返す let result2 = add_seven(2); // add_five内で「7 + 2 = 9」が計算される println!("{}", result2); // 出力: 9 let result3 = add(1)(5); // 直接関数を指定できる。この場合「1 + 5 = 6」が計算される println!("{}", result3); // 出力: 6 }
■クロージャ 他の言語で「無名関数」や「ラムダ式」と呼ばれるもの
fn main() { let square = | x: i32 | { x * x }; println!("{}", square(9)); }
■マクロ マクロは「マクロ名!」で呼び出すことができる
macro_rules! log { ($x:expr) => { println!("{}", $x); } } fn main() { log!("TEST"); }
Rustのマクロを覚える #Rust - Qiita https://qiita.com/k5n/items/758111b12740600cc58f Rust の macros はどういう時に使われているのか? https://zenn.dev/linnefromice/articles/when-to-use-macros-in-rust ■所有権 スカラー型における所有権 以下のコードは実行できる
fn main() { let x = 1; let y = x; println!("xは{}です。", x); // 「xは1です。」となる println!("yは{}です。", y); // 「yは1です。」となる }
文字列型における所有権 以下のコードは実行できない(文字列の場合は値ではなく参照を保持しているが、参照の場合はメモリ安全のために所有権の概念が出てくる) ただし「let s2 = s1;」の部分を「let s2 = s1.clone();」と書き換えると実行できる(この場合は所有権の移動ではなく、所有権の複製となる)
fn main() { let s1 = String::from("Hello, Rust!"); //let s1 = "Hello, Rust!".to_string(); // このように書くこともできる //let s1 = "Hello, Rust!"; // このように書くこともできる let s2 = s1; println!("s1は{}です。", s1); // コンパイルエラーになるので実行されない println!("s2は{}です。", s2); }
■借用と参照 検索対象の文字列を後から使用するために、「s2」に代入しなおしている ただしこの場合、プログラムが冗長になってしまっている
fn main() { let s = String::from("Hello, Rust!"); let c = 'R'; let (s2, pos) = search_position(s, c); println!("文字'{}'は「{}」内の{}文字目にあります。", c, s2, pos); // ここではsを参照できない } fn search_position(s: String, c: char) -> (String, usize) { let pos = s.find(c).unwrap(); (s, pos) }
参照で値を渡すと、読み取るだけの値として渡すことができる この場合、所有権が移動されないので、プログラムを簡潔に書くことができている
fn main() { let s = String::from("Hello, Rust!"); let c = 'R'; let pos = search_position(&s, c); // 参照(&)で値を渡し、位置だけを受け取る println!("文字'{}'は「{}」内の{}文字目にあります。", c, s, pos); } fn search_position(s: &String, c: char) -> usize { // 参照(&)で値を受け取る let pos = s.find(c).unwrap(); pos // 位置だけを返す }
参照が有効なとき、値の移動はできなくなる
fn main() { let s = String::from("Hello, Rust!"); let r = &s; println!("sは「{}」です。", s); println!("rは「{}」です。", r); // rを参照できる let s2 = s; println!("s2は「{}」です。", s2); println!("rは「{}」です。", r); // sの所有権が移動したので、rを参照できない }
参照で値を渡す際に「mut」を付けると、変更可能な参照となる (変数の宣言時も、同様に「mut」で宣言しておく必要がある)
fn main() { let mut s = String::from("Hello"); println!("変更前の文字列は「{}」です。", s); change_string(&mut s); println!("変更後の文字列は「{}」です。", s); } fn change_string(s: &mut String) { s.push_str(", Rust!"); }
所有権、借用、参照については、後述の「ライフタイム」も参照 ■unwrap 「unwrap()」は、Option型やResult型の値がSomeやOkであることを前提として、値を取り出すもの 値がNoneだった場合、panicが発生する
fn main() { let opt: Option<i32> = Some(42); println!("{}", opt.unwrap()); // 「42」と表示される let opt: Option<i32> = None; println!("{}", opt.unwrap()); // panicが発生する }
ただしプログラムがパニック(クラッシュ)を起こすようなような処理は、特にプロダクション環境では避けるべき このような場合、「unwrap()」ではなく「unwrap_or()」を使用すればデフォルト値を返すことができ、安全に処理を行える
fn main() { let opt: Option<i32> = Some(42); println!("{}", opt.unwrap()); // 「42」と表示される let opt: Option<i32> = None; println!("{}", opt.unwrap_or(0)); // 「0」と表示される }
Rust Optionとunwrapとは 使い方と注意点について|webdrawer https://note.com/webdrawer/n/n069975792f6c rust - Rustの"unwrap()"は何をするものですか? - スタック・オーバーフロー https://ja.stackoverflow.com/questions/1730/rust%E3%81%AEunwrap%E3%81%AF%E4%BD%95%E3%82%92%E3%81%99%... Rust のエラーハンドリングはシンタックスシュガーが豊富で完全に初見殺しなので自信を持って使えるように整理してみたら完全に理解した #Rust - Qiita https://qiita.com/nirasan/items/321e7cc42e0e0f238254 ■構造体 構造体はstructを使って定義できる
struct Person { name: String, height: f64, weight: f64, } fn main() { let taro = Person { name: String::from("山田太郎"), height: 170.0, weight: 60.0, }; println!("{}さんの身長は{}、体重は{}です。", taro.name, taro.height, taro.weight); }
構造体のメソッド メソッドは「impl」内に「fn」を使って実装する fnの引数には「&self」が必要で、値を参照するときは「selft」を使う
struct Person { name: String, height: f64, weight: f64, } impl Person { fn bmi(&self) -> f64 { self.weight / ((self.height / 100.0) * (self.height / 100.0)) } } fn main() { let taro = Person { name: String::from("山田太郎"), height: 170.0, weight: 60.0, }; println!("{}さんの身長は{}、体重は{}です。", taro.name, taro.height, taro.weight); println!("{}さんのBMIは{}です。", taro.name, taro.bmi()); }
メソッドの戻り値に自身の型を指定することで、メソッドチェーンを使うことができるようになる
struct Person { name: String, } impl Person { fn say_hello(&self) -> &Self { println!("{}さんこんにちは。", self.name); self } fn say_bye(&self) -> &Self { println!("{}さんさようなら。", self.name); self } } fn main() { let taro = Person { name: String::from("山田太郎"), }; taro.say_hello().say_bye(); }
■列挙型 列挙型はenumを使って定義できる
enum Card { HEART, SPADE, DIAMOND, CLOVER, } fn main() { let card = Card::SPADE; match card { Card::HEART => println!("カードはハートです。"), Card::SPADE => println!("カードはスペードです。"), Card::DIAMOND => println!("カードはダイヤです。"), Card::CLOVER => println!("カードはクローバーです。"), _ => println!("カードはどれでもありません。"), } }
構造体と同様、列挙型にもメソッドを定義できる
enum Card { HEART, SPADE, DIAMOND, CLOVER, } impl Card { // カードの名前を返すメソッド fn get_name(&self) -> &str { match self { Card::HEART => "ハート", Card::SPADE => "スペード", Card::DIAMOND => "ダイヤ", Card::CLOVER => "クローバー", } } // カードの色を返すメソッド fn get_color(&self) -> &str { match self { Card::HEART | Card::DIAMOND => "赤", Card::SPADE | Card::CLOVER => "黒", } } } fn main() { let card = Card::SPADE; println!("カードは{}です。色は{}です。", card.get_name(), card.get_color()); let another_card = Card::HEART; println!("カードは{}です。色は{}です。", another_card.get_name(), another_card.get_color()); }
■Trait 様々な型に、共通のメソッドを実装するように促すことができる
trait Tweet { fn tweet(&self); fn tweet_twice(&self) { self.tweet(); self.tweet(); } fn shout(&self) { println!("Uoooohh!!!!"); } } struct Dove(); struct Duck(); impl Tweet for Dove { fn tweet(&self) { println!("Coo!"); } } impl Tweet for Duck { fn tweet(&self) { println!("Quack!"); } } fn main() { let dove = Dove {}; dove.tweet(); dove.tweet_twice(); dove.shout(); let duck = Duck {}; duck.tweet(); duck.tweet_twice(); duck.shout(); }
■Derive 構造体や列挙型に振る舞いを追加できる 例えば以下のプログラムは「`Data` cannot be formatted using `{:?}`」というエラーになるが、
struct Data{ value: i32, } fn main() { let d = Data {value: 2}; println!("{:?}", d); }
一行目に「#[derive(Debug)]」を指定すると、「{:?}」によって「Data { value: 2 }」という結果を得られるようになる Rustの構造体などに追加できる振る舞いを確認する #Rust - Qiita https://qiita.com/apollo_program/items/2495dda519ae160971ed RustのトレイトとDeriveのキホンがよくわからんかったので自分なりに落とし込んだ #Rust - Qiita https://qiita.com/Papillon6814/items/97c175fd94f0107d3821 100日後にRustをちょっと知ってる人になる: [Day 42]属性 (アトリビュート) https://zenn.dev/shinyay/articles/hello-rust-day042 ■ResultとOption Resultは、成功と失敗を扱うことができる
enum Result<T, E> { Ok(T), // 成功 Err(E), // 失敗 }
Optionは、値の有無を扱うことができる
enum Option<T> { None, // 値がない Some(T), // 値がある }
Rust のエラーハンドリングはシンタックスシュガーが豊富で完全に初見殺しなので自信を持って使えるように整理してみたら完全に理解した #Rust - Qiita https://qiita.com/nirasan/items/321e7cc42e0e0f238254 RustのOptionとResult #Rust - Qiita https://qiita.com/take4s5i/items/c890fa66db3f71f41ce7 ■パターンマッチ
fn main() { let input1 = "+2"; let input2 = "-3"; let input3 = "42"; print_sign(input1); // "+" print_sign(input2); // "-" print_sign(input3); // "No sign found." } fn print_sign(input: &str) { if let Some(sign @ ('+' | '-')) = input.chars().next() { println!("{}", sign); } else { println!("No sign found."); } }
実行すると以下のように表示される sign のように代入して後から使う必要が無い場合、変数名を「_sign」とすることで「warning: unused variable: `sign`」の警告を回避できる (つまり、使わない変数の名前の先頭に「_」を付けることで、「使わないが無視したくない」を示すことができる) + - No sign found. ■if-let / let-else 一例だが以下のようにすると、「値が数値か否かで分岐」ができる
fn main() { let value = "10"; //let value = "ABC"; let val = if let Ok(n) = value.parse::<i32>() { println!("{} is a number.", value); // 10 is a number. println!("n = {}", n); // n = 10 } else { println!("{} is not a number.", value); }; println!("val = {:?}", val); // val = () }
Rust if letとは 使い方と注意点について|webdrawer https://note.com/webdrawer/n/ne14eda25abeb Rustのlet-else文気持ち良すぎだろ #Rust - Qiita https://qiita.com/namn1125/items/ccedf9cc06b8cef71557 Rustならではな書き方で、少し戸惑いがあるので挙動の詳細を確認する まず以下のようにして、同じ値かどうかを比較できる
#[derive(PartialEq, Debug)] enum Sample { Value1(String), Value2(String), } fn main() { let sample = Sample::Value1("Hello".to_string()); if Sample::Value1("Hello".to_string()) == sample { println!("Your state is Value1({:?})", sample); } else { println!("Your state is Value2."); }; }
それとは別に、Rustでは以下のような書き方で「同じ型かどうか」を比較できる これは「sample の型が Sample::Value1 なら、値を取り出して state_inner に格納する」という意味で、型を保証して値を取り出すための構文 Rustでは先のような「if let Ok」という分岐がよく出てくるが、この延長上にある書き方
enum Sample { Value1(String), Value2(String), } fn main() { let sample = Sample::Value1("Hello".to_string()); if let Sample::Value1(state_inner) = sample { println!("Your state is Value1({})", state_inner); } else { println!("Your state is Value2."); }; }
なお、以下のように値を受け取ることもできる
enum Sample { Value1(String), Value2(String), } fn main() { let sample = Sample::Value1("Hello".to_string()); let state_inner = if let Sample::Value1(state_inner) = sample { state_inner } else { println!("Your state is Value2."); return; }; println!("Your state is Value1({})", state_inner); }
■collect 一例だが以下のようにすると、「スペース区切りの文字を逆順に並べて表示」ができる 「::<Vec<_>>」部分では、収集するコレクションの型を指定している
fn main() { let value = "A B C"; let tokens = value.split_whitespace().rev().collect::<Vec<_>>(); //let tokens: Vec<_> = value.split_whitespace().rev().collect(); println!("{}", value); println!("{:?}", tokens); }
イテレータ|Rust入門 https://zenn.dev/mebiusbox/books/22d4c1ed9b0003/viewer/a86937 ■ファイル入出力とエラー処理 プロジェクト直下に message.txt を作成し、適当なメッセージを記述しておく 文字コードは UTF-8 にしておく message.txt
これはサンプルメッセージです。 Rustから読み書きします。
以下のとおりプログラムを記述する
use std::env; use std::fs::File; use std::io::prelude::*; fn main() { let args: Vec<String> = env::args().collect(); let filename = &args[1]; let mut file = File::open(filename).unwrap(); let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); println!("{}", contents); }
以下のとおり、ファイル名も指定して実行する ファイルが存在しなかったり、文字コードが UTF-8 で無かった場合はエラーになる exeファイルを直接実行する場合も同じ >cargo run message.txt Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s Running `target\debug\hello.exe message.txt` これはサンプルファイルです。 Rustから読み書きします。 >cargo run message.txt Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s Running `target\debug\hello.exe message.txt` thread 'main' panicked at src/main.rs:8:41: called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "指定されたファイルが見つかりません 。" } note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: process didn't exit successfully: `target\debug\hello.exe message.txt` (exit code: 101) >cargo run message.txt Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s Running `target\debug\hello.exe message.txt` thread 'main' panicked at src/main.rs:10:40: called `Result::unwrap()` on an `Err` value: Error { kind: InvalidData, message: "stream did not contain valid UTF-8" } note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: process didn't exit successfully: `target\debug\hello.exe message.txt` (exit code: 101) >hello message.txt これはサンプルファイルです。 Rustから読み書きします。
use std::env; use std::fs::File; use std::io::prelude::*; fn main() { let args: Vec<String> = env::args().collect(); let filename = &args[1]; match File::open(filename) { Ok(mut file) => { let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); println!("{}", contents); }, Err(error) => { println!("ファイル{}が見つかりません。", filename); println!("{}", error) } }; }
>cargo run message.txt Compiling hello v0.1.0 (C:\Users\refirio\Rust\hello) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.67s Running `target\debug\hello.exe message.txt` これはサンプルファイルです。 Rustから読み書きします。 >cargo run message2.txt Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s Running `target\debug\hello.exe message2.txt` ファイルmessage2.txtが見つかりません。 指定されたファイルが見つかりません。 (os error 2)
use std::env; use std::fs::File; use std::io; use std::io::prelude::*; fn main() { let args: Vec<String> = env::args().collect(); let filename = &args[1]; match read_file(filename) { Ok(s) => println!("{}", s), Err(e) => println!("エラー:{}", e), }; } fn read_file(filename: &String) -> Result<String, io::Error> { let mut file = File::open(filename)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) }
>cargo run message.txt Compiling hello v0.1.0 (C:\Users\refirio\Rust\hello) Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.24s Running `target\debug\hello.exe message.txt` これはサンプルファイルです。 Rustから読み書きします。 >cargo run message2.txt Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s Running `target\debug\hello.exe message2.txt` エラー:指定されたファイルが見つかりません。 (os error 2) ResultやOption型を使う場合、エラーハンドリングを簡潔に記述できる「?」が利用できる 例えば以下のプログラムがあったとして、
use std::num::ParseIntError; // 文字列をi32に変換して返す fn check_number(x: &str) -> Result<i32, ParseIntError> { //let num: Result<i32, ParseIntError> = x.parse(); let num = x.parse(); match num { Ok(n) => Ok(n), Err(e) => Err(e), } } fn main() { // 正常なケース match check_number("10") { Ok(num) => println!("Ok: {}", num), Err(e) => println!("Err: {}", e), } // エラーが発生するケース match check_number("abc") { Ok(num) => println!("Ok: {}", num), Err(e) => println!("Err: {}", e), } }
「?」を使えば、check_number関数を以下のように書くことができる (エラーが発生した場合、処理を即座に停止して呼び出し元に結果を返す)
fn check_number(x: &str) -> Result<i32, ParseIntError> { let num: i32 = x.parse()?; Ok(num) }
なお今回の場合、以下のように書くこともできる x.parse() は「Result<i32, ParseIntError>」の型を返すため、関数の戻り値の型に適合する (最後のセミコロンを省略することで値を返す書き方は、このファイル内の「戻り値のある関数」を参照)
fn check_number(x: &str) -> Result<i32, ParseIntError> { x.parse() }
■ベクター 配列とは違い、内部に収められる要素の数を増減させることができる 型の異なる要素を含むことはできない
fn main() { // ベクター(同じデータ型の値を複数持つことができる) let mut months = Vec::new(); months.push("January"); months.push("February"); months.push("March"); months.push("April"); // 初期値のあるベクターはマクロでも定義できる //let months = vec!["January", "February", "March", "April"]; // 値の参照 println!("Count: {}, {:?}", months.len(), months); println!("{}", months[1]); // 値の走査 for month in &months { println!("{}", month); } }
■ハッシュマップ 他の言語で「連想配列」と呼ばれるもの
use std::collections::HashMap; fn main() { // ハッシュマップ(キーと値のリスト。ほかの言語では「マップ」や「連想配列」などと呼ばれるもの) let mut months = HashMap::new(); months.insert("January", 1); months.insert("February", 2); months.insert("March", 3); months.insert("April", 4); // ベクターからハッシュマップを生成できる //let keys = vec!["January", "February", "March", "April"]; //let values = vec![1, 2, 3, 4]; //let months: HashMap<_, _> = keys.iter().zip(values.iter()).collect(); // 値の参照 println!("Count: {}, {:?}", months.len(), months); println!("{}", months["February"]); }
■参照型 「&」でアドレスを取得でき、「*」でアドレスの指す値を取得できる ただし以下のように、「*」が無くても同様に値が表示される これは、標準ライブラリの「fmt::Display」トレイトの実装によって、アドレスではなく参照されている値が表示されるため
fn main() { let a = 123; let p = &a; println!("{}", *p); // 「123」と表示される println!("{}", p); // 「123」と表示される }
ミュータブルな参照を用いることで、参照先の値を変更することができる
fn main() { let mut a = 123; // ミュータブルな変数を定義 let p = &mut a; // ミュータブルな参照を定義 *p = 456; // 参照先の値を変更 println!("{}", p); // 「456」が表示される }
■文字列型 文字列は「str」で表すが、Rustの基本的な文字列型としては「&str」を使う 文字列のメモリへのポインタを指すようなもの
fn main() { let s: &str = "Hello, Rust!"; //let s = "Hello, Rust!"; // このように書くこともできる println!("sは{}です。", s); }
また、ライブラリとして提供される文字列型に「String」がある
fn main() { // 文字列(文字のコレクション) let jan = String::from("January"); let feb_utf8 = vec![0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79]; let feb = String::from_utf8(feb_utf8).unwrap(); println!("{}, {}", jan, feb); }
■ボックス コンパイル時にサイズが判らない型を格納できる(実行時にサイズが判ればいい) 以下を実行すると「error[E0277]: the size for values of type `[u8]` cannot be known at compilation time」のエラーになる
fn main() { let byte_array = [b'h', b'e', b'l', b'l', b'o']; print(byte_array); } fn print(s: [u8]) { println!("{:?}", s); }
以下のようにボックスで渡すと、エラーにならなくなる
fn main() { let byte_array = [b'h', b'e', b'l', b'l', b'o']; print(Box::new(byte_array)); } fn print(s: Box<[u8]>) { println!("{:?}", s); }
■ジェネリクス型 型を指定せずに処理を作ることができる
struct Range<T> { min: T, max: T, } fn main() { let int_range = Range { min: 1, max: 10 }; let float_range = Range { min: 2.0, max: 5.5 }; // 型を明示することもできる //let int_range: Range<i32> = Range { min: 1, max: 10 }; //let float_range: Range<f32> = Range { min: 2.0, max: 5.5 }; println!("int_range.min = {}, int_range.max = {}", int_range.min, int_range.max); println!("float_range.min = {}, float_range.max = {}", float_range.min, float_range.max); }
ジェネリクス型の構造体にもメソッドを定義できる ただしminとmaxの値は数字とは限らないため、単純に四則計算を行うことはできない この場合、「use std::ops::Sub」を指定することで、引き算を行なうことを認められるようになる ただしそれだけだと「error[E0507]: cannot move out of `self.min` which is behind a shared reference」というエラーになる 詳細として「move occurs because `self.min` has type `T`, which does not implement the `Copy` trait」が表示される 意味は「`Copy` トレイトが実装されていないため move が発生します」となり、指定のとおり「+ Copy」を追加することでプログラムを実行できるようになる (Copyトレイトにより、代入時に複製が行なわれるようにする。例えばString型はCopyトレイトを実装していないので、値は複製されず移動される)
use std::ops::Sub; struct Range<T> { min: T, max: T, } impl<T> Range<T> where T: Sub<Output = T> + Copy { fn difference(&self) -> T { self.max - self.min } } fn main() { let int_range = Range { min: 1, max: 10 }; let float_range = Range { min: 2.0, max: 5.5 }; println!("int_range.min = {}, int_range.max = {}, int_range.difference = {}", int_range.min, int_range.max, int_range.difference()); println!("float_range.min = {}, float_range.max = {}, float_range.difference = {}", float_range.min, float_range.max, float_range.difference()); }
■クラス Rustにはクラスが無い(代わりに構造体を使う) オブジェクト指向経験者のためのRust入門 #Rust - Qiita https://qiita.com/nacika_ins/items/cf3782bd371da79def74
ライフタイム
&を用いた参照を利用した際に、解放されたメモリを参照してしまうことを避けるための仕組み 例えば以下のプログラムは、実行すると「error[E0597]: `x` does not live long enough」のエラーになる printlnでrを参照する時点で、参照元のxは破棄されて内容を参照できないため
fn main() { { let r; { let x = 5; r = &x; } println!("r: {}", r); } }
上記はプログラムが単純だが、「&str」で文字列を参照する際に同じ問題が発生する RustのLifetimeってなんなん https://zenn.dev/ucwork/articles/6de5c9c2257f2d Rustのライフタイムについて知りたい #Rust - Qiita https://qiita.com/toreis/items/970bcfed6a930e3c84dc Rust の 所有権、借用、ライフタイムについて初心者目線で説明と整理を試みる | blog.ojisan.io https://blog.ojisan.io/rust-ownership-wakaran/ ■関数でのライフタイム 以下は文字列の長さを比較するプログラムだが、実行すると「error[E0106]: missing lifetime specifier」のエラーになる 関数longestにxとyが渡されているが、関数からどちらの値が返ってくるかコンパイル時に確定できないため
fn main() { let string1 = "ABC"; let string2 = "ABCD"; let result = longest(string1, string2); println!("The longest string is: {}", result); } fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } }
以下のとおりライフタイム「'a」を設定すると、xとyの両方が同じ期間だけ生きることを宣言でき、エラーにならなくなる (実行結果は「The longest string is: ABCD」となる)
fn main() { let string1 = "ABC"; let string2 = "ABCD"; let result = longest(string1, string2); println!("The longest string is: {}", result); } fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
また以下のように、「参照を含まずにデータを共有する」ように変更することでもエラーを回避できる (前述の「借用と参照」にあるコードと同じような内容になる) ライフタイムの管理が不要になるメリットはあるが、文字列のコピーが発生するのでメモリ使用量が増え、パフォーマンスも落ちる
fn main() { let string1 = String::from("ABC"); let string2 = String::from("ABCD"); let result = longest(string1, string2); println!("The longest string is: {}", result); } fn longest(x: String, y: String) -> String { if x.len() > y.len() { x } else { y } }
■構造体でのライフタイム 以下は構造体を使用するプログラムだが、実行すると「error[E0106]: missing lifetime specifier」のエラーになる
struct Person { name: &str, } fn main() { let taro = Person { name: "山田太郎", }; println!("{}さんこんにちは。", taro.name); }
以下のとおりライフタイム「'a」を設定すると、xとyの両方が同じ期間だけ生きることを宣言でき、エラーにならなくなる (実行結果は「山田太郎さんこんにちは。」となる)
struct Person<'a> { name: &'a str, } fn main() { let taro = Person { name: "山田太郎", }; println!("{}さんこんにちは。", taro.name); }
また以下のように、「参照を含まずにデータを共有する」ように変更することでもエラーを回避できる (前述の「構造体」にあるコードと同じような内容になる) ライフタイムの管理が不要になるメリットはあるが、文字列のコピーが発生するのでメモリ使用量が増え、パフォーマンスも落ちる
struct Person { name: String, } fn main() { let taro = Person { name: String::from("山田太郎"), }; println!("{}さんこんにちは。", taro.name); }
■その他 初期はライフタイムの明記が必須だったらしいが、今は以下の場合にライフタイムを明記しなくてもいいようになっている…らしい 構造体のメソッドはselfを持つことが多いので、ライフタイムの指定は不要になることが多い…らしい ・参照の各引数は、独自のライフタイム引数を得る ・1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入される ・selfのあるメソッドの場合、selfのライフタイムが全出力ライフタイム引数に代入される 以下は構造体のメソッドfirst_charを定義しているが、このように「self」が渡されることが多い
struct Person<'a> { name: &'a str, } impl<'a> Person<'a> { fn first_char(&self) -> Option<char> { self.name.chars().next() } } fn main() { let taro = Person { name: "山田太郎", }; println!("{}さんこんにちは。", taro.name); if let Some(first) = taro.first_char() { println!("先頭の文字は{}です。", first); } }
以下メモ ・「'a」という記述があるが、これはライフタイム指定でジェネリクスの一種 ライフタイムは、Rustの借用チェッカーが参照の有効期間を検証するための仕組み 「a」という名前は、「src」など任意のものを付けられる 例えば「Value<'src>」の場合、列挙型「Value」内で参照を持つフィールドのライフタイムが「src」であることを示している ・ライフタイムは、コンパイラが参照の有効期間を追跡し、メモリ安全性を保障する 複数の参照が同じライフタイムを共有するか、または別々のライフタイムを持つかを明示することで、より厳密なメモリ管理を行なう ・コンパイラは多くの場合、自動的にライフタイムを推測することができるが、特に複雑なケースでは明示的なライフタイム指定が必要になる また構造体や列挙型のような型定義で参照を含む場合、ライフタイム指定が必須になる ・ライフタイムは他の言語に似た概念が存在しない
クレートとモジュール
・クレート … ソースコードが書かれたファイル。実行バイナリとライブラリがある ・実行バイナリ … エントリーポイント(main関数)を持つクレート ・ライブラリクレート … エントリーポイント(main関数)を持たないクレート ・モジュール … クレート内に、階層構造を持った名前空間を定義できる ■準備 以下のとおり、改めてプロジェクトを作成するものとする >cd C:\Users\refirio\Rust >cargo new example --bin >cd example この時点で、Cargo.toml の内容は以下のとおり
[package] name = "example" version = "0.1.0" edition = "2021" [dependencies]
src/main.rs の内容は以下のとおり
fn main() { println!("Hello, world!"); }
以下のとおり実行できる >cargo run Compiling example v0.1.0 (C:\Users\refirio\Rust\example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s Running `target\debug\example.exe` Hello, world! 実行すると Cargo.lock が作成される あわせて target フォルダ内に、実行可能ファイルなどが作成される
# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "example" version = "0.1.0"
■外部クレートの導入 以下のサイトで外部クレートを見つけることができる crates.io: Rust Package Registry https://crates.io/ 今回「rand」で検索すると、「rand v0.8.5」が見つかった https://crates.io/crates/rand 上記ページの「Install」部分に、インストールコマンドとして「rand = "0.8.5"」が表示されている これをもとに、Cargo.toml の最終行を以下のように調整する
[dependencies] ↓ [dependencies] rand = "0.8.5"
あとはcargoコマンドが、初回ビルド時に外部クレートの依存関係を解決し、必要なものをダウンロードしてくれる (依存関係は target フォルダ内に、プログラム自体は C:\Users\refirio\.cargo\registry フォルダ内に保存されるみたい) >cargo run Updating crates.io index Locking 8 packages to latest compatible versions Adding cfg-if v1.0.0 Adding getrandom v0.2.15 Adding libc v0.2.155 Adding ppv-lite86 v0.2.17 Adding rand v0.8.5 Adding rand_chacha v0.3.1 Adding rand_core v0.6.4 Adding wasi v0.11.0+wasi-snapshot-preview1 (latest: v0.13.1+wasi-0.2.0) Downloaded cfg-if v1.0.0 Downloaded rand_core v0.6.4 Downloaded rand_chacha v0.3.1 Downloaded rand v0.8.5 Downloaded ppv-lite86 v0.2.17 Downloaded getrandom v0.2.15 Downloaded 6 crates (192.4 KB) in 0.39s Compiling cfg-if v1.0.0 Compiling ppv-lite86 v0.2.17 Compiling getrandom v0.2.15 Compiling rand_core v0.6.4 Compiling rand_chacha v0.3.1 Compiling rand v0.8.5 Compiling example v0.1.0 (C:\Users\refirio\Rust\example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.52s Running `target\debug\example.exe` Hello, world! クレートの使い方は、「Documentation」部分からリンクが張られている 今回は、以下のようにすると0〜1の範囲で値を取得できる
use rand::prelude::*; fn main() { let mut rng = rand::thread_rng(); let x: f64 = rng.gen(); println!("x={}", x); }
■独自クレートの作成 >cd C:\Users\refirio\Rust >cd example example\src\lib.rs として、以下のコードを作成する
pub mod module_a; mod module_b;
example\src\module_a.rs を作成する。内容はカラにしておく example\src\module_b.rs として、以下のコードを作成する
mod module_c; mod module_d;
example\src\module_b\module_c.rs を作成する。内容はカラにしておく example\src\module_b\module_d.rs を作成する。内容はカラにしておく src/main.rs の内容を以下にしておく
use example::module_a; // モジュールは「use」で呼び出せる //use example::module_b; // 「pub」を指定していないので呼び出せない fn main() { println!("Hello, world!"); }
>cargo run 実行すると「warning: unused import」が表示されるものの、ビルドは通る また module_a.rs から module_b.rs を呼び出す場合、example\src\module_a.rs を以下のようにする つまり同じパッケージ内なら、クレート名の代わりに「crate」を指定する
use crate::module_b;
テスト
Rustは標準で自動テスト機能をサポートしているので、別途ライブラリやツールをインストールする必要は無い 以下のコマンドで単体テストを作成できる >cargo new --lib testings 以下のとおり、testings\src\lib.rs にテストが作成される
pub fn add(left: usize, right: usize) -> usize { left + right } #[cfg(test)] mod tests { use super::*; #[test] fn it_works() { let result = add(2, 2); assert_eq!(result, 4); } }
以下のコマンドでテストを実行できる テスト実行時はmain関数が実行されず、アノテーション「#[test]」を付けた部分が実行されるようになる …と思ったが、そもそもテストは何も実行されていない?(失敗するテストにしても「ok」となってしまう) >cargo test Finished `test` profile [unoptimized + debuginfo] target(s) in 0.02s Running unittests src/main.rs (target\debug\deps\hello-19e713fe88107cdf.exe) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s src\main.rs の最後に testings\src\lib.rs の内容を張り付けると、以下のとおりテストを実行できた (成功するテストだと「ok」と表示され、失敗するテストにすると、「error」と表示される) ※テストはテスト専用ファイルではなく、プログラムと同じファイルに「#[test]」として書くため、このような挙動になっているのかもしれない ただしこれは単体テストの場合で、結合テストの場合は tests フォルダ内にテストを置くといいらしい >cargo test Compiling hello v0.1.0 (C:\Users\refirio\Rust\hello) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.26s Running unittests src/main.rs (target\debug\deps\hello-19e713fe88107cdf.exe) running 1 test test tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s >cargo test Compiling hello v0.1.0 (C:\Users\refirio\Rust\hello) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.23s Running unittests src/main.rs (target\debug\deps\hello-19e713fe88107cdf.exe) running 1 test test tests::it_works ... FAILED failures: ---- tests::it_works stdout ---- thread 'tests::it_works' panicked at src/main.rs:17:9: assertion `left == right` failed left: 4 right: 3 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::it_works test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--bin hello`
コンソールアプリケーション
実践Rustプログラミング入門 - 秀和システム あなたの学びをサポート! https://www.shuwasystem.co.jp/book/9784798061702.html ここでは、逆ポーランド記法で計算できるプログラムを作成する 以下のとおり、プロジェクトを作成する >cd C:\Users\refirio\Rust >cargo new rpncalc --bin >cd rpncalc ■コマンドライン引数の処理(クレートを使用しない場合) src/main.rs の内容を以下のようにする
use std::env; fn main() { let args: Vec<String> = env::args().collect(); println!("{:?}", args); }
以下のように、引数を処理できるようになる >cargo run ["target\\debug\\rpncalc.exe"] >cargo run 1 a xyz 2.0 ["target\\debug\\rpncalc.exe", "1", "a", "xyz", "2.0"] 実行ファイルを呼び出す場合も、以下のとおり処理できている >rpncalc ["rpncalc"] >rpncalc 1 a xyz 2.0 ["rpncalc", "1", "a", "xyz", "2.0"] ただしこの場合、引数の位置や順序が固定となっている このまま作りこむこともできるが、clapを使用すると容易に実装できる ■コマンドライン引数の処理(clapクレートを使用する場合) 「clap v4.5.9」を使用する https://crates.io/crates/clap 上記ページの「Install」部分に、インストールコマンドとして「clap = "4.5.9"」が表示されている これをもとに、Cargo.toml の最終行を以下のように調整するが、今回は「derive」を使用するために以下のようにする
[dependencies] ↓ [dependencies] clap = { version = "4.5.9", features = ["derive"] }
クレートの使い方は、「Documentation」部分からリンクが張られている 今回は、以下のようにするとコマンドライン引数を取得できる
use clap::Parser; /// Simple program to greet a person #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { /// Name of the person to greet #[arg(short, long)] name: String, /// Number of times to greet #[arg(short, long, default_value_t = 1)] count: u8, } fn main() { let args = Args::parse(); for _ in 0..args.count { println!("Hello {}!", args.name); } }
そのまま実行すると「必須の引数が無い」のエラーになるが、「--name Taro」を指定すると名前が表示される (cargo run で引数を指定する場合、「--」に続けて指定する) また「--help」を指定するとヘルプが表示されるが、この内容はプログラム内のコメントも使用されている >cargo run error: the following required arguments were not provided: --name <NAME> Usage: rpncalc.exe --name <NAME> For more information, try '--help'. error: process didn't exit successfully: `target\debug\rpncalc.exe` (exit code: 2) >cargo run -- --name Taro Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s Running `target\debug\rpncalc.exe --name Taro` Hello Taro! >cargo run -- --help Compiling rpncalc v0.1.0 (C:\Users\refirio\Rust\rpncalc) Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.56s Running `target\debug\rpncalc.exe --help` Simple program to greet a person! Usage: rpncalc.exe [OPTIONS] --name <NAME> Options: -n, --name <NAME> Name of the person to greet!! -c, --count <COUNT> Number of times to greet!!! [default: 1] -h, --help Print help -V, --version Print version 実行ファイルを直接実行すると、以下のようになる >rpncalc error: the following required arguments were not provided: --name <NAME> Usage: rpncalc --name <NAME> For more information, try '--help'. >rpncalc --name Taro Hello Taro! オプションの「-n」「--name」「-c」「--count」は、構造体で定義した「name」「count」をもとに、自動で決定される (名前や先頭の1文字が重複していると、正しく決定できないのでエラーになる) また、例えば以下のようにすると、オプションを自分で決定できる。この場合、自動判定の内容と同じく「-n」「--name」「-c」「--count」とみなされる (「short」「long」を指定しない場合、位置引数であると解釈される)
struct Args { /// Name of the person to greet #[arg(short = 'n', long = "name")] name: String, /// Number of times to greet #[arg(short = 'c', long = "count", default_value_t = 1)] count: u8, }
詳細は以下のページが解りやすい clapの使い方まとめ - ぽよメモ https://poyo.hatenablog.jp/entry/2022/10/10/170000 今回はプログラムを以下のように修正し、これをベースに機能を追加していくものとする
use clap::Parser; /// RPN Calculator #[derive(Parser, Debug)] #[command( version = "1.0.0", author = "refirio", about = "Super awesome sample RPN calculator." )] struct Args { /// Set the level of verbosity #[arg(short, long)] verbose: bool, /// Formulas written in RPN #[arg(name = "FILE")] formula_file: Option<String>, } fn main() { let args = Args::parse(); match args.formula_file { Some(file) => println!("File specified: {}", file), None => println!("No file specified."), } println!("Is verbosity specified?: {}", args.verbose); }
以下のとおり実行できる >cargo run Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s Running `target\debug\rpncalc.exe` No file specified. Is verbosity specified?: false >cargo run -- --version Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s Running `target\debug\rpncalc.exe --version` rpncalc 1.0.0 >cargo run -- --help Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s Running `target\debug\rpncalc.exe --help` Super awesome sample RPN calculator. Usage: rpncalc.exe [OPTIONS] [FILE] Arguments: [FILE] Formulas written in RPN Options: -v, --verbose Set the level of verbosity -h, --help Print help -V, --version Print version ■ファイルの読み込み
use clap::Parser; use std::fs::File; use std::io::{BufRead, BufReader}; /// RPN Calculator #[derive(Parser, Debug)] #[command( version = "1.0.0", author = "refirio", about = "Super awesome sample RPN calculator." )] struct Args { /// Set the level of verbosity #[arg(short, long)] verbose: bool, /// Formulas written in RPN #[arg(name = "FILE")] formula_file: Option<String>, } fn main() { let args = Args::parse(); if let Some(path) = args.formula_file { let f = File::open(path).unwrap(); let reader = BufReader::new(f); for line in reader.lines() { let line = line.unwrap(); println!("{}", line); } } else { println!("No file is specified."); } }
プログラムを実行する場所に「input.txt」を作成し、その中に「1 1 +」と書いた場合、 以下のように実行してファイルの内容を読み込むことができる >cargo run -- input.txt 1 1 + >cargo run No file is specified. ■標準入力の読み取り
use clap::Parser; use std::fs::File; use std::io::{stdin, BufRead, BufReader}; /// RPN Calculator #[derive(Parser, Debug)] #[command( version = "1.0.0", author = "refirio", about = "Super awesome sample RPN calculator." )] struct Args { /// Set the level of verbosity #[arg(short, long)] verbose: bool, /// Formulas written in RPN #[arg(name = "FILE")] formula_file: Option<String>, } fn main() { let args = Args::parse(); if let Some(path) = args.formula_file { let f = File::open(path).unwrap(); let reader = BufReader::new(f); run(reader, args.verbose); } else { let stdin = stdin(); let reader = stdin.lock(); run(reader, args.verbose); } } fn run<R: BufRead>(mut reader: R, verbose: bool) { let mut line = String::new(); if reader.read_line(&mut line).unwrap() > 0 { println!("{}", line.trim_end()); } }
引数なしで実行すると、以下のように「入力した文字がそのまま表示される」となる >cargo run test test また今回は、run関数の内容を以下のようにする
fn run<R: BufRead>(reader: R, verbose: bool) { for line in reader.lines() { let line = line.unwrap(); println!("{}", line); } }
引数なしで実行すると、以下のように「入力した文字がそのまま表示される」となる また入力した後にプログラムは終了せず、再度入力できるようになる(「Ctrl+C」で終了できる) >cargo run test test ■計算の準備
use clap::Parser; use std::fs::File; use std::io::{stdin, BufRead, BufReader}; /// RPN Calculator #[derive(Parser, Debug)] #[command( version = "1.0.0", author = "refirio", about = "Super awesome sample RPN calculator." )] struct Args { /// Set the level of verbosity #[arg(short, long)] verbose: bool, /// Formulas written in RPN #[arg(name = "FILE")] formula_file: Option<String>, } struct RpnCalculator(bool); impl RpnCalculator { pub fn new(verbose: bool) -> Self { Self(verbose) } pub fn eval(&self, formula: &str) -> i32 { 0 } } fn main() { let args = Args::parse(); if let Some(path) = args.formula_file { let f = File::open(path).unwrap(); let reader = BufReader::new(f); run(reader, args.verbose); } else { let stdin = stdin(); let reader = stdin.lock(); run(reader, args.verbose); } } fn run<R: BufRead>(reader: R, verbose: bool) { let calc = RpnCalculator::new(verbose); for line in reader.lines() { let line = line.unwrap(); let answer = calc.eval(&line); println!("{}", answer); } }
この時点では、入力された内容に関わらず「0」を返す >cargo run -- input.txt 0 0 0 >cargo run 1 1 + 0 ■計算の実装
use clap::Parser; use std::fs::File; use std::io::{stdin, BufRead, BufReader}; /// RPN Calculator #[derive(Parser, Debug)] #[command( version = "1.0.0", author = "refirio", about = "Super awesome sample RPN calculator." )] struct Args { /// Set the level of verbosity #[arg(short, long)] verbose: bool, /// Formulas written in RPN #[arg(name = "FILE")] formula_file: Option<String>, } struct RpnCalculator(bool); impl RpnCalculator { pub fn new(verbose: bool) -> Self { Self(verbose) } pub fn eval(&self, formula: &str) -> i32 { let mut tokens = formula.split_whitespace().rev().collect::<Vec<_>>(); self.eval_inner(&mut tokens) } pub fn eval_inner(&self, tokens: &mut Vec<&str>) -> i32 { let mut stack = Vec::new(); while let Some(token) = tokens.pop() { if let Ok(x) = token.parse::<i32>() { stack.push(x); } else { let y = stack.pop().expect("invalid syntax"); let x = stack.pop().expect("invalid syntax"); let res = match token { "+" => x + y, "-" => x - y, "*" => x * y, "/" => x / y, "%" => x % y, _ => panic!("invalid token"), }; stack.push(res); } // もし「-v」オプションが指定されていれば(無名フィールドの先頭要素に値があれば)、この時点でのトークンとスタックの状態を出力する if self.0 { println!("{:?} {:?}", tokens, stack); } } if stack.len() == 1 { stack[0] } else { panic!("invalid syntax") } } } fn main() { let args = Args::parse(); if let Some(path) = args.formula_file { let f = File::open(path).unwrap(); let reader = BufReader::new(f); run(reader, args.verbose); } else { let stdin = stdin(); let reader = stdin.lock(); run(reader, args.verbose); } } fn run<R: BufRead>(reader: R, verbose: bool) { let calc = RpnCalculator::new(verbose); for line in reader.lines() { let line = line.unwrap(); let answer = calc.eval(&line); println!("{}", answer); } }
以下のとおり、逆ポーランド記法で計算できる またオプションとして「-v」を指定すれば、計算の過程も表示できる >cargo run -- input.txt 2 21 1000000 >cargo run 1 1 + 2 ■テストの追加 main.rs の最後に以下を追加する
#[cfg(test)] mod tests { use super::*; #[test] fn test_ok() { assert_eq!(2 * 2, 4); } }
以下のとおり、テストを実行できる >cargo test running 1 test test tests::test_ok ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 以下のとおり、テストを修正する(テスト内容の追加)
#[cfg(test)] mod tests { use super::*; #[test] fn test_ok() { let calc = RpnCalculator::new(false); assert_eq!(calc.eval("5"), 5); assert_eq!(calc.eval("50"), 50); assert_eq!(calc.eval("-50"), -50); assert_eq!(calc.eval("2 3 +"), 5); assert_eq!(calc.eval("2 3 -"), -1); assert_eq!(calc.eval("2 3 *"), 6); assert_eq!(calc.eval("2 3 /"), 0); assert_eq!(calc.eval("2 3 %"), 2); } #[test] #[should_panic] fn test_ng() { let calc = RpnCalculator::new(false); assert_eq!(calc.eval("2 3 ^"), 5); } }
以下のとおり、テストを実行できる >cargo test running 2 tests test tests::test_ok ... ok test tests::test_ng - should panic ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ■エラーハンドリングの追加 まずはエラーハンドリングしないプログラムを用意する エラーが発生したらexpectにより、「panic!」へエラーメッセージを送るようにはしている
use std::fs; fn get_int_from_file() -> i32 { let path = "number.txt"; let num_str = fs::read_to_string(path).expect("failed to open the file."); let ret = num_str .trim() .parse::<i32>() .expect("failed to parse string to a number."); ret * 2 } fn main() { println!("{}", get_int_from_file()); }
プログラムを実行する場所に「number.txt」を作成し、その中に「2」と書いた場合、 以下のように実行して2倍にした値を表示する >cargo run 4 「number.txt」が見つからない場合、以下のようにpanicが発生する >cargo run thread 'main' panicked at src/main.rs:6:44: failed to open the file.: Os { code: 2, kind: NotFound, message: "指定されたファイルが見つかりません。" } note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: process didn't exit successfully: `target\debug\rpncalc.exe` (exit code: 101) このプログラムに、エラーハンドリングを追加する
use std::fs; fn get_int_from_file() -> Result<i32, String> { let path = "number.txt"; let num_str = fs::read_to_string(path).map_err(|e| e.to_string())?; let ret = num_str .trim() .parse::<i32>() .map(|t| t * 2) .map_err(|e| e.to_string()); ret } fn main() { match get_int_from_file() { Ok(x) => println!("{}", x), Err(e) => println!("{}", e), } }
Result型を使って、返す値によって処理を分岐させる 今回は「正常終了時はi32を返し、エラー時はStringを返す」とする get_int_from_file関数内に「?」があるが、これはResult型を返す関数で使える演算子 今回の場合は「fs::read_to_string(path) がOkなら値を返し、Errならエラー内容を文字列にして関数からエラーを返す」となっている mapとmap_errは、Okの場合の処理とErrの場合の処理を記述できるもの 「.parse::<i32>() がOkなら値を2倍にして返し、Errならエラー内容を文字列にして関数からエラーを返す」となっている 以下のように実行でき、エラー時はエラーメッセージが出力される >cargo run 4 >cargo run 指定されたファイルが見つかりません。 (os error 2) ■エラーハンドリングの追加(anyhowクレートを使用する場合) 「anyhow v1.0.86」を使用する anyhow - crates.io: Rust Package Registry https://crates.io/crates/anyhow Cargo.toml の「[dependencies]」部分に以下を追加する
anyhow = "1.0"
プログラムを以下のように変更する get_int_from_fileの返す方は「i32」のみでいい エラー部分は「context」もしくは「with_context」とすることで処理できる
use std::fs; use anyhow::{Context, Result}; fn get_int_from_file() -> Result<i32> { let path = "number.txt"; let num_str = fs::read_to_string(path).with_context(|| format!("failed to read string from {}", path))?; let ret = num_str .trim() .parse::<i32>() .map(|t| t * 2) .context("failed to parse string"); ret } fn main() { match get_int_from_file() { Ok(x) => println!("{}", x), Err(e) => println!("{}", e), } }
以下のとおり実行できる >cargo run 4 >cargo run failed to read string from number.txt … ファイルが存在しない場合 >cargo run failed to parse string … ファイルの内容が数字で無い場合 ■エラーハンドリングの追加(thiserrorクレートを使用する場合) ※未検証 anyhowクレートは自分のアプリケーション作成に、 thiserrorは他のアプリケーションから呼び出されるライブラリ作成に、 それぞれ向いているらしい ■逆ポーランド記法のプログラムにエラー処理を追加
use clap::Parser; use std::path::PathBuf; use std::fs::File; use std::io::{stdin, BufRead, BufReader}; use anyhow::{bail, ensure, Context, Result}; /// RPN Calculator #[derive(Parser, Debug)] #[command( version = "1.0.0", author = "refirio", about = "Super awesome sample RPN calculator." )] struct Args { /// Set the level of verbosity #[arg(short, long)] verbose: bool, /// Formulas written in RPN #[arg(name = "FILE")] formula_file: Option<PathBuf>, } struct RpnCalculator(bool); impl RpnCalculator { pub fn new(verbose: bool) -> Self { Self(verbose) } pub fn eval(&self, formula: &str) -> Result<i32> { let mut tokens = formula.split_whitespace().rev().collect::<Vec<_>>(); self.eval_inner(&mut tokens) } pub fn eval_inner(&self, tokens: &mut Vec<&str>) -> Result<i32> { let mut stack = Vec::new(); let mut pos = 0; while let Some(token) = tokens.pop() { pos += 1; if let Ok(x) = token.parse::<i32>() { stack.push(x); } else { let y = stack.pop().context(format!("invalid syntax at {}", pos))?; let x = stack.pop().context(format!("invalid syntax at {}", pos))?; let res = match token { "+" => x + y, "-" => x - y, "*" => x * y, "/" => x / y, "%" => x % y, _ => bail!("invalid token at {}", pos), }; stack.push(res); } // もし「-v」オプションが指定されていれば(無名フィールドの先頭要素に値があれば)、この時点でのトークンとスタックの状態を出力する if self.0 { println!("{:?} {:?}", tokens, stack); } } ensure!(stack.len() == 1, "invalid syntax"); Ok(stack[0]) } } fn main() -> Result<()> { let args = Args::parse(); if let Some(path) = args.formula_file { let f = File::open(path)?; let reader = BufReader::new(f); run(reader, args.verbose) } else { let stdin = stdin(); let reader = stdin.lock(); run(reader, args.verbose) } } fn run<R: BufRead>(reader: R, verbose: bool) -> Result<()> { let calc = RpnCalculator::new(verbose); for line in reader.lines() { let line = line?; match calc.eval(&line) { Ok(answer) => println!("{}", answer), Err(e) => eprintln!("{:#?}", e), } } Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_ok() { let calc = RpnCalculator::new(false); assert_eq!(calc.eval("5").unwrap(), 5); assert_eq!(calc.eval("50").unwrap(), 50); assert_eq!(calc.eval("-50").unwrap(), -50); assert_eq!(calc.eval("2 3 +").unwrap(), 5); assert_eq!(calc.eval("2 3 -").unwrap(), -1); assert_eq!(calc.eval("2 3 *").unwrap(), 6); assert_eq!(calc.eval("2 3 /").unwrap(), 0); assert_eq!(calc.eval("2 3 %").unwrap(), 2); } #[test] #[should_panic] fn test_ng() { let calc = RpnCalculator::new(false); assert_eq!(calc.eval("2 3 ^").unwrap(), 5); } }
anyhowのbailとensureについては、以下が参考になる Rust, anyhowのマクロ(anyhow!, bail!, ensure!)の使い方 | rs.nkmk.me https://rs.nkmk.me/rust-anyhow-macro/ またエラー処理に加えて、パスをString型ではなくstd::path::PathBuf型を使うようにも変更している
Webアプリケーション
実践Rustプログラミング入門 - 秀和システム あなたの学びをサポート! https://www.shuwasystem.co.jp/book/9784798061702.html ここでは、WebアプリケーションでTODOリストを作成する 以下のとおり、プロジェクトを作成する >cd C:\Users\refirio\Rust >cargo new todo --bin >cd todo 「actix-web v4.8.0」「actix-rt v2.10.0」を使用する https://crates.io/crates/actix-web https://crates.io/crates/actix-rt Cargo.toml の最終行を以下のように調整する
[dependencies] ↓ [dependencies] actix-web = "4.8" actix-rt = "2.10"
src/main.rs の内容を以下のようにする
use actix_web::{get, App, HttpServer, Responder}; #[get("/")] async fn index() -> impl Responder { format!("Hello, world!") } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new().service(index) }) .bind(("127.0.0.1", 8080))? .run() .await }
以下のとおり実行する >cargo run ブラウザソフトで以下にアクセスすると、画面に「Hello, world!」と表示される http://localhost:8080/ 以下のようにすると、さらに http://localhost:8080/hello/Taro にアクセスしたとき「Hello, Taro!」と表示されるようになる
use actix_web::{get, web, App, HttpServer, Responder}; #[get("/")] async fn index() -> impl Responder { format!("Hello, world!") } #[get("/hello/{name}")] async fn hello(name: web::Path<String>) -> impl Responder { format!("Hello, {name}!") } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .service(index) .service(hello) }) .bind(("127.0.0.1", 8080))? .run() .await }
■データベース Rust大好きっ子のためのデータベース考 https://zenn.dev/kyoheiu/articles/c3b6b6f156e57a ■メモ Rust で Web アプリケーションはどこまで開発できるのか - Speaker Deck https://speakerdeck.com/helloyuk13/rust-de-web-apurikesiyonhadokomadekai-fa-dekirufalseka Rustでサイトを再実装 https://r7kamura.com/articles/2021-11-07-ruby-to-rust-at-this-site RustでWebアプリケーションのバックエンドを開発するには ─ 型システムの堅牢性と柔軟性を業務システムにも! - エンジニアHub|Webエンジニアのキャリアを考える! https://eh-career.com/engineerhub/entry/2022/09/12/093000 Rust on Rails!? Rust 版の Rails と呼ばれる "Loco" を試す! https://zenn.dev/collabostyle/articles/45762b07bc16fb
WebAssembly
RustからWebAssembly (wasm)を生成してJavaScriptとブリッジ通信してみる | DevelopersIO https://dev.classmethod.jp/articles/rust-webassembly-javascript/ RustではじめるWebAssembly入門〜JavaScriptを超える高速なWebアプリ開発を実践しよう (1/2):CodeZine(コードジン) https://codezine.jp/article/detail/14567 動かして学ぶ! Rustの言語仕様 (1/2):CodeZine(コードジン) https://codezine.jp/article/detail/15083 Amazon Prime Videoが動画再生にWebAssemblyを採用。再生デバイス上にWasm VMをデプロイ、高フレームレートなど実現 − Publickey https://www.publickey1.jp/blog/22/amazon_prime_videowebassemblywasm_vm.html WebAssemblyの歴史について https://zenn.dev/hodagi/articles/4925afbeb3c4dc 「第4のブラウザ言語」WebAssemblyが変えるフロントエンド開発 - レバテックラボ(レバテックLAB) https://levtech.jp/media/article/column/detail_484/ ウェブエンジニアでもWasmを使いたい! アフタートーク https://zenn.dev/monicle/articles/a543b8ef919058
画像操作
線形代数学+Rustで画像圧縮のアルゴリズムを実装する #Rust - Qiita https://qiita.com/tronicboy/items/cfa9f68f14f1823c813e Rust で画像処理? #入門 - Qiita https://qiita.com/RotaryKer/items/a9897ddccdd0d8e0a1c2 Rustでカメラから取得した画像に対して画像処理をする - Scepter914 Website https://scepter914.github.io/blog/2021/20210813_rust_image_processing/ Rust研究:画像ファイルの処理 #image - Qiita https://qiita.com/mekamaru/items/7ace29cc7a0ad076f452
Excel操作
Rust で Excel オートメーション (windows-rs 版) - Qiita https://qiita.com/benki/items/42099c58e07b16293609 Rust で Excel オートメーション - Qiita https://qiita.com/benki/items/de2e104a5866fad0ebab
テキストエディタ
hecto: Build Your Own Text Editor in Rust | philipp's blog https://www.flenker.blog/hecto/ 以下はC言語版の紹介記事 C言語1000行でテキストエディタを作るチュートリアルをやった https://zenn.dev/kawarimidoll/articles/69119b3f5e1172
デスクトップアプリケーション
RustでGUIアプリケーションを開発するためのフレームワークを解説! https://jitera.com/ja/insights/33923 [Rust] icedに入門したのでメモを残す #GUI - Qiita https://qiita.com/Yosh31207/items/81632c6e2b54eb2f6f7c Rust によるデスクトップアプリケーションフレームワーク Tauri | 豆蔵デベロッパーサイト https://developer.mamezou-tech.com/blogs/2022/03/06/tauri/ 軽量RustフレームワークTauriでデスクトップアプリ開発をはじめよう | gihyo.jp https://gihyo.jp/article/2022/10/rust-monthly-topics-02 Electron代替を目指す「Tauri 2.0」正式版に到達。デスクトップアプリとモバイルアプリ開発の両方に対応 − Publickey https://www.publickey1.jp/blog/24/electrontauri_20.html
ブラウザソフト
ちいさな Web ブラウザを作ってみよう https://browserbook.shift-js.info/ ちいさなWebブラウザを作ってみようをやってみる https://zenn.dev/nissy_dev/scraps/ea74073a90f3c0
OS
Writing an OS in Rust https://os.phil-opp.com/ Writing an OS in Rust https://os.phil-opp.com/ja/ Writing an OS in Rustをやる https://zenn.dev/razokulover/scraps/8482e2478bfa14 ゼロからのOS自作入門をRustで実装した https://zenn.dev/egu/articles/2435e740b8284c 「ゼロからのOS自作入門」を Rust でやる (第1章〜第4章) - gifnksmの雑多なメモ https://gifnksm.hatenablog.jp/entry/2021/05/09/155149
プログラミング言語(rustack)
Rustで作るプログラミング言語 ―コンパイラ/インタプリタの基礎からプログラミング言語の新潮流まで:書籍案内|技術評論社 https://gihyo.jp/book/2024/978-4-297-14192-9 GitHub - msakuta/rustack: A very simple stack based language interpreter in Rust https://github.com/msakuta/rustack 前述の「コンソールアプリケーション」でも作ったが、書籍の順番どおり、まずは逆ポーランド記法で計算できるプログラムを作成する 以下のとおり、プロジェクトを作成する >cd C:\Users\refirio\Rust >cargo new rustack --bin >cd rustack ■ハードコーディングでの実装 src/main.rs の内容を以下のようにする (「{stack:?}」という書き方は、前述の「基本の文法」内にある「変数」を参照)
fn main() { let mut stack = vec![]; stack.push(42); stack.push(36); add(&mut stack); println!("stack: {stack:?}"); } fn add(stack: &mut Vec<i32>) { let x = stack.pop().unwrap(); let y = stack.pop().unwrap(); stack.push(x + y); }
以下のように、計算結果を表示できる >cargo run stack: [78] ■標準入力からの読み込み src/main.rs の内容を以下のようにする
fn main() { let mut stack = vec![]; stack.push(42); stack.push(36); add(&mut stack); println!("stack: {stack:?}"); } fn add(stack: &mut Vec<i32>) { let x = stack.pop().unwrap(); let y = stack.pop().unwrap(); stack.push(x + y); }
以下のように、入力内容に応じた結果が表示される 終了したい場合、Ctrl+Cで終了する >cargo run 42 36 + … 入力 Line: ["42", "36", "+"] … 結果が表示される ■パースとコマンドの実行 src/main.rs の内容を以下のようにする
fn main() { for line in std::io::stdin().lines() { let mut stack = vec![]; if let Ok(line) = line { let words: Vec<_> = line.split(" ").collect(); for word in words { if let Ok(parsed) = word.parse::<i32>() { stack.push(parsed); } else { match word { "+" => add(&mut stack), _ => panic!("{word:?} could not be parsed"), } } } println!("stack: {stack:?}"); } } } fn add(stack: &mut Vec<i32>) { let x = stack.pop().unwrap(); let y = stack.pop().unwrap(); stack.push(x + y); }
以下のように、入力内容に応じた計算結果が表示される 終了したい場合、Ctrl+Cで終了する >cargo run 42 36 + … 入力 stack: [78] … 計算結果が表示される 42 36 + 22 + … 入力 stack: [100] … 計算結果が表示される ■四則計算 src/main.rs の内容を以下のようにする (直感的な計算結果にするため、各計算の関数内でxとyの順番を逆にしているので注意)
fn main() { for line in std::io::stdin().lines() { let mut stack = vec![]; if let Ok(line) = line { let words: Vec<_> = line.split(" ").collect(); for word in words { if let Ok(parsed) = word.parse::<i32>() { stack.push(parsed); } else { match word { "+" => add(&mut stack), "-" => sub(&mut stack), "*" => mul(&mut stack), "/" => div(&mut stack), "%" => rem(&mut stack), _ => panic!("{word:?} could not be parsed"), } } } println!("stack: {stack:?}"); } } } fn add(stack: &mut Vec<i32>) { let y = stack.pop().unwrap(); let x = stack.pop().unwrap(); stack.push(x + y); } fn sub(stack: &mut Vec<i32>) { let y = stack.pop().unwrap(); let x = stack.pop().unwrap(); stack.push(x - y); } fn mul(stack: &mut Vec<i32>) { let y = stack.pop().unwrap(); let x = stack.pop().unwrap(); stack.push(x * y); } fn div(stack: &mut Vec<i32>) { let y = stack.pop().unwrap(); let x = stack.pop().unwrap(); stack.push(x / y); } fn rem(stack: &mut Vec<i32>) { let y = stack.pop().unwrap(); let x = stack.pop().unwrap(); stack.push(x % y); }
以下のように、入力内容に応じた計算結果が表示される 終了したい場合、Ctrl+Cで終了する >cargo run 2 3 + … 入力 stack: [5] … 計算結果が表示される 2 3 - … 入力 stack: [-1] … 計算結果が表示される 2 3 * … 入力 stack: [6] … 計算結果が表示される 2 3 / … 入力 stack: [0] … 計算結果が表示される 2 3 % … 入力 stack: [2] … 計算結果が表示される ■ブロックとネスト構造 src/main.rs の内容を以下のようにする
#[derive(Debug, PartialEq, Eq)] enum Value<'src> { Num(i32), Op(&'src str), Block(Vec<Value<'src>>), } impl<'src> Value<'src> { fn as_num(&self) -> i32 { match self { Self::Num(val) => *val, _=> panic!("Value is not a number"), } } } fn main() { for line in std::io::stdin().lines().flatten() { parse(&line); } } fn parse<'a>(line: &'a str) -> Vec<Value> { let mut stack = vec![]; let input: Vec<_> = line.split(" ").collect(); let mut words = &input[..]; while let Some((&word, mut rest)) = words.split_first() { if word.is_empty() { break; } if word == "{" { let value; (value, rest) = parse_block(rest); stack.push(value); } else if let Ok(parsed) = word.parse::<i32>() { stack.push(Value::Num(parsed)); } else { match word { "+" => add(&mut stack), "-" => sub(&mut stack), "*" => mul(&mut stack), "/" => div(&mut stack), "%" => rem(&mut stack), _ => panic!("{word:?} could not be parsed"), } } words = rest; } println!("stack: {stack:?}"); stack } fn parse_block<'src, 'a>(input: &'a[&'src str]) -> (Value<'src>, &'a[&'src str]) { let mut tokens = vec![]; let mut words = input; while let Some((&word, mut rest)) = words.split_first() { if word.is_empty() { break; } if word == "{" { let value; (value, rest) = parse_block(rest); tokens.push(value); } else if word == "}" { return(Value::Block(tokens), rest); } else if let Ok(value) = word.parse::<i32>() { tokens.push(Value::Num(value)); } else { tokens.push(Value::Op(word)); } words = rest; } (Value::Block(tokens), words) } fn add(stack: &mut Vec<Value>) { let y = stack.pop().unwrap().as_num(); let x = stack.pop().unwrap().as_num(); stack.push(Value::Num(x + y)); } fn sub(stack: &mut Vec<Value>) { let y = stack.pop().unwrap().as_num(); let x = stack.pop().unwrap().as_num(); stack.push(Value::Num(x - y)); } fn mul(stack: &mut Vec<Value>) { let y = stack.pop().unwrap().as_num(); let x = stack.pop().unwrap().as_num(); stack.push(Value::Num(x * y)); } fn div(stack: &mut Vec<Value>) { let y = stack.pop().unwrap().as_num(); let x = stack.pop().unwrap().as_num(); stack.push(Value::Num(x / y)); } fn rem(stack: &mut Vec<Value>) { let y = stack.pop().unwrap().as_num(); let x = stack.pop().unwrap().as_num(); stack.push(Value::Num(x % y)); } #[cfg(test)] mod tests { use super::{parse, Value::*}; #[test] fn test_group() { assert_eq!( parse("1 2 + { 3 4 }"), vec![Num(3), Block(vec![Num(3), Num(4)])] ); } }
以下のように、ブロックの処理内容が表示される 終了したい場合、Ctrl+Cで終了する >cargo run 1 1 + … 入力 stack: [Num(2)] … 計算結果が表示される 1 2 + { 3 4 + } 5 … 入力 stack: [Num(3), Block([Num(3), Num(4), Op("+")]), Num(5)] … 計算結果が表示される 1 2 + { 3 4 } … 入力 stack: [Num(3), Block([Num(3), Num(4)])] … 計算結果が表示される ■if制御構文 src/main.rs の内容を以下のようにする
#[derive(Debug, Clone, PartialEq, Eq)] enum Value<'src> { Num(i32), Op(&'src str), Block(Vec<Value<'src>>), } impl<'src> Value<'src> { fn as_num(&self) -> i32 { match self { Self::Num(val) => *val, _=> panic!("Value is not a number"), } } fn to_block(self) -> Vec<Value<'src>> { match self { Self::Block(val) => val, _=> panic!("Value is not a block"), } } } fn main() { for line in std::io::stdin().lines().flatten() { parse(&line); } } fn parse<'a>(line: &'a str) -> Vec<Value> { let mut stack = vec![]; let input: Vec<_> = line.split(" ").collect(); let mut words = &input[..]; while let Some((&word, mut rest)) = words.split_first() { if word.is_empty() { break; } if word == "{" { let value; (value, rest) = parse_block(rest); stack.push(value); } else { let code = if let Ok(num) = word.parse::<i32>() { Value::Num(num) } else { Value::Op(word) }; eval(code, &mut stack); } words = rest; } println!("stack: {stack:?}"); stack } fn eval<'src>(code: Value<'src>, stack: &mut Vec<Value<'src>>) { match code { Value::Op(op) => match op { "+" => add(stack), "-" => sub(stack), "*" => mul(stack), "/" => div(stack), "%" => rem(stack), "if" => op_if(stack), _ => panic!("{op:?} could not be parsed"), }, _ => stack.push(code.clone()), } } fn parse_block<'src, 'a>(input: &'a[&'src str]) -> (Value<'src>, &'a[&'src str]) { let mut tokens = vec![]; let mut words = input; while let Some((&word, mut rest)) = words.split_first() { if word.is_empty() { break; } if word == "{" { let value; (value, rest) = parse_block(rest); tokens.push(value); } else if word == "}" { return(Value::Block(tokens), rest); } else if let Ok(value) = word.parse::<i32>() { tokens.push(Value::Num(value)); } else { tokens.push(Value::Op(word)); } words = rest; } (Value::Block(tokens), words) } fn add(stack: &mut Vec<Value>) { let y = stack.pop().unwrap().as_num(); let x = stack.pop().unwrap().as_num(); stack.push(Value::Num(x + y)); } fn sub(stack: &mut Vec<Value>) { let y = stack.pop().unwrap().as_num(); let x = stack.pop().unwrap().as_num(); stack.push(Value::Num(x - y)); } fn mul(stack: &mut Vec<Value>) { let y = stack.pop().unwrap().as_num(); let x = stack.pop().unwrap().as_num(); stack.push(Value::Num(x * y)); } fn div(stack: &mut Vec<Value>) { let y = stack.pop().unwrap().as_num(); let x = stack.pop().unwrap().as_num(); stack.push(Value::Num(x / y)); } fn rem(stack: &mut Vec<Value>) { let y = stack.pop().unwrap().as_num(); let x = stack.pop().unwrap().as_num(); stack.push(Value::Num(x % y)); } fn op_if(stack: &mut Vec<Value>) { let false_branch = stack.pop().unwrap().to_block(); let true_branch = stack.pop().unwrap().to_block(); let cond = stack.pop().unwrap().to_block(); for code in cond { eval(code, stack); } let cond_result = stack.pop().unwrap().as_num(); if cond_result != 0 { for code in true_branch { eval(code, stack); } } else { for code in false_branch { eval(code, stack); } } } #[cfg(test)] mod tests { use super::{parse, Value::*}; #[test] fn test_group() { assert_eq!( parse("1 2 + { 3 4 }"), vec![Num(3), Block(vec![Num(3), Num(4)])] ); } #[test] fn test_if_false() { assert_eq!( parse("{ 1 -1 + } { 100 } { -100 } if"), vec![Num(-100)] ); } #[test] fn test_if_true() { assert_eq!( parse("{ 1 1 + } { 100 } { -100 } if"), vec![Num(100)] ); } }
>cargo run { 1 -1 + } { 100 } { -100 } if … 入力 stack: [Num(-100)] … 処理結果が表示される { 1 1 + } { 100 } { -100 } if … 入力 stack: [Num(100)] … 処理結果が表示される ■変数の定義 src/main.rs の内容を以下のようにする
use std::collections::HashMap; #[derive(Debug, Clone, PartialEq, Eq)] enum Value<'src> { Num(i32), Op(&'src str), Sym(&'src str), Block(Vec<Value<'src>>), } impl<'src> Value<'src> { fn as_num(&self) -> i32 { match self { Self::Num(val) => *val, _=> panic!("Value is not a number"), } } fn to_block(self) -> Vec<Value<'src>> { match self { Self::Block(val) => val, _=> panic!("Value is not a block"), } } fn as_sym(&self) -> &'src str { if let Self::Sym(sym) = self { *sym } else { panic!("Value is not a symbol") } } } struct Vm<'src> { stack: Vec<Value<'src>>, vars: HashMap<&'src str, Value<'src>>, } impl<'src> Vm<'src> { fn new() -> Self { Self { stack: vec![], vars: HashMap::new(), } } } fn main() { for line in std::io::stdin().lines().flatten() { parse(&line); } } fn parse<'a>(line: &'a str) -> Vec<Value> { let mut vm = Vm::new(); let input: Vec<_> = line.split(" ").collect(); let mut words = &input[..]; while let Some((&word, mut rest)) = words.split_first() { if word.is_empty() { break; } if word == "{" { let value; (value, rest) = parse_block(rest); vm.stack.push(value); } else { let code = if let Ok(num) = word.parse::<i32>() { Value::Num(num) } else if word.starts_with("/") { Value::Sym(&word[1..]) } else { Value::Op(word) }; eval(code, &mut vm); } words = rest; } println!("stack: {:?}", vm.stack); vm.stack } fn eval<'src>(code: Value<'src>, vm: &mut Vm<'src>) { match code { Value::Op(op) => match op { "+" => add(&mut vm.stack), "-" => sub(&mut vm.stack), "*" => mul(&mut vm.stack), "/" => div(&mut vm.stack), "%" => rem(&mut vm.stack), "<" => lt(&mut vm.stack), ">" => gt(&mut vm.stack), "if" => op_if(vm), "def" => op_def(vm), _ => { let val = vm.vars.get(op).expect(&format!( "{op:?} is not a defined operation" )); vm.stack.push(val.clone()); } }, _ => vm.stack.push(code.clone()), } } fn parse_block<'src, 'a>(input: &'a[&'src str]) -> (Value<'src>, &'a[&'src str]) { let mut tokens = vec![]; let mut words = input; while let Some((&word, mut rest)) = words.split_first() { if word.is_empty() { break; } if word == "{" { let value; (value, rest) = parse_block(rest); tokens.push(value); } else if word == "}" { return(Value::Block(tokens), rest); } else if let Ok(value) = word.parse::<i32>() { tokens.push(Value::Num(value)); } else { tokens.push(Value::Op(word)); } words = rest; } (Value::Block(tokens), words) } macro_rules! impl_op { {$name:ident, $op:tt} => { fn $name(stack: &mut Vec<Value>) { let y = stack.pop().unwrap().as_num(); let x = stack.pop().unwrap().as_num(); stack.push(Value::Num((x $op y) as i32)); } } } impl_op!(add, +); impl_op!(sub, -); impl_op!(mul, *); impl_op!(div, /); impl_op!(rem, %); impl_op!(lt, <); impl_op!(gt, >); fn op_if(vm: &mut Vm) { let false_branch = vm.stack.pop().unwrap().to_block(); let true_branch = vm.stack.pop().unwrap().to_block(); let cond = vm.stack.pop().unwrap().to_block(); for code in cond { eval(code, vm); } let cond_result = vm.stack.pop().unwrap().as_num(); if cond_result != 0 { for code in true_branch { eval(code, vm); } } else { for code in false_branch { eval(code, vm); } } } fn op_def(vm: &mut Vm) { let value = vm.stack.pop().unwrap(); eval(value, vm); let value = vm.stack.pop().unwrap(); let sym = vm.stack.pop().unwrap().as_sym(); vm.vars.insert(sym, value); } #[cfg(test)] mod tests { use super::{parse, Value::*}; #[test] fn test_group() { assert_eq!( parse("1 2 + { 3 4 }"), vec![Num(3), Block(vec![Num(3), Num(4)])] ); } #[test] fn test_if_false() { assert_eq!( parse("{ 1 -1 + } { 100 } { -100 } if"), vec![Num(-100)] ); } #[test] fn test_if_true() { assert_eq!( parse("{ 1 1 + } { 100 } { -100 } if"), vec![Num(100)] ); } #[test] fn test_var() { assert_eq!( parse("/x 10 def /y 20 def x y *"), vec![Num(200)] ); } #[test] fn test_var_if() { assert_eq!( parse("/x 10 def /y 20 def { x y < } { x } { y } if"), vec![Num(10)] ); } }
>cargo run /x 10 def /y 20 def x y * … 入力 stack: [Num(200)] … 処理結果が表示される /x 10 def /y 20 def { x y < } { x } { y } if … 入力 stack: [Num(10)] … 処理結果が表示される ■複数行のソースコードへの対応 src/main.rs の内容を以下のようにする
use std::{ collections::HashMap, io::{BufRead, BufReader} }; #[derive(Debug, Clone, PartialEq, Eq)] enum Value { Num(i32), Op(String), Sym(String), Block(Vec<Value>), } impl Value { fn as_num(&self) -> i32 { match self { Self::Num(val) => *val, _=> panic!("Value is not a number"), } } fn to_string(&self) -> String { match self { Self::Num(i) => i.to_string(), Self::Op(ref s) | Self::Sym(ref s) => s.clone(), Self::Block(_) => "<Block>".to_string(), } } fn as_sym(&self) -> &str { if let Self::Sym(sym) = self { sym } else { panic!("Value is not a symbol") } } fn to_block(self) -> Vec<Value> { match self { Self::Block(val) => val, _=> panic!("Value is not a block"), } } } struct Vm { stack: Vec<Value>, vars: HashMap<String, Value>, blocks: Vec<Vec<Value>>, } impl Vm { fn new() -> Self { Self { stack: vec![], vars: HashMap::new(), blocks: vec![], } } } fn main() { if let Some(f) = std::env::args() .nth(1) .and_then(|f| std::fs::File::open(f).ok()) { parse_batch(BufReader::new(f)); } else { parse_interactive(); } } fn parse_batch(source: impl BufRead) -> Vec<Value> { let mut vm = Vm::new(); for line in source.lines().flatten() { for word in line.split(" ") { parse_word(word, &mut vm); } } vm.stack } fn parse_interactive() { let mut vm = Vm::new(); for line in std::io::stdin().lines().flatten() { for word in line.split(" ") { parse_word(word, &mut vm); } println!("stack: {:?}", vm.stack); } } fn parse_word(word: &str, vm: &mut Vm) { if word.is_empty() { return; } if word == "{" { vm.blocks.push(vec![]); } else if word == "}" { let top_block = vm.blocks.pop().expect("Block stack underrun!"); eval(Value::Block(top_block), vm); } else { let code = if let Ok(num) = word.parse::<i32>() { Value::Num(num) } else if word.starts_with("/") { Value::Sym(word[1..].to_string()) } else { Value::Op(word.to_string()) }; eval(code, vm); } } fn eval(code: Value, vm: &mut Vm) { if let Some(top_block) = vm.blocks.last_mut() { top_block.push(code); return; } match code { Value::Op(ref op) => match op as &str { "+" => add(&mut vm.stack), "-" => sub(&mut vm.stack), "*" => mul(&mut vm.stack), "/" => div(&mut vm.stack), "%" => rem(&mut vm.stack), "<" => lt(&mut vm.stack), ">" => gt(&mut vm.stack), "if" => op_if(vm), "def" => op_def(vm), "puts" => puts(vm), _ => { let val = vm.vars.get(op).expect(&format!( "{op:?} is not a defined operation" )); vm.stack.push(val.clone()); } }, _ => vm.stack.push(code.clone()), } } macro_rules! impl_op { {$name:ident, $op:tt} => { fn $name(stack: &mut Vec<Value>) { let y = stack.pop().unwrap().as_num(); let x = stack.pop().unwrap().as_num(); stack.push(Value::Num((x $op y) as i32)); } } } impl_op!(add, +); impl_op!(sub, -); impl_op!(mul, *); impl_op!(div, /); impl_op!(rem, %); impl_op!(lt, <); impl_op!(gt, >); fn op_if(vm: &mut Vm) { let false_branch = vm.stack.pop().unwrap().to_block(); let true_branch = vm.stack.pop().unwrap().to_block(); let cond = vm.stack.pop().unwrap().to_block(); for code in cond { eval(code, vm); } let cond_result = vm.stack.pop().unwrap().as_num(); if cond_result != 0 { for code in true_branch { eval(code, vm); } } else { for code in false_branch { eval(code, vm); } } } fn op_def(vm: &mut Vm) { let value = vm.stack.pop().unwrap(); eval(value, vm); let value = vm.stack.pop().unwrap(); let sym = vm.stack.pop().unwrap().as_sym().to_string(); vm.vars.insert(sym, value); } fn puts(vm: &mut Vm) { let value = vm.stack.pop().unwrap(); println!("{}", value.to_string()); } #[cfg(test)] mod tests { use super::{Value::*, *}; use std::io::Cursor; fn parse(input: &str) -> Vec<Value> { parse_batch(Cursor::new(input)) } #[test] fn test_group() { assert_eq!( parse("1 2 + { 3 4 }"), vec![Num(3), Block(vec![Num(3), Num(4)])] ); } #[test] fn test_if_false() { assert_eq!( parse("{ 1 -1 + } { 100 } { -100 } if"), vec![Num(-100)] ); } #[test] fn test_if_true() { assert_eq!( parse("{ 1 1 + } { 100 } { -100 } if"), vec![Num(100)] ); } #[test] fn test_var() { assert_eq!( parse("/x 10 def /y 20 def x y *"), vec![Num(200)] ); } #[test] fn test_var_if() { assert_eq!( parse("/x 10 def /y 20 def { x y < } { x } { y } if"), vec![Num(10)] ); } #[test] fn test_multiline() { assert_eq!( parse( r#" /x 10 def /y 20 def { x y < } { x } { y } if "# ), vec![Num(10)] ); } }
input.txt を作成し、以下の内容を記述する
/x 10 def /y 20 def { x y < } { x } { y } if puts
>cargo run -- input.txt 10 なお、プログラム中にある「and_then」は、成功した場合のみ処理を実行するもの 「(|f| std::fs::File::open(f).ok())」については、前述の「クロージャ」を参照 ■関数呼び出し src/main.rs の内容を以下のようにする
use std::{ collections::HashMap, io::{BufRead, BufReader} }; #[derive(Debug, Clone, PartialEq, Eq)] enum Value { Num(i32), Op(String), Sym(String), Block(Vec<Value>), Native(NativeOp) } impl Value { fn as_num(&self) -> i32 { match self { Self::Num(val) => *val, _=> panic!("Value is not a number"), } } fn to_string(&self) -> String { match self { Self::Num(i) => i.to_string(), Self::Op(ref s) | Self::Sym(ref s) => s.clone(), Self::Block(_) => "<Block>".to_string(), Self::Native(_) => "<Native>".to_string(), } } fn as_sym(&self) -> &str { if let Self::Sym(sym) = self { sym } else { panic!("Value is not a symbol") } } fn to_block(self) -> Vec<Value> { match self { Self::Block(val) => val, _=> panic!("Value is not a block"), } } } #[derive(Clone)] struct NativeOp(fn(&mut Vm)); impl PartialEq for NativeOp { fn eq(&self, other: &NativeOp) -> bool { self.0 as *const fn() == other.0 as *const fn() } } impl Eq for NativeOp {} impl std::fmt::Debug for NativeOp { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "<NativeOp>") } } struct Vm { stack: Vec<Value>, vars: HashMap<String, Value>, blocks: Vec<Vec<Value>>, } impl Vm { fn new() -> Self { let functions: [(&str, fn(&mut Vm)); 14] = [ ("+", add), ("-", sub), ("*", mul), ("/", div), ("%", rem), ("<", lt), (">", gt), ("if", op_if), ("def", op_def), ("puts", puts), ("pop", pop), ("dup", dup), ("exch", exch), ("index", index), ]; Self { stack: vec![], vars: functions .into_iter() .map(|(name, fun)| { (name.to_owned(), Value::Native(NativeOp(fun))) }) .collect(), blocks: vec![], } } } fn main() { if let Some(f) = std::env::args() .nth(1) .and_then(|f| std::fs::File::open(f).ok()) { parse_batch(BufReader::new(f)); } else { parse_interactive(); } } fn parse_batch(source: impl BufRead) -> Vec<Value> { let mut vm = Vm::new(); for line in source.lines().flatten() { for word in line.split(" ") { parse_word(word, &mut vm); } } vm.stack } fn parse_interactive() { let mut vm = Vm::new(); for line in std::io::stdin().lines().flatten() { for word in line.split(" ") { parse_word(word, &mut vm); } println!("stack: {:?}", vm.stack); } } fn parse_word(word: &str, vm: &mut Vm) { if word.is_empty() { return; } if word == "{" { vm.blocks.push(vec![]); } else if word == "}" { let top_block = vm.blocks.pop().expect("Block stack underrun!"); eval(Value::Block(top_block), vm); } else { let code = if let Ok(num) = word.parse::<i32>() { Value::Num(num) } else if word.starts_with("/") { Value::Sym(word[1..].to_string()) } else { Value::Op(word.to_string()) }; eval(code, vm); } } fn eval(code: Value, vm: &mut Vm) { if let Some(top_block) = vm.blocks.last_mut() { top_block.push(code); return; } if let Value::Op(ref op) = code { let val = vm.vars.get(op).expect(&format!( "{op:?} is not a defined operation" )).clone(); match val { Value::Block(block) => { for code in block { eval(code, vm); } } Value::Native(op) => op.0(vm), _ => vm.stack.push(val), } } else { vm.stack.push(code.clone()); } } macro_rules! impl_op { {$name:ident, $op:tt} => { fn $name(vm: &mut Vm) { let y = vm.stack.pop().unwrap().as_num(); let x = vm.stack.pop().unwrap().as_num(); vm.stack.push(Value::Num((x $op y) as i32)); } } } impl_op!(add, +); impl_op!(sub, -); impl_op!(mul, *); impl_op!(div, /); impl_op!(rem, %); impl_op!(lt, <); impl_op!(gt, >); fn op_if(vm: &mut Vm) { let false_branch = vm.stack.pop().unwrap().to_block(); let true_branch = vm.stack.pop().unwrap().to_block(); let cond = vm.stack.pop().unwrap().to_block(); for code in cond { eval(code, vm); } let cond_result = vm.stack.pop().unwrap().as_num(); if cond_result != 0 { for code in true_branch { eval(code, vm); } } else { for code in false_branch { eval(code, vm); } } } fn op_def(vm: &mut Vm) { let value = vm.stack.pop().unwrap(); eval(value, vm); let value = vm.stack.pop().unwrap(); let sym = vm.stack.pop().unwrap().as_sym().to_string(); vm.vars.insert(sym, value); } fn puts(vm: &mut Vm) { let value = vm.stack.pop().unwrap(); println!("{}", value.to_string()); } fn pop(vm: &mut Vm) { vm.stack.pop().unwrap(); } fn dup(vm: &mut Vm) { let value = vm.stack.last().unwrap(); vm.stack.push(value.clone()); } fn exch(vm: &mut Vm) { let last = vm.stack.pop().unwrap(); let second = vm.stack.pop().unwrap(); vm.stack.push(last); vm.stack.push(second); } fn index(vm: &mut Vm) { let index = vm.stack.pop().unwrap().as_num() as usize; let value = vm.stack[vm.stack.len() - index - 1].clone(); vm.stack.push(value); } #[cfg(test)] mod tests { use super::{Value::*, *}; use std::io::Cursor; fn parse(input: &str) -> Vec<Value> { parse_batch(Cursor::new(input)) } #[test] fn test_group() { assert_eq!( parse("1 2 + { 3 4 }"), vec![Num(3), Block(vec![Num(3), Num(4)])] ); } #[test] fn test_if_false() { assert_eq!( parse("{ 1 -1 + } { 100 } { -100 } if"), vec![Num(-100)] ); } #[test] fn test_if_true() { assert_eq!( parse("{ 1 1 + } { 100 } { -100 } if"), vec![Num(100)] ); } #[test] fn test_var() { assert_eq!( parse("/x 10 def /y 20 def x y *"), vec![Num(200)] ); } #[test] fn test_var_if() { assert_eq!( parse("/x 10 def /y 20 def { x y < } { x } { y } if"), vec![Num(10)] ); } #[test] fn test_multiline() { assert_eq!( parse( r#" /x 10 def /y 20 def { x y < } { x } { y } if "# ), vec![Num(10)] ); } #[test] fn test_function() { assert_eq!( parse( r#" /double { 2 * } def 10 double "# ), vec![Num(20)] ); } }
input.txt を作成し、以下の内容を記述する
/double { 2 * } def /square { dup * } def 10 double puts 10 square puts
>cargo run -- input.txt 20 100 今度は input.txt の内容を以下のように変更する これで10の階上を計算できる(関数の再帰呼び出しができていることを確認できる)
/factorial { 1 factorial_int } def /factorial_int { /acc exch def /n exch def { n 2 < } { acc } { n 1 - acc n * factorial_int } if } def 10 factorial puts
>cargo run -- input.txt 3628800 ■ローカル変数 引き続き…
プログラミング言語(ruscal)
Rustで作るプログラミング言語 ―コンパイラ/インタプリタの基礎からプログラミング言語の新潮流まで:書籍案内|技術評論社 https://gihyo.jp/book/2024/978-4-297-14192-9 GitHub - msakuta/ruscal: Programming language implementation learning project https://github.com/msakuta/ruscal 以下のとおり、プロジェクトを作成する >cd C:\Users\refirio\Rust >cargo new ruscal --bin >cd ruscal ■構文へのマッチ src/main.rs の内容を以下のようにする
fn main() { let source = "123 world"; println!( "source: {}, parsed: {:?}", source, ident(whitespace(number(source))) ); } // スペースを認識する(文字列の先頭にあるスペースを取り除く) fn whitespace(mut input: &str) -> &str { while matches!( input.chars().next(), // 先頭の文字を取得(charsでイテレータを取得し、nextで最初の文字が参照される) Some(' ') // 先頭の文字がスペースかどうか ) { let mut chars = input.chars(); // 文字列のイテレータを取得 chars.next(); // 最初の文字を取得(この時点で、先頭のスペースが消費される) input = chars.as_str(); // 残りの文字列を再割り当て(この時点で、inputから先頭のスペースが取り除かれる) } input } fn ident(mut input: &str) -> &str { if matches!( input.chars().next(), Some(_x @ ('a'..='z' | 'A'..='Z')) // 先頭の文字がアルファベットかどうか ) { while matches!( input.chars().next(), Some(_x @ ('a'..='z' | 'A'..='Z' | '0'..='9')) // 2文字目がアルファベットもしくは数字かどうか ) { let mut chars = input.chars(); chars.next(); input = chars.as_str(); } } input } fn number(mut input: &str) -> &str { if matches!( input.chars().next(), Some(_x @ ('-' | '+' | '.' | '0'..='9')) // 先頭の文字が数字かどうか(プラスマイナスの記号も含む) ) { while matches!( input.chars().next(), Some(_x @ ('.' | '0'..='9')) // 2文字目が数字かどうか(プラスマイナスの記号は含まない) ) { let mut chars = input.chars(); chars.next(); input = chars.as_str(); } } input } #[cfg(test)] mod tests { use super::*; #[test] fn test_whitespace() { assert_eq!(whitespace(" "), ""); } #[test] fn test_ident() { assert_eq!(ident("Adam"), ""); } #[test] fn test_number() { assert_eq!(number("123.45 "), " "); } }
以下のように実行できる >cargo run source: 123 world, parsed: "" ※「_x」はパターンマッチに成功したときの値を一時的に保持する変数だが、今回は使用していない 詳細は「パターンマッチ」を参照 ■トークンの切り出し src/main.rs の内容を以下のようにする
fn main() { let input = "123 world"; println!("source: {}, parsed: {:?}", input, source(input)); let input = "Hello World"; println!("source: {}, parsed: {:?}", input, source(input)); let input = " world"; println!("source: {}, parsed: {:?}", input, source(input)); } fn source(mut input: &str) -> Vec<Token> { let mut tokens = vec![]; while !input.is_empty() { input = if let (next_input, Some(token)) = token(input) { tokens.push(token); next_input } else { break; } } tokens } #[derive(Debug, PartialEq, Eq)] enum Token { Ident, Number, } fn token(i: &str) -> (&str, Option<Token>) { if let (i, Some(ident_res)) = ident(whitespace(i)) { return (i, Some(ident_res)); } if let (i, Some(number_res)) = number(whitespace(i)) { return (i, Some(number_res)) } (i, None) } // スペースを認識する(文字列の先頭にあるスペースを取り除く) fn whitespace(mut input: &str) -> &str { while matches!( input.chars().next(), // 先頭の文字を取得(charsでイテレータを取得し、nextで最初の文字が参照される) Some(' ') // 先頭の文字がスペースかどうか ) { let mut chars = input.chars(); // 文字列のイテレータを取得 chars.next(); // 最初の文字を取得(この時点で、先頭のスペースが消費される) input = chars.as_str(); // 残りの文字列を再割り当て(この時点で、inputから先頭のスペースが取り除かれる) } input } // アルファベットを認識する(文字列の先頭にあるアルファベットを取り除く) fn ident(mut input: &str) -> (&str, Option<Token>) { if matches!( input.chars().next(), Some(_x @ ('a'..='z' | 'A'..='Z')) // 先頭の文字がアルファベットかどうか ) { while matches!( input.chars().next(), Some(_x @ ('a'..='z' | 'A'..='Z' | '0'..='9')) // 2文字目がアルファベットもしくは数字かどうか ) { let mut chars = input.chars(); chars.next(); input = chars.as_str(); } (input, Some(Token::Ident)) } else { (input, None) } } // 数字を認識する(文字列の先頭にある数字を取り除く) fn number(mut input: &str) -> (&str, Option<Token>) { if matches!( input.chars().next(), Some(_x @ ('-' | '+' | '.' | '0'..='9')) // 先頭の文字が数字かどうか(プラスマイナスの記号も含む) ) { while matches!( input.chars().next(), Some(_x @ ('.' | '0'..='9')) // 2文字目が数字かどうか(プラスマイナスの記号は含まない) ) { let mut chars = input.chars(); chars.next(); input = chars.as_str(); } (input, Some(Token::Number)) } else { (input, None) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_whitespace() { assert_eq!(whitespace(" "), ""); } #[test] fn test_ident() { assert_eq!(ident("Adam"), ("", Some(Token::Ident))); } #[test] fn test_number() { assert_eq!(number("123.45 "), (" ", Some(Token::Number))); } }
以下のように実行できる >cargo run source: 123 world, parsed: [Number, Ident] source: Hello World, parsed: [Ident, Ident] source: world, parsed: [Ident] ■カッコによるグループ化 src/main.rs の内容を以下のようにする
fn main() { let input = "(123 456 world)"; println!("source: {}, parsed: {:?}", input, source(input)); let input = "((car cdr) cdr)"; println!("source: {}, parsed: {:?}", input, source(input)); let input = "()())))((()))"; println!("source: {}, parsed: {:?}", input, source(input)); } fn advance_char(input: &str) -> &str { let mut chars = input.chars(); chars.next(); chars.as_str() } fn peek_char(input: &str) -> Option<char> { input.chars().next() } fn source(mut input: &str) -> Vec<Token> { let mut tokens = vec![]; while !input.is_empty() { input = if let (next_input, Some(token)) = token(input) { tokens.push(token); next_input } else { break; } } tokens } #[derive(Debug, PartialEq, Eq)] enum Token { Ident, Number, LParen, RParen, } fn token(i: &str) -> (&str, Option<Token>) { if let (i, Some(ident_res)) = ident(whitespace(i)) { return (i, Some(ident_res)); } if let (i, Some(number_res)) = number(whitespace(i)) { return (i, Some(number_res)) } if let (i, Some(number_res)) = lparen(whitespace(i)) { return (i, Some(number_res)) } if let (i, Some(number_res)) = rparen(whitespace(i)) { return (i, Some(number_res)) } (whitespace(i), None) } // スペースを認識する(文字列の先頭にあるスペースを取り除く) fn whitespace(mut input: &str) -> &str { while matches!( peek_char(input), Some(' ') // 先頭の文字がスペースかどうか ) { input = advance_char(input); } input } // アルファベットを認識する(文字列の先頭にあるアルファベットを取り除く) fn ident(mut input: &str) -> (&str, Option<Token>) { if matches!( peek_char(input), Some(_x @ ('a'..='z' | 'A'..='Z')) // 先頭の文字がアルファベットかどうか ) { while matches!( peek_char(input), Some(_x @ ('a'..='z' | 'A'..='Z' | '0'..='9')) // 2文字目がアルファベットもしくは数字かどうか ) { input = advance_char(input); } (input, Some(Token::Ident)) } else { (input, None) } } // 数字を認識する(文字列の先頭にある数字を取り除く) fn number(mut input: &str) -> (&str, Option<Token>) { if matches!( peek_char(input), Some(_x @ ('-' | '+' | '.' | '0'..='9')) // 先頭の文字が数字かどうか(プラスマイナスの記号も含む) ) { while matches!( peek_char(input), Some(_x @ ('.' | '0'..='9')) // 2文字目が数字かどうか(プラスマイナスの記号は含まない) ) { input = advance_char(input); } (input, Some(Token::Number)) } else { (input, None) } } // 左カッコを認識する(文字列の先頭にある左カッコを取り除く) fn lparen(mut input: &str) -> (&str, Option<Token>) { if matches!( peek_char(input), Some('(') // 先頭の文字が左カッコかどうか ) { input = advance_char(input); (input, Some(Token::LParen)) } else { (input, None) } } // 右カッコを認識する(文字列の先頭にある右カッコを取り除く) fn rparen(mut input: &str) -> (&str, Option<Token>) { if matches!( peek_char(input), Some(')') // 先頭の文字が右カッコかどうか ) { input = advance_char(input); (input, Some(Token::RParen)) } else { (input, None) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_whitespace() { assert_eq!(whitespace(" "), ""); } #[test] fn test_ident() { assert_eq!(ident("Adam"), ("", Some(Token::Ident))); } #[test] fn test_number() { assert_eq!(number("123.45 "), (" ", Some(Token::Number))); } }
以下のように実行できる >cargo run source: (123 456 world), parsed: [LParen, Number, Number, Ident, RParen] source: ((car cdr) cdr), parsed: [LParen, LParen, Ident, Ident, RParen, Ident, RParen] source: ()())))((())), parsed: [LParen, RParen, LParen, RParen, RParen, RParen, RParen, LParen, LParen, LParen, RParen, RParen, RParen] ■木構造の構築 src/main.rs の内容を以下のようにする
fn main() { let input = "Hello world"; println!("source: {}, parsed: {:?}", input, source(input)); let input = "(123 456 ) world"; println!("source: {}, parsed: {:?}", input, source(input)); let input = "((car cdr) cdr)"; println!("source: {}, parsed: {:?}", input, source(input)); let input = "()())))((()))"; println!("source: {}, parsed: {:?}", input, source(input)); } fn advance_char(input: &str) -> &str { let mut chars = input.chars(); chars.next(); chars.as_str() } fn peek_char(input: &str) -> Option<char> { input.chars().next() } fn source(mut input: &str) -> (&str, TokenTree) { let mut tokens = vec![]; while !input.is_empty() { input = if let Some((next_input, token)) = token(input) { match token { Token::LParen => { let (next_input, tt) = source(next_input); tokens.push(tt); next_input } Token::RParen => { return (next_input, TokenTree::Tree(tokens)) } _ => { tokens.push(TokenTree::Token(token)); next_input } } } else { break; } } (input, TokenTree::Tree(tokens)) } #[derive(Debug, PartialEq)] enum Token<'src> { Ident(&'src str), Number(f64), LParen, RParen, } #[derive(Debug, PartialEq)] enum TokenTree<'src> { Token(Token<'src>), Tree(Vec<TokenTree<'src>>), } fn token(input: &str) -> Option<(&str, Token)> { if let Some(res) = ident(whitespace(input)) { return Some(res); } if let Some(res) = number(whitespace(input)) { return Some(res); } if let Some(res) = lparen(whitespace(input)) { return Some(res); } if let Some(res) = rparen(whitespace(input)) { return Some(res); } None } // スペースを認識する(文字列の先頭にあるスペースを取り除く) fn whitespace(mut input: &str) -> &str { while matches!( peek_char(input), Some(' ') // 先頭の文字がスペースかどうか ) { input = advance_char(input); } input } // アルファベットを認識する(文字列の先頭にあるアルファベットを取り除く) fn ident(mut input: &str) -> Option<(&str, Token)> { let start = input; if matches!( peek_char(input), Some(_x @ ('a'..='z' | 'A'..='Z')) // 先頭の文字がアルファベットかどうか ) { while matches!( peek_char(input), Some(_x @ ('a'..='z' | 'A'..='Z' | '0'..='9')) // 2文字目がアルファベットもしくは数字かどうか ) { input = advance_char(input); } Some(( input, Token::Ident(&start[..(start.len() - input.len())]), )) } else { None } } // 数字を認識する(文字列の先頭にある数字を取り除く) fn number(mut input: &str) -> Option<(&str, Token)> { let start = input; if matches!( peek_char(input), Some(_x @ ('-' | '+' | '.' | '0'..='9')) // 先頭の文字が数字かどうか(プラスマイナスの記号も含む) ) { while matches!( peek_char(input), Some(_x @ ('.' | '0'..='9')) // 2文字目が数字かどうか(プラスマイナスの記号は含まない) ) { input = advance_char(input); } if let Ok(num) = start[..(start.len() - input.len())].parse::<f64>() { Some((input, Token::Number(num))) } else { None } } else { None } } // 左カッコを認識する(文字列の先頭にある左カッコを取り除く) fn lparen(mut input: &str) -> Option<(&str, Token)> { if matches!( peek_char(input), Some('(') // 先頭の文字が左カッコかどうか ) { input = advance_char(input); Some((input, Token::LParen)) } else { None } } // 右カッコを認識する(文字列の先頭にある右カッコを取り除く) fn rparen(mut input: &str) -> Option<(&str, Token)> { if matches!( peek_char(input), Some(')') // 先頭の文字が右カッコかどうか ) { input = advance_char(input); Some((input, Token::RParen)) } else { None } } #[cfg(test)] mod tests { use super::*; #[test] fn test_whitespace() { assert_eq!(whitespace(" "), ""); } #[test] fn test_ident() { assert_eq!(ident("Adam"), Some(("", Token::Ident("Adam")))); } #[test] fn test_number() { assert_eq!(number("123.45 "), Some((" ", Token::Number(123.45)))); } }
以下のように実行できる >cargo run source: Hello world, parsed: ("", Tree([Token(Ident("Hello")), Token(Ident("world"))])) source: (123 456 ) world, parsed: ("", Tree([Tree([Token(Number(123.0)), Token(Number(456.0))]), Token(Ident("world"))])) source: ((car cdr) cdr), parsed: ("", Tree([Tree([Tree([Token(Ident("car")), Token(Ident("cdr"))]), Token(Ident("cdr"))])])) source: ()())))((())), parsed: ("))((()))", Tree([Tree([]), Tree([])])) ■式の構文木 src/main.rs の内容を以下のようにする
fn main() { let input = "123"; println!("source: {}, parsed: {:?}", input, expr(input)); let input = "Hello + world"; println!("source: {}, parsed: {:?}", input, expr(input)); let input = "(123 + 456 ) + world"; println!("source: {}, parsed: {:?}", input, expr(input)); let input = "car + cdr + cdr"; println!("source: {}, parsed: {:?}", input, expr(input)); let input = "((1 + 2) + (3 + 4)) + 5 + 6"; println!("source: {}, parsed: {:?}", input, expr(input)); } fn advance_char(input: &str) -> &str { let mut chars = input.chars(); chars.next(); chars.as_str() } fn peek_char(input: &str) -> Option<char> { input.chars().next() } #[derive(Debug, PartialEq)] enum Expression<'src> { Ident(&'src str), NumLiteral(f64), Add(Box<Expression<'src>>, Box<Expression<'src>>), } // 式の全体を処理する fn expr(input: &str) -> Option<(&str, Expression)> { if let Some(res) = add(input) { return Some(res); } if let Some(res) = term(input) { return Some(res); } None } // カッコで囲まれた式を処理する fn paren(input: &str) -> Option<(&str, Expression)> { let next_input = lparen(whitespace(input))?; let (next_input, expr) = expr(next_input)?; let next_input = rparen(whitespace(next_input))?; Some((next_input, expr)) } // 加算演算の左側を処理する fn add_term(input: &str) -> Option<(&str, Expression)> { let (next_input, lhs) = term(input)?; let next_input = plus(whitespace(next_input))?; Some((next_input, lhs)) } // 加算式を処理する fn add(mut input: &str) -> Option<(&str, Expression)> { let mut left = None; while let Some((next_input, expr)) = add_term(input) { if let Some(prev_left) = left { left = Some(Expression::Add( Box::new(prev_left), Box::new(expr), )) } else { left = Some(expr); } input = next_input } let left = left?; let (next_input, rhs) = expr(input)?; Some(( next_input, Expression::Add(Box::new(left), Box::new(rhs)), )) } // 項を処理する fn term(input: &str) -> Option<(&str, Expression)> { if let Some(res) = paren(input) { return Some(res) } if let Some(res) = token(input) { return Some(res) } None } // 文字列を処理する fn token(input: &str) -> Option<(&str, Expression)> { if let Some(res) = ident(whitespace(input)) { return Some(res); } if let Some(res) = number(whitespace(input)) { return Some(res); } None } // スペースを認識する(文字列の先頭にあるスペースを取り除く) fn whitespace(mut input: &str) -> &str { while matches!( peek_char(input), Some(' ') // 先頭の文字がスペースかどうか ) { input = advance_char(input); } input } // アルファベットを認識する(文字列の先頭にあるアルファベットを取り除く) fn ident(mut input: &str) -> Option<(&str, Expression)> { let start = input; if matches!( peek_char(input), Some(_x @ ('a'..='z' | 'A'..='Z')) // 先頭の文字がアルファベットかどうか ) { while matches!( peek_char(input), Some(_x @ ('a'..='z' | 'A'..='Z' | '0'..='9')) // 2文字目がアルファベットもしくは数字かどうか ) { input = advance_char(input); } } if start.len() == input.len() { None } else { Some(( input, Expression::Ident(&start[..(start.len() - input.len())]), )) } } // 数字を認識する(文字列の先頭にある数字を取り除く) fn number(mut input: &str) -> Option<(&str, Expression)> { let start = input; if matches!( peek_char(input), Some(_x @ ('-' | '+' | '.' | '0'..='9')) // 先頭の文字が数字かどうか(プラスマイナスの記号も含む) ) { while matches!( peek_char(input), Some(_x @ ('.' | '0'..='9')) // 2文字目が数字かどうか(プラスマイナスの記号は含まない) ) { input = advance_char(input); } if let Ok(num) = start[..(start.len() - input.len())].parse::<f64>() { Some((input, Expression::NumLiteral(num))) } else { None } } else { None } } // 左カッコを認識する(文字列の先頭にある左カッコを取り除く) fn lparen(input: &str) -> Option<&str> { if matches!( peek_char(input), Some('(') // 先頭の文字が左カッコかどうか ) { Some(advance_char(input)) } else { None } } // 右カッコを認識する(文字列の先頭にある右カッコを取り除く) fn rparen(input: &str) -> Option<&str> { if matches!( peek_char(input), Some(')') // 先頭の文字が右カッコかどうか ) { Some(advance_char(input)) } else { None } } // プラスを認識する(文字列の先頭にあるプラスを取り除く) fn plus(input: &str) -> Option<&str> { if matches!( peek_char(input), Some('+') // 先頭の文字がプラスかどうか ) { Some(advance_char(input)) } else { None } } #[cfg(test)] mod tests { use super::*; #[test] fn test_whitespace() { assert_eq!(whitespace(" "), ""); } #[test] fn test_ident() { assert_eq!(ident("Adam"), Some(("", Expression::Ident("Adam")))); } #[test] fn test_number() { assert_eq!(number("123.45 "), Some((" ", Expression::NumLiteral(123.45)))); } }
以下のように実行できる >cargo run source: 123, parsed: Some(("", NumLiteral(123.0))) source: Hello + world, parsed: Some(("", Add(Ident("Hello"), Ident("world")))) source: (123 + 456 ) + world, parsed: Some(("", Add(Add(NumLiteral(123.0), NumLiteral(456.0)), Ident("world")))) source: car + cdr + cdr, parsed: Some(("", Add(Add(Ident("car"), Ident("cdr")), Ident("cdr")))) source: ((1 + 2) + (3 + 4)) + 5 + 6, parsed: Some(("", Add(Add(Add(Add(NumLiteral(1.0), NumLiteral(2.0)), Add(NumLiteral(3.0), NumLiteral(4.0))), NumLiteral(5.0)), NumLiteral(6.0)))) ■パーサコンピュータ nom 「anyhow v1.0.86」を使用する nom - crates.io: Rust Package Registry https://crates.io/crates/nom Cargo.toml の「[dependencies]」部分に以下を追加する
nom = "7.1.3"
src/main.rs の内容を以下のようにする
use nom::{ branch::alt, bytes::complete::tag, character::complete::{ alpha1, alphanumeric1, char, multispace0, }, combinator::recognize, multi::{fold_many0, many0}, number::complete::recognize_float, sequence::{delimited, pair}, IResult, }; fn main() { let input = "123"; println!("source: {:?}, parsed: {:?}", input, expr(input)); let input = "Hello + world"; println!("source: {:?}, parsed: {:?}", input, expr(input)); let input = "(123 + 456 ) + world"; println!("source: {:?}, parsed: {:?}", input, expr(input)); let input = "car + cdr + cdr"; println!("source: {:?}, parsed: {:?}", input, expr(input)); let input = "((1 + 2) + (3 + 4)) + 5 + 6"; println!("source: {:?}, parsed: {:?}", input, expr(input)); } #[derive(Debug, PartialEq, Clone)] enum Token<'src> { Ident(&'src str), Number(f64), } #[derive(Debug, PartialEq, Clone)] enum Expression<'src> { Value(Token<'src>), Add(Box<Expression<'src>>, Box<Expression<'src>>), } fn term(i: &str) -> IResult<&str, Expression> { alt((number, ident, parens))(i) } fn ident(input: &str) -> IResult<&str, Expression> { let (r, res) = delimited(multispace0, identifier, multispace0)(input)?; Ok((r, Expression::Value(Token::Ident(res)))) } fn identifier(input: &str) -> IResult<&str, &str> { recognize(pair( alt((alpha1, tag("_"))), many0(alt((alphanumeric1, tag("_")))), ))(input) } fn number(input: &str) -> IResult<&str, Expression> { let (r, v) = delimited(multispace0, recognize_float, multispace0)(input)?; Ok(( r, Expression::Value(Token::Number(v.parse().map_err( |_| { nom::Err::Error(nom::error::Error { input, code: nom::error::ErrorKind::Digit, }) }, )?)), )) } fn parens(i: &str) -> IResult<&str, Expression> { delimited( multispace0, delimited(tag("("), expr, tag(")")), multispace0 )(i) } fn expr(i: &str) -> IResult<&str, Expression> { let (i, init) = term(i)?; fold_many0( pair(delimited(multispace0, char('+'), multispace0), term), move || init.clone(), |acc, (_op, val): (char, Expression)| { Expression::Add(Box::new(acc), Box::new(val)) }, )(i) } #[cfg(test)] mod tests { use super::*; #[test] fn test_number() { assert_eq!(expr("123"), Ok(("", Expression::Value(Token::Number(123.0))))); } #[test] fn test_ident() { assert_eq!(expr("hello"), Ok(("", Expression::Value(Token::Ident("hello"))))); } }
以下のように実行できる >cargo run source: "123", parsed: Ok(("", Value(Number(123.0)))) source: "Hello + world", parsed: Ok(("", Add(Value(Ident("Hello")), Value(Ident("world"))))) source: "(123 + 456 ) + world", parsed: Ok(("", Add(Add(Value(Number(123.0)), Value(Number(456.0))), Value(Ident("world"))))) source: "car + cdr + cdr", parsed: Ok(("", Add(Add(Value(Ident("car")), Value(Ident("cdr"))), Value(Ident("cdr"))))) source: "((1 + 2) + (3 + 4)) + 5 + 6", parsed: Ok(("", Add(Add(Add(Add(Value(Number(1.0)), Value(Number(2.0))), Add(Value(Number(3.0)), Value(Number(4.0)))), Value(Number(5.0))), Value(Number(6.0))))) ■パーサコンピュータ nom(基本的な使い方を確認) Rust: nom によるパーサー実装 - MOXBOX https://hazm.at/mox/lang/rust/nom/index.html nomの基本的な挙動を確認する 「42abc」という文字が与えられたとして、digit1関数によって「42」という数値部分を取得する 厳密には「前方一致する部分と、それ以降の部分に分割する」という挙動になる
use nom::{ character::complete::digit1, IResult, }; fn main() { let input = "42abc"; match number(input) { Ok((remaining, parsed)) => println!("Input: {:?}, Parsed: {:?}, Remaining: {:?}", input, parsed, remaining), Err(error) => println!("Input: {:?}, Error: {:?}", input, error), } } fn number(input: &str) -> IResult<&str, &str> { let (input, value) = digit1(input)?; Ok((input, value)) }
以下のとおり実行できる >cargo run Input: "42abc", Parsed: "42", Remaining: "abc" multispace0関数によって0文字以上の空白を取得できる これを組み合わせれば、以下のように数字部分のみを取得できる
use nom::{ character::complete::digit1, character::complete::multispace0, IResult, }; fn main() { let input = " 42 "; match number(input) { Ok((remaining, parsed)) => println!("Input: {:?}, Parsed: {:?}, Remaining: {:?}", input, parsed, remaining), Err(error) => println!("Input: {:?}, Error: {:?}", input, error), } } fn number(input: &str) -> IResult<&str, &str> { let (input, _) = multispace0(input)?; let (input, value) = digit1(input)?; let (input, _) = multispace0(input)?; Ok((input, value)) }
以下のとおり実行できる >cargo run Input: " 42 ", Parsed: "42", Remaining: "" delimited関数によって、複数の関数をひとまとめにして使用できる 「multispace0, digit1, multispace0」を認識する関数を準備し、その関数に「input」という引数を渡して解析する なお、このように関数をひとまとめにできるのは、Rustにおける関数合成の文法によるもの
use nom::{ character::complete::digit1, character::complete::multispace0, sequence::delimited, IResult, }; fn main() { let input = " 42 "; match number(input) { Ok((remaining, parsed)) => println!("Input: {:?}, Parsed: {:?}, Remaining: {:?}", input, parsed, remaining), Err(error) => println!("Input: {:?}, Error: {:?}", input, error), } } fn number(input: &str) -> IResult<&str, &str> { let (remaining, value) = delimited( multispace0, digit1, multispace0 )(input)?; Ok((remaining, value)) }
さらに「Ok」部分を省略して、以下のように書くことができる(結果は変わらず)
use nom::{ character::complete::digit1, character::complete::multispace0, sequence::delimited, IResult, }; fn main() { let input = " 42 "; match number(input) { Ok((remaining, parsed)) => println!("Input: {:?}, Parsed: {:?}, Remaining: {:?}", input, parsed, remaining), Err(error) => println!("Input: {:?}, Error: {:?}", input, error), } } fn number(input: &str) -> IResult<&str, &str> { delimited( multispace0, digit1, multispace0 )(input) }
以下のとおり実行できる >cargo run Input: " 42 ", Parsed: "42", Remaining: "" 基本的に、この形式で使われることになる ■パーサコンピュータ nom(nomに付属する関数の挙動を確認) Rust: nom によるパーサー実装 - MOXBOX https://hazm.at/mox/lang/rust/nom/index.html alpha1関数について確認する これは、1文字以上のアルファベット文字(a-zまたはA-Z)を検出するパーサー 解析に成功すると、アルファベット文字の部分を返し、残りの文字列をタプルとして返す src/main.rs
use nom::character::complete::alpha1; use nom::error::Error; fn main() { let result = alpha1::<&str, Error<&str>>("hello123"); println!("{:?}", result); }
>cargo run Ok(("123", "hello")) alphanumeric1関数について確認する これは、1文字以上の英数字(アルファベットまたは数字)を検出するパーサー 解析に成功すると、英数字の部分を返し、残りの文字列をタプルとして返す src/main.rs
use nom::character::complete::alphanumeric1; use nom::error::Error; fn main() { let result = alphanumeric1::<&str, Error<&str>>("hello123!"); println!("{:?}", result); }
>cargo run Ok(("!", "hello123")) char関数について確認する これは、特定の1文字を解析するパーサーを作成する 今回の場合、char('a')は先頭の「a」を解析し、残りの文字列「bc」を返す src/main.rs
use nom::character::complete::char; use nom::error::Error; fn main() { let parse_a = char::<&str, Error<&str>>('a'); let result = parse_a("abc"); println!("{:?}", result); }
>cargo run Ok(("bc", 'a')) multispace0関数について確認する これは、0文字以上の空白(スペース、タブ、改行)を検出するパーサー 解析に成功すると、空白についての部分を返し、残りの文字列をタプルとして返す src/main.rs
use nom::character::complete::multispace0; use nom::error::Error; fn main() { let result = multispace0::<&str, Error<&str>>(" hello123"); println!("{:?}", result); }
>cargo run Ok(("hello123", " ")) delimited関数について確認する これは「開始部分」「メイン」「終了部分」を認識し、その「メイン」の内容を検出するパーサー カッコやスペースなどを無視して中間部分を抽出する処理を簡潔に書くことができる 以下はカッコを無視して抽出する例 src/main.rs
use nom::{ bytes::complete::tag, character::complete::alphanumeric1, sequence::delimited, IResult, }; fn main() { let input = "[hello123]"; match parse_bracketed(input) { Ok((remaining, parsed)) => println!("Parsed: {:?}, Remaining: {:?}", parsed, remaining), Err(error) => println!("Error: {:?}", error), } } fn parse_bracketed(input: &str) -> IResult<&str, &str> { // 角括弧で囲まれた内容を抽出 delimited( tag("["), // 開始部分: "[" にマッチ alphanumeric1, // メイン: 英数字にマッチ tag("]") // 終了部分: "]" にマッチ )(input) }
>cargo run Parsed: "hello123", Remaining: "" 以下はスペースを無視して抽出する例 src/main.rs
use nom::{ character::complete::{multispace0, digit1}, sequence::delimited, IResult, }; fn main() { let input = " 123 "; match parse_number_with_spaces(input) { Ok((remaining, parsed)) => println!("Parsed: {:?}, Remaining: {:?}", parsed, remaining), Err(error) => println!("Error: {:?}", error), } } fn parse_number_with_spaces(input: &str) -> IResult<&str, &str> { delimited( multispace0, // 前の空白を無視 digit1, // 数値部分を解析 multispace0 // 後ろの空白を無視 )(input) }
>cargo run Parsed: "123", Remaining: "" ■パーサコンピュータ nom(nomに付属する関数の挙動を確認した際の「::<T>」という書き方について) パス演算子「::」の直後に型を指定すると、「この関数に対して型を明示的に指定して呼び出す」となる 順を追って確認する 以下は型を明示した関数の例
// 大きい方の値を返す関数を定義 fn get_larger(a: i32, b: i32) -> i32 { if a > b { a } else { b } } fn main() { // 整数を渡して実行 let larger_int = get_larger(10, 20); println!("Larger integer: {}", larger_int); // 20 // 以下は型が異なるので実行できない //let larger_float = get_larger(1.5, 0.3); //println!("Larger float: {}", larger_float); }
以下はジェネリクス型を使った関数の例 このような関数がある場合に、型推論を使わないで呼び出し時に明示的に型を指定して実行できる (「PartialOrd」はトレイト境界(型の持つべき機能を明確にするための制約)で、Tが比較可能であることを保証するもの これがあるおかげで、「a > b」のような比較演算ができる このファイル内を「トレイト」で検索すると関連情報がある)
// 大きい方の値を返す関数を定義 fn get_larger<T: PartialOrd>(a: T, b: T) -> T { if a > b { a } else { b } } fn main() { // 整数を渡して実行 let larger_int = get_larger(10, 20); println!("Larger integer: {}", larger_int); // 20 // 浮動小数点数を渡して実行 let larger_float = get_larger(1.5, 0.3); println!("Larger float: {}", larger_float); // 1.5 // 型推論を使わないで呼び出し時に明示的に型を指定して実行 let max_int = get_larger::<i32>(30, 50); let max_float = get_larger::<f64>(5.5, 3.2); println!("Larger integer: {}", max_int); // 20 println!("Larger float: {}", max_float); // 5.5 }
なお「let result = alpha1::<&str, Error<&str>>("hello123");」のコードでは、型を指定する必要がある これを省略すると「cannot infer type of the type parameter `E` declared on the function `alpha1`」のエラーになる ただし通常「alt((alpha1, tag("_")))」のように使用されるため、そもそも型指定が問題になることは無いのだと思われる ■パーサコンピュータ nom(recognize関数の挙動を確認) recognize関数について確認する recognize関数を使わなければ、パースされた結果が構造化された状態で返される recognize関数を使うと、パースされた結果が元の文字列として返される。元の文字列として取得し、後で別の処理に使用することができる pair関数は2つのパーサーを順に実行し、両方が成功した場合にパースした部分を組み合わせて返す つまりalphanumeric1やdelimitedでのパース結果のように文字列が返されずに、タプルが返される 他の関数と挙動を合わせるために、recognize関数で文字列として返すようにする…ということだと思われる 以下はrecognize関数を使わない例
use nom::{ character::complete::{alpha1, digit1}, sequence::pair, IResult, }; fn main() { // サンプル入力 let inputs = vec![ "abc123", // 有効な文字列: アルファベットはじまりのアルファベットと数字の組み合わせ "ab12cd34", // 有効な文字列: アルファベットはじまりのアルファベットと数字の組み合わせ "456def", // 数字はじまり "xyz", // アルファベットのみ "789", // 数字のみ ]; // 各サンプル文字列に対してパーサーを実行 for input in inputs { match parse_combined(input) { Ok((remaining, parsed)) => println!("Input: {:?}, Parsed: {:?}, Remaining: {:?}", input, parsed, remaining), Err(error) => println!("Input: {:?}, Error: {:?}", input, error), } } } // `recognize` を使わず、アルファベットと数字の組み合わせをパースし、構造を返す fn parse_combined(input: &str) -> IResult<&str, (&str, &str)> { // `pair` によって、アルファベットと数字のペアを解析して返す pair( alpha1, // 最初にアルファベットがある digit1 // その後に数字が続く )(input) }
>cargo run Input: "abc123", Parsed: ("abc", "123"), Remaining: "" Input: "ab12cd34", Parsed: ("ab", "12"), Remaining: "cd34" Input: "456def", Error: Error(Error { input: "456def", code: Alpha }) Input: "xyz", Error: Error(Error { input: "", code: Digit }) Input: "789", Error: Error(Error { input: "789", code: Alpha }) 以下はrecognize関数を使った例
use nom::{ character::complete::{alpha1, digit1}, sequence::pair, combinator::recognize, IResult, }; fn main() { // サンプル入力 let inputs = vec![ "abc123", // 有効な文字列: アルファベットはじまりのアルファベットと数字の組み合わせ "ab12cd34", // 有効な文字列: アルファベットはじまりのアルファベットと数字の組み合わせ "456def", // 数字はじまり "xyz", // アルファベットのみ "789", // 数字のみ ]; // 各サンプル文字列に対してパーサーを実行 for input in inputs { match parse_combined(input) { Ok((remaining, parsed)) => println!("Input: {:?}, Parsed: {:?}, Remaining: {:?}", input, parsed, remaining), Err(error) => println!("Input: {:?}, Error: {:?}", input, error), } } } // アルファベットと数字の組み合わせをパースし、その元の文字列全体を返す fn parse_combined(input: &str) -> IResult<&str, &str> { // `recognize`を使って、パース成功部分の文字列全体を返す recognize( pair( alpha1, // 最初にアルファベットがある digit1 // その後に数字が続く ) )(input) }
>cargo run Compiling example v0.1.0 (C:\Users\refirio\Rust\example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.01s Running `target\debug\example.exe` Input: "abc123", Parsed: "abc123", Remaining: "" Input: "ab12cd34", Parsed: "ab12", Remaining: "cd34" Input: "456def", Error: Error(Error { input: "456def", code: Alpha }) Input: "xyz", Error: Error(Error { input: "", code: Digit }) Input: "789", Error: Error(Error { input: "789", code: Alpha }) ■パーサコンピュータ nom(identifier関数の挙動を確認) identifier関数について確認する これは、識別子として有効な部分を解析するもの 独自関数だが、単に「先頭の文字がアルファベットで、2文字目以降はアルファベットもしくは数字である」という判定をしているだけ(アンダーバーも有効な文字とみなす) src/main.rs
use nom::{ bytes::complete::tag, character::complete::{alpha1, alphanumeric1}, combinator::recognize, multi::many0, sequence::pair, branch::alt, IResult, }; fn main() { // サンプル文字列をテストする let test_strings = vec![ "hello123", // 有効な識別子 "_variable", // アンダースコアで始まる有効な識別子 "123abc", // 数字で始まる無効な識別子 "a_b_c", // 有効な識別子 "var-1", // 無効な識別子(ハイフンが含まれている) "func_name", // 有効な識別子 ]; // 各テスト文字列について、identifier関数を使用して解析を行う for input in test_strings { match identifier(input) { Ok((remaining, parsed)) => println!("Input: {:?}, Parsed: {:?}, Remaining: {:?}", input, parsed, remaining), Err(error) => println!("Input: {:?}, Error: {:?}", input, error), } } } // 入力文字列を受け取り、識別子として有効な部分を解析する fn identifier(input: &str) -> IResult<&str, &str> { recognize( // 中にあるパーサーを実行し、パースした内容を返す pair( // 2つのパーサーを順に実行し、両方が成功した場合にパースした部分を組み合わせて返す alt( // いずれかのパーサーが成功するまで、順にパースを試みる ( alpha1, // 先頭から1文字以上のアルファベットを解析する tag("_") // 指定された文字列を解析する ) ), many0( // 0回以上繰り返し使用できるパーサーを実行し、その結果をベクターとして返す alt( ( alphanumeric1, // 先頭から1文字以上のアルファベットまたは数字を解析する tag("_") // 指定された文字列を解析する ) ) ), ) )(input) }
>cargo run Input: "hello123", Parsed: "hello123", Remaining: "" Input: "_variable", Parsed: "_variable", Remaining: "" Input: "123abc", Error: Error(Error { input: "123abc", code: Tag }) Input: "a_b_c", Parsed: "a_b_c", Remaining: "" Input: "var-1", Parsed: "var", Remaining: "-1" Input: "func_name", Parsed: "func_name", Remaining: "" ■パーサコンピュータ nom(number関数の挙動を確認) number関数について確認する これは、識別子として認識された浮動小数点数をf64にパースして返す独自関数 src/main.rs
use nom::{ character::complete::multispace0, number::complete::recognize_float, sequence::delimited, IResult, }; fn main() { // サンプル文字列をテストする let test_strings = vec![ "123", // 有効な整数 "456.789", // 有効な小数 " 42.0 ", // 前後にスペースがある有効な小数 "-99", // 負の整数 "-3.14", // 負の小数 "abc", // 無効な数値 ]; // 各テスト文字列について、number関数を使用して解析を行う for input in test_strings { match number(input) { Ok((remaining, parsed)) => println!("Input: {:?}, Parsed: {:?}, Remaining: {:?}", input, parsed, remaining), Err(error) => println!("Input: {:?}, Error: {:?}", input, error), } } } // 入力文字列を受け取り、識別子として認識された浮動小数点数をf64にパースして返す fn number(input: &str) -> IResult<&str, f64> { let (remaining, value) = delimited(multispace0, recognize_float, multispace0)(input)?; let parsed_value: f64 = value.parse().map_err(|_| { nom::Err::Error(nom::error::Error { input, code: nom::error::ErrorKind::Digit, }) })?; Ok((remaining, parsed_value)) }
>cargo run Input: "123", Parsed: 123.0, Remaining: "" Input: "456.789", Parsed: 456.789, Remaining: "" Input: " 42.0 ", Parsed: 42.0, Remaining: "" Input: "-99", Parsed: -99.0, Remaining: "" Input: "-3.14", Parsed: -3.14, Remaining: "" Input: "abc", Error: Error(Error { input: "abc", code: Char }) ■パーサコンピュータ nom(map_errでのエラー処理部分を確認) ※nomとは直接関係ないが、number関数内で使われているmap_errの挙動について確認する ※map_errの基本については、前述の「コンソールアプリケーション」内にある「エラーハンドリングの追加」を参照 map_errを使わない場合、エラー処理は一例だが以下のようになる 「input.parse::<i32>()」が失敗したとき、エラーメッセージを手動で処理している
fn main() { //let input = "123"; let input = "abc"; match parse_number(input) { Ok(n) => println!("Parsed number: {}", n), Err(e) => println!("Error: {}", e), } } fn parse_number(input: &str) -> Result<i32, String> { match input.parse::<i32>() { Ok(n) => Ok(n), Err(_) => Err("Failed to parse the number".to_string()), } }
map_errを使うと、以下のようにエラー処理を簡潔に書くことができる エラーハンドリングのコードが簡潔になり、Errの変換部分がより明確になる
fn main() { //let input = "123"; let input = "abc"; match parse_number(input) { Ok(n) => println!("Parsed number: {}", n), Err(e) => println!("Error: {}", e), } } fn parse_number(input: &str) -> Result<i32, String> { //input.parse::<i32>().map_err(|_| "Failed to parse the number".to_string()) input.parse::<i32>().map_err( |_| { "Failed to parse the number".to_string() } ) }
エラーは以下のようなものが返されることになる これにより、エラーが発生した場所(abc)とエラーの種類(nom::error::ErrorKind::Digit)が返される なおこのエラーは「数値として認識できなかった」というもの
nom::Err::Error(nom::error::Error { input: "abc", code: nom::error::ErrorKind::Digit, })
■パーサコンピュータ nom(expr関数の挙動を確認) 以下の関数を理解するため、個別に詳細を確認していく
fn expr(i: &str) -> IResult<&str, Expression> { let (i, init) = term(i)?; fold_many0( pair(delimited(multispace0, char('+'), multispace0), term), move || init.clone(), |acc, (_op, val): (char, Expression)| { Expression::Add(Box::new(acc), Box::new(val)) }, )(i) }
まずはクロージャでの所有権の移動について
fn main() { let s1 = String::from("hello"); let s2 = s1; // 所有権がs1からs2に移動 // println!("{}", s1); // この行はエラーになる。s1の所有権はs2に移動しているため、s1はもう使用できない println!("{}", s2); // "hello" が出力される }
クロージャを使った場合、所有権の移動は「参照」となる よってクロージャの中と外の両方で参照できる
fn main() { let s1 = String::from("hello"); let closure = || { println!("{}", s1); // クロージャ内で`s1`を使用 }; println!("{}", s1); // "hello" が出力される closure(); // クロージャ内で "hello" が出力される }
moveを使うと、クロージャの中に所有権を移すことができる 所有権を移す変数を指定する必要は無く、クロージャ内で使用される変数が自動的に対象となる
fn main() { let s1 = String::from("hello"); let closure = move || { println!("{}", s1); // クロージャ内で`s1`を使用 }; // println!("{}", s1); // この行はエラーになる。s1の所有権はクロージャに移動しているため closure(); // クロージャ内で "hello" が出力される }
expr関数では、クロージャ内で独立して init を扱うためにmoveを指定している これにより、意図しない init の変更を防ぎ、変数を安全に扱うことができる 次にmany0とfold_many0の違いについて many0はパース結果を単純にリストとして保持するが、 fold_many0はクロージャで任意のロジックを指定して保持できるもので、これにより再帰処理ができる 以下のプログラムがあるとする
use nom::character::complete::{digit1, multispace0}; use nom::sequence::delimited; use nom::multi::many0; use nom::IResult; fn main() { let input = "10 20 30"; let result = parse_numbers(input); println!("{:?}", result); } fn parse_numbers(input: &str) -> IResult<&str, Vec<&str>> { many0( delimited(multispace0, digit1, multispace0) )(input) }
この場合、以下のように実行できる >cargo run Ok(("", ["10", "20", "30"])) 以下のプログラムがあるとする
use nom::character::complete::{digit1, multispace0}; use nom::sequence::delimited; use nom::multi::fold_many0; use nom::IResult; use std::str::FromStr; fn main() { let input = "10 20 30"; let result = parse_sum(input); println!("{:?}", result); } fn parse_sum(input: &str) -> IResult<&str, i32> { fold_many0( delimited(multispace0, digit1, multispace0), // 前後のスペースを無視して数値をパースする || { 0 // 初期値は0(fold_many0の仕様により、クロージャで渡す必要がある) }, |acc, item: &str| { // 変数「acc」に加算を行っていく。「acc」はaccumulator(累積変数)をもとにした命名で、「total」のような意味 acc + i32::from_str(item).unwrap() // 初期値の0に対して10・20・30が加算されていく }, )(input) }
この場合、以下のように実行できる >cargo run Ok(("", 60)) ■パーサコンピュータ nom(SQLの解析例) RustのnomでCREATE TABLEをパースする #Rust - Qiita https://qiita.com/aoyagikouhei/items/6013e24e686236f10e1b 上の記事では以下のSQLを
CREATE TABLE users ( uuid UUID NOT NULL PRIMARY KEY ,name TEXT NOT NULL DEFAULT '' )
以下のとおりパースしている
Table { name: "users", columns: [ TableColumn { name: "uuid", sql_type: "UUID", primary_key_flag: true, not_null_flag: true, default_value: None }, TableColumn { name: "name", sql_type: "TEXT", primary_key_flag: false, not_null_flag: true, default_value: Some("") } ] }
その他メモ Rustのパーサーコンビネーターnomを使ってPL/pgSQLをパースする #PostgreSQL - Qiita https://qiita.com/aoyagikouhei/items/c9b066a3687f484dc22a Rust でパーサコンビネータを作ってみる (前編) https://zenn.dev/nojima/articles/05cb9ffa0f993b Rust でパーサコンビネータを作ってみる (後編) https://zenn.dev/nojima/articles/e597d22660205d ■ASTインタプリタ src/main.rs の内容を以下のようにする
use nom::{ branch::alt, bytes::complete::tag, character::complete::{ alpha1, alphanumeric1, char, multispace0, }, combinator::recognize, multi::{fold_many0, many0}, number::complete::recognize_float, sequence::{delimited, pair}, IResult, }; fn main() { fn ex_eval<'src>( input: &'src str, ) -> Result<f64, nom::Err<nom::error::Error<&'src str>>> { expr(input).map(|(_, e)| eval(e)) } let input = "123"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "(123 + 456 ) + pi"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "10 + (100 + 1)"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "((1 + 2) + (3 + 4)) + 5 + 6"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); } #[derive(Debug, PartialEq, Clone)] enum Expression<'src> { Ident(&'src str), NumLiteral(f64), Add(Box<Expression<'src>>, Box<Expression<'src>>), } fn eval(expr: Expression) -> f64 { match expr { Expression::Ident("pi") => std::f64::consts::PI, Expression::Ident(id) => panic!("Unknown name {:?}", id), Expression::NumLiteral(n) => n, Expression::Add(lhs, rhs) => eval(*lhs) + eval(*rhs), } } fn term(i: &str) -> IResult<&str, Expression> { alt((number, ident, parens))(i) } fn ident(input: &str) -> IResult<&str, Expression> { let (r, res) = delimited(multispace0, identifier, multispace0)(input)?; Ok((r, Expression::Ident(res))) } fn identifier(input: &str) -> IResult<&str, &str> { recognize(pair( alt((alpha1, tag("_"))), many0(alt((alphanumeric1, tag("_")))), ))(input) } fn number(input: &str) -> IResult<&str, Expression> { let (r, v) = delimited(multispace0, recognize_float, multispace0)(input)?; Ok(( r, Expression::NumLiteral(v.parse().map_err( |_| { nom::Err::Error(nom::error::Error { input, code: nom::error::ErrorKind::Digit, }) }, )?), )) } fn parens(i: &str) -> IResult<&str, Expression> { delimited( multispace0, delimited(tag("("), expr, tag(")")), multispace0 )(i) } fn expr(i: &str) -> IResult<&str, Expression> { let (i, init) = term(i)?; fold_many0( pair(delimited(multispace0, char('+'), multispace0), term), move || init.clone(), |acc, (_op, val): (char, Expression)| { Expression::Add(Box::new(acc), Box::new(val)) }, )(i) } #[cfg(test)] mod tests { use super::*; #[test] fn test_number() { assert_eq!(expr("123"), Ok(("", Expression::NumLiteral(123.0)))); } #[test] fn test_pi_ident() { assert_eq!(expr("pi"), Ok(("", Expression::Ident("pi")))); } #[test] fn test_ident() { assert_eq!(expr("hello"), Ok(("", Expression::Ident("hello")))); } }
以下のように実行できる >cargo run source: "123", parsed: Ok(123.0) source: "(123 + 456 ) + pi", parsed: Ok(582.1415926535898) source: "10 + (100 + 1)", parsed: Ok(111.0) source: "((1 + 2) + (3 + 4)) + 5 + 6", parsed: Ok(21.0) ■ASTインタプリタ(計算の流れを確認) 以下の流れで処理されるみたい 1. 計算したい式をex_evalに渡す 2. exprによってExpressionの構造が構築され、構築に成功するとevalによって計算が行われる 3. 加算が含まれていると、eval内で再帰的にevalが呼び出されるので、カッコが複数あっても計算が行われる なお「2 + 3 + 4」のような式は「(2 + 3) + 4」のように解釈される 例えば以下の計算があった場合、
let input = "2 + 3"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input));
実行結果は以下のようになる >cargo run source: "2 + 3", parsed: Ok(5.0) 以下のように変更すると、
let input = "2 + 3"; println!("source: {:?}, parsed: {:?}", input, expr(input));
実行結果は以下のようになる >cargo run source: "2 + 3", parsed: Ok(("", Add(NumLiteral(2.0), NumLiteral(3.0)))) ■ASTインタプリタ(四則演算) src/main.rs の内容を以下のようにする
use nom::{ branch::alt, bytes::complete::tag, character::complete::{ alpha1, alphanumeric1, char, multispace0, }, combinator::recognize, error::ParseError, multi::{fold_many0, many0}, number::complete::recognize_float, sequence::{delimited, pair}, IResult, Parser, }; fn main() { fn ex_eval<'src>( input: &'src str, ) -> Result<f64, nom::Err<nom::error::Error<&'src str>>> { expr(input).map(|(_, e)| eval(e)) } let input = "123"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "2 + 3"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "5 - 2"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "2 * 3"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "6 / 2"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "2 * pi"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "(123 + 456 ) + pi"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "10 - (100 + 1)"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "(3 + 7) / (2 + 3)"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); } #[derive(Debug, PartialEq, Clone)] enum Expression<'src> { Ident(&'src str), NumLiteral(f64), Add(Box<Expression<'src>>, Box<Expression<'src>>), Sub(Box<Expression<'src>>, Box<Expression<'src>>), Mul(Box<Expression<'src>>, Box<Expression<'src>>), Div(Box<Expression<'src>>, Box<Expression<'src>>), } fn eval(expr: Expression) -> f64 { match expr { Expression::Ident("pi") => std::f64::consts::PI, Expression::Ident(id) => panic!("Unknown name {:?}", id), Expression::NumLiteral(n) => n, Expression::Add(lhs, rhs) => eval(*lhs) + eval(*rhs), Expression::Sub(lhs, rhs) => eval(*lhs) - eval(*rhs), Expression::Mul(lhs, rhs) => eval(*lhs) * eval(*rhs), Expression::Div(lhs, rhs) => eval(*lhs) / eval(*rhs), } } fn space_delimited<'src, O, E>( f: impl Parser<&'src str, O, E>, ) -> impl FnMut(&'src str) -> IResult<&'src str, O, E> where E: ParseError<&'src str>, { delimited(multispace0, f, multispace0) } fn factor(i: &str) -> IResult<&str, Expression> { alt((number, ident, parens))(i) } fn ident(input: &str) -> IResult<&str, Expression> { let (r, res) = space_delimited(identifier)(input)?; Ok((r, Expression::Ident(res))) } fn identifier(input: &str) -> IResult<&str, &str> { recognize(pair( alt((alpha1, tag("_"))), many0(alt((alphanumeric1, tag("_")))), ))(input) } fn number(input: &str) -> IResult<&str, Expression> { let (r, v) = space_delimited(recognize_float)(input)?; Ok(( r, Expression::NumLiteral(v.parse().map_err( |_| { nom::Err::Error(nom::error::Error { input, code: nom::error::ErrorKind::Digit, }) }, )?), )) } fn parens(i: &str) -> IResult<&str, Expression> { space_delimited( delimited(tag("("), expr, tag(")")) )(i) } fn term(i: &str) -> IResult<&str, Expression> { let (i, init) = factor(i)?; fold_many0( pair(space_delimited(alt((char('*'), char('/')))), factor), move || init.clone(), |acc, (op, val): (char, Expression)| { match op { '*' => Expression::Mul(Box::new(acc), Box::new(val)), '/' => Expression::Div(Box::new(acc), Box::new(val)), _ => panic!( "Multiplicative expression should have '*' or '/' operator" ), } }, )(i) } fn expr(i: &str) -> IResult<&str, Expression> { let (i, init) = term(i)?; fold_many0( pair(space_delimited(alt((char('+'), char('-')))), term), move || init.clone(), |acc, (op, val): (char, Expression)| match op { '+' => Expression::Add(Box::new(acc), Box::new(val)), '-' => Expression::Sub(Box::new(acc), Box::new(val)), _ => panic!( "Additive expression should have '+' or '-' operator" ), }, )(i) } #[cfg(test)] mod tests { use super::*; #[test] fn test_number() { assert_eq!(expr("123"), Ok(("", Expression::NumLiteral(123.0)))); } #[test] fn test_pi_ident() { assert_eq!(expr("pi"), Ok(("", Expression::Ident("pi")))); } #[test] fn test_ident() { assert_eq!(expr("hello"), Ok(("", Expression::Ident("hello")))); } }
以下のように実行できる >cargo run source: "123", parsed: Ok(123.0) source: "2 + 3", parsed: Ok(5.0) source: "5 - 2", parsed: Ok(3.0) source: "2 * 3", parsed: Ok(6.0) source: "6 / 2", parsed: Ok(3.0) source: "2 * pi", parsed: Ok(6.283185307179586) source: "(123 + 456 ) + pi", parsed: Ok(582.1415926535898) source: "10 - (100 + 1)", parsed: Ok(-91.0) source: "(3 + 7) / (2 + 3)", parsed: Ok(2.0) ■関数呼び出し src/main.rs の内容を以下のようにする
use nom::{ branch::alt, bytes::complete::tag, character::complete::{ alpha1, alphanumeric1, char, multispace0, }, combinator::{opt, recognize}, error::ParseError, multi::{fold_many0, many0}, number::complete::recognize_float, sequence::{delimited, pair}, IResult, Parser, }; fn main() { fn ex_eval<'src>( input: &'src str, ) -> Result<f64, nom::Err<nom::error::Error<&'src str>>> { expr(input).map(|(_, e)| eval(e)) } let input = "123"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "2 + 3"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "5 - 2"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "2 * 3"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "6 / 2"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "2 * pi"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "(123 + 456 ) + pi"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "10 - (100 + 1)"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "(3 + 7) / (2 + 3)"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "sqrt(2) / 2"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "sin(pi / 4)"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); let input = "atan2(1, 1)"; println!("source: {:?}, parsed: {:?}", input, ex_eval(input)); } #[derive(Debug, PartialEq, Clone)] enum Expression<'src> { Ident(&'src str), NumLiteral(f64), FnInvoke(&'src str, Vec<Expression<'src>>), Add(Box<Expression<'src>>, Box<Expression<'src>>), Sub(Box<Expression<'src>>, Box<Expression<'src>>), Mul(Box<Expression<'src>>, Box<Expression<'src>>), Div(Box<Expression<'src>>, Box<Expression<'src>>), } fn unary_fn( f: fn(f64) -> f64, ) -> impl Fn(Vec<Expression>) -> f64 { move |args| { f(eval( args.into_iter().next().expect("function missing argument"), )) } } fn binary_fn( f: fn(f64, f64) -> f64, ) -> impl Fn(Vec<Expression>) -> f64 { move |args| { let mut args = args.into_iter(); let lhs = eval( args.next().expect("function missing the first argument"), ); let rhs = eval( args.next().expect("function missing the second argument"), ); f(lhs, rhs) } } fn eval(expr: Expression) -> f64 { use Expression::*; match expr { Ident("pi") => std::f64::consts::PI, Ident(id) => panic!("Unknown name {:?}", id), NumLiteral(n) => n, FnInvoke("sqrt", args) => unary_fn(f64::sqrt)(args), FnInvoke("sin", args) => unary_fn(f64::sin)(args), FnInvoke("cos", args) => unary_fn(f64::cos)(args), FnInvoke("tan", args) => unary_fn(f64::tan)(args), FnInvoke("asin", args) => unary_fn(f64::asin)(args), FnInvoke("acos", args) => unary_fn(f64::acos)(args), FnInvoke("atan", args) => unary_fn(f64::atan)(args), FnInvoke("atan2", args) => binary_fn(f64::atan2)(args), FnInvoke("pow", args) => binary_fn(f64::powf)(args), FnInvoke("exp", args) => unary_fn(f64::exp)(args), FnInvoke("log", args) => binary_fn(f64::log)(args), FnInvoke("log10", args) => unary_fn(f64::log10)(args), FnInvoke(name, _) => { panic!("Unknown function {name:?}") }, Add(lhs, rhs) => eval(*lhs) + eval(*rhs), Sub(lhs, rhs) => eval(*lhs) - eval(*rhs), Mul(lhs, rhs) => eval(*lhs) * eval(*rhs), Div(lhs, rhs) => eval(*lhs) / eval(*rhs), } } fn space_delimited<'src, O, E>( f: impl Parser<&'src str, O, E>, ) -> impl FnMut(&'src str) -> IResult<&'src str, O, E> where E: ParseError<&'src str>, { delimited(multispace0, f, multispace0) } fn factor(i: &str) -> IResult<&str, Expression> { alt((number, func_call, ident, parens))(i) } fn func_call(i: &str) -> IResult<&str, Expression> { let (r, ident) = space_delimited(identifier)(i)?; let (r, args) = space_delimited(delimited( tag("("), many0(delimited( multispace0, expr, space_delimited(opt(tag(","))), )), tag(")"), ))(r)?; Ok((r, Expression::FnInvoke(ident, args))) } fn ident(input: &str) -> IResult<&str, Expression> { let (r, res) = space_delimited(identifier)(input)?; Ok((r, Expression::Ident(res))) } fn identifier(input: &str) -> IResult<&str, &str> { recognize(pair( alt((alpha1, tag("_"))), many0(alt((alphanumeric1, tag("_")))), ))(input) } fn number(input: &str) -> IResult<&str, Expression> { let (r, v) = space_delimited(recognize_float)(input)?; Ok(( r, Expression::NumLiteral(v.parse().map_err( |_| { nom::Err::Error(nom::error::Error { input, code: nom::error::ErrorKind::Digit, }) }, )?), )) } fn parens(i: &str) -> IResult<&str, Expression> { space_delimited( delimited(tag("("), expr, tag(")")) )(i) } fn term(i: &str) -> IResult<&str, Expression> { let (i, init) = factor(i)?; fold_many0( pair(space_delimited(alt((char('*'), char('/')))), factor), move || init.clone(), |acc, (op, val): (char, Expression)| { match op { '*' => Expression::Mul(Box::new(acc), Box::new(val)), '/' => Expression::Div(Box::new(acc), Box::new(val)), _ => panic!( "Multiplicative expression should have '*' or '/' operator" ), } }, )(i) } fn expr(i: &str) -> IResult<&str, Expression> { let (i, init) = term(i)?; fold_many0( pair(space_delimited(alt((char('+'), char('-')))), term), move || init.clone(), |acc, (op, val): (char, Expression)| match op { '+' => Expression::Add(Box::new(acc), Box::new(val)), '-' => Expression::Sub(Box::new(acc), Box::new(val)), _ => panic!( "Additive expression should have '+' or '-' operator" ), }, )(i) } #[cfg(test)] mod tests { use super::*; #[test] fn test_number() { assert_eq!(expr("123"), Ok(("", Expression::NumLiteral(123.0)))); } #[test] fn test_pi_ident() { assert_eq!(expr("pi"), Ok(("", Expression::Ident("pi")))); } #[test] fn test_ident() { assert_eq!(expr("hello"), Ok(("", Expression::Ident("hello")))); } }
以下のように実行できる >cargo run source: "123", parsed: Ok(123.0) source: "2 + 3", parsed: Ok(5.0) source: "5 - 2", parsed: Ok(3.0) source: "2 * 3", parsed: Ok(6.0) source: "6 / 2", parsed: Ok(3.0) source: "2 * pi", parsed: Ok(6.283185307179586) source: "(123 + 456 ) + pi", parsed: Ok(582.1415926535898) source: "10 - (100 + 1)", parsed: Ok(-91.0) source: "(3 + 7) / (2 + 3)", parsed: Ok(2.0) source: "sqrt(2) / 2", parsed: Ok(0.7071067811865476) source: "sin(pi / 4)", parsed: Ok(0.7071067811865476) source: "atan2(1, 1)", parsed: Ok(0.7853981633974483) >cargo run -- input.rscl 「unary_fn」「binary_fn」という関数を定義しているが、これは「引数が1つの関数」「引数が2つの関数」の意味 バイナリデータを扱うための処理…というわけでは無い(なお「バイナリデータ」は「2進数のデータ」のこと) 関数は引数の個数に応じて「unary」「binary」「ternary」などと呼ばれるらしい #PHP - Qiita https://qiita.com/suin/items/0ccf115cc30d6e9d641a arity: 最近知った英単語 | アルファのブログ https://alpha3166.github.io/blog/20121111.html
プログラミング言語(インタプリタ)
Rustで作るプログラミング言語 ―コンパイラ/インタプリタの基礎からプログラミング言語の新潮流まで:書籍案内|技術評論社 https://gihyo.jp/book/2024/978-4-297-14192-9 GitHub - msakuta/ruscal: Programming language implementation learning project https://github.com/msakuta/ruscal ■文の導入 src/main.rs の内容を以下のようにする
use std::io::Read; use nom::{ branch::alt, bytes::complete::tag, character::complete::{ alpha1, alphanumeric1, char, multispace0, }, combinator::{opt, recognize}, error::ParseError, multi::{fold_many0, many0, separated_list0}, number::complete::recognize_float, sequence::{delimited, pair}, Finish, IResult, Parser, }; fn main() { let mut buf = String::new(); if std::io::stdin().read_to_string(&mut buf).is_ok() { let parsed_statements = match statements(&buf) { Ok(parsed_statements) => parsed_statements, Err(e) => { eprintln!("Parse error: {e:?}"); return; } }; for statement in parsed_statements { println!("eval: {:?}", eval(statement)); } } } #[derive(Debug, PartialEq, Clone)] enum Expression<'src> { Ident(&'src str), NumLiteral(f64), FnInvoke(&'src str, Vec<Expression<'src>>), Add(Box<Expression<'src>>, Box<Expression<'src>>), Sub(Box<Expression<'src>>, Box<Expression<'src>>), Mul(Box<Expression<'src>>, Box<Expression<'src>>), Div(Box<Expression<'src>>, Box<Expression<'src>>), } type Statements<'a> = Vec<Expression<'a>>; fn unary_fn( f: fn(f64) -> f64, ) -> impl Fn(Vec<Expression>) -> f64 { move |args| { f(eval( args.into_iter().next().expect("function missing argument"), )) } } fn binary_fn( f: fn(f64, f64) -> f64, ) -> impl Fn(Vec<Expression>) -> f64 { move |args| { let mut args = args.into_iter(); let lhs = eval( args.next().expect("function missing the first argument"), ); let rhs = eval( args.next().expect("function missing the second argument"), ); f(lhs, rhs) } } fn eval(expr: Expression) -> f64 { use Expression::*; match expr { Ident("pi") => std::f64::consts::PI, Ident(id) => panic!("Unknown name {:?}", id), NumLiteral(n) => n, FnInvoke("sqrt", args) => unary_fn(f64::sqrt)(args), FnInvoke("sin", args) => unary_fn(f64::sin)(args), FnInvoke("cos", args) => unary_fn(f64::cos)(args), FnInvoke("tan", args) => unary_fn(f64::tan)(args), FnInvoke("asin", args) => unary_fn(f64::asin)(args), FnInvoke("acos", args) => unary_fn(f64::acos)(args), FnInvoke("atan", args) => unary_fn(f64::atan)(args), FnInvoke("atan2", args) => binary_fn(f64::atan2)(args), FnInvoke("pow", args) => binary_fn(f64::powf)(args), FnInvoke("exp", args) => unary_fn(f64::exp)(args), FnInvoke("log", args) => binary_fn(f64::log)(args), FnInvoke("log10", args) => unary_fn(f64::log10)(args), FnInvoke(name, _) => { panic!("Unknown function {name:?}") }, Add(lhs, rhs) => eval(*lhs) + eval(*rhs), Sub(lhs, rhs) => eval(*lhs) - eval(*rhs), Mul(lhs, rhs) => eval(*lhs) * eval(*rhs), Div(lhs, rhs) => eval(*lhs) / eval(*rhs), } } fn space_delimited<'src, O, E>( f: impl Parser<&'src str, O, E>, ) -> impl FnMut(&'src str) -> IResult<&'src str, O, E> where E: ParseError<&'src str>, { delimited(multispace0, f, multispace0) } fn factor(i: &str) -> IResult<&str, Expression> { alt((number, func_call, ident, parens))(i) } fn func_call(i: &str) -> IResult<&str, Expression> { let (r, ident) = space_delimited(identifier)(i)?; let (r, args) = space_delimited(delimited( tag("("), many0(delimited( multispace0, expr, space_delimited(opt(tag(","))), )), tag(")"), ))(r)?; Ok((r, Expression::FnInvoke(ident, args))) } fn ident(input: &str) -> IResult<&str, Expression> { let (r, res) = space_delimited(identifier)(input)?; Ok((r, Expression::Ident(res))) } fn identifier(input: &str) -> IResult<&str, &str> { recognize(pair( alt((alpha1, tag("_"))), many0(alt((alphanumeric1, tag("_")))), ))(input) } fn number(input: &str) -> IResult<&str, Expression> { let (r, v) = space_delimited(recognize_float)(input)?; Ok(( r, Expression::NumLiteral(v.parse().map_err( |_| { nom::Err::Error(nom::error::Error { input, code: nom::error::ErrorKind::Digit, }) }, )?), )) } fn parens(i: &str) -> IResult<&str, Expression> { space_delimited( delimited(tag("("), expr, tag(")")) )(i) } fn term(i: &str) -> IResult<&str, Expression> { let (i, init) = factor(i)?; fold_many0( pair(space_delimited(alt((char('*'), char('/')))), factor), move || init.clone(), |acc, (op, val): (char, Expression)| { match op { '*' => Expression::Mul(Box::new(acc), Box::new(val)), '/' => Expression::Div(Box::new(acc), Box::new(val)), _ => panic!( "Multiplicative expression should have '*' or '/' operator" ), } }, )(i) } fn expr(i: &str) -> IResult<&str, Expression> { let (i, init) = term(i)?; fold_many0( pair(space_delimited(alt((char('+'), char('-')))), term), move || init.clone(), |acc, (op, val): (char, Expression)| match op { '+' => Expression::Add(Box::new(acc), Box::new(val)), '-' => Expression::Sub(Box::new(acc), Box::new(val)), _ => panic!( "Additive expression should have '+' or '-' operator" ), }, )(i) } fn statements(i: &str) -> Result<Statements, nom::error::Error<&str>> { let (_, res) = separated_list0(tag(";"), expr)(i).finish()?; Ok(res) }
input.rscl を作成し、内容を以下のようにする
1; 2+3; 4*5; sin(pi/2);
以下のように実行できる >cargo run -- < input.rscl eval: 1.0 eval: 5.0 eval: 20.0 eval: 1.0 プログラム内にあるfinish関数は、パーサーが成功した場合に残りの入力が無いかを確認するもの 未処理の入力が残っていたら、パースが失敗したとみなされる 処理結果として「残りの入力」と「パース下結果」を返しているが、残りの入力は(成功なら空なので)無視している ■変数の宣言 src/main.rs の内容を以下のようにする
use std::{ collections::HashMap, io::Read }; use nom::{ branch::alt, bytes::complete::tag, character::complete::{ alpha1, alphanumeric1, char, multispace0, multispace1, }, combinator::{opt, recognize}, error::ParseError, multi::{fold_many0, many0, separated_list0}, number::complete::recognize_float, sequence::{delimited, pair}, Finish, IResult, Parser, }; fn main() { let mut buf = String::new(); if !std::io::stdin().read_to_string(&mut buf).is_ok() { panic!("Failed to read from stdin"); } let parsed_statements = match statements(&buf) { Ok(parsed_statements) => parsed_statements, Err(e) => { eprintln!("Parse error: {e:?}"); return; } }; let mut variables = HashMap::new(); for statement in parsed_statements { match statement { Statement::Expression(expr) => { println!("eval: {:?}", eval(expr, &variables)) } Statement::VarDef(name, expr) => { let value = eval(expr, &variables); variables.insert(name, value); } } } } #[derive(Debug, PartialEq, Clone)] enum Expression<'src> { Ident(&'src str), NumLiteral(f64), FnInvoke(&'src str, Vec<Expression<'src>>), Add(Box<Expression<'src>>, Box<Expression<'src>>), Sub(Box<Expression<'src>>, Box<Expression<'src>>), Mul(Box<Expression<'src>>, Box<Expression<'src>>), Div(Box<Expression<'src>>, Box<Expression<'src>>), } #[derive(Debug, PartialEq, Clone)] enum Statement<'src> { Expression(Expression<'src>), VarDef(&'src str, Expression<'src>), } type Statements<'a> = Vec<Statement<'a>>; fn unary_fn( f: fn(f64) -> f64, ) -> impl Fn(Vec<Expression>, &HashMap<&str, f64>) -> f64 { move |args, variables| { f(eval( args.into_iter().next().expect("function missing argument"), variables, )) } } fn binary_fn( f: fn(f64, f64) -> f64, ) -> impl Fn(Vec<Expression>, &HashMap<&str, f64>) -> f64 { move |args, variables| { let mut args = args.into_iter(); let lhs = eval( args.next().expect("function missing the first argument"), variables, ); let rhs = eval( args.next().expect("function missing the second argument"), variables, ); f(lhs, rhs) } } fn eval(expr: Expression, vars: &HashMap<&str, f64>) -> f64 { use Expression::*; match expr { Ident("pi") => std::f64::consts::PI, Ident(id) => *vars.get(id).expect("Variable not found"), NumLiteral(n) => n, FnInvoke("sqrt", args) => unary_fn(f64::sqrt)(args, vars), FnInvoke("sin", args) => unary_fn(f64::sin)(args, vars), FnInvoke("cos", args) => unary_fn(f64::cos)(args, vars), FnInvoke("tan", args) => unary_fn(f64::tan)(args, vars), FnInvoke("asin", args) => unary_fn(f64::asin)(args, vars), FnInvoke("acos", args) => unary_fn(f64::acos)(args, vars), FnInvoke("atan", args) => unary_fn(f64::atan)(args, vars), FnInvoke("atan2", args) => binary_fn(f64::atan2)(args, vars), FnInvoke("pow", args) => binary_fn(f64::powf)(args, vars), FnInvoke("exp", args) => unary_fn(f64::exp)(args, vars), FnInvoke("log", args) => binary_fn(f64::log)(args, vars), FnInvoke("log10", args) => unary_fn(f64::log10)(args, vars), FnInvoke(name, _) => { panic!("Unknown function {name:?}") }, Add(lhs, rhs) => eval(*lhs, vars) + eval(*rhs, vars), Sub(lhs, rhs) => eval(*lhs, vars) - eval(*rhs, vars), Mul(lhs, rhs) => eval(*lhs, vars) * eval(*rhs, vars), Div(lhs, rhs) => eval(*lhs, vars) / eval(*rhs, vars), } } fn space_delimited<'src, O, E>( f: impl Parser<&'src str, O, E>, ) -> impl FnMut(&'src str) -> IResult<&'src str, O, E> where E: ParseError<&'src str>, { delimited(multispace0, f, multispace0) } fn factor(i: &str) -> IResult<&str, Expression> { alt((number, func_call, ident, parens))(i) } fn func_call(i: &str) -> IResult<&str, Expression> { let (r, ident) = space_delimited(identifier)(i)?; let (r, args) = space_delimited(delimited( tag("("), many0(delimited( multispace0, expr, space_delimited(opt(tag(","))), )), tag(")"), ))(r)?; Ok((r, Expression::FnInvoke(ident, args))) } fn ident(input: &str) -> IResult<&str, Expression> { let (r, res) = space_delimited(identifier)(input)?; Ok((r, Expression::Ident(res))) } fn identifier(input: &str) -> IResult<&str, &str> { recognize(pair( alt((alpha1, tag("_"))), many0(alt((alphanumeric1, tag("_")))), ))(input) } fn number(input: &str) -> IResult<&str, Expression> { let (r, v) = space_delimited(recognize_float)(input)?; Ok(( r, Expression::NumLiteral(v.parse().map_err( |_| { nom::Err::Error(nom::error::Error { input, code: nom::error::ErrorKind::Digit, }) }, )?), )) } fn parens(i: &str) -> IResult<&str, Expression> { space_delimited( delimited(tag("("), expr, tag(")")) )(i) } fn term(i: &str) -> IResult<&str, Expression> { let (i, init) = factor(i)?; fold_many0( pair(space_delimited(alt((char('*'), char('/')))), factor), move || init.clone(), |acc, (op, val): (char, Expression)| { match op { '*' => Expression::Mul(Box::new(acc), Box::new(val)), '/' => Expression::Div(Box::new(acc), Box::new(val)), _ => panic!( "Multiplicative expression should have '*' or '/' operator" ), } }, )(i) } fn expr(i: &str) -> IResult<&str, Expression> { let (i, init) = term(i)?; fold_many0( pair(space_delimited(alt((char('+'), char('-')))), term), move || init.clone(), |acc, (op, val): (char, Expression)| match op { '+' => Expression::Add(Box::new(acc), Box::new(val)), '-' => Expression::Sub(Box::new(acc), Box::new(val)), _ => panic!( "Additive expression should have '+' or '-' operator" ), }, )(i) } fn var_def(i: &str) -> IResult<&str, Statement> { let (i, _) = delimited(multispace0, tag("var"), multispace1)(i)?; let (i, name) = space_delimited(identifier)(i)?; let (i, _) = space_delimited(char('='))(i)?; let (i, expr) = space_delimited(expr)(i)?; Ok((i, Statement::VarDef(name, expr))) } fn expr_statement(i: &str) -> IResult<&str, Statement> { let (i, res) = expr(i)?; Ok((i, Statement::Expression(res))) } fn statement(i: &str) -> IResult<&str, Statement> { alt((var_def, expr_statement))(i) } fn statements(i: &str) -> Result<Statements, nom::error::Error<&str>> { let (_, res) = separated_list0(tag(";"), statement)(i).finish()?; Ok(res) }
input.rscl を作成し、内容を以下のようにする
var a = 1; a + 2;
以下のように実行できる >cargo run -- < input.rscl eval: 3.0 ■変数への代入 src/main.rs の内容を以下のようにする
use std::{ collections::HashMap, io::Read }; use nom::{ branch::alt, bytes::complete::tag, character::complete::{ alpha1, alphanumeric1, char, multispace0, multispace1, }, combinator::{opt, recognize}, error::ParseError, multi::{fold_many0, many0, separated_list0}, number::complete::recognize_float, sequence::{delimited, pair}, Finish, IResult, Parser, }; fn main() { let mut buf = String::new(); if !std::io::stdin().read_to_string(&mut buf).is_ok() { panic!("Failed to read from stdin"); } let parsed_statements = match statements(&buf) { Ok(parsed_statements) => parsed_statements, Err(e) => { eprintln!("Parse error: {e:?}"); return; } }; let mut variables = HashMap::new(); for statement in parsed_statements { match statement { Statement::Expression(expr) => { println!("eval: {:?}", eval(expr, &variables)) } Statement::VarDef(name, expr) => { let value = eval(expr, &variables); variables.insert(name, value); } Statement::VarAssign(name, expr) => { if !variables.contains_key(name) { panic!("Variable is not defined"); } let value = eval(expr, &variables); variables.insert(name, value); } } } } #[derive(Debug, PartialEq, Clone)] enum Expression<'src> { Ident(&'src str), NumLiteral(f64), FnInvoke(&'src str, Vec<Expression<'src>>), Add(Box<Expression<'src>>, Box<Expression<'src>>), Sub(Box<Expression<'src>>, Box<Expression<'src>>), Mul(Box<Expression<'src>>, Box<Expression<'src>>), Div(Box<Expression<'src>>, Box<Expression<'src>>), } #[derive(Debug, PartialEq, Clone)] enum Statement<'src> { Expression(Expression<'src>), VarDef(&'src str, Expression<'src>), VarAssign(&'src str, Expression<'src>), } type Statements<'a> = Vec<Statement<'a>>; fn unary_fn( f: fn(f64) -> f64, ) -> impl Fn(Vec<Expression>, &HashMap<&str, f64>) -> f64 { move |args, variables| { f(eval( args.into_iter().next().expect("function missing argument"), variables, )) } } fn binary_fn( f: fn(f64, f64) -> f64, ) -> impl Fn(Vec<Expression>, &HashMap<&str, f64>) -> f64 { move |args, variables| { let mut args = args.into_iter(); let lhs = eval( args.next().expect("function missing the first argument"), variables, ); let rhs = eval( args.next().expect("function missing the second argument"), variables, ); f(lhs, rhs) } } fn eval(expr: Expression, vars: &HashMap<&str, f64>) -> f64 { use Expression::*; match expr { Ident("pi") => std::f64::consts::PI, Ident(id) => *vars.get(id).expect("Variable not found"), NumLiteral(n) => n, FnInvoke("sqrt", args) => unary_fn(f64::sqrt)(args, vars), FnInvoke("sin", args) => unary_fn(f64::sin)(args, vars), FnInvoke("cos", args) => unary_fn(f64::cos)(args, vars), FnInvoke("tan", args) => unary_fn(f64::tan)(args, vars), FnInvoke("asin", args) => unary_fn(f64::asin)(args, vars), FnInvoke("acos", args) => unary_fn(f64::acos)(args, vars), FnInvoke("atan", args) => unary_fn(f64::atan)(args, vars), FnInvoke("atan2", args) => binary_fn(f64::atan2)(args, vars), FnInvoke("pow", args) => binary_fn(f64::powf)(args, vars), FnInvoke("exp", args) => unary_fn(f64::exp)(args, vars), FnInvoke("log", args) => binary_fn(f64::log)(args, vars), FnInvoke("log10", args) => unary_fn(f64::log10)(args, vars), FnInvoke(name, _) => { panic!("Unknown function {name:?}") }, Add(lhs, rhs) => eval(*lhs, vars) + eval(*rhs, vars), Sub(lhs, rhs) => eval(*lhs, vars) - eval(*rhs, vars), Mul(lhs, rhs) => eval(*lhs, vars) * eval(*rhs, vars), Div(lhs, rhs) => eval(*lhs, vars) / eval(*rhs, vars), } } fn space_delimited<'src, O, E>( f: impl Parser<&'src str, O, E>, ) -> impl FnMut(&'src str) -> IResult<&'src str, O, E> where E: ParseError<&'src str>, { delimited(multispace0, f, multispace0) } fn factor(i: &str) -> IResult<&str, Expression> { alt((number, func_call, ident, parens))(i) } fn func_call(i: &str) -> IResult<&str, Expression> { let (r, ident) = space_delimited(identifier)(i)?; let (r, args) = space_delimited(delimited( tag("("), many0(delimited( multispace0, expr, space_delimited(opt(tag(","))), )), tag(")"), ))(r)?; Ok((r, Expression::FnInvoke(ident, args))) } fn ident(input: &str) -> IResult<&str, Expression> { let (r, res) = space_delimited(identifier)(input)?; Ok((r, Expression::Ident(res))) } fn identifier(input: &str) -> IResult<&str, &str> { recognize(pair( alt((alpha1, tag("_"))), many0(alt((alphanumeric1, tag("_")))), ))(input) } fn number(input: &str) -> IResult<&str, Expression> { let (r, v) = space_delimited(recognize_float)(input)?; Ok(( r, Expression::NumLiteral(v.parse().map_err( |_| { nom::Err::Error(nom::error::Error { input, code: nom::error::ErrorKind::Digit, }) }, )?), )) } fn parens(i: &str) -> IResult<&str, Expression> { space_delimited( delimited(tag("("), expr, tag(")")) )(i) } fn term(i: &str) -> IResult<&str, Expression> { let (i, init) = factor(i)?; fold_many0( pair(space_delimited(alt((char('*'), char('/')))), factor), move || init.clone(), |acc, (op, val): (char, Expression)| { match op { '*' => Expression::Mul(Box::new(acc), Box::new(val)), '/' => Expression::Div(Box::new(acc), Box::new(val)), _ => panic!( "Multiplicative expression should have '*' or '/' operator" ), } }, )(i) } fn expr(i: &str) -> IResult<&str, Expression> { let (i, init) = term(i)?; fold_many0( pair(space_delimited(alt((char('+'), char('-')))), term), move || init.clone(), |acc, (op, val): (char, Expression)| match op { '+' => Expression::Add(Box::new(acc), Box::new(val)), '-' => Expression::Sub(Box::new(acc), Box::new(val)), _ => panic!( "Additive expression should have '+' or '-' operator" ), }, )(i) } fn var_def(i: &str) -> IResult<&str, Statement> { let (i, _) = delimited(multispace0, tag("var"), multispace1)(i)?; let (i, name) = space_delimited(identifier)(i)?; let (i, _) = space_delimited(char('='))(i)?; let (i, expr) = space_delimited(expr)(i)?; Ok((i, Statement::VarDef(name, expr))) } fn var_assign(i: &str) -> IResult<&str, Statement> { let (i, name) = space_delimited(identifier)(i)?; let (i, _) = space_delimited(char('='))(i)?; let (i, expr) = space_delimited(expr)(i)?; Ok((i, Statement::VarAssign(name, expr))) } fn expr_statement(i: &str) -> IResult<&str, Statement> { let (i, res) = expr(i)?; Ok((i, Statement::Expression(res))) } fn statement(i: &str) -> IResult<&str, Statement> { alt((var_def, var_assign, expr_statement))(i) } fn statements(i: &str) -> Result<Statements, nom::error::Error<&str>> { let (_, res) = separated_list0(tag(";"), statement)(i).finish()?; Ok(res) }
input.rscl を作成し、内容を以下のようにする
var a = 1; a = a + 2; a * 2;
以下のように実行できる >cargo run -- < input.rscl eval: 6.0 ■条件分岐 src/main.rs の内容を以下のようにする
use std::{ collections::HashMap, io::Read }; use nom::{ branch::alt, bytes::complete::tag, character::complete::{ alpha1, alphanumeric1, char, multispace0, multispace1, }, combinator::{opt, recognize}, error::ParseError, multi::{fold_many0, many0, separated_list0}, number::complete::recognize_float, sequence::{delimited, pair, preceded}, Finish, IResult, Parser, }; fn main() { let mut buf = String::new(); if !std::io::stdin().read_to_string(&mut buf).is_ok() { panic!("Failed to read from stdin"); } let parsed_statements = match statements(&buf) { Ok(parsed_statements) => parsed_statements, Err(e) => { eprintln!("Parse error: {e:?}"); return; } }; let mut variables = HashMap::new(); for statement in parsed_statements { match statement { Statement::Expression(expr) => { println!("eval: {:?}", eval(expr, &variables)) } Statement::VarDef(name, expr) => { let value = eval(expr, &variables); variables.insert(name, value); } Statement::VarAssign(name, expr) => { if !variables.contains_key(name) { panic!("Variable is not defined"); } let value = eval(expr, &variables); variables.insert(name, value); } } } } #[derive(Debug, PartialEq, Clone)] enum Expression<'src> { Ident(&'src str), NumLiteral(f64), FnInvoke(&'src str, Vec<Expression<'src>>), Add(Box<Expression<'src>>, Box<Expression<'src>>), Sub(Box<Expression<'src>>, Box<Expression<'src>>), Mul(Box<Expression<'src>>, Box<Expression<'src>>), Div(Box<Expression<'src>>, Box<Expression<'src>>), If( Box<Expression<'src>>, Box<Expression<'src>>, Option<Box<Expression<'src>>>, ), } #[derive(Debug, PartialEq, Clone)] enum Statement<'src> { Expression(Expression<'src>), VarDef(&'src str, Expression<'src>), VarAssign(&'src str, Expression<'src>), } type Statements<'a> = Vec<Statement<'a>>; fn unary_fn( f: fn(f64) -> f64, ) -> impl Fn(Vec<Expression>, &HashMap<&str, f64>) -> f64 { move |args, variables| { f(eval( args.into_iter().next().expect("function missing argument"), variables, )) } } fn binary_fn( f: fn(f64, f64) -> f64, ) -> impl Fn(Vec<Expression>, &HashMap<&str, f64>) -> f64 { move |args, variables| { let mut args = args.into_iter(); let lhs = eval( args.next().expect("function missing the first argument"), variables, ); let rhs = eval( args.next().expect("function missing the second argument"), variables, ); f(lhs, rhs) } } fn eval(expr: Expression, vars: &HashMap<&str, f64>) -> f64 { use Expression::*; match expr { Ident("pi") => std::f64::consts::PI, Ident(id) => *vars.get(id).expect("Variable not found"), NumLiteral(n) => n, FnInvoke("sqrt", args) => unary_fn(f64::sqrt)(args, vars), FnInvoke("sin", args) => unary_fn(f64::sin)(args, vars), FnInvoke("cos", args) => unary_fn(f64::cos)(args, vars), FnInvoke("tan", args) => unary_fn(f64::tan)(args, vars), FnInvoke("asin", args) => unary_fn(f64::asin)(args, vars), FnInvoke("acos", args) => unary_fn(f64::acos)(args, vars), FnInvoke("atan", args) => unary_fn(f64::atan)(args, vars), FnInvoke("atan2", args) => binary_fn(f64::atan2)(args, vars), FnInvoke("pow", args) => binary_fn(f64::powf)(args, vars), FnInvoke("exp", args) => unary_fn(f64::exp)(args, vars), FnInvoke("log", args) => binary_fn(f64::log)(args, vars), FnInvoke("log10", args) => unary_fn(f64::log10)(args, vars), FnInvoke(name, _) => { panic!("Unknown function {name:?}") }, Add(lhs, rhs) => eval(*lhs, vars) + eval(*rhs, vars), Sub(lhs, rhs) => eval(*lhs, vars) - eval(*rhs, vars), Mul(lhs, rhs) => eval(*lhs, vars) * eval(*rhs, vars), Div(lhs, rhs) => eval(*lhs, vars) / eval(*rhs, vars), If(cond, t_case, f_case) => { if eval(*cond, vars) != 0. { eval(*t_case, vars) } else if let Some(f_case) = f_case { eval(*f_case, vars) } else { 0. } } } } fn space_delimited<'src, O, E>( f: impl Parser<&'src str, O, E>, ) -> impl FnMut(&'src str) -> IResult<&'src str, O, E> where E: ParseError<&'src str>, { delimited(multispace0, f, multispace0) } fn factor(i: &str) -> IResult<&str, Expression> { alt((number, func_call, ident, parens))(i) } fn func_call(i: &str) -> IResult<&str, Expression> { let (r, ident) = space_delimited(identifier)(i)?; let (r, args) = space_delimited(delimited( tag("("), many0(delimited( multispace0, expr, space_delimited(opt(tag(","))), )), tag(")"), ))(r)?; Ok((r, Expression::FnInvoke(ident, args))) } fn ident(input: &str) -> IResult<&str, Expression> { let (r, res) = space_delimited(identifier)(input)?; Ok((r, Expression::Ident(res))) } fn identifier(input: &str) -> IResult<&str, &str> { recognize(pair( alt((alpha1, tag("_"))), many0(alt((alphanumeric1, tag("_")))), ))(input) } fn number(input: &str) -> IResult<&str, Expression> { let (r, v) = space_delimited(recognize_float)(input)?; Ok(( r, Expression::NumLiteral(v.parse().map_err( |_| { nom::Err::Error(nom::error::Error { input, code: nom::error::ErrorKind::Digit, }) }, )?), )) } fn parens(i: &str) -> IResult<&str, Expression> { space_delimited( delimited(tag("("), expr, tag(")")) )(i) } fn term(i: &str) -> IResult<&str, Expression> { let (i, init) = factor(i)?; fold_many0( pair(space_delimited(alt((char('*'), char('/')))), factor), move || init.clone(), |acc, (op, val): (char, Expression)| { match op { '*' => Expression::Mul(Box::new(acc), Box::new(val)), '/' => Expression::Div(Box::new(acc), Box::new(val)), _ => panic!( "Multiplicative expression should have '*' or '/' operator" ), } }, )(i) } fn num_expr(i: &str) -> IResult<&str, Expression> { let (i, init) = term(i)?; fold_many0( pair(space_delimited(alt((char('+'), char('-')))), term), move || init.clone(), |acc, (op, val): (char, Expression)| match op { '+' => Expression::Add(Box::new(acc), Box::new(val)), '-' => Expression::Sub(Box::new(acc), Box::new(val)), _ => panic!( "Additive expression should have '+' or '-' operator" ), }, )(i) } fn open_brace(i: &str) -> IResult<&str, ()> { let (i, _) = space_delimited(char('{'))(i)?; Ok((i, ())) } fn close_brace(i: &str) -> IResult<&str, ()> { let (i, _) = space_delimited(char('}'))(i)?; Ok((i, ())) } fn if_expr(i: &str) -> IResult<&str, Expression> { let (i, _) = space_delimited(tag("if"))(i)?; let (i, cond) = expr(i)?; let (i, t_case) = delimited(open_brace, expr, close_brace)(i)?; let (i, f_case) = opt(preceded( space_delimited(tag("else")), delimited(open_brace, expr, close_brace), ))(i)?; Ok(( i, Expression::If( Box::new(cond), Box::new(t_case), f_case.map(Box::new), ), )) } fn expr(i: &str) -> IResult<&str, Expression> { alt((if_expr, num_expr))(i) } fn var_def(i: &str) -> IResult<&str, Statement> { let (i, _) = delimited(multispace0, tag("var"), multispace1)(i)?; let (i, name) = space_delimited(identifier)(i)?; let (i, _) = space_delimited(char('='))(i)?; let (i, expr) = space_delimited(expr)(i)?; Ok((i, Statement::VarDef(name, expr))) } fn var_assign(i: &str) -> IResult<&str, Statement> { let (i, name) = space_delimited(identifier)(i)?; let (i, _) = space_delimited(char('='))(i)?; let (i, expr) = space_delimited(expr)(i)?; Ok((i, Statement::VarAssign(name, expr))) } fn expr_statement(i: &str) -> IResult<&str, Statement> { let (i, res) = expr(i)?; Ok((i, Statement::Expression(res))) } fn statement(i: &str) -> IResult<&str, Statement> { alt((var_def, var_assign, expr_statement))(i) } fn statements(i: &str) -> Result<Statements, nom::error::Error<&str>> { let (_, res) = separated_list0(tag(";"), statement)(i).finish()?; Ok(res) }
input.rscl を作成し、内容を以下のようにする
var one = 1; var a = if one { 10 } else { 20 }; a + 2;
以下のように実行できる >cargo run -- < input.rscl eval: 12.0 ■繰り返し src/main.rs の内容を以下のようにする
use std::{ collections::HashMap, io::Read }; use nom::{ branch::alt, bytes::complete::tag, character::complete::{ alpha1, alphanumeric1, char, multispace0, multispace1, }, combinator::{opt, recognize}, error::ParseError, multi::{fold_many0, many0}, number::complete::recognize_float, sequence::{delimited, pair, preceded, terminated}, Finish, IResult, Parser, }; fn main() { let mut buf = String::new(); if !std::io::stdin().read_to_string(&mut buf).is_ok() { panic!("Failed to read from stdin"); } let parsed_statements = match statements_finish(&buf) { Ok(parsed_statements) => parsed_statements, Err(e) => { eprintln!("Parse error: {e:?}"); return; } }; let mut variables = HashMap::new(); eval_stmts(&parsed_statements, &mut variables); } type Variables = HashMap<String, f64>; fn eval_stmts(stmts: &[Statement], variables: &mut Variables) { for statement in stmts { match statement { Statement::Expression(expr) => { println!("eval: {:?}", eval(expr, variables)) } Statement::VarDef(name, expr) => { let value = eval(expr, variables); variables.insert(name.to_string(), value); } Statement::VarAssign(name, expr) => { if !variables.contains_key(*name) { panic!("Variable is not defined"); } let value = eval(expr, variables); variables.insert(name.to_string(), value); } Statement::For { loop_var, start, end, stmts, } => { let start = eval(start, variables) as isize; let end = eval(end, variables) as isize; for i in start..end { variables.insert(loop_var.to_string(), i as f64); eval_stmts(stmts, variables); } } } } } #[derive(Debug, PartialEq, Clone)] enum Expression<'src> { Ident(&'src str), NumLiteral(f64), FnInvoke(&'src str, Vec<Expression<'src>>), Add(Box<Expression<'src>>, Box<Expression<'src>>), Sub(Box<Expression<'src>>, Box<Expression<'src>>), Mul(Box<Expression<'src>>, Box<Expression<'src>>), Div(Box<Expression<'src>>, Box<Expression<'src>>), If( Box<Expression<'src>>, Box<Expression<'src>>, Option<Box<Expression<'src>>>, ), } #[derive(Debug, PartialEq, Clone)] enum Statement<'src> { Expression(Expression<'src>), VarDef(&'src str, Expression<'src>), VarAssign(&'src str, Expression<'src>), For { loop_var: &'src str, start: Expression<'src>, end: Expression<'src>, stmts: Statements<'src>, }, } type Statements<'a> = Vec<Statement<'a>>; fn unary_fn( f: fn(f64) -> f64, ) -> impl Fn(&[Expression], &Variables) -> f64 { move |args, variables| { f(eval( args.into_iter().next().expect("function missing argument"), variables, )) } } fn binary_fn( f: fn(f64, f64) -> f64, ) -> impl Fn(&[Expression], &Variables) -> f64 { move |args, variables| { let mut args = args.into_iter(); let lhs = eval( args.next().expect("function missing the first argument"), variables, ); let rhs = eval( args.next().expect("function missing the second argument"), variables, ); f(lhs, rhs) } } fn eval(expr: &Expression, vars: &Variables) -> f64 { use Expression::*; match expr { Ident("pi") => std::f64::consts::PI, Ident(id) => *vars.get(*id).expect("Variable not found"), NumLiteral(n) => *n, FnInvoke("sqrt", args) => unary_fn(f64::sqrt)(args, vars), FnInvoke("sin", args) => unary_fn(f64::sin)(args, vars), FnInvoke("cos", args) => unary_fn(f64::cos)(args, vars), FnInvoke("tan", args) => unary_fn(f64::tan)(args, vars), FnInvoke("asin", args) => unary_fn(f64::asin)(args, vars), FnInvoke("acos", args) => unary_fn(f64::acos)(args, vars), FnInvoke("atan", args) => unary_fn(f64::atan)(args, vars), FnInvoke("atan2", args) => binary_fn(f64::atan2)(args, vars), FnInvoke("pow", args) => binary_fn(f64::powf)(args, vars), FnInvoke("exp", args) => unary_fn(f64::exp)(args, vars), FnInvoke("log", args) => binary_fn(f64::log)(args, vars), FnInvoke("log10", args) => unary_fn(f64::log10)(args, vars), FnInvoke(name, _) => { panic!("Unknown function {name:?}") }, Add(lhs, rhs) => eval(lhs, vars) + eval(rhs, vars), Sub(lhs, rhs) => eval(lhs, vars) - eval(rhs, vars), Mul(lhs, rhs) => eval(lhs, vars) * eval(rhs, vars), Div(lhs, rhs) => eval(lhs, vars) / eval(rhs, vars), If(cond, t_case, f_case) => { if eval(cond, vars) != 0. { eval(t_case, vars) } else if let Some(f_case) = f_case { eval(f_case, vars) } else { 0. } } } } fn space_delimited<'src, O, E>( f: impl Parser<&'src str, O, E>, ) -> impl FnMut(&'src str) -> IResult<&'src str, O, E> where E: ParseError<&'src str>, { delimited(multispace0, f, multispace0) } fn factor(i: &str) -> IResult<&str, Expression> { alt((number, func_call, ident, parens))(i) } fn func_call(i: &str) -> IResult<&str, Expression> { let (r, ident) = space_delimited(identifier)(i)?; let (r, args) = space_delimited(delimited( tag("("), many0(delimited( multispace0, expr, space_delimited(opt(tag(","))), )), tag(")"), ))(r)?; Ok((r, Expression::FnInvoke(ident, args))) } fn ident(input: &str) -> IResult<&str, Expression> { let (r, res) = space_delimited(identifier)(input)?; Ok((r, Expression::Ident(res))) } fn identifier(input: &str) -> IResult<&str, &str> { recognize(pair( alt((alpha1, tag("_"))), many0(alt((alphanumeric1, tag("_")))), ))(input) } fn number(input: &str) -> IResult<&str, Expression> { let (r, v) = space_delimited(recognize_float)(input)?; Ok(( r, Expression::NumLiteral(v.parse().map_err( |_| { nom::Err::Error(nom::error::Error { input, code: nom::error::ErrorKind::Digit, }) }, )?), )) } fn parens(i: &str) -> IResult<&str, Expression> { space_delimited( delimited(tag("("), expr, tag(")")) )(i) } fn term(i: &str) -> IResult<&str, Expression> { let (i, init) = factor(i)?; fold_many0( pair(space_delimited(alt((char('*'), char('/')))), factor), move || init.clone(), |acc, (op, val): (char, Expression)| { match op { '*' => Expression::Mul(Box::new(acc), Box::new(val)), '/' => Expression::Div(Box::new(acc), Box::new(val)), _ => panic!( "Multiplicative expression should have '*' or '/' operator" ), } }, )(i) } fn num_expr(i: &str) -> IResult<&str, Expression> { let (i, init) = term(i)?; fold_many0( pair(space_delimited(alt((char('+'), char('-')))), term), move || init.clone(), |acc, (op, val): (char, Expression)| match op { '+' => Expression::Add(Box::new(acc), Box::new(val)), '-' => Expression::Sub(Box::new(acc), Box::new(val)), _ => panic!( "Additive expression should have '+' or '-' operator" ), }, )(i) } fn open_brace(i: &str) -> IResult<&str, ()> { let (i, _) = space_delimited(char('{'))(i)?; Ok((i, ())) } fn close_brace(i: &str) -> IResult<&str, ()> { let (i, _) = space_delimited(char('}'))(i)?; Ok((i, ())) } fn if_expr(i: &str) -> IResult<&str, Expression> { let (i, _) = space_delimited(tag("if"))(i)?; let (i, cond) = expr(i)?; let (i, t_case) = delimited(open_brace, expr, close_brace)(i)?; let (i, f_case) = opt(preceded( space_delimited(tag("else")), delimited(open_brace, expr, close_brace), ))(i)?; Ok(( i, Expression::If( Box::new(cond), Box::new(t_case), f_case.map(Box::new), ), )) } fn expr(i: &str) -> IResult<&str, Expression> { alt((if_expr, num_expr))(i) } fn var_def(i: &str) -> IResult<&str, Statement> { let (i, _) = delimited(multispace0, tag("var"), multispace1)(i)?; let (i, name) = space_delimited(identifier)(i)?; let (i, _) = space_delimited(char('='))(i)?; let (i, expr) = space_delimited(expr)(i)?; Ok((i, Statement::VarDef(name, expr))) } fn var_assign(i: &str) -> IResult<&str, Statement> { let (i, name) = space_delimited(identifier)(i)?; let (i, _) = space_delimited(char('='))(i)?; let (i, expr) = space_delimited(expr)(i)?; Ok((i, Statement::VarAssign(name, expr))) } fn expr_statement(i: &str) -> IResult<&str, Statement> { let (i, res) = expr(i)?; Ok((i, Statement::Expression(res))) } fn for_statement(i: &str) -> IResult<&str, Statement> { let (i, _) = space_delimited(tag("for"))(i)?; let (i, loop_var) = space_delimited(identifier)(i)?; let (i, _) = space_delimited(tag("in"))(i)?; let (i, start) = space_delimited(expr)(i)?; let (i, _) = space_delimited(tag("to"))(i)?; let (i, end) = space_delimited(expr)(i)?; let (i, stmts) = delimited(open_brace, statements, close_brace)(i)?; Ok((i, Statement::For { loop_var, start, end, stmts })) } fn statement(i: &str) -> IResult<&str, Statement> { alt(( for_statement, terminated( alt((var_def, var_assign, expr_statement)), char(';'), ), ))(i) } fn statements(i: &str) -> IResult<&str, Statements> { let (i, stmts) = many0(statement)(i)?; let (i, _) = opt(char(';'))(i)?; Ok((i, stmts)) } fn statements_finish( i: &str, ) -> Result<Statements, nom::error::Error<&str>> { let (_, res) = statements(i).finish()?; Ok(res) }
input.rscl を作成し、内容を以下のようにする
var a = 1; for i in 0 to 3 { a + i; }
以下のように実行できる >cargo run -- < input.rscl eval: 1.0 eval: 2.0 eval: 3.0 プログラム内にあるterminated関数は、パーサーの後に指定した文字が続いているかを判定するもの 今回の場合、変数定義や変数代入や文に続けてセミコロンが書かれているか、を確認している ■関数の定義 src/main.rs の内容を以下のようにする
use std::{ collections::HashMap, io::Read }; use nom::{ branch::alt, bytes::complete::tag, character::complete::{ alpha1, alphanumeric1, char, multispace0, multispace1, }, combinator::{opt, recognize}, error::ParseError, multi::{fold_many0, many0, separated_list0}, number::complete::recognize_float, sequence::{delimited, pair, preceded, terminated}, Finish, IResult, Parser, }; fn main() { let mut buf = String::new(); if !std::io::stdin().read_to_string(&mut buf).is_ok() { panic!("Failed to read from stdin"); } let parsed_statements = match statements_finish(&buf) { Ok(parsed_statements) => parsed_statements, Err(e) => { eprintln!("Parse error: {e:?}"); return; } }; let mut frame = StackFrame::new(); eval_stmts(&parsed_statements, &mut frame); } enum FnDef<'src> { User(UserFn<'src>), Native(NativeFn), } impl<'src> FnDef<'src> { fn call(&self, args: &[f64], frame: &StackFrame) -> f64 { match self { Self::User(code) => { let mut new_frame = StackFrame::push_stack(frame); new_frame.vars = args.iter().zip(code.args.iter()).map(|(arg, name)| (name.to_string(), *arg)).collect(); eval_stmts(&code.stmts, &mut new_frame) } Self::Native(code) => (code.code)(args), } } } struct UserFn<'src> { args: Vec<&'src str>, stmts: Statements<'src>, } struct NativeFn { code: Box<dyn Fn(&[f64]) -> f64>, } type Variables = HashMap<String, f64>; type Functions<'src> = HashMap<String, FnDef<'src>>; struct StackFrame<'src> { vars: Variables, funcs: Functions<'src>, uplevel: Option<&'src StackFrame<'src>>, } impl<'src> StackFrame<'src> { fn new() -> Self { let mut funcs = Functions::new(); funcs.insert("sqrt".to_string(), unary_fn(f64::sqrt)); funcs.insert("sin".to_string(), unary_fn(f64::sin)); funcs.insert("cos".to_string(), unary_fn(f64::cos)); funcs.insert("tan".to_string(), unary_fn(f64::tan)); funcs.insert("asin".to_string(), unary_fn(f64::asin)); funcs.insert("acos".to_string(), unary_fn(f64::acos)); funcs.insert("atan".to_string(), unary_fn(f64::atan)); funcs.insert("atan2".to_string(), binary_fn(f64::atan2)); funcs.insert("pow".to_string(), binary_fn(f64::powf)); funcs.insert("exp".to_string(), unary_fn(f64::exp)); funcs.insert("log".to_string(), binary_fn(f64::log)); funcs.insert("log10".to_string(), unary_fn(f64::log10)); funcs.insert("print".to_string(), unary_fn(print)); Self { vars: Variables::new(), funcs, uplevel: None, } } fn push_stack(uplevel: &'src Self) -> Self { Self { vars: HashMap::new(), funcs: HashMap::new(), uplevel: Some(uplevel), } } fn get_fn(&self, name: &str) -> Option<&FnDef<'src>> { let mut next_frame = Some(self); while let Some(frame) = next_frame { if let Some(func) = frame.funcs.get(name) { return Some(func); } next_frame = frame.uplevel; } None } } fn print(arg: f64) -> f64 { println!("print: {arg}"); 0. } fn eval_stmts<'src>(stmts: &[Statement<'src>], frame: &mut StackFrame<'src>) -> f64 { let mut last_result = 0.; for statement in stmts { match statement { Statement::Expression(expr) => { last_result = eval(expr, frame); } Statement::VarDef(name, expr) => { let value = eval(expr, frame); frame.vars.insert(name.to_string(), value); } Statement::VarAssign(name, expr) => { if !frame.vars.contains_key(*name) { panic!("Variable is not defined"); } let value = eval(expr, frame); frame.vars.insert(name.to_string(), value); } Statement::For { loop_var, start, end, stmts, } => { let start = eval(start, frame) as isize; let end = eval(end, frame) as isize; for i in start..end { frame.vars.insert(loop_var.to_string(), i as f64); eval_stmts(stmts, frame); } } Statement::FnDef { name, args, stmts, } => { frame.funcs.insert( name.to_string(), FnDef::User(UserFn { args: args.clone(), stmts: stmts.clone(), }), ); } } } last_result } #[derive(Debug, PartialEq, Clone)] enum Expression<'src> { Ident(&'src str), NumLiteral(f64), FnInvoke(&'src str, Vec<Expression<'src>>), Add(Box<Expression<'src>>, Box<Expression<'src>>), Sub(Box<Expression<'src>>, Box<Expression<'src>>), Mul(Box<Expression<'src>>, Box<Expression<'src>>), Div(Box<Expression<'src>>, Box<Expression<'src>>), Gt(Box<Expression<'src>>, Box<Expression<'src>>), Lt(Box<Expression<'src>>, Box<Expression<'src>>), If( Box<Expression<'src>>, Box<Expression<'src>>, Option<Box<Expression<'src>>>, ), } #[derive(Debug, PartialEq, Clone)] enum Statement<'src> { Expression(Expression<'src>), VarDef(&'src str, Expression<'src>), VarAssign(&'src str, Expression<'src>), For { loop_var: &'src str, start: Expression<'src>, end: Expression<'src>, stmts: Statements<'src>, }, FnDef { name: &'src str, args: Vec<&'src str>, stmts: Statements<'src>, }, } type Statements<'a> = Vec<Statement<'a>>; fn unary_fn<'a>(f: fn(f64) -> f64) -> FnDef<'a> { FnDef::Native(NativeFn { code: Box::new(move |args| { f(*args.into_iter().next().expect("function missing argument")) }) }) } fn binary_fn<'a>(f: fn(f64, f64) -> f64) -> FnDef<'a> { FnDef::Native(NativeFn { code: Box::new(move |args| { let mut args = args.into_iter(); let lhs = args.next().expect("function missing the first argument"); let rhs = args.next().expect("function missing the second argument"); f(*lhs, *rhs) }) }) } fn eval(expr: &Expression, frame: &StackFrame) -> f64 { use Expression::*; match expr { Ident("pi") => std::f64::consts::PI, Ident(id) => *frame.vars.get(*id).expect("Variable not found"), NumLiteral(n) => *n, FnInvoke(name, args) => { if let Some(func) = frame.get_fn(*name) { let args: Vec<_> = args.iter().map(|arg| eval(arg, frame)).collect(); func.call(&args, frame) } else { panic!("Unknown function {name:?}") } }, Add(lhs, rhs) => eval(lhs, frame) + eval(rhs, frame), Sub(lhs, rhs) => eval(lhs, frame) - eval(rhs, frame), Mul(lhs, rhs) => eval(lhs, frame) * eval(rhs, frame), Div(lhs, rhs) => eval(lhs, frame) / eval(rhs, frame), Gt(lhs, rhs) => { if eval(lhs, frame) > eval(rhs, frame) { 1. } else { 0. } } Lt(lhs, rhs) => { if eval(lhs, frame) < eval(rhs, frame) { 1. } else { 0. } } If(cond, t_case, f_case) => { if eval(cond, frame) != 0. { eval(t_case, frame) } else if let Some(f_case) = f_case { eval(f_case, frame) } else { 0. } } } } fn space_delimited<'src, O, E>( f: impl Parser<&'src str, O, E>, ) -> impl FnMut(&'src str) -> IResult<&'src str, O, E> where E: ParseError<&'src str>, { delimited(multispace0, f, multispace0) } fn factor(i: &str) -> IResult<&str, Expression> { alt((number, func_call, ident, parens))(i) } fn func_call(i: &str) -> IResult<&str, Expression> { let (r, ident) = space_delimited(identifier)(i)?; let (r, args) = space_delimited(delimited( tag("("), many0(delimited( multispace0, expr, space_delimited(opt(tag(","))), )), tag(")"), ))(r)?; Ok((r, Expression::FnInvoke(ident, args))) } fn ident(input: &str) -> IResult<&str, Expression> { let (r, res) = space_delimited(identifier)(input)?; Ok((r, Expression::Ident(res))) } fn identifier(input: &str) -> IResult<&str, &str> { recognize(pair( alt((alpha1, tag("_"))), many0(alt((alphanumeric1, tag("_")))), ))(input) } fn number(input: &str) -> IResult<&str, Expression> { let (r, v) = space_delimited(recognize_float)(input)?; Ok(( r, Expression::NumLiteral(v.parse().map_err( |_| { nom::Err::Error(nom::error::Error { input, code: nom::error::ErrorKind::Digit, }) }, )?), )) } fn parens(i: &str) -> IResult<&str, Expression> { space_delimited( delimited(tag("("), expr, tag(")")) )(i) } fn term(i: &str) -> IResult<&str, Expression> { let (i, init) = factor(i)?; fold_many0( pair(space_delimited(alt((char('*'), char('/')))), factor), move || init.clone(), |acc, (op, val): (char, Expression)| { match op { '*' => Expression::Mul(Box::new(acc), Box::new(val)), '/' => Expression::Div(Box::new(acc), Box::new(val)), _ => panic!( "Multiplicative expression should have '*' or '/' operator" ), } }, )(i) } fn num_expr(i: &str) -> IResult<&str, Expression> { let (i, init) = term(i)?; fold_many0( pair(space_delimited(alt((char('+'), char('-')))), term), move || init.clone(), |acc, (op, val): (char, Expression)| match op { '+' => Expression::Add(Box::new(acc), Box::new(val)), '-' => Expression::Sub(Box::new(acc), Box::new(val)), _ => panic!( "Additive expression should have '+' or '-' operator" ), }, )(i) } fn cond_expr(i: &str) -> IResult<&str, Expression> { let (i, first) = num_expr(i)?; let (i, cond) = space_delimited(alt((char('<'), char('>'))))(i)?; let (i, second) = num_expr(i)?; Ok(( i, match cond { '<' => Expression::Lt(Box::new(first), Box::new(second)), '>' => Expression::Gt(Box::new(first), Box::new(second)), _ => unreachable!(), }, )) } fn open_brace(i: &str) -> IResult<&str, ()> { let (i, _) = space_delimited(char('{'))(i)?; Ok((i, ())) } fn close_brace(i: &str) -> IResult<&str, ()> { let (i, _) = space_delimited(char('}'))(i)?; Ok((i, ())) } fn if_expr(i: &str) -> IResult<&str, Expression> { let (i, _) = space_delimited(tag("if"))(i)?; let (i, cond) = expr(i)?; let (i, t_case) = delimited(open_brace, expr, close_brace)(i)?; let (i, f_case) = opt(preceded( space_delimited(tag("else")), delimited(open_brace, expr, close_brace), ))(i)?; Ok(( i, Expression::If( Box::new(cond), Box::new(t_case), f_case.map(Box::new), ), )) } fn expr(i: &str) -> IResult<&str, Expression> { alt((if_expr, cond_expr, num_expr))(i) } fn var_def(i: &str) -> IResult<&str, Statement> { let (i, _) = delimited(multispace0, tag("var"), multispace1)(i)?; let (i, name) = space_delimited(identifier)(i)?; let (i, _) = space_delimited(char('='))(i)?; let (i, expr) = space_delimited(expr)(i)?; Ok((i, Statement::VarDef(name, expr))) } fn var_assign(i: &str) -> IResult<&str, Statement> { let (i, name) = space_delimited(identifier)(i)?; let (i, _) = space_delimited(char('='))(i)?; let (i, expr) = space_delimited(expr)(i)?; Ok((i, Statement::VarAssign(name, expr))) } fn expr_statement(i: &str) -> IResult<&str, Statement> { let (i, res) = expr(i)?; Ok((i, Statement::Expression(res))) } fn for_statement(i: &str) -> IResult<&str, Statement> { let (i, _) = space_delimited(tag("for"))(i)?; let (i, loop_var) = space_delimited(identifier)(i)?; let (i, _) = space_delimited(tag("in"))(i)?; let (i, start) = space_delimited(expr)(i)?; let (i, _) = space_delimited(tag("to"))(i)?; let (i, end) = space_delimited(expr)(i)?; let (i, stmts) = delimited(open_brace, statements, close_brace)(i)?; Ok((i, Statement::For { loop_var, start, end, stmts })) } fn fn_def_statement(i: &str) -> IResult<&str, Statement> { let (i, _) = space_delimited(tag("fn"))(i)?; let (i, name) = space_delimited(identifier)(i)?; let (i, _) = space_delimited(tag("("))(i)?; let (i, args) = separated_list0(char(','), space_delimited(identifier))(i)?; let (i, _) = space_delimited(tag(")"))(i)?; let (i, stmts) = delimited(open_brace, statements, close_brace)(i)?; Ok((i, Statement::FnDef { name, args, stmts })) } fn statement(i: &str) -> IResult<&str, Statement> { alt(( for_statement, fn_def_statement, terminated( alt((var_def, var_assign, expr_statement)), char(';'), ), ))(i) } fn statements(i: &str) -> IResult<&str, Statements> { let (i, stmts) = many0(statement)(i)?; let (i, _) = opt(char(';'))(i)?; Ok((i, stmts)) } fn statements_finish( i: &str, ) -> Result<Statements, nom::error::Error<&str>> { let (_, res) = statements(i).finish()?; Ok(res) }
input.rscl を作成し、内容を以下のようにする
fn add(a, b) { a + b; } print(add(1, 2));
以下のように実行できる >cargo run -- < input.rscl print: 3 なおmain関数内にある
let mut frame = StackFrame::new();
の直前に以下を追加すると、
println!("parsed_statements: {:?}", parsed_statements);
以下のように構文解析の結果が出力される(実際は改行なしで出力される)
parsed_statements: [ FnDef { name: "add", args: ["a", "b"], stmts: [Expression(Add(Ident("a"), Ident("b")))] }, Expression ( FnInvoke("print", [FnInvoke("add", [NumLiteral(1.0), NumLiteral(2.0)])]) ) ]
プログラミング言語(メモ)
Rustで作る自作言語(10) 代数的データ型 https://zenn.dev/taka2/articles/7205a0fd9f71e3

Advertisement