1

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.

  1. 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;
}

  1. 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.

Bockeman
  • 11
  • 3
  • Whoah. I won't fault you for not putting enough effort into the question, but... this is not minimal. It **is** self-contained, but it is perhaps the opposite of minimal. – sehe May 21 '21 at 15:06

1 Answers1

0

I've just spent 100 minutes just making it compile in separate files as intended.

Let me respond to a few of the generic complaints:

How should I interpret this type of error message?

Read through them with the help of your tooling (IDE navigation support). See also How do I grok boost spirit compiler errors

  1. The error message is enormous (sometimes larger than my terminal scroll buffer) and is undecipherable (to me).

Consider using camomilla to help see the big picture.

There used to be a similar thing ("STLFitler" or so?) that might still be useful for MSVC, though I don't use it.

For this case, the problem occurs when WITH_INSTANCED_STRING_WHITE is undefined.

Okay, this immediately tells me you might be running into the age-old "single-element fusion sequences" edge case, which has never fully been resolved:

Grokking the cause

Let's undefine WITH_INSTANCED_STRING_WHITE and then

make |& camomilla -d0

This looks like this on my system:

[ 33%] Building CXX object CMakeFiles/test.dir/rgw29_yacc_grammar.cpp.o
In file included from /home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/auxiliary/any_parser.hpp:17,
                 from /home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/auxiliary.hpp:11,
                 from /home/sehe/custom/boost_1_76_0/boost/spirit/home/x3.hpp:14,
                 from /home/sehe/Projects/stackoverflow/rgw29_yacc/rgw29_yacc.hpp:10,
                 from /home/sehe/Projects/stackoverflow/rgw29_yacc/rgw29_yacc_ast.hpp:2,
                 from /home/sehe/Projects/stackoverflow/rgw29_yacc/rgw29_yacc_grammar.hpp:2,
                 from /home/sehe/Projects/stackoverflow/rgw29_yacc/rgw29_yacc_grammar.cpp:1:
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/support/traits/move_to.hpp: In instantiation of ‘void boost::spirit::x3::traits::detail::move_to(Iterator, Iterator, Dest&, boost::spirit::x3::traits::container_attribute) [with Iterator = __gnu_cxx::__normal_iterator<?>; Dest = minimal::ast::defs]’:
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/support/traits/move_to.hpp:224:24:   required from ‘void boost::spirit::x3::traits::move_to(Iterator, Iterator, Dest&) [with Iterator = __gnu_cxx::__normal_iterator<?>; Dest = minimal::ast::defs]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/support/traits/move_to.hpp:88:28:   required from ‘typename boost::enable_if<?>::type boost::spirit::x3::traits::detail::move_to(Source&, Dest&, boost::spirit::x3::traits::container_attribute) [with Source = minimal::ast::gap; Dest = minimal::ast::defs; typename boost::enable_if<?>::type = void]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/support/traits/move_to.hpp:196:24:   required from ‘void boost::spirit::x3::traits::move_to(Source&&, Dest&) [with Source = minimal::ast::gap; Dest = minimal::ast::defs]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/nonterminal/detail/transform_attribute.hpp:30:28:   required from ‘static void boost::spirit::x3::default_transform_attribute<?>::post(Exposed&, Transformed&&) [with Exposed = minimal::ast::defs; Transformed = minimal::ast::gap]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/nonterminal/rule.hpp:156:32:   required from ‘bool boost::spirit::x3::rule<?>::parse(Iterator&, const Iterator&, const Context&, boost::spirit::x3::unused_type, Attribute_&) const [with Iterator = __gnu_cxx::__normal_iterator<?>; Context = boost::spirit::x3::context<?>; Attribute_ = minimal::ast::defs; ID = minimal::parser::gap_class; Attribute = minimal::ast::gap; bool force_attribute_ = false]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/operator/detail/alternative.hpp:189:20:   [ skipping 34 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/nonterminal/detail/rule.hpp:334:37:   required from ‘static bool boost::spirit::x3::detail::rule_parser<?>::call_rule_definition(const RHS&, const char*, Iterator&, const Iterator&, const Context&, ActualAttribute&, ExplicitAttrPropagation) [with RHS = boost::spirit::x3::no_skip_directive<?>; Iterator = __gnu_cxx::__normal_iterator<?>; Context = boost::spirit::x3::context<?>; ActualAttribute = minimal::ast::spec; ExplicitAttrPropagation = mpl_::bool_<?>; Attribute = minimal::ast::spec; ID = minimal::parser::start_class; bool skip_definition_injection = true]’
/home/sehe/Projects/stackoverflow/rgw29_yacc/rgw29_yacc_grammar.cpp:77:5:   required from ‘bool minimal::parser::parse_rule(boost::spirit::x3::detail::rule_id<?>, Iterator&, const Iterator&, const Context&, boost::spirit::x3::rule<?>::attribute_type&) [with Iterator = __gnu_cxx::__normal_iterator<?>; Context = boost::spirit::x3::context<?>; boost::spirit::x3::rule<?>::attribute_type = minimal::ast::spec]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/nonterminal/rule.hpp:155:27:   required from ‘bool boost::spirit::x3::rule<?>::parse(Iterator&, const Iterator&, const Context&, boost::spirit::x3::unused_type, Attribute_&) const [with Iterator = __gnu_cxx::__normal_iterator<?>; Context = boost::spirit::x3::context<?>; Attribute_ = minimal::ast::spec; ID = minimal::parser::start_class; Attribute = minimal::ast::spec; bool force_attribute_ = false]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/core/parse.hpp:119:36:   required from ‘bool boost::spirit::x3::phrase_parse_main(Iterator&, Iterator, const Parser&, const Skipper&, Attribute&, boost::spirit::x3::skip_flag) [with Iterator = __gnu_cxx::__normal_iterator<?>; Parser = boost::spirit::x3::rule<?>; Skipper = boost::spirit::x3::char_class<?>; Attribute = minimal::ast::spec]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/core/parse.hpp:136:33:   required from ‘bool boost::spirit::x3::phrase_parse(Iterator&, Iterator, const Parser&, const Skipper&, Attribute&, boost::spirit::x3::skip_flag) [with Iterator = __gnu_cxx::__normal_iterator<?>; Parser = boost::spirit::x3::rule<?>; Skipper = boost::spirit::x3::char_class<?>; Attribute = minimal::ast::spec]’
/home/sehe/Projects/stackoverflow/rgw29_yacc/rgw29_yacc_grammar.cpp:100:39:   required from here
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/support/traits/move_to.hpp:171:29: error: could not convert ‘first’ from ‘__gnu_cxx::__normal_iterator<?>’ to ‘vector<?>’
  171 |                 dest = Dest(first, last);
      |                             ^~~~~
      |                             |
      |                             __gnu_cxx::__normal_iterator<?>
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/support/traits/move_to.hpp: In instantiation of ‘void boost::spirit::x3::traits::detail::move_to(Iterator, Iterator, Dest&, boost::spirit::x3::traits::container_attribute) [with Iterator = __gnu_cxx::__normal_iterator<?>; Dest = minimal::ast::rule]’:
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/support/traits/move_to.hpp:224:24:   required from ‘void boost::spirit::x3::traits::move_to(Iterator, Iterator, Dest&) [with Iterator = __gnu_cxx::__normal_iterator<?>; Dest = minimal::ast::rule]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/support/traits/move_to.hpp:88:28:   required from ‘typename boost::enable_if<?>::type boost::spirit::x3::traits::detail::move_to(Source&, Dest&, boost::spirit::x3::traits::container_attribute) [with Source = minimal::ast::gap; Dest = minimal::ast::rule; typename boost::enable_if<?>::type = void]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/support/traits/move_to.hpp:196:24:   required from ‘void boost::spirit::x3::traits::move_to(Source&&, Dest&) [with Source = minimal::ast::gap; Dest = minimal::ast::rule]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/nonterminal/detail/transform_attribute.hpp:30:28:   required from ‘static void boost::spirit::x3::default_transform_attribute<?>::post(Exposed&, Transformed&&) [with Exposed = minimal::ast::rule; Transformed = minimal::ast::gap]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/nonterminal/rule.hpp:156:32:   required from ‘bool boost::spirit::x3::rule<?>::parse(Iterator&, const Iterator&, const Context&, boost::spirit::x3::unused_type, Attribute_&) const [with Iterator = __gnu_cxx::__normal_iterator<?>; Context = boost::spirit::x3::context<?>; Attribute_ = minimal::ast::rule; ID = minimal::parser::gap_class; Attribute = minimal::ast::gap; bool force_attribute_ = false]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/core/detail/parse_into_container.hpp:264:36:   [ skipping 41 instantiation contexts, use -ftemplate-backtrace-limit=0 to disable ]
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/nonterminal/detail/rule.hpp:334:37:   required from ‘static bool boost::spirit::x3::detail::rule_parser<?>::call_rule_definition(const RHS&, const char*, Iterator&, const Iterator&, const Context&, ActualAttribute&, ExplicitAttrPropagation) [with RHS = boost::spirit::x3::no_skip_directive<?>; Iterator = __gnu_cxx::__normal_iterator<?>; Context = boost::spirit::x3::context<?>; ActualAttribute = minimal::ast::spec; ExplicitAttrPropagation = mpl_::bool_<?>; Attribute = minimal::ast::spec; ID = minimal::parser::start_class; bool skip_definition_injection = true]’
/home/sehe/Projects/stackoverflow/rgw29_yacc/rgw29_yacc_grammar.cpp:77:5:   required from ‘bool minimal::parser::parse_rule(boost::spirit::x3::detail::rule_id<?>, Iterator&, const Iterator&, const Context&, boost::spirit::x3::rule<?>::attribute_type&) [with Iterator = __gnu_cxx::__normal_iterator<?>; Context = boost::spirit::x3::context<?>; boost::spirit::x3::rule<?>::attribute_type = minimal::ast::spec]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/nonterminal/rule.hpp:155:27:   required from ‘bool boost::spirit::x3::rule<?>::parse(Iterator&, const Iterator&, const Context&, boost::spirit::x3::unused_type, Attribute_&) const [with Iterator = __gnu_cxx::__normal_iterator<?>; Context = boost::spirit::x3::context<?>; Attribute_ = minimal::ast::spec; ID = minimal::parser::start_class; Attribute = minimal::ast::spec; bool force_attribute_ = false]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/core/parse.hpp:119:36:   required from ‘bool boost::spirit::x3::phrase_parse_main(Iterator&, Iterator, const Parser&, const Skipper&, Attribute&, boost::spirit::x3::skip_flag) [with Iterator = __gnu_cxx::__normal_iterator<?>; Parser = boost::spirit::x3::rule<?>; Skipper = boost::spirit::x3::char_class<?>; Attribute = minimal::ast::spec]’
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/core/parse.hpp:136:33:   required from ‘bool boost::spirit::x3::phrase_parse(Iterator&, Iterator, const Parser&, const Skipper&, Attribute&, boost::spirit::x3::skip_flag) [with Iterator = __gnu_cxx::__normal_iterator<?>; Parser = boost::spirit::x3::rule<?>; Skipper = boost::spirit::x3::char_class<?>; Attribute = minimal::ast::spec]’
/home/sehe/Projects/stackoverflow/rgw29_yacc/rgw29_yacc_grammar.cpp:100:39:   required from here
/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/support/traits/move_to.hpp:171:29: error: could not convert ‘first’ from ‘__gnu_cxx::__normal_iterator<?>’ to ‘vector<?>’
CMakeFiles/test.dir/build.make:95: recipe for target 'CMakeFiles/test.dir/rgw29_yacc_grammar.cpp.o' failed
make[2]: *** [CMakeFiles/test.dir/rgw29_yacc_grammar.cpp.o] Error 1
CMakeFiles/Makefile2:151: recipe for target 'CMakeFiles/test.dir/all' failed
make[1]: *** [CMakeFiles/test.dir/all] Error 2
Makefile:103: recipe for target 'all' failed
make: *** [all] Error 2

Daunting, but nowhere near the scrollbuffer limits. Let's read. Visually, the line with the first error: jumps out:

/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/support/traits/move_to.hpp:171:29: error: could not convert ‘first’ from ‘__gnu_cxx::__normal_iterator<?>’ to ‘vector<?>’<br/>

  171 |               dest = Dest(first, last);
      |                             ^~~~~
      |                             |
      |                             __gnu_cxx::__normal_iterator<?>`

That's pretty clear. The next line tells us that Dest = minimal::ast::rule. So, that constructor is being called with iterators, instead of the expected things.

On a hunch I added using vector::vector; to all your vector-derived types, and using string::string; to the string-derived:

  struct white : string {
      using string::string;
  };

Now we get

/home/sehe/custom/boost_1_76_0/boost/spirit/home/x3/support/ast/variant.hpp:151:39: error: no matching function for call to ‘boost::variant<?>::variant(minimal::ast::gap_item&)’

At this point I'm choosing to bail out because I see there's no fewer than 4 different x3::variant-deriving AST nodes, none of which take gap_item as element type. In fact, NOTHING in the code base does, except

  struct gap : vector<gap_item> {
  };

Which makes no sense, unless gap is never explicitly synthesized. But is is:

using gap_type = x3::rule<struct gap_class, ast::gap>;

I suggest you take it from here. If you whittle away until the sample is actually minimal, then I can look again.

Closing Thoughts

Before others are going to jump on it: deriving from std::vector/std::string is a leaky abstraction. The types weren't designed for it, and the implicit-conversion-to-base will confuse the attribute propagation system, as you may be finding out right about now.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • I'm aghast that you took 100 minutes to reconstruct my example into separate files. Sorry. The "//{{{ " comments were intended to show from which files the code originally resided, not for the purpose of reconstructing. There were many constructs that I had ommitted, so I can see that caused you a lot of work to re-invent such. My example was intended as a standalone single compilable file. I have now learned how even this example is way larger than it need be. Future MCVE's from me will be considerably smaller. – Bockeman May 22 '21 at 20:26
  • 1. I recognise that single element tuples are not supported. And this is easily fixed by not using such. 2. I recognise that derived classes/structs do not inherit the constructable/copyable methods, so these must be added. 3. Understanding error messages from heavily templated libraries is a separate issue, but thanks for your suggestions. – Bockeman May 22 '21 at 20:30
  • the 100 minutes was including review/reading. I linked you the repo for that reason. I have some local changes that I didn't push because they didn't lead to improvement. I'm glad that you recognize that the sample was way larger than required. Probably, it's the size that also prevents you from diagnosing the root cause too, as I can tell that you have plenty skill in the required areas. – sehe May 22 '21 at 22:48
  • 1a. they are, there are sometimes lingering edge cases. 1b. Oops, that's swimming the wrong way. Like I said, you're much likely to cause the system to malfunction with unwarranted inheritance. 3/ Cheers :) – sehe May 22 '21 at 22:48
  • I am not sure if I have acheived the _whittle away until the sample is actually minimal_ but I have got it smaller and followed your suggestions, thanks, (though confess to not fully understanding the implications of **leaky** abstraction). Added by edit above. – Bockeman May 31 '21 at 00:57
  • That's MUCH better. I think there's something fishy with `white ~= std::string` vs `white != char` - when "without white" - that's an incongruente. Regardless, I'd sidestep the whole issue by avoiding gratuitous inheritance: https://godbolt.org/z/oz754zo8P In fact I'd suggest simplifying the AST for braced: https://godbolt.org/z/hjeh7jh7f. You will note that I keep the x3::variant inheritance, as clearly it was designed for inheritance (`base_type` gives that away) and apparently doesn't quite lead to the attribute propagation confusion immediately. – sehe May 31 '21 at 12:11
  • Thank you very much for your many suggestions. You will see that I have found two independent workarounds, inspired by your suggestions, but neither of the code examples you provided meets my requirement for consistency and scaleability -- and I shall explain why: There follow 7 points in separate comments (to avoid the comment size limit). – Bockeman May 31 '21 at 18:16
  • 1) Replace "gratuitous inheritance" with single element tuple, noting that some corner cases may not be supported. You only did this in one place. I applied it consistently to all cases, and voila! it worked. Thanks. – Bockeman May 31 '21 at 18:21
  • 2) Replace "gratuitous inheritance" with alias (using ...). However I cannot use this because the AST hook is lost. – Bockeman May 31 '21 at 18:22
  • 3) Replace braced with a raw string. Yes this does compile, but it defeats the purpose of example, like without white. Not clear from this minimal example is that "braced" is a simplification of the real world X3 application. – Bockeman May 31 '21 at 18:24
  • 4) Use graph instead of char_ -space. Brilliant. Thanks for this tip. – Bockeman May 31 '21 at 18:24
  • 5) Use *~char_('}') instead of *(char_ -'}'). Brilliant. Thanks for this tip. – Bockeman May 31 '21 at 18:25
  • 6) Use **static** auto const rule_def. static is not used in the X3 tutorials. Could you explain why you use/prefer this? This could also be a good tip. – Bockeman May 31 '21 at 18:28
  • 7) A possible in-line rule definition, but I could not get this to work. ``` static auto const gap_item //= x3::rule{"gap_item"} = white | braced; ``` This is commented, so obviously not used or even recommended. But I am intrigued, and again possibly another tip to add the armoury. Thanks again for all your help. – Bockeman May 31 '21 at 18:29
  • 6) I use it to silence warnings from some compilers that the definitions are not "required" and will not be "emitted". Making them explicitly file-static makes the intent cliear. – sehe May 31 '21 at 19:13
  • 7) Indeed, I'm not fond of the DEFINE macro magic. I do actually recommend inline definitions in many cases (in fact in most cases where I think X3 is viable :)). But there are cases (nested contexts, recursive rules, multi-TU grammars) where you absolutely need named rule objects and specifically tag based parser definition dispatch (BOOST_SPIRIT_[DEFINE|DECLARE|INSTANTIATE] family). – sehe May 31 '21 at 19:18
  • I often use this trick to ensure the _declared attribute type_ of a rule, much in the way of `qi::attr_cast<>` or `qi::transform_attr`. See [my pet "as" helper](https://stackoverflow.com/search?q=user%3A85371+as_type+x3) (_faux directive_ if you will). This was also what I had temporarily added it for while trying things out, but in the end it was no longer necessary, and I left it in precisely as a hint of possibilities :) – sehe May 31 '21 at 19:19