💡

Rustで作る自作言語(7)

2023/05/03に公開

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

今回の目標

前回の記事の最後を参照

「単純な項」を定義

ここでいう「単純な項」とは、関数適用の呼び出される関数側に現れる項を指します。

func(2, 3)
^^^^
ここに現れる項

具体的には、変数、括弧で挟まれた式、ブロック式、文字列、数字などです。
パーサーはこのようになります。

pub fn simple_term(input: &str) -> IResult<&str, Expr> {
    alt((
        expr_int,
        |input| {
            let (input, _) = symbol("(")(input)?;
            let (input, _) = symbol(")")(input)?;
            Ok((input, Expr::EUnit))
        },
        parse_block_expr,
        delimited(symbol("("), parser_expr, symbol(")")),
        |input| {
            let (input, ident) = identifier(input)?;
            Ok((input, Expr::EVar(ident)))
        },
        expr_string,
    ))(input)
}

1引数の関数適用時に、括弧を省略出来るようにします。

succ 1 // succ(1)の省略記法

ここで先程のsimple_termも使います。

pub fn fun_app(input: &str) -> IResult<&str, Expr> {
    let (input, e) = simple_term(input)?;
    let (input, args) = alt((
        |input| {
            let (input, _) = symbol("(")(input)?;
            let (input, args) = separated_list0(symbol(","), parser_expr)(input)?;
            let (input, _) = symbol(")")(input)?;
            Ok((input, args))
        },
        |input| {
            let (input, arg) = simple_term(input)?;
            Ok((input, vec![arg]))
        },
    ))(input)?;
    Ok((
        input,
        args.iter()
            .fold(e, |acc, expr| e_fun_app(acc, expr.clone())),
    ))
}

そして、identifierが予約語を受け取らないようにします。

fn identifier(input: &str) -> IResult<&str, String> {
    let (input, _) = multispace0(input)?;
    let (input, first_char) = one_of("abcdefghijklmnopqrstuvwxyz")(input)?;
    let (input, chars) = alphanumeric0(input)?;
    let (input, _) = multispace0(input)?;
    let is_keyword = |x: &&str| vec!["if", "else", "let"].contains(x);
    let ident = first_char.to_string() + chars;
    if is_keyword(&(&ident as &str)) {
        return Err(nom::Err::Error(nom::error::Error::new(
            input,
            nom::error::ErrorKind::Tag,
        )));
    }
    Ok((input, ident))
}

加えて最終引数がsimple termである時のみ最終引数のみを括弧の外側に出すことが出来るようにしました。

@@ -224,8 +224,13 @@ pub fn fun_app(input: &str) -> IResult<&str, Expr> {
     let (input, args) = alt((
         |input| {
             let (input, _) = symbol("(")(input)?;
-            let (input, args) = separated_list0(symbol(","), parser_expr)(input)?;
+            let (input, mut args) = separated_list0(symbol(","), parser_expr)(input)?;
             let (input, _) = symbol(")")(input)?;
+            let (input, optarg) = opt(simple_term)(input)?;
+            match optarg {
+                Some(arg) => args.push(arg),
+                None => (),
+            };
             Ok((input, args))
         },
         |input| {

(その後最終引数で括弧が省略出来るのはブロック式の場合のみにした)

.を使ってメソッド風の呼び出しにする

このような関数呼び出しが、

add(2, 3)

次のように書き換えられるようにする

3.add(2)

具体的なパーサーは以下の通り

pub fn dot_expr(input: &str) -> IResult<&str, Expr> {
    let (input, mut expr) = simple_term(input)?;
    let (input, mut temp_exprs) = many1(|input| {
        let (input, _) = symbol(".")(input)?;
        let (input, ident) = identifier(input)?;
        let (input, args) = opt(|input| {
            let (input, _) = symbol("(")(input)?;
            let (input, args) = separated_list0(symbol(","), parser_expr)(input)?;
            let (input, _) = symbol(")")(input)?;
            Ok((input, args))
        })(input)?;
        let args = args.unwrap_or(vec![]);
        let mut temp_expr = Expr::EVar(ident);
        for arg in args {
            temp_expr = e_fun_app(temp_expr, arg);
        }
        Ok((input, temp_expr))
    })(input)?;
    let (input, opt_expr) = opt(parse_block_expr)(input)?;
    match opt_expr {
        None => (),
        Some(e) => {
            let len = temp_exprs.len();
            temp_exprs[len - 1] = e_fun_app(temp_exprs[len - 1].clone(), e)
        }
    }
    for temp_expr in temp_exprs {
        expr = e_fun_app(temp_expr, expr)
    }
    Ok((input, expr))
}

省略記法のせいでかなり仰々しくなってしまったが、以下のプログラムがちゃんと動く。

3.inc == 4;
3.inc() == 4;
3.add(2) == 5;
3.add {
    2;
} == 5;

ラムダ式(匿名関数)

ラムダ式の文法は次の通り

fn x, y -> x + y

パーサーは次のようになる。

pub fn lambda_fn(input: &str) -> IResult<&str, Expr> {
    let (input, _) = symbol("fn")(input)?;
    let (input, args) = separated_list0(symbol(","), identifier)(input)?;
    let (input, _) = symbol("->")(input)?;
    let (input, mut e) = parser_expr(input)?;
    for i in 0..args.len() {
        let index = args.len() - i - 1;
        e = Expr::EFun(args[index].to_owned(), Box::new(e))
    }
    Ok((input, e))
}

また、次のような記法も用意する。

fn x -> {print(x); x;}は、
{fn x ->
  print(x);
  x;
}
でも表せる

Vector

次は、いよいよVectorを用意していく。
まずはASTの定義から

pub enum Expr {
     EString(String),
     EUnit,
     EBlockExpr(Vec<StatementOrExpr>),
+    EVector(Vec<Expr>),
 }

次にパーサーを定義する。

pub fn expr_vector(input: &str) -> IResult<&str, Expr> {
    let (input, vec) = delimited(
        symbol("["),
        separated_list0(symbol(","), parser_expr),
        symbol("]"),
    )(input)?;
    Ok((input, Expr::EVector(vec)))
}

その後Vector型の型推論を実装する。

            Expr::EVector(exprs) => {
                let ty = self.newTVar();
                for expr in exprs {
                    unify(&ty, &self.typeinfer_expr(expr)?)?;
                }
                Ok(Type::TVector(Box::new(ty)))
            }

そしてevalも実装する。

            Expr::EVector(exprs) => {
                let mut vvec = vec![];
                for e in exprs {
                    vvec.push(self.eval_expr(e)?)
                }
                Ok(Value::VVector(vvec))
            }

次に、このvectorを用いる組み込み関数を実装する。

foreach

諸々あって、複数の引数を持つビルトイン関数用にValue::VBuiltinを変更した。

    VBuiltin(BuiltinFn, Vec<Value>, usize),

引数の数が3つ目の引数の値に満たない場合Vec<Value>に追加されるようになっている。

        builtin.insert(
            "foreach".to_owned(),
            Value::VBuiltin(
                |values, eval| match (&values[0], &values[1]) {
                    (Value::VFun(_, _, _) | Value::VBuiltin(_, _, _), Value::VVector(vec)) => {
                        for value in vec {
                            eval.fun_app_helper(values[0].clone(), value.clone())?;
                        }
                        Ok(Value::VUnit)
                    }
                    _ => Err(EvalError::InternalTypeError),
                },
                vec![],
                2,
            ),
        );

次に、Int型からString型に変換するint_to_string関数を実装します。

        builtin.insert(
            "int_to_string".to_owned(),
            Value::VBuiltin(
                |values, _| match &values[0] {
                    Value::VInt(n) => Ok(Value::VString(n.to_string())),
                    _ => Err(EvalError::InternalTypeError),
                },
                vec![],
                1,
            ),
        );

%演算子のパーサー

%は*や/と同じ優先順位なので、expr_op_7lに%を追加する。

 pub fn expr_op_7l(input: &str) -> IResult<&str, Expr> {
     let (input, e1) = term(input)?;
     let (input, e2) = many0(|input| {
-        let (input, op) = alt((symbol("*"), symbol("/")))(input)?;
+        let (input, op) = alt((symbol("*"), symbol("/"), symbol("%")))(input)?;
         let (input, ex) = term(input)?;
         Ok((input, (op, ex)))
     })(input)?;

型推論・評価については他の演算子と同様に実装する。

for...in文のパーサー

for...in文は次のような文法だ。

for (i in ["a", "b", "c"]) {
    puts(i)
}

パーサーは次のようになる。

pub fn for_in_expr(input: &str) -> IResult<&str, Expr> {
    let (input, _) = symbol("for")(input)?;
    let (input, _) = symbol("(")(input)?;
    let (input, ident) = identifier(input)?;
    let (input, _) = symbol("in")(input)?;
    let (input, vec) = parser_expr(input)?;
    let (input, _) = symbol(")")(input)?;
    let (input, expr) = parser_expr(input)?;
    Ok((
        input,
        e_fun_app(
            e_fun_app(e_var("foreach"), Expr::EFun(ident, Box::new(expr))),
            vec,
        ),
    ))
}

連続する整数からなるVector

まず組み込み関数のenum_from_untilとenum_from_toを用意します。

enum_from_until(1, 5) == [1, 2, 3, 4, 5]
enum_from_to(1, 5) == [1, 2, 3, 4]

enum_from_untilは[n1..=n2]に、enum_from_toは[n1..n2]に対応します。次に、パーサーを書きます。

pub fn enum_vec_expr(input: &str) -> IResult<&str, Expr> {
    let (input, _) = symbol("[")(input)?;
    let (input, e1) = parser_expr(input)?;
    let (input, op) = alt((symbol("..="), symbol("..")))(input)?;
    let (input, e2) = parser_expr(input)?;
    let (input, _) = symbol("]")(input)?;
    match op {
        "..=" => Ok((
            input,
            e_fun_app(e_fun_app(e_var("enum_from_until"), e1), e2),
        )),
        ".." => Ok((input, e_fun_app(e_fun_app(e_var("enum_from_to"), e1), e2))),
        _ => panic!("internal error"),
    }
}

これで完成です!次のプログラムが動くようになりました!

let main = {
  for (i in [1..=100]) {
    puts(
      if (i % 15 == 0) "fizzbuzz"
      else if (i % 3 == 0) "fizz"
      else if (i % 5 == 0) "buzz"
      else int_to_string(i)
    );
  };
};

Discussion