LoginSignup
7

More than 5 years have passed since last update.

D言語入門記事についてと、JSONとかVariantとかのお話

Last updated at Posted at 2013-12-21

一般ピープルな私ごときが、21日目のD言語ACを書きたいと思います。

この記事の前半はパートAとBに分かれており、パートAはd-manualの存在意義とかのことをさかなクンさん用にお話します。
パートBでは、d-manualのお作法について人間用に記述してあります。

後半ではmarkdownとかPandocとかJSONとかVariantを使った代数的データ型と、あとUDAのことをD言語中毒者向けに話します。

お楽しみ下さい。

前半戦パートA「ウマいD言語たのしく食べよう」

鮮魚を養殖しよう

正直に言いますと、一般消費者がD言語を食べることは非常に難しいでしょう。
魚市場に行っても簡単な入門用の魚はいませんし、Googleという巨大釣り堀で釣っても少し元気の無い魚がヒットしてしまします。

しかし、これはしょうが無いことだと諦めるべきです。
D言語はまだまだマイナーな魚ですし、言語仕様や生きている環境が非常に短期間で様変わりするようなお魚です。
今数百円で買えるあの魚拓や多数のウェブサイトに載っている魚拓は、当時鮮度が高くピチピチしていた魚の魚拓ということは想像できますが、今となってはニボシの魚拓です。

一般消費者はダシが取れるニボシが欲しいのではありません!
鮮度がよく美味しいお刺身が欲しいのです。
誰でもニボシとお刺身ならお刺身を選ぶでしょう!

そのような理由から、d-manualは一般消費者に常に品質管理された鮮魚を送りつけるためにスタートしました。

徹底された品質管理

我々d-manualでは、奈良県大和郡山市にある金魚用超大型いけすと、それを管理する大型いけす管制システムDAIBUTSUによる制御によって魚の養殖を行なっています。
というのは嘘で、GitHubとかいう素晴らしいSNSっぽいなにかによって魚たちの管理をしています。

k3kaimu/d-manual

魚の種類は開発部がいけすの改良を行うことによって1ヶ月毎くらいに増えていきます。
しかし、開発部の人間が現在はすくなく、さらにはその少ない人材を養殖以外の他の事業にも割り振っているため、開発部門は滞りがちになっています。

しかし、安心して下さい。
いけすの開発が遅いからといって品質管理は疎かになっていません。
当初想定していた人数を大きく上回る品質管理部の人たちによって、出荷されていく魚たちの品質は最高の状態に保たれています。

君も養殖しよう

この記事を読んでいるD言語マスターなあなたに頼み事があります。
d-manualでは、私を含め品質管理部の人間は充実しているのですが、開発部やマーケティング部,広報宣伝部などについてはまだまだうまく機能していません。
あなたのD言語能力を活かして養殖をしてみませんか?

前半戦パートB「d-manualの真面目なお話」

前半戦パートBでは結構真面目に書いていきます。

名前について

d-manualという名前については適当につけたものですが、なぜか定着してしまったようなのでそのまま使用しています。

誤字報告でも大歓迎

誤字脱字、乱文や、ミスリードにつながる文章、そもそも書いてることが嘘などなど、目についたものについては、

  • GitHubでIssueを立てる
  • Twitterでリプライを飛ばす
  • 修正Pull Requestを送る

などなどをいただけると大変助かります。
もちろん、報告された問題については迅速に対応,解決します。

私のTwitterアカウントは@k3_kaimuです。

訳語について

D言語には、他の言語には無いような機能がたくさんあります。
入門記事のような日本語ドキュメントを書く際には、入門者になるべく分かりやすく・なじみやすく書く必要があるため、そのような機能の最適な日本語訳が必要になります。
この議論については以下でまとめています。

訳語や語句の統一と訳語一覧表 · Issue #26 · k3kaimu/d-manual
D言語の訳語に関する議論 - Togetterまとめ

意見などありましたら、TwitterかGitHubのIssueにて知恵をお貸しください。

記事の投稿の仕方

「せっかく書いてたのに先を越された」という状況を回避するため、
新しい記事(章)を書く場合には、Issueを確認して事前に誰かが同章の記事を書いていないか確認してください。
そして、誰も記事を書くことを表明していなければIssueを立ててください。

フォーマットについてはGFM(GitHub形式のマークダウン)でお願いします。
記事自体の文体などについては、他の章を参考にしてください。

現在のところ、以下の章の記事を募集しています。
もちろん、既存記事のリプレースでも大歓迎です。

  • 構造体
  • 共用体:構造体と同一の記事にしてもよい
  • 列挙体
  • クラスとインターフェイス
  • 例外
  • 単体テスト
  • 契約プログラミング:単体テストと同一の記事にしてもよい
  • モジュール
  • テンプレート
  • その他テクニックなど

なにかわからないことなどあれば、私に相談していただければ対応致します。

ライセンスに関して

私がライセンスに対してあまりにも知識が無いため、現在は「 クリエイティブ・コモンズ 表示 - 非営利 - 継承 3.0 非移植 ライセンス」となっています。
結構きつめのライセンスかもしれませんが、d-manualはソースコードじゃなくて文章が主体なので、このライセンスを選びました。

ちなみに、非営利にしていますが、これは私にも適応されるので 私も含めて 誰もが営利目的で使用できないことを意味します。

GitHub上での閲覧以外の閲覧方法,他フォーマットでの配布について

そのうちPDF形式での配布やGitHub Pagesなど、他のフォーマットでの配布もしようと考えています。
しかし、リンクの問題がかなり深刻なのでどうにかしてやる必要があります。

リンクの問題というのは、例えばa.mdからb.mdに飛びたいとします。
しかし、HTMLに変換してしまえばa.htmlb.htmlになりますし、もしかしたら結合して一つのHTMLとしたいかもしれません。
そのような場合にリンクをどのように解決するか、という問題です。

この問題については、以下でまとめています。

GitHub Pagesとその他公開方法 · Issue #27 · k3kaimu/d-manual

後半戦「PandocのJSON-FilterをDで作る」

生粋のD言語erお待ちかねの後半戦です。
前半戦では、ソースコードが一切載ってませんでしたが、後半戦ではソースコードだらけです。
嬉しいですね。

Pandocというツールをご存知でしょうか。
Pandocは、例えばmarkdownで書かれた文章をHTMLに変換したりするといった、ドキュメントのフォーマットを変換してくれる素晴らしいツールです。

d-manualでは、先ほど述べたようにPDFやHTMLなどの他のフォーマットでの配布も考えていますが、その際にはPandocを用いて変換しようと考えています。

しかし、先ほど挙げましたリンクの問題を解決するためには、プログラムを用いてリンクの部分を変換してやる必要があります。

たとえば、a.md#fooへのリンクを#a_fooに変換するといったようなことです。

変換前
[fooo](a.md#foo)
変換後
[fooo](#a_foo)

PandocはHaskellで書かれているので、Haskellであれば簡単にPandocをハックできるのですが、私達はD言語erなのでD言語でこのような問題を解決したいのです。
Pandocの公式ドキュメントにはPythonを用いていろいろする方法が書かれているので、私もそれを参考にしてD言語からいろいろしてみました。

ドキュメント内のD言語のコードをevalしてReplaceしたい

今回は、markdownのドキュメント中に書かれたD言語のコードをevalしてReplaceするようなプログラムを説明します。
つまり、以下の様にドキュメントを変換してくれるプログラムを目指します。

インデントはQiitaのMarkdown記法のバグを回避するためだけに存在するので、気にしないでください。

    こんにちは、世界!

    `"Hello, World!"`{.dcode}

    ~~~~{.dcode}
    int a = 12;
    ~~~~

    `a`{.dcode}

こんにちは、世界!

Hello, World!

12

D言語からPandocで文章フォーマット変換…!!!

Pandocは、次のように呼び出してstdinにドキュメントを流してやれば、変換した文字列をstdoutに返してくれます。

pandoc --to=TOFormat --from=FROMFormat

例えば、markdownをhtmlに変換するのであれば、以下のように呼び出してstdinに文字列を流し込んでやります。

pandoc --from=markdown --to=html

std.processは素晴らしいので、この程度のプログラムは簡単に記述できます。

import std.array;
import std.process;


enum DocFormat : string
{
    json = "json",
    markdown = "markdown",
    html = "html",
    html5 = "html5",
}


string convertByPandoc(string doc, DocFormat from, DocFormat to)
{
    auto pandoc = pipeProcess(["pandoc", "--from=" ~ from, "--to=" ~ to], Redirect.stdin | Redirect.stdout);
    scope(exit) wait(pandoc.pid);

    auto sendData = cast(immutable(ubyte)[])(doc);
    pandoc.stdin.rawWrite(sendData);
    pandoc.stdin.close();

    auto str = appender!string();
    foreach(ubyte[] buf; pandoc.stdout.byChunk(4096))
        str ~= cast(string)(buf.dup);

    return str.data;
}

JSONを吐くPandoc…!!!

PandocはパースしたドキュメントをJSON形式で吐いてくれるようです。
もちろん、JSONを渡して他のドキュメントに変換することも可能です。

    import std.stdio;

    void main()
    {
        auto txt = q"[
    こんにちは、世界!

    `"Hello, World!"`{.dcode}

    ~~~~{.dcode}
    int a = 12;
    ~~~~

    `a`{.dcode}

    ]";

        writeln(txt.convertByPandoc(DocFormat.markdown, DocFormat.json));
    }

q"[]"のようにデリミタを任意に設定できるq""文字列リテラルが便利で素敵ですね。
Delimited Strings : Lexical - D Programming Language

さて、実行結果は以下のようになります。

[{"unMeta":{}},[{"t":"Para","c":[{"t":"Str","c":"こんにちは、世界!"}]},{"t":"Para","c":[{"t":"Code","c":[["",["d
code"],[]],"\"Hello, World!\""]}]},{"t":"CodeBlock","c":[["",["dcode"],[]],"int a = 12;"]},{"t":"Para","c":[{"t":"Code",
"c":[["",["dcode"],[]],"a"]}]}]]

std.jsonを使う…!!!

std.jsonでJSONValue型に変換してしまいます。

    import std.stdio;

    void main()
    {
        auto txt = q"[
    こんにちは、世界!

    `"Hello, World!"`{.dcode}

    ~~~~{.dcode}
    int a = 12;
    ~~~~

    `a`{.dcode}

    ]";


        auto json = txt.convertByPandoc(DocFormat.markdown, DocFormat.json);
        auto jsonValue = json.parseJSON();
    }

JSONValueは、JSONのASTになっています。

JSONValueからPandocのASTを作る…!!!

その前にPandocのASTの型の宣言を。
この型宣言は、Haskellのソースコードを参考にしています。

std.variant.Algebraicを使えばHaskellでの代数的データ型っぽいのをD言語でも表すことができます。
また、後で楽するためにUDAを使っていろいろ属性をつけています。

import std.typetuple;
import std.variant;

/**
UDAのための型です。
この型がAttributeとして付いている変数には、walkが再帰的に適用されます
*/
struct ApplyWalk{}

/**
UDAのための型です。
この型がAttributeとして付いている型は、Pandocが吐くJSON formatted ASTではOBJECTとして扱われます。
*/
struct AsObjectInJSON
{
    /**
    UDAのための型です。
    OBJECTでの名前を指定するために使用します。
    */
    struct Name
    {
        string name;        /// OBJECTでの名前
    }
}


/**
Algebraicを使いやすくするためのテンプレート
*/
mixin template UseAlgebraic(T...)
{
    Algebraic!(T) content;

    static auto as(string type, T...)(T args)
    {
        mixin("alias U = " ~ type ~ ";");
        return typeof(this)(U(args));
    }
}


/**
Pandocが吐くAST
*/
struct Pandoc
{
    @ApplyWalk Meta meta;           // メタ情報を管理する
    @ApplyWalk Block[] document;    // ドキュメントのAST
}


/**
ドキュメントのメタ情報を管理します
*/
@AsObjectInJSON
struct Meta
{
    /**
    キーに"title"や"author", "date"が格納され、値にはそれに対応したものが格納されています。

    Example:
    --------
    ["title": Meta.Value(Meta.Value.MetaInlines([Inline(Inline.Str("d-manual"))])),
     "author":Meta.Value(Meta.Value.MetaList([Meta.Value(Meta.Value.MetaInlines([Inline(Inline.Str("Kazuki")), Inline(Inline.Space()), Inline(Inline.Str("Komatsu"))]))])),
     "date":  Meta.Value(Meta.Value.MetaInlines([Inline(Inline.Str("May")), Inline(Inline.Space()), Inline(Inline.Str("5,")), Inline(Inline.Space()), Inline(Inline.Str("2013")), Inline(Inline.Space()), Inline(Inline.Str("~"))]))]
    --------
    */
    @(AsObjectInJSON.Name("unMeta")) @ApplyWalk
    Value[string] unMeta;
    alias unMeta this;


    struct Value
    {
        this(T)(T t)
        if(staticIndexOf!(T, InnerTypes) != -1)
        {
            content = t;
        }


        alias InnerTypes = TypeTuple!(MetaMap, MetaList, MetaBool, MetaString, MetaInlines, MetaBlocks);

        struct MetaMap { Value[string] content; }
        struct MetaList { Value[] content; }
        struct MetaBool { bool content; }
        struct MetaString { string content; }
        struct MetaInlines { Inline[] content; }
        struct MetaBlocks { Block[] content; }

        mixin UseAlgebraic!(InnerTypes);
    }
}


enum Alignment : string
{
    left = "AlignLeft",
    right = "AlignRight",
    center = "AlignCenter",
    default_ = "AlignDefault",
}


struct ListAttributes
{
    enum NumberStyle : string
    {
        default_ = "DefaultStyle",
        example = "Example",
        decimal = "Decimal",
        lowerRoman = "LowerRoman",
        upperRoman = "UpperRoman",
        lowerAlpha = "LowerAlpha",
        upperAlpha = "UpperAlpha",
    }


    enum NumberDelim : string
    {
        default_ = "DefaultDelim",
        period = "Period",
        oneParen = "OneParen",
        twoParen = "TwoParen",
    }


    int level;
    NumberStyle numStyle;
    NumberDelim numDelim;
}


struct Attr
{
    string identifier;
    string[] classes;
    Tuple!(string, string)[] keyval;
}


alias TableCell = Block[];


struct Format { string content; alias content this; }


struct Block
{
    this(T)(T t)
    if(staticIndexOf!(T, InnerTypes) != -1)
    {
        content = t;
    }


    alias InnerTypes = TypeTuple!(Plain, Para, CodeBlock, RawBlock, BlockQuote, OrderedList,
                                  BulletList, DefinitionList, Header, HorizontalRule, Table,
                                  Div, Null);

    struct Plain { @ApplyWalk Inline[] content; alias content this; }
    struct Para { @ApplyWalk Inline[] content; alias content this; }
    struct CodeBlock { Attr attr; string content; }
    struct RawBlock { Format format; string content; }
    struct BlockQuote { @ApplyWalk Block[] content; alias content this; }
    struct OrderedList { ListAttributes attr; @ApplyWalk Block[][] content; }
    struct BulletList { @ApplyWalk Block[][] content; alias content this; }
    struct DefinitionList { @ApplyWalk Element[] content; struct Element { @ApplyWalk Inline[] term; @ApplyWalk Block[][] defs; } alias content this; }
    struct Header { int level; Attr attr; @ApplyWalk Inline[] content; }
    struct HorizontalRule{}
    struct Table { @ApplyWalk Inline[] caption; Alignment[] colAligns; double[] relColWidth; @ApplyWalk TableCell[] colHeaders; @ApplyWalk TableCell[][] rows; }
    struct Div { Attr attr; @ApplyWalk Block[] content; }
    struct Null {}


    mixin UseAlgebraic!(InnerTypes);
    alias content this;
}


struct Target { string url; string title; }


enum MathType : string { display = "DisplayMath", inline = "InlineMath" }


struct Inline
{
    this(T)(T t)
    if(staticIndexOf!(T, InnerTypes) != -1)
    {
        content = t;
    }


    alias InnerTypes = TypeTuple!(Str, Emph, Strong, Strikeout, Superscript, Subscript, SmallCaps,
                                  Quoted, Cite, Code, Space, LineBreak, Math, RawInline, Link,
                                  Image, Note, Span);

    struct Str { string content; alias content this; }
    struct Emph { @ApplyWalk Inline[] content; alias content this; }
    struct Strong { @ApplyWalk Inline[] content; alias content this; }
    struct Strikeout { @ApplyWalk Inline[] content; alias content this; }
    struct Superscript { @ApplyWalk Inline[] content; alias content this; }
    struct Subscript { @ApplyWalk Inline[] content; alias content this; }
    struct SmallCaps { @ApplyWalk Inline[] content; alias content this; }
    struct Quoted { Type type; @ApplyWalk Inline[] content; enum Type : string { singleQuote = "SingleQuote", doubleQuote = "DoubleQuote" } }
    struct Cite { Citation[] citation; @ApplyWalk Inline[] content; }
    struct Code { Attr attr; string content; }
    struct Space {}
    struct LineBreak {}
    struct Math { MathType type; string content; }
    struct RawInline { Format format; string content; }
    struct Link { @ApplyWalk Inline[] content; Target target; }
    struct Image { @ApplyWalk Inline[] content; Target target; }
    struct Note { @ApplyWalk Block[] content; alias content this; }
    struct Span { Attr attr; @ApplyWalk Inline[] content; }


    mixin UseAlgebraic!(InnerTypes);
    alias content this;
}


@AsObjectInJSON struct Citation
{
    enum Mode : string
    {
        authorInText = "AuthorInText",
        suppressAuthor = "SuppressAuthor",
        normalCitation = "NormalCitation",
    }


    @(AsObjectInJSON.Name("citationId")) string id;
    @(AsObjectInJSON.Name("citationPrefix")) Inline[] prefix;
    @(AsObjectInJSON.Name("citationSuffix")) Inline[] suffix;
    @(AsObjectInJSON.Name("citationMode")) Mode mode;
    @(AsObjectInJSON.Name("citationNoteNum")) int noteNum;
    @(AsObjectInJSON.Name("citationHash")) int hash;
}

で、肝心のJSONからASTへの変換ですが、たったこれだけで表現できます。
非常に簡単ですね。

/**
構造体などのメンバに対して、再帰的にwalkを働かせるかどうか判定します。
*/
template isAppliedWalkField(alias field)
{
    enum isAppliedWalkField = staticIndexOf!(ApplyWalk, __traits(getAttributes, field)) != -1;
}


/**
Pandocが吐くJSON formatted ASTにおいてOBJECTとして表現されているか判断します。
*/
template isObjectInJSON(alias field)
{
    enum isObjectInJSON = staticIndexOf!(AsObjectInJSON, __traits(getAttributes, field)) != -1;
}


/**
Pandocが吐くJSON formatted ASTにおいてOBJECTとして表現されている場合、対応する名前を返します。
*/
template getNameInJSONObject(alias field)
{
    template isAsObjectInJSONName(alias A)
    {
        static if(isExpressionTuple!A)
            enum isAsObjectInJSONName = is(typeof(A) == AsObjectInJSON.Name);
        else
            enum isAsObjectInJSONName = false;
    }

    enum idx = staticIndexOf!(isAsObjectInJSONName, __traits(getAttributes, field));

    static if(idx != -1)
        enum getNameInJSONObject = __traits(getAttributes, field)[idx].name;
    else
        enum getNameInJSONObject = field.stringof;
}


T fromJSONValue(T)(JSONValue json)
if(is(T == string) && !is(T == enum))
in{
    assert(json.type == JSON_TYPE.STRING);
}
body{
    return json.str;
}


T fromJSONValue(T : int)(JSONValue json)
in{
    assert(json.type == JSON_TYPE.INTEGER);
}
body{
    return json.integer.to!int();
}


T fromJSONValue(T : bool)(JSONValue json)
in{
    assert(json.type == JSON_TYPE.TRUE || json.type == JSON_TYPE.FALSE);
}
body{
    return json.type == JSON_TYPE.TRUE;
}


T fromJSONValue(T : double)(JSONValue json)
in{
    assert(json.type == JSON_TYPE.FLOAT);
}
body{
    return json.floating;
}


T fromJSONValue(T)(JSONValue json)
if(isArray!T && !is(T : string))
in{
    assert(json.type == JSON_TYPE.ARRAY);
}
body{
    return json.array.map!(a => a.fromJSONValue!(typeof(T.init[0]))()).array();
}


T fromJSONValue(T)(JSONValue json)
if(isAssociativeArray!(T))
in{
    assert(json.type == JSON_TYPE.OBJECT);
}
body{
    alias V = typeof(T.init.values[0]);

    T dst;

    foreach(k, v; json.object)
        dst[k] = v.fromJSONValue!V();

    return dst;
}


T fromJSONValue(T)(JSONValue json)
if((is(T == enum) && is(T : string)) || is(typeof(T.InnerTypes.length)) || (is(T == struct) && isObjectInJSON!T) || is(T == struct))
{
    static if(is(T == enum) && is(T : string))
    {
        assert(json.type == JSON_TYPE.OBJECT);

        foreach(e; EnumMembers!T)
            if(json["t"].fromJSONValue!string() == e)
                return e;

        enforce(0, format(`JSON parser of pandoc AST throw an error "%s".`, format("Invalid type tag '%s' of " ~ T.stringof, json["t"].fromJSONValue!string())));
        assert(0);
    }
    else static if(is(typeof(T.InnerTypes.length)))
    {
        assert(json.type == JSON_TYPE.OBJECT);

        foreach(i, U; T.InnerTypes)
            if(json["t"].fromJSONValue!string() == U.stringof)
                return T(json["c"].fromJSONValue!U());

        enforce(0, format(`JSON parser of pandoc AST throw an error "%s".`, format("Invalid type tag '%s' of " ~ T.stringof, json["t"].fromJSONValue!string())));
        assert(0);
    }
    else static if(is(T == struct) && isObjectInJSON!T)
    {
        assert(json.type == JSON_TYPE.OBJECT);

        T dst;

        foreach(i, ref e; dst.tupleof){
            if(auto p = getNameInJSONObject!(T.tupleof[i]) in json.object)
                e = fromJSONValue!(typeof(e))(*p);
            else
                enforce(0, format(`JSON parser of pandoc AST throw an error "%s".`, format("Cannot find object member name '%s' of " ~ T.stringof, getNameInJSONObject!(T.tupleof[i]))));
        }

        return dst;
    }
    else static if(is(T == struct))
    {
        T dst;

        static if(T.tupleof.length == 1)
            dst = T(json.fromJSONValue!(typeof(T.tupleof))());
        else
        {
            enforce(json.type == JSON_TYPE.ARRAY, format(`JSON parser of pandoc AST throw an error "%s".`, format("JSON_TYPE is '%s', not '%s' in '%s'", json.type, JSON_TYPE.ARRAY, __FUNCTION__)));

            foreach(i, ref e; dst.tupleof)
                e = json[i].fromJSONValue!(typeof(T.tupleof[i]))();
        }

        return dst;
    }
    else
        static assert(0, "'" ~ T.stringof ~ "' is not matched.");
}
    import std.stdio;

    void main()
    {
        auto txt = q"[
    こんにちは、世界!

    `"Hello, World!"`{.dcode}

    ~~~~{.dcode}
    int a = 12;
    ~~~~

    `a`{.dcode}

    ]";


        auto json = txt.convertByPandoc(DocFormat.markdown, DocFormat.json);
        auto jsonValue = json.parseJSON();
        auto ast = jsonValue.fromJSONValue();
    }

Pandoc-ASTを操作する

結局ASTさえ操作できれば何でも解決できるのです。
今回は、Inline.CodeBlock.CodeBlockに対して、次のようなフォーマットのものを対象にします。

    .dcodeがクラスとして付いているInline.Code
    評価されるD言語の式を書いている必要がある。
    `1 + 2`{.dcode}

    .dcodeがクラスとして付いているBlock.CodeBlock
    このブロックは、D言語の文を書いている必要がある。
    ~~~~{.dcode}
    int a;
    ~~~~

.dcodeというクラスは、Attr構造体のstring[] classesの要素の一つに格納されているはずなので、これを目印にします。
次のようなコードになります。

template isWalkableFunction(alias f, A)
{
    enum isWalkableFunction = is(typeof((A a){return f(a);}(A.init)) : A);
}


T walk(alias f, T)(auto ref T tree)
{
    static if(is(typeof(T.InnerTypes.length)))
        foreach(U; T.InnerTypes)
            if(U* p = tree.content.peek!U)
                *p = walk!f(*p);

    static if(isArray!T)
        foreach(ref e; tree)
            e = walk!f(e);

    static if(is(typeof(tree.tupleof.length)))
        foreach(i, ref e; tree.tupleof)
            static if(isAppliedWalkField!(T.tupleof[i]))
                e = walk!f(e);

    static if(isWalkableFunction!(f, T))
        return f(tree);
    else
        return tree;
}


enum classIdentifier = "dcode";


void evalInPlace(T...)(ref T docs)
if(EraseAll!(Pandoc, T).length == 0)
{
    auto dcode = appender!string(`
        {
            string[] _evaled__;`
            );

    Inline*[] replaced;

    foreach(ref doc; docs)
        doc.walk!((ref a)
            {
                Inline fI(ref Inline a)
                {
                    if(auto p = a.content.peek!(Inline.Code)){
                        if(p.attr.classes.find("dcode")){
                            dcode ~= `_evaled__ ~= to!string(`;
                            dcode ~= p.content;
                            dcode ~= `);`;
                        }

                        replaced ~= &a;
                    }

                    return a;
                }


                Block fB(ref Block a)
                {
                    if(auto p = a.content.peek!(Block.CodeBlock))
                        if(p.attr.classes.find("dcode")){
                            dcode ~= p.content;
                            return Block.as!"Null";
                        }

                    return a;
                }


                T fA(T)(ref T a)
                if(!is(T == Inline) && !is(T == Block))
                { return a; }


                static if(is(typeof(fI(a))))
                    return fI(a);
                else static if(is(typeof(fB(a))))
                    return fB(a);
                else
                    return fA(a);
            })();

    dcode ~= `
        writeln(_evaled__);
    }`;


    auto runDStatements(string str)
    {
        auto rdmd = execute(["rdmd", "--eval=" ~ str]);

        return rdmd.output
               .findSplit("\n")[0]
               .chomp()
               .to!(string[])();
    }


    string[] evaledTable = runDStatements(dcode.data);

    foreach(i, inline; replaced)
        *inline = Inline.as!"Str"(evaledTable[i]);
}
    import std.stdio;

    void main()
    {
        auto txt = q"[
    こんにちは、世界!

    `"Hello, World!"`{.dcode}

    ~~~~{.dcode}
    int a = 12;
    ~~~~

    `a`{.dcode}

    ]";


        auto json = txt.convertByPandoc(DocFormat.markdown, DocFormat.json);
        auto jsonValue = json.parseJSON();
        auto ast = jsonValue.fromJSONValue();
        ast.evalInPlace();
    }

簡単すぎてあくびが出ますね。
ちなみに、ソースコードを読めばわかることですが、D言語のコードの評価にはrdmd --evalを使ってます。

Pandoc-ASTをJSON文字列に変換する

まずJSONValueに変換してから、std.json.toJSONで文字列に変換してしまいます。
次のようになります。

JSONValue toJSONValue(T)(T value)
if(is(T == typeof(null)))
{
    JSONValue dst = void;
    dst.type = JSON_TYPE.NULL;

    return dst;
}
JSONValue toJSONValue(T : string)(T value)
out(result){
    assert(result.type == JSON_TYPE.STRING);
}


body{
    JSONValue dst = void;
    dst.type = JSON_TYPE.STRING;
    dst.str = value;

    return dst;
}


JSONValue toJSONValue(T)(T value)
if(isSigned!T && isIntegral!T)
out(result){
    assert(result.type == JSON_TYPE.INTEGER);
}
body{
    JSONValue dst = void;
    dst.type = JSON_TYPE.INTEGER;
    dst.integer = value;

    return dst;
}


JSONValue toJSONValue(T)(T value)
if(isUnsigned!T && isIntegral!T)
out(result){
    assert(result.type == JSON_TYPE.UINTEGER);
}
body{
    JSONValue dst = void;
    dst.type = JSON_TYPE.UINTEGER;
    dst.integer = value;

    return dst;
}


JSONValue toJSONValue(T)(T value)
if(is(T == bool))
out(result){
    assert(result.type == JSON_TYPE.TRUE || result.type == JSON_TYPE.FALSE);
}
body{
    JSONValue dst = void;
    dst.type = value ? JSON_TYPE.TRUE : JSON_TYPE.FALSE;

    return dst;
}


JSONValue toJSONValue(T)(T value)
if(isFloatingPoint!T)
out(result){
    assert(result.type == JSON_TYPE.FLOAT);
}
body{
    JSONValue dst = void;
    dst.type = JSON_TYPE.FLOAT;
    dst.floating = value;

    return dst;
}


JSONValue toJSONValue(T)(T value)
if(isArray!T)
out(result){
    assert(result.type == JSON_TYPE.ARRAY);
}
body{
    JSONValue dst = void;
    dst.type = JSON_TYPE.ARRAY;
    dst.array = null;

    foreach(e; value)
        dst.array ~= e.toJSONValue();

    return dst;
}


JSONValue toJSONValue(T)(T value)
if(isAssociativeArray!T)
out(result){
    assert(result.type == JSON_TYPE.OBJECT);
}
body{
    JSONValue dst = void;
    dst.type = JSON_TYPE.OBJECT;
    dst.object = null;

    foreach(k, v; value)
        dst.object[k] = v.toJSONValue();

    return dst;
}


JSONValue toJSONValue(T)(T value)
if(is(T == enum) && is(T : string))
{
    JSONValue dst = void;
    dst.type = JSON_TYPE.OBJECT;
    dst.object = null;

    dst.object["t"] = (cast(string)value).toJSONValue();
    dst.object["c"] = null.toJSONValue();

    return dst;
}


JSONValue toJSONValue(T)(T value)
if(is(typeof(T.InnerTypes.length)))
{
    JSONValue dst = void;
    dst.type = JSON_TYPE.OBJECT;
    dst.object = null;

    foreach(i, U; T.InnerTypes)
        if(auto p = value.tupleof[0].peek!U())
        {
            dst.object["t"] = U.stringof.toJSONValue();
            dst.object["c"] = (*p).toJSONValue();
        }

    return dst;
}


JSONValue toJSONValue(T)(T value)
if(is(T == struct) && isObjectInJSON!T)
{
    JSONValue dst = void;
    dst.type = JSON_TYPE.OBJECT;
    dst.object = null;

    foreach(i, e; value.tupleof)
        dst.object[getNameInJSONObject!(T.tupleof[i])] = e.toJSONValue();

    return dst;
}


JSONValue toJSONValue(T)(T value)
if(is(T == struct) && !isObjectInJSON!T && !is(typeof(T.InnerTypes.length)))
{
    JSONValue dst = void;

    static if(T.tupleof.length == 0)
    {
        dst.type = JSON_TYPE.ARRAY;
        dst.array = null;
    }
    else static if(T.tupleof.length == 1)
        dst = value.tupleof[0].toJSONValue();
    else
    {
        dst.type = JSON_TYPE.ARRAY;
        dst.array = null;

        foreach(i, ref e; value.tupleof)
            dst.array ~= e.toJSONValue();
    }

    return dst;
}


string toJSONFormatString(JSONValue json)
{
    return toJSON(&json);
}
    import std.stdio;

    void main()
    {
        auto txt = q"[
    こんにちは、世界!

    `"Hello, World!"`{.dcode}

    ~~~~{.dcode}
    int a = 12;
    ~~~~

    `a`{.dcode}

    ]";


        auto json = txt.convertByPandoc(DocFormat.markdown, DocFormat.json);
        auto jsonValue = json.parseJSON();
        auto ast = jsonValue.fromJSONValue();
        ast.evalInPlace();

        auto trasformedJSON = ast.toJSONValue().toJSONFormatString();
    }

JSONをmarkdownに再変換する

最初のほうで書いたconvertByPandocを使えばそれで結構です。

    import std.stdio;

    void main()
    {
        auto txt = q"[
    こんにちは、世界!

    `"Hello, World!"`{.dcode}

    ~~~~{.dcode}
    int a = 12;
    ~~~~

    `a`{.dcode}

    ]";


        auto json = txt.convertByPandoc(DocFormat.markdown, DocFormat.json);
        auto jsonValue = json.parseJSON();
        auto ast = jsonValue.fromJSONValue!Pandoc();
        ast.evalInPlace();

        auto transformedJSON = ast.toJSONValue().toJSONFormatString();
        writeln(transformedJSON.convertByPandoc(DocFormat.json, DocFormat.markdown));
    }

やったー!!!!
できたー!!!!

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
7