🦔

Rustで作る自作言語(3)

2023/03/13に公開

前回: https://zenn.dev/taka2/articles/22d139f741d207
次回: https://zenn.dev/taka2/articles/0e433fa0283d2e

今回の目標

独自のError型を定義する
変数の定義、使用が出来るようにする

Error型

現状では、エラーはString型で表しているが、これだと少々不便
そこで、Error型を定義

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TypeInferError {
    UnifyError(Type, Type),
    OccurError(u64, Type),
    UnimplementedOperatorError(String),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EvalError {
    InternalTypeError,
    UnimplementedOperatorError(String),
}

続いて、Displayトレイトを実装するが、その前にType型のDisplayトレイトを実装する。

impl Display for Type {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Type::TInt => write!(f, "{}", "Int"),
            Type::TBool => write!(f, "{}", "Bool"),
            Type::TVar(n, t) => match *(**t).borrow() {
                Some(ref t) => t.fmt(f),
                None => write!(f, "{}", n),
            },
        }
    }
}

そして、それぞれのエラー型でDisplayトレイトを実装

impl Display for TypeInferError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::UnifyError(t1, t2) => write!(f, "Cannot unify {} to {}", t1, t2),
            Self::OccurError(n, t) => write!(f, "{} is occur in type {}", n, t),
	    Self::UnimplementedOperatorError(op) => write!(f, "{} is unimplemented", op),
        }
    }
}

impl Display for EvalError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InternalTypeError => write!(f, "InternalTypeError"),
            Self::UnimplementedOperatorError(op) => write!(f, "{} is unimplemented", op),
        }
    }
}

あとは、typeinferモジュール内とevalモジュール内のStringでのエラーをそれぞれ対応するエラー型に置き換えた。

変数の定義

次のようなプログラムが実行出来るようにしたい

let a = 1;
let b = 2;
let main = a + b;

(mainはエントリーポイントになる)
そのために、まず文のASTを定義する。

#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Statement {
    Assign(String, Expr),
}

そして、変数のパーサーを定義する。変数は、a-zから始まり、a-z,A-Z,0-9が0個以上続くものとして定義した。

fn identifier(input: &str) -> IResult<&str, String> {
    let (input, _) = multispace0(input)?;
    let (input, firstnew_char) = one_of("abcdefghijklmnopqrstuvwxyz")(input)?;
    let (input, chars) = alphanumeric0(input)?;
    let (input, _) = multispace0(input)?;
    Ok((input, (first_char.to_string() + chars)))
}

変数束縛の文のパーサーを定義した。

pub fn statement_assign(input: &str) -> IResult<&str, Statement> {
    let (input, _) = keyword("let")(input)?;
    let (input, id) = identifier(input)?;
    let (input, _) = symbol("=")(input)?;
    let (input, e) = parser_expr(input)?;
    let (input, _) = symbol(";")(input)?;
    Ok((input, Statement::Assign(id, e)))
}

type Statements = Vec<Statement>と定義していた。Statementsを返すパーサーを定義した。

pub fn parser_statements(input: &str) -> IResult<&str, Statements> {
    let (input, statements) = many1(statement_assign)(input)?;
    Ok((input, statements))
}

Evalの実装

まず環境が必要なので、環境を表す型を定義する。

pub struct Environment {
    env: HashMap<String, Value>,
}

impl Environment {
    pub fn new() -> Self {
        Environment {
            env: HashMap::new(),
        }
    }
    pub fn get(&self, name: String) -> Result<Value, EvalError> {
        match self.env.get(&name) {
            Some(value) => Ok(value.clone()),
            None => Err(EvalError::UndefinedVariable(name)),
        }
    }
    pub fn insert(&mut self, name: String, val: Value) {
        self.env.insert(name, val);
    }
}

そして文を評価するメソッドを追加した。

impl Eval {
    pub fn eval_statement(&mut self, ast: Statement) -> Result<(), EvalError> {
        match ast {
            Statement::Assign(name, e) => {
                let val = self.eval_expr(e)?;
                Ok(self.env.insert(name, val))
            }
        }
    }
    pub fn eval_statements(&mut self, asts: Statements) -> Result<(), EvalError> {
        for ast in asts {
            self.eval_statement(ast)?;
        }
        Ok(())
    }
    pub fn eval_main(&self) -> Result<Value, EvalError> {
        self.env.get("main".to_string())
    }
}

変数の使用

次に、定義した変数を使えるようにする。ASTを次のように定義する。

pub enum Expr {
    ...
    EVar(String),
}

次にtermを変更する

pub fn term(input: &str) -> IResult<&str, Expr> {
    alt((
        expr_int,
        delimited(symbol("("), expr_op_4n, symbol(")")),
        expr_if,
        |input| {
            let (input, ident) = identifier(input)?;
            Ok((input, Expr::EVar(ident)))
        },
    ))(input)
}

次に、eval_exprのastのパターンマッチに変数の場合を追加すれば良い。

Expr::EVar(ident) => self.env.get(ident),

型推論の実装

まず、evalで定義したような、環境を表す型を定義した。

struct TypeEnv {
    env: HashMap<String, Type>,
}

impl TypeEnv {
    pub fn new() -> Self {
        TypeEnv {
            env: HashMap::new(),
        }
    }
    pub fn get(&self, name: String) -> Result<Type, TypeInferError> {
        match self.env.get(&name) {
            Some(ty) => Ok(ty.clone()),
            None => Err(TypeInferError::UndefinedVariable(name)),
        }
    }
    pub fn insert(&mut self, name: String, val: Type) {
        self.env.insert(name, val);
    }
}

次に、TypeInfer型を定義した

pub struct TypeInfer {
    env: TypeEnv,
    unassigned_num: u64,
}

代入文の型推論の主な処理はtypeinfer_statementメソッドでやっている

    pub fn typeinfer_statement(&mut self, ast: &Statement) -> Result<(), TypeInferError> {
        match ast {
            Statement::Assign(name, e) => {
                let ty = self.newTVar();
                self.env.insert(name.to_string(), ty.clone());
                let inferred_ty = self.typeinfer_expr(e)?;
                unify(ty, inferred_ty)?;
            }
        }
        Ok(())
    }

newTVarメソッドは新しい型変数を用意するメソッドである。

replで変数定義出来るようにする

まず、式と文のどちらも許容できる型を作る

#[derive(PartialEq, Eq, Debug, Clone)]
pub enum StatementOrExpr {
    Statement(Statement),
    Expr(Expr),
}

そして、StatementOrExpr用のパーサーを用意した。

pub fn parser_statement_or_expr(input: &str) -> IResult<&str, StatementOrExpr> {
    match statement_assign(input) {
        Ok((input, stmt)) => Ok((input, StatementOrExpr::Statement(stmt))),
        Err(_) => parser_expr(input).map(|(input, e)| (input, StatementOrExpr::Expr(e))),
    }
}

repl.rsを書き換えた(変更点が多いので全体を載せる)

pub struct REPL {
    eval: Eval,
    typeinfer: TypeInfer,
    is_typecheck: bool,
    program: String,
}

impl REPL {
    fn new() -> Self {
        REPL {
            eval: Eval::new(),
            typeinfer: TypeInfer::new(),
            is_typecheck: false,
            program: "".to_string(),
        }
    }
    fn welcome(&self) {
        println!("Welcome to lunalang repl!");
    }
    fn prompt(&self) {
        print!(">>");
        io::stdout().flush().unwrap();
    }
    fn input(&mut self) {
        let mut program = String::new();
        io::stdin()
            .read_line(&mut program)
            .expect("Failed to read line.");
        self.program = program;
        self.is_typecheck = false;
    }
    fn is_quit(&self) -> bool {
        symbol(":q")(&self.program).is_ok()
    }
    fn parse_typecheck(&mut self) {
        let result = symbol(":t")(&self.program);
        match result {
            Ok((input, _)) => {
                self.program = input.to_string();
                self.is_typecheck = true
            }
            Err(_) => self.is_typecheck = false,
        }
    }
}

pub fn repl() {
    let mut repl = REPL::new();
    repl.welcome();
    loop {
        repl.prompt();
        repl.input();
        if repl.is_quit() {
            break;
        }
        repl.parse_typecheck();
        let ast = parser_statement_or_expr(&repl.program);
        match ast {
            Ok((_, StatementOrExpr::Expr(ast))) => {
                let ty = repl.typeinfer.typeinfer_expr(&ast);
                if let Err(err) = ty {
                    println!("type error: {}", err);
                    continue;
                }
                if repl.is_typecheck {
                    println!("{}", ty.unwrap());
                    continue;
                }
                let result = repl.eval.eval_expr(ast);
                println!("{:?}", result);
            }
            Ok((_, StatementOrExpr::Statement(stmt))) => {
                repl.typeinfer.typeinfer_statement(&stmt);
                repl.eval.eval_statement(stmt);
            }
            Err(err) => {
                println!("{:?}", err);
            }
        }
    }
}

次回は関数を追加したい。

Discussion