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