Files
IfcOpenShell/src/ifcexpressparser/IfcExpressParser.py
T

491 lines
21 KiB
Python

header = """
/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
""".strip()
###############################################################################
# #
# This file can be used to generate C++ code from Express schema files. The #
# generated code works alongside the IfcOpenShell IfcParse library. This #
# script has only been tested on IFC2X3_TC1.exp and will most probably not #
# work on any other schemas. #
# #
# Note this script uses funcparserlib, which is available at: #
# http://code.google.com/p/funcparserlib/ #
# The script only works with revision e1df5066addd because it uses the some() #
# parser and is incompatible with other changes as well. #
# #
###############################################################################
import os, sys
import IfcDocumentation
filename = sys.argv[1]
#
# A class to split the Express schema files into seperate tokens
#
class Tokenizer(object):
comment = ['(*','*)']
termchars = ',;()=[]:'
def __init__(self, fn):
if hasattr(fn,'read'): object.__setattr__(self,'f',fn)
else: object.__setattr__(self,'f',open(fn,'rb'))
def __getattr__(self, name):
return getattr(self.f, name)
def __setattr__(self, name, value):
setattr(self.f, name, value)
def __iter__(self): return self
def next(self):
def get():
buffer = ''
in_comment = False
in_string = False
offset = self.tell()
while True:
c = self.read(2)
if len(c) < 2: raise StopIteration
if c in Tokenizer.comment:
in_comment = c == Tokenizer.comment[0]
continue
if in_string and c == "''":
buffer += "'"
continue
self.seek(-1,1)
if not in_string and c[0].isspace():
if ( len(buffer) ): return buffer
else:
offset = self.tell()
continue
if not in_comment:
if len(buffer) and (c[0] in Tokenizer.termchars or buffer[-1] in Tokenizer.termchars):
self.seek(-1,1)
return buffer
buffer += c[0]
return get()
#
# Some global variables to keep track of variable names
#
express_to_cpp = {
'BOOLEAN':'bool',
'LOGICAL':'bool',
'INTEGER':'int',
'REAL':'double',
'NUMBER':'double',
'STRING':'std::string'
}
schema_version = ''
enumerations = set()
selections = set()
entity_names = set()
simple_types = set()
selectable_simple_types = set()
argument_count = {}
parent_relations = {}
#
# Since inherited arguments of Express entities are placed in sequence before the non-inherited once, we need to keep track of how many inherited arguments exist
#
def argument_start(c):
if c not in parent_relations: return 0
i = 0
while True:
c = parent_relations[c]
i += argument_count[c] if c in argument_count else 0
if not (c in parent_relations): break
return i
#
# Several classes to generate code from Express types and entities
#
class ArrayType:
def __init__(self,l):
self.type = express_to_cpp.get(l[3],l[3])
self.upper = l[2]
self.lower = l[1]
def __str__(self):
if self.type in entity_names:
return "SHARED_PTR< IfcTemplatedEntityList<%s> >"%self.type
elif self.type in selections:
return "SHARED_PTR< IfcTemplatedEntityList<IfcAbstractSelect> >"
else:
return "std::vector<%(type)s> /*[%(lower)s:%(upper)s]*/"%self.__dict__
class ScalarType:
def __init__(self,l): self.type = express_to_cpp.get(l,l)
def __str__(self): return self.type
class EnumType:
def __init__(self,l):
self.v = [(x,'%s_%s'%('%(fancy_name)s',x)) for x in l]
self.maxlen = max([len(v) for v in self.v])
def __str__(self):
if generator_mode == 'HEADER':
return "enum {%s}"%", ".join([v2 for v1,v2 in self.v])
elif generator_mode == 'SOURCE_TO':
return '{ "%s" }'%'","'.join([v1 for v1,v2 in self.v])
elif generator_mode == 'SOURCE_FROM':
return "".join([' if(s=="%s"%s) return %s::%s;\n'%(v1.upper()," "*(self.maxlen-len(v1)),"%(name)s",v2) for v1,v2 in self.v])
def __len__(self): return len(self.v)
class SelectType:
def __init__(self,l):
for x in l:
if x in simple_types: selectable_simple_types.add(x)
def __str__(self): return "IfcSchemaEntity"
class BinaryType:
def __init__(self,l): self.l = int(l)
def __str__(self): return "char[%s]"%self.l
class InverseType:
def __init__(self,l):
self.name, self.type, self.reference = l
class Typedef:
def __init__(self,l):
self.name,self.type=l[1:3]
self.fancy_name = self.name[:-4] if self.name.endswith("Enum") else self.name
if isinstance(self.type,EnumType):
enumerations.add(self.name)
self.len = len(self.type)
elif isinstance(self.type,SelectType): selections.add(self.name)
simple_types.add(self.name)
comment = IfcDocumentation.description(self.name)
self.comment = comment+"\n" if comment else ''
def __str__(self):
global generator_mode
if generator_mode == 'HEADER' and isinstance(self.type,EnumType):
return ("namespace %(name)s {\n%(comment)stypedef %(type)s %(name)s;\nstd::string ToString(%(name)s v);\n%(name)s FromString(const std::string& s);\n}"%self.__dict__)%self.__dict__
elif generator_mode == 'HEADER':
return "%stypedef %s %s;"%(self.comment,self.type,self.name)
elif generator_mode == 'SOURCE' and isinstance(self.type,EnumType):
generator_mode = 'SOURCE_TO'
s = "std::string %(name)s::ToString(%(name)s v) {\n if ( v < 0 || v >= %(len)d ) throw IfcException(\"Unable to find find keyword in schema\");\n const char* names[] = %(type)s;\n return names[v];\n}\n"%self.__dict__
generator_mode = 'SOURCE_FROM'
s += ("%(name)s::%(name)s %(name)s::FromString(const std::string& s) {\n%(type)s throw IfcException(\"Unable to find find keyword in schema\");\n}"%self.__dict__)%self.__dict__
generator_mode = 'SOURCE'
return s
class Argument(object):
def __init__(self,l):
self.name, self.optional, self.type = l
class ArgumentList:
def __init__(self,l):
self.l = [Argument(a) for a in l]
self.argstart = 0
def __len__(self): return len(self.l)
def __str__(self):
s = ""
argv = self.argstart
for a in self.l:
class_name = indent = comment = optional_comment = ""
return_type = str(a.type)
if generator_mode == 'SOURCE':
class_name = "%(class_name)s::"
if isinstance(a.type,BinaryType) or (isinstance(a.type,ArrayType) and isinstance(a.type.type,BinaryType)):
function_body = " { throw; /* Not implemented argument 7 */ }"
elif isinstance(a.type,ArrayType) and str(a.type.type) in entity_names:
function_body = " { RETURN_AS_LIST(%s,%d) }"%(a.type.type,argv)
elif isinstance(a.type,ArrayType) and str(a.type.type) in selections:
function_body = " { RETURN_AS_LIST(IfcAbstractSelect,%d) }"%(argv)
elif return_type in entity_names:
function_body = " { return reinterpret_pointer_cast<IfcBaseClass,%s>(*entity->getArgument(%d)); }"%(return_type,argv)
elif return_type in enumerations:
function_body = " { return %s::FromString(*entity->getArgument(%d)); }"%(return_type,argv)
else:
function_body = " { return *entity->getArgument(%d); }"%argv
function_body2 = " { return !entity->getArgument(%d)->isNull(); }"%argv
else:
indent = " "
function_body = function_body2 = ";"
comment = IfcDocumentation.description((self.class_name,a.name))
comment = comment+"\n" if comment else ''
comment = comment.replace("///","%s///"%indent)
optional_comment = "%s/// Whether the optional attribute %s is defined for this %s\n"%(indent,a.name,self.class_name)
if a.optional: s += "\n%s%sbool %shas%s()%s"%(optional_comment,indent,class_name,a.name,function_body2)
if ( str(a.type) in enumerations ):
return_type = "%(type)s::%(type)s"%a.__dict__
elif ( str(a.type) in entity_names ):
return_type = "%(type)s*"%a.__dict__
s += "\n%s%s%s %s%s()%s"%(comment,indent,return_type,class_name,a.name,function_body)
argv += 1
return s
class InverseList:
def __init__(self,l):
self.l = l
def __str__(self):
if self.l is None: return ""
s = ""
for i in self.l:
if generator_mode == 'HEADER':
s += "\n SHARED_PTR< IfcTemplatedEntityList<%s> > %s(); // INVERSE %s::%s"%(i.type.type,i.name,i.type.type,i.reference)
elif generator_mode == 'SOURCE':
s += "\n%s::list %s::%s() { RETURN_INVERSE(%s) }"%(i.type.type,"%(class_name)s",i.name,i.type.type)
return s
class Classdef:
def __init__(self,l):
self.class_name, self.parent_class, self.arguments, self.inverse = l
self.arguments.class_name = self.class_name
entity_names.add(self.class_name)
parent_relations[self.class_name] = self.parent_class
argument_count[self.class_name] = len(self.arguments)
def __str__(self):
if generator_mode == 'HEADER':
comment = IfcDocumentation.description(self.class_name)
comment = comment+"\n" if comment else ''
return "%sclass %s : public %s {\npublic:%s%s%s\n};" % (comment,self.class_name,
"IfcBaseClass" if self.parent_class is None else self.parent_class,
self.arguments,
self.inverse,
("\n bool is(Type::Enum v) const;"+
"\n Type::Enum type() const;"+
"\n static Type::Enum Class();"+
"\n %(class_name)s (IfcAbstractEntityPtr e = IfcAbstractEntityPtr());"+
"\n typedef %(class_name)s* ptr;"+
"\n typedef SHARED_PTR< IfcTemplatedEntityList<%(class_name)s> > list;"+
"\n typedef IfcTemplatedEntityList<%(class_name)s>::it it;")%self.__dict__
)
elif generator_mode == 'SOURCE':
self.arguments.argstart = argument_start(self.class_name)
return (("\n// Function implementations for %(class_name)s"+str(self.arguments)+str(self.inverse)+
("\nbool %(class_name)s::is(Type::Enum v) const { return v == Type::%(class_name)s; }" if self.parent_class is None else
"\nbool %(class_name)s::is(Type::Enum v) const { return v == Type::%(class_name)s || %(parent_class)s::is(v); }")+
"\nType::Enum %(class_name)s::type() const { return Type::%(class_name)s; }"+
"\nType::Enum %(class_name)s::Class() { return Type::%(class_name)s; }"+
"\n%(class_name)s::%(class_name)s(IfcAbstractEntityPtr e) { if (!is(Type::%(class_name)s)) throw IfcException(\"Unable to find find keyword in schema\"); entity = e; }")%self.__dict__)%self.__dict__
from funcparserlib.parser import a, skip, many, maybe, some
#
# Lambda functions to map combinator output to classes
#
array_type = lambda t: ArrayType(t)
scalar_type = lambda t: ScalarType(t)
enum_type = lambda t: EnumType(t)
select_type = lambda t: SelectType(t)
binary_type = lambda t: BinaryType(t)
inverse_type = lambda t: InverseType(t)
format_type = lambda t: Typedef(t)
argument_list = lambda t: ArgumentList(t)
inverse_list = lambda t: InverseList(t)
format_options = lambda t: [t[0]]+t[1]
#
# The actual grammar definition
#
s = some(lambda t: not t in ['UNIQUE','WHERE','END_ENTITY','END_TYPE','INVERSE','DERIVE'])
x = lambda s:skip(a(s))
list_or_array = a('ARRAY') | a('LIST') | a('SET')
binary = x('BINARY')+x('(') + s + x(')') >> binary_type
array = list_or_array + x('[') + s + x(':') + s + x(']') + x('OF') + skip(maybe(a('UNIQUE'))) + (binary|s) >> array_type
options = x('(') + s + many(x(',')+s) + x(')') >> format_options
enum = x('ENUMERATION') + x('OF') + options >> enum_type
select = x('SELECT') + options >> select_type
single = s + skip(maybe(x('(')+s+x(')')) + maybe(a('FIXED'))) >> scalar_type
type_type = array | enum | select | single
type_start = a('TYPE') + s + x('=') + type_type + x(';')
type_end = a('END_TYPE') + x(';')
to_end = many(some(lambda t: t != ';'))
clause = s + x(':') + to_end + x(';')
where = a('WHERE') + many(clause)
type = type_start + maybe(where) + type_end >> format_type
subtype = x('SUBTYPE') + x('OF') + x('(') + s + x(')')
supertype = maybe(x('ABSTRACT')) + x('SUPERTYPE') + x('OF') + x('(') + x('ONEOF') + options + x(')')
entity_start = x('ENTITY') + s + skip(maybe(supertype)) + maybe(subtype) + x(';')
entity_end = x('END_ENTITY') + x(';')
key_value = s + x(':') + maybe(a('OPTIONAL')) + (array|binary|single) + x(';')
arguments = many(key_value) >> argument_list
unique_value = s + x(':') + s + many(a(',')+s) + a(';')
unique = skip(a('UNIQUE') + many(unique_value))
inverse_def = s + x(':') + (array|single) + x('FOR') + s + x(';') >> inverse_type
inverse = maybe(x('INVERSE') + many( inverse_def )) >> inverse_list
derive = skip(a('DERIVE') + many(clause))
entity = entity_start + arguments + skip(maybe(unique)) + skip(maybe(derive)) + inverse + skip(maybe(where)) + entity_end >> Classdef
schema = skip(a('SCHEMA')) + s + x(';')
express = schema + many(type) + many(entity)
schema_version,types,entities = express.parse(list(Tokenizer(filename)))
schema_version = schema_version.capitalize()
#
# Writing of the three generated files starts here
#
h_file = open("%s.h"%schema_version,'w')
enumh_file = open("%senum.h"%schema_version,'w')
cpp_file = open("%s.cpp"%schema_version,'w')
header += """
/********************************************************************************
* *
* This file has been generated from %s. Do not make modifications *
* but instead modify the python script that has been used to generate this. *
* *
********************************************************************************/
"""%filename
generator_mode = 'HEADER'
print >>h_file, header
print >>enumh_file, header
print >>cpp_file, header
print >>h_file, """#ifndef %(schema_upper)s_H
#define %(schema_upper)s_H
#include <string>
#include <vector>
#include <map>
#include "../ifcparse/IfcUtil.h"
#include "../ifcparse/%(schema)senum.h"
using namespace IfcUtil;
#define RETURN_INVERSE(T) \\
IfcEntities e = entity->getInverse(T::Class()); \\
SHARED_PTR< IfcTemplatedEntityList<T> > l ( new IfcTemplatedEntityList<T>() ); \\
for ( IfcEntityList::it it = e->begin(); it != e->end(); ++ it ) { \\
l->push(reinterpret_pointer_cast<IfcBaseClass,T>(*it)); \\
} \\
return l;
#define RETURN_AS_SINGLE(T,a) \\
return reinterpret_pointer_cast<IfcBaseClass,T>(*entity->getArgument(a));
#define RETURN_AS_LIST(T,a) \\
IfcEntities e = *entity->getArgument(a); \\
SHARED_PTR< IfcTemplatedEntityList<T> > l ( new IfcTemplatedEntityList<T>() ); \\
for ( IfcEntityList::it it = e->begin(); it != e->end(); ++ it ) { \\
l->push(reinterpret_pointer_cast<IfcBaseClass,T>(*it)); \\
} \\
return l;
namespace %(schema)s {
"""%{'schema_upper':schema_version.upper(),'schema':schema_version}
simple_enumerations = sorted(selectable_simple_types)
entity_enumerations = sorted(entity_names)
all_enumerations = simple_enumerations + entity_enumerations
print >>enumh_file, """#ifndef IFC2X3ENUM_H
#define IFC2X3ENUM_H
namespace Ifc2x3 {
namespace Type {
typedef enum {
%(enum)s
} Enum;
Enum Parent(Enum v);
Enum FromString(const std::string& s);
std::string ToString(Enum v);
}
}
#endif
"""%{'schema_upper':schema_version.upper(),'schema':schema_version,'enum':", ".join(all_enumerations + ["ALL"])}
defined_types = set(express_to_cpp.values())
deferred_types = []
for t in [T for T in types if not (isinstance(T.type,EnumType) or isinstance(T.type,SelectType))]:
if isinstance(t.type,ScalarType) and str(t.type) not in defined_types:
deferred_types.append(t)
else:
print >>h_file, t
for t in [T for T in types if isinstance(T.type,SelectType)]:
print >>h_file, t
for t in deferred_types:
print >>h_file, t
for t in [T for T in types if isinstance(T.type,EnumType)]:
print >>h_file, t
print >>h_file, "// Forward definitions"
print >>h_file, "class %s;\n"%"; class ".join([e.class_name for e in entities])
defined_classes = set()
while True:
classes = [c for c in entities if c.class_name not in defined_classes]
if not len(classes): break
for c in classes:
if c.parent_class is None or c.parent_class in defined_classes:
defined_classes.add(c.class_name)
print >>h_file, c
print >>h_file, "void InitStringMap();"
print >>h_file, "IfcSchemaEntity SchemaEntity(IfcAbstractEntityPtr e = 0);"
print >>h_file, "}\n\n#endif"
generator_mode = 'SOURCE'
print >>cpp_file, """#include "%(schema)s.h"
#include "IfcException.h"
using namespace %(schema)s;
using namespace IfcParse;
IfcSchemaEntity %(schema)s::SchemaEntity(IfcAbstractEntityPtr e) {
switch(e->type()){"""%{'schema':schema_version}
for e in simple_enumerations:
print >>cpp_file, " case Type::%s: return new IfcEntitySelect(e); break;"%e
for e in entity_enumerations:
print >>cpp_file, " case Type::%s: return new %s(e); break;"%(e,e)
print >>cpp_file, " default: throw IfcException(\"Unable to find find keyword in schema\"); break; "
print >>cpp_file, " }\n}"
print >>cpp_file
print >>cpp_file, "std::string Type::ToString(Enum v) {"
print >>cpp_file, " if (v < 0 || v >= %d) throw IfcException(\"Unable to find find keyword in schema\");"%len(all_enumerations)
print >>cpp_file, ' const char* names[] = { "%s" };'%'","'.join(all_enumerations)
print >>cpp_file, ' return names[v];'
print >>cpp_file, "}"
print >>cpp_file
#print >>cpp_file, "Type::Enum Type::FromStringOld(const std::string& s){"
#elseif = "if"
#maxlen = max([len(e) for e in all_enumerations])
#for e in all_enumerations:
# print >>cpp_file, ' %s(s=="%s"%s) { return %s; }'%(elseif,e.upper()," "*(maxlen-len(e)),e)
#print >>cpp_file, " throw;"
#print >>cpp_file, "}"
print >>cpp_file, "std::map<std::string,Type::Enum> string_map;"
print >>cpp_file, "void Ifc2x3::InitStringMap() {"
maxlen = max([len(e) for e in all_enumerations])
for e in all_enumerations:
print >>cpp_file, ' string_map["%s"%s] = Type::%s;'%(e.upper()," "*(maxlen-len(e)),e)
print >>cpp_file, """}
Type::Enum Type::FromString(const std::string& s) {
std::map<std::string,Type::Enum>::const_iterator it = string_map.find(s);
if ( it == string_map.end() ) throw IfcException("Unable to find find keyword in schema");
else return it->second;
}"""
print >>cpp_file, "Type::Enum Type::Parent(Enum v){"
print >>cpp_file, " if (v < 0 || v >= %d) return (Enum)-1;"%len(all_enumerations)
for e in entity_enumerations:
if e not in parent_relations or parent_relations[e] is None: continue
print >>cpp_file, ' if(v==%s%s) { return %s; }'%(e," "*(maxlen-len(e)),parent_relations[e])
print >>cpp_file, " return (Enum)-1;"
print >>cpp_file, "}"
for t in [T for T in types if isinstance(T.type,EnumType)]:
print >>cpp_file, t
for e in entities: print >>cpp_file, e,