Feature: express rules (#2662)

* temp reasonable state, missing rule_head contents

* better state, with init() and rule_id, but some missing parameters

* performance; embed root in dict; fix lookup

* Add ast_utils

* Fixes related to listnode and typo

* First steps with code gen

* Finalize first steps towards working code generation

* Backwards compat

* Rule execution, error formatting, test cases

* Where rule: support indexing, more expressions, test plane angle measure

* positive length measure test cases

* Venture into entity rules, test actor role

* Fixed for not unary op and address test cases

* Test group qualifier and air terminal type

* typeof() implementation and test annotation curve occ

* Flexible handling of expression, type set intersection, IfcAnnotationSurface test cases

* Functions, derived attrs, fix branch order, allow to terminate branches, apply -1 to index

* Implement express query(), test cases for 2x3 bspline curve

* Function args, if-else, repeat, entity instance construction, cshape test cases

* Multiple function call arguments and other small but influential changes. IfcDotProject() almost working except for variable case

* Make lowercase and fix assignment to qualified lhs

* Entity instance schema without file, aggregate assignment workaround, range fix, lowercase instance locals, extruded area direction test cases

* Enumeration item handling, query() robustness, arbitrary profile test cases

* Empty aggregate_initializer, xor and instance equality, nested expression fix in IfcCrossProduct, axis2-3d test cases

* Numeric stable sort with indices greater then 10, get ast node parent, redeclared derived support, nested expression robustness, define entity functions, case statements, xor, mod, bool literals, nvl, unknown, conversion based unit test cases

* Safeguard for schema name case norm, case norm query variable_id

* free instance comparison, supertype serialization fix, include inherited attributes in locals, proper elif-else in case stmt, loindex, shape rep test cases

* Union operator on set, retain general_aggr_types in parse tree, test cases for property set

* Escape stmt, rule locals and statements

* Return calculated values for redeclared derived attributes

* fix for rules without stmt, add derived and inverse attributes to locals, filter unused locals, wrap exists() arg with lambda to catch indexerror, support repeated aggr init element, typeof() none check, enum namespace uppercase

* Regen 2x3 rules

* Rule test filter only on basename

* Update fixtures for compliance with full body of rules

* Add rule support to ifcopenshell.validate

* Implement usedin() function, test cases for IfcWallSC MLS

* blength

* Run code generation on all schemas

* Try to eliminate runtime pyparsing dep

* Try to eliminate runtime pyparsing dep
This commit is contained in:
Thomas Krijnen
2022-12-26 11:29:32 +01:00
committed by GitHub
parent 2ccf25f7eb
commit 0f9c84c1ce
143 changed files with 265486 additions and 270 deletions
@@ -22,6 +22,7 @@ from __future__ import division
from __future__ import print_function
import functools
import importlib
import numbers
import itertools
@@ -33,7 +34,7 @@ except ImportError as e:
logging = type("logger", (object,), {"exception": staticmethod(lambda s: print(s))})
def set_derived_atribute(*args):
def set_derived_attribute(*args):
raise TypeError("Unable to set derived attribute")
@@ -73,7 +74,7 @@ def register_schema_attributes(schema):
# resolve to actual functions in wrapper
functions = [
set_derived_atribute
set_derived_attribute
if mname == "setArgumentAsDerived"
else getattr(ifcopenshell_wrapper.entity_instance, mname)
for mname in fn_names
@@ -123,17 +124,30 @@ class entity_instance(object):
INVALID, FORWARD, INVERSE = range(3)
attr_cat = self.wrapped_data.get_attribute_category(name)
if attr_cat == FORWARD:
return entity_instance.wrap_value(
self.wrapped_data.get_argument(
self.wrapped_data.get_argument_index(name)
),
self.wrapped_data.file,
)
idx = self.wrapped_data.get_argument_index(name)
if _method_dict[self.is_a(True)][idx] != set_derived_attribute:
# A bit ugly, but we fall through to derived attribute handling below
return entity_instance.wrap_value(
self.wrapped_data.get_argument(idx), self.wrapped_data.file
)
elif attr_cat == INVERSE:
return entity_instance.wrap_value(
self.wrapped_data.get_inverse(name), self.wrapped_data.file
)
else:
return entity_instance.wrap_value(self.wrapped_data.get_inverse(name), self.wrapped_data.file)
# derived attribute perhaps?
schema_name = self.wrapped_data.is_a(True).split('.')[0]
rules = importlib.import_module(f"ifcopenshell.express.rules.{schema_name}")
def yield_supertypes():
decl = ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(self.is_a())
while decl:
yield decl.name()
decl = decl.supertype()
for sty in yield_supertypes():
fn = getattr(rules, f"calc_{sty}_{name}", None)
if fn:
return fn(self)
if attr_cat != FORWARD:
raise AttributeError(
"entity instance of type '%s' has no attribute '%s'"
% (self.wrapped_data.is_a(True), name)
@@ -218,7 +232,7 @@ class entity_instance(object):
method = self.method_list[idx]
if value is None:
if method is not set_derived_atribute:
if method is not set_derived_attribute:
self.wrapped_data.setArgumentAsNull(idx)
else:
self.method_list[idx](
@@ -275,17 +289,22 @@ class entity_instance(object):
def __eq__(self, other):
if not isinstance(self, type(other)):
return False
# Proper entity instances have a stable identity by means of the numeric
# step id. Selected type instances (such as IfcPropertySingleValue.NominalValue
# always have id=0, so we compare <type, value, file pointer>
if self.id():
return self.wrapped_data == other.wrapped_data
elif None in (self.wrapped_data.file, other.wrapped_data.file):
# when not added to a file, we can only compare attribute values
# and we need this for where rule evaluation
return self.get_info(recursive=True, include_identifier=False) == other.get_info(recursive=True, include_identifier=False)
else:
return (self.is_a(), self[0], self.wrapped_data.file_pointer()) == (
other.is_a(),
other[0],
other.wrapped_data.file_pointer(),
)
# Proper entity instances have a stable identity by means of the numeric
# step id. Selected type instances (such as IfcPropertySingleValue.NominalValue
# always have id=0, so we compare <type, value, file pointer>
if self.id():
return self.wrapped_data == other.wrapped_data
else:
return (self.is_a(), self[0], self.wrapped_data.file_pointer()) == (
other.is_a(),
other[0],
other.wrapped_data.file_pointer(),
)
def __hash__(self):
# Proper entity instances have a stable identity by means of the numeric
@@ -29,11 +29,9 @@ if not os.path.exists(exp_parser_fn):
with open(exp_parser_fn, "w") as f:
subprocess.call([sys.executable, "bootstrap.py"], cwd=d, stdout=f)
import express_parser
import schema_class
import ifcopenshell.ifcopenshell_wrapper
def parse(fn):
import express_parser
import schema_class
mapping = express_parser.parse(fn)
return schema_class.SchemaClass(mapping, schema_class.LateBoundSchemaInstantiator).code
@@ -151,6 +151,8 @@ actions = {
"string_type": "StringType",
"named_types": "NamedType",
"simple_types": "SimpleType",
"function_decl": "FunctionDeclaration",
"rule_decl": "RuleDeclaration",
}
to_emit = set(id for id, expr in express)
@@ -192,7 +194,9 @@ for id in to_emit:
if id in to_combine:
stmt = "Suppress%s" % stmt
if id not in no_action and not isinstance(expr.contents, Keyword):
node_type = "ListNode" if "ZeroOrMore" in stmt else "Node"
children = list(map(operator.attrgetter('contents'), reduce(lambda x, y: x | y, (find_bytype(e, Keyword) for e in [expr]))))
has_duplicates = len(children) > len(set(children))
node_type = "ListNode" if ("ZeroOrMore" in stmt or has_duplicates) else "Node"
action = ".setParseAction(%s)" % (
actions[id] if id in actions else 'lambda s, loc, t: %s(s, loc, t, rule="%s")' % (node_type, id)
)
@@ -16,6 +16,18 @@
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import itertools
import functools
def indent(n, s):
if isinstance(s, str):
strs = [s]
else:
strs = s
splitted = itertools.chain.from_iterable(map(functools.partial(str.split, sep="\n"), map(str, strs)))
return "\n".join(" "*n + l for l in splitted)
class Base(object):
"""
@@ -152,7 +152,7 @@ def parse(fn):
special = ((not_paren_star_quote_special | CaselessLiteral("(") | CaselessLiteral(")") | CaselessLiteral("*") | CaselessLiteral("\"\""))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="special"))("special")
binary_literal = ((CaselessLiteral("%") + bit + ZeroOrMore(bit))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="binary_literal"))("binary_literal")
integer_literal = (digits)("integer_literal")
simple_id = ~CaselessKeyword("abstract") + ~CaselessKeyword("reference") + ~CaselessKeyword("pi") + ~CaselessKeyword("andor") + ~CaselessKeyword("loindex") + ~CaselessKeyword("aggregate") + ~CaselessKeyword("self") + ~CaselessKeyword("length") + ~CaselessKeyword("optional") + ~CaselessKeyword("for") + ~CaselessKeyword("end") + ~CaselessKeyword("local") + ~CaselessKeyword("true") + ~CaselessKeyword("logical") + ~CaselessKeyword("constant") + ~CaselessKeyword("nvl") + ~CaselessKeyword("bag") + ~CaselessKeyword("repeat") + ~CaselessKeyword("boolean") + ~CaselessKeyword("otherwise") + ~CaselessKeyword("type") + ~CaselessKeyword("typeof") + ~CaselessKeyword("alias") + ~CaselessKeyword("in") + ~CaselessKeyword("mod") + ~CaselessKeyword("escape") + ~CaselessKeyword("or") + ~CaselessKeyword("of") + ~CaselessKeyword("like") + ~CaselessKeyword("value_unique") + ~CaselessKeyword("tan") + ~CaselessKeyword("oneof") + ~CaselessKeyword("log") + ~CaselessKeyword("schema") + ~CaselessKeyword("fixed") + ~CaselessKeyword("by") + ~CaselessKeyword("integer") + ~CaselessKeyword("div") + ~CaselessKeyword("log10") + ~CaselessKeyword("not") + ~CaselessKeyword("skip") + ~CaselessKeyword("odd") + ~CaselessKeyword("return") + ~CaselessKeyword("end_subtype_constraint") + ~CaselessKeyword("end_alias") + ~CaselessKeyword("remove") + ~CaselessKeyword("unknown") + ~CaselessKeyword("end_repeat") + ~CaselessKeyword("enumeration") + ~CaselessKeyword("query") + ~CaselessKeyword("function") + ~CaselessKeyword("list") + ~CaselessKeyword("end_local") + ~CaselessKeyword("cos") + ~CaselessKeyword("atan") + ~CaselessKeyword("hibound") + ~CaselessKeyword("rolesof") + ~CaselessKeyword("end_function") + ~CaselessKeyword("abs") + ~CaselessKeyword("renamed") + ~CaselessKeyword("select") + ~CaselessKeyword("end_if") + ~CaselessKeyword("case") + ~CaselessKeyword("sizeof") + ~CaselessKeyword("var") + ~CaselessKeyword("end_case") + ~CaselessKeyword("acos") + ~CaselessKeyword("supertype") + ~CaselessKeyword("then") + ~CaselessKeyword("inverse") + ~CaselessKeyword("hiindex") + ~CaselessKeyword("false") + ~CaselessKeyword("generic") + ~CaselessKeyword("as") + ~CaselessKeyword("use") + ~CaselessKeyword("end_entity") + ~CaselessKeyword("rule") + ~CaselessKeyword("derive") + ~CaselessKeyword("set") + ~CaselessKeyword("subtype") + ~CaselessKeyword("unique") + ~CaselessKeyword("subtype_constraint") + ~CaselessKeyword("where") + ~CaselessKeyword("until") + ~CaselessKeyword("usedin") + ~CaselessKeyword("value") + ~CaselessKeyword("array") + ~CaselessKeyword("sqrt") + ~CaselessKeyword("value_in") + ~CaselessKeyword("to") + ~CaselessKeyword("xor") + ~CaselessKeyword("sin") + ~CaselessKeyword("while") + ~CaselessKeyword("with") + ~CaselessKeyword("string") + ~CaselessKeyword("total_over") + ~CaselessKeyword("binary") + ~CaselessKeyword("exp") + ~CaselessKeyword("and") + ~CaselessKeyword("number") + ~CaselessKeyword("generic_entity") + ~CaselessKeyword("const_e") + ~CaselessKeyword("end_type") + ~CaselessKeyword("log2") + ~CaselessKeyword("lobound") + ~CaselessKeyword("end_procedure") + ~CaselessKeyword("end_schema") + ~CaselessKeyword("end_constant") + ~CaselessKeyword("procedure") + ~CaselessKeyword("else") + ~CaselessKeyword("end_rule") + ~CaselessKeyword("if") + ~CaselessKeyword("based_on") + ~CaselessKeyword("exists") + ~CaselessKeyword("asin") + ~CaselessKeyword("blength") + ~CaselessKeyword("entity") + ~CaselessKeyword("from") + ~CaselessKeyword("format") + ~CaselessKeyword("insert") + ~CaselessKeyword("begin") + ~CaselessKeyword("extensible") + ~CaselessKeyword("real") + originalTextFor(Combine((letter + ZeroOrMore((letter | digit | CaselessLiteral("_"))))))("simple_id")
simple_id = ~CaselessKeyword("bag") + ~CaselessKeyword("lobound") + ~CaselessKeyword("aggregate") + ~CaselessKeyword("reference") + ~CaselessKeyword("abstract") + ~CaselessKeyword("value_unique") + ~CaselessKeyword("if") + ~CaselessKeyword("loindex") + ~CaselessKeyword("format") + ~CaselessKeyword("true") + ~CaselessKeyword("insert") + ~CaselessKeyword("exp") + ~CaselessKeyword("end_type") + ~CaselessKeyword("end") + ~CaselessKeyword("optional") + ~CaselessKeyword("in") + ~CaselessKeyword("like") + ~CaselessKeyword("type") + ~CaselessKeyword("end_rule") + ~CaselessKeyword("repeat") + ~CaselessKeyword("nvl") + ~CaselessKeyword("otherwise") + ~CaselessKeyword("procedure") + ~CaselessKeyword("number") + ~CaselessKeyword("boolean") + ~CaselessKeyword("exists") + ~CaselessKeyword("andor") + ~CaselessKeyword("alias") + ~CaselessKeyword("entity") + ~CaselessKeyword("constant") + ~CaselessKeyword("tan") + ~CaselessKeyword("or") + ~CaselessKeyword("oneof") + ~CaselessKeyword("from") + ~CaselessKeyword("escape") + ~CaselessKeyword("typeof") + ~CaselessKeyword("extensible") + ~CaselessKeyword("div") + ~CaselessKeyword("then") + ~CaselessKeyword("by") + ~CaselessKeyword("unknown") + ~CaselessKeyword("var") + ~CaselessKeyword("pi") + ~CaselessKeyword("inverse") + ~CaselessKeyword("skip") + ~CaselessKeyword("array") + ~CaselessKeyword("end_subtype_constraint") + ~CaselessKeyword("use") + ~CaselessKeyword("self") + ~CaselessKeyword("end_alias") + ~CaselessKeyword("select") + ~CaselessKeyword("for") + ~CaselessKeyword("sizeof") + ~CaselessKeyword("fixed") + ~CaselessKeyword("local") + ~CaselessKeyword("remove") + ~CaselessKeyword("enumeration") + ~CaselessKeyword("end_local") + ~CaselessKeyword("not") + ~CaselessKeyword("function") + ~CaselessKeyword("cos") + ~CaselessKeyword("logical") + ~CaselessKeyword("query") + ~CaselessKeyword("atan") + ~CaselessKeyword("return") + ~CaselessKeyword("schema") + ~CaselessKeyword("hiindex") + ~CaselessKeyword("rolesof") + ~CaselessKeyword("log10") + ~CaselessKeyword("end_function") + ~CaselessKeyword("abs") + ~CaselessKeyword("length") + ~CaselessKeyword("renamed") + ~CaselessKeyword("acos") + ~CaselessKeyword("end_case") + ~CaselessKeyword("case") + ~CaselessKeyword("mod") + ~CaselessKeyword("end_if") + ~CaselessKeyword("list") + ~CaselessKeyword("end_repeat") + ~CaselessKeyword("generic") + ~CaselessKeyword("of") + ~CaselessKeyword("supertype") + ~CaselessKeyword("false") + ~CaselessKeyword("end_entity") + ~CaselessKeyword("odd") + ~CaselessKeyword("integer") + ~CaselessKeyword("hibound") + ~CaselessKeyword("rule") + ~CaselessKeyword("as") + ~CaselessKeyword("derive") + ~CaselessKeyword("log") + ~CaselessKeyword("set") + ~CaselessKeyword("subtype_constraint") + ~CaselessKeyword("unique") + ~CaselessKeyword("value") + ~CaselessKeyword("subtype") + ~CaselessKeyword("until") + ~CaselessKeyword("with") + ~CaselessKeyword("sqrt") + ~CaselessKeyword("where") + ~CaselessKeyword("value_in") + ~CaselessKeyword("to") + ~CaselessKeyword("xor") + ~CaselessKeyword("sin") + ~CaselessKeyword("while") + ~CaselessKeyword("string") + ~CaselessKeyword("usedin") + ~CaselessKeyword("total_over") + ~CaselessKeyword("binary") + ~CaselessKeyword("and") + ~CaselessKeyword("end_schema") + ~CaselessKeyword("generic_entity") + ~CaselessKeyword("end_constant") + ~CaselessKeyword("const_e") + ~CaselessKeyword("based_on") + ~CaselessKeyword("else") + ~CaselessKeyword("asin") + ~CaselessKeyword("blength") + ~CaselessKeyword("real") + ~CaselessKeyword("end_procedure") + ~CaselessKeyword("log2") + ~CaselessKeyword("begin") + originalTextFor(Combine((letter + ZeroOrMore((letter | digit | CaselessLiteral("_"))))))("simple_id")
simple_string_literal = ((CaselessLiteral("'") + ZeroOrMore(((CaselessLiteral("'") + CaselessLiteral("'")) | not_quote)) + CaselessLiteral("'")))("simple_string_literal")
abstract_entity_declaration = (ABSTRACT)("abstract_entity_declaration")
abstract_supertype = ((ABSTRACT + SUPERTYPE + CaselessLiteral(";"))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="abstract_supertype"))("abstract_supertype")
@@ -250,224 +250,224 @@ def parse(fn):
constructed_types = ((enumeration_type | select_type)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="constructed_types"))("constructed_types")
reference_clause = ((REFERENCE + FROM + schema_ref + Optional((CaselessLiteral("(") + resource_or_rename + ZeroOrMore((CaselessLiteral(",") + resource_or_rename)) + CaselessLiteral(")"))) + CaselessLiteral(";"))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="reference_clause"))("reference_clause")
interface_specification = ((reference_clause | use_clause)).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="interface_specification"))("interface_specification")
list_type = Forward()("list_type")
parameter_type = Forward()("parameter_type")
qualifiable_factor = Forward()("qualifiable_factor")
supertype_expression = Forward()("supertype_expression")
precision_spec = Forward()("precision_spec")
element = Forward()("element")
subtype_constraint_decl = Forward()("subtype_constraint_decl")
derived_attr = Forward()("derived_attr")
general_bag_type = Forward()("general_bag_type")
aggregation_types = Forward()("aggregation_types")
entity_decl = Forward()("entity_decl")
rule_decl = Forward()("rule_decl")
numeric_expression = Forward()("numeric_expression")
increment = Forward()("increment")
supertype_constraint = Forward()("supertype_constraint")
concrete_types = Forward()("concrete_types")
remark = Forward()("remark")
width = Forward()("width")
index = Forward()("index")
selector = Forward()("selector")
simple_factor = Forward()("simple_factor")
actual_parameter_list = Forward()("actual_parameter_list")
general_set_type = Forward()("general_set_type")
declaration = Forward()("declaration")
repeat_stmt = Forward()("repeat_stmt")
until_control = Forward()("until_control")
supertype_factor = Forward()("supertype_factor")
one_of = Forward()("one_of")
stmt = Forward()("stmt")
general_aggregation_types = Forward()("general_aggregation_types")
procedure_call_stmt = Forward()("procedure_call_stmt")
repetition = Forward()("repetition")
case_action = Forward()("case_action")
inverse_attr = Forward()("inverse_attr")
aggregate_initializer = Forward()("aggregate_initializer")
bound_spec = Forward()("bound_spec")
interval_high = Forward()("interval_high")
algorithm_head = Forward()("algorithm_head")
real_type = Forward()("real_type")
case_stmt = Forward()("case_stmt")
constant_decl = Forward()("constant_decl")
alias_stmt = Forward()("alias_stmt")
function_head = Forward()("function_head")
interval_low = Forward()("interval_low")
aggregate_source = Forward()("aggregate_source")
query_expression = Forward()("query_expression")
derived_attr = Forward()("derived_attr")
width_spec = Forward()("width_spec")
interval_item = Forward()("interval_item")
subsuper = Forward()("subsuper")
array_type = Forward()("array_type")
primary = Forward()("primary")
case_label = Forward()("case_label")
index_1 = Forward()("index_1")
entity_constructor = Forward()("entity_constructor")
subtype_constraint_body = Forward()("subtype_constraint_body")
constant_body = Forward()("constant_body")
actual_parameter_list = Forward()("actual_parameter_list")
type_decl = Forward()("type_decl")
selector = Forward()("selector")
abstract_supertype_declaration = Forward()("abstract_supertype_declaration")
explicit_attr = Forward()("explicit_attr")
entity_body = Forward()("entity_body")
repeat_control = Forward()("repeat_control")
expression = Forward()("expression")
if_stmt = Forward()("if_stmt")
subtype_constraint = Forward()("subtype_constraint")
while_control = Forward()("while_control")
logical_expression = Forward()("logical_expression")
aggregate_type = Forward()("aggregate_type")
interval = Forward()("interval")
general_array_type = Forward()("general_array_type")
aggregation_types = Forward()("aggregation_types")
domain_rule = Forward()("domain_rule")
entity_constructor = Forward()("entity_constructor")
function_call = Forward()("function_call")
numeric_expression = Forward()("numeric_expression")
general_set_type = Forward()("general_set_type")
qualifier = Forward()("qualifier")
formal_parameter = Forward()("formal_parameter")
index_1 = Forward()("index_1")
underlying_type = Forward()("underlying_type")
instantiable_type = Forward()("instantiable_type")
supertype_rule = Forward()("supertype_rule")
generalized_types = Forward()("generalized_types")
local_variable = Forward()("local_variable")
schema_body = Forward()("schema_body")
general_list_type = Forward()("general_list_type")
assignment_stmt = Forward()("assignment_stmt")
bound_2 = Forward()("bound_2")
binary_type = Forward()("binary_type")
syntax = Forward()("syntax")
aggregate_source = Forward()("aggregate_source")
while_control = Forward()("while_control")
interval_low = Forward()("interval_low")
parameter = Forward()("parameter")
string_type = Forward()("string_type")
supertype_term = Forward()("supertype_term")
embedded_remark = Forward()("embedded_remark")
increment_control = Forward()("increment_control")
local_decl = Forward()("local_decl")
precision_spec = Forward()("precision_spec")
one_of = Forward()("one_of")
subtype_constraint_body = Forward()("subtype_constraint_body")
general_array_type = Forward()("general_array_type")
list_type = Forward()("list_type")
subtype_constraint_decl = Forward()("subtype_constraint_decl")
schema_decl = Forward()("schema_decl")
algorithm_head = Forward()("algorithm_head")
query_expression = Forward()("query_expression")
primary = Forward()("primary")
repeat_control = Forward()("repeat_control")
factor = Forward()("factor")
procedure_head = Forward()("procedure_head")
function_decl = Forward()("function_decl")
type_decl = Forward()("type_decl")
general_aggregation_types = Forward()("general_aggregation_types")
domain_rule = Forward()("domain_rule")
schema_decl = Forward()("schema_decl")
derive_clause = Forward()("derive_clause")
return_stmt = Forward()("return_stmt")
bag_type = Forward()("bag_type")
procedure_decl = Forward()("procedure_decl")
abstract_supertype_declaration = Forward()("abstract_supertype_declaration")
inverse_clause = Forward()("inverse_clause")
index_qualifier = Forward()("index_qualifier")
bound_1 = Forward()("bound_1")
compound_stmt = Forward()("compound_stmt")
set_type = Forward()("set_type")
where_clause = Forward()("where_clause")
qualifier = Forward()("qualifier")
entity_head = Forward()("entity_head")
stmt = Forward()("stmt")
index_2 = Forward()("index_2")
term = Forward()("term")
function_call = Forward()("function_call")
formal_parameter = Forward()("formal_parameter")
aggregate_type = Forward()("aggregate_type")
repeat_stmt = Forward()("repeat_stmt")
entity_body = Forward()("entity_body")
interval_high = Forward()("interval_high")
logical_expression = Forward()("logical_expression")
simple_expression = Forward()("simple_expression")
remark = Forward()("remark")
simple_factor = Forward()("simple_factor")
case_stmt = Forward()("case_stmt")
derive_clause = Forward()("derive_clause")
supertype_constraint = Forward()("supertype_constraint")
assignment_stmt = Forward()("assignment_stmt")
entity_head = Forward()("entity_head")
set_type = Forward()("set_type")
instantiable_type = Forward()("instantiable_type")
declaration = Forward()("declaration")
binary_type = Forward()("binary_type")
interval = Forward()("interval")
parameter_type = Forward()("parameter_type")
term = Forward()("term")
index = Forward()("index")
expression = Forward()("expression")
bag_type = Forward()("bag_type")
schema_body = Forward()("schema_body")
until_control = Forward()("until_control")
simple_types = Forward()("simple_types")
list_type << (((LIST + Optional(bound_spec) + OF + Optional(UNIQUE) + instantiable_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="list_type"))
parameter_type << (((generalized_types | simple_types | named_types))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="parameter_type"))
qualifiable_factor << (((function_call | attribute_ref | constant_factor | general_ref | population))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="qualifiable_factor"))
supertype_expression << (((supertype_factor + ZeroOrMore((ANDOR + supertype_factor))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="supertype_expression"))
precision_spec << (numeric_expression)
element << (((expression + Optional((CaselessLiteral(":") + repetition))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="element"))
subtype_constraint_decl << (((subtype_constraint_head + subtype_constraint_body + END_SUBTYPE_CONSTRAINT + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subtype_constraint_decl"))
derived_attr << (((attribute_decl + CaselessLiteral(":") + parameter_type + CaselessLiteral(":=") + expression + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="derived_attr"))
general_bag_type << (((BAG + Optional(bound_spec) + OF + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="general_bag_type"))
aggregation_types << (((array_type | bag_type | list_type | set_type))).setParseAction(AggregationType)
entity_decl << (((entity_head + entity_body + END_ENTITY + CaselessLiteral(";")))).setParseAction(EntityDeclaration)
rule_decl << (((rule_head + algorithm_head + ZeroOrMore(stmt) + where_clause + END_RULE + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="rule_decl"))
numeric_expression << (simple_expression)
increment << (numeric_expression)
supertype_constraint << (((abstract_supertype_declaration | abstract_entity_declaration | supertype_rule))).setParseAction(SuperTypeExpression)
concrete_types << (((aggregation_types | simple_types | type_ref))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="concrete_types"))
remark << (((embedded_remark | tail_remark))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="remark"))
width << (numeric_expression)
index << (numeric_expression)
selector << (expression)
simple_factor << (((aggregate_initializer | interval | query_expression | (Optional(unary_op) + ((CaselessLiteral("(") + expression + CaselessLiteral(")")) | primary)) | entity_constructor | enumeration_reference))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="simple_factor"))
actual_parameter_list << (((CaselessLiteral("(") + Optional(parameter) + ZeroOrMore((CaselessLiteral(",") + parameter)) + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="actual_parameter_list"))
general_set_type << (((SET + Optional(bound_spec) + OF + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="general_set_type"))
declaration << (((entity_decl | function_decl | procedure_decl | subtype_constraint_decl | type_decl))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="declaration"))
repeat_stmt << (((REPEAT + repeat_control + CaselessLiteral(";") + stmt + ZeroOrMore(stmt) + END_REPEAT + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="repeat_stmt"))
until_control << (((UNTIL + logical_expression))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="until_control"))
supertype_factor << (((supertype_term + ZeroOrMore((AND + supertype_term))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="supertype_factor"))
one_of << (((ONEOF + CaselessLiteral("(") + supertype_expression + ZeroOrMore((CaselessLiteral(",") + supertype_expression)) + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="one_of"))
procedure_call_stmt << ((((built_in_procedure | procedure_ref) + actual_parameter_list + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="procedure_call_stmt"))
repetition << (numeric_expression)
case_action << (((case_label + ZeroOrMore((CaselessLiteral(",") + case_label)) + CaselessLiteral(":") + stmt))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="case_action"))
inverse_attr << (((attribute_decl + CaselessLiteral(":") + Optional(((SET | BAG) + Optional(bound_spec) + OF)) + entity_ref + FOR + Optional((entity_ref + CaselessLiteral("."))) + attribute_ref + CaselessLiteral(";")))).setParseAction(InverseAttribute)
aggregate_initializer << (((CaselessLiteral("[") + Optional((element + ZeroOrMore((CaselessLiteral(",") + element)))) + CaselessLiteral("]")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="aggregate_initializer"))
bound_spec << (((CaselessLiteral("[") + bound_1 + CaselessLiteral(":") + bound_2 + CaselessLiteral("]")))).setParseAction(BoundSpecification)
interval_high << (simple_expression)
algorithm_head << (((ZeroOrMore(declaration) + Optional(constant_decl) + Optional(local_decl)))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="algorithm_head"))
real_type << (((REAL + Optional((CaselessLiteral("(") + precision_spec + CaselessLiteral(")")))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="real_type"))
case_stmt << (((CASE + selector + OF + ZeroOrMore(case_action) + Optional((OTHERWISE + CaselessLiteral(":") + stmt)) + END_CASE + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="case_stmt"))
constant_decl << (((CONSTANT + constant_body + ZeroOrMore(constant_body) + END_CONSTANT + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="constant_decl"))
alias_stmt << (((ALIAS + variable_id + FOR + general_ref + ZeroOrMore(qualifier) + CaselessLiteral(";") + stmt + ZeroOrMore(stmt) + END_ALIAS + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="alias_stmt"))
function_head << (((FUNCTION + function_id + Optional((CaselessLiteral("(") + formal_parameter + ZeroOrMore((CaselessLiteral(";") + formal_parameter)) + CaselessLiteral(")"))) + CaselessLiteral(":") + parameter_type + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="function_head"))
interval_low << (simple_expression)
aggregate_source << (simple_expression)
query_expression << (((QUERY + CaselessLiteral("(") + variable_id + CaselessLiteral("<*") + aggregate_source + CaselessLiteral("|") + logical_expression + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="query_expression"))
width_spec << (((CaselessLiteral("(") + width + CaselessLiteral(")") + Optional(FIXED)))).setParseAction(WidthSpec)
interval_item << (simple_expression)
subsuper << (((Optional(supertype_constraint) + Optional(subtype_declaration)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subsuper"))
array_type << (((ARRAY + bound_spec + OF + Optional(OPTIONAL) + Optional(UNIQUE) + instantiable_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="array_type"))
primary << (((literal | (qualifiable_factor + ZeroOrMore(qualifier))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="primary"))
case_label << (expression)
index_1 << (index)
entity_constructor << (((entity_ref + CaselessLiteral("(") + Optional((expression + ZeroOrMore((CaselessLiteral(",") + expression)))) + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="entity_constructor"))
subtype_constraint_body << (((Optional(abstract_supertype) + Optional(total_over) + Optional((supertype_expression + CaselessLiteral(";")))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subtype_constraint_body"))
constant_body << (((constant_id + CaselessLiteral(":") + instantiable_type + CaselessLiteral(":=") + expression + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="constant_body"))
explicit_attr << (((attribute_decl + ZeroOrMore((CaselessLiteral(",") + attribute_decl)) + CaselessLiteral(":") + Optional(OPTIONAL) + parameter_type + CaselessLiteral(";")))).setParseAction(ExplicitAttribute)
entity_body << (((ZeroOrMore(explicit_attr) + Optional(derive_clause) + Optional(inverse_clause) + Optional(unique_clause) + Optional(where_clause)))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="entity_body"))
repeat_control << (((Optional(increment_control) + Optional(while_control) + Optional(until_control)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="repeat_control"))
expression << (((simple_expression + Optional((rel_op_extended + simple_expression))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="expression"))
if_stmt << (((IF + logical_expression + THEN + stmt + ZeroOrMore(stmt) + Optional((ELSE + stmt + ZeroOrMore(stmt))) + END_IF + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="if_stmt"))
subtype_constraint << (((OF + CaselessLiteral("(") + supertype_expression + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subtype_constraint"))
while_control << (((WHILE + logical_expression))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="while_control"))
logical_expression << (expression)
aggregate_type << (((AGGREGATE + Optional((CaselessLiteral(":") + type_label)) + OF + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="aggregate_type"))
interval << (((CaselessLiteral("{") + interval_low + interval_op + interval_item + interval_op + interval_high + CaselessLiteral("}")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="interval"))
general_array_type << (((ARRAY + Optional(bound_spec) + OF + Optional(OPTIONAL) + Optional(UNIQUE) + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="general_array_type"))
underlying_type << (((constructed_types | concrete_types))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="underlying_type"))
instantiable_type << (((concrete_types | entity_ref))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="instantiable_type"))
supertype_rule << (((SUPERTYPE + subtype_constraint))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="supertype_rule"))
generalized_types << (((aggregate_type | general_aggregation_types | generic_entity_type | generic_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="generalized_types"))
local_variable << (((variable_id + ZeroOrMore((CaselessLiteral(",") + variable_id)) + CaselessLiteral(":") + parameter_type + Optional((CaselessLiteral(":=") + expression)) + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="local_variable"))
schema_body << (((ZeroOrMore(interface_specification) + Optional(constant_decl) + ZeroOrMore((declaration | rule_decl))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="schema_body"))
general_list_type << (((LIST + Optional(bound_spec) + OF + Optional(UNIQUE) + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="general_list_type"))
assignment_stmt << (((general_ref + ZeroOrMore(qualifier) + CaselessLiteral(":=") + expression + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="assignment_stmt"))
bound_2 << (numeric_expression)
binary_type << (((BINARY + Optional(width_spec)))).setParseAction(BinaryType)
syntax << (((schema_decl + ZeroOrMore(schema_decl)))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="syntax"))
parameter << (expression)
string_type << (((STRING + Optional(width_spec)))).setParseAction(StringType)
supertype_term << (((one_of | (CaselessLiteral("(") + supertype_expression + CaselessLiteral(")")) | entity_ref))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="supertype_term"))
embedded_remark << (((CaselessLiteral("(*") + Optional(remark_tag) + ZeroOrMore(((not_paren_star + ZeroOrMore(not_paren_star)) | lparen_then_not_lparen_star | (CaselessLiteral("*") + ZeroOrMore(CaselessLiteral("*"))) | not_rparen_star_then_rparen | embedded_remark)) + CaselessLiteral("*)")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="embedded_remark"))
increment_control << (((variable_id + CaselessLiteral(":=") + bound_1 + TO + bound_2 + Optional((BY + increment))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="increment_control"))
local_decl << (((LOCAL + local_variable + ZeroOrMore(local_variable) + END_LOCAL + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="local_decl"))
factor << (((simple_factor + Optional((CaselessLiteral("**") + simple_factor))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="factor"))
procedure_head << (((PROCEDURE + procedure_id + Optional((CaselessLiteral("(") + Optional(VAR) + formal_parameter + ZeroOrMore((CaselessLiteral(";") + Optional(VAR) + formal_parameter)) + CaselessLiteral(")"))) + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="procedure_head"))
function_decl << (((function_head + algorithm_head + stmt + ZeroOrMore(stmt) + END_FUNCTION + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="function_decl"))
type_decl << (((TYPE + type_id + CaselessLiteral("=") + underlying_type + CaselessLiteral(";") + Optional(where_clause) + END_TYPE + CaselessLiteral(";")))).setParseAction(TypeDeclaration)
general_aggregation_types << (((general_array_type | general_bag_type | general_list_type | general_set_type))).setParseAction(AggregationType)
domain_rule << (((Optional((rule_label_id + CaselessLiteral(":"))) + expression))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="domain_rule"))
schema_decl << (((SCHEMA + schema_id + Optional(schema_version_id) + CaselessLiteral(";") + schema_body + END_SCHEMA + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="schema_decl"))
derive_clause << (((DERIVE + derived_attr + ZeroOrMore(derived_attr)))).setParseAction(AttributeList)
return_stmt << (((RETURN + Optional((CaselessLiteral("(") + expression + CaselessLiteral(")"))) + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="return_stmt"))
bag_type << (((BAG + Optional(bound_spec) + OF + instantiable_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="bag_type"))
procedure_decl << (((procedure_head + algorithm_head + ZeroOrMore(stmt) + END_PROCEDURE + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="procedure_decl"))
abstract_supertype_declaration << (((ABSTRACT + SUPERTYPE + Optional(subtype_constraint)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="abstract_supertype_declaration"))
inverse_clause << (((INVERSE + inverse_attr + ZeroOrMore(inverse_attr)))).setParseAction(AttributeList)
index_qualifier << (((CaselessLiteral("[") + index_1 + Optional((CaselessLiteral(":") + index_2)) + CaselessLiteral("]")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="index_qualifier"))
bound_1 << (numeric_expression)
compound_stmt << (((BEGIN + stmt + ZeroOrMore(stmt) + END + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="compound_stmt"))
set_type << (((SET + Optional(bound_spec) + OF + instantiable_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="set_type"))
where_clause << (((WHERE + domain_rule + CaselessLiteral(";") + ZeroOrMore((domain_rule + CaselessLiteral(";")))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="where_clause"))
qualifier << (((attribute_qualifier | group_qualifier | index_qualifier))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="qualifier"))
entity_head << (((ENTITY + entity_id + subsuper + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="entity_head"))
subsuper = Forward()("subsuper")
entity_decl = Forward()("entity_decl")
concrete_types = Forward()("concrete_types")
element = Forward()("element")
general_bag_type = Forward()("general_bag_type")
interval_item = Forward()("interval_item")
constant_body = Forward()("constant_body")
increment = Forward()("increment")
case_label = Forward()("case_label")
case_action = Forward()("case_action")
width = Forward()("width")
procedure_decl = Forward()("procedure_decl")
increment_control = Forward()("increment_control")
index_qualifier = Forward()("index_qualifier")
constant_decl = Forward()("constant_decl")
supertype_rule = Forward()("supertype_rule")
syntax = Forward()("syntax")
function_head = Forward()("function_head")
repetition = Forward()("repetition")
if_stmt = Forward()("if_stmt")
supertype_expression = Forward()("supertype_expression")
inverse_clause = Forward()("inverse_clause")
aggregate_initializer = Forward()("aggregate_initializer")
return_stmt = Forward()("return_stmt")
generalized_types = Forward()("generalized_types")
bound_2 = Forward()("bound_2")
real_type = Forward()("real_type")
index_2 = Forward()("index_2")
array_type = Forward()("array_type")
local_decl = Forward()("local_decl")
supertype_term = Forward()("supertype_term")
where_clause = Forward()("where_clause")
embedded_remark = Forward()("embedded_remark")
compound_stmt = Forward()("compound_stmt")
bound_1 = Forward()("bound_1")
alias_stmt = Forward()("alias_stmt")
subtype_constraint = Forward()("subtype_constraint")
string_type = Forward()("string_type")
function_decl = Forward()("function_decl")
general_list_type = Forward()("general_list_type")
supertype_factor = Forward()("supertype_factor")
rule_decl = Forward()("rule_decl")
qualifiable_factor = Forward()("qualifiable_factor")
bound_spec = Forward()("bound_spec")
stmt << (((alias_stmt | assignment_stmt | case_stmt | compound_stmt | escape_stmt | if_stmt | null_stmt | procedure_call_stmt | repeat_stmt | return_stmt | skip_stmt))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="stmt"))
index_2 << (index)
term << (((factor + ZeroOrMore((multiplication_like_op + factor))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="term"))
general_aggregation_types << (((general_array_type | general_bag_type | general_list_type | general_set_type))).setParseAction(AggregationType)
procedure_call_stmt << ((((built_in_procedure | procedure_ref) + actual_parameter_list + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="procedure_call_stmt"))
inverse_attr << (((attribute_decl + CaselessLiteral(":") + Optional(((SET | BAG) + Optional(bound_spec) + OF)) + entity_ref + FOR + Optional((entity_ref + CaselessLiteral("."))) + attribute_ref + CaselessLiteral(";")))).setParseAction(InverseAttribute)
derived_attr << (((attribute_decl + CaselessLiteral(":") + parameter_type + CaselessLiteral(":=") + expression + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="derived_attr"))
width_spec << (((CaselessLiteral("(") + width + CaselessLiteral(")") + Optional(FIXED)))).setParseAction(WidthSpec)
actual_parameter_list << (((CaselessLiteral("(") + Optional(parameter) + ZeroOrMore((CaselessLiteral(",") + parameter)) + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="actual_parameter_list"))
type_decl << (((TYPE + type_id + CaselessLiteral("=") + underlying_type + CaselessLiteral(";") + Optional(where_clause) + END_TYPE + CaselessLiteral(";")))).setParseAction(TypeDeclaration)
selector << (expression)
abstract_supertype_declaration << (((ABSTRACT + SUPERTYPE + Optional(subtype_constraint)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="abstract_supertype_declaration"))
explicit_attr << (((attribute_decl + ZeroOrMore((CaselessLiteral(",") + attribute_decl)) + CaselessLiteral(":") + Optional(OPTIONAL) + parameter_type + CaselessLiteral(";")))).setParseAction(ExplicitAttribute)
aggregation_types << (((array_type | bag_type | list_type | set_type))).setParseAction(AggregationType)
domain_rule << (((Optional((rule_label_id + CaselessLiteral(":"))) + expression))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="domain_rule"))
entity_constructor << (((entity_ref + CaselessLiteral("(") + Optional((expression + ZeroOrMore((CaselessLiteral(",") + expression)))) + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="entity_constructor"))
function_call << ((((built_in_function | function_ref) + actual_parameter_list))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="function_call"))
numeric_expression << (simple_expression)
general_set_type << (((SET + Optional(bound_spec) + OF + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="general_set_type"))
qualifier << (((attribute_qualifier | group_qualifier | index_qualifier))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="qualifier"))
formal_parameter << (((parameter_id + ZeroOrMore((CaselessLiteral(",") + parameter_id)) + CaselessLiteral(":") + parameter_type))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="formal_parameter"))
index_1 << (index)
underlying_type << (((constructed_types | concrete_types))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="underlying_type"))
local_variable << (((variable_id + ZeroOrMore((CaselessLiteral(",") + variable_id)) + CaselessLiteral(":") + parameter_type + Optional((CaselessLiteral(":=") + expression)) + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="local_variable"))
aggregate_source << (simple_expression)
while_control << (((WHILE + logical_expression))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="while_control"))
interval_low << (simple_expression)
parameter << (expression)
precision_spec << (numeric_expression)
one_of << (((ONEOF + CaselessLiteral("(") + supertype_expression + ZeroOrMore((CaselessLiteral(",") + supertype_expression)) + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="one_of"))
subtype_constraint_body << (((Optional(abstract_supertype) + Optional(total_over) + Optional((supertype_expression + CaselessLiteral(";")))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subtype_constraint_body"))
general_array_type << (((ARRAY + Optional(bound_spec) + OF + Optional(OPTIONAL) + Optional(UNIQUE) + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="general_array_type"))
list_type << (((LIST + Optional(bound_spec) + OF + Optional(UNIQUE) + instantiable_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="list_type"))
subtype_constraint_decl << (((subtype_constraint_head + subtype_constraint_body + END_SUBTYPE_CONSTRAINT + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subtype_constraint_decl"))
schema_decl << (((SCHEMA + schema_id + Optional(schema_version_id) + CaselessLiteral(";") + schema_body + END_SCHEMA + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="schema_decl"))
algorithm_head << (((ZeroOrMore(declaration) + Optional(constant_decl) + Optional(local_decl)))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="algorithm_head"))
query_expression << (((QUERY + CaselessLiteral("(") + variable_id + CaselessLiteral("<*") + aggregate_source + CaselessLiteral("|") + logical_expression + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="query_expression"))
primary << (((literal | (qualifiable_factor + ZeroOrMore(qualifier))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="primary"))
repeat_control << (((Optional(increment_control) + Optional(while_control) + Optional(until_control)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="repeat_control"))
factor << (((simple_factor + Optional((CaselessLiteral("**") + simple_factor))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="factor"))
procedure_head << (((PROCEDURE + procedure_id + Optional((CaselessLiteral("(") + Optional(VAR) + formal_parameter + ZeroOrMore((CaselessLiteral(";") + Optional(VAR) + formal_parameter)) + CaselessLiteral(")"))) + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="procedure_head"))
aggregate_type << (((AGGREGATE + Optional((CaselessLiteral(":") + type_label)) + OF + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="aggregate_type"))
repeat_stmt << (((REPEAT + repeat_control + CaselessLiteral(";") + stmt + ZeroOrMore(stmt) + END_REPEAT + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="repeat_stmt"))
entity_body << (((ZeroOrMore(explicit_attr) + Optional(derive_clause) + Optional(inverse_clause) + Optional(unique_clause) + Optional(where_clause)))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="entity_body"))
interval_high << (simple_expression)
logical_expression << (expression)
simple_expression << (((term + ZeroOrMore((add_like_op + term))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="simple_expression"))
remark << (((embedded_remark | tail_remark))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="remark"))
simple_factor << (((aggregate_initializer | interval | query_expression | (Optional(unary_op) + ((CaselessLiteral("(") + expression + CaselessLiteral(")")) | primary)) | entity_constructor | enumeration_reference))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="simple_factor"))
case_stmt << (((CASE + selector + OF + ZeroOrMore(case_action) + Optional((OTHERWISE + CaselessLiteral(":") + stmt)) + END_CASE + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="case_stmt"))
derive_clause << (((DERIVE + derived_attr + ZeroOrMore(derived_attr)))).setParseAction(AttributeList)
supertype_constraint << (((abstract_supertype_declaration | abstract_entity_declaration | supertype_rule))).setParseAction(SuperTypeExpression)
assignment_stmt << (((general_ref + ZeroOrMore(qualifier) + CaselessLiteral(":=") + expression + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="assignment_stmt"))
entity_head << (((ENTITY + entity_id + subsuper + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="entity_head"))
set_type << (((SET + Optional(bound_spec) + OF + instantiable_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="set_type"))
instantiable_type << (((concrete_types | entity_ref))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="instantiable_type"))
declaration << (((entity_decl | function_decl | procedure_decl | subtype_constraint_decl | type_decl))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="declaration"))
binary_type << (((BINARY + Optional(width_spec)))).setParseAction(BinaryType)
interval << (((CaselessLiteral("{") + interval_low + interval_op + interval_item + interval_op + interval_high + CaselessLiteral("}")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="interval"))
parameter_type << (((generalized_types | simple_types | named_types))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="parameter_type"))
term << (((factor + ZeroOrMore((multiplication_like_op + factor))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="term"))
index << (numeric_expression)
expression << (((simple_expression + Optional((rel_op_extended + simple_expression))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="expression"))
bag_type << (((BAG + Optional(bound_spec) + OF + instantiable_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="bag_type"))
schema_body << (((ZeroOrMore(interface_specification) + Optional(constant_decl) + ZeroOrMore((declaration | rule_decl))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="schema_body"))
until_control << (((UNTIL + logical_expression))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="until_control"))
simple_types << (((binary_type | boolean_type | integer_type | logical_type | number_type | real_type | string_type))).setParseAction(SimpleType)
subsuper << (((Optional(supertype_constraint) + Optional(subtype_declaration)))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subsuper"))
entity_decl << (((entity_head + entity_body + END_ENTITY + CaselessLiteral(";")))).setParseAction(EntityDeclaration)
concrete_types << (((aggregation_types | simple_types | type_ref))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="concrete_types"))
element << (((expression + Optional((CaselessLiteral(":") + repetition))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="element"))
general_bag_type << (((BAG + Optional(bound_spec) + OF + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="general_bag_type"))
interval_item << (simple_expression)
constant_body << (((constant_id + CaselessLiteral(":") + instantiable_type + CaselessLiteral(":=") + expression + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="constant_body"))
increment << (numeric_expression)
case_label << (expression)
case_action << (((case_label + ZeroOrMore((CaselessLiteral(",") + case_label)) + CaselessLiteral(":") + stmt))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="case_action"))
width << (numeric_expression)
procedure_decl << (((procedure_head + algorithm_head + ZeroOrMore(stmt) + END_PROCEDURE + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="procedure_decl"))
increment_control << (((variable_id + CaselessLiteral(":=") + bound_1 + TO + bound_2 + Optional((BY + increment))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="increment_control"))
index_qualifier << (((CaselessLiteral("[") + index_1 + Optional((CaselessLiteral(":") + index_2)) + CaselessLiteral("]")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="index_qualifier"))
constant_decl << (((CONSTANT + constant_body + ZeroOrMore(constant_body) + END_CONSTANT + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="constant_decl"))
supertype_rule << (((SUPERTYPE + subtype_constraint))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="supertype_rule"))
syntax << (((schema_decl + ZeroOrMore(schema_decl)))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="syntax"))
function_head << (((FUNCTION + function_id + Optional((CaselessLiteral("(") + formal_parameter + ZeroOrMore((CaselessLiteral(";") + formal_parameter)) + CaselessLiteral(")"))) + CaselessLiteral(":") + parameter_type + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="function_head"))
repetition << (numeric_expression)
if_stmt << (((IF + logical_expression + THEN + stmt + ZeroOrMore(stmt) + Optional((ELSE + stmt + ZeroOrMore(stmt))) + END_IF + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="if_stmt"))
supertype_expression << (((supertype_factor + ZeroOrMore((ANDOR + supertype_factor))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="supertype_expression"))
inverse_clause << (((INVERSE + inverse_attr + ZeroOrMore(inverse_attr)))).setParseAction(AttributeList)
aggregate_initializer << (((CaselessLiteral("[") + Optional((element + ZeroOrMore((CaselessLiteral(",") + element)))) + CaselessLiteral("]")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="aggregate_initializer"))
return_stmt << (((RETURN + Optional((CaselessLiteral("(") + expression + CaselessLiteral(")"))) + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="return_stmt"))
generalized_types << (((aggregate_type | general_aggregation_types | generic_entity_type | generic_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="generalized_types"))
bound_2 << (numeric_expression)
real_type << (((REAL + Optional((CaselessLiteral("(") + precision_spec + CaselessLiteral(")")))))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="real_type"))
index_2 << (index)
array_type << (((ARRAY + bound_spec + OF + Optional(OPTIONAL) + Optional(UNIQUE) + instantiable_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="array_type"))
local_decl << (((LOCAL + local_variable + ZeroOrMore(local_variable) + END_LOCAL + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="local_decl"))
supertype_term << (((one_of | (CaselessLiteral("(") + supertype_expression + CaselessLiteral(")")) | entity_ref))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="supertype_term"))
where_clause << (((WHERE + domain_rule + CaselessLiteral(";") + ZeroOrMore((domain_rule + CaselessLiteral(";")))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="where_clause"))
embedded_remark << (((CaselessLiteral("(*") + Optional(remark_tag) + ZeroOrMore(((not_paren_star + ZeroOrMore(not_paren_star)) | lparen_then_not_lparen_star | (CaselessLiteral("*") + ZeroOrMore(CaselessLiteral("*"))) | not_rparen_star_then_rparen | embedded_remark)) + CaselessLiteral("*)")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="embedded_remark"))
compound_stmt << (((BEGIN + stmt + ZeroOrMore(stmt) + END + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="compound_stmt"))
bound_1 << (numeric_expression)
alias_stmt << (((ALIAS + variable_id + FOR + general_ref + ZeroOrMore(qualifier) + CaselessLiteral(";") + stmt + ZeroOrMore(stmt) + END_ALIAS + CaselessLiteral(";")))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="alias_stmt"))
subtype_constraint << (((OF + CaselessLiteral("(") + supertype_expression + CaselessLiteral(")")))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="subtype_constraint"))
string_type << (((STRING + Optional(width_spec)))).setParseAction(StringType)
function_decl << (((function_head + algorithm_head + stmt + ZeroOrMore(stmt) + END_FUNCTION + CaselessLiteral(";")))).setParseAction(FunctionDeclaration)
general_list_type << (((LIST + Optional(bound_spec) + OF + Optional(UNIQUE) + parameter_type))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="general_list_type"))
supertype_factor << (((supertype_term + ZeroOrMore((AND + supertype_term))))).setParseAction(lambda s, loc, t: ListNode(s, loc, t, rule="supertype_factor"))
rule_decl << (((rule_head + algorithm_head + ZeroOrMore(stmt) + where_clause + END_RULE + CaselessLiteral(";")))).setParseAction(RuleDeclaration)
qualifiable_factor << (((function_call | attribute_ref | constant_factor | general_ref | population))).setParseAction(lambda s, loc, t: Node(s, loc, t, rule="qualifiable_factor"))
bound_spec << (((CaselessLiteral("[") + bound_1 + CaselessLiteral(":") + bound_2 + CaselessLiteral("]")))).setParseAction(BoundSpecification)
syntax.ignore("--" + restOfLine)
syntax.ignore(Regex(r"\((?:\*(?:[^*]*\*+)+?\))"))
@@ -24,6 +24,7 @@ import string
import operator
import collections
import bootstrap
class Node:
def __init__(self, s, loc, tokens, rule=None):
@@ -57,10 +58,18 @@ class ListNode:
self.rule = rule or (type(self).__name__)
self.tokens = tokens.asList()
self.dict_tokens = collections.defaultdict(list)
rules_as_list = set()
for t in self.tokens:
r = getattr(t, 'rule', None)
if r:
self.dict_tokens[r].append(t)
rules_as_list.add(r)
self.dict_tokens[r].append(t)
for r, t in tokens.asDict().items():
if r not in rules_as_list:
self.dict_tokens[r].append(t)
self.flat = sum([getattr(t, "flat", [t]) for t in self.tokens], [])
def __repr__(self):
@@ -68,9 +77,10 @@ class ListNode:
def __iter__(self):
return iter(self.tokens)
def __getitem__(self, i):
return self.tokens[i]
# Somehow indexing messes up the pyparsing results, so instead of x[0] use list(x)[0]
# def __getitem__(self, i):
# return self.tokens[i]
def init(self):
pass
@@ -115,7 +125,7 @@ class TypeDeclaration(Node):
self.where = []
clause = self.where_clause
if clause:
clause = clause[0]
clause = list(clause[0])
self.where = [(r.simple_id, format_clause(r.expression[0])) for r in clause[1::2]]
@@ -170,7 +180,7 @@ class EntityDeclaration(Node):
self.where = []
clause = [r for r in self.entity_body[0] if r.rule == "where_clause"]
if clause:
clause = clause[0]
clause = list(clause[0])
self.where = [(r.simple_id, format_clause(r.expression[0])) for r in clause[1::2]]
@@ -178,7 +188,7 @@ class EntityDeclaration(Node):
clause = [r for r in self.entity_body[0] if r.rule == "unique_clause"]
if clause:
clause = clause[0]
self.unique = [(r[0], r[2].simple_id) for r in clause[1::2]]
self.unique = [(r[0], r[2].simple_id) for r in map(list, list(clause)[1::2])]
def __repr__(self):
strm = io.StringIO()
@@ -222,7 +232,7 @@ class EntityDeclaration(Node):
class EnumerationType(Node):
values = property(lambda self: self.enumeration_type[2][1::2])
values = property(lambda self: list(self.enumeration_type[2])[1::2])
def __repr__(self):
return "ENUMERATION OF (" + ",".join(self.values) + ")"
@@ -241,29 +251,160 @@ def do_try(fn):
except: pass
def get_rule_id(x):
if not isinstance(x, str):
x = type(x).__name__
matches = [k for k, v in bootstrap.actions.items() if v == x]
if matches:
return matches[0]
rule_dependencies = {
k: list(map(operator.attrgetter('contents'), bootstrap.reduce(lambda x, y: x | y, (bootstrap.find_bytype(e, bootstrap.Keyword) for e in [v])))) \
for k, v in bootstrap.express
}
all_rules = [k for k, e in bootstrap.express]
rule_definitions = {k: v for k, v in bootstrap.express}
def to_tree(x, key=None):
def get_rule_id(x):
from bootstrap import actions
ty = type(x).__name__
matches = [k for k, v in actions.items() if v == ty]
if matches:
return matches[0]
def prune(di):
import bootstrap
rule_dependencies = {
k: list(map(operator.attrgetter('contents'), bootstrap.reduce(lambda x, y: x | y, (bootstrap.find_bytype(e, bootstrap.Keyword) for e in [v])))) \
for k, v in bootstrap.express
}
subrules = list(filter(str.islower, rule_dependencies[key]))
return {k: v for k, v in di.items() if k in subrules}
# translate class names back to grammar rules if nested actions are encountered
di = {get_rule_id(k) or k: v for k, v in di.items()}
def replace_synonyms(x):
for y in x:
yield y
if False: # y in di:
# production element from grammar is found in parsed data,
# return that.
# we now always explore other synonym, because more often than not we loose data otherwise
yield y
else:
# lookup rule
rule = [e for k, e in bootstrap.express if k == y][0]
def is_synonym(rl):
if isinstance(rl, bootstrap.Term) and isinstance(rl.contents, bootstrap.Keyword):
return rl.contents.contents
# is this a synonym? then processs that
if S := is_synonym(rule):
yield S
# Do this recursively
yield from replace_synonyms([S])
# is this a concatenation with zero or more synonyms? then also processs that
# @todo catches:
# - simple_expression = term { add_like_op term } .
# but should probably also work on
# - a = b { b }
# in which case the second Concat would be eliminated
elif isinstance(rule, bootstrap.Concat) and \
len(rule.contents) == 2 and \
is_synonym(rule.contents[0]) and \
isinstance(rule.contents[1].contents, bootstrap.Repeated) and \
isinstance(rule.contents[1].contents.contents[0], bootstrap.Concat) and \
str(rule.contents[1].contents.contents[0].contents[1]) == str(rule.contents[0]):
S = is_synonym(rule.contents[0])
yield S
# Do this recursively
yield from replace_synonyms([S])
subrules = list(replace_synonyms(rule_dependencies[key]))
if key == "aggregation_types":
# hack hack hack apparently the parser can't distinguish these
subrules += list(replace_synonyms(rule_dependencies["general_aggregation_types"]))
if rule_dependencies[key] and not subrules:
# sometimes an intermediate production rule is missing
# from the pyparsing output, e.g from parameter to simple_expression
# directly. Recover from this.
subrules = sum(map(rule_dependencies.__getitem__, rule_dependencies[key]), [])
if not isinstance(rule_definitions[key], bootstrap.Union):
# Filter out terminals when not a union. E.g no
# reason to retain TYPE, END_TYPE, but operators
# such as IN, LIKE should be retained.
subrules = list(filter(str.islower, subrules))
vs = list(di.values())
return {k: v for k, v in di.items() if k in subrules or (k == key and len(vs) == 1 and vs[0] not in all_rules)}
def simplify(di):
if isinstance(di, list):
if set(map(type, di)) == {str} and set(map(len, di)) == {1}:
return "".join(di)
return [simplify(v) for v in di]
elif isinstance(di, dict) and len(di) == 1 and next(iter(di.values())) == {}:
return next(iter(di.keys()))
elif isinstance(di, dict):
return {k: simplify(v) for k, v in di.items()}
else:
return di
if isinstance(x, ListNode):
return to_tree(x.dict_tokens, key=get_rule_id(x) or key)
if isinstance(x, Node,):
return to_tree(x.tokens, key=get_rule_id(x) or key)
d = to_tree(x.dict_tokens, key=get_rule_id(x) or key)
if key == 'if_stmt':
# The definition of if statement if (roughy):
# 'if' expr 'then' stmt+ 'else' stmt+
# this causes stmt to be joined under the same
# dict key. The code below creates an artifical
# `else_stmt` that collects the second group
# of stmts.
statements = x.dict_tokens['stmt']
else_index = None
if_nesting = 0
for i, tk in enumerate(x.flat):
if tk == 'if': if_nesting += 1
if tk == 'end_if': if_nesting -= 1
if tk == 'else' and if_nesting == 1:
else_index = i
if else_index:
indices = []
for s in statements:
for i in range(max(indices, default=0), len(x.flat)):
if x.flat[i:i+len(s.flat)] == s.flat:
indices.append(i)
break
assert len(indices) == len(statements)
before_else = [i < else_index for i in indices]
else_stmt = [st for b, st in zip(before_else, d['stmt']) if not b]
d['stmt'] = [st for b, st in zip(before_else, d['stmt']) if b]
if else_stmt:
d['else_stmt'] = else_stmt
if key == 'formal_parameter':
# Not so pretty hack to fix the overwriting of simple_id-like
# ast nodes. The full solution would probably to register parse
# actions. And directly reassign.
pid = d['parameter_id'][0][0]
d['parameter_id'][0] = x.flat[:x.flat.index(pid)+1:2]
if key is None:
return {get_rule_id(x): d}
return d
elif isinstance(x, Node):
d = to_tree(x.tokens, key=get_rule_id(x) or key)
if key is None:
return {get_rule_id(x): d}
return d
elif isinstance(x, dict):
return prune({k: to_tree(v, key=k) for k, v in x.items()})
# d = {k: to_tree(v, key=k) for k, v in x.items()}
# not fully understood, but when finding specific node Types and production rules, prioritize the former
d = {get_rule_id(k) or k: to_tree(v, key=k) for k, v in sorted(x.items(), key=lambda p: get_rule_id(p[0]) is not None)}
return simplify(prune(d))
elif isinstance(x, list):
return [to_tree(v, key=key) for v in x]
else:
@@ -306,7 +447,7 @@ class AggregationType(Node):
class SelectType(Node):
values = property(lambda self: self.select_type[1][1::2])
values = property(lambda self: list(self.select_type[1])[1::2])
def __repr__(self):
return "SELECT (" + ",".join(map(str, self.values)) + ")"
@@ -321,7 +462,7 @@ class SuperTypeExpression(Node):
else:
constraint = self.supertype_rule[0]
return [
s[0][0].simple_id for s in constraint.subtype_constraint[0].supertype_expression[0][0][0].one_of[0][2::2]
list(list(s)[0])[0].simple_id for s in list(list(list(constraint.subtype_constraint[0].supertype_expression[0])[0])[0].one_of[0])[2::2]
]
sub_types = property(get_sub_types)
@@ -418,7 +559,7 @@ class WidthSpec(Node):
fixed = property(lambda self: self.FIXED is not None)
def init(self):
self.width = int("".join(self.width[0].flat))
self.width = int("".join(list(self.width)[0].flat))
def __repr__(self):
return "(%d)%s" % (self.width, " fixed" if self.fixed else "")
@@ -432,3 +573,16 @@ class StringType(Node):
if self.width:
s += " " + repr(self.width)
return s
class ProcedureDeclaration(ListNode):
@property
def name(self):
return self.flat[1]
class FunctionDeclaration(ProcedureDeclaration):
pass
class RuleDeclaration(ProcedureDeclaration):
pass
@@ -0,0 +1,703 @@
import os
import re
import sys
import json
import hashlib
import operator
import functools
import itertools
import ifcopenshell.express
import networkx as nx
from codegen import indent
DEBUG = False
def to_graph(tree):
g = nx.DiGraph()
# Convert
def write_to_graph(val, name=None):
if isinstance(val, list):
pairs = ((None, v) for v in val)
elif isinstance(val, dict):
pairs = val.items()
else:
assert name
g.add_edge(name, name + "_value")
return g.add_node(name + "_value", label=val)
for i, (k, v) in enumerate(pairs):
i = f"{i:03d}"
nid = f"{name or 'root'}_{k or i}"
g.add_node(nid, label=k)
if name:
g.add_edge(name, nid)
write_to_graph(v, nid)
write_to_graph(tree)
to_remove = set()
# Remove intermediate anonymous nodes. Often the result of ZeroOrMore() productions in
# bootstrap.py that result in an intermediate list index node in to_tree()
# Start with the intermediate nodes and filter out root (needs to have predecessors)
intermediate = [n for n in g.nodes if g.nodes[n].get('label') is None and list(g.predecessors(n))]
for n in intermediate:
pr = list(g.predecessors(n))
if len(pr) == 1 and g.nodes[pr[0]].get('label'):
# when eliminating a grouping node with heterogeneous content
# rather copy the predecessor node label to the grouping node
# and later delete the predecessor
sc = list(g.successors(n))
if len(sc) > 1:
sc_labels = list(map(lambda x: g.nodes[x].get('label'), sc))
if len(set(sc_labels)) > 1 and None not in sc_labels:
g.nodes[n]['label'] = g.nodes[pr[0]].get('label')
to_remove.add(pr[0])
continue
for ab in itertools.product(g.predecessors(n), g.successors(n)):
g.add_edge(*ab)
g.remove_node(n)
# The removal process above can decide to not fold the anonymous node, but rather
# the predecessor of it, in which case it is deleted in this step.
for n in to_remove:
for ab in itertools.product(g.predecessors(n), g.successors(n)):
g.add_edge(*ab)
g.remove_node(n)
for n in g.nodes:
if len(list(g.successors(n))) == 0 and g.nodes[n].get('label') not in ifcopenshell.express.express_parser.all_rules:
g.nodes[n]['is_terminal'] = True
return g
def write_dot(fn, g):
with open(fn, "w") as f:
def w(*args, **kwargs):
print(*args, file=f, **kwargs)
w("digraph", "{")
def nodename(n):
return "N"+hashlib.md5(n.encode()).hexdigest()
def format(di):
Q = '"'
inner = ",".join(f"{k}={'' if v.startswith('<') else Q}{v}{'' if v.startswith('<') else Q}" for k, v in di.items())
if inner:
inner = f"[{inner}]"
return inner
for n in g.nodes:
lbl = g.nodes[n].get('label')
if lbl:
attrs = {"label": lbl}
else:
attrs = {"label": n}
if g.nodes[n].get('is_terminal'):
attrs["shape"] = "rect"
attrs["label"] = f"\\\"{attrs['label']}\\\""
else:
attrs["shape"] = "none"
w(nodename(n), format(attrs), ";", sep="")
for a,b in g.edges:
w(nodename(a), "->", nodename(b), ";")
w("}", flush=True)
from pyparsing import *
SLASH = Suppress("/")
identifier = Word(alphanums + "_")
rule = identifier + (ZeroOrMore(SLASH + identifier))
def paths(G, root, length):
if length == 1:
yield (G.nodes[root].get('label'),)
return
sd = dict(nx.bfs_successors(G, root, depth_limit=length-1))
def r(x, p=None):
if p and len(p) == length:
yield tuple(map(lambda n: G.nodes[n].get('label'), p))
else:
for y in sd.get(x, []):
yield from r(y, (p or [x])+[y])
yield from r(root)
class context:
def __init__(self, graph, rules):
self.graph = graph
self.rules = rules
def __getattr__(self, k):
def inner():
for r in self.rules:
label_id_pairs = map(
lambda n: (self.graph.nodes[n].get('label'), n),
# itertools.chain.from_iterable(
# dict(nx.bfs_successors(self.graph, r)).values()
# )
self.graph.successors(r)
)
matching = filter(lambda p: p[0] == k, label_id_pairs)
yield from map(operator.itemgetter(1), matching)
return context(self.graph, list(inner()))
def has_inverse(self, a):
for r in self.rules:
if a in map(
lambda n: self.graph.nodes[n].get('label'),
self.graph.predecessors(r)
):
return True
return False
def __iter__(self):
for r in self.rules:
yield context(self.graph, [r])
def descendants(self):
return [b.rules[0][len(self.rules[0])+1:] for b in self.branches(allow_multiple=True)]
def __repr__(self):
try:
s = "\n\n"+str(self)
except:
s = ""
return f"<rule_context ({' '.join(self.descendants())})>{s}"
def __str__(self):
assert len(self.rules) == 1
nodes = itertools.chain(self.rules, itertools.chain.from_iterable(
dict(nx.bfs_successors(self.graph, self.rules[0])).values()
))
terminals_or_values = list(filter(
lambda n: self.graph.nodes[n].get('is_terminal') or self.graph.nodes[n].get('value'),
nodes
))
# assert len(terminals) == 1
attrs = [self.graph.nodes[tv] for tv in terminals_or_values]
attrs = [a.get('value', a['label']) for a in attrs]
attr_types = list(map(type, attrs))
if empty in attr_types[0:1]:
return ""
attrs = list(filter(lambda s: isinstance(s, str), attrs))
return attrs[0]
def __eq__(self, other):
return self.graph == other.graph and self.rules == other.rules
def __hash__(self):
return hash(self.rules)
def branches(self, allow_multiple=False, exclude=()):
if not allow_multiple:
assert len(self.rules) == 1
combined = sum([sorted((context(self.graph, [n]) for n in self.graph.successors(R)), key=lambda c: c.rules[0] if c.rules else "") for R in self.rules], [])
return [c for c in combined if c not in exclude]
def parent(self):
assert len(self.rules) == 1
return context(self.graph, list(self.graph.predecessors(self.rules[0])))
def branch(self, i):
return self.branches()[i]
def __len__(self):
return len(self.rules)
def __getitem__(self, k):
return list(self)[k]
# @todo
context_class = context
class codegen_rule:
def __init__(self, pattern, fn):
self.pattern = tuple(rule.parseString(pattern))
self.fn = fn
if not hasattr(codegen_rule, 'all_rules'):
codegen_rule.all_rules = []
codegen_rule.all_rules.append(self)
def __call__(self, graph, node):
# try:
v = self.fn(context(graph, [node]))
# except:
# v = "ERROR!!"
graph.nodes[node]['value'] = v
return v
@staticmethod
def apply(G):
v = None
for n in reversed(list(nx.topological_sort(G))):
for r in codegen_rule.all_rules:
if r.pattern in paths(G, n, len(r.pattern)):
v = r(G, n)
return v
def process_rule_decl(context):
return f"""
class {context.rule_head.rule_id}:
SCOPE = "file"
@staticmethod
def __call__(file):
{context.rule_head.entity_ref} = file.by_type("{context.rule_head.entity_ref}")
{indent(8, context.algorithm_head.local_decl)}
{indent(8, context.stmt.branches()) if context.stmt else ''}
{indent(8, context.where_clause.domain_rule)}
"""
class empty:
pass
wb = r"\b"
def process_type_decl(scope, context):
class_name = context.type_id if scope == 'type' else context.entity_head.entity_id
attributes = []
if scope == 'entity':
def get_attributes(nm):
ent = schema.entities[nm]
if ent.supertypes:
yield from get_attributes(ent.supertypes[0])
yield from [a.name for a in ent.attributes]
yield from [a.name for a in ent.inverse]
# redeclared do not need to be printed, because they're emitted
# as part of supertype
yield from [a[0] for a in ent.derive if isinstance(a[0], str)]
# @todo derived and inverse attributes
attributes = list(get_attributes(class_name))
def format_rule(domain_rule):
return f"""
class {class_name}_{domain_rule.rule_label_id}:
SCOPE = "{scope}"
TYPE_NAME = "{class_name}"
RULE_NAME = "{domain_rule.rule_label_id}"
@staticmethod
def __call__(self):
{indent(8, (f"{a.lower()} = self.{a}" for a in attributes if re.search(f'{wb}{a.lower()}{wb}', str(domain_rule))))}
{indent(8, domain_rule)}
"""
rule_parent = context if scope == 'type' else context.entity_body
statements = []
if rule_parent.where_clause:
# @todo should we not try to maintain a 1-1 correspondence?
statements.extend(map(format_rule, rule_parent.where_clause.branches()))
if scope == 'entity':
def format_derived(derived_attr):
slash = "\\"
return f"""
def calc_{class_name}_{str(derived_attr.attribute_decl.redeclared_attribute.qualified_attribute.attribute_qualifier)[1:] if derived_attr.attribute_decl.redeclared_attribute else derived_attr.attribute_decl}(self):
{indent(4, (f"{a.lower()} = self.{a}" for a in attributes if re.search(f'{wb}{a.lower()}{wb}', str(derived_attr.expression))))}
{indent(4, f"return {slash}")}
{indent(4, derived_attr.expression)}
"""
if context.entity_body.derive_clause:
statements.extend(map(format_derived, context.entity_body.derive_clause.branches()))
return "\n\n".join(statements)
def process_domain_rule(context):
return f"""
assert {context.expression}
"""
def process_expression(context):
def wrap(s):
s = str(s)
if " " in s:
s = '(%s)' % s
return s
def concat(a, b, **kwargs):
return " ".join(map(str, sum(zip(
[None] + a.branches(**kwargs),
map(wrap, b.branches(**kwargs))
), ())[1:]))
if context.rel_op_extended:
if context.term:
# IfcSameValue
return concat(context.rel_op_extended, context, allow_multiple=True, exclude=[context.rel_op_extended])
else:
return concat(context.rel_op_extended, context.simple_expression)
elif context.multiplication_like_op:
if str(context.multiplication_like_op.branches()[0]) == '||':
all_args = {}
most_concrete_type = None
most_concrete_type_inheritance_chain_length = -1
for s in context.factor.branches():
typename, args = str(s).split('(', 1)
args = args[:-1]
break_points = [[0]]
bracket_nesting = 0
for i, tk in enumerate(args):
if tk in '[(': bracket_nesting += 1
if tk in ')]': bracket_nesting -= 1
if tk == ',' and bracket_nesting == 0:
break_points[-1].append(i)
break_points.append([i+1])
break_points[-1].append(len(args))
S = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema.name)
entity = S.declaration_by_name(typename)
entity_attributes = entity.attributes()
def count_chain_length(ent):
length = 0
while ent:
ent = ent.supertype()
length += 1
return length
args = [args[slice(*x)] for x in break_points]
for i, arg in filter(lambda p: p[1], enumerate(args)):
all_args[entity_attributes[i].name()] = arg
cl = count_chain_length(entity)
if cl > most_concrete_type_inheritance_chain_length:
most_concrete_type = entity.name()
most_concrete_type_inheritance_chain_length = cl
return f"{most_concrete_type}({', '.join(f'{a[0]}={a[1]}' for a in all_args.items())})"
else:
return concat(context.multiplication_like_op, context.factor)
elif context.add_like_op:
if context.factor or len(context.term) > 1:
# @todo now sure why this is required (in IfcCrossProduct)
# @todo not sure what's going on here, why we have both factor and term as direct child productions of simple_expression (in IfcDotProduct)
return concat(context.add_like_op, context, allow_multiple=True, exclude=[context.add_like_op])
else:
return concat(context.add_like_op, context.term)
def process_interval(context):
op0, op1 = context.interval_op.branches()
return " ".join(map(str, (
context.interval_low,
op0,
context.interval_item,
op1,
context.interval_high
)))
def simple_concat(context):
# simple_factor:
# only to join unary op (-) with number literal
# primary:
# only to join index with qualifyable operand
def qualifier_position(s):
# @todo this is a really ugly hack, can we not depend on stable branch order and why?
# unary operators
if s in ("-", "+", "not"): return -1
# qualifiers
if s and s[0] in ('.', '['): return 1
# default
return 0
branches = sorted(map(str, context.branches()), key=qualifier_position)
# sorting no longer necessary as we sort in branches() now
# correction: still necessary, apparently.
# branches = list(map(str, context.branches()))
concat = ""
if len(branches) == 2 and branches[0] == 'not':
concat = " "
v = concat.join(branches)
return v
def process_rel_op(context):
# @todo the distinction between value comparison and instance comparison
if str(context) == "<>" or str(context) == ":<>:":
return "!="
elif str(context) == "=" or str(context) == ":=:":
return "=="
def process_if_stmt(context):
s = f"if {context.logical_expression if context.logical_expression.branches() else context.expression}:\n{indent(4, context.stmt.branches())}"
if context.else_stmt:
s += f"\nelse:\n{indent(4, context.else_stmt.branches())}"
return s
def process_repeat_stmt(context):
ic = context.repeat_control.increment_control
return f"for {ic.variable_id} in range({ic.bound_1}, {ic.bound_2} + 1):\n{indent(4, context.stmt.branches())}"
def process_function_decl(context):
arguments = map(str.lower, map(str, context.function_head.formal_parameter.parameter_id.branches(allow_multiple=True)))
return f"def {context.function_head.function_id}({', '.join(arguments)}):\n{indent(4, context.algorithm_head.local_decl)}\n{indent(4, context.stmt.branches())}"
def process_query(context):
return f"[{str(context.variable_id).lower()} for {str(context.variable_id).lower()} in {context.aggregate_source} if {context.logical_expression if context.logical_expression and context.logical_expression.branches() else context.expression}]"
def process_local_variable(context):
if context.expression:
expr = str(context.expression)
if context.parameter_type.generalized_types.general_aggregation_types.general_set_type:
expr = re.sub('(\[[^\]]*\])', 'express_set(\\1)', expr)
return '%s = %s' % (str(context.variable_id).lower(), expr)
else:
return empty()
def process_function_call(context):
nm = f"{context.built_in_function if context.built_in_function else context.function_ref}"
args = f"{context.actual_parameter_list if context.actual_parameter_list and context.actual_parameter_list.branches() else ''}"
if nm == "exists" and '[' in args:
# exists check if it receives a callable to catch IndexError, because express semantics
# dictate that out of bounds index returned unknown (IfcTypeObject_WR1)
wrap = "lambda: "
else:
wrap = ""
return f"{nm}({wrap}{args})"
def make_lowercase(context):
return str(context).lower()
def make_lowercase_if(fn):
def inner(context):
if fn(context):
return make_lowercase(context)
return inner
def process_assignment(context):
lhs = str(context.general_ref)
if context.qualifier:
lhs += str(context.qualifier)
if m := re.match(r'^([^\[]+)\[([^\[]+)\]$', lhs):
# @todo ugly regex hack
aggr, index = m.groups()
return f"temp = list({aggr})\ntemp[{index}] = {context.expression}\n{aggr} = temp"
else:
return '%s = %s' % (lhs, context.expression)
def process_case_action(context):
first = context.parent().branches().index(context)
pred = "elif" if first else "if"
if re.match(r"^'[a-z0-9]+'$", str(context.expression)):
# @todo this is yet again an ugly hack
lower = '.lower()'
else:
lower = ''
return f"{pred} {context.parent().expression}{lower} == {context.expression}:\n{indent(4, context.stmt.branches())}"
def process_case_statement(context):
branches = context.branches(exclude=[getattr(context, v) for v in context.descendants() if not v.startswith('case_action')])
if context.stmt and context.stmt.branches():
branches += [f"else:\n{indent(4, context.stmt)}"]
return'\n'.join(map(str, branches))
def process_aggregate_initializer(context):
if context.element.repetition:
return '([%s] * %s)' % (context.element.expression, context.element.repetition)
else:
return '[%s]' % ','.join(map(str, context.element.branches() if context.element else ()))
# implemented sizeof() function in generated code
# codegen_rule("built_in_function/SIZEOF", lambda context: f"len")
# @todo
codegen_rule("function_call", process_function_call)
codegen_rule("actual_parameter_list", lambda context: ','.join(map(str, context.expression.branches() if context.expression else [])))
codegen_rule("entity_decl", functools.partial(process_type_decl, 'entity'))
codegen_rule("rule_decl", process_rule_decl)
codegen_rule("type_decl", functools.partial(process_type_decl, 'type'))
codegen_rule("function_decl", process_function_decl)
codegen_rule("domain_rule", process_domain_rule)
codegen_rule("expression", process_expression)
codegen_rule("simple_expression", process_expression)
codegen_rule("logical_expression", process_expression)
codegen_rule("term", process_expression)
codegen_rule("query_expression", process_query)
codegen_rule("aggregate_initializer", process_aggregate_initializer)
codegen_rule("interval", process_interval)
codegen_rule("simple_factor", simple_concat)
codegen_rule("primary", simple_concat)
codegen_rule("qualifier", simple_concat)
codegen_rule("return_stmt", lambda context: 'return %s' % context)
codegen_rule("compound_stmt", lambda context: '\n'.join(map(str, context.stmt.branches())))
codegen_rule("if_stmt", process_if_stmt)
codegen_rule("repeat_stmt", process_repeat_stmt)
# codegen_rule("index", lambda context: '**express_index(%s)' % context)
codegen_rule("index", lambda context: '[%s - 1]' % context)
codegen_rule("group_qualifier", lambda context: empty())
codegen_rule("attribute_qualifier", lambda context: '.%s' % context)
codegen_rule("rel_op", process_rel_op)
codegen_rule("built_in_constant", lambda context: "None" if str(context) == "?" else str(context))
codegen_rule("assignment_stmt", process_assignment)
codegen_rule("local_variable", process_local_variable)
codegen_rule("local_decl", lambda context: '\n'.join(map(str, context.branches())))
codegen_rule("general_ref/parameter_ref", make_lowercase)
codegen_rule("qualifiable_factor/attribute_ref", make_lowercase_if(lambda context: str(context) not in set(map(str, schema.all_declarations.keys()))))
codegen_rule("case_action", process_case_action)
codegen_rule("case_stmt", process_case_statement)
codegen_rule("escape_stmt", lambda context: "break")
codegen_rule("XOR", lambda context: "^")
codegen_rule("MOD", lambda context: "%")
codegen_rule("TRUE", lambda context: "True")
codegen_rule("FALSE", lambda context: "False")
if __name__ == "__main__":
import sys
import shutil
import subprocess
schema = ifcopenshell.express.express_parser.parse(sys.argv[1]).schema
ofn = os.path.join(os.path.dirname(__file__), "rules", f"{schema.name}.py")
output = open(ofn, "w")
print("import ifcopenshell", file=output, sep='\n')
print("""
def exists(v):
if callable(v):
try: return v() is not None
except IndexError as e: return False
else: return v is not None
""", "\n", file=output, sep='\n')
print("def nvl(v, default): return v if v is not None else default", "\n", file=output, sep='\n')
print("sizeof = len", file=output, sep='\n')
print("hiindex = len", file=output, sep='\n')
print("blength = len", file=output, sep='\n')
print("loindex = lambda x: 1", file=output, sep='\n')
print("from math import *", file=output, sep='\n')
# @todo this will get us in trouble when evaluating the truthness
print("unknown = 'UNKNOWN'", file=output, sep='\n')
print("""
def usedin(inst, ref_name):
if inst is None:
return []
_, __, attr = ref_name.split('.')
def filter():
for ref, attr_idx in inst.wrapped_data.file.get_inverse(inst, allow_duplicate=True, with_attribute_indices=True):
if ref.wrapped_data.get_attribute_names()[attr_idx].lower() == attr:
yield ref
return list(filter())
class express_set(set):
def __rmul__(self, other):
return express_set(set(other) & self)
def __add__(self, other):
def make_list(v):
# Comply with 12.6.3 Union operator
if isinstance(v, (list, tuple, set, express_set)):
return list(v)
else:
return [v]
return express_set(list(self) + make_list(other))
__radd__ = __add__
def __repr__(self):
return repr(set(self))
def typeof(inst):
if not inst:
# If V evaluates to indeterminate (?), an empty set is returned.
return express_set([])
schema_name = inst.is_a(True).split('.')[0].lower()
def inner():
decl = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(inst.is_a())
while decl:
yield '.'.join((schema_name, decl.name().lower()))
decl = decl.supertype()
return express_set(inner())
""", file=output, sep='\n')
print("class enum_namespace:\n def __getattr__(self, k):\n return k.upper()", "\n", file=output, sep='\n')
for k, v in schema.enumerations.items():
print(f"{k} = enum_namespace()", "\n", file=output, sep='\n')
for vi in v.values:
print(f"{vi.lower()} = {k}.{vi}", "\n", file=output, sep='\n')
for k in schema.entities.keys():
print(f"def {k}(*args, **kwargs): return ifcopenshell.create_entity({k!r}, {schema.name!r}, *args, **kwargs)", "\n", file=output, sep='\n')
for nm in schema.all_declarations.keys():
print(nm)
tree = ifcopenshell.express.express_parser.to_tree(schema[nm])
if DEBUG:
with open(f"{nm}.json", "w") as f:
json.dump(tree, f, indent=2)
G = to_graph(tree)
rule_code = codegen_rule.apply(G)
if DEBUG:
for n in G.nodes.values():
if v := n.get('value'):
if isinstance(v, str):
nl = "\n"
es = "\\n"
n['label'] = f'<<table cellborder="0" cellpadding="0"><tr><td><b>{n.get("label")}</b></td></tr><tr><td align="left" balign="left">{v.replace("<", "&lt;").replace(">", "&gt;").replace(nl, "<br/>")}</td></tr></table>>'
elif isinstance(v, empty):
n['label'] = f'<<table cellborder="0" cellpadding="0"><tr><td><b>{n.get("label")}</b></td></tr><tr><td align="left" balign="left">---</td></tr></table>>'
fn = f"{nm}.dot"
write_dot(fn, G)
subprocess.call([shutil.which("dot") or "dot", fn, "-O", "-Tpng"])
print(rule_code, "\n", file=output, sep='\n')
output.close()
@@ -0,0 +1,180 @@
import os
import ast
import collections
from dataclasses import dataclass
from _pytest import assertion
import ifcopenshell
from ifcopenshell.validate import json_logger
from codegen import indent
def reverse_compile(s):
return s.strip().replace('len(', 'SIZEOF(').replace('assert ', '')
@dataclass
class error(Exception):
rule_name : str
rule_definition : str
violation : str
instance : ifcopenshell.entity_instance = None
def __str__(self):
inst = ""
if self.instance:
inst = f"On instance:\n{indent(4, str(self.instance))}\n"
return f"{inst}Rule {self.rule_name}:\n{indent(4, self.rule_definition)}\nViolated by:\n{indent(4, self.violation)}"
def fix_type(v):
if isinstance(v, (list, tuple)):
# 1-based indexing:
#
# @todo this is not the best way, because it still allows to index the 0-th element,
# but given the existing body of rules this should be sufficient.
# return type(v)([None]) + type(v)(map(fix_type, v))
# We don't do this anymore, because it doesn't fix instance attribute lookups
# We now instead perform a -1 on the index qualifier in the code generation
pass
# @todo enrich entity instances with code to evaluate derived attributes
return v
def run(f, logger):
fn = os.path.join(os.path.dirname(__file__), "rules", f"{f.schema}.py")
source = open(fn, "r").read()
a = ast.parse(source)
assertion.rewrite.rewrite_asserts(mod=a, source=source)
cd = compile(a, f"{f.schema}.py", 'exec')
scope = {}
exec(cd, scope)
S = ifcopenshell.ifcopenshell_wrapper.schema_by_name(f.schema)
rules = list(filter(lambda x: hasattr(x, 'SCOPE'), scope.values()))
for R in [r for r in rules if r.SCOPE == 'file']:
try:
R()(f)
except Exception as e:
ln = e.__traceback__.tb_next.tb_lineno
logger.error(str(error(
R.__name__,
reverse_compile(source.split("\n")[ln-1]),
reverse_compile(e.args[0])
)))
types = {}
subtypes = collections.defaultdict(list)
for d in S.declarations():
if isinstance(d, ifcopenshell.ifcopenshell_wrapper.type_declaration):
types[d.name()] = d
if isinstance(d.declared_type(), ifcopenshell.ifcopenshell_wrapper.named_type):
subtypes[d.declared_type().declared_type().name()].append(d.name())
D = collections.defaultdict(list)
for r in rules:
if r.SCOPE == 'type':
def visit(nm):
D[nm].append(r)
for nm2 in subtypes[nm]:
visit(nm2)
visit(r.TYPE_NAME)
def type_name(ty):
if isinstance(ty, ifcopenshell.ifcopenshell_wrapper.named_type):
return type_name(ty.declared_type())
elif isinstance(ty, ifcopenshell.ifcopenshell_wrapper.aggregation_type):
# breakpoint()
pass
elif isinstance(ty, ifcopenshell.ifcopenshell_wrapper.simple_type):
pass
else:
return ty.name()
def check(value, type, instance):
if value is None:
return
if type_name(type) in D:
for R in D[type_name(type)]:
try:
R()(fix_type(value))
except Exception as e:
ln = e.__traceback__.tb_next.tb_lineno
logger.error(str(error(
R.__name__,
reverse_compile(source.split("\n")[ln-1]),
reverse_compile(e.args[0]),
instance
)))
# @nb something can be a named type with rules and still be an aggregation.
# case in point IfcCompoundPlaneAngleMeasure. Therefore only unpack named
# type references from this point onwards.
while isinstance(type, (ifcopenshell.ifcopenshell_wrapper.named_type, ifcopenshell.ifcopenshell_wrapper.type_declaration)):
type = type.declared_type()
if isinstance(value, (list, tuple)):
assert isinstance(type, ifcopenshell.ifcopenshell_wrapper.aggregation_type)
ty = type.type_of_element()
for v in value:
check(v, ty, instance=inst)
elif isinstance(value, ifcopenshell.entity_instance):
if isinstance(S.declaration_by_name(value.is_a()), ifcopenshell.ifcopenshell_wrapper.entity):
# top level entity instances will be checked on their own
pass
else:
# unpack the type instance
check(value[0], S.declaration_by_name(value.is_a()), instance=inst)
for inst in f:
values = list(inst)
entity = S.declaration_by_name(inst.is_a())
attrs = entity.all_attributes()
for i, (attr, val, is_derived) in enumerate(zip(attrs, values, entity.derived())):
if is_derived:
# @todo
pass
else:
check(val, attr.type_of_attribute(), instance=inst)
for R in [r for r in rules if r.SCOPE == 'entity']:
for inst in f.by_type(R.TYPE_NAME):
try:
R()(inst)
except Exception as e:
ln = e.__traceback__.tb_next.tb_lineno
logger.error(str(error(
R.__name__,
reverse_compile(source.split("\n")[ln-1]),
reverse_compile(e.args[0]),
inst
)))
if __name__ == "__main__":
import sys
import json
import logging
import ifcopenshell
filenames = [x for x in sys.argv[1:] if not x.startswith("--")]
flags = set(x for x in sys.argv[1:] if x.startswith("--"))
for fn in filenames:
if "--json" in flags:
logger = json_logger()
else:
logger = logging.getLogger("validate")
logger.setLevel(logging.DEBUG)
f = ifcopenshell.open(fn)
run(f, logger)
if "--json" in flags:
print("\n".join(json.dumps(x, default=str) for x in logger.statements))
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -81,25 +81,33 @@ class Schema:
return iter(self.keys)
def __getitem__(self, key):
return self.types_entities[key]
return self.all_declarations[OrderedCaseInsensitiveDict_KeyObject(key)]
def __init__(self, parsetree):
self.tree = parsetree
self.name = parsetree.syntax[0][0].simple_id
schema = next(iter(parsetree.syntax[0]))
self.name = schema.simple_id
schema_declarations = list(schema.schema_body[0])
sort = lambda d: OrderedCaseInsensitiveDict(sorted(d))
declarations = [
d.any()[0]
for d in parsetree.syntax[0][0].schema_body[0]
if d.rule == "declaration" and d.any()[0].rule != "function_decl"
for d in schema_declarations
if d.rule == "declaration"
] + [
d
for d in schema_declarations
if d.rule == "RuleDeclaration"
]
self.types = sort([(t.name, t) for t in declarations if isinstance(t, nodes.TypeDeclaration)])
self.entities = sort([(t.name, t) for t in declarations if isinstance(t, nodes.EntityDeclaration)])
self.rules = sort([(t.name, t) for t in declarations if isinstance(t, nodes.RuleDeclaration)])
self.functions = sort([(t.name, t) for t in declarations if isinstance(t, nodes.FunctionDeclaration)])
self.keys = list(self.types.keys()) + list(self.entities.keys())
self.types_entities = {k: v for d in (self.types, self.entities) for k, v in d.items()}
self.keys = list(self.types.keys()) + list(self.entities.keys()) + list(self.rules.keys()) + list(self.functions.keys())
self.all_declarations = {k: v for d in (self.types, self.entities, self.rules, self.functions) for k, v in d.items()}
of_type = lambda *types: sort(
[(a, b.type) for a, b in self.types.items() if any(isinstance(b.type, ty) for ty in types)]
@@ -410,7 +410,7 @@ class SchemaClass(codegen.Base):
x.begin_schema()
emitted = set()
len_to_emit = len(mapping.schema)
len_to_emit = len(mapping.schema) - len(mapping.schema.rules) - len(mapping.schema.functions)
def write_simpletype(schema_name, name, type):
try:
@@ -446,6 +446,10 @@ class SchemaClass(codegen.Base):
fn = write_entity
elif mapping.schema.is_select(name):
fn = write_select
elif name in mapping.schema.rules:
return
elif name in mapping.schema.functions:
return
decl = mapping.schema[name]
if isinstance(decl, nodes.TypeDeclaration):
File diff suppressed because it is too large Load Diff
@@ -27,6 +27,7 @@ import functools
from collections import namedtuple
import ifcopenshell
import ifcopenshell.express.rule_executor
named_type = ifcopenshell.ifcopenshell_wrapper.named_type
aggregation_type = ifcopenshell.ifcopenshell_wrapper.aggregation_type
@@ -265,7 +266,7 @@ def get_entity_attributes(schema, entity):
return entity_attrs
def validate(f, logger):
def validate(f, logger, express_rules=False):
"""
For an IFC population model `f` (or filepath to such a file) validate whether the entity attribute values are correctly supplied. As this
is a function that is applied after a file has been parsed, certain types of errors in syntax, duplicate
@@ -402,6 +403,9 @@ def validate(f, logger):
# Restore the original value for 'use_attribute_value_derived'
ifcopenshell.ifcopenshell_wrapper.set_feature('use_attribute_value_derived', attribute_value_derived_org)
if express_rules:
ifcopenshell.express.rule_executor.run(f, logger)
if __name__ == "__main__":
import sys
import logging
@@ -417,8 +421,7 @@ if __name__ == "__main__":
logger.setLevel(logging.DEBUG)
print("Validating", fn, file=sys.stderr)
validate(fn, logger)
validate(fn, logger, "--rules" in flags)
if "--json" in flags:
conv = str