I have two, what I presume to be legitimate, ways of defining a class in my ast that represents a string, illustrated here:
struct white : std::string {};
or
struct white {std::string text;};
which I use in several places for things like literals, identifiers, comments, or even white space, which I want to capture in my ast.
In some places I have to use the first form, and in other places I have to use the second form, otherwise I get compile errors with horrendous error messages:
...
no matching function for call to ??minimal::ast::rules::rules(__gnu_cxx::__normal_iterator<minimal::ast::gap_item*,
std::vector<minimal::ast::gap_item> >&,
__gnu_cxx::__normal_iterator<minimal::ast::gap_item*, std::vector<minimal::ast::gap_item> >&)??
...
embedded in several pages of error output with no clue regarding the line of source code which might be at fault.
How should I interpret this type of error message? Why do I have to use a different form/style of class in different places?
This minimal example illustrates the problem (when the #defines are [un]commented)
//{{{ Notes
/*
Purpose: Demonstrate problem causing error messages like:
no matching function for call to ??minimal::ast::rule::rule(std::_List_iterator<minimal::ast::gap_item>&,
std::_List_iterator<minimal::ast::gap_item>&)??
no matching function for call to ??minimal::ast::rules::rules(__gnu_cxx::__normal_iterator<minimal::ast::gap_item*,
std::vector<minimal::ast::gap_item> >&,
__gnu_cxx::__normal_iterator<minimal::ast::gap_item*, std::vector<minimal::ast::gap_item> >&)??
There are two aspects to this problem:
1) The error message is enormous (sometimes larger than my terminal scroll buffer) and is undecipherable (to me).
2) I do not understand what is really causing the problem, especially where, for similar requirements,
one construct works in one place, but another construct is needed elsewhere.
The problem appears with a subtle change in coding style for the ast:
#ifndef WITH_INSTANCED_STRING_WHITE
struct white : string {};
#else
struct white {string text;};
#endif
For this case, the problem occurs when WITH_INSTANCED_STRING_WHITE is undefined.
However, the opposite happens for WITH_INSTANCED_STRING_LITERAL or WITH_INSTANCED_STRING_C_IDENTIFIER.
Context: Attempting to parse "yacc" as a precursor to morphing yacc to x3.
There is important information in the c-style comments so gaps (comments or white) are not skipped.
Notation: Nested pairs of "//{{{" and "//}}}" denote folds which some editors can hide or fold into one line
Compile: g++ src/rgw29_minimal.cpp -o bin/rgw29_minimal
*/
//}}}
//{{{ rgw29_yacc.hpp
//{{{ Define
#define WITH_INSTANCED_STRING_WHITE
//#define WITH_INSTANCED_STRING_LITERAL
//#define WITH_INSTANCED_STRING_C_IDENTIFIER
//}}}
//{{{ include
#include <string>
#include <boost/cstdlib.hpp> // for boost::exit_success
#include <boost/filesystem.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp> // for x3::variant<...>
//}}}
//{{{ namespace and globals
namespace x3 = boost::spirit::x3;
using iterator_type = std::string::const_iterator;
using context_type = x3::phrase_parse_context<x3::ascii::space_type>::type;
//}}}
//}}}
//{{{ data/rgw29/yacc_grammar.yacc
//https://pubs.opengroup.org/onlinepubs/009696799/utilities/yacc.html
#include <string>
namespace minimal {
std::string bad_data = R"(
/* This is to demonstrate successful compilation, but a parse failure. */
spec : defs MARK rules tail
;
)";
std::string yacc_data = R"(
%start spec
%%
spec : defs MARK rules tail
;
)";
}
//}}}
//{{{ rgw29_yacc_ast.hpp
//{{{ include
#include <iostream>
#include <boost/fusion/include/io.hpp> // for boost::fusion::tuple_open/close/delimiter
#include <boost/fusion/include/std_pair.hpp> // for usage inside BOOST_FUSION_ADAPT_STRUCT
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/optional/optional_io.hpp> // for ostream<< boost::optional<>
//}}}
//{{{ ast yacc
namespace minimal { namespace ast
{
using namespace std;
enum yacc_token
{ lcomment
, rcomment
, semi
, colon
, mark
};
inline ostream& operator<<(ostream& os, yacc_token const& a)
{
//os << "yacc_token: ";
switch (a)
{
case lcomment : os << "/*"; break;
case rcomment : os << "*/"; break;
case semi : os << ";"; break;
case colon : os << ":"; break;
case mark : os << "%%"; break;
default : BOOST_ASSERT(0);
}
return os;
}
#ifndef WITH_INSTANCED_STRING_WHITE
struct white
: string
{
};
#else
struct white
{
string text;
};
#endif
struct comment
{
yacc_token open;
string text;
yacc_token close;
};
struct gap_item
: x3::variant
< comment
, white
>
{
using base_type::base_type;
using base_type::operator=;
};
struct gap
: vector<gap_item>
{
};
#ifndef WITH_INSTANCED_STRING_LITERAL
struct literal
: string
{
};
#else
struct literal
{
string text;
};
#endif
#ifndef WITH_INSTANCED_STRING_C_IDENTIFIER
struct c_identifier
: string
{
};
#else
struct c_identifier
{
string text;
};
#endif
struct rule_item
: x3::variant
< yacc_token
, c_identifier
, gap
, literal
//, string
>
{
using base_type::base_type;
using base_type::operator=;
};
struct rule
: vector<rule_item>
{
};
struct rules
: vector<rule>
{
};
struct def_item
: x3::variant
< gap
, string
>
{
using base_type::base_type;
using base_type::operator=;
};
struct defs
: vector<def_item>
{
};
struct spec_item
: x3::variant
< defs
, rules
, yacc_token
, gap
>
{
using base_type::base_type;
using base_type::operator=;
};
struct spec
{
defs defs_;
yacc_token mark_;
gap gap1;
rules rules_;
};
using yacc_ast_type = spec;
}}
//}}}
//{{{ fusion adapt
#ifdef WITH_INSTANCED_STRING_WHITE
BOOST_FUSION_ADAPT_STRUCT(minimal::ast::white, text)
#endif
#ifdef WITH_INSTANCED_STRING_LITERAL
BOOST_FUSION_ADAPT_STRUCT(minimal::ast::literal, text)
#endif
#ifdef WITH_INSTANCED_STRING_C_IDENTIFIER
BOOST_FUSION_ADAPT_STRUCT(minimal::ast::c_identifier, text)
#endif
BOOST_FUSION_ADAPT_STRUCT(minimal::ast::comment, open, text, close)
BOOST_FUSION_ADAPT_STRUCT(minimal::ast::spec, defs_, mark_, gap1, rules_)
//}}}
//}}}
//{{{ rgw29_yacc_grammar.hpp
//{{{ alias
namespace minimal { namespace parser
{
using x3::char_;
using x3::lexeme;
using x3::no_skip;
using x3::alnum;
using x3::alpha;
using x3::print;
using x3::ascii::string;
using x3::ascii::space;
}}
//}}}
//{{{ parse rule type
namespace minimal { namespace parser
{
struct comment_class;
using comment_type = x3::rule<comment_class, ast::comment>;
BOOST_SPIRIT_DECLARE(comment_type);
struct white_class;
using white_type = x3::rule<white_class, ast::white>;
BOOST_SPIRIT_DECLARE(white_type);
struct gap_class;
using gap_type = x3::rule<gap_class, ast::gap>;
BOOST_SPIRIT_DECLARE(gap_type);
struct defs_class;
using defs_type = x3::rule<defs_class, ast::defs>;
BOOST_SPIRIT_DECLARE(defs_type);
struct c_identifier_class;
using c_identifier_type = x3::rule<c_identifier_class, ast::c_identifier>;
BOOST_SPIRIT_DECLARE(c_identifier_type);
struct literal_class;
using literal_type = x3::rule<literal_class, ast::literal>;
BOOST_SPIRIT_DECLARE(literal_type);
struct rules_class;
using rules_type = x3::rule<rules_class, ast::rules>;
BOOST_SPIRIT_DECLARE(rules_type);
struct rule_class;
using rule_type = x3::rule<rule_class, ast::rule>;
BOOST_SPIRIT_DECLARE(rule_type);
struct spec_class;
using spec_type = x3::rule<spec_class, ast::spec>;
BOOST_SPIRIT_DECLARE(spec_type);
struct inner_class;
using inner_rule_type = x3::rule<inner_class, ast::yacc_ast_type>;
BOOST_SPIRIT_DECLARE(inner_rule_type);
struct start_class;
using start_rule_type = x3::rule<start_class, ast::yacc_ast_type>;
BOOST_SPIRIT_DECLARE(start_rule_type);
}}
//}}}
//}}}
//{{{ rgw29_yacc_grammar.cpp
//{{{ Token
namespace minimal { namespace parser
{
x3::symbols<ast::yacc_token> lcomment;
x3::symbols<ast::yacc_token> rcomment;
x3::symbols<ast::yacc_token> semi;
x3::symbols<ast::yacc_token> colon;
x3::symbols<ast::yacc_token> mark;
}}
//}}}
//{{{ add_keywords
namespace minimal { namespace parser
{
void add_keywords()
{
static bool once = false;
if (once)
return;
once = true;
lcomment.add ("/*", ast::lcomment);
rcomment.add ("*/", ast::rcomment);
semi.add (";", ast::semi);
colon.add (":", ast::colon);
mark.add ("%%", ast::mark);
}
}}
//}}}
//{{{ rule id
namespace minimal { namespace parser
{
c_identifier_type const c_identifier = "c_identifier";
literal_type const literal = "literal";
comment_type const comment = "comment";
white_type const white = "white";
gap_type const gap = "gap";
defs_type const defs = "defs";
rules_type const rules = "rules";
spec_type const spec = "spec";
rule_type const rule = "rule";
inner_rule_type const inner_rule = "inner_rule";
start_rule_type const start_rule = "start_rule";
}}
//}}}
//{{{ rule definition (cut down for this minimal example)
namespace minimal { namespace parser
{
auto const comment_def = lcomment >> *(char_ - rcomment) >> rcomment; // /* ... */
auto const white_def = +space; // single space as string
auto const gap_def = +(white | comment); // spaces/comments
auto const c_identifier_def = (alpha | char_("_.")) >> *(alnum | char_("_.")); // with non-initial number
auto const literal_def = char_("'") >> -char_("\\") >> (print - '\'') >> char_("'"); // 'c' or '\n'
auto const spec_def = defs >> mark >> gap >> rules; // without tail
auto const defs_def = +(gap | +(char_ -lcomment -rcomment -mark)); // skip over all defs
auto const rules_def = +rule;
auto const rule_def = c_identifier > gap > colon > gap
> *(gap | literal | +(char_ -lcomment -rcomment -semi -literal)) // catch_all to next semi
> semi > gap;
auto const inner_rule_def = no_skip[spec];
auto const start_rule_def = inner_rule_def;
}}
//}}}
//{{{ tie rule to definition
namespace minimal { namespace parser
{
BOOST_SPIRIT_DEFINE(comment, white, gap, c_identifier, literal)
BOOST_SPIRIT_DEFINE(defs, rule, rules, spec);
BOOST_SPIRIT_DEFINE(inner_rule, start_rule);
}}
//}}}
//{{{ start_rule()
namespace minimal
{
parser::start_rule_type const& start_rule(){parser::add_keywords(); return parser::start_rule;}
}
//}}}
//{{{ parse_without_error_handler_and_positions(...)
namespace minimal { namespace parser
{
bool
parse_without_error_handler_and_positions
( iterator_type& iter
, iterator_type end
, ast::yacc_ast_type& ast
)
{
auto const parser_with_error_handler_and_positions = minimal::start_rule();
return phrase_parse(iter, end, parser_with_error_handler_and_positions, space, ast);
}
}}
//}}}
//}}}
//{{{ rgw29_yacc_trial.cpp
//{{{ yacc implementation
namespace minimal
{
//{{{ parse_yacc_string (not a lambda)
bool
parse_yacc_string(std::string const& source)
{
iterator_type iter (source.begin());
iterator_type const end (source.end());
minimal::ast::yacc_ast_type ast;
bool success = parser::parse_without_error_handler_and_positions(iter, end, ast);
if (success && iter==end)
{
std::cout << " parse succeeded" << std::endl;
}
else
{
std::cout << " parse failed (as expected)" << std::endl;
}
return success;
};
//}}}
}
//}}}
//}}}
//{{{ rgw29_yacc.cpp
//{{{ main
int
main(int argc, char* argv[], char* envp[])
{
using namespace std;
using namespace minimal;
parse_yacc_string(yacc_data);
parse_yacc_string(bad_data);
cout << "... successfully completed " << argv[0] << endl;
return boost::exit_success;
}
//}}}
//}}}
****** Edit 2021-05-30 ******
I believe there is a bug in X3 associated with multiple nested containers with variants. But that is something for the developers to sort out.
I have a more minimal example:
// Purpose: Demonstrate problem with lower level detail (gap_item) where wrapped rule (gap) should do.
#define BOOST_SPIRIT_X3_DEBUG // Provide some meaningful reassuring output.
//#define WITHOUT_WHITE // All ok until white added.
#define WITH_GAP_ITEM_IN_VARIANT // Unnacceptable workaround
#include <string>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace x3 = boost::spirit::x3;
namespace minimal { namespace ast
{
struct white : std::string {using std::string::string;};
struct braced {char open; std::string text; char close;};
#ifdef WITHOUT_WHITE
struct gap_item : x3::variant<char,braced> {using base_type::operator=;};
#else // WITHOUT_WHITE
struct gap_item : x3::variant<white,braced> {using base_type::operator=;};
#endif // WITHOUT_WHITE
struct gap : std::vector<gap_item> {};
#ifndef WITH_GAP_ITEM_IN_VARIANT
struct start_item : x3::variant<gap,std::string> {using base_type::operator=;};
#else // WITH_GAP_ITEM_IN_VARIANT
struct start_item : x3::variant<gap,std::string,gap_item,char> {using base_type::base_type; using base_type::operator=;};
#endif // WITH_GAP_ITEM_IN_VARIANT
struct start : std::vector<start_item> {using std::vector<start_item>::vector;};
}}
BOOST_FUSION_ADAPT_STRUCT(minimal::ast::braced, open, text, close)
namespace minimal { namespace parser
{
using x3::char_;
using x3::space;
using x3::raw;
x3::rule<struct white, ast::white> const white = "white";
x3::rule<struct braced, ast::braced> const braced = "braced";
x3::rule<struct gap, ast::gap> const gap = "gap";
x3::rule<struct start, ast::start> const start = "start";
auto const white_def = raw[+space];
auto const braced_def = char_('{') >> *(char_ -'}') >> char_('}'); // { ... }
#ifdef WITHOUT_WHITE
auto const gap_def = +(space | braced); // spaces etc
#else // WITHOUT_WHITE
auto const gap_def = +(white | braced); // spaces etc
#endif // WITHOUT_WHITE
auto const start_def = +(gap | +(char_ -'{' -space)); // gap=container or string
BOOST_SPIRIT_DEFINE(white, gap, braced, start);
}}
int
main()
{
char const* iter = "? {;};", * const end = iter + std::strlen(iter);
minimal::ast::start ast;
return !parse(iter, end, minimal::parser::start, ast) || iter!=end;
}
//}}}
I started without white
for collective white space, instead of a single char
.
and I included two defines for ease of testing/demonstration.
#define WITHOUT_WHITE // All ok until white added.
//#define WITH_GAP_ITEM_IN_VARIANT // Unnacceptable workaround
All is fine without white
.
When I add white
by commenting //#define WITHOUT_WHITE
I get error messages essentially telling me to add char
and gap_item
to my start_item variant. Ok, so the code now compiles and runs. But the bug is that gap
is ignored/bypassed and actions associated with the gap in the ast (such as printing) are not performed.
Can anyone suggest a different way to structure the AST or the grammar such that X3 does what it is supposed to?
****** Edit 2021-05-31 ******
I have found two workarounds which are consistent and possibly scalable to a real world full blown application of X3 (but I have not yet propagated either of these workarounds back into the X3 applications -- that will take some time). Either workaround alone does the job for this minimal example, and applying both is ok.
- Replace each instance of any "gratuitous inheritance" with a single element structure. This causes a single element tuple to be generated, and though this is supposedly supported, there may be corner cases where bugs are lurking.
// Purpose: Working example using single element *** TUPLE *** consistently, instead of class inheritance
#define BOOST_SPIRIT_X3_DEBUG // Provide some meaningful reassuring output.
#include <string>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace x3 = boost::spirit::x3;
namespace ast {
struct white {std::string value;}; // *** TUPLE ***
struct braced {char open; std::string text; char close;};
struct gap_item : x3::variant<white,braced> {using base_type::operator=;};
struct gap {std::vector<gap_item> value;}; // *** TUPLE ***
struct start_item : x3::variant<gap,std::string> {using base_type::operator=;};
struct start {std::vector<start_item> value;}; // *** TUPLE ***
}
BOOST_FUSION_ADAPT_STRUCT(ast::white, value) // *** TUPLE ***
BOOST_FUSION_ADAPT_STRUCT(ast::gap, value) // *** TUPLE ***
BOOST_FUSION_ADAPT_STRUCT(ast::start, value) // *** TUPLE ***
BOOST_FUSION_ADAPT_STRUCT(ast::braced, open, text, close)
namespace parser {
using x3::char_; using x3::space; using x3::raw; using x3::graph;
x3::rule<struct white, ast::white> const white = "white";
x3::rule<struct braced, ast::braced> const braced = "braced";
x3::rule<struct gap, ast::gap> const gap = "gap";
x3::rule<struct start, ast::start> const start = "start";
static auto const white_def = raw[+space];
static auto const braced_def = char_('{') >> *~char_('}') >> char_('}'); // { ... }
static auto const gap_def = +(white | braced); // spaces etc
static auto const start_def = +(gap | +(graph -'{')); // gap or body
BOOST_SPIRIT_DEFINE(white, braced, gap, start);
}
int main()
{
char const* iter = "? {;};", * const end = iter + std::strlen(iter);
ast::start ast;
return !parse(iter, end, parser::start, ast) || iter!=end;
}
- For each case where there is a container of a variant, add an extra rule in the grammar for that variant.
// Purpose: Working example when an *** EXTRA RULE *** (start_item) is used to break synthesized nested containers
#define BOOST_SPIRIT_X3_DEBUG // Provide some meaningful reassuring output.
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
namespace x3 = boost::spirit::x3;
namespace ast {
struct white : std::string {using std::string::string;};
struct braced {char open; std::string text; char close;};
struct gap_item : x3::variant<white,braced> {using base_type::operator=;};
struct gap : std::vector<gap_item> {};
struct start_item : x3::variant<gap,std::string> {using base_type::operator=;};
struct start : std::vector<start_item> {using std::vector<start_item>::vector;};
}
BOOST_FUSION_ADAPT_STRUCT(ast::braced, open, text, close)
namespace parser {
using x3::char_; using x3::space; using x3::raw; using x3::graph;
x3::rule<struct white, ast::white> const white = "white";
x3::rule<struct braced, ast::braced> const braced = "braced";
x3::rule<struct gap_item, ast::gap_item> const gap_item = "gap_item"; // *** EXTRA RULE ***
x3::rule<struct gap, ast::gap> const gap = "gap";
x3::rule<struct start_item, ast::start_item> const start_item = "start_item"; // *** EXTRA RULE ***
x3::rule<struct start, ast::start> const start = "start";
auto const white_def = raw[+space];
auto const braced_def = char_('{') >> *~char_('}') >> char_('}'); // { ... }
auto const gap_item_def = white | braced; // white etc // *** EXTRA RULE ***
auto const gap_def = +gap_item; // spaces etc
auto const start_item_def = gap | +(char_ - '{' - space); // gap or body // *** EXTRA RULE ***
auto const start_def = +start_item; // multiple gaps or bodies
BOOST_SPIRIT_DEFINE(white, gap, braced, start_item, start);
}
int main() {
char const* iter = "? {;};", * const end = iter + std::strlen(iter);
ast::start ast;
return !parse(iter, end, parser::start, ast) || iter!=end;
}
The changes are denoted with // *** TUPLE ***
or // *** EXTRA RULE ***
respectively.
I personally prefer the change to the AST (it is just a different way of defining the class) rather than the change to the parser (which adds extra clutter and breaks the spirit of X3 [pun!]).
Please could you experts help me and others determine which is the better.