2022-01-19 12:18:33 +11:00
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 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
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
2014-02-17 22:13:55 +00:00
2020-09-08 14:40:27 +02:00
import os
2016-04-09 13:29:35 +02:00
import operator
import itertools
2014-02-17 22:13:55 +00:00
from pyparsing import *
2020-11-01 20:08:27 +07:00
try :
from functools import reduce
except :
pass
2016-04-09 13:29:35 +02:00
2014-02-17 22:13:55 +00:00
class Expression :
def __init__ ( self , contents ) :
self . contents = contents [ 0 ]
2020-11-01 20:08:27 +07:00
2014-02-17 22:13:55 +00:00
def __repr__ ( self ) :
2020-11-01 20:08:27 +07:00
if self . op is None :
return repr ( self . contents )
c = [ isinstance ( c , str ) and c or str ( c ) for c in self . contents ]
if " %s " in self . op :
return self . op % ( " " . join ( c ) )
else :
return " ( %s ) " % ( " %s " % self . op ) . join ( c )
2014-02-17 22:13:55 +00:00
def __iter__ ( self ) :
return self . contents . __iter__ ( )
2020-11-01 20:08:27 +07:00
2014-02-17 22:13:55 +00:00
class Union ( Expression ) :
op = " | "
2020-11-01 20:08:27 +07:00
2014-02-17 22:13:55 +00:00
class Concat ( Expression ) :
op = " + "
2020-11-01 20:08:27 +07:00
2014-02-17 22:13:55 +00:00
class Optional ( Expression ) :
op = " Optional( %s ) "
2020-11-01 20:08:27 +07:00
2014-02-17 22:13:55 +00:00
class Repeated ( Expression ) :
op = " ZeroOrMore( %s ) "
2020-11-01 20:08:27 +07:00
2014-02-17 22:13:55 +00:00
class Term ( Expression ) :
op = None
2020-11-01 20:08:27 +07:00
2014-02-17 22:13:55 +00:00
class Keyword :
def __init__ ( self , contents ) :
self . contents = contents [ 0 ]
2020-11-01 20:08:27 +07:00
2014-02-17 22:13:55 +00:00
def __repr__ ( self ) :
return self . contents
2020-11-01 20:08:27 +07:00
2014-02-17 22:13:55 +00:00
class Terminal :
def __init__ ( self , contents ) :
self . contents = contents [ 0 ]
s = self . contents
2020-11-01 20:08:27 +07:00
self . is_keyword = len ( s ) > = 4 and s [ 0 : : len ( s ) - 1 ] == ' " " ' and all ( c in alphanums + " _ " for c in s [ 1 : - 1 ] )
2016-04-09 13:29:35 +02:00
def __repr__ ( self ) :
ty = " CaselessKeyword " if self . is_keyword else " CaselessLiteral "
return " %s ( %s ) " % ( ty , self . contents )
2014-02-17 22:13:55 +00:00
LPAREN = Suppress ( " ( " )
RPAREN = Suppress ( " ) " )
LBRACK = Suppress ( " [ " )
RBRACK = Suppress ( " ] " )
LBRACE = Suppress ( " { " )
RBRACE = Suppress ( " } " )
EQUALS = Suppress ( " = " )
2020-11-01 20:08:27 +07:00
VBAR = Suppress ( " | " )
2014-02-17 22:13:55 +00:00
PERIOD = Suppress ( " . " )
2020-11-01 20:08:27 +07:00
HASH = Suppress ( " # " )
2014-02-17 22:13:55 +00:00
2020-11-01 20:08:27 +07:00
identifier = Word ( alphanums + " _ " )
keyword = Word ( alphanums + " _ " ) . setParseAction ( Keyword )
2014-02-17 22:13:55 +00:00
expression = Forward ( )
2020-11-01 20:08:27 +07:00
optional = Group ( LBRACK + expression + RBRACK ) . setParseAction ( Optional )
repeated = Group ( LBRACE + expression + RBRACE ) . setParseAction ( Repeated )
terminal = quotedString . setParseAction ( Terminal )
term = ( keyword | terminal | optional | repeated | ( LPAREN + expression + RPAREN ) ) . setParseAction ( Term )
concat = Group ( term + OneOrMore ( term ) ) . setParseAction ( Concat )
factor = concat | term
union = Group ( factor + OneOrMore ( VBAR + factor ) ) . setParseAction ( Union )
rule = identifier + EQUALS + expression + PERIOD
2014-02-17 22:13:55 +00:00
expression << ( union | factor )
grammar = OneOrMore ( Group ( rule ) )
grammar . ignore ( HASH + restOfLine )
2020-11-01 20:08:27 +07:00
express = grammar . parseFile ( os . path . join ( os . path . dirname ( __file__ ) , " express.bnf " ) )
2014-02-17 22:13:55 +00:00
2020-11-01 20:08:27 +07:00
def find_bytype ( expr , ty , li = None ) :
if li is None :
li = [ ]
2014-02-17 22:13:55 +00:00
if isinstance ( expr , Term ) :
expr = expr . contents
2016-04-09 13:29:35 +02:00
if isinstance ( expr , ty ) :
li . append ( expr )
return set ( li )
2014-02-17 22:13:55 +00:00
elif isinstance ( expr , Expression ) :
for term in expr :
2016-04-09 13:29:35 +02:00
find_bytype ( term , ty , li )
2014-02-17 22:13:55 +00:00
return set ( li )
2020-11-01 20:08:27 +07:00
2014-02-17 22:13:55 +00:00
actions = {
2020-11-01 20:08:27 +07:00
" type_decl " : " TypeDeclaration " ,
" entity_decl " : " EntityDeclaration " ,
" enumeration_type " : " EnumerationType " ,
" aggregation_types " : " AggregationType " ,
" general_aggregation_types " : " AggregationType " ,
" select_type " : " SelectType " ,
" binary_type " : " BinaryType " ,
" subtype_declaration " : " SubTypeExpression " ,
" supertype_constraint " : " SuperTypeExpression " ,
" derive_clause " : " AttributeList " ,
" inverse_clause " : " AttributeList " ,
" inverse_attr " : " InverseAttribute " ,
" bound_spec " : " BoundSpecification " ,
" explicit_attr " : " ExplicitAttribute " ,
" width_spec " : " WidthSpec " ,
" string_type " : " StringType " ,
" named_types " : " NamedType " ,
" simple_types " : " SimpleType " ,
2022-12-26 11:29:32 +01:00
" function_decl " : " FunctionDeclaration " ,
" rule_decl " : " RuleDeclaration " ,
2014-02-17 22:13:55 +00:00
}
to_emit = set ( id for id , expr in express )
emitted = set ( )
to_combine = set ( [ " simple_id " ] )
2026-06-25 13:35:35 +02:00
to_original_text = set ( [ " simple_string_literal " ] )
2014-02-17 22:13:55 +00:00
statements = [ ]
2020-11-01 20:08:27 +07:00
terminals = reduce ( lambda x , y : x | y , ( find_bytype ( e , Terminal ) for id , e in express ) )
keywords = list ( filter ( operator . attrgetter ( " is_keyword " ) , terminals ) )
2016-04-09 13:29:35 +02:00
negated_keywords = map ( lambda s : " ~ %s " % s , keywords )
2026-02-26 12:36:01 +01:00
no_action = {
" letter " ,
" digit " ,
" digits " ,
" real_literal " ,
" integer_literal " ,
" string_literal " ,
" simple_string_literal " ,
" letter " ,
" not_quote " ,
" not_paren_star_quote_special " ,
}
2014-02-17 22:13:55 +00:00
while True :
emitted_in_loop = set ( )
for id , expr in express :
2016-04-09 13:29:35 +02:00
kws = map ( repr , find_bytype ( expr , Keyword ) )
2014-02-17 22:13:55 +00:00
found = [ k in emitted for k in kws ]
if id in to_emit and all ( found ) :
emitted_in_loop . add ( id )
emitted . add ( id )
stmt = " ( %s ) " % expr
if id in to_combine :
2016-04-09 13:29:35 +02:00
stmt = " + " . join ( itertools . chain ( negated_keywords , ( " originalTextFor(Combine %s ) " % stmt , ) ) )
2026-06-25 13:35:35 +02:00
elif id in to_original_text :
2026-06-26 15:13:22 +02:00
# We use lower() because it better matches the previous default of the CaselessLiterals for individual lexemes and express dictates case-insensitive comparisons anyway
stmt = " (originalTextFor %s ).addParseAction(tokenMap(str.lower)) " % stmt
2020-08-29 10:23:59 +02:00
if id not in no_action and not isinstance ( expr . contents , Keyword ) and not id in to_combine :
node_type = " ListNode " if " ZeroOrMore " in stmt else " Node "
2020-11-01 20:08:27 +07:00
action = actions . get ( id , ' lambda s, loc, t: %s (s, loc, t, rule= " %s " ) ' % ( node_type , id ) )
2020-08-29 10:23:59 +02:00
stmt = " %s .setParseAction( %s ) " % ( stmt , action )
2020-11-01 20:08:27 +07:00
statements . append ( ' %s = %s ( " %s " ) ' % ( id , stmt , id ) )
2014-02-17 22:13:55 +00:00
to_emit - = emitted_in_loop
2020-11-01 20:08:27 +07:00
if not emitted_in_loop :
break
2014-02-17 22:13:55 +00:00
for id in to_emit :
2020-11-01 20:08:27 +07:00
statements . append ( ' %s = Forward()( " %s " ) ' % ( id , id ) )
2014-02-17 22:13:55 +00:00
for id in to_emit :
expr = [ e for k , e in express if k == id ] [ 0 ]
stmt = " ( %s ) " % expr
if id in to_combine :
stmt = " Suppress %s " % stmt
2026-06-25 13:35:35 +02:00
elif id in to_original_text :
2026-06-26 15:13:22 +02:00
stmt = " (originalTextFor %s ).addParseAction(tokenMap(str.lower)) " % stmt
2020-08-29 10:23:59 +02:00
if id not in no_action and not isinstance ( expr . contents , Keyword ) :
2026-02-26 12:36:01 +01:00
children = list (
map ( operator . attrgetter ( " contents " ) , reduce ( lambda x , y : x | y , ( find_bytype ( e , Keyword ) for e in [ expr ] ) ) )
)
2022-12-26 11:29:32 +01:00
has_duplicates = len ( children ) > len ( set ( children ) )
node_type = " ListNode " if ( " ZeroOrMore " in stmt or has_duplicates ) else " Node "
2020-11-01 20:08:27 +07:00
action = " .setParseAction( %s ) " % (
actions [ id ] if id in actions else ' lambda s, loc, t: %s (s, loc, t, rule= " %s " ) ' % ( node_type , id )
)
2020-08-29 10:23:59 +02:00
stmt = " ( %s ) %s " % ( stmt , action )
2014-02-17 22:13:55 +00:00
statements . append ( " %s << %s " % ( id , stmt ) )
2022-07-10 21:51:25 +02:00
if __name__ == " __main__ " :
2026-07-10 20:42:18 +01:00
print ( r """
2020-08-29 10:23:59 +02:00
# This file is generated by IfcOpenShell ifcexpressparser bootstrap.py
2024-06-24 11:20:04 +05:00
from __future__ import annotations
2020-08-29 10:23:59 +02:00
import os
2015-08-13 14:26:14 +02:00
import sys
import pickle
2020-08-29 10:23:59 +02:00
import schema
import mapping
2014-02-17 22:13:55 +00:00
2020-08-29 10:23:59 +02:00
from pyparsing import *
from nodes import *
2024-06-24 11:20:04 +05:00
def parse(fn: str) -> mapping.Mapping:
2020-08-29 10:23:59 +02:00
cache_file = fn + " .cache.dat "
2020-09-08 14:40:27 +02:00
if os.path.exists(cache_file) and os.path.getmtime(cache_file) >= os.path.getmtime(fn):
2020-08-29 10:23:59 +02:00
with open(cache_file, " rb " ) as f:
m = pickle.load(f)
else:
%s
syntax.ignore( " -- " + restOfLine)
syntax.ignore(Regex(r " \ ((?: \ *(?:[^*]* \ *+)+? \ )) " ))
ast = syntax.parseFile(fn)
s = schema.Schema(ast)
m = mapping.Mapping(s)
with open(cache_file, " wb " ) as f:
pickle.dump(m, f, protocol=0)
return m
if __name__ == " __main__ " :
m = parse(sys.argv[1])
import importlib
for output in sys.argv[2:]:
mdl = importlib.import_module(output)
mdl.Generator(m).emit()
sys.stdout.write(m.schema.name)
2026-07-10 20:42:18 +01:00
""" % ( " \n " . join ( statements ) ) )