Skip to content

Commit 17f873b

Browse files
committed
First stab at a new express parser to have some more luck with Ifc4
1 parent 87259c0 commit 17f873b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+82103
-9785
lines changed

cmake/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ INCLUDE_DIRECTORIES(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCL
125125

126126
ADD_LIBRARY(IfcParse STATIC
127127
../src/ifcparse/Ifc2x3.cpp
128+
../src/ifcparse/Ifc4.cpp
128129
../src/ifcparse/IfcUtil.cpp
129130
../src/ifcparse/IfcParse.cpp
130131
../src/ifcparse/IfcCharacterDecoder.cpp

src/examples/IfcOpenHouse.cpp

Lines changed: 170 additions & 76 deletions
Large diffs are not rendered by default.

src/examples/IfcParseExamples.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
#include "../ifcparse/IfcParse.h"
2121

22-
using namespace Ifc2x3;
22+
using namespace IfcSchema;
2323

2424
int main(int argc, char** argv) {
2525

src/ifcexpressparser/IfcExpressParser.py

Lines changed: 0 additions & 659 deletions
This file was deleted.

src/ifcexpressparser/README.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
This folder contains Python code to generate C++ type information based on an
2+
Express schema. In particular is has only been tested using recent version of
3+
the IFC schema and will most likely fail on any other Express schema.
4+
5+
The code can be invoked in the following way and results in two header files
6+
and a single implementation file named according to the schema name in the
7+
Express file. A python 3 interpreter with the pyparsing library is required.
8+
9+
$ python bootstrap.py express.bnf > express_parser.py && python express_parser.py IFC2X3_TC1.exp

src/ifcexpressparser/bootstrap.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
###############################################################################
2+
# #
3+
# This file is part of IfcOpenShell. #
4+
# #
5+
# IfcOpenShell is free software: you can redistribute it and/or modify #
6+
# it under the terms of the Lesser GNU General Public License as published by #
7+
# the Free Software Foundation, either version 3.0 of the License, or #
8+
# (at your option) any later version. #
9+
# #
10+
# IfcOpenShell is distributed in the hope that it will be useful, #
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
13+
# Lesser GNU General Public License for more details. #
14+
# #
15+
# You should have received a copy of the Lesser GNU General Public License #
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
17+
# #
18+
###############################################################################
19+
20+
import sys
21+
import string
22+
from pyparsing import *
23+
24+
class Expression:
25+
def __init__(self, contents):
26+
self.contents = contents[0]
27+
def __repr__(self):
28+
if self.op is None: return repr(self.contents)
29+
c = [isinstance(c,str) and c or str(c) for c in self.contents]
30+
if "%s" in self.op: return self.op % (" ".join(c))
31+
else: return "(%s)" % (" %s "%self.op).join(c)
32+
def __iter__(self):
33+
return self.contents.__iter__()
34+
35+
class Union(Expression):
36+
op = "|"
37+
38+
class Concat(Expression):
39+
op = "+"
40+
41+
class Optional(Expression):
42+
op = "Optional(%s)"
43+
44+
class Repeated(Expression):
45+
op = "ZeroOrMore(%s)"
46+
47+
class Term(Expression):
48+
op = None
49+
50+
class Keyword:
51+
def __init__(self, contents):
52+
self.contents = contents[0]
53+
def __repr__(self):
54+
return self.contents
55+
56+
class Terminal:
57+
def __init__(self, contents):
58+
self.contents = contents[0]
59+
def __repr__(self):
60+
s = self.contents
61+
is_keyword = len(s) >= 4 and s[0::len(s)-1] == '""' and \
62+
all(c in alphanums+"_" for c in s[1:-1])
63+
ty = "CaselessKeyword" if is_keyword else "CaselessLiteral"
64+
return "%s(%s)" % (ty, s)
65+
66+
67+
LPAREN = Suppress("(")
68+
RPAREN = Suppress(")")
69+
LBRACK = Suppress("[")
70+
RBRACK = Suppress("]")
71+
LBRACE = Suppress("{")
72+
RBRACE = Suppress("}")
73+
EQUALS = Suppress("=")
74+
VBAR = Suppress("|")
75+
PERIOD = Suppress(".")
76+
HASH = Suppress("#")
77+
78+
identifier = Word(alphanums+"_")
79+
keyword = Word(alphanums+"_").setParseAction(Keyword)
80+
expression = Forward()
81+
optional = Group(LBRACK + expression + RBRACK).setParseAction(Optional)
82+
repeated = Group(LBRACE + expression + RBRACE).setParseAction(Repeated)
83+
terminal = quotedString.setParseAction(Terminal)
84+
term = (keyword | terminal | optional | repeated | (LPAREN + expression + RPAREN)).setParseAction(Term)
85+
concat = Group(term + OneOrMore(term)).setParseAction(Concat)
86+
factor = concat | term
87+
union = Group(factor + OneOrMore(VBAR + factor)).setParseAction(Union)
88+
rule = identifier + EQUALS + expression + PERIOD
89+
90+
expression << (union | factor)
91+
92+
grammar = OneOrMore(Group(rule))
93+
grammar.ignore(HASH + restOfLine)
94+
95+
express = grammar.parseFile(sys.argv[1])
96+
97+
def find_keywords(expr, li = None):
98+
if li is None: li = []
99+
if isinstance(expr, Term):
100+
expr = expr.contents
101+
if isinstance(expr, Keyword):
102+
li.append(repr(expr))
103+
return li
104+
elif isinstance(expr, Expression):
105+
for term in expr:
106+
find_keywords(term, li)
107+
return set(li)
108+
109+
actions = {
110+
'type_decl' : "lambda t: TypeDeclaration(t)",
111+
'entity_decl' : "lambda t: EntityDeclaration(t)",
112+
'underlying_type' : "lambda t: UnderlyingType(t)",
113+
'enumeration_type' : "lambda t: EnumerationType(t)",
114+
'aggregation_types' : "lambda t: AggregationType(t)",
115+
'general_aggregation_types' : "lambda t: AggregationType(t)",
116+
'select_type' : "lambda t: SelectType(t)",
117+
'binary_type' : "lambda t: BinaryType(t)",
118+
'subtype_declaration' : "lambda t: SubtypeExpression(t)",
119+
'derive_clause' : "lambda t: AttributeList('derive', t)",
120+
'derived_attr' : "lambda t: DerivedAttribute(t)",
121+
'inverse_clause' : "lambda t: AttributeList('inverse', t)",
122+
'inverse_attr' : "lambda t: InverseAttribute(t)",
123+
'bound_spec' : "lambda t: BoundSpecification(t)",
124+
'explicit_attr' : "lambda t: ExplicitAttribute(t)",
125+
}
126+
127+
to_emit = set(id for id, expr in express)
128+
emitted = set()
129+
to_combine = set(["simple_id"])
130+
to_ignore = set(["where_clause", "supertype_constraint", "unique_clause"])
131+
statements = []
132+
133+
while True:
134+
emitted_in_loop = set()
135+
for id, expr in express:
136+
kws = find_keywords(expr)
137+
found = [k in emitted for k in kws]
138+
if id in to_emit and all(found):
139+
emitted_in_loop.add(id)
140+
emitted.add(id)
141+
stmt = "(%s)" % expr
142+
if id in to_combine:
143+
stmt = "originalTextFor(Combine%s)" % stmt
144+
if id in actions:
145+
stmt = "%s.setParseAction(%s)" % (stmt, actions[id])
146+
statements.append("%s = %s" % (id, stmt))
147+
to_emit -= emitted_in_loop
148+
if not emitted_in_loop: break
149+
150+
for id in to_emit:
151+
action = ".setParseAction(%s)" % actions[id] if id in actions else ""
152+
statements.append("%s = Forward()%s" % (id, action))
153+
154+
for id in to_emit:
155+
expr = [e for k, e in express if k == id][0]
156+
stmt = "(%s)" % expr
157+
if id in to_combine:
158+
stmt = "Suppress%s" % stmt
159+
statements.append("%s << %s" % (id, stmt))
160+
161+
print ("""import sys
162+
from pyparsing import *
163+
from nodes import *
164+
165+
%s
166+
167+
import schema
168+
import mapping
169+
170+
import header
171+
import enum_header
172+
import implementation
173+
174+
syntax.ignore(Regex(r"\((?:\*(?:[^*]*\*+)+?\))"))
175+
ast = syntax.parseFile(sys.argv[1])
176+
schema = schema.Schema(ast)
177+
mapping = mapping.Mapping(schema)
178+
179+
header.Header(mapping).emit()
180+
enum_header.EnumHeader(mapping).emit()
181+
implementation.Implementation(mapping).emit()
182+
"""%('\n'.join(statements)))
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
oid_to_desc = {}
3737
oid_to_name = {}
3838
oid_to_pid = {}
39-
regices = list(zip([re.compile(s,re.M) for s in [r'<[\w\n=" \-/\.;_\t:%#,\?\(\)]+>',r'(\n[\t ]*){2,}',r'^[\t ]+','^']],['','\n\n',' ','/// ']))
39+
regices = list(zip([re.compile(s,re.M) for s in [r'<[\w\n=" \-/\.;_\t:%#,\?\(\)]+>',r'(\n[\t ]*){2,}',r'^[\t ]+']],['','\n\n',' ']))
4040

4141
definition_files = ['DocEntity.csv', 'DocEnumeration.csv', 'DocDefined.csv', 'DocSelect.csv']
4242
for fn in definition_files:
@@ -49,23 +49,22 @@
4949
with open('DocEntityAttributes.csv') as f:
5050
for pid, x, oid in csv.reader(f, delimiter=';', quotechar='"'):
5151
oid_to_pid[oid] = pid
52-
52+
5353
with open('DocAttribute.csv') as f:
5454
for oid, name, desc in csv.reader(f, delimiter=';', quotechar='"'):
5555
pid = oid_to_pid[oid]
5656
pname = oid_to_name[pid]
5757
name_to_oid[(pname, name)] = oid
5858
oid_to_desc[oid] = desc
59-
59+
6060
def description(item):
6161
global name_to_oid, oid_to_desc, oid_to_name, oid_to_pid
6262
oid = name_to_oid.get(item,0)
63-
desc = oid_to_desc.get(oid,None)
63+
desc = oid_to_desc.get(oid, None)
6464
if desc:
6565
for a,b in entitydefs.items(): desc = desc.replace("&%s;"%a,b)
6666
desc = desc.replace("\r","")
67-
for r,s in regices[:-1]: desc = r.sub(s,desc)
67+
for r,s in regices: desc = r.sub(s,desc)
6868
desc = desc.strip()
69-
r,s = regices[-1]
70-
desc = r.sub(s,desc)
71-
return desc
69+
return desc.split("\n")
70+
else: return []
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
###############################################################################
2+
# #
3+
# This file is part of IfcOpenShell. #
4+
# #
5+
# IfcOpenShell is free software: you can redistribute it and/or modify #
6+
# it under the terms of the Lesser GNU General Public License as published by #
7+
# the Free Software Foundation, either version 3.0 of the License, or #
8+
# (at your option) any later version. #
9+
# #
10+
# IfcOpenShell is distributed in the hope that it will be useful, #
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
13+
# Lesser GNU General Public License for more details. #
14+
# #
15+
# You should have received a copy of the Lesser GNU General Public License #
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
17+
# #
18+
###############################################################################
19+
20+
import templates
21+
22+
class EnumHeader:
23+
def __init__(self, mapping):
24+
selectable_simple_types = sorted(set(sum([b.values for a,b in mapping.schema.selects.items()], [])) & set(mapping.schema.types.keys()))
25+
enumerable_types = selectable_simple_types + [name for name, type in mapping.schema.entities.items()]
26+
27+
self.str = templates.enum_header % {
28+
'schema_name_upper' : mapping.schema.name.upper(),
29+
'schema_name' : mapping.schema.name.capitalize(),
30+
'types' : ', '.join(enumerable_types)
31+
}
32+
33+
self.schema_name = mapping.schema.name.capitalize()
34+
def __repr__(self):
35+
return self.str
36+
def emit(self):
37+
f = open('%senum.h'%self.schema_name, 'w', encoding='utf-8')
38+
f.write(str(self))
39+
f.close()

0 commit comments

Comments
 (0)