Here is my issue. I experiment using boost::spirit::qi and am trying using placeholders like "_1" and "_a". I would like to access the underlying object "behind" a boost::qi/phoenix placeholder but I'm a bit struggling here.
Let's say I have the following class:
class Tag {
public:
Tag() = default; // Needed by qi
Tag(std::uint8_t _raw_tag) : m_raw_tag( _raw_tag ) {}
std::uint8_t get_size() { return m_raw_tag & 0b111; }
std::uint8_t get_type() { return m_raw_tag & 0b1000; }
private:
std::uint8_t m_raw_tag;
};
I have to parse frames starting with a tag byte that gives information about what I have to read next. To do this, I have written little helper class named Tag that unmasks these pieces of information like the type of the tag or size of the piece of data to come next. I always store the data in an std::uint32_t but it is possible that the size of the data is 3 bytes and not something pre-defined like 1, 2 or 4 in which case I can respectively use qi::byte or qi::big_word or qi::big_qword (assuming the big endianness). Therefore, I'm thinking about reading the data byte after byte and bit-shifting them in the output std::uint32_t.
That would give such a parser in pseudo cpp code:
template<typename _Iterator>
struct Read_frame : qi::grammar<_Iterator, std::uint32_t(), qi::locals<std::uint8_t>> {
Read_frame() : Read_frame::base_type(data_parser)
{
using boost::spirit::qi::byte_;
using boost::spirit::qi::omit;
using boost::spirit::qi::repeat;
using boost::spirit::qi::_val;
using namespace qi::labels;
tag_parser %= byte_;
// we read what's in the tag but we don't store it
// Call the method get_size() of Tag is my issue, I don't know how to do it
data_parser %= omit[tag_parser[ _a = _1.get_size()]] >> eps[_val = 0]
>> repeat(_a)[ byte_[ _val += (_1 << (--_a * 8)) ];
}
qi::rule<_Iterator, std::uint32_t(), qi::locals<std::uint8_t>> data_parser;
qi::rule<_Iterator, Tag()> tag_parser;
};
The line:
data_parser %= omit[context_tag[ _a = _1.get_size()]] >> eps[_val = 0]
is where my problem lies. I don't know how to access method of Tag in a semantic actions. Thereby I thought about using boost::phoenix::static_cast_<Tag*>(&_1)->get_size() or something alike but it does not work.
This is the first time I'm using the whole boost::spirit thing along with boost::phoenix and to be quite honest I don't think I really understood how the placeholders in boost work nor the principle of boost::phoenix::static_cast_. That's why I'm here gently asking for your help :). If you need more details, I will give them to you with pleasure
Thanks in advance,
A newbie with boost spirit
Semantic actions are lazy phoenix actors. That is, they are "deferred functions". You can also see them as dynamically defined composed functions.
The "value behind a placeholder" depends on the context. That context is runtime. The Phoenix transformation ("evaluation") uses that context to retrieve the actual object behind the placeholder during invocation.
The last part is the point: any runtime effect must be deferred to during invocation. That means that you need a Phoenix actor to access the
get_size()method and lazily invoke it.Clumsy? You bet. The whole semantic-action eDSL is limited. Luckily, there are many ways to approach this:
you can use
phoenix::bindwith a pointer-to-member functionyou can use many predefined lazy functions for things like construction or most of STL (
#include <boost/phoenix/stl.hpp>).Incidentally.
phoenix::sizedoesn't work for your type because it doesn't adhere to STL conventions (size_t T::size() constinstead ofget_size).You can write your own actors as polymorphic function objects, and adapt them either
phoenix::function<>In fact my favorite take on this has become
px::function f = [](auto& a, auto& b) { return a + b; };, fully leveraging C++17 CTADLet's demonstrate all or most of these.
Step #1 Pinning Down Behaviour
As mentioned in my comment, I'm a bit confused by the apparent behavior of the parser as given, so let's first pin it down using the
phoenix::bindapproach as an example:Note several other simplifications/readability tricks. Now with some test cases Live On Compiler Explorer:
Step #2: Simplify
Instead of the mutating of the qi::local, I'd simply incrementally shift:
We have the unit tests now to verify the behavior is the same: Live On Compiler Explorer.
Step #3 Other Bind Approaches
As promised:
using
phoenix::functionand C++17 lambda goodness: LiveNote that the nature of deferred function objects is polymorphic, so this works just the same:
using the same without C++17 goodness: Live
using adaptation macros (
BOOST_PHOENIX_ADAPT_CALLABLE), LiveBONUS Simplify #2
Still using Qi, I would note that there is nothing in the
Tagthat necessitates using that as an attribute type. In fact, we need only the trivial bit mask which might be a free function, if you really want. So, this minimal code does the same without much of the unneeded complexity:Live On Compiler Explorer
A free function would be just as easy: Live
BONUS Simplify And Modernize
In real life, I'd certainly code a custom parser. You can do so in Spirit Qi, but to go with the times, vastly reduce compile times and just generally make my life easier, I'd go with Spirit X3:
Live On Compiler Explorer
Note only does this compile 10x¹ faster, I suspect it will be way easier for the compiler to optimize. Indeed this program
Optimizes all the way to
See it Live On Compiler Explorer including the generated assembly code
¹ proven by finger dipping