caper for D

手書きと強弁できることが目標らしいパーサジェネレータcaperに、D言語吐かせてみました。
http://panathenaia.halfmoon.jp/alang/caper.7z
caperは所謂スキャナレスの状態遷移型パーサーです。スキャナは自分で書く必要があります。パースを途中で打ち切るのもパース中の状態を保存するのも簡単です。各ノードに別々の型を使えます。何より出力コードが何やってるのかを追おうと思えるぐらい簡潔なコードを吐いてくれるのが嬉しいです。
例えば四則演算の文法calc0.cpgを実行して計算を行うようなのは↓のように書けます。(caper/examples/calc0.cpp相当)

module calc0_main;

import
    std.c.stdio, std.ctype, std.string,
    calc = calc0;    // "calc0.d" generated form calc0.cpg

class unexpected_char : Exception
{
    this(char[] message){ super(message); }
}

class Scanner
{
    typedef int char_type;

    this(FILE *file)
    {
        file_ = file;
    }

    calc.Token get(out int v)
    {
        int c;
        do {
            c = fgetc(file_);
            if(c == '\n') return calc.Token.token_eof;
        } while( isspace( c ) );

        // 記号類
        switch( c ) {
        case '+': return calc.Token.token_Add;
        case '-': return calc.Token.token_Sub;
        case '*': return calc.Token.token_Mul;
        case '/': return calc.Token.token_Div;
        case EOF: return calc.Token.token_eof;
        default:
            // 整数
            if( isdigit( c ) ) {
                int n = 0;
                while( c != EOF && isdigit( c ) ) {
                    n *= 10;
                    n += c - '0';
                    c = fgetc(file_);
                }
                ungetc(c, file_);
                v = n;
                return calc.Token.token_Number;
            }
        }

        char cc = c;
        throw new unexpected_char(format("bad input char '%s'(%d)\n", (&cc)[0..1], c));
    }

private:
    FILE *file_;
}

class SemanticAction {
    void syntax_error(){}
    void stack_overflow(){}
    void downcast(out int x, int y ) { x = y; }
    void upcast(out int x, int y ) { x = y; }
    int Identity(int n) { return n; }

    int MakeAdd(int x, int y)
    {
        fprintf(stderr, "%d + %d\n", x, y);
        return x + y ; 
    }

    int MakeSub(int x, int y)
    {
        fprintf(stderr, "%d - %d\n", x, y);
        return x - y ; 
    }

    int MakeMul(int x, int y)
    { 
        fprintf(stderr, "%d * %d\n", x, y);
        return x * y ; 
    }

    int MakeDiv(int x, int y)
    { 
        fprintf(stderr, "%d / %d\n", x, y);
        return x / y ; 
    }
}

int main(char [][] args)
{
    auto s = new Scanner(stdin);

    alias calc.Parser!(int, SemanticAction) Parser;
    auto parser = new Parser(new SemanticAction);

    printf(">");

    calc.Token token;
    int v;
    do{
        token = s.get(v);
    }while(!parser.post(token, v));

    if(parser.accept(v)){
        printf("accpeted %d\n", v);
    }

    return 0;
}

C++版を踏襲しているため現在SemanticActionはこんな形ですが、SemanticActionをtemplateにしてParserクラスにmixinなんてのは充分ありと思います。
実にどうでもいいですが、.exeが1MBを超えるのは、やはりBoostが何をBoostしているかといえば実行ファイルサイズをBoostしているのだという私の説を裏づける結果になったと言えましょう。
もっとどうでもいいですが、D言語デバッグ環境は、私がDを触らなくなってから全く改善されてないと判明しました。windbg.exeが付いてくるようにはなりましたがまともに追えませんので同じです。
むしろ今のDなら、boost/spiritよりだいぶマシな言語内DSL型パーサジェネレータ作れてしまうのではないでしょうか、という恐れあり。
あった……http://www.prowiki.org/wiki4d/wiki.cgi?BoostInspiredProjects