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

平文スタイルのおかげで YAML を手打ちする手間が減ります。平文スタイルはシングル・クォーテッドの両側のシングル・クォートを省いたようなものですが、区切りのインジケータがないので、どこまでが平文スタイルなのかを、先読みを使って判断する必要があります。マッピングのキーに使われるか、そうでないか、フロー・コレクションの中で使われるかどうかで、解釈が変化します。

enum {T_SINGLE_QUOTED, T_DOUBLE_QUOTED, T_PLAIN, T_NOT_PLAIN, T_LITERAL};

//@<T_PLAIN, T_NOT_PLAIN フィルタ@>=
        case T_PLAIN:
            res = wjson::ns_plain (s, spec[i].n, spec[i].ctx, node);
            ts.ok (res && node.as<std::wstring> () == spec[i].expected, spec[i].name);
            break;
        case T_NOT_PLAIN:
            res = wjson::ns_plain (s, spec[i].n, spec[i].ctx, node);
            ts.ok (! res, spec[i].name);
            break;

平文スタイルはインジケータ以外の印字可能な文字から始まります。

    {L"Plain First Character",
     T_PLAIN, 0, wjson::CTX_FLOW_OUT,
     L"abc",
     //expected
     L"abc"},

    {L"Plain not started by indicators",
     T_NOT_PLAIN, 0, wjson::CTX_FLOW_OUT,
     L",abc",
     //expected
     L",abc"},

ただし、インジケータの中のハイフン・コロン・疑問符の 3 つは後続文字によりインジケータかどうかの解釈が変化し、インジケータにならないときは、始まりの文字として利用できます。インジケータになるのは後続文字が空白・改行のときと、フロー・コレクション中でカンマなどのフロー・インジケータが後続するときです。

    {L"Plain First Character",
     T_PLAIN, 0, wjson::CTX_FLOW_OUT,
     L":,abc",
     //expected
     L":,abc"},

    {L"Plain First Character fail",
     T_NOT_PLAIN, 0, wjson::CTX_FLOW_IN,
     L":,abc",
     //expected
     L":,abc"},

    {L"Plain First Character fail",
     T_NOT_PLAIN, 0, wjson::CTX_FLOW_OUT,
     L": abc",
     //expected
     L": abc"},

文脈に依存しない平文スタイルの終わりは、行末空白列の手前、コメント前空白列の手前、改行の手前といった、s-b-comment で解釈できる行末部分を除いた箇所になります。

    {L"Plain Characters break",
     T_PLAIN, 0, wjson::CTX_FLOW_OUT,
     L"foo bar  \n",
     //expected
     L"foo bar"},

    {L"Plain Characters comment",
     T_PLAIN, 0, wjson::CTX_FLOW_OUT,
     L"foo bar   # comment\n",
     //expected
     L"foo bar"},

    {L"Plain Characters comment",
     T_PLAIN, 0, wjson::CTX_FLOW_OUT,
     L"foo bar\n",
     //expected
     L"foo bar"},

さらに、文脈に依存しない終わりがもう一通りあり、マッピングのキーと値のセパレータであるコロンとの間の空白列を除いた手前までが平文スタイルになります。このとき、コロンがセパレータになるためには、コロンの継続文字が空白・タブ・改行に限ります。コロンの前の空白はあってもなくても良く、空白があるときは平文スタイルから除きます。

    {L"Plain Characters space colon space",
     T_PLAIN, 0, wjson::CTX_FLOW_OUT,
     L"foo bar   : value",
     //expected
     L"foo bar"},

    {L"Plain Characters colon",
     T_PLAIN, 0, wjson::CTX_FLOW_OUT,
     L"foo bar : value  \n",
     //expected
     L"foo bar"},

    {L"Plain Characters colon",
     T_PLAIN, 0, wjson::CTX_FLOW_OUT,
     L"foo bar:\n  value  \n",
     //expected
     L"foo bar"},

文脈に依存する平文スタイルの終わりは、フロー・コレクション中かどうかによって変わります。フロー・コレクション中 (CTX_FLOW_IN) では、コンマとシーケンス開始終了インジケータ、マッピング開始終了インジケータと、それの直前の空白列を除いた箇所までが平文スタイルになります。コロンも直後にフロー・インジケータがあるときは平文の一部になるかどうか文脈に依存します。

    {L"Example 7.10. Plain Characters",
     T_PLAIN, 0, wjson::CTX_FLOW_OUT,
     L"Up, up, and away!\n",
     //expected
     L"Up, up, and away!"},

    {L"Example 7.10. Plain Characters",
     T_PLAIN, 0, wjson::CTX_FLOW_IN,
     L"Up, up, and away!\n",
     //expected
     L"Up"},

    {L"Example 7.10. Plain Characters",
     T_PLAIN, 0, wjson::CTX_FLOW_IN,
     L"Up  , up, and away!\n",
     //expected
     L"Up"},

    {L"Plain Characters colon flow-indicator",
     T_PLAIN, 0, wjson::CTX_FLOW_OUT,
     L"foo bar:, and:,  \n",
     //expected
     L"foo bar:, and:,"},

    {L"Plain Characters colon flow-indicator",
     T_PLAIN, 0, wjson::CTX_FLOW_IN,
     L"foo bar:, and:,  \n",
     //expected
     L"foo bar"},

シャープがコメント・インジケータ、コロンがマッピング区切りインジケータになるのは、それぞれ直前にブランクがあるとき、直後にブランクがあるときに限ります。この条件を満たさないときは、平文の一部にとりこみます。

    {L"Example 7.10. Plain Characters",
     T_PLAIN, 0, wjson::CTX_FLOW_OUT,
     L"http://example.com/foo#bar\n",
     //expected
     L"http://example.com/foo#bar"},

平文スタイルの行の畳み込みは、シングル・クォートと同じふるまいをします。行頭・行末の空白列を削り、空白でない行の間の改行の個数が 1 個のときは空白に、2 個以上のときは一つ少ない改行を文字列に追加します。

    {L"Example 7.12. Plain Lines",
     T_PLAIN, 0, wjson::CTX_FLOW_IN,
     L"1st non-empty\n"
     L"\n"
     L" 2nd non-empty \n"
     L"\t3rd non-empty\n",
     //expected
     L"1st non-empty\n2nd non-empty 3rd non-empty"},

実装では、まず最初の文字が平文スタイルに使えるものかどうかを調べます。その後は、空白列の後にどのような文字がくるかによって、平文スタイルの終わりかどうかを判定していきます。そのために、先読み用の s1 に s をコピーして、s1 を進めていきます。平文の終わりではないときは、読み飛ばしてしまっていた空白列を文字列に取り込んでから s1 を sに反映します。

namespace wjson {

static bool ns_plain (derivs_t& s0, int const n, int const ctx, json& value)
{
    std::wstring lit;
    derivs_t s = s0;
    if (s.check_eos () || c_forbidden (s))
        return s0.fail ();
    int ch1 = s.peek ();
    if (! s.check (L"%P"))
        return s0.fail ();
    if ('-' == ch1 || '?' == ch1 || ':' == ch1) {
        if (! s.check (L".%S"))
            return s0.fail ();
        if (CTX_FLOW_IN == ctx || CTX_FLOW_KEY == ctx) {
            if (s.check (L".%F"))
                return s0.fail ();
        }
    }
    lit.push_back (s.get ());
    for (;;) {
        derivs_t s1 = s;
        if (s1.scan (L"%s{1,*}")) {
            if ('#' == s1.peek ())
                break;
        }
        if (s1.check_eos ())
            break;
        int nbreak = -1;
        if ('\n' == s1.peek ()) {
            if (CTX_BLOCK_KEY == ctx || CTX_FLOW_KEY == ctx)
                break;
            derivs_t la = s1;
            la.get ();
            if (c_forbidden (la))
                break;
            if (! s_flow_folded (s1, n, nbreak))
                break;
        }
        if (s1.lookahead (L":%b"))
            break;
        if (CTX_FLOW_IN == ctx || CTX_FLOW_KEY == ctx) {
            if (s1.lookahead (L":%F") || s1.check (L"%F"))
                break;
        }
        if (nbreak < 0 && s.cbegin () < s1.cend ())
            lit.append (s.cbegin (), s1.cend ());
        else if (nbreak == 0)
            lit.push_back (' ');
        else if (nbreak > 0)
            lit.append (nbreak, '\n');
        s.match (s1);
        lit.push_back (s.get ());
    }
    value = json (lit);
    return s0.match (s);
}

}//namespace wjson