2015-08-11 2 views
2

У меня есть вход в JSon типа формыBoost.Spirit: объединение preparsing с ключевыми словами синтаксического анализа и Nabialek трюк

{ 
    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_1, int_2a и int_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_ здесь просматривает таблицу символов для имени типа и устанавливает локальную переменную данные правило к указателю найденного правила.

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

К сожалению, я понятия не имею, как реализовать его или вообще можно.

Я включил упрощенный парсер JSON, найденный в stackoverflow, в мою демонстрационную версию для удобства.

Вопрос: Возможно ли вообще с духом? Если да, как это можно сделать?

PS. Полный демо-источник:

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

ответ

1

Дух не может заглянуть в будущее.

Трюк Nabialek служит для разных целей альтернативных парсеров.

Итак, концептуально то, что вы описываете, не может быть выполнено: вы не можете переключать парсеры на основе будущего типа.

Уловка Nabialek просто не соответствует сценарию. Вам понадобится обобщенный тип данных JSON и функция пост-обработки для создания фактического носа AST после того, как будут известны все необходимые данные.

В прошлом я опубликовал полноразмерные грамматики JSON и использовал их лично в своем собственном проекте.

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

Если вы заботитесь и имеете терпение, я могу попробовать свои силы в этом слое, когда я рядом с компьютером с некоторым Интернетом.

+0

Я очень забочусь и терплю. Заранее спасибо. К настоящему времени я изучаю возможность написания пользовательских терминальных парсеров для этой цели, что-то вроде «lazy_or_json (lazy_parser, pointer_to_the_range)». К сожалению, он выглядит чрезвычайно сложным. Но я думаю, все еще возможно. –

+0

Причина, по которой я ограничена статическими парсерами для поля «значение», - это тот факт, что реальные структуры за «значением» - это не только один или два целых числа, но скорее являются огромными объектами, и у меня много разных «типов». Я мог разбирать JSON в AST, а затем динамически преобразовывать AST в свои структуры данных. Однако такой код будет загружен коммутаторами и ветвями и будет подвержен ошибкам и будет трудно поддерживать. –

+0

Это меня не убеждает. Несмотря на то, что трюк Набиалека одинаково нагружен ветвями. И вы собирались добавить условные обозначения – sehe

 Смежные вопросы

  • Нет связанных вопросов^_^