Daniel
Daniel

Reputation: 35

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?

Upvotes: 1

Views: 113

Answers (1)

sehe
sehe

Reputation: 392911

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

Upvotes: 1

Related Questions