JSON 記述用 YAML 1.2 サブセットのローダ (その8)

YAML のフロー・コレクションは、JSON の配列とオブジェクトの構文をそれぞれ緩めたシーケンスとマッピングの 2 種類に分かれています。これら 2 つに、3つのスカラーであるシングル・クォーテッド・スタイル、ダブル・クォーテッド・スタイル、平文スタイルを加えたものがフロー・ノードです。フロー・コレクションの要素になるのはフロー・ノードに限ります。ブロックであるリテラルとフォールデッド等はフロー・コレクションの要素に使えません。

ふるまい記述では、インデントを正しく解釈できているかどうかを確かめる目的で、s-separate を付け加えてあります。

enum {
    T_SEPARATE, T_S_L_COMMENT,
    T_SIGLE_QUOTED, T_DOUBLE_QUOTED, T_PLAIN, T_NOT_PLAIN,
    T_FLOW_SEQ, T_FLOW_MAP, T_FLOW_NODE,
    T_LITERAL, T_FOLDED};

//@<T_FLOW_SEQ のフィルタ@>=
            res = s_separate (s, spec[i].n, spec[i].ctx)
               && c_flow_sequence (s, spec[i].n, spec[i].ctx, node);
            ts.ok (res && node.dump () == spec[i].expected, spec[i].name);
            break;

//@<T_FLOW_MAP のフィルタ@>=
            res = s_separate (s, spec[i].n, spec[i].ctx)
               && c_flow_mapping (s, spec[i].n, spec[i].ctx, node);
            ts.ok (res && node.dump () == spec[i].expected, spec[i].name);
            break;

//@<T_FLOW_NODE のフィルタ@>=
            res = s_separate (s, spec[i].n, spec[i].ctx)
               && cns_flow_node (s, spec[i].n, spec[i].ctx, node);
            ts.ok (res && node.dump () == spec[i].expected, spec[i].name);
            break;

フロー・シーケンスは JSON の配列記述を受け付けます。より正確に言えば、インデントがゼロのとき、YAML のフロー・シーケンスは JSON と同じふるまいをします。

    {L"Flow Sequence as JSON",
     T_FLOW_SEQ, 0, wjson::CTX_FLOW_OUT,
     LR"(["one", "two", "three", "four"])" L"\n",
     //expected
     LR"(["one", "two", "three", "four"])"},

インデントがゼロでないときは、フロー・シーケンスの中でもインデント・レベルの字下げが必要です。ただし、インデントさえしておけば、それ以上の字下げは自由です。

    {L"Flow Sequence as indented JSON",
     T_FLOW_SEQ, 2, wjson::CTX_FLOW_OUT,
     LR"(  [)" L"\n"
     LR"(  "one",)" L"\n"
     LR"(    "two", "three",)" L"\n"
     LR"(  "four")" L"\n"
     LR"(  ])" L"\n",
     //expected
     LR"(["one", "two", "three", "four"])"},

JSON とは異なり最後の要素の後のコンマがあってもかまいません。コンマは前後の空白の影響を受けず、平文スタイルであってもコンマは常に要素の区切りになります。

    {L"Example 7.13. Flow Sequence Entries (modified)",
     T_FLOW_SEQ, 0, wjson::CTX_FLOW_OUT,
     LR"([[ one, two, ], [three ,four,five]])" L"\n",
     //expected
     LR"([["one", "two"], ["three", "four", "five"]])"},

これまた JSON とは異なり、フロー・シーケンス中のスカラーは一行で完結する必要がなく、複数行に渡っていてもかまいません。入れ子のフロー・コレクションも当然、複数行は可能です。

    {L"Example 7.14. Flow Sequence Entries",
     T_FLOW_SEQ, 2, wjson::CTX_FLOW_OUT,
     LR"(  [)" L"\n"
     LR"(    "double)" L"\n"
     LR"(     quoted", 'single)" L"\n"
     LR"(               quoted')" L"\n"
     LR"(    ,plain)" L"\n"
     LR"(    text, [ )" L"\n"
     LR"(      nested ],)" L"\n"
     LR"(  ])" L"\n",
     //expected
     LR"(["double quoted", "single quoted", "plain text", ["nested"]])"},

フロー・マッピングJSON のオブジェクト記述を受け付けます。フロー・シーケンス同様、インデントがゼロのときに JSON と同じふるまいをします。

    {L"Flow Mappings as JSON",
     T_FLOW_MAP, 0, wjson::CTX_FLOW_OUT,
     LR"({"one": "two", "three": "four"})" L"\n",
     //expected
     LR"({"one": "two", "three": "four"})"},

インデントがゼロでないときの字下げも、フロー・シーケンスと同様です。

    {L"Flow Mappings as indented JSON",
     T_FLOW_MAP, 2, wjson::CTX_FLOW_OUT,
     LR"(  {)" L"\n"
     LR"(  "one":)" L"\n"
     LR"(    "two",)" L"\n"
     LR"(  "three": "four")" L"\n"
     LR"(  })" L"\n",
     //expected
     LR"({"one": "two", "three": "four"})"},

フロー・マッピングでも最後のコンマを追加してかまいません。平文スタイルではフロー・マッピングでも、コロンに空白が続いているときに限ってキーと値の区切りになります。一方、コンマは前後に空白がなくても、区切りになります。

    {L"Example 7.15. Flow Mappings (modified)",
     T_FLOW_MAP, 2, wjson::CTX_FLOW_OUT,
     LR"(  {  1: { one : two , three: four ,})" L"\n"
     LR"(    ,2: {five: six,seven : eight}})",
     //expected
     LR"({"1": {"one": "two", "three": "four"}, )"
      LR"("2": {"five": "six", "seven": "eight"}})"},

JSON より構文が緩くなっており、キーか値の一方を省略することができます。キーを省略すると空文字のキーに、値を省略すると null の値にします。値を省略するときはコロンごと省略してもかまいません。なお、キーかコロンのいずれかは必須です。キー・コロン・値をすべて省略してコンマを2つ連続させると構文エラーにします。

    {L"Example 7.17 Flow Mapping Separate Values",
     T_FLOW_MAP, 0, wjson::CTX_FLOW_OUT,
     LR"({)" L"\n"
     LR"(unquoted : "separate",)" L"\n"
     LR"(http://foo.com,)" L"\n"
     LR"(omitted value:,)" L"\n"
     LR"(: omitted key,)" L"\n"
     LR"(})" L"\n",
     //expected
     LR"({"": "omitted key", )"
      LR"("http:\/\/foo.com": null, )"
      LR"("omitted value": null, )"
      LR"("unquoted": "separate"})"},

細かな話ですが、平文スタイルのキーの区切りにするにはコロンの後に空白か改行が必要ですが、ダブル・クォーテッド・スタイルとシングル・クォーテッド・スタイルではコロンの後の空白を省略してもかまいません。これは JSON 互換にするための構文なのだそうです。

    {L"Example 7.18 Flow Mapping Adjacent Values",
     T_FLOW_MAP, 0, wjson::CTX_FLOW_OUT,
     LR"({)" L"\n"
     LR"("adjacent":value,)" L"\n"
     LR"("readable": value,)" L"\n"
     LR"("empty":)" L"\n"
     LR"(})" L"\n",
     //expected
     LR"({"adjacent": "value", "empty": null, "readable": "value"})"},

フロー・シーケンスの要素とフロー・マッピングの値にすることができるフロー・ノードには、2 種類のコレクションと 3 種類のスタイルのスカラーがなれます。このサブセットではプロパティを削ったので、フロー・ノードになれるのは、この 5 種類だけです。

    {L"Example 7.23 Flow Content (modified)",
     T_FLOW_NODE, 0, wjson::CTX_FLOW_OUT,
     LR"([[ a, b ], { a: b }, "a", 'b', c])" L"\n",
     //expected
     LR"([["a", "b"], {"a": "b"}, "a", "b", "c"])"},

フロー・ノードの実装では、マッピングのキーにできるスカラーを別の関数に分けてあります。なお、c_quoted は c_single_quoted と c_double_quoted を一つにまとめた関数です。

namespace wjson {

static bool ns_flow_scalar (derivs_t& s0, int const n, int const ctx, json& value)
{
    return ns_plain (s0, n, ctx, value) || c_quoted (s0, n, ctx, value);
}

static bool ns_flow_node (derivs_t& s0, int const n, int const ctx, json& value)
{
    return ns_flow_scalar (s0, n, ctx, value)
        || c_flow_sequence (s0, n, ctx, value)
        || c_flow_mapping (s0, n, ctx, value);
}

}//namespace wjson

フロー・シーケンスは ns_flow_node で解釈できた要素をベクタに取り込んでいきます。

namespace wjson {

static bool c_flow_sequence (derivs_t& s0, int const n, int const ctx0, json& value)
{
    derivs_t s = s0;
    if (! s.scan (L"["))
        return s0.fail ();
    value = json (json::array {});
    s_separate (s, n, ctx0);
    int const ctx = (CTX_BLOCK_KEY == ctx0 || CTX_FLOW_KEY == ctx0 ) ? CTX_FLOW_KEY
                  : CTX_FLOW_IN;
    for (;;) {
        json item;
        if (! ns_flow_node (s, n, ctx, item))
            break;
        value.as<json::array> ().push_back (item);
        s_separate (s, n, ctx);
        if (! s.scan (L","))
            break;
        s_separate (s, n, ctx);
    }
    if (! s.scan (L"]"))
        return s0.fail ();
    return s0.match (s);
}

}//namespace wjson

フロー・マッピングはキー、コロン、値を順に解釈して、キーと値のペアをマップに付け加えていきます。キーかコロンの一方を省略することができ、キーがあるときは、コロンも値も省略可能で、適切な省略がおこなわれているかをチェックするために got 変数を使っています。

namespace wjson {

static bool c_flow_mapping (derivs_t& s0, int const n, int const ctx0, json& value)
{
    derivs_t s = s0;
    if (! s.scan (L"{"))
        return s0.fail ();
    value = json (json::object {});
    s_separate (s, n, ctx0);
    int const ctx = (CTX_BLOCK_KEY == ctx0 || CTX_FLOW_KEY == ctx0 ) ? CTX_FLOW_KEY
                  : CTX_FLOW_IN;
    for (;;) {
        json key (L"");
        json item;
        int got = 0;
        derivs_t la = s;
        if (ns_flow_scalar (s, n, ctx, key)) {
            s_separate (s, n, ctx);
            ++got;
        }
        if (s.scan (L":")) {
            s_separate (s, n, ctx);
            if (! s.lookahead (L",") && ! s.lookahead (L"}")
                    && ! ns_flow_node (s, n, ctx, item))
                break;
            s_separate (s, n,ctx);
            ++got;
        }
        if (got == 0)
            break;
        value.as<object> ()[key.as<std::wstring> ()] = item;
        if (! s.scan (L","))
            break;
        s_separate (s, n, ctx);
    }
    if (! s.scan (L"}"))
        return s0.fail ();
    return s0.match (s);
}

}//namespace wjson