🌟

Rustで作る自作言語(5)

2023/03/26に公開

前回: https://zenn.dev/taka2/articles/0e433fa0283d2e
次回: https://zenn.dev/taka2/articles/5e4e936e056d8a

今回の目標

リファクタリングする
文字列の追加

cloneを減らしたい

そのためになるべく参照渡しを使うようにする

         }
     }
-    pub fn get(&self, name: String) -> Result<Type, TypeInferError> {
-        match self.env.get(&name) {
+    pub fn get(&self, name: &str) -> Result<Type, TypeInferError> {
+        match self.env.get(name) {
             Some(ty) => Ok(ty.clone()),
             None => match &self.outer {
-                None => Err(TypeInferError::UndefinedVariable(name)),
+                None => Err(TypeInferError::UndefinedVariable(name.to_owned())),
                 Some(env) => env.borrow().get(name),
             },
         }

unifyやoccurも同様にする

-fn unify(t1: Type, t2: Type) -> Result<(), TypeInferError> {
+fn unify(t1: &Type, t2: &Type) -> Result<(), TypeInferError> {
-fn occur(n: u64, t: Type) -> bool {
+fn occur(n: u64, t: &Type) -> bool {

evalの方でもEnvironment::getの引数を参照渡しにした。

文字列の追加

今回は文字列を追加したい。

"Hello, world!"

そのために、まずASTを追加する

EString(String)

パーサーの定義

pub fn expr_string(input: &str) -> IResult<&str, Expr> {
    let (input, _) = space0(input)?;
    let (input, _) = tag("\"")(input)?;
    let (input, str) = many0(none_of("\""))(input)?;
    let (input, _) = symbol("\"")(input)?;
    Ok((input, Expr::EString(str.iter().collect())))
}

空白が消えてしまわないように最初の"はsymbolを用いていない。

型推論の実装

まず文字列型を追加し、

pub enum Type {
    TInt,
    TBool,
+    TString,
    TFun(Box<Type>, Box<Type>),
    TVar(u64, Rc<RefCell<Option<Type>>>),
}

パターンマッチのマッチアームに文字列の場合を追加した

            Expr::EString(_) => Ok(Type::TString),

評価の実装

文字列のオブジェクトを定義

    VString(String),

文字列の場合のマッチアームを追加

            Expr::EString(str) => Ok(Value::VString(str)),

組み込み関数を追加

ただ文字列があるだけではつまらないので、文字列を表示するputs関数を用意する。

puts: String -> ()

そのために、まずUnit型を用意する。

Unitの追加

まずASTを定義し、

    EUnit,

パーサーを書く

 pub fn term(input: &str) -> IResult<&str, Expr> {
     alt((
         expr_int,
+        |input| {
+            let (input, _) = symbol("(")(input)?;
+            let (input, _) = symbol(")")(input)?;
+            Ok((input, Expr::EUnit))
+        },
         delimited(symbol("("), parser_expr, symbol(")")),
         expr_if,
         fun_app,

Unit型を追加

pub enum Type {
     TInt,
     TBool,
     TString,
+    TUnit,
     TFun(Box<Type>, Box<Type>),
     TVar(u64, Rc<RefCell<Option<Type>>>),
 }

Unitの評価を実装した

@@ -10,6 +10,7 @@ pub enum Value {
     VBool(bool),
     VFun(String, Expr, Environment),
     VString(String),
+    VUnit,
 }
@@ -119,7 +120,7 @@ impl Eval {
                 }
             }
             Expr::EString(str) => Ok(Value::VString(str)),
-            Expr::EUnit => todo!(),
+            Expr::EUnit => Ok(Value::VUnit),
         }

Unitを実装したので、満を持してputs関数を実装する。

組み込み関数の実装

組み込み関数の型推論

まず、型環境に組み込み関数用の環境を用意する

 struct TypeEnv {
     env: HashMap<String, Type>,
     outer: Option<Rc<RefCell<TypeEnv>>>,
+    builtin: HashMap<String, Type>,
 }

変数を検索するときに、このbuiltinも検索するようにする。

     pub fn get(&self, name: &str) -> Result<Type, TypeInferError> {
         match self.env.get(name) {
             Some(ty) => Ok(ty.clone()),
             None => match &self.outer {
-                None => Err(TypeInferError::UndefinedVariable(name.to_owned())),
+                None => match self.builtin.get(name) {
+                    Some(ty) => Ok(ty.clone()),
+                    None => Err(TypeInferError::UndefinedVariable(name.to_owned())),
+                },
                 Some(env) => env.borrow().get(name),

builtinは関連関数で作られ、コンストラクタで用いられる。

+    fn builtin() -> HashMap<String, Type> {
+        let mut builtin = HashMap::new();
+        builtin.insert("puts".to_owned(), t_fun(Type::TString, Type::TUnit));
+        builtin
+    }

組み込み関数の評価

Valueにbuiltin関数を表す値を用意する

@@ -11,6 +11,7 @@ pub enum Value {
     VFun(String, Expr, Environment),
     VString(String),
     VUnit,
+    VBuiltin(fn(Value) -> Result<Value, EvalError>),
 }

あとは、TypeEnvと同様に環境にbuiltinを追加する。
その後、FunAppのマッチアームにVBuiltinの場合を追加する。

@@ -116,6 +137,7 @@ impl Eval {
                         eval.env.borrow_mut().insert(arg, v2);
                         eval.eval_expr(expr)
                     }
+                    Value::VBuiltin(fun) => fun(v2),
                     _ => Err(EvalError::InternalTypeError),
                 }
             }

これで組み込み関数putsが使えるようになった。

Hello, world!
Ok(VUnit)

puts自体の実装は以下の通り

|value| match value {
                Value::VString(str) => {
                    println!("{}", str);
                    Ok(Value::VUnit)
                }
                _ => Err(EvalError::InternalTypeError),
            }

次回の目標

オプション周りの整備
Vectorの追加

Discussion