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

YAML の 5 種類のスタイルのスカラーが揃ったので、コレクションにとりかかる前にセパレータを記述します。セパレータは大きく分けて、空白の s-separate と、改行の s-l-comments の 2 つがあります。前者は文脈によっては後者を含みます。両者とも、単なる空白・改行だけでなく、コメントと空行を読み飛ばす働きをします。s-l-comments は非空白文字を含む行の行頭にスキャン・ポインタを動かす働きを担当し、行指向の YAML の字句解析では重要な存在です。

これらのふるまい記述は、読み飛ばした後に残る、これから読むべき部分が得られているかどうかを確認することにします。

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

//@<T_SEPARATE のフィルタ@>=
        case T_SEPARATE:
            res = wjson::s_separate (s, spec[i].n, spec[i].ctx);
            ts.ok (res && std::wstring (s.cbegin(), input.cend ()) == spec[i].expected, spec[i].name);
            break;

//@<T_S_L_COMMENT のフィルタ@>=
        case T_S_L_COMMENT:
            res = wjson::s_l_comment (s);
            ts.ok (res && std::wstring (s.cbegin(), input.cend ()) == spec[i].expected, spec[i].name);
            break;

s-separate は、行の途中から空白を読み飛ばします。同じ行にコメント・改行・空白以外の非空白文字が存在するときは単に空白を読み飛ばすだけです。

    {L"separate",
     T_SEPARATE, 2, wjson::CTX_BLOCK_IN,
     L"   : value\n",
     //expected
     L": value\n"},

空白とコメントを読み飛ばして行末に達したときは、続く空白とコメントだけからなる空行を読み飛ばして、指定されたインデントの後にある非空白文字まで読み飛ばします。

    {L"Example 6.12. Separation Spaces",
     T_SEPARATE, 2, wjson::CTX_BLOCK_IN,
     L"\n# Statistics:\n  hr:  # Home runs\n     65\n",
     //expected
     L"hr:  # Home runs\n     65\n"},

    {L"Example 6.12. Separation Spaces",
     T_SEPARATE, 2, wjson::CTX_BLOCK_IN,
     L"  # Home runs\n     65\n",
     //expected
     L"65\n"},

s-l-comments は、s-separate の後者の場合に似ますが、非空白文字がある行の行頭で読み飛ばしを停止する点が異なります。

    {L"Example 6.11. Multi-Line Comments",
     T_S_L_COMMENT, 0, wjson::CTX_BLOCK_IN,
     L"    # Comment\n"
     L"    # lines\n"
     L"    \n"
     L"   value\n",
     //expected
     L"   value\n"},

もう一つ、s-l-comments 特有のふるまいに、最初から非空白文字がある行の行頭にスキャン・ポインタがあるときは、何もせずにマッチングを成功させます。これは、主に YAML の先頭からノードを読み始めるときに使います。

    {L"without comment",
     T_S_L_COMMENT, 0, wjson::CTX_BLOCK_IN,
     L"value\n",
     //expected
     L"value\n"},

s-separate の実装は、s-l-comments で非空白行の先頭へスキャン・ポインタを動かしてから、インデントより深い非空白文字位置まで読み飛ばします。それに失敗したときは、同じ行内で空白だけを読み飛ばします。

s-l-comments の実装は、s-b-comment で行末空白列かコメントを読み飛ばしてその行の改行を読み飛ばして次の行に移ります。特例として、次の行に移れないときであっても、最初から行頭にスキャン・ポインタがあるときは良しとします。その後は、空白列かコメントのいずれかだけを含む行を読み飛ばします。

namespace wjson {

static bool s_separate (derivs_t& s0, int const n, int const ctx)
{
    if (CTX_BLOCK_KEY == ctx || CTX_FLOW_KEY == ctx)
        return s_separate_in_line (s0);
    int m = n < 0 ? 0 : n;
    derivs_t s = s0;
    if (s_l_comment (s) && s.scan_indent (m, m) && s.scan (L"%s{0,*}"))
        return s0.match (s);
    return s_separate_in_line (s0);
}

static bool s_separate_in_line (derivs_t& s0)
{
    return s0.scan (L"%s{1,*}") || s0.check_bol ();
}

static bool s_l_comment (derivs_t& s0)
{
    derivs_t s = s0;
    if (! (s_b_comment (s) || s.check_bol ()))
        return s0.fail ();
    while (! s.check_eos ())
        if (! (s.scan (L"%s{0,*}$\n{0,1}") || s.scan (L"%s{0,*}#.{0,*}$\n{0,1}")))
            break;
    return s0.match (s);
}

}//namespace wjson