Open11
[練習]低レイヤを知りたい人のためのCコンパイラ作成入門をRustで書いてみる
Rustの練習になればととりあえず初めてみる
最初のコード
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
panic!("引数の個数が正しくありません")
}
println!(".intel_syntax noprefix");
println!(".globl main");
println!("main:");
println!(" mov rax, {}", args[1]);
println!(" ret");
}
まずは、こんなかんじ?
$ cargo run 123
Compiling cc9 v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 2.65s
Running `target/debug/cc9 123`
.intel_syntax noprefix
.globl main
main:
mov rax, 123
ret
$ cargo run 123 > tmp.s
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/cc9 123`
$ cat tmp.s
.intel_syntax noprefix
.globl main
main:
mov rax, 123
ret
$ cc -o tmp tmp.s
$ ./tmp
$ echo $?
123
ひとまず同様の出力が得られた
CのstrtolをRustでどう表現するかが難しそう・・・
ステップ2:加減算のできるコンパイラの作成
まずは普通にループさせてみた
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
panic!("引数の個数が正しくありません")
}
let mut p = args[1].as_str().chars();
println!(".intel_syntax noprefix");
println!(".globl main");
println!("main:");
println!(" mov rax, {}", p.next().unwrap() as i32 - 48);
loop {
let c = p.next();
match c {
Some(a) => {
match a {
'+' => println!(" add rax, {}", p.next().unwrap() as i32 - 48),
'-' => println!(" sub rax, {}", p.next().unwrap() as i32 - 48),
_ => println!("予期しない文字です: {}", a)
}
},
None => {break;}
}
}
println!(" ret");
}
$ cargo run "5+20-4"
Compiling cc9 v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 1.57s
Running `target/debug/cc9 5+20-4`
.intel_syntax noprefix
.globl main
main:
mov rax, 5
add rax, 2
予期しない文字です: 0
sub rax, 4
ret
単純にnextを呼び出すだけだと2桁の整数がうまくパースできない。
もう少し工夫が必要
peekableを使えば良さそう
いきなり実装するのは無理そうだったのでテストから書く
pub fn str_to_u<T: Iterator<Item=char>>(iterator: &mut Peekable<T>) -> u32 {
1
}
#[cfg(test)]
extern crate speculate;
extern crate rstest;
use speculate::speculate;
use rstest::*;
speculate! {
describe "str_to_uは文字列を受け取って数値に変換できる場合はu32型に変換する関数" {
#[rstest]
fn _1_を受け取った場合u32型にして返す() {
let mut c = "1".chars().peekable();
assert_eq!(1, str_to_u(&mut c));
}
}
}
きれいに書けた!(テストはあとでちゃんと整理)
pub fn str_to_u<T: Iterator<Item=char>>(iter: &mut Peekable<T>) -> u32 {
let mut result: u32 = 0;
while let Some(i) = iter.peek() {
match i.to_digit(10) {
Some(num) => result = result*10 + num,
None => break,
}
iter.next();
}
result
}
#[cfg(test)]
extern crate speculate;
extern crate rstest;
use speculate::speculate;
use rstest::*;
speculate! {
describe "str_to_uは文字列を受け取って数値に変換できる場合はu32型に変換する関数" {
#[rstest]
fn _1_を受け取った場合u32型にして返す() {
let mut c = "1".chars().peekable();
assert_eq!(1, str_to_u(&mut c));
}
#[rstest]
fn _2_を受け取った場合u32にして返す() {
let mut c = "2".chars().peekable();
assert_eq!(2, str_to_u(&mut c));
}
#[rstest]
fn _10_を受け取った場合u32にして返す() {
let mut c = "10".chars().peekable();
assert_eq!(10, str_to_u(&mut c));
}
}
}
main関数を修正
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
panic!("引数の個数が正しくありません")
}
let mut p = args[1].chars().peekable();
println!(".intel_syntax noprefix");
println!(".globl main");
println!("main:");
println!(" mov rax, {}", str_to_u(&mut p));
while let Some(c) = p.next() {
match c {
'+' => println!(" add rax, {}", str_to_u(&mut p)),
'-' => println!(" sub rax, {}", str_to_u(&mut p)),
_ => println!("予期しない文字です: {}", c),
}
}
println!(" ret");
}
$ cargo run -- "5+20-4"
Compiling cc9 v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 1.24s
Running `target/debug/cc9 5+20-4`
.intel_syntax noprefix
.globl main
main:
mov rax, 5
add rax, 20
sub rax, 4
ret
テストも整理
speculate! {
describe "str_to_uは文字列を受け取って数値に変換できる場合はu32型に変換する関数" {
#[rstest(input, expected,
case::文字列1を受け取った場合u32型の1を返す("1", 1),
case::文字列10を受け取った場合u32型の10を返す("10", 10),
case::文字列100を受け取った場合u32型の100を返す("100", 100)
)]
fn 数値を文字列で受け取ってu32に変換するテスト(input: &str, expected: u32) {
let mut c = input.chars().peekable();
assert_eq!(expected, str_to_u(&mut c));
}
}
}
$ cargo test
Compiling cc9 v0.1.0
Finished test [unoptimized + debuginfo] target(s) in 2.20s
Running unittests (target/debug/deps/cc9-b1f3302b0d777d91)
running 3 tests
test speculate_0::str_to_uは文字列を受け取って数値に変換できる場合はu32型に変換する関数::数値を文字列で受け取ってu32に変換するテスト::case_2_文字列10を受け取った場合u32型の10を返す ... ok
test speculate_0::str_to_uは文字列を受け取って数値に変換できる場合はu32型に変換する関数::数値を文字列で受け取ってu32に変換するテスト::case_1_文字列1を受け取った場合u32型の1を返す ... ok
test speculate_0::str_to_uは文字列を受け取って数値に変換できる場合はu32型に変換する関数::数値を文字列で受け取ってu32に変換するテスト::case_3_文字列100を受け取った場合u32型の100を返す ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
不正な文字を受け取った場合にエラーになるようにResult型を返したいね
ステップ3むずかしい・・・