📌

Rustで作る自作言語(1)

2023/02/06に公開

次回: https://zenn.dev/taka2/articles/22d139f741d207
Rustで自作言語を書いていきます。言語の簡単なコンセプトは次の通りです。

  • 静的型付き非純粋関数型言語
  • 記法を割と一般的な言語に寄せつつ好みの文法にする
  • Effect systemが入ると嬉しい

現状決めていることは、以下の通りです。

  • パーサーはnomを使う
  • とりあえずtree walking型インタプリタにする
  • なるべくドキュメントを書く
  • 作業ログを残す(この記事)

リポジトリ: https://github.com/taka231/lunalang

今回の目標

四則演算が出来るようになる

3 * 3 + 4 * 4 # => 25
4 / 2 - 6 / 3 # => 0
(10 + 20) * 3 # => 90

ASTの定義

#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Expr {
    EInt(i64),
    EBinOp(String, Box<Expr>, Box<Expr>),
}

1+1 # => EBinOp("+", EInt(1), EInt(1))

後々のことも考えてスマートコンストラクタを定義しておきます。

pub fn e_int(n: i64) -> Expr {
    Expr::EInt(n)
}

pub fn e_bin_op(str: &str, e1: Expr, e2: Expr) -> Expr {
    Expr::EBinOp(str.to_string(), Box::new(e1), Box::new(e2))
}

パーサー

中心となるのは以下の部分です。

pub fn term(input: &str) -> IResult<&str, Expr> {
    alt((expr_int, delimited(symbol("("), expr_op_6l, symbol(")"))))(input)
}

pub fn expr_op_7l(input: &str) -> IResult<&str, Expr> {
    let (input, e1) = term(input)?;
    let (input, e2) = many0(|input| {
        let (input, op) = alt((tag("*"), tag("/")))(input)?;
        let (input, ex) = term(input)?;
        Ok((input, (op, ex)))
    })(input)?;
    Ok((
        input,
        e2.iter()
            .fold(e1, |acc, (op, ex)| e_bin_op(&op, acc, ex.clone())),
    ))
}

pub fn expr_op_6l(input: &str) -> IResult<&str, Expr> {
    let (input, e1) = expr_op_7l(input)?;
    let (input, e2) = many0(|input| {
        let (input, op) = alt((tag("+"), tag("-")))(input)?;
        let (input, ex) = expr_op_7l(input)?;
        Ok((input, (op, ex)))
    })(input)?;
    Ok((
        input,
        e2.iter()
            .fold(e1, |acc, (op, ex)| e_bin_op(&op, acc, ex.clone())),
    ))
}

Evalを書く

まずは評価された結果の値を定義します。

#[derive(Eq, PartialEq, Debug, Clone)]
pub enum Value {
    VInt(i64),
}

次にeval本体を実装します。現状ではEvalは何も持っていませんが、これは後々にEvalに状態をもたせるための余地です。

pub struct Eval {}

impl Eval {
    fn new() -> Eval {
        Eval {}
    }
    fn eval(&self, ast: Expr) -> Result<Value, &str> {
        match ast {
            Expr::EBinOp(op, e1, e2) => {
                let v1 = self.eval(*e1)?;
                let v2 = self.eval(*e2)?;
                match (v1, v2) {
                    (Value::VInt(n1), Value::VInt(n2)) => match &op as &str {
                        "+" => Ok(v_int(n1 + n2)),
                        "-" => Ok(v_int(n1 - n2)),
                        "*" => Ok(v_int(n1 * n2)),
                        "/" => Ok(v_int(n1 / n2)),
                        _ => Err("unimplemented operator"),
                    },
                }
            }
            Expr::EInt(n) => Ok(v_int(n)),
        }
    }
}

REPLの実装

pub fn repl() {
    println!("Welcome to lunalang repl!");
    let eval = Eval::new();
    loop {
        let mut program = String::new();
        io::stdin()
            .read_line(&mut program)
            .expect("Failed to read line.");
        if program == ":q\n" {
            break;
        }
        let ast = parser_expr(&program, &PRIORITY_HASHMAP);
        match ast {
            Ok((_, ast)) => {
                let result = eval.eval_expr(ast);
                println!("{:?}", result);
            }
            Err(err) => {
                println!("{:?}", err);
            }
        }
    }
}

Discussion