Boost.Spirit语法。属性和_val问题

时间:2013-11-24 02:47:34

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

我正在尝试创建一个可以读取相当简单语法的Boost :: Spirit语法类。

start   = roster;
roster  = *student;
student = int >> string;

代码的目标是根据正在解析的输入文件创建命令对象树。正在创建此语法的迭代器是给定的精神文件迭代器。

基本上,我遇到的麻烦就是移动并使用每个规则的合成属性。我需要根据这些数据创建一个对象树,并且创建所述对象的唯一函数需要在那时知道父对象。我正在使用命令模式来延迟创建,直到我解析了所有数据并且可以正确地构建树。到目前为止我实现这个的方式是我的命令都包含其他命令的向量。执行命令时,它只需要父对象,并相应地创建和附加子对象。然后,对象将执行它自己的向量中的每个命令,并将自身作为父对象传递。这样就可以创建我需要的树形结构。

问题:

我遇到的问题是如何在解析数据时构建命令,以及如何将它们加载到适当的向量中。到目前为止,我已尝试过3种不同的方式。

  1. 我尝试将每个规则的属性更改为std :: vector,并一次一个地解析属性作为命令。这个问题是它将向量嵌入到std :: vector>中。类型数据,我无法使用。

  2. 我尝试使用boost :: phoenix placehold _val作为正在创建的命令的代理。我为这个解决方案感到自豪,有点不高兴它不起作用。我为所有命令重载了+ =运算符,这样当A和B都是命令时,A + = B将B推入A的命令向量。 _val不是Command,所以编译器不喜欢这个。我似乎无法将任何东西变成一个更可行的状态。如果可能的话,这是最干净的解决方案,我希望能够工作。

  3. 当前表单中的代码让我尝试将操作绑定在一起。如果我有一个成员函数指针指向_val并传递它创建的命令它将推回它。再次,_val实际上并不是一个命令,因此无法解决问题。

  4. 我要发布这段代码,这是我写过的语法清理了一下,以及它被调用的地方。

    template <typename Iterator>
    struct roster_grammar : qi::grammar<Iterator, qi::space_type, T3_Command()>
    {   
    //roster_grammar constructor
    roster_grammar() : 
        roster_grammar::base_type(start_)
    {
        using qi::lit;
        using qi::int_;
        using qi::char_;
        using qi::lexeme;
    
        start_ = student[boost::bind(&T3_Command::add_command, qi::_val, _1)];
    
        //I removed the roster for the time being to simplify the grammar
        //it still containes my second solution that I detailed above. This 
        //would be my ideal solution if it could work this way.
        //roster = *(student[qi::_val += _1]);
    
        student = 
            qi::eps       [ boost::bind(&T3_Command::set_identity, qi::_val, "Student") ]
            >>
            int_age       [ boost::bind(&T3_Command::add_command, qi::_val, _1) ]
            >>
            string_name   [ boost::bind(&T3_Command::add_command, qi::_val, _1) ];
    
        int_age     =
            int_          [ boost::bind(&Command_Factory::create_int_comm, &cmd_creator, "Age", _1) ];
        string_name =
            string_p      [ boost::bind(&Command_Factory::create_string_comm, &cmd_creator, "Name", _1) ];
    
        //The string parser. Returns type std::string
        string_p %= +qi::alnum;
    }
    
    qi::rule<Iterator,   qi::space_type, T3_Model_Command()>  roster;
    qi::rule<Iterator,   qi::space_type, T3_Atom_Command()>   student;
    qi::rule<Iterator,   qi::space_type, T3_Int_Command()>    int_age;
    qi::rule<Iterator,   qi::space_type, T3_String_Command()> string_name;
    qi::rule<Iterator,   qi::space_type, T3_Command()>        start_;
    qi::rule<Iterator,   std::string()>  string_p;
    Command_Factory cmd_creator;
    };
    

    这就是语法实例化和使用的方式。

    typedef boost::spirit::istream_iterator iter_type;
    typedef roster_grammar<iter_type> student_p;
    student_p my_parser;
    
    //open the target file and wrap istream into the iterator
    std::ifstream in = std::ifstream(path);
    in.unsetf(std::ios::skipws);//Disable Whitespace Skipping
    iter_type begin(in);
    iter_type end;
    
    using boost::spirit::qi::space;
    using boost::spirit::qi::phrase_parse;
    bool r = phrase_parse(begin, end, my_parser, space);
    

    这么长的故事,我有一个语法,我想建立命令(调用T3_Command)。命令有一个std:Vector数据成员,它在树中包含其下面的其他命令。

    我需要的是一种创建Command作为语义动作的简洁方法,我需要能够将其加载到其他命令的向量中(通过属性或直接函数调用)。命令的类型应该在创建时指定(将定义它所生成的树节点的类型),并且某些命令具有数据值(int,string或float,它们各自命令中的所有命名值)。

    或者如果可能有更好的方法来构建树,我会乐于接受建议。 非常感谢您提供任何帮助!

    修改 我会试着更清楚我正在努力解决的原始问题。感谢您的耐心等待。

    鉴于语法(或实际上的任何语法),我希望能够解析它并根据解析器中的语义动作创建命令树。 所以使用我的示例语法和输入

    "23 Bryan 45 Tyler 4 Stephen"
    

    我希望最终的树能够产生以下数据结构。

    Command with type = "Roster" holding 3 "Student" type commands.
    Command with type = "Student" each holding an Int_Command and a String_Command
    Int_Command holds the stored integer and String_Command the stored string.
    
    E.g.
    
    r1 - Roster - [s1][s2][s3]
    s1 - Student - [int 23][string Bryan]
    s2 - Student - [int 45][string Tyler]
    s3 - Student - [int 4][string Stephen]
    

    这是我编写的命令的当前结构(实现非常简单)。

    class T3_Command
    {
        public:
            T3_Command(void);
            T3_Command(const std::string &type);
            ~T3_Command(void);
    
            //Executes this command and all subsequent commands in the command vector.
            void Execute(/*const Folder_in parent AND const Model_in parent*/);
    
            //Pushes the passed T3_Command into the Command Vector
            //@param comm - The command to be pushed.
            void add_command(const T3_Command &comm);
    
            //Sets the Identity of the command.
            //@param ID - the new identity to be set.
            void set_identity(std::string &ID);
    
        private:    
    
            const std::string ident;
            std::vector <T3_Command> command_vec;
            T3_Command& operator+=(const T3_Command& rhs);
    };
    
    #pragma once
    #include "T3_command.h"
    class T3_Int_Command :
            public T3_Command
    {
    public:
            T3_Int_Command();
            T3_Int_Command(const std::string &type, const int val);
            ~T3_Int_Command(void);
            void Execute();
            void setValue(int val);
    private:
            int value;
    };
    

    所以我遇到的问题是我希望能够创建一个代表解析树的各种命令的数据结构,因为精神会通过它解析。

1 个答案:

答案 0 :(得分:2)

针对已修改的问题进行了更新

虽然还有很多信息缺失(请参阅我的[新评论]),至少现在你展示了一些输入和输出:)

所以,不用多说,让我解释一下:

  • 你仍然想要解析(int,string)对,但每行

    • 使用qi :: blank_type作为队长
    • 执行roster % eol解析名册行
    • 我的示例解析为一个Rosters向量(每行一个)
    • 每个名册包含可变数量的学生:

      start   = roster %  eol;
      roster  = +student;
      student = int_ >> string_p;
      
        

      注意:规则#1 除非您确实需要

      ,否则不要使解析器复杂化
  • 你想要输出单个元素(“命令”?!?) - 我假设这部分不平凡的部分是同一Student可能出现在几个部分的部分名册?

    • 通过定义学生的总排序:

      bool operator<(Student const& other) const {
          return boost::tie(i,s) < boost::tie(other.i, other.s);
      }
      

      您可以将一组独特的学生存储在例如: std::set<Student>

  • 也许生成'变量名'(我的意思是r1s1s2 ...)也是任务的一部分。因此,为了与每个学生建立一个独特的“变量名称”,我创建了一个双向的学生地图(解析后,请参阅规则#1 :不要复杂化解析器,除非绝对必要):

    boost::bimap<std::string, Student> student_vars;
    auto generate_id = [&] () { return "s" + std::to_string(student_vars.size()+1); };
    
    for(Roster const& r: data)
        for(Student const& s: r.students)
            student_vars.insert({generate_id(), s});
    

这就是我能想到的一切。我使用c ++ 11并在这里大量提升以节省代码行,但是在没有c ++ 11 / boost的情况下编写这个也是相当微不足道的。的 C++03 version online now

以下示例输入:

ParsedT3Data const data = parseData(
        "23 Bryan 45 Tyler 4 Stephen\n"
        "7 Mary 45 Tyler 8 Stephane\n"
        "23 Bryan 8 Stephane");

结果(见 Live On Coliru ):

parse success
s1 - Student - [int 23][string Bryan]
s2 - Student - [int 45][string Tyler]
s3 - Student - [int 4][string Stephen]
s4 - Student - [int 7][string Mary]
s5 - Student - [int 8][string Stephane]
r1 [s1][s2][s3]
r2 [s4][s2][s5]
r3 [s1][s5]

完整代码:

#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/tuple/tuple_comparison.hpp>
#include <boost/bimap.hpp>

namespace qi    = boost::spirit::qi;

struct Student
{
    int i;
    std::string s;

    bool operator<(Student const& other) const {
        return boost::tie(i,s) < boost::tie(other.i, other.s);
    }
    friend std::ostream& operator<<(std::ostream& os, Student const& o) {
        return os << "Student - [int " << o.i << "][string " << o.s << "]";
    }
};

struct Roster
{
    std::vector<Student> students;
};

BOOST_FUSION_ADAPT_STRUCT(Student, (int, i)(std::string, s))
BOOST_FUSION_ADAPT_STRUCT(Roster, (std::vector<Student>, students))

typedef std::vector<Roster> ParsedT3Data;

template <typename Iterator>
struct roster_grammar : qi::grammar<Iterator, ParsedT3Data(), qi::blank_type>
{   
    roster_grammar() : 
        roster_grammar::base_type(start)
    {
        using namespace qi;

        start   = roster %  eol;
        roster  = eps    >> +student; // known workaround
        student = int_   >> string_p;

        string_p = lexeme[+(graph)];

        BOOST_SPIRIT_DEBUG_NODES((start)(roster)(student)(string_p))
    }

    qi::rule <Iterator, ParsedT3Data(), qi::blank_type> start;
    qi::rule <Iterator, Roster(),       qi::blank_type> roster;
    qi::rule <Iterator, Student(),      qi::blank_type> student;
    qi::rule <Iterator, std::string()>  string_p;
};

ParsedT3Data parseData(std::string const& demoData)
{
    typedef boost::spirit::istream_iterator iter_type;
    typedef roster_grammar<iter_type> student_p;
    student_p my_parser;

    //open the target file and wrap istream into the iterator
    std::istringstream iss(demoData);
    iss.unsetf(std::ios::skipws);//Disable Whitespace Skipping

    iter_type begin(iss), end;
    ParsedT3Data result;
    bool r = phrase_parse(begin, end, my_parser, qi::blank, result);

    if (r)
        std::cout << "parse (partial) success\n";
    else      
        std::cerr << "parse failed: '" << std::string(begin,end) << "'\n";
    if (begin!=end) 
        std::cerr << "trailing unparsed: '" << std::string(begin,end) << "'\n";

    if (!r) 
        throw "TODO error handling";

    return result;
}

int main()
{
    ParsedT3Data const data = parseData(
            "23 Bryan 45 Tyler 4 Stephen\n"
            "7 Mary 45 Tyler 8 Stephane\n"
            "23 Bryan 8 Stephane");

    // now produce that list of stuff :)
    boost::bimap<std::string, Student> student_vars;
    auto generate_id = [&] () { return "s" + std::to_string(student_vars.size()+1); };

    for(Roster const& r: data)
        for(Student const& s: r.students)
            student_vars.insert({generate_id(), s});

    for(auto const& s: student_vars.left)
        std::cout << s.first << " - " << s.second << "\n";

    int r_id = 1;
    for(Roster const& r: data)
    {
        std::cout << "r" << (r_id++) << " ";
        for(Student const& s: r.students)
            std::cout << "[" << student_vars.right.at(s) << "]";
        std::cout << "\n";
    }
}

OLD ANSWER

我会在等待更多信息的同时回复个别观点:

1。 “这个问题是它将向量嵌入到std :: vector&gt;类型数据中,我无法使用”

这里的解决方案是

  • 升压::矢量&lt;&GT;它允许在实例化时不完整的元素类型(Boost Containers有几个其他漂亮的属性,请阅读它们!)
  • boost::variantrecursive_wrapper<>,因此您确实可以创建逻辑树。我在显示此方法的标记中有很多答案(例如表达式树)。

2。从语义动作中调用工厂方法

我有一些小提示:

  • 您可以使用qi::_1qi::_2 ...来引用复合属性的元素
  • 你应该更喜欢在凤凰演员中使用phoenix::bind(语义动作是凤凰演员)
  • 您可以指定qi::_pass表示解析器失败

这是语法的简化版本,它显示了这些语法的实际效果。我实际上没有构建一棵树,因为你没有描述任何所需的行为。相反,我只是在向树添加节点时打印调试行。

查看 Live on Coliru

#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <fstream>

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

struct T3_Command
{
    bool add_command(int i, std::string const& s) 
    {
        std::cout << "adding command [" << i << ", " << s << "]\n";
        return i != 42; // just to show how you can do input validation
    }
};

template <typename Iterator>
struct roster_grammar : qi::grammar<Iterator, T3_Command(), qi::space_type>
{   
    roster_grammar() : 
        roster_grammar::base_type(start_)
    {
        start_   = *(qi::int_ >> string_p) 
            [qi::_pass = phx::bind(&T3_Command::add_command, qi::_val, qi::_1, qi::_2)];

        string_p = qi::lexeme[+(qi::graph)];
    }

    qi::rule <Iterator, T3_Command(), qi::space_type> start_;
    qi::rule <Iterator, std::string()> string_p;
};

int main()
{
    typedef boost::spirit::istream_iterator iter_type;
    typedef roster_grammar<iter_type> student_p;
    student_p my_parser;

    //open the target file and wrap istream into the iterator
    std::ifstream in("input.txt");
    in.unsetf(std::ios::skipws);//Disable Whitespace Skipping
    iter_type begin(in);
    iter_type end;

    using boost::spirit::qi::space;
    using boost::spirit::qi::phrase_parse;
    bool r = phrase_parse(begin, end, my_parser, space);

    if (r)
        std::cout << "parse (partial) success\n";
    else      
        std::cerr << "parse failed: '" << std::string(begin,end) << "'\n";
    if (begin!=end) 
        std::cerr << "trailing unparsed: '" << std::string(begin,end) << "'\n";

    return r?0:255;
}

输入:

1 klaas-jan
2 doeke-jan
3 jan-herbert
4 taeke-jan
42 oops-invalid-number
5 not-parsed

输出:

adding command [1, klaas-jan]
adding command [2, doeke-jan]
adding command [3, jan-herbert]
adding command [4, taeke-jan]
adding command [42, oops-invalid-number]
parse success
trailing unparsed: '42 oops-invalid-number
5 not-parsed
'