CppGenerator.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. #!/usr/bin/env python3
  2. # -*- Coding: UTF-8 -*-
  3. # ---------------------------------------------------------------------------
  4. # Open Asset Import Library (ASSIMP)
  5. # ---------------------------------------------------------------------------
  6. #
  7. # Copyright (c) 2006-2020, ASSIMP Development Team
  8. #
  9. # All rights reserved.
  10. #
  11. # Redistribution and use of this software in source and binary forms,
  12. # with or without modification, are permitted provided that the following
  13. # conditions are met:
  14. #
  15. # * Redistributions of source code must retain the above
  16. # copyright notice, this list of conditions and the
  17. # following disclaimer.
  18. #
  19. # * Redistributions in binary form must reproduce the above
  20. # copyright notice, this list of conditions and the
  21. # following disclaimer in the documentation and/or other
  22. # materials provided with the distribution.
  23. #
  24. # * Neither the name of the ASSIMP team, nor the names of its
  25. # contributors may be used to endorse or promote products
  26. # derived from this software without specific prior
  27. # written permission of the ASSIMP Development Team.
  28. #
  29. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  30. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  31. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  32. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  33. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  34. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  35. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  36. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  37. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  38. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  39. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  40. # ---------------------------------------------------------------------------
  41. """Generate the C++ glue code needed to map EXPRESS to C++"""
  42. import sys, os, re
  43. if sys.version_info < (3, 0):
  44. print("must use python 3.0 or greater")
  45. sys.exit(-2)
  46. use_ifc_template = False
  47. input_step_template_h = 'StepReaderGen.h.template'
  48. input_step_template_cpp = 'StepReaderGen.cpp.template'
  49. input_ifc_template_h = 'IFCReaderGen.h.template'
  50. input_ifc_template_cpp = 'IFCReaderGen.cpp.template'
  51. cpp_keywords = "class"
  52. output_file_h = ""
  53. output_file_cpp = ""
  54. if (use_ifc_template ):
  55. input_template_h = input_ifc_template_h
  56. input_template_cpp = input_ifc_template_cpp
  57. output_file_h = os.path.join('..','..','code','IFCReaderGen.h')
  58. output_file_cpp = os.path.join('..','..','code','IFCReaderGen.cpp')
  59. else:
  60. input_template_h = input_step_template_h
  61. input_template_cpp = input_step_template_cpp
  62. output_file_h = os.path.join('..','..','code/Importer/StepFile','StepReaderGen.h')
  63. output_file_cpp = os.path.join('..','..','code/Importer/StepFile','StepReaderGen.cpp')
  64. template_entity_predef = '\tstruct {entity};\n'
  65. template_entity_predef_ni = '\ttypedef NotImplemented {entity}; // (not currently used by Assimp)\n'
  66. template_entity = r"""
  67. // C++ wrapper for {entity}
  68. struct {entity} : {parent} ObjectHelper<{entity},{argcnt}> {{ {entity}() : Object("{entity}") {{}}
  69. {fields}
  70. }};"""
  71. template_entity_ni = ''
  72. template_type = r"""
  73. // C++ wrapper type for {type}
  74. typedef {real_type} {type};"""
  75. template_stub_decl = '\tDECL_CONV_STUB({type});\n'
  76. template_schema = '\t\tSchemaEntry("{normalized_name}",&STEP::ObjectHelper<{type},{argcnt}>::Construct )\n'
  77. template_schema_type = '\t\tSchemaEntry("{normalized_name}",nullptr )\n'
  78. template_converter = r"""
  79. // -----------------------------------------------------------------------------------------------------------
  80. template <> size_t GenericFill<{type}>(const DB& db, const LIST& params, {type}* in)
  81. {{
  82. {contents}
  83. }}"""
  84. template_converter_prologue_a = '\tsize_t base = GenericFill(db,params,static_cast<{parent}*>(in));\n'
  85. template_converter_prologue_b = '\tsize_t base = 0;\n'
  86. template_converter_check_argcnt = '\tif (params.GetSize() < {max_arg}) {{ throw STEP::TypeError("expected {max_arg} arguments to {name}"); }}'
  87. template_converter_code_per_field = r""" do {{ // convert the '{fieldname}' argument
  88. std::shared_ptr<const DataType> arg = params[base++];{handle_unset}{convert}
  89. }} while(0);
  90. """
  91. template_allow_optional = r"""
  92. if (dynamic_cast<const UNSET*>(&*arg)) break;"""
  93. template_allow_derived = r"""
  94. if (dynamic_cast<const ISDERIVED*>(&*arg)) {{ in->ObjectHelper<Assimp::IFC::{type},{argcnt}>::aux_is_derived[{argnum}]=true; break; }}"""
  95. template_convert_single = r"""
  96. try {{ GenericConvert( in->{name}, arg, db ); break; }}
  97. catch (const TypeError& t) {{ throw TypeError(t.what() + std::string(" - expected argument {argnum} to {classname} to be a `{full_type}`")); }}"""
  98. template_converter_omitted = '// this data structure is not used yet, so there is no code generated to fill its members\n'
  99. template_converter_epilogue = '\treturn base;'
  100. import ExpressReader
  101. def get_list_bounds(collection_spec):
  102. start,end = [(int(n) if n!='?' else 0) for n in re.findall(r'(\d+|\?)',collection_spec)]
  103. return start,end
  104. def get_cpp_type(field,schema):
  105. isobjref = field.type in schema.entities
  106. base = field.type
  107. if isobjref:
  108. base = 'Lazy< '+(base if base in schema.whitelist else 'NotImplemented')+' >'
  109. if field.collection:
  110. start,end = get_list_bounds(field.collection)
  111. base = 'ListOf< {0}, {1}, {2} >'.format(base,start,end)
  112. if not isobjref:
  113. base += '::Out'
  114. if field.optional:
  115. base = 'Maybe< '+base+' >'
  116. return base
  117. def generate_fields(entity,schema):
  118. fields = []
  119. for e in entity.members:
  120. fields.append('\t\t{type} {name};'.format(type=get_cpp_type(e,schema),name=e.name))
  121. return '\n'.join(fields)
  122. def handle_unset_args(field,entity,schema,argnum):
  123. n = ''
  124. # if someone derives from this class, check for derived fields.
  125. if any(entity.name==e.parent for e in schema.entities.values()):
  126. n += template_allow_derived.format(type=entity.name,argcnt=len(entity.members),argnum=argnum)
  127. if not field.optional:
  128. return n+''
  129. return n+template_allow_optional.format()
  130. def get_single_conversion(field,schema,argnum=0,classname='?'):
  131. name = field.name
  132. return template_convert_single.format(name=name,argnum=argnum,classname=classname,full_type=field.fullspec)
  133. def count_args_up(entity,schema):
  134. return len(entity.members) + (count_args_up(schema.entities[entity.parent],schema) if entity.parent else 0)
  135. def resolve_base_type(base,schema):
  136. if base in ('INTEGER','REAL','STRING','ENUMERATION','BOOLEAN','NUMBER', 'SELECT','LOGICAL'):
  137. return base
  138. if base in schema.types:
  139. return resolve_base_type(schema.types[base].equals,schema)
  140. print(base)
  141. return None
  142. def gen_type_struct(typen,schema):
  143. base = resolve_base_type(typen.equals,schema)
  144. if not base:
  145. return ''
  146. if typen.aggregate:
  147. start,end = get_list_bounds(typen.aggregate)
  148. base = 'ListOf< {0}, {1}, {2} >'.format(base,start,end)
  149. return template_type.format(type=typen.name,real_type=base)
  150. def gen_converter(entity,schema):
  151. max_arg = count_args_up(entity,schema)
  152. arg_idx = arg_idx_ofs = max_arg - len(entity.members)
  153. code = template_converter_prologue_a.format(parent=entity.parent) if entity.parent else template_converter_prologue_b
  154. if entity.name in schema.blacklist_partial:
  155. return code+template_converter_omitted+template_converter_epilogue;
  156. if max_arg > 0:
  157. code +=template_converter_check_argcnt.format(max_arg=max_arg,name=entity.name)
  158. for field in entity.members:
  159. code += template_converter_code_per_field.format(fieldname=field.name,
  160. handle_unset=handle_unset_args(field,entity,schema,arg_idx-arg_idx_ofs),
  161. convert=get_single_conversion(field,schema,arg_idx,entity.name))
  162. arg_idx += 1
  163. return code+template_converter_epilogue
  164. def get_base_classes(e,schema):
  165. def addit(e,out):
  166. if e.parent:
  167. out.append(e.parent)
  168. addit(schema.entities[e.parent],out)
  169. res = []
  170. addit(e,res)
  171. return list(reversed(res))
  172. def get_derived(e,schema):
  173. def get_deriv(e,out): # bit slow, but doesn't matter here
  174. s = [ee for ee in schema.entities.values() if ee.parent == e.name]
  175. for sel in s:
  176. out.append(sel.name)
  177. get_deriv(sel,out)
  178. res = []
  179. get_deriv(e,res)
  180. return res
  181. def get_hierarchy(e,schema):
  182. return get_derived(e, schema)+[e.name]+get_base_classes(e,schema)
  183. def sort_entity_list(schema):
  184. deps = []
  185. entities = schema.entities
  186. for e in entities.values():
  187. deps += get_base_classes(e,schema)+[e.name]
  188. checked = []
  189. for e in deps:
  190. if e not in checked:
  191. checked.append(e)
  192. return [entities[e] for e in checked]
  193. def work(filename):
  194. schema = ExpressReader.read(filename,silent=True)
  195. entities, stub_decls, schema_table, converters, typedefs, predefs = '','',[],'','',''
  196. entitylist = 'ifc_entitylist.txt'
  197. if not use_ifc_template:
  198. entitylist = 'step_entitylist.txt'
  199. whitelist = []
  200. with open(entitylist, 'rt') as inp:
  201. whitelist = [n.strip() for n in inp.read().split('\n') if n[:1]!='#' and n.strip()]
  202. schema.whitelist = set()
  203. schema.blacklist_partial = set()
  204. for ename in whitelist:
  205. try:
  206. e = schema.entities[ename]
  207. except KeyError:
  208. # type, not entity
  209. continue
  210. for base in [e.name]+get_base_classes(e,schema):
  211. schema.whitelist.add(base)
  212. for base in get_derived(e,schema):
  213. schema.blacklist_partial.add(base)
  214. schema.blacklist_partial -= schema.whitelist
  215. schema.whitelist |= schema.blacklist_partial
  216. # Generate list with reserved keywords from c++
  217. cpp_types = cpp_keywords.split(',')
  218. # uncomment this to disable automatic code reduction based on whitelisting all used entities
  219. # (blacklisted entities are those who are in the whitelist and may be instanced, but will
  220. # only be accessed through a pointer to a base-class.
  221. #schema.whitelist = set(schema.entities.keys())
  222. #schema.blacklist_partial = set()
  223. for ntype in schema.types.values():
  224. typedefs += gen_type_struct(ntype,schema)
  225. schema_table.append(template_schema_type.format(normalized_name=ntype.name.lower()))
  226. sorted_entities = sort_entity_list(schema)
  227. for entity in sorted_entities:
  228. parent = entity.parent+',' if entity.parent else ''
  229. if ( entity.name in cpp_types ):
  230. entity.name = entity.name + "_t"
  231. print( "renaming " + entity.name)
  232. if entity.name in schema.whitelist:
  233. converters += template_converter.format(type=entity.name,contents=gen_converter(entity,schema))
  234. schema_table.append(template_schema.format(type=entity.name,normalized_name=entity.name.lower(),argcnt=len(entity.members)))
  235. entities += template_entity.format(entity=entity.name,argcnt=len(entity.members),parent=parent,fields=generate_fields(entity,schema))
  236. predefs += template_entity_predef.format(entity=entity.name)
  237. stub_decls += template_stub_decl.format(type=entity.name)
  238. else:
  239. entities += template_entity_ni.format(entity=entity.name)
  240. predefs += template_entity_predef_ni.format(entity=entity.name)
  241. schema_table.append(template_schema.format(type="NotImplemented",normalized_name=entity.name.lower(),argcnt=0))
  242. schema_table = ','.join(schema_table)
  243. with open(input_template_h,'rt') as inp:
  244. with open(output_file_h,'wt') as outp:
  245. # can't use format() here since the C++ code templates contain single, unescaped curly brackets
  246. outp.write(inp.read().replace('{predefs}',predefs).replace('{types}',typedefs).replace('{entities}',entities).replace('{converter-decl}',stub_decls))
  247. with open(input_template_cpp,'rt') as inp:
  248. with open(output_file_cpp,'wt') as outp:
  249. outp.write(inp.read().replace('{schema-static-table}',schema_table).replace('{converter-impl}',converters))
  250. # Finished without error, so return 0
  251. return 0
  252. if __name__ == "__main__":
  253. sys.exit(work(sys.argv[1] if len(sys.argv)>1 else 'schema.exp'))