5

私自身の小さなパーサー フレームワークのために、次の関数を定義しようとしています。

template <class T>
// with operator>>( std::istream&, T& )
void tryParse( std::istream& is, T& tgt )
{
    is >> tgt /* , *BUT* store every character that is consumed by this operation
    in some string. If afterwards, is.fail() (which should indicate a parsing
    error for now), put all the characters read back into the 'is' stream so that
    we can try a different parser. */
}

次に、次のようなものを書くことができます: (おそらく最良の例ではありません)

/* grammar: MyData     = <IntTriple> | <DoublePair>
            DoublePair = <double> <double>
            IntTriple  = <int> <int> <int> */
class MyData
{ public:
    union { DoublePair dp; IntTriple it; } data;
    bool isDoublePair;
};

istream& operator>>( istream& is, MyData& md )
{
    /* If I used just "is >> md.data.it" here instead, the
       operator>>( ..., IntTriple ) might consume two ints, then hit an
       unexpected character, and fail, making it impossible to read these two
       numbers as doubles in the "else" branch below. */
    tryParse( is, md.data.it );
    if ( !is.fail() )
        md.isDoublePair = false;
    else
    {
        md.isDoublePair = true;
        is.clear();
        is >> md.data.dp;
    }
    return is;
}

どんな助けでも大歓迎です。

4

4 に答える 4

3

これは、ストリームが意図しているものではありません。解析するデータをバッファに読み込んでから、そのバッファを(できればイテレータ範囲として)解析する関数に渡す必要があります。これは次のようになります。

template <class T, class U>
bool tryParse( U & begin, U & end, T & target ) {
    // return true if parse was successful, false otherwise
}

からバッファに読み込むには、 :istreamを使用できます。istream_iterator

 std::vector< char > buffer(std::istream_iterator<char>(is), std::istream_iterator<char>());

これにより、ストリームの作成時にストリーム全体がベクターに読み込まれます。

于 2010-09-23T12:29:00.217 に答える
3

残念ながら、ストリームには非常に最小限で初歩的なプットバック サポートしかありません。

前回これが必要だったときは、ストリームをラップする独自のリーダー クラスを作成しましたが、元に戻すバッファがあり、そのバッファが空の場合にのみストリームから読み取りました。これらには状態を取得する方法があり、状態をコミットしたり、以前の状態にロールバックしたりできました。
状態クラスのデストラクタのデフォルト アクションはロールバックでした。これにより、エラー処理をあまり考えずに解析を進めることができました。例外は、別の文法規則が試行された時点までパーサーの状態を単純にロールバックするためです。(これはバックトラッキングと呼ばれていると思います。) スケッチは次のとおりです。

class parse_buffer {
    friend class parse_state;
public:
    typedef std::string::size_type index_type;

    parse_buffer(std::istream& str);

    index_type get_current_index() const;
    void set_current_index(index_type) const;

    std::string get_next_string(bool skip_ws = true) const;
    char get_next_char(bool skip_ws = true);
    char peek_next_char(bool skip_ws = true); 

    std::string get_error_string() const; // returns string starting at error idx
    index_type get_error_index() const;
    void set_error_index(index_type);

    bool eof() const;

    // ...
};

class parse_state {
public:
    parse_state(parse_buffer&);
    ~parse_state();

    void commit();
    void rollback();

    // ...
};

これはあなたにアイデアを与えるはずです。実装はありませんが、それは簡単で、やり直しも簡単なはずです。また、実際のコードには、区切られた文字列を読み取る関数の読み取り、指定されたキーワードの 1 つである場合は文字列を消費する関数、文字列を読み取ってテンプレート パラメーターごとに指定された型に変換する関数など、多くの便利な関数がありました。

関数はエラー インデックスを開始位置に設定し、解析状態を保存し、成功するか行き止まりになるまで解析を試みるという考えでした。後者の場合、例外がスローされるだけです。これにより、スタック上のオブジェクトが破棄parse_stateされ、例外をキャッチして別のことを試みるか、エラーを出力する可能性のある関数まで状態がロールバックされます (これが発生する場所get_error_string()です)。

本当に高速なパーサーが必要な場合、この戦略は間違っている可能性がありますが、ストリームも遅くなることがよくあります。OTOH、前回このようなものを使用したとき、3D レンダラーでシーンを表すために使用される独自の DOM で動作する XPath パーサーを作成しました。そして、より高いフレーム レートを取得しようとする人々からすべての熱を集めたのは、XPath パーサーではありませんでした。:)

于 2010-09-23T12:38:43.453 に答える
2

文字を戻すのは難しいです。一部のストリームは および をサポートunget()してputback(somechar)いますが、取得できる文字数 (存在する場合) の保証はありません。

より信頼性の高い方法は、文字をバッファーに読み取って解析するか、最初の解析試行で読み取った文字を保存し、2 回目の解析時にそのバッファーを使用することです。

于 2010-09-23T12:29:41.823 に答える
1

streambufストリーム メンバーを使用すると、いくつかの興味深いことができます。特に、バッファのポインタに直接アクセスできます。

ただし、バッファーのサイズは保証されません。

于 2010-09-23T12:49:42.163 に答える