D言語 Advent Calendar 2016の23日目の記事です.
更に,Aizu Advent Calendar 2016の23日目の記事でもあります.
前回
前回の記事でD言語の構文拡張ライブラリであるTcenalの話をしました.
この記事ではTcenalの変更点などを見ていきます.
Tcenalの変更点
まず大きな変更点として,構文解析を行う前に字句解析を行うようにしました.
これによって前回問題になっていたint
がin
とt
に解析されてしまう問題を回避できます.
入力が字句列になることによって,構文解析時に字句の種類を調べる必要が出てきます.
それ用の構文をDSLに追加しました.
Tcenalの使い方などは変わってません.
例:代数的データ型とそのパターンマッチ
今流行りの(?)パターンマッチをやってみました.
パーサを書く
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
の仕様は前回と変わりません.
コード生成器を書く
パースツリーを直接いじってるので,かなり複雑になっています.
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();
}
試してみる
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なので(浮動小数点数リテラルに対応していないなど),その辺りをどうにかしたいと思っています.