LoginSignup
5
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-12-23

D言語 Advent Calendar 2016の23日目の記事です.
更に,Aizu Advent Calendar 2016の23日目の記事でもあります.

前回

前回の記事でD言語の構文拡張ライブラリであるTcenalの話をしました.
この記事ではTcenalの変更点などを見ていきます.

Tcenalの変更点

まず大きな変更点として,構文解析を行う前に字句解析を行うようにしました.
これによって前回問題になっていたintintに解析されてしまう問題を回避できます.

入力が字句列になることによって,構文解析時に字句の種類を調べる必要が出てきます.
それ用の構文を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();
    }
}));
$ dub
29
57

代数的データ型やパターンマッチの構文が非常にアレですが,なんとか動いてくれました.

動くコード

まとめ

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

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2