Update EXPRESS code generator to work on ancient IFC schemas

This commit is contained in:
Thomas Krijnen
2016-04-08 23:23:29 +02:00
parent 63c3b54762
commit 0ff9de3cd9
8 changed files with 86 additions and 24 deletions
+2
View File
@@ -122,6 +122,8 @@ actions = {
'inverse_attr' : "lambda t: InverseAttribute(t)",
'bound_spec' : "lambda t: BoundSpecification(t)",
'explicit_attr' : "lambda t: ExplicitAttribute(t)",
'width_spec' : "lambda t: WidthSpec(t)",
'string_type' : "lambda t: StringType(t)",
}
to_emit = set(id for id, expr in express)
+4 -2
View File
@@ -31,12 +31,14 @@ import re
import os
import csv
from schema import OrderedCaseInsensitiveDict
try: from html.entities import entitydefs
except: from htmlentitydefs import entitydefs
make_absolute = lambda fn: os.path.join(os.path.dirname(os.path.realpath(__file__)), fn)
name_to_oid = {}
name_to_oid = OrderedCaseInsensitiveDict()
oid_to_desc = {}
oid_to_name = {}
oid_to_pid = {}
@@ -59,7 +61,7 @@ with open(make_absolute('DocAttribute.csv')) as f:
for oid, name, desc in csv.reader(f, delimiter=';', quotechar='"'):
pid = oid_to_pid[oid]
pname = oid_to_name[pid]
name_to_oid[(pname, name)] = oid
name_to_oid[".".join((pname, name))] = oid
oid_to_desc[oid] = desc
def description(item):
+14 -7
View File
@@ -41,15 +41,18 @@ class Header(codegen.Base):
emitted_simpletypes = set()
while len(emitted_simpletypes) < len(mapping.schema.simpletypes):
for name, type in mapping.schema.simpletypes.items():
if name in emitted_simpletypes: continue
if name.lower() in emitted_simpletypes: continue
type_str = mapping.make_type_string(mapping.flatten_type_string(type))
attr_type = mapping.make_argument_type(type)
superclass = mapping.simple_type_parent(name)
if superclass is None:
superclass = "IfcUtil::IfcBaseType"
elif superclass not in emitted_simpletypes:
elif superclass.lower() not in emitted_simpletypes:
continue
emitted_simpletypes.add(name)
else:
# Case normalize
superclass = [k for k in mapping.schema.simpletypes.keys() if k.lower() == superclass.lower()][0]
emitted_simpletypes.add(name.lower())
write(templates.simpletype, name=name, type=type_str, attr_type=attr_type, superclass=superclass)
class_definitions = []
@@ -60,14 +63,14 @@ class Header(codegen.Base):
emitted_entities = set()
while len(emitted_entities) < len(mapping.schema.entities):
for name, type in mapping.schema.entities.items():
if name in emitted_entities: continue
if len(type.supertypes) == 0 or set(type.supertypes) < emitted_entities:
if name.lower() in emitted_entities: continue
if len(type.supertypes) == 0 or set(map(str.lower, type.supertypes)) <= emitted_entities:
attr_lines = []
def write_method(attr):
if attr.optional:
attr_lines.append(templates.optional_attribute_description % (attr.name, name))
attr_lines.append("bool has%s() const;"%(attr.name))
attr_lines.extend(["/// %s"%d for d in documentation.description((name, attr.name))])
attr_lines.extend(["/// %s"%d for d in documentation.description(".".join((name, attr.name)))])
type_str = mapping.get_parameter_type(attr, allow_optional=False, allow_entities=False)
if mapping.make_argument_type(attr) != "IfcUtil::Argument_UNKNOWN":
attr_lines.append("%s %s() const;"%(type_str, attr.name))
@@ -88,7 +91,11 @@ class Header(codegen.Base):
inverse = "\n".join(["%s%s"%(' '*4, a) for a in inv_lines])
if len(inverse): inverse += '\n'
supertypes = type.supertypes if len(type.supertypes) else ['IfcUtil::IfcBaseEntity']
def case_norm(n):
n = n.lower()
return [k for k in mapping.schema.entities.keys() if k.lower() == n][0]
supertypes = map(case_norm, type.supertypes) if len(type.supertypes) else ['IfcUtil::IfcBaseEntity']
superclass = ": %s "%(", ".join(["public %s"%c for c in supertypes]))
argument_count = mapping.argument_count(type)
+10 -1
View File
@@ -20,6 +20,8 @@
import codegen
import templates
from schema import OrderedCaseInsensitiveDict
class Implementation(codegen.Base):
def __init__(self, mapping):
enumeration_functions = []
@@ -132,7 +134,7 @@ class Implementation(codegen.Base):
def get_attribute_index(entity, attr_name):
related_entity = mapping.schema.entities[entity]
return [a['name'] for a in mapping.get_assignable_arguments(related_entity, include_derived=True)].index(attr_name)
return [a['name'].lower() for a in mapping.get_assignable_arguments(related_entity, include_derived=True)].index(attr_name.lower())
inverse = [templates.const_function % {
'class_name' : name,
@@ -167,6 +169,13 @@ class Implementation(codegen.Base):
'name' : name,
'padding' : ' ' * (max_len - len(name))
} for name in enumerable_types]
enumeration_index_by_str = OrderedCaseInsensitiveDict((j,i) for i,j in enumerate(enumerable_types))
def get_parent_id(s):
e = mapping.schema.entities.get(s)
if e and e.supertypes:
return enumeration_index_by_str[e.supertypes[0]]
else: return -1
parent_type_statements = [templates.parent_type_stmt % {
'name' : name,
@@ -41,11 +41,10 @@ class LateBoundImplementation(codegen.Base):
})
emitted_entities = set()
entities_to_emit = mapping.schema.entities.keys()
while len(emitted_entities) < len(mapping.schema.entities):
for name, type in mapping.schema.entities.items():
if name in emitted_entities: continue
if len(type.supertypes) == 0 or set(type.supertypes) < emitted_entities:
if name.lower() in emitted_entities: continue
if len(type.supertypes) == 0 or set(map(str.lower, type.supertypes)) <= emitted_entities:
constructor_arguments = mapping.get_assignable_arguments(type, include_derived = True)
entity_descriptor_attributes = []
for arg in constructor_arguments:
@@ -61,8 +60,9 @@ class LateBoundImplementation(codegen.Base):
})
emitted_entities.add(name)
parent_statement = '0' if len(type.supertypes) != 1 else templates.entity_descriptor_parent % {
'type' : type.supertypes[0]
'type' : [k for k in mapping.schema.entities.keys() if k.lower() == type.supertypes[0].lower()][0]
}
entity_descriptors.append(templates.entity_descriptor % {
'type' : name,
@@ -92,13 +92,13 @@ class LateBoundImplementation(codegen.Base):
if type.inverse:
for attr in type.inverse.elements:
related_entity = mapping.schema.entities[attr.entity]
related_attrs = [a['name'] for a in mapping.get_assignable_arguments(related_entity, include_derived=True)]
related_attrs = [a['name'].lower() for a in mapping.get_assignable_arguments(related_entity, include_derived=True)]
inverse_implementations.append(templates.inverse_implementation % {
'type' : name,
'name' : attr.name,
'related_type' : attr.entity,
'index' : related_attrs.index(attr.attribute)
'index' : related_attrs.index(attr.attribute.lower())
})
self.str = templates.lb_implementation % {
+6 -3
View File
@@ -57,7 +57,7 @@ class Mapping:
return None if str(parent) in self.express_to_cpp_typemapping else parent
def make_type_string(self, type):
if isinstance(type, (str, nodes.BinaryType)):
if isinstance(type, (str, nodes.BinaryType, nodes.StringType)):
return self.express_to_cpp_typemapping.get(str(type), type)
else:
is_list = self.schema.is_entity(type.type)
@@ -89,6 +89,8 @@ class Mapping:
return "ENTITY_INSTANCE"
elif isinstance(type, nodes.BinaryType):
return "BINARY"
elif isinstance(type, nodes.StringType):
return "STRING"
elif isinstance(type, nodes.EnumerationType):
return "ENUMERATION"
elif isinstance(type, nodes.AggregationType):
@@ -128,10 +130,11 @@ class Mapping:
type_str = templates.untyped_list
elif self.schema.is_simpletype(ty) or str(ty) in self.express_to_cpp_typemapping.values():
tmpl = templates.nested_array_type if is_nested_list else templates.array_type
bounds = (attr_type.bounds.lower, attr_type.bounds.upper) if attr_type.bounds else (-1, -1)
type_str = tmpl % {
'instance_type' : ty,
'lower' : attr_type.bounds.lower,
'upper' : attr_type.bounds.upper
'lower' : bounds[0],
'upper' : bounds[1]
}
else:
tmpl = templates.list_list_type if is_nested_list else templates.list_type
+20 -3
View File
@@ -21,8 +21,8 @@ import string
import collections
class Node:
def __init__(self, tokens):
self.tokens = tokens
def __init__(self, tokens = None):
self.tokens = tokens or []
self.init()
def tokens_of_type(self, cls):
return [t for t in self.tokens if isinstance(t, cls)]
@@ -128,7 +128,7 @@ class AttributeList(Node):
class InverseAttribute(Node):
name = property(lambda self: self.tokens[0])
type = property(lambda self: self.tokens[2])
bounds = property(lambda self: None if len(self.tokens) == 6 else self.tokens[3])
bounds = property(lambda self: None if len(self.tokens) != 9 else self.tokens[3])
entity = property(lambda self: self.tokens[-4])
attribute = property(lambda self: self.tokens[-2])
def init(self):
@@ -170,6 +170,23 @@ class ExplicitAttribute(Node):
def init(self):
# NB: This assumes a single name per attribute
# definition, which is not necessarily the case.
if self.tokens[0] == "self":
i = list(self.tokens).index(":")
self.tokens = self.tokens[i-1:]
assert self.tokens[1] == ':'
def __repr__(self):
return "%s : %s%s" % (self.name, self.type, " ?" if self.optional else "")
class WidthSpec(Node):
def init(self):
if self.tokens[-1] == "fixed":
self.tokens[-1:] = []
assert (self.tokens[0], self.tokens[-1]) == ("(", ")")
self.width = int("".join(self.tokens[1:-1]))
class StringType(Node):
def init(self):
pass
def __repr__(self):
return "string"
+24 -2
View File
@@ -24,6 +24,28 @@ import collections
if tuple(map(int, platform.python_version_tuple())) < (2, 7):
import ordereddict
collections.OrderedDict = ordereddict.OrderedDict
# According to ISO 10303-11 7.1.2: Letters: "... The case of
# letters is significant only within explicit string literals."
class OrderedCaseInsensitiveDict(collections.OrderedDict):
class KeyObject(str):
def __eq__(self, other):
return self.lower() == other.lower()
def __hash__(self):
return hash(self.lower())
def __init__(self, *args, **kwargs):
collections.OrderedDict.__init__(self)
for key, value in collections.OrderedDict(*args, **kwargs).items():
self[OrderedCaseInsensitiveDict.KeyObject(key)] = value
def __setitem__(self, key, value):
return collections.OrderedDict.__setitem__(self, OrderedCaseInsensitiveDict.KeyObject(key), value)
def __getitem__(self, key):
return collections.OrderedDict.__getitem__(self, OrderedCaseInsensitiveDict.KeyObject(key))
def get(self, key, *args, **kwargs):
return collections.OrderedDict.get(self, OrderedCaseInsensitiveDict.KeyObject(key), *args, **kwargs)
def __contains__(self, key):
return collections.OrderedDict.__contains__(self, OrderedCaseInsensitiveDict.KeyObject(key))
class Schema:
def is_enumeration(self, v):
@@ -39,7 +61,7 @@ class Schema:
def __init__(self, parsetree):
self.name = parsetree[1]
sort = lambda d: collections.OrderedDict(sorted(d))
sort = lambda d: OrderedCaseInsensitiveDict(sorted(d))
self.types = sort([(t.name,t) for t in parsetree if isinstance(t, nodes.TypeDeclaration)])
self.entities = sort([(t.name,t) for t in parsetree if isinstance(t, nodes.EntityDeclaration)])
@@ -48,4 +70,4 @@ class Schema:
self.enumerations = of_type(nodes.EnumerationType)
self.selects = of_type(nodes.SelectType)
self.simpletypes = of_type(str, nodes.AggregationType, nodes.BinaryType)
self.simpletypes = of_type(str, nodes.AggregationType, nodes.BinaryType, nodes.StringType)