自作言語の構文を考えるのがめんどくさいそこのアナタ、Rustのマクロで楽してもいいわよ?

最近 とっくんのYouTubeチャンネル - YouTube さんの再度ハマり言語野が支配されてます、id:Krouton です。

事の発端

つまり「言語を作りたいけど具象構文考えるのめんどくせ〜〜!!!!」「パーサジェネレータ使うにしてもそれすら書き下すのもめんどくせ〜〜!!!!!」って時ありますよね?(あってくれ)
そんなアナタのためにS式でASTを表現するのをオススメします。Rustが書きたかったのでそうしましたが、他のマクロがサポートされてる言語なら大体できそう?

TL;DR と まとめ

  • (1 + 2) + (3 + 4) を表現するのに
BinOP(
    Box::new(BinOP(Box::new(Number(1)), Add, Box::new(Number(2)))),
    Add,
    Box::new(BinOP(Box::new(Number(3)), Add, Box::new(Number(4)))),
)

から

ast!((+ (+ 1 2) (+ 3 4)))

という風にS式で表現できるようになる。Box::newを手書きするのから解放されるようになる。

  • 簡単にコンパイラのミドルエンド*1から手をつけられるようになる。
  • 言語処理系が書きたくなる。

本編

type AST = Expression;

#[derive(Debug, PartialEq)]
pub enum Expression {
    Number(usize),
    Ident(String),
}

use Expression::*;

impl From<usize> for Expression {
    fn from(n: usize) -> Self {
        Number(n)
    }
}

impl<'a> From<&'a str> for Expression {
    fn from(s: &'a str) -> Expression {
        Ident(s.to_string())
    }
}

macro_rules! ast {
    ($i:ident) => {
        Expression::from(stringify!($i))
    };
    ($e:expr) => {
        Expression::from($e)
    };
}

fn main() {
    dbg!(ast!(2)); // Number(2)
    dbg!(ast!(id)); // Ident("id")
}

ここはFromを使ってるだけで特に難しいところはありませんね。ASTとマクロを拡張して関数適用(Apply)を考えてみましょう。

#[derive(Debug, PartialEq)]
pub enum Expression {
    // 略
    Apply {
        fn_lit: Box<Expression>,
        args: Vec<Expression>,
    },
}

macro_rules! ast {
    (($fn:tt $( $arg:tt )*)) => {
        Apply {
            fn_lit: Box::new(ast!($fn)),
            args: vec![$( ast!($arg), )*]
        }
    };
    // 略
    ($e:expr) => {
        Expression::from($e)
    };
    // exprより後にしないと1 や 2が全てIdentになるので最後に置く
    ($t:tt) => {
        Expression::from(stringify!($t))
    }
}

ast!((print 1 2)); // Apply { fn_lit: Ident("print"), args: [Number(1), Number(2)] }
ast!((+ 1 2)); // Apply { fn_lit: Ident("+"), args: [Number(1), Number(2)] }

ミソとしては四則演算を関数適用として、stringify!で+などの演算子をIdentとして解釈する部分です。これのおかげで、四則演算用のマクロのルールを定義せずに表現できるようになりました。
あとは好きな風にASTを拡張して好きなS式で表現しましょう!

拡張したgist gist.github.com

Special Thanks

*1: コンパイラのミドルエンドって滅多に使わない言葉らしいってWikipedia先生が言ってた コンパイラ - Wikipedia

*2: 質問者は私です