1

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?

cigien
  • 57,834
  • 11
  • 73
  • 112
merkatorix
  • 35
  • 5

1 Answers1

1

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.

    • In particular to get a std::string use qi::as_string[]. If you don't use semantic actions, indeed this kind of attribute compatibility/transformation is automatic¹.
    • in similar vein, to parse a single item into an explicit container, use 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 with char?

Applied Answers:

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

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Now, boost::spirit looks like a minefield, again. I saw that you changed it to char before, and I should have stuck there. But then, I thought, everything is fine as long as it is detected as a std::string, and I somehow hoped it would encapsulate it more. However, it won't matter if I switch from string to wstring or from char to wchar. I just didn't expect so many problems. But that wasn't the minefield. Edit: And I have to be cautious with my variable naming; you are right. – merkatorix Jan 14 '22 at 15:13
  • Problem 1: Am I right that you were able to remove the std::vector just by adding a proper constructor CVar(std::string v = {}) : sVariable() {} instead of CVar(stringvec sVariable) : sVariable(sVariable) {} CVar() {} The move is just a bonus, but I would have thought, that there should be no difference between the two variants. I think there is something fundamental I was missing there. – merkatorix Jan 14 '22 at 15:26
  • Problem 1 (repeat): I found a post from you describing that: https://stackoverflow.com/questions/35098618/boost-spirit-how-to-use-repeat-to-parse-into-a-struct But that is even more of a mystery to me. – merkatorix Jan 14 '22 at 15:35
  • 1
    "everything is fine as long as it is detected as a std::string" - I think you need to be a bit more accurate in your thinking (or phrasing): "everything"? What exactly "it is [detected]" - What is? What do you mean by "detected as"? – sehe Jan 14 '22 at 20:27
  • 1
    To the second comment (first Problem 1): nah the constructor is just an elegant way to write default and conversion constructor in one: http://coliru.stacked-crooked.com/a/6d60ceadd2d24448. As you can see there *is* no difference there. The move is obviously side-catch, but I tend to want to demonstrate good practice in my code. – sehe Jan 14 '22 at 21:00
  • 1
    To the third comment ("second" Problem 1 [sic]): that post describes *the opposite* (how to force a repeat() parser to be compatible with a **non**-container attribute). Of course you're going to be confused if you think it's describing your situation. You were looking for forcing a `char` to be compatible with `std::vector` in some variations. My many flavours show how to deal with things. Only 1 even requires the `repeat` to help out. – sehe Jan 14 '22 at 21:06
  • 1
    The point of providing 8 different versions (plus variants) of code that *all* work is that *you* can figure out what works and what you're doing differently. I explain why I do that in the first paragraphs of the answer. – sehe Jan 14 '22 at 21:07
  • 1
    Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/241075/discussion-between-sehe-and-merkatorix). – sehe Jan 14 '22 at 21:07