"копирка" c ++ istream? - PullRequest
       22

"копирка" c ++ istream?

5 голосов
/ 23 сентября 2010

Для моего очень маленького каркаса парсера я пытаюсь определить (что-то вроде) следующую функцию:

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 ]

3 голосов
/ 23 сентября 2010

К сожалению, потоки имеют только очень минимальную и элементарную поддержку возврата.

В прошлый раз, когда я нуждался в этом, я писал свои собственные классы чтения, которые обертывали поток, но имели буфер для возврата вещей и чтения из потока, только когда этот буфер пуст. У них были способы получить состояние, и вы могли зафиксировать состояние или выполнить откат к более раннему состоянию.
Действие по умолчанию в деструкторе класса состояний состояло в откате, так что вы могли анализировать заранее, не задумываясь об обработке ошибок, поскольку исключение просто откатывало бы состояние синтаксического анализатора до точки, где пробовали другое правило грамматики. (Я думаю, что это называется возвращением.) Вот эскиз:

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();

    // ...
};

Это должно дать вам представление. Он не имеет реализации, но это было просто и должно быть легко переделано. Кроме того, в реальном коде было много удобных функций, таких как функции чтения, которые читают строку с разделителями, потребляют строку, если это было одно из нескольких заданных ключевых слов, читают строку и преобразуют ее в тип, заданный для каждого параметра шаблона, и тому подобное.

Идея заключалась в том, что функция установит индекс ошибки в свою исходную позицию, сохранит состояние анализа и попытается выполнить синтаксический анализ до тех пор, пока не преуспеет или не перейдет в тупик. В последнем случае это просто выкинет исключение. Это уничтожило бы parse_state объекты в стеке, откатив состояние до функции, которая могла бы перехватить исключение и либо попробовать что-то еще, либо вывести ошибку (в этом месте get_error_string().)

Если вам нужен действительно быстрый синтаксический анализатор, эта стратегия может быть неправильной, но тогда потоки часто тоже замедляются. OTOH, в последний раз, когда я использовал что-то подобное, я создал парсер XPath, который работает на проприетарном DOM, который используется для представления сцен в 3D-рендере. И это был , а не синтаксический анализатор XPath, который получил все тепло от парней, пытающихся получить более высокую частоту кадров. :)

3 голосов
/ 23 сентября 2010

Это не то, для чего предназначены потоки.Вы должны прочитать данные, которые вы хотите проанализировать, в буфер, а затем передать этот буфер (предпочтительно как диапазон итераторов) функциям, которые его анализируют.Это может выглядеть примерно так:

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>());

Это читает весь поток в вектор, когда он создан.

2 голосов
/ 23 сентября 2010

Задать символы обратно сложно. Некоторые потоки поддерживают unget() и putback(somechar), но нет никакой гарантии, сколько символов вы можете отключить (если есть).

Более надежным способом является чтение символов в буфер и их синтаксический анализ или сохранение прочитанных символов при первой попытке анализа и использование этого буфера при повторном анализе.

1 голос
/ 23 сентября 2010

Вы можете сделать некоторые интересные вещи с streambuf членами потока. В частности, у вас есть прямой доступ к указателям на буферы.

Однако у вас нет гарантии на размер буферов.

...