对于我自己的小解析器框架,我试图定义(类似)以下函数:
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;
}
非常感谢任何帮助。
答案 0 :(得分: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>());
在创建向量时,将整个流读取到向量中。
答案 1 :(得分: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();
// ...
};
这应该会给你一个想法。它没有实现,但这很简单,应该很容易重做。此外,真正的代码有许多方便的功能,如读取分隔字符串的读取函数,如果它是几个给定关键字之一,则使用字符串,读取字符串并将其转换为每个模板参数给定的类型,以及类似的东西。
这个想法是一个函数将错误索引设置为其起始位置,保存解析状态,并尝试解析,直到它成功或遇到死胡同。在后一种情况下,它只会抛出异常。这会破坏堆栈上的parse_state
对象,将状态回滚到一个函数,该函数可以捕获异常并尝试其他东西,或输出错误(这是get_error_string()
进来的地方。)
如果你想要一个非常快速的解析器,这个策略可能是错误的,但是流也经常变慢。 OTOH,我最后一次使用这样的东西,我制作了一个XPath解析器,它运行在一个专有的DOM上,用于表示3D渲染器中的场景。并且不是 XPath解析器从那些试图获得更高帧速率的人那里获得了所有热量。 :)
答案 2 :(得分:2)
放回角色很棘手。有些流支持unget()
和putback(somechar)
,但无法保证您可以取消多少个字符(如果有)。
更可靠的方法是将字符读入缓冲区并解析,或者存储在第一次解析尝试中读取的字符,并在第二次解析时使用该缓冲区。
答案 3 :(得分:1)
您可以使用streambuf
流成员做一些有趣的事情。特别是,您可以直接访问缓冲区的指针。
但是,您无法保证缓冲区的大小。