今年もD言語の構文を拡張する

D言語 Advent Calendar 2016の23日目の記事です．
更に，[Aizu Advent Calendar 2016](http://qiita.com/advent-calendar/2016/aizu)の23日目の記事でもあります．

## 前回
[前回の記事](http://qiita.com/youxkei/items/b916a130a0a8d96feddd)でD言語の構文拡張ライブラリであるTcenalの話をしました．
この記事ではTcenalの変更点などを見ていきます．

## Tcenalの変更点
まず大きな変更点として，構文解析を行う前に字句解析を行うようにしました．
これによって前回問題になっていた`int`が`in`と`t`に解析されてしまう問題を回避できます．

入力が字句列になることによって，構文解析時に字句の種類を調べる必要が出てきます．
それ用の構文をDSLに追加しました．

Tcenalの使い方などは変わってません．

## 例：代数的データ型とそのパターンマッチ
今流行りの（？）パターンマッチをやってみました．

### パーサを書く
```source/pattern_matching.d
mixin(generateParsers(
q{
    AggregateDeclaration <- CaseClassDeclaration / super
    CaseClassDeclaration <- "case" "class" @identifier "{" CaseClassConstructor*<","> ","? "}"
    CaseClassConstructor <- @identifier "(" (Type @identifier)*<","> ","? ")"

    PrimaryExpression <- CaseExpression / super
    CaseExpression <- "case" "(" Expression ")" "{" Case* "}"
    Case <- Pattern ":" Expression ";"
    Pattern <- @identifier ("(" Pattern*<","> ","? ")")? / PrimaryExpression
}));
```

前回のSwap演算子よりは多少複雑になってます．
`@identifier`は，前述したような，字句の種類を調べ，識別子だったら成功するようなパーサです．
`super`の仕様は前回と変わりません．

### コード生成器を書く
パースツリーを直接いじってるので，かなり複雑になっています．

```source/pattern_matching.d
string generateDlangCode(ParseTreeNode node)
{
    switch (node.ruleName)
    {
        case "CaseClassDeclaration":
        {
            string code = "class " ~ node.children[0].children[2].token.value ~ "{}";

            foreach (caseClassConstructor; node.children[0].children[4].children)
            {
                code ~=
                    "class " ~ caseClassConstructor.children[0].children[0].token.value ~ ":" ~ node.children[0].children[2].token.value ~"{"
                    q{this(typeof(this.tupleof) args) { this.tupleof = args; }}
                    q{static typeof(this) opCall(typeof(this.tupleof) args) { return new typeof(this)(args); }}
                ;

                foreach (field; caseClassConstructor.children[0].children[2].children)
                {
                    code ~= field.children[0].generateDlangCode() ~ " " ~ field.children[1].token.value ~ ";";
                }

                code ~= "}";
            }

            return code;
        }

        case "CaseExpression":
        {
            string code = "(){auto FOR_PATTERN_MATCHING_DONT_REFER_ME = " ~ node.children[0].children[2].generateDlangCode() ~ ";" ;

            foreach (case_; node.children[0].children[5].children)
            {
                code ~= case_.generateDlangCode();
            }

            code ~= "assert(0);}()";

            return code;
        }

        case "Case":
            return node.children[0].children[0].generateDlangCodeFromPattern("FOR_PATTERN_MATCHING_DONT_REFER_ME", "return " ~ node.children[0].children[2].generateDlangCode() ~ ";");

        default:
        {
            if (0 < node.ruleName.length) {
                string code;

                foreach (child; node.children)
                {
                    code ~= child.generateDlangCode();
                }

                return code;
            } else {
                return node.token.value ~ " ";
            }
        }
    }
}

string generateDlangCodeFromPattern(ParseTreeNode node, string matchingValue, string continuation)
{
    if (node.children[0].ruleName == "PrimaryExpression")
    {
        return "if (" ~ matchingValue ~ " == " ~ node.children[0].generateDlangCode() ~ ") {" ~ continuation ~ "}";
    }
    else if (node.children[0].children[1].children.length == 0)
    {
        if (node.children[0].children[0].token.value == "_")
        {
            return continuation;
        }
        else
        {
            return "auto " ~ node.children[0].children[0].token.value ~ " = " ~ matchingValue ~ ";" ~ continuation;
        }
    }
    else
    {
        string code = continuation;

        foreach_reverse (i, pattern; node.children[0].children[1].children[0].children[1].children)
        {
            code = pattern.generateDlangCodeFromPattern("(cast(" ~ node.children[0].children[0].token.value ~ ") " ~ matchingValue ~ ").tupleof[" ~ i.to!string ~ "]", code);
        }

        code = "if (cast(" ~ node.children[0].children[0].token.value ~ ") " ~ matchingValue ~ ") {" ~ code ~ "}";

        return code;
    }
}

alias parse = parseWithoutMemo!(Module!(createRuleSelector!().RuleSelector));

string PATTERN_MATCHING(string source)
{
    return source.lex().parse().node.generateDlangCode();
}
```

### 試してみる
```source/app.d
import pattern_matching : PATTERN_MATCHING;

mixin(PATTERN_MATCHING(
q{
    import std.stdio;

    case class Expr {
        Number(int number),
        UnOp(string op, Expr expr),
        BinOp(string op, Expr lhs, Expr rhs),
    }

    int eval(Expr e)
    {
        return case (e) {
            Number(n): n;
            UnOp("-", operand): -operand.eval();
            BinOp("+", lhs, rhs): lhs.eval() + rhs.eval();
            BinOp("-", lhs, rhs): lhs.eval() - rhs.eval();
        };
    }

    void main()
    {
        BinOp("-", Number(33), Number(4)).eval().writeln();
        BinOp("+", Number(50), UnOp("-", Number(-7))).eval().writeln();
    }
}));
```

```bash
$ dub
29
57
```
代数的データ型やパターンマッチの構文が非常にアレですが，なんとか動いてくれました．

### 動くコード
https://github.com/youxkei/pattern_matching

## まとめ
字句解析を行うことで，それなりにちゃんと構文解析が出来るようになりました．
ただ，字句解析器が非常にtoy lexerなので（浮動小数点数リテラルに対応していないなど），その辺りをどうにかしたいと思っています．