tree-gen
C++ code generator for tree structures
tree-gen.cpp
Go to the documentation of this file.
1 
5 #include "tree-gen.hpp"
6 #include "tree-gen-cpp.hpp"
7 #include "tree-gen-python.hpp"
8 #include "parser.hpp"
9 #include "lexer.hpp"
10 
11 namespace tree_gen {
12 
16 std::vector<Field> Node::all_fields() const {
17  std::vector<Field> children = this->fields;
18  if (parent) {
19  auto from_parent = parent->all_fields();
20  children.insert(children.end(), from_parent.begin(), from_parent.end());
21  }
22  if (order.empty()) {
23  return children;
24  }
25  std::vector<Field> reordered;
26  for (const auto &name : order) {
27  bool done = false;
28  for (auto it = children.begin(); it != children.end(); ++it) {
29  if (it->name == name) {
30  reordered.push_back(*it);
31  children.erase(it);
32  done = true;
33  break;
34  }
35  }
36  if (!done) {
37  throw std::runtime_error("Unknown field in field order: " + name);
38  }
39  }
40  reordered.insert(reordered.end(), children.begin(), children.end());
41  return reordered;
42 }
43 
48 std::string replace_all(std::string str, const std::string &from, const std::string &to) {
49  size_t start_pos = 0;
50  while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
51  str.replace(start_pos, from.length(), to);
52  start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
53  }
54  return str;
55 }
56 
60 NodeBuilder::NodeBuilder(const std::string &name, const std::string &doc) {
61  node = std::make_shared<Node>();
62  node->snake_case_name = name;
63  node->doc = doc;
64  node->is_error_marker = false;
65 
66  // Generate title case name.
67  auto snake_ss = std::stringstream(name);
68  auto title_ss = std::ostringstream();
69  std::string token;
70  while (std::getline(snake_ss, token, '_')) {
71  title_ss << (char)std::toupper(token[0]) << token.substr(1);
72  }
73  node->title_case_name = title_ss.str();
74 }
75 
79 NodeBuilder *NodeBuilder::derive_from(std::shared_ptr<Node> parent) {
80  node->parent = parent;
81  parent->derived.push_back(node);
82  return this;
83 }
84 
89  EdgeType type,
90  const std::string &node_name,
91  const std::string &name,
92  const std::string &doc
93 ) {
94  auto child = Field();
95  child.type = type;
96  child.prim_type = node_name;
97  child.py_prim_type = "";
98  child.py_multi_type = "";
99  child.name = name;
100  child.doc = doc;
101  child.ext_type = type;
102  node->fields.push_back(std::move(child));
103  return this;
104 }
105 
110  const std::string &prim,
111  const std::string &name,
112  const std::string &doc,
113  EdgeType type
114 ) {
115  auto child = Field();
116  child.type = Prim;
117  switch (type) {
118  case Maybe: child.prim_type = "Maybe<" + prim + ">"; break;
119  case One: child.prim_type = "One<" + prim + ">"; break;
120  case Any: child.prim_type = "Any<" + prim + ">"; break;
121  case Many: child.prim_type = "Many<" + prim + ">"; break;
122  case OptLink: child.prim_type = "OptLink<" + prim + ">"; break;
123  case Link: child.prim_type = "Link<" + prim + ">"; break;
124  default: child.prim_type = prim; break;
125  }
126  child.py_prim_type = replace_all(prim, "::", ".");
127  auto pos = child.py_prim_type.find_last_of('.');
128  if (pos == std::string::npos) {
129  child.py_multi_type = "Multi" + child.py_prim_type;
130  } else {
131  child.py_multi_type = child.py_prim_type.substr(0, pos + 1)
132  + "Multi"
133  + child.py_prim_type.substr(pos + 1);
134  }
135  child.name = name;
136  child.doc = doc;
137  child.ext_type = type;
138  node->fields.push_back(std::move(child));
139  return this;
140 }
141 
146 NodeBuilder *NodeBuilder::with_order(std::list<std::string> &&order) {
147  node->order = std::move(order);
148  return this;
149 }
150 
155  node->is_error_marker = true;
156  return this;
157 }
158 
162 void Specification::set_source_doc(const std::string &doc) {
163  source_doc = doc;
164 }
165 
169 void Specification::set_header_doc(const std::string &doc) {
170  header_doc = doc;
171 }
172 
176 void Specification::set_header_fname(const std::string &fname) {
177  header_fname = fname;
178 }
179 
183 void Specification::set_python_doc(const std::string &doc) {
184  python_doc = doc;
185 }
186 
190 void Specification::set_tree_namespace(const std::string &name_space) {
191  if (!tree_namespace.empty()) {
192  throw std::runtime_error("duplicate tree namespace declaration");
193  }
194  tree_namespace = name_space;
195 }
196 
200 void Specification::set_support_namespace(const std::string &name_space) {
201  if (!support_namespace.empty()) {
202  throw std::runtime_error("duplicate tree namespace declaration");
203  }
204  support_namespace = name_space;
205 }
206 
210 void Specification::set_initialize_function(const std::string &init_fn) {
211  if (!initialize_function.empty()) {
212  throw std::runtime_error("duplicate initialization function declaration");
213  }
214  initialize_function = init_fn;
215 }
216 
220 void Specification::set_serdes_functions(const std::string &ser_fn, const std::string &des_fn) {
221  if (!serialize_fn.empty()) {
222  throw std::runtime_error("duplicate serialize/deserialize function declaration");
223  }
224  serialize_fn = ser_fn;
225  py_serialize_fn = replace_all(ser_fn, "::", ".");
226  deserialize_fn = des_fn;
227  py_deserialize_fn = replace_all(des_fn, "::", ".");
228 }
229 
233 void Specification::set_source_location(const std::string &ident) {
234  if (!source_location.empty()) {
235  throw std::runtime_error("duplicate source location object declaration");
236  }
237  source_location = ident;
238 }
239 
243 void Specification::add_include(const std::string &include) {
244  includes.push_back(include);
245 }
246 
250 void Specification::add_src_include(const std::string &include) {
251  src_includes.push_back(include);
252 }
253 
257 void Specification::add_python_include(const std::string &include) {
258  python_includes.push_back(include);
259 }
260 
264 void Specification::add_namespace(const std::string &name_space, const std::string &doc) {
265  namespaces.push_back(name_space);
266  if (!doc.empty()) {
267  namespace_doc = doc;
268  }
269 }
270 
274 void Specification::add_node(std::shared_ptr<NodeBuilder> &node_builder) {
275  auto name = node_builder->node->snake_case_name;
276  if (builders.count(name)) {
277  throw std::runtime_error("duplicate node name " + name);
278  }
279  builders.insert(std::make_pair(name, node_builder));
280 }
281 
286  if (initialize_function.empty()) {
287  throw std::runtime_error("initialization function not specified");
288  }
289  if (support_namespace.empty()) {
290  support_namespace = "::tree";
291  }
292  for (auto &it : builders) {
293  for (auto &child : it.second->node->fields) {
294  if (child.type != Prim) {
295  auto name = child.prim_type;
296  child.prim_type = "";
297  auto nb_it = builders.find(name);
298  if (nb_it == builders.end()) {
299  throw std::runtime_error("use of undefined node " + name);
300  }
301  child.node_type = nb_it->second->node;
302  }
303  }
304  nodes.push_back(it.second->node);
305  }
306 }
307 
308 }
309 
313 int main(
314  int argc,
315  char *argv[]
316 ) {
317  using namespace tree_gen;
318 
319  // Check command line and open files.
320  if (argc < 4 || argc > 5) {
321  std::cerr << "Usage: tree-gen <spec-file> <header-file> <source-file> [python-file]" << std::endl;
322  return 1;
323  }
324 
325  // Create the scanner.
326  yyscan_t scanner = nullptr;
327  int retcode = yylex_init(&scanner);
328  if (retcode) {
329  std::cerr << "Failed to construct scanner: " << strerror(retcode) << std::endl;
330  return 1;
331  }
332 
333  // Try to open the file and read it to an internal string.
334  auto filename = std::string(argv[1]);
335  FILE *fptr = fopen(filename.c_str(), "r");
336  if (!fptr) {
337  std::cerr << "Failed to open input file " << filename << ": " << strerror(errno) << std::endl;
338  return 1;
339  }
340  yyset_in(fptr, scanner);
341 
342  // Do the actual parsing.
343  Specification specification;
344  retcode = yyparse(scanner, specification);
345  if (retcode == 2) {
346  std::cerr << "Out of memory while parsing " << filename << std::endl;
347  return 1;
348  } else if (retcode) {
349  std::cerr << "Failed to parse " << filename << std::endl;
350  return 1;
351  }
352  try {
353  specification.build();
354  } catch (std::exception &e) {
355  std::cerr << "Analysis error: " << e.what() << std::endl;
356  return 1;
357  }
358 
359  // Clean up.
360  yylex_destroy(scanner);
361  fclose(fptr);
362 
363  // Generate C++ code.
364  cpp::generate(argv[2], argv[3], specification);
365 
366  // Generate Python code if requested.
367  if (argc >= 5) {
368  python::generate(argv[4], specification);
369  }
370 
371  return 0;
372 }
void set_source_location(const std::string &ident)
Sets the source location object.
Definition: tree-gen.cpp:233
void set_initialize_function(const std::string &init_fn)
Sets the initialization function.
Definition: tree-gen.cpp:210
std::vector< Field > all_fields() const
Gathers all child nodes, including those in parent classes.
Definition: tree-gen.cpp:16
Exactly one node.
Definition: tree-gen.hpp:645
Header file for tree-gen-python.cpp.
NodeBuilder * mark_error()
Indicate that this node marks a recovered parse error.
Definition: tree-gen.cpp:154
void set_header_fname(const std::string &fname)
Sets the header filename for the #include directive.
Definition: tree-gen.cpp:176
Struct containing everything needed for a complete specification.
Definition: tree-gen.hpp:850
NodeBuilder * with_prim(const std::string &prim, const std::string &name, const std::string &doc="", EdgeType type=Prim)
Adds a child primitive.
Definition: tree-gen.cpp:109
void add_include(const std::string &include)
Adds an include statement to the header file.
Definition: tree-gen.cpp:243
Zero or one nodes.
Definition: tree-gen.hpp:640
void add_node(std::shared_ptr< NodeBuilder > &node_builder)
Adds the given node.
Definition: tree-gen.cpp:274
Namespace for the tree-gen program.
void generate(const std::string &header_filename, const std::string &source_filename, Specification &specification)
Generate the complete C++ code (source and header).
NodeBuilder * with_order(std::list< std::string > &&order)
Sets the order in which the parameters must appear in the dumps and constructor.
Definition: tree-gen.cpp:146
void set_header_doc(const std::string &doc)
Sets the header file documentation.
Definition: tree-gen.cpp:169
void set_support_namespace(const std::string &name_space)
Sets the support namespace.
Definition: tree-gen.cpp:200
NodeBuilder * derive_from(std::shared_ptr< Node > parent)
Marks this node as deriving from the given node type.
Definition: tree-gen.cpp:79
Link to zero or one nodes elsewhere in the tree.
Definition: tree-gen.hpp:660
NodeBuilder * with_child(EdgeType type, const std::string &node_name, const std::string &name, const std::string &doc="")
Adds a child node.
Definition: tree-gen.cpp:88
EdgeType
Types of edges between nodes and primitives.
Definition: tree-gen.hpp:635
Primitive type.
Definition: tree-gen.hpp:670
Header file for tree-gen-cpp.cpp.
void generate(const std::string &python_filename, Specification &specification)
Generates the complete Python code.
std::string doc
Class documentation.
Definition: tree-gen.hpp:748
void build()
Checks for errors, resolves node names, and builds the nodes vector.
Definition: tree-gen.cpp:285
void add_namespace(const std::string &name_space, const std::string &doc="")
Adds a namespace level.
Definition: tree-gen.cpp:264
Represents a field.
Definition: tree-gen.hpp:679
int main(int argc, char *argv[])
Main function for generating the the header and source file for a tree.
Definition: tree-gen.cpp:313
One or more nodes.
Definition: tree-gen.hpp:655
std::string replace_all(std::string str, const std::string &from, const std::string &to)
Convenience method for replacing all occurrences of a substring in a string with another string...
Definition: tree-gen.cpp:48
Convenience class for constructing a node.
Definition: tree-gen.hpp:796
void set_tree_namespace(const std::string &name_space)
Sets the tree namespace.
Definition: tree-gen.cpp:190
NodeBuilder(const std::string &name, const std::string &doc="")
Construct a node with the given snake_case name and class documentation.
Definition: tree-gen.cpp:60
std::vector< Field > fields
Child nodes.
Definition: tree-gen.hpp:763
Header file for tree-gen.
std::list< std::string > order
Optional override for field order as returned by all_fields().
Definition: tree-gen.hpp:768
void add_src_include(const std::string &include)
Adds an include statement to the source file.
Definition: tree-gen.cpp:250
void set_source_doc(const std::string &doc)
Sets the source file documentation.
Definition: tree-gen.cpp:162
void set_python_doc(const std::string &doc)
Sets the Python file documentation.
Definition: tree-gen.cpp:183
Zero or more nodes.
Definition: tree-gen.hpp:650
std::shared_ptr< Node > parent
The node type this is derived from, if any.
Definition: tree-gen.hpp:753
void add_python_include(const std::string &include)
Adds an import statement to the Python file.
Definition: tree-gen.cpp:257
void set_serdes_functions(const std::string &ser_fn, const std::string &des_fn)
Sets the serialization/deserialization functions.
Definition: tree-gen.cpp:220
Link to exactly one node elsewhere in the tree.
Definition: tree-gen.hpp:665