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

ローダのエントリ・ポイント wjson::load_yaml 関数は、3 つの引数をとります。入力文字列、出力の JSON ノード、最後は省略可能で入力文字列の pos 番目からロードを開始します。ロード開始位置のデフォルトはゼロで入力文字列の先頭からとりかかります。これがゼロよりも大きいときは、YAML ストリームの 2 番目以降をそこから読み取り開始するものとします。2 番目以降のドキュメントはドキュメント・エンド・マーカーかディレクティブ・エンド・マーカーで始まらなければなりません。そのため、もしも、文字列に埋め込んである途中にある YAML ドキュメントを load_yaml するときは、substr して入力文字列を別に切り出して pos をゼロにしないとエラーになります。

namespace wjson {

std::wstring::size_type load_yaml (std::wstring const& input, json& value, std::size_t pos = 0);

}//namespace wjson

load_yaml はドキュメントを一つ読み込んで、次のドキュメントの pos を戻り値で返します。pos が入力文字列の長さと同じときは、YAML ストリームの終わりに達したことを表します。npos のときは、構文エラーがあり、正しくドキュメントを読み取れなかったことを表します。

ドキュメントを 2 つもつ YAML ストリームを読むときは、得られた pos を次回に渡して load_yaml を 2 回繰り返します。

//@<Example 2.7. でテストする@>=
void t_exam2_7 (test::simple& ts)
{
    std::wstring name (L"Example 2.7. Two Documents in a Stream");
    std::wstring input (LR"__(
# Ranking of 1998 home runs
---
- Mark McGwire
- Sammy Sosa
- Ken Griffey

# Team ranking
---
- Chicago Cubs
- St Louis Cardinals
)__"
    );
    std::wstring expected0 (LR"(["Mark McGwire", "Sammy Sosa", "Ken Griffey"])");
    std::wstring expected1 (LR"(["Chicago Cubs", "St Louis Cardinals"])");
    std::wstring expected2 (LR"(null)");
    wjson::json node0;
    wjson::json node1;
    wjson::json node2;
    std::wstring::size_type pos0 = load_yaml (input, node0);
    std::wstring got0 = node0.dump ();
    ts.ok (pos0 != std::wstring::npos && got0 == expected0, name + L" doc0");
    std::wstring::size_type pos1 = load_yaml (input, node1, pos0);
    std::wstring got1 = node1.dump ();
    ts.ok (pos1 != std::wstring::npos && got1 == expected1, name + L" doc1");
    std::wstring::size_type pos2 = load_yaml (input, node2, pos1);
    std::wstring got2 = node2.dump ();
    ts.ok (pos2 == input.size () && got2 == expected2, name + L" end stream");
}

ドキュメント・エンド・マーカーを load_yaml は最初に読み飛ばします。その後に入力文字列の末尾に達したかどうかをチェックします。最後に読み飛ばさずに先読みするようにしてあるのは、2番目以後のドキュメントでパーセントで始まる記号をディレクティブとして扱うか、平文スカラーの一部として扱うかの解釈がこのマーカーの有無で変化するためです。

//@<Example 2.8. でテストする@>=
void t_exam2_8 (test::simple& ts)
{
    std::wstring name (L"Example 2.8. Play by Play Feed");
    std::wstring input (LR"__(
---
time: 20:03:20
player: Sammy Sosa
action: strike (miss)
...
---
time: 20:03:47
player: Sammy Sosa
action: grand slam
...
)__"
    );
    std::wstring expected0 (
        LR"_({"action": "strike (miss)", "player": "Sammy Sosa", "time": "20:03:20"})_");
    std::wstring expected1 (
        LR"_({"action": "grand slam", "player": "Sammy Sosa", "time": "20:03:47"})_");
    std::wstring expected2 (LR"(null)");
    wjson::json node0;
    wjson::json node1;
    wjson::json node2;
    std::wstring::size_type j0 = load_yaml (input, node0);
    std::wstring got0 = node0.dump ();
    ts.ok (j0 != std::wstring::npos && got0 == expected0, name + L" doc0");
    std::wstring::size_type j1 = load_yaml (input, node1, j0);
    std::wstring got1 = node1.dump ();
    ts.ok (j1 != std::wstring::npos && got1 == expected1, name + L" doc1");
    std::wstring::size_type j2 = load_yaml (input, node2, j1);
    std::wstring got2 = node2.dump ();
    ts.ok (j2 == input.size () && got2 == expected2, name + L" end stream");
}

上の 2 つのテストを動かす main 関数は次のようにします。

#include <string>
#include <locale>
#include "taptests.hpp"
#include "jyaml.hpp"

void t_exam2_7 (test::simple& ts);
void t_exam2_8 (test::simple& ts);

int main ()
{
    std::locale::global (std::locale (""));
    std::wcout.imbue (std::locale (""));
    test::simple ts (6);
    t_exam2_7 (ts);
    t_exam2_8 (ts);
    return ts.done_testing ();
}

//@<Example 2.7. でテストする@>
//@<Example 2.8. でテストする@>

load_yaml は、pos の位置にスキャン・ポインタをセットしてから、ストリームの末尾かどうかを調べて、そうでないときはドキュメントの構文解析をおこないます。

namespace wjson {

std::wstring::size_type load_yaml (std::wstring const& input, json& value, std::size_t pos)
{
    wjson::derivs_t s (input.cbegin (), input.cend ());
    s.advance (pos);
    int endok = l_endstream (s);
    if (endok < 0)
        return std::wstring::npos;
    if (endok == 0) {
        value = json ();
        return input.size ();
    }
    bool ok = l_document (s, value);
    return ok ? s.cend () - input.cbegin () : std::wstring::npos;
}

}//namespace wjson

ストリート末尾かどうかは、先読みをおこない、ドキュメント・エンド・マーカーの後が入力文字列末尾かどうかで判定しています。これが負を返すと構文エラー、ゼロを返すと末尾です。正を返すと次のドキュメントがある可能性があることを示します。

namespace wjson {

static int l_endstream (derivs_t s)
{
    while (s.scan (L"^%.%.%.%b")) {
        if (! s_l_comment (s))
            return -1;
    }
    return s.check_eos () ? 0 : 1;
}

}//namespace wjson