generate_initializers.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 from __future__ import print_function
3 import sys
4 import os
5 import re
6 
7 
8 def to_camel_cased(name):
9  r = re.compile(r"([_\/:][a-z]|[_\/:][0-9])")
10  ret = ""
11  first = True
12  tokens = r.split(name, 1)
13  while True:
14  if len(tokens) > 1:
15  if tokens[1][0] == "_":
16  if first:
17  ret += (
18  tokens[0][0].upper()
19  + tokens[0][1:].lower()
20  + tokens[1][1].upper()
21  )
22  else:
23  ret += tokens[0].lower() + tokens[1][1].upper()
24  else:
25  if first:
26  ret += (
27  tokens[0][0].upper()
28  + tokens[0][1:].lower()
29  + tokens[1][0]
30  + tokens[1][1].upper()
31  )
32  else:
33  ret += tokens[0].lower() + tokens[1][0] + tokens[1][1].upper()
34  tokens = r.split(tokens[2], 1)
35  else:
36  if first:
37  if len(tokens) > 1:
38  ret += tokens[0][0].upper() + tokens[0][1:].lower() + tokens[1][0]
39  else:
40  ret += tokens[0][0].upper() + tokens[0][1:].lower()
41  else:
42  ret += tokens[0].lower()
43  return ret
44  first = False
45 
46 
47 def to_underscores(name, num_pass=0):
48  r = re.compile(r"([A-Za-z][0-9]|[0-9][A-z]|[a-z][A-Z]|[A-Z][A-Z][a-z]|[_\/:][A-z])")
49  ret = ""
50  tokens = r.split(name, 1)
51  while True:
52  if len(tokens) > 1:
53  if (
54  tokens[1][0] == "/"
55  or tokens[1][0] == ":"
56  or tokens[1][0] == "_"
57  or tokens[1][1] == "_"
58  ):
59  ret += tokens[0].lower() + tokens[1][0].lower() + tokens[1][1:].lower()
60  else:
61  ret += (
62  tokens[0].lower()
63  + tokens[1][0].lower()
64  + "_"
65  + tokens[1][1:].lower()
66  )
67  tokens = r.split(tokens[2], 1)
68  else:
69  ret += tokens[0].lower()
70  if num_pass < 1:
71  return to_underscores(ret, num_pass + 1)
72  else:
73  return ret
74 
75 
76 def eprint(*args, **kwargs):
77  print(*args, file=sys.stderr, **kwargs)
78 
79 
80 def eprint(msg):
81  sys.stderr.write(msg + "\n")
82  sys.exit(2)
83 
84 
86  ret = ""
87  for d in data:
88  if "Required" in d:
89  ret += (
90  " " + d["Type"] + " _" + (d["Name"]) + default_argument_value(d) + ","
91  )
92  return ret[0:-1]
93 
94 
95 def constructor_list(data):
96  ret = ""
97  for d in data:
98  if "Required" in d:
99  ret += ",\n " + (d["Name"]) + "(_" + (d["Name"]) + ") "
100  return ret
101 
102 
103 def default_value(data):
104  if data["Value"] is None:
105  return ""
106  elif data["Value"] == "{}":
107  return ""
108  else:
109  return data["Value"]
110 
111 
113  if data["Value"] == None:
114  return ""
115  else:
116  return " = " + data["Value"]
117 
118 
119 def is_required(data):
120  if data["Required"]:
121  return "true"
122  else:
123  return "false"
124 
125 
127  ret = ""
128  for d in data:
129  if "Required" in d and not d["Required"]:
130  ret += ",\n " + (d["Name"]) + "(" + default_value(d) + ") "
131  return ret
132 
133 
135  for d in data:
136  if d["Required"]:
137  return True
138  return False
139 
140 
141 def declaration(data):
142  if "Required" in data:
143  return " " + data["Type"] + " " + (data["Name"]) + ";\n"
144  else:
145  return ""
146 
147 
148 def parser(type_in):
149  parser = ""
150  if type_in == "std::string":
151  return "boost::any_cast<" + type_in + ">(prop.Get())"
152  elif type_in == "exotica::Initializer" or type_in == "Initializer":
153  return "prop.IsInitializerVectorType()?boost::any_cast<std::vector<exotica::Initializer>>(prop.Get()).at(0):boost::any_cast<exotica::Initializer>(prop.Get())"
154  elif (
155  type_in == "std::vector<Initializer>"
156  or type_in == "std::vector<exotica::Initializer>"
157  ):
158  return "boost::any_cast<std::vector<exotica::Initializer>>(prop.Get())"
159  elif type_in == "Eigen::VectorXd":
160  parser = "ParseVector<double,Eigen::Dynamic>"
161  elif type_in == "Eigen::Vector4d":
162  parser = "ParseVector<double,4>"
163  elif type_in == "Eigen::Vector3d":
164  parser = "ParseVector<double,3>"
165  elif type_in == "Eigen::Vector2d":
166  parser = "ParseVector<double,2>"
167  elif type_in == "Eigen::VectorXi":
168  parser = "ParseVector<int,Eigen::Dynamic>"
169  elif type_in == "bool":
170  parser = "ParseBool"
171  elif type_in == "double":
172  parser = "ParseDouble"
173  elif type_in == "int":
174  parser = "ParseInt"
175  elif type_in == "std::vector<std::string>":
176  parser = "ParseList"
177  elif type_in == "std::vector<int>":
178  parser = "ParseIntList"
179  elif type_in == "std::vector<bool>":
180  parser = "ParseBoolList"
181  else:
182  eprint("Unknown data type '" + type_in + "'")
183  sys.exit(2)
184 
185  return (
186  "prop.IsStringType()?"
187  + parser
188  + "(boost::any_cast<std::string>(prop.Get())):boost::any_cast<"
189  + type_in
190  + ">(prop.Get())"
191  )
192 
193 
194 def copy(data):
195  if "Required" in data:
196  return (
197  ' ret.properties_.emplace("'
198  + data["Name"]
199  + '", Property("'
200  + data["Name"]
201  + '", '
202  + is_required(data)
203  + ", boost::any("
204  + (data["Name"])
205  + ")));\n"
206  )
207  else:
208  return ""
209 
210 
211 def add(data):
212  if "Required" in data:
213  return (
214  ' if (other.HasProperty("'
215  + data["Name"]
216  + '")) { const Property& prop = other.properties_.at("'
217  + data["Name"]
218  + '"); if(prop.IsSet()) {'
219  + (data["Name"])
220  + " = "
221  + parser(data["Type"])
222  + ";} }\n"
223  )
224  else:
225  return ""
226 
227 
228 def check(data, name):
229  if "Required" in data and data["Required"]:
230  return (
231  ' if(!other.HasProperty("'
232  + data["Name"]
233  + '") || !other.properties_.at("'
234  + data["Name"]
235  + '").IsSet()) ThrowPretty("Initializer '
236  + name
237  + " requires property "
238  + data["Name"]
239  + ' to be set!");\n'
240  )
241  else:
242  return ""
243 
244 
245 def construct(namespace, class_name_orig, data, include):
246  class_name = class_name_orig + "Initializer"
247  ret = (
248  """// This file was automatically generated. Do not edit this file!
249 #ifndef INITIALIZER_"""
250  + namespace.upper()
251  + "_"
252  + to_underscores(class_name).upper()
253  + """_H
254 #define INITIALIZER_"""
255  + namespace.upper()
256  + "_"
257  + to_underscores(class_name).upper()
258  + """_H
259 
260 #include <exotica_core/property.h>
261 
262 namespace exotica
263 {
264 inline std::vector<Initializer> Get"""
265  + to_camel_cased(namespace)
266  + """Initializers();
267 }
268 
269 namespace exotica
270 {
271 
272 class """
273  + class_name
274  + " : public InitializerBase"
275  + """
276 {
277 public:
278  static std::string GetContainerName() { return """
279  + '"exotica/'
280  + class_name_orig
281  + '"'
282  + """; }
283 
284  """
285  )
286  if needs_default_constructor(data):
287  ret += (
288  class_name
289  + "() : InitializerBase()"
291  + """
292  {
293  }
294 
295  """
296  )
297  ret += (
298  class_name
299  + "("
301  + ") : InitializerBase()"
302  + constructor_list(data)
303  + """
304  {
305  }
306 
307  """
308  + class_name
309  + """(const Initializer& other) : """
310  + class_name
311  + """()
312  {
313 """
314  )
315  for d in data:
316  ret += add(d)
317  ret += (
318  """ }
319 
320  virtual ~"""
321  + class_name
322  + """()
323  {
324  }
325 
326  virtual Initializer GetTemplate() const
327  {
328  return (Initializer)"""
329  + class_name
330  + """();
331  }
332 
333  virtual std::vector<Initializer> GetAllTemplates() const
334  {
335  return Get"""
336  + to_camel_cased(namespace)
337  + """Initializers();
338  }
339 
340  virtual void Check(const Initializer& other) const
341  {
342 """
343  )
344  for d in data:
345  ret += check(d, class_name)
346  ret += """ }
347 
348  operator Initializer()
349  {
350  Initializer ret(GetContainerName());
351 """
352  for d in data:
353  ret += copy(d)
354  ret += """ return ret;
355  }
356 
357 """
358  for d in data:
359  ret += declaration(d)
360  ret += (
361  "};"
362  + """
363 
364 }
365 
366 #include <"""
367  + namespace
368  + "/"
369  + namespace
370  + """_initializers_numerator.h>
371 
372 """
373  )
374  for i in include:
375  ret += "#include <" + i + ".h>\n"
376  ret += """
377 #endif"""
378  return ret
379 
380 
381 def parse_line(line, line_number, function_name):
382  # ignore lines with comments, otherwise comments including ';' will not be recognised
383  if line.startswith("//"):
384  return None
385 
386  last = line.find(";")
387  if last >= 0:
388  line = line[0:last].strip()
389  else:
390  last = line.find("//")
391  if last >= 0:
392  line = line[0:last].strip()
393  else:
394  line = line.strip()
395 
396  if len(line) == 0:
397  return None
398 
399  if line.startswith("include"):
400  return {
401  "Include": line[7:].strip().strip(">").strip("<").strip('"'),
402  "Code": line.strip(),
403  }
404  if line.startswith("extend"):
405  return {
406  "Extends": line[6:].strip().strip(">").strip("<").strip('"'),
407  "Code": line.strip(),
408  }
409  if line.startswith("class"):
410  return {
411  "ClassName": line[5:].strip().strip(">").strip("<").strip('"'),
412  "Code": line.strip(),
413  }
414 
415  if last == -1:
416  eprint("Can't find ';' in '" + function_name + "', on line " + str(line_number))
417  sys.exit(2)
418 
419  required = True
420  if line.startswith("Required"):
421  required = True
422  elif line.startswith("Optional"):
423  required = False
424  else:
425  eprint(
426  "Can't parse 'Required/Optional' tag in '"
427  + function_name
428  + "', on line "
429  + str(line_number)
430  )
431  sys.exit(2)
432 
433  value = None
434  field_type = ""
435  name = ""
436  if not required:
437  eq = line.find("=")
438  has_default_arg = not (eq == -1)
439  if has_default_arg:
440  value = line[eq + 1 : last]
441  else:
442  # we need this to get the parameter name
443  eq = last
444  name_start = line[0:eq].strip().rfind(" ")
445  name = line[name_start:eq].strip()
446  field_type = line[9:name_start].strip()
447  if not has_default_arg:
448  eprint("Optional parameter '" + name + "' requires a default argument!")
449  else:
450  name_start = line[0:last].strip().rfind(" ")
451  name = line[name_start:last].strip()
452  field_type = line[9:name_start].strip()
453 
454  return {"Required": required, "Type": field_type, "Name": name, "Value": value}
455 
456 
457 def parse_file(file_name):
458  with open(file_name) as f:
459  lines = f.readlines()
460  data = []
461  include = []
462  extends = []
463  names = []
464  i = 0
465  optionalOnly = False
466  for l in lines:
467  i = i + 1
468  d = parse_line(l, i, file_name)
469  if d != None:
470  if "Required" in d:
471  if d["Required"] == False:
472  optionalOnly = True
473  else:
474  if optionalOnly:
475  eprint(
476  "Required properties_ have to come before Optional ones, in '"
477  + file_name
478  + "', on line "
479  + str(i)
480  )
481  sys.exit(2)
482  data.append(d)
483  if "Include" in d:
484  include.append(d["Include"])
485  if "Extends" in d:
486  extends.append(d["Extends"])
487  if "ClassName" in d:
488  names.append(d["ClassName"])
489  if len(names) != 1:
490  eprint("Could not parse initializer class name in '" + file_name + "'!")
491  eprint(names)
492  sys.exit(2)
493  return {"Data": data, "Include": include, "Extends": extends, "ClassName": names[0]}
494 
495 
496 def contains_data(type_name, name, list_in):
497  for d in list_in:
498  if d["Type"] == type_name and d["Name"] == name:
499  return d["Class"]
500  return False
501 
502 
503 def contains_include(name, list_in):
504  for d in list_in:
505  if d == name:
506  return True
507  return False
508 
509 
510 def contains_extends(name, list_in):
511  for d in list_in:
512  if d == name:
513  return True
514  return False
515 
516 
517 def collect_extensions(input_files, search_dirs, content):
518  file_content = parse_file(input_files)
519  class_name = file_content["ClassName"]
520  if "Extends" in file_content:
521  for e in file_content["Extends"]:
522  if not contains_extends(e, content["Extends"]):
523  file_name = None
524  ext = e.split("/")
525  for d in search_dirs:
526  ff = d + "/share/" + ext[0] + "/init/" + ext[1] + ".in"
527  if os.path.isfile(ff):
528  file_name = ff
529  break
530  if not file_name:
531  eprint("Cannot find extension '" + e + "'!")
532  content["Extends"].append(e)
533  content = collect_extensions(file_name, search_dirs, content)
534 
535  if "Data" in file_content:
536  for d in file_content["Data"]:
537  cls = contains_data(d["Type"], d["Name"], content["Data"])
538  if cls:
539  for e in content["Data"]:
540  if e["Name"] == d["Name"]:
541  e["Value"] = d["Value"]
542  else:
543  d["Class"] = class_name
544  content["Data"].append(d)
545  if "Include" in file_content:
546  for i in file_content["Include"]:
547  if not contains_include(i, content["Include"]):
548  content["Include"].append(i)
549 
550  content["ClassName"] = class_name
551  return content
552 
553 
554 def sort_data(data):
555  a = []
556  b = []
557  for d in data:
558  if d["Required"]:
559  a.append(d)
560  else:
561  b.append(d)
562  return a + b
563 
564 
565 def generate(input_files, output_files, namespace, search_dirs, devel_dir):
566  print("Generating " + output_files)
567  content = collect_extensions(
568  input_files, search_dirs, {"Data": [], "Include": [], "Extends": []}
569  )
570  txt = construct(
571  namespace, content["ClassName"], sort_data(content["Data"]), content["Include"]
572  )
573  path = os.path.dirname(output_files)
574  if not os.path.exists(path):
575  os.makedirs(path)
576  with open(output_files, "w") as f:
577  f.write(txt)
578  return content["ClassName"]
579 
580 
581 def create_class_init_header(class_inits, file_name):
582  ret = (
583  """// This file was automatically generated. Do not edit this file!
584 #ifndef INITIALIZE_PROJECT_HEADER_"""
585  + namespace.upper()
586  + """_H_$
587 #define INITIALIZE_PROJECT_HEADER_"""
588  + namespace.upper()
589  + """_H_$
590 
591 #include <exotica_core/property.h>
592 """
593  )
594  for init in class_inits:
595  ret += "#include <" + namespace + "/" + init[0] + "_initializer.h>\n"
596  ret += (
597  """
598 
599 namespace exotica
600 {
601 
602 inline std::vector<Initializer> Get"""
603  + to_camel_cased(namespace)
604  + """Initializers()
605 {
606  std::vector<Initializer> ret;
607 """
608  )
609  for init in class_inits:
610  ret += " ret.push_back(" + init[1] + "Initializer().GetTemplate());\n"
611  ret += """ return ret;
612 }
613 
614 }
615 
616 #endif
617 """
618  path = os.path.dirname(file_name)
619  if not os.path.exists(path):
620  os.makedirs(path)
621  with open(file_name, "w") as f:
622  f.write(ret)
623 
624 
625 if __name__ == "__main__":
626  if len(sys.argv) > 5:
627  offset = 5
628  n = int((len(sys.argv) - offset) / 2)
629  namespace = sys.argv[1]
630  search_dirs = sys.argv[2].split(":")
631  devel_dir = sys.argv[3]
632  class_inits_header_file = sys.argv[4]
633  if not os.path.exists(devel_dir + "/init"):
634  os.makedirs(devel_dir + "/init")
635 
636  for i in range(0, n):
637  input_files = sys.argv[offset + i]
638  class_name = os.path.basename(sys.argv[offset + i][0:-3])
639  with open(input_files, "r") as fi:
640  with open(devel_dir + "/init/" + class_name + ".in", "w") as f:
641  f.write(fi.read())
642 
643  class_inits = []
644  for i in range(0, n):
645  input_files = sys.argv[offset + i]
646  output_files = sys.argv[offset + n + i]
647  class_file_name = os.path.basename(sys.argv[offset + i][0:-3])
648  class_inits.append(
649  (
650  class_file_name,
651  generate(
652  input_files, output_files, namespace, search_dirs, devel_dir
653  ),
654  )
655  )
656 
657  create_class_init_header(class_inits, class_inits_header_file)
658  else:
659  eprint("Initializer generation failure: invalid arguments!")
660  sys.exit(1)
def create_class_init_header(class_inits, file_name)
def contains_extends(name, list_in)
def collect_extensions(input_files, search_dirs, content)
def generate(input_files, output_files, namespace, search_dirs, devel_dir)
def construct(namespace, class_name_orig, data, include)
def contains_include(name, list_in)
def parse_line(line, line_number, function_name)
def contains_data(type_name, name, list_in)
def to_underscores(name, num_pass=0)


exotica_core
Author(s): Yiming Yang, Michael Camilleri
autogenerated on Sat Apr 10 2021 02:34:49