I'm trying to make bison more memory efficient by using std::shared_ptr
. I do not want to use raw pointers. I'm using a node system as the parse tree so I define YYTYPE
as std::shared_ptr<Node>
. After running it with some simple grammar, I get the compile error:
C2039 'blockNode': is not a member of 'std::shared_ptr'
I find this strange as the equivalent code ran in C++ works just fine
std::shared_ptr<Node> test = std::make_shared<BlockNode>();
What am I missing?
Requires
%code requires {
typedef void* yyscan_t;
#include "node.h"
#include <memory>
#define YYSTYPE std::shared_ptr<Node>
}
Union
%union {
std::shared_ptr<BlockNode> blockNode;
std::shared_ptr<TestNode> testNode;
}
%type <blockNode> input
%type <testNode> expr
Grammar
%start program
%%
program : input { *result = $1; } // <- error here
;
input: '\n' { $$ = std::make_shared<BlockNode>();} // <- error here
;
%%
Node.h
class Node /*: public std::enable_shared_from_this<Node>*/ {
public:
std::string myString;
Node() {}
~Node() { std::cout << "destoryed" << myString << std::endl; }
};
class BlockNode : public Node {
public:
BlockNode() {
myString = "block node";
std::cout << "created" << myString << std::endl;
}
};
The first thing you should know is that this design cannot work. If you use the default C API for bison, you cannot use a semantic type which is not trivially copyable, because bison will bytewise copy its stack if it needs to reallocate it (and I believe there are other issues having to do with overwriting bytes without calling destructors). If you want to use shared pointers, you should work out how to use the C++ API, which I think is reaching some kind of maturity (although I haven't used it much). You'll probably be much happier with the result.
Regardless of that, there are some other issues with your code.
First, modern bison applications should not #define YYSTYPE
, not even inside a %code requires
block. You should instead use
%define api.value.type { /* SemanticType */ }
If you had done that, bison would have been able to tell you that you cannot use both a %union
declaration and a fixed api.value.type
. If the semantic type is a union, it's a union. It cannot also be a shared_pointer
. Since you seem to want it to be a union whose members are both shared pointers, then it's a union and you don't want to define it otherwise.
If you do use #define YYSTYPE
, and also use %union
, then you'll find that the %union
never gets applied. %union
inserts a default definition of YYSTYPE
(as union YYSTYPE
), but your explicit definition of YYSTYPE
overrides that. But bison doesn't know you've done that -- it doesn't become apparent until the C compiler actually compiles the generated code -- so it rewrites semantic value references using the tags you provided in the %type
declarations. In other words, when you say %type <blockNode> input
, bison will automatically change any reference to $n
where that refers to an instance of the input non-terminal
by adding a field reference, as though you had written $n.blockNode
(which, of course, you must not do because bison has already added the field reference). But the #define
-overridden YYSTYPE
is not a union, it's a shared_pointer<Node>
, and shared_pointer<Node>
does not have a blockNode
member, as the C++ compiler error message indicates.
Similarly, in the rules for input
, the %type
declaration causes bison to emit code which will assign to the (non-existent) blockNode
member.
By way of illustrating my first point -- that you cannot use shared_pointer
as a semantic type or union member with the C code generator -- I "fixed" your code by applying the suggestion above (that is, remove the #define YYSTYPE
, and made a more or less minimal set of changes to avoid other bison and compiler errors, resulting in the following reduced reproducible example:
%code requires {
#include "tom_node.h"
#include <memory>
}
%code {
std::shared_ptr<Node> result;
void yyerror(const char* msg) {
std::cerr << msg << '\n';
}
int yylex();
}
%union {
std::shared_ptr<BlockNode> blockNode;
std::shared_ptr<Node> testNode;
}
%type <blockNode> input
%%
program : input { *result = *$1; /* ?? I don't know the actual intent */ }
input: '\n' { $$ = std::make_shared<BlockNode>();}
#ifndef TOM_NODE_H
#define TOM_NODE_H
#include <iostream>
#include <string>
class Node /*: public std::enable_shared_from_this<Node>*/ {
public:
std::string myString;
Node() {}
~Node() { std::cout << "destroyed" << myString << std::endl; }
};
class BlockNode : public Node {
public:
BlockNode() {
myString = "block node";
std::cout << "created" << myString << std::endl;
}
};
#endif
The result is a sequence of similar errors, all dealing with the fact that std::shared_pointer
is not a trivial type. Here's the first few:
$ bison -o tom.cc tom.yy
$ gcc -Wall -o tom tom.cc -ly
tom.cc:956:9: error: use of deleted function ‘YYSTYPE::YYSTYPE()’
YYSTYPE yylval;
^~~~~~
tom.cc:104:7: note: ‘YYSTYPE::YYSTYPE()’ is implicitly deleted because the default definition would be ill-formed:
union YYSTYPE
^~~~~~~
tom.yy:15:32: error: union member ‘YYSTYPE::blockNode’ with non-trivial ‘constexpr std::shared_ptr<_Tp>::shared_ptr() [with _Tp = BlockNode]’
std::shared_ptr<BlockNode> blockNode;
^~~~~~~~~
tom.yy:16:27: error: union member ‘YYSTYPE::testNode’ with non-trivial ‘constexpr std::shared_ptr<_Tp>::shared_ptr() [with _Tp = Node]’
std::shared_ptr<Node> testNode;
^~~~~~~~
tom.cc: In function ‘int yyparse()’:
tom.cc:985:30: error: use of deleted function ‘YYSTYPE::YYSTYPE()’
YYSTYPE yyvsa[YYINITDEPTH];
^