【问题标题】:"carbon-copy" a c++ istream?“复制”一个c ++ istream?
【发布时间】:2010-09-23 12:20:23
【问题描述】:

对于我自己的小解析器框架,我正在尝试定义(类似于)以下函数:

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;
}

非常感谢任何帮助。

【问题讨论】:

  • 流不是合适的工具,因为它们缺乏适当的回退。当设计像这样的简单内联解析器时(否则,试试boost::spirit),解析函数应该真的需要一对迭代器。回滚变得很容易(只需在回溯解析器之前保存迭代器值)。

标签: c++ parsing istream


【解决方案1】:

不幸的是,流只有非常少和基本的回退支持。

最后一次我需要这个时,我编写了自己的阅读器类,它包装了一个流,但有一个缓冲区可以将内容放回,并且只有在该缓冲区为空时才从流中读取。这些有获取状态的方法,您可以提交状态或回滚到更早的状态。
状态类的析构函数中的默认操作是回滚,这样您就可以提前解析而无需过多考虑错误处理,因为异常只会将解析器的状态回滚到尝试不同语法规则的点。 (我认为这称为回溯。)这是一个草图:

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,上次我使用这样的东西时,我制作了一个在专有 DOM 上运行的 XPath 解析器,用于在 3D 渲染器中表示场景。 不是 XPath 解析器从那些试图获得更高帧速率的人那里得到了所有的热量。 :)

【讨论】:

  • 这听起来很有趣。你还有代码吗?它是开源的吗/我可以看看吗?
  • 哦,真好!那么,parse_state::rollback() 只是在它的 parse_buffer 上调用 set_current_index(index_on_my_creation) 吗?并且 commit() 确实 - 嗯 - 什么都没有,将 parse_buffer 的索引留在原处?啊,但是 commit() 可以告诉 parse_buffer 我们不会在当前位置之前回滚(),因此忘记该位置之前的所有内容是安全的(至少对于这个 parse_state 而言)......是的,我想我开始了去理解。我要试试这个,非常感谢!
  • @rainmaker:是的,你明白了。提交所做的另一件事是将错误索引设置为当前位置,以便以后的解析错误会产生当前索引作为错误文本的开始位置。我依稀记得这有它的角落和缝隙(parse_state 不会也必须存储旧的错误索引吗?),但是几年前我使用了这个方案,所以,如果没有旧代码,我需要再做一次,我不会有比上述更多的东西来开始。 :)
【解决方案2】:

这不是流的用途。您应该将要解析的数据读入缓冲区,然后将该缓冲区(最好作为迭代器范围)交给解析它的函数。这可能看起来像这样:

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

这会在创建向量时将整个流读入向量中。

【讨论】:

    【解决方案3】:

    把角色放回去很棘手。某些流支持unget()putback(somechar),但无法保证您可以取消多少个字符(如果有的话)。

    更可靠的方法是将字符读入缓冲区并对其进行解析,或者将在第一次解析尝试中读取的字符存储并在第二次解析时使用该缓冲区。

    【讨论】:

      【解决方案4】:

      您可以与streambuf 流成员一起做一些有趣的事情。特别是,您可以直接访问缓冲区的指针。

      但是,您无法保证缓冲区的大小。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-05-19
        • 2013-10-30
        • 1970-01-01
        • 2023-03-24
        • 1970-01-01
        • 1970-01-01
        • 2021-12-24
        • 1970-01-01
        相关资源
        最近更新 更多