🌟
Rustで作る自作言語(5)
前回: 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