c++ - boost spirit : combining preparsing with keyword parser and with Nabialek trick

标签 c++ parsing boost boost-spirit boost-spirit-qi

我有类似 json 形式的输入

{
  type: "name",
  value: <json_value>,
}

类型名称可以是“int1”、“int2a”、“int2b”中的一个(当然,我简化了实际情况以提供最少的相关代码)。

该值始终符合 JSON 语法,但也取决于类型名称。可能的情况是:

type: int1 => expected value: <number>
type: int2a => expected value: [ <number>, <number> ]
type: int2b => expected value: [ <number>, <number> ]

我需要将输入解析为以下数据类型:

struct int_1  { int i1; };
struct int_2a { int i1, i2; };
struct int_2b { int i1, i2; };

using any_value = boost::variant<int_1, int_2a, int_2b>;

struct data { std::string type; any_value value; };

我将关键字解析器与 Nabialek 技巧相结合。我创建符号表并将指向 int_1int_2aint_2b 解析器的指针存储到其中:

using value_rule_type = qi::rule<It, any_value (), Skipper>;

qi::symbols<char, value_rule_type *> value_selector;

qi::rule<It, int_1  (), Skipper> int1_parser;
qi::rule<It, int_2a (), Skipper> int2a_parser;
qi::rule<It, int_2b (), Skipper> int2b_parser;

value_rule_type int1_rule, int2a_rule, int2b_rule;

int1_parser  =        int_                      ;
int2a_parser = '[' >> int_ >> ',' >> int_ >> ']';
int2b_parser = '[' >> int_ >> ',' >> int_ >> ']';

int1_rule  = int1_parser;
int2a_rule = int2a_parser;
int2b_rule = int2b_parser;

value_selector.add
  ( "\"int1\"",  &int1_rule  )
  ("\"int2a\"",  &int2a_rule )
  ("\"int2b\"",  &int2b_rule )
;

我使用关键字解析器来解析外部数据结构:

data_
  %= eps [ _a = px::val (nullptr) ]
  > '{' > (
      kwd ( lit ("\"type\"" ) ) [ ':' >> parsed_type_ (_a) >> ',' ]
    / kwd ( lit ("\"value\"") ) [ ':' >> value_ (_a)       >> ',' ]
  ) > '}'
;

此处的 parsed_type_ 规则在符号表中查找类型名称,并将 data 规则的局部变量设置为找到的规则指针。

parsed_type_ %=  raw[value_selector [ _r1  = _1 ]];

并且 value_ 规则具有 Nabialek 技巧的常用形式:

value_ = lazy (*_r1);

这个解析器工作得很好(live demo)...除了在类型名称之前传递值的情况:

{
  value: <json_value>,
  type: "name",
}

由于我们在存储的指向规则的指针中有 NULL,并且“类型”字段的解析器尚未运行,程序在解析“值”字段期间崩溃。

我想解决这个问题。如果值字段跟在类型名称字段之后,我希望应用当前的解析器逻辑(如现场演示)。

如果值在类型键之前,我想用 json 解析器预先解析值字段(只是为了找到该字段的结束边界)并以迭代器范围的形式将该字段存储到某个本地多变的。在解析器获得“类型”字段后,我想在存储范围内启动特定的解析器 - int1_rule、int2a_rule 或 int2b_rule。

所以,表达式

value_ = lazy (*_r1);

可能应该更改为:

if (_r1 == NULL) { 
  <parse json value>
  <store raw range into local variable> 
} else { 
  lazy (*_r1); 
}  

和表达

parsed_type_ %=  raw[value_selector [ _r1  = _1 ]];

应该扩展为:

if (has stored range) parse it with lazy (*_r1);  

不幸的是,我不知道如何实现它,或者根本不可能。

为了方便起见,我将在 stackoverflow 上找到的简化 JSON 解析器包含到我的现场演示中。

问题: spirit 是否可能?如果是,怎么办?

附言。完整的演示源:

#define BOOST_SPIRIT_USE_PHOENIX_V3

#include <boost/optional.hpp>
#include <boost/variant.hpp>
#include <boost/variant/recursive_variant.hpp>

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

#include <boost/spirit/repository/include/qi_kwd.hpp>
#include <boost/spirit/repository/include/qi_keywords.hpp>

#include <boost/fusion/adapted.hpp>
#include <boost/fusion/adapted/std_pair.hpp>

namespace spirit = ::boost::spirit;
namespace qi = spirit::qi;
namespace px = ::boost::phoenix;

namespace json {

struct null {
  constexpr bool operator== (null) const { return true; }
};

template<typename Ch, typename Tr>
std::basic_ostream<Ch, Tr>&
operator<< (std::basic_ostream<Ch, Tr>& os, null) { return os << "null"; }

using text = std::string;

using value = boost::make_recursive_variant<
    null
  , text                                        // string
  , double                                      // number
  , std::map<text, boost::recursive_variant_>   // object
  , std::vector<boost::recursive_variant_>      // array
  , bool
>::type;

using member = std::pair<text, value>;
using object = std::map<text, value>;
using array = std::vector<value>;

static auto const null_ = "null" >> qi::attr (null {});

static auto const bool_ =
  "true" >> qi::attr (true) | "false" >> qi::attr (false);

#if 0
static auto const text_   =
  '"' >> qi::raw [*('\\' >> qi::char_ | ~qi::char_('"'))] >> '"';
#endif

template <typename It, typename Skipper = qi::space_type>
struct grammar : qi::grammar<It, value (), Skipper>
{
  grammar () : grammar::base_type (value_)
  {
    using namespace qi;

    text_   = '"' >> qi::raw [*('\\' >> qi::char_ | ~qi::char_('"'))] >> '"';

    value_  = null_ | bool_ | text_ | double_ | object_ | array_;
    member_ = text_ >> ':' >> value_;
    object_ = '{' >> -(member_ % ',') >> '}';
    array_  = '[' >> -(value_ % ',') >> ']';

    BOOST_SPIRIT_DEBUG_NODES((value_)(member_)(object_)(array_))
  }

private:
  qi::rule<It, std::string ()> text_;
  qi::rule<It, json:: value (), Skipper> value_;
  qi::rule<It, json::member (), Skipper> member_;
  qi::rule<It, json::object (), Skipper> object_;
  qi::rule<It, json:: array (), Skipper> array_;
};

template <typename Range,
  typename It = typename boost::range_iterator<Range const>::type>
value parse(Range const& input)
{
  grammar<It> g;

  It first(boost::begin(input)), last(boost::end(input));
  value parsed;
  bool ok = qi::phrase_parse(first, last, g, qi::space, parsed);

  if (ok && (first == last))
    return parsed;

  throw std::runtime_error("Remaining unparsed: '" +
                           std::string(first, last) + "'");
}

} // namespace json

namespace mine {

struct int_1  { int_1 (int i) : i1 (i) {} int_1 () : i1 () {} int i1; };
struct int_2a { int i1, i2; };
struct int_2b { int i1, i2; };

using any_value = boost::variant<int_1, int_2a, int_2b>;

struct data { std::string type; any_value value; };

template <class C, class T> std::basic_ostream<C,T>&
operator<< (std::basic_ostream<C,T>& os, int_1 const& i)
{
  return os << "{int1:" << i.i1 << '}';
}

template <class C, class T> std::basic_ostream<C,T>&
operator<< (std::basic_ostream<C,T>& os, int_2a const& i)
{
  return os << "{int2a:" << i.i1 << ',' << i.i2 << '}';
}

template <class C, class T> std::basic_ostream<C,T>&
operator<< (std::basic_ostream<C,T>& os, int_2b const& i)
{
  return os << "{int2b:" << i.i1 << ',' << i.i2 << '}';
}

template <class C, class T> std::basic_ostream<C,T>&
operator<< (std::basic_ostream<C,T>& os, data const& d)
{
  return os << "{type=" << d.type << ",value=" << d.value << '}';
}

}

BOOST_FUSION_ADAPT_STRUCT(mine::int_1,  (int, i1) )
BOOST_FUSION_ADAPT_STRUCT(mine::int_2a, (int, i1) (int, i2) )
BOOST_FUSION_ADAPT_STRUCT(mine::int_2b, (int, i1) (int, i2) )
BOOST_FUSION_ADAPT_STRUCT(mine::data,(std::string,type)(mine::any_value,value))

namespace mine {

template <typename It, typename Skipper = qi::space_type>
struct grammar: qi::grammar<It, data (), Skipper>
{
  grammar () : grammar::base_type (start)
  {
    using namespace qi;
    using spirit::repository::qi::kwd;

    int1_parser  =        int_                      ;
    int2a_parser = '[' >> int_ >> ',' >> int_ >> ']';
    int2b_parser = '[' >> int_ >> ',' >> int_ >> ']';

    int1_rule  = int1_parser;
    int2a_rule = int2a_parser;
    int2b_rule = int2b_parser;

    value_selector.add
      ( "\"int1\"",  &int1_rule  )
      ("\"int2a\"",  &int2a_rule )
      ("\"int2b\"",  &int2b_rule )
    ;

    start = data_.alias ();

    parsed_type_ %=  raw[value_selector [ _r1  = _1 ]];
    value_ = lazy (*_r1);

    data_
      %= eps [ _a = px::val (nullptr) ]
      > '{' > (
          kwd ( lit ("\"type\"" ) ) [ ':' >> parsed_type_ (_a) >> ',' ]
        / kwd ( lit ("\"value\"") ) [ ':' >> value_ (_a)       >> ',' ]
      ) > '}'
     ;

    on_error<fail>(start,
                   px::ref(std::cout)
                   << "Error! Expecting "
                   << qi::_4
                   << " here: '"
                   << px::construct<std::string>(qi::_3, qi::_2)
                   << "'\n"
                   );

  }

private:
  using value_rule_type = qi::rule<It, any_value (), Skipper>;
  qi::rule<It, data (), Skipper> start;

  qi::rule<It, data (), qi::locals<value_rule_type *>, Skipper> data_;

  qi::symbols<char, value_rule_type *> value_selector;

  qi::rule<It, int_1  (), Skipper> int1_parser;
  qi::rule<It, int_2a (), Skipper> int2a_parser;
  qi::rule<It, int_2b (), Skipper> int2b_parser;

  value_rule_type int1_rule, int2a_rule, int2b_rule;

  qi::rule<It, std::string (value_rule_type *&)         > parsed_type_;
  qi::rule<It, any_value   (value_rule_type *&), Skipper> value_;
};

template <typename Range,
typename It = typename boost::range_iterator<Range const>::type>
data parse(Range const& input)
{
  grammar<It> g;

  It first(boost::begin(input)), last(boost::end(input));
  data parsed;
  bool ok = qi::phrase_parse(first, last, g, qi::space, parsed);

  if (ok && (first == last))
    return parsed;

  throw std::runtime_error("Remaining unparsed: '" +
                           std::string(first, last) + "'");
}

}

static std::string const sample1 = R"(
{
  "type": "int1",
  "value": 111,
})";

static std::string const sample2 = R"(
{
  "type": "int2a",
  "value": [ 111, 222 ],
})";

static std::string const sample3 = R"(
{
  "type": "int2b",
  "value": [ 111, 333 ],
})";

static std::string const sample4 = R"(
{
  "value": 111,
  "type": "int1",
})";

int main()
{
  auto mine = mine::parse(sample1); std::cout << mine << '\n';
       mine = mine::parse(sample2); std::cout << mine << '\n';
       mine = mine::parse(sample3); std::cout << mine << '\n';
       // mine = mine::parse(sample4); std::cout << mine << '\n';
  return 0;
}

最佳答案

spirit 无法展望 future 。

Nabialek 技巧服务于与变体替代解析器不同的目标。

因此,从概念上讲,您所描述的是无法完成的:您不能根据 future 的类型切换解析器。

Nabialek 把戏根本不适合这种情况。您需要的是一个通用的 JSON 数据类型和一个后处理函数,以便在所有必要数据已知后创建实际的 AST nose。

我过去发布过完整的 JSON 语法,并在我自己的项目中亲自使用它们。

您描述的解决方法可以解释为与此方法类似,但我的印象是您正在努力坚持当前的解析器结构。我会说您需要重构解析器(概念上),只有然后您可能会发现您仍然可以重用旧解析器的一些部分。

如果您关心并且有耐心,当我靠近一台可以上网的电脑时,我可以尝试一下它。

关于c++ - boost spirit : combining preparsing with keyword parser and with Nabialek trick,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31932432/

相关文章:

c++ - 转换元组类型

c++ - 使用C++生成问题-C++/CLI-C#项目(Borland和VS特定组件)

java - 线程 "main"org.pdfclown.util.parsers.ParseException : 'name' table does NOT exist 中的异常

Java XML 解析器 block (非常不寻常和奇怪!)

c++ - Boost asio 自定义 HTTP 服务器读取 HTTP post 请求

c++ - 使用 std::set 替代 BOOST_FOREACH?

c++ - Valgrind 和 QEMU - 无法检测内存泄漏

c++ - 如何限制缓存内存ubuntu?

python - 超越循环:高性能,大格式的数据文件解析

c++ - boost lexical_cast <int> 检查