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?
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 foroperator%
.
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_;
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
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