I have to parse a netlist which looks like this:
*comment line 1
V1 N001 N002 10
R1 N001 N002 24.9
*comment line 2
R2 N002 N003 20
V2 N002 N003 5
This is how my structure looks like: (The comment lines need to be in a separate structure in case this part needs to be expanded later)
struct CommentLine {
boost::optional<std::string> comment_line;
};
struct ElementStatement {
boost::optional<std::string> label;
boost::optional<std::string> node1;
boost::optional<std::string> node2;
boost::optional<double> value;
};
struct SpiceNetlist {
std::vector<CommentLine> comment_lines;
std::vector<ElementStatement> elements;
};
This is what I come up with using code suggested from a previous question:
BOOST_FUSION_ADAPT_STRUCT(SpiceNetlist, comment_lines, elements)
BOOST_FUSION_ADAPT_STRUCT(CommentLine, comment_line)
BOOST_FUSION_ADAPT_STRUCT(ElementStatement, element_label, element_node1, element_node2, element_value)
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)[comment_lines >> (statements || comment_lines)];
comment_lines = comment_line % qi::eol;
comment_line = qi::lit('*') >> *(qi::graph) >> qi::space >> *(qi::graph) >>
qi::space >> qi::graph;
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_)(comment_lines)(comment_line)(statements) (statement)(label)(node1)(node2)(value))
}
private:
qi::rule<It, SpiceNetlist()> spice_netlist_;
using Skipper = qi::blank_type;
Skipper skipper_;
qi::rule<It, std::vector<CommentLine>, Skipper> comment_lines;
qi::rule<It, std::string()> comment_line;
qi::rule<It, ElementStatements(), Skipper> statements_;
qi::rule<It, ElementStatement(), Skipper> statement_;
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(){
for (const auto &[comment] : netlist.comment_lines) {
std::cout << "Comment: " << comment << std::endl;
}
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;
}
}
The parsing is successful until the second '*comment li' and the attributes are also being wrongly filled. Is the problem with my grammar for spice_netlist or comment_line or am I defining the rule with the Skipper wrong? I have also tried spice_netlist = qi::skip(qi::blank)[comment_lines >> *(statements | comment_lines)]; but it generates build errors
The structure doesn't match the input example. It loses the grouping of comment-lines with elementstatements.
Secondly, it really looks like you don't know what skippers do, or what you want:
I'd expect a simple syntax like:
Suggested Fix
I'd suggest to make comment just another statement, like they appear in the example. Don't make the string optional, because you can never have a comment line without a string. A line with a single
*will be an empty string:Now you can adjust the grammar likewise:
Now, already the output is close:
Live On Coliru
The Empty Trailing Element
This is caused by
element_having only optional parts.q = -a >> -b >> -c >> -dwill always match a zero-length input, so*qwill infinitely match andq % eolwill match empty lines too. Fix that by making at least one part non-optional:What you probably expect is that empty lines have no effect:
Demo
Live On Coliru
Printing