c++data-structuresboost-spirit-qininjanetlist

Parsing struct into struct using boost spirit


I am trying to parse this netlist from a .net file:

V1 N001 N002 10
R1 N001 N002 24.9

I have the following structs:

struct ElementStatement {
  boost::optional<std::string> element_label;
  boost::optional<std::string> element_node1;
  boost::optional<std::string> element_node2;
  boost::optional<double> element_value;
};

struct SpiceNetlist {

  std::vector<ElementStatement> element_statements;
//to be expanded
};

This is my code in the .cpp file to parse and print out the parsed variables:

BOOST_FUSION_ADAPT_STRUCT(ElementStatement, element_label, element_node1,
                      element_node2, element_value)

BOOST_FUSION_ADAPT_STRUCT(SpiceNetlist, element_statements)

namespace qi = boost::spirit::qi;

class SpiceGrammar
    : public boost::spirit::qi::grammar<std::string::const_iterator,
                                    SpiceNetlist()> {

public:
  using Iterator = std::string::const_iterator;
  SpiceGrammar() : SpiceGrammar::base_type(spice_netlist) {

    spice_netlist %=
        element_statements >> *(element_statements);

    element_statements %=
        element_label >> element_node1 >> element_node2 >> element_value >>
        qi::eol; 

    element_label %= (qi::char_ >> qi::char_) >> qi::space;
    element_node1 %=
        (qi::char_ >> qi::char_ >> qi::char_ >> qi::char_) >> qi::space;

    element_node2 %=
        qi::char_ >> qi::char_ >> qi::char_ >> qi::char_ >> qi::space;

    element_value %= qi::double_;

    BOOST_SPIRIT_DEBUG_NODE(spice_netlist);
    BOOST_SPIRIT_DEBUG_NODE(element_statements);
    BOOST_SPIRIT_DEBUG_NODE(element_label);
    BOOST_SPIRIT_DEBUG_NODE(element_node1);
    BOOST_SPIRIT_DEBUG_NODE(element_node2);
    BOOST_SPIRIT_DEBUG_NODE(element_value);
  }
  qi::rule<Iterator, SpiceNetlist()> spice_netlist;
  qi::rule<Iterator, ElementStatement> element_statements;
  qi::rule<Iterator, boost::optional<std::string>()> element_label;
  qi::rule<Iterator, boost::optional<std::string>()> element_node1;
   qi::rule<Iterator, boost::optional<std::string>()> element_node2;
   qi::rule<Iterator, boost::optional<double>()> element_value;
 };

 NetlistLoader::NetlistLoader() = default;

 SpiceNetlist NetlistLoader::parse_netlist_from_string(
     const std::string &netlist_string) const {

   SpiceNetlist netlist;
   std::cout << "Trying to parse:\n" << netlist_string << "\n";

   std::cout << "Size of netlist_string: " << netlist_string.size() << std::endl;
   auto iter = netlist_string.begin();
   auto last = netlist_string.end();

   std::cout << "Characters to go " << std::distance(iter, last) << std::endl;

   bool success = qi::parse(iter, last, SpiceGrammar(), netlist);

   std::cout << "Parsed: " << success << "\n";
   std::cout << "Characters to go " << std::distance(iter, last) << std::endl
             << std::endl;

   if (success) {
     std::cout << "Parsed netlist content:" << std::endl;

     for (std::size_t i = 0; i < netlist.element_statements.size(); ++i) {
       const auto &statement = netlist.element_statements[i];

       std::cout << "Element Label: ";
       if (statement.element_label) {
         std::cout << *statement.element_label;
       } else {
         std::cout << "Not specified";
       }
       std::cout << "\n ";

       std::cout << "Element Node1: ";
       if (statement.element_node1) {
         std::cout << *statement.element_node1;
       } else {
         std::cout << "Not specified";
       }
       std::cout << "\n";

       std::cout << "Element Node2: ";
       if (statement.element_node2) {
         std::cout << *statement.element_node2;
       } else {
         std::cout << "Not specified";
       }
       std::cout << "\n";

       std::cout << "Element Value: ";
       if (statement.element_value) {
         std::cout << *statement.element_value;
       } else {
         std::cout << "Not specified";
       }
       std::cout << std::endl;
       std::cout << std::endl;
     }
   } else {
     std::cout << "Failed to parse netlist.\n";
   }
   return netlist;
 }

I am able to sucessfully parse all the characters but I am only getting the first line i.e netlist.element_statement[0] as output.

Parsed netlist content
Element Label: V1 
Element Node1: N001 
Element Node2: N002 
Element Value: 10

I have tried modifying the rule for element_statements to qi::rule<Iterator, std::vector()> element_statements; but it generates build errors.

Where am I going wrong?


Solution

  • You are dealing with whitespace manually, but never deal with new-line characters.

    I suggest to leave the whitespace ignoring to a skipper, and specify the new-line separator (assuming it is required):

    spice_netlist = statement_ % qi::eol;
    

    Note that p >> *p is equivalent to just +p. See docs for operator%.

    Using a qi::blank skipper you can simplify all the rules. I'd suggest replacing the blanket qi::char_ with qi::graph which is probably what you intended.

    Also, don't bother with optional attributes, just make the expressions optional:

    statement_ = -label_ >> -node1_ >> -node2_ >> -value_;
    

    Demo

    Live On Coliru

    //#define BOOST_SPIRIT_DEBUG
    #include <boost/spirit/include/qi.hpp>
    #include <iomanip>
    struct ElementStatement {
        boost::optional<std::string> label, node1, node2;
        boost::optional<double>      value;
    };
    using ElementStatements = std::vector<ElementStatement>;
    struct SpiceNetlist { ElementStatements elements; }; // to be expanded
    
    BOOST_FUSION_ADAPT_STRUCT(ElementStatement, label, node1, node2, value)
    BOOST_FUSION_ADAPT_STRUCT(SpiceNetlist, elements)
    
    namespace qi = boost::spirit::qi;
    
    template <typename It>
    class SpiceGrammar : public qi::grammar<It, SpiceNetlist()> {
      public:
        SpiceGrammar() : SpiceGrammar::base_type(spice_netlist_) {
            spice_netlist_ = qi::skip(qi::blank)[statements_];
            statements_    = statement_ % qi::eol;
            statement_     = -label_ >> -node1_ >> -node2_ >> -value_;
            label_         = qi::graph >> qi::graph;
            node1_         = qi::graph >> qi::graph >> qi::graph >> qi::graph;
            node2_         = qi::graph >> qi::graph >> qi::graph >> qi::graph;
            value_         = qi::double_;
    
            BOOST_SPIRIT_DEBUG_NODES((spice_netlist_)(statements_)(statement_)(label_)(node1_)(node2_)(value_))
        }
    
      private:
        qi::rule<It, SpiceNetlist()> spice_netlist_;
    
        using Skipper = qi::blank_type;
        Skipper                                    skipper_;
        qi::rule<It, ElementStatements(), Skipper> statements_;
        qi::rule<It, ElementStatement(), Skipper>  statement_;
        // lexemes
        qi::rule<It, std::string()> label_, node1_, node2_;
        qi::rule<It, double()>      value_;
    };
    
    SpiceNetlist parse_netlist_from_string(std::string_view input) {
        using It = std::string_view::const_iterator;
        static SpiceGrammar<It> const g;
    
        SpiceNetlist netlist;
    
        It f = input.begin(), l = input.end();
        bool success = qi::parse(f, l, g, netlist);
    
        std::cout << "Parsing: " << quoted(input) << " -> "
                  << "Parse " << (success ? "SUCCESS" : "FAILED") << "\n"
                  << "Remaining: " << quoted(std::string_view(f, l)) << std::endl;
    
        return netlist;
    }
    
    int main() {
        auto netlist = parse_netlist_from_string(R"(V1 N001 N002 10
                R1 N001 N002 24.9)");
    
        for (auto const& [label, node1, node2, value] : netlist.elements) {
            std::cout                                                                              //
                << "Element Label: " << label.value_or("Not specified") << "\n"                    //
                << "        Node1: " << node1.value_or("Not specified") << "\n"                    //
                << "        Node2: " << node2.value_or("Not specified") << "\n"                    //
                << "        Value: " << (value ? std::to_string(*value) : "Not specified") << "\n" //
                << std::endl;
        }
    }
    

    Printing

    Parsing: "V1 N001 N002 10
                R1 N001 N002 24.9" -> Parse SUCCESS
    Remaining: ""
    Element Label: V1
            Node1: N001
            Node2: N002
            Value: 10.000000
    
    Element Label: R1
            Node1: N001
            Node2: N002
            Value: 24.900000
    

    Summary/Notes

    There were many simplifications in my listing.

    There's one subtler point I want to point out: the rules marked "lexemes" do not use the declared skipper. This is important to avoid matching things across whitespace: Boost spirit skipper issues