My goal is to parse something by boost::spirit, create a class (and maybe put it into a container) by using semantic actions.
I manage to create a class using boost::spirit using the BOOST_FUSION_ADAPT_STRUCT macro.
//The struct
using stringvec = std::string;
struct CVar
{
public:
stringvec sVariable;
CVar(stringvec sVariable) : sVariable(sVariable) {}
CVar() {}
};
//The macro gluing everything
BOOST_FUSION_ADAPT_STRUCT(
::CVar,
(stringvec, sVariable)
)
//The core of the grammar
varName = qi::char_("a-z");
start = *varName;
qi::rule<Iterator, std::string()> varName;
qi::rule<Iterator, std::vector<CVar>() > start;
http://coliru.stacked-crooked.com/a/56dd8325f854a8c9
Actually, I would have liked to omit the vector surrounding the class, but I ran into problems http://coliru.stacked-crooked.com/a/3719cee9c7594254 I thought I managed to match a class as the root of the tree at some point. The error message looks, like the adapt struct really wants to call push_back at some point. And somehow, it makes sense, in case there is no class to store.
However, I would like to call the constructor by a semantic action.
start = (varName[qi::_val = boost::phoenix::construct<CVar>(qi::_1)]);
Coliru helped me a lot by printing a very direct error message (which I couldn't say about VSC++2022).\ error: no match for 'operator=' (operand types are 'std::vector' and 'CVar') http://coliru.stacked-crooked.com/a/7305e8483ee83b22 \
Furthermore, I would like to not use too much boost::bind or phoenix constructs. A plain lambda would be nice. I'm aware, that the [] operator already provides a kind of boost::lambda. However, I'm not sure how to use the placeholders qi::_val and qi::_1. Should I catch them in a lambda expression to execute something like qi::_val.push_back(Cvar(qi::_1))?
Some directions I looked into:
I would have expected something like this to work:
[&]() { qi::_val.push_back(CVar((std::string)qi::_1)) }
But how can I actually get it to work?
If it wasn't for my desire not to use boost and for the push_back, I could use boost::phoenix::construct( qi::_1), couldn't I?
I think this similar question is very important How to build a synthesized argument from a C++11 lambda semantic action in boost spirit? There I am still searching how to use the context to get access to qi::_val and qi::_1. It looks like I am on to something, but I already tried a lot and I might have to search for a tutorial how to read the Visual Studio errors or I should consider to switch the compiler.
P.S.: In VSC++2022 I also get two warnings which say something "boost::function_base::functor is not initialized and boost::detail::local_counted_base::count_type doesn't have range limits. Rank enum class before enum. How do I trigger them?
There's a lot that confuses me. You state "I want XYZ" without any reasoning why that would be preferable. Furthermore, you state the same for different approaches, so I don't know which one you prefer (and why).
The example code define
using stringvec = std::string;
This is confusing, because stringvec
as a name suggests vector<string>
, not string
? Right now it looks more like CVar
is "like a string" and your attribute is vector<CVar>
, i.e. like a vector of string, just not std::string
.
All in all I can give you the following hints:
in general, avoid semantic actions. They're heavy on the compiler, leaky abstractions, opt out of attribute compatibility¹, creates atomicity problems under backtracking (see Boost Spirit: "Semantic actions are evil"?)
secondly, if you use semantic actions, realize that the raw synthesized attribute for more parser expressions are Fusion sequences/containers.
std::string
use qi::as_string[]
. If you don't use semantic actions, indeed this kind of attribute compatibility/transformation is automatic¹.repeat(1)[p]
the constructors work like you show with phx::construct<>
except for all the downsides of relying on semantic actions
Side observation: did you notice I reduced parser/AST friction in this previous answer by replacing
std::string
withchar
?
Q. Actually, I would have liked to omit the vector surrounding the class, but I ran into problems http://coliru.stacked-crooked.com/a/3719cee9c7594254 I thought I managed to match a class as the root of the tree at some point. The error message looks, like the adapt struct really wants to call push_back at some point. And somehow, it makes sense, in case there is no class to store.
Simplified using as_string
: Live On Coliru
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <iostream>
namespace qi = boost::spirit::qi;
using Iterator = std::string::const_iterator;
using stringval = std::string;
struct CVar { stringval sVariable; };
BOOST_FUSION_ADAPT_STRUCT(CVar, sVariable)
struct TestGrammar : qi::grammar<Iterator, CVar()> {
TestGrammar() : TestGrammar::base_type(start) {
start = qi::as_string[qi::char_("a-z")];
}
private:
qi::rule<Iterator, CVar() > start;
};
void do_test(std::string const& input) {
CVar output;
static const TestGrammar p;
auto f = input.begin(), l = input.end();
qi::parse(f, l, p, output);
std::cout << std::quoted(input) << " -> " << std::quoted(output.sVariable) << "\n";
}
Using repeat(1)
: Live On Coliru
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <iostream>
namespace qi = boost::spirit::qi;
using Iterator = std::string::const_iterator;
using stringval = std::string;
struct CVar { stringval sVariable; };
BOOST_FUSION_ADAPT_STRUCT(CVar, sVariable)
struct TestGrammar : qi::grammar<Iterator, CVar()> {
TestGrammar() : TestGrammar::base_type(start) {
cvar = qi::repeat(1)[qi::char_("a-z")];
start = cvar;
}
private:
qi::rule<Iterator, CVar()> start;
qi::rule<Iterator, std::string()> cvar;
};
void do_test(std::string const& input) {
CVar output;
static const TestGrammar p;
auto f = input.begin(), l = input.end();
qi::parse(f, l, p, output);
std::cout << std::quoted(input) << " -> " << std::quoted(output.sVariable) << "\n";
}
Without using ADAPT_STRUCT: minimal change vs: simplified
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
#include <iostream>
namespace qi = boost::spirit::qi;
using Iterator = std::string::const_iterator;
using stringval = std::string;
struct CVar {
CVar(std::string v = {}) : sVariable(std::move(v)) {}
stringval sVariable;
};
struct TestGrammar : qi::grammar<Iterator, CVar()> {
TestGrammar() : TestGrammar::base_type(start) {
start = qi::as_string[qi::lower];
}
private:
qi::rule<Iterator, CVar()> start;
};
Using Semantic Actions (not recommended): 4 modes live
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <iomanip>
#include <iostream>
namespace qi = boost::spirit::qi;
namespace px = boost::phoenix;
using Iterator = std::string::const_iterator;
using stringval = std::string;
struct CVar {
CVar(std::string v = {}) : sVariable(std::move(v)) {}
stringval sVariable;
};
enum mode {
AS_STRING_CONSTRUCT = 1,
DIRECT_ASSIGN = 2,
USING_ACTOR = 3,
TRANSPARENT_CXX14_LAMBDA = 4,
};
struct TestGrammar : qi::grammar<Iterator, CVar()> {
TestGrammar(mode m) : TestGrammar::base_type(start) {
switch (m) {
case AS_STRING_CONSTRUCT: {
using namespace qi::labels;
start = qi::as_string[qi::lower][_val = px::construct<CVar>(_1)];
break;
}
case DIRECT_ASSIGN: {
// or directly
using namespace qi::labels;
start = qi::lower[_val = px::construct<std::string>(1ull, _1)];
break;
}
case USING_ACTOR: {
// or... indeed
using namespace qi::labels;
px::function as_cvar = [](char var) -> CVar { return {{var}}; };
start = qi::lower[_val = as_cvar(_1)];
break;
}
case TRANSPARENT_CXX14_LAMBDA: {
// or even more bespoke: (this doesn't require qi::labels or phoenix.hpp)
auto propagate = [](auto& attr, auto& ctx) {
at_c<0>(ctx.attributes) = {{attr}};
};
start = qi::lower[propagate];
break;
}
}
}
private:
qi::rule<Iterator, CVar()> start;
};
void do_test(std::string const& input, mode m) {
CVar output;
const TestGrammar p(m);
auto f = input.begin(), l = input.end();
qi::parse(f, l, p, output);
std::cout << std::quoted(input) << " -> " << std::quoted(output.sVariable) << "\n";
}
int main() {
for (mode m : {AS_STRING_CONSTRUCT, DIRECT_ASSIGN, USING_ACTOR,
TRANSPARENT_CXX14_LAMBDA}) {
std::cout << " ==== mode #" << static_cast<int>(m) << " === \n";
for (auto s : {"a", "d", "ac"})
do_test(s, m);
}
}
Just to demonstrate how the latter two approaches can both do without the constructor or even without any phoenix support.
As before, by that point I'd recommend going C++14 with Boost Spirit X3 anyways: http://coliru.stacked-crooked.com/a/dbd61823354ea8b6 or even 20 LoC: http://coliru.stacked-crooked.com/a/b26b3db6115c14d4
¹ there's a non-main "hack" that could help you there by defining BOOST_SPIRIT_ACTIONS_ALLOW_ATTR_COMPAT