🐥

Rustで作る自作言語(6)

2023/04/08に公開

前回: https://zenn.dev/taka2/articles/97df820e0c38be

今回の目標

ファイルから実行出来るようにする
ブロック式が使えるようにする
トップレベルlet多相型推論を実装する

コマンドライン

引数なしの時はreplが、引数が一つあるときはその引数のファイルを実行するようにしたい。
argopt(作者の解説)を使った。

src/main.rs
#[argopt::cmd]
fn main(file_name: Option<String>) {
    match file_name {
        Some(file_name) => exec_file(file_name),
        None => repl(),
    }
}

exec_fileの中身はこんな感じ

fn exec_file(file_name: String) {
    let path = Path::new(&file_name);
    let display = path.display();

    let mut file = match File::open(&path) {
        Err(why) => panic!("couldn't open {}: {}", display, why),
        Ok(file) => file,
    };

    let mut s = String::new();
    match file.read_to_string(&mut s) {
        Err(why) => panic!("couldn't read {}: {}", display, why),
        Ok(_) => match parser_statements(&s) {
            Err(err) => println!("parse error: {}", err),
            Ok(ast) => {
                let mut typeinfer = TypeInfer::new();
                match typeinfer.typeinfer_statements(&ast.1) {
                    Err(err) => println!("type error: {}", err),
                    Ok(_) => {
                        let eval = Eval::new();
                        match eval.eval_statements(ast.1) {
                            Err(err) => println!("eval error: {}", err),
                            Ok(_) => {
                                eval.eval_main();
                            }
                        }
                    }
                }
            }
        },
    }
}

...嫌すぎる、これを書き直そう

まずErrorトレイトを定義する

src/error.rs
pub trait Error {
    fn print_error(&self);
}

それぞれのエラー型について実装

src/error.rs
impl Error for TypeInferError {
    fn print_error(&self) {
        println!("type error: {}", self)
    }
}
impl Error for EvalError {
    fn print_error(&self) {
        println!("eval error: {}", self)
    }
}

ここでstd::error::Errorの存在に気付く
Rust By Exampleにもあった(https://doc.rust-jp.rs/rust-by-example-ja/error/multiple_error_types/define_error_type.html)
書き直し

src/error.rs
use std::error::Error;
impl Error for TypeInferError {}
impl Error for EvalError {}

これでexec_fileも書き直した

src/main.rs
fn exec_file(file_name: String) -> Result<(), Box<dyn Error>> {
    let path = Path::new(&file_name);
    let display = path.display();

    let mut file = match File::open(&path) {
        Err(why) => panic!("couldn't open {}: {}", display, why),
        Ok(file) => file,
    };

    let mut s = String::new();
    match file.read_to_string(&mut s) {
        Err(why) => panic!("couldn't read {}: {}", display, why),
        Ok(_) => match parser_statements(&s) {
            Err(err) => panic!("{}", err),
            Ok((_, ast)) => {
                let mut typeinfer = TypeInfer::new();
                typeinfer.typeinfer_statements(&ast)?;
                let eval = Eval::new();
                eval.eval_statements(ast)?;
                Ok(())
            }
        },
    }
}

ブロック式

ブロック式は次のような文法をしている。

{
    let a = 1;
    a + 1
}

ブロック式のAST

@@ -8,6 +8,7 @@ pub enum Expr {
     EFunApp(Box<Expr>, Box<Expr>),
     EString(String),
     EUnit,
+    EBlockExpr(Vec<StatementOrExpr>),
 }

ブロック式のパーサー

src/parser.rs
fn parse_block_expr(input: &str) -> IResult<&str, Expr> {
    let (input, _) = symbol("{")(input)?;
    let (input, statement_or_expr_vec) = many1(parser_statement_or_expr)(input)?;
    let (input, _) = symbol("}")(input)?;
    Ok((input, Expr::EBlockExpr(statement_or_expr_vec)))
}

ブロック式の型推論

src/typeinfer.rs
            Expr::EBlockExpr(asts) => {
                let mut typeinfer = TypeInfer::from(
                    TypeEnv::new_enclosed_env(Rc::clone(&self.env)),
                    self.unassigned_num,
                );
                if asts.len() > 1 {
                    for i in 0..(asts.len() - 1) {
                        match &asts[i] {
                            StatementOrExpr::Expr(e) => {
                                typeinfer.typeinfer_expr(&e)?;
                            }
                            StatementOrExpr::Statement(stmt) => {
                                typeinfer.typeinfer_statement(&stmt)?
                            }
                        }
                    }
                }
                let ty = match &asts[asts.len() - 1] {
                    StatementOrExpr::Expr(e) => typeinfer.typeinfer_expr(&e)?,
                    StatementOrExpr::Statement(stmt) => {
                        typeinfer.typeinfer_statement(&stmt)?;
                        Type::TUnit
                    }
                };
                self.unassigned_num = typeinfer.unassigned_num;
                Ok(ty)
            }

ブロック式の評価

src/eval.rs
            Expr::EBlockExpr(asts) => {
                let eval = Eval::from(Environment::new_enclosed_env(Rc::clone(&self.env)));
                if asts.len() > 1 {
                    for i in 0..(asts.len() - 1) {
                        match &asts[i] {
                            StatementOrExpr::Statement(s) => eval.eval_statement(s.clone())?,
                            StatementOrExpr::Expr(e) => {
                                eval.eval_expr(e.clone())?;
                            }
                        }
                    }
                }
                match &asts[asts.len() - 1] {
                    StatementOrExpr::Statement(stmt) => {
                        self.eval_statement(stmt.clone())?;
                        Ok(Value::VUnit)
                    }
                    StatementOrExpr::Expr(e) => eval.eval_expr(e.clone()),
                }
            }

多相型推論

let多相

let多相とは、let式の右辺にのみ多相な型が現れることを許す多相です。

let id(x) = x;

例えばこのようなidの型は∀a. a->aと表される。

今まではidの型は使用時に具体化されていた。

Welcome to lunalang repl!
>>let id(n) = n;
>>id(3==3)
Ok(VBool(true))
>>id(3)
type error: Cannot unify Bool to Int

let多相下では、idを使う時は、generalizeされた型変数は具体化される。
具体化のためのメソッドinstantiateを定義する。

    fn instantiate(&mut self, ty: Type) -> Type {
        let ty = ty.simplify();
        fn go(ty: Type, map: &mut HashMap<u64, Type>, self_: &mut TypeInfer) -> Type {
            match ty {
                Type::TQVar(n) => match map.get(&n) {
                    Some(t) => t.clone(),
                    None => {
                        let ty = self_.newTVar();
                        map.insert(n, ty.clone());
                        ty
                    }
                },
                Type::TInt => Type::TInt,
                Type::TBool => Type::TBool,
                Type::TString => Type::TString,
                Type::TUnit => Type::TUnit,
                Type::TFun(t1, t2) => t_fun(go(*t1, map, self_), go(*t2, map, self_)),
                t @ Type::TVar(_, _) => t,
            }
        }
        go(ty, &mut HashMap::new(), self)
    }

次に、多相化する関数generalizeを次のように定義する。

fn generalize(ty: &Type) {
    match ty.simplify() {
        Type::TVar(n, r) => *(*r).borrow_mut() = Some(Type::TQVar(n)),
        Type::TFun(t1, t2) => {
            generalize(&*t1);
            generalize(&*t2);
        }
        _ => (),
    }
}

最後に、トップレベル変数定義の時に型を一般化すれば良い

@@ -195,6 +201,7 @@ impl TypeInfer {
                 self.env.borrow_mut().insert(name.to_string(), ty.clone());
                 let inferred_ty = self.typeinfer_expr(e)?;
                 unify(&ty, &inferred_ty)?;
+                generalize(&ty);
             }
         }
         Ok(())

(これは実装をサボっていて、本来のlet多相はネストしたlet束縛でも多相になるのだが、今回はトップレベルのlet束縛でしか多相にならない。)

これで多相関数が定義出来るようになった!

Welcome to lunalang repl!
>>let id(n) = n;
>>id(3)
Ok(VInt(3))
>>id(3==3)
Ok(VBool(true))

次回の目標

次回はvector型及びvector型を扱う組み込み関数を定義する。
次のようなfizzbuzzプログラムが動くようになるのが目標

let main = {
    for i in [1..=100] {
        puts(
	    if (i % 15 == 0) "fizzbuzz"
	    else if (i % 3 == 0) "fizz"
	    else if (i % 5 == 0) "buzz"
	    else int_to_string(n)
	);
    };
};

Discussion