diff --git a/examples/complex_goal/target.goal b/examples/complex_goal/target.goal deleted file mode 100644 index 37f68ca..0000000 --- a/examples/complex_goal/target.goal +++ /dev/null @@ -1,70 +0,0 @@ -Broker HomeMQTT - host: 'localhost' - port: 1883 - auth: - username: '' - password: '' -end - -Entity TempSensor1 - type: sensor - topic: 'bedroom.sensor.temperature' - source: HomeMQTT - attributes: - - temp: float -end - -Entity TempSensor2 - type: sensor - topic: 'bathroom.sensor.temperature' - source: HomeMQTT - attributes: - - temp: float -end - -Entity TempSensor3 - type: sensor - topic: 'livingroom.sensor.temperature' - source: HomeMQTT - attributes: - - temp: float -end - -Entity AirQualitySensor1 - type: sensor - topic: 'kitchen.sensor.airq' - source: HomeMQTT - attributes: - - gas: float - - humidity: float -end - -Goal Goal_1 - entity: TempSensor1 -end - -Goal Goal_2 - condition: - (AirQualitySensor1.gas < 10) and - (AirQualitySensor1.humidity < 30) -end - -Goal Goal_3 - condition: (TempSensor1.temp > 10) and - (TempSensor2.temp > 10) and - (TempSensor3.temp > 10) -end - -Goal CG1 - goals: - - Goal_1 - - Goal_2 - - Goal_3 - strategy: ALL_ACCOMPLISHED_ORDERED -end - -Scenario MyScenario - goals: - - CG1 - concurrent: False -end diff --git a/goal_dsl/codegen.py b/goal_dsl/codegen.py index 62b5fcf..2215730 100644 --- a/goal_dsl/codegen.py +++ b/goal_dsl/codegen.py @@ -40,8 +40,6 @@ def generate(model_fpath: str, wgoals = scenario.goals set_defaults(scenario, broker, wgoals) - # report_broker(broker) - # report_goals(scenario) goals = [goal.goal for goal in wgoals] diff --git a/goal_dsl/grammar/entity.tx b/goal_dsl/grammar/entity.tx index 70c362d..a4e956d 100644 --- a/goal_dsl/grammar/entity.tx +++ b/goal_dsl/grammar/entity.tx @@ -23,7 +23,7 @@ Entity: ( ('type:' etype=EntityType) ('topic:' topic=STRING)? - ('source:' broker=EntitySource) + ('source:' source=EntitySource) ('description:' description=STRING)? ('freq:' freq=NUMBER)? ('attributes:' '-' attributes*=Attribute['-']) diff --git a/goal_dsl/language.py b/goal_dsl/language.py index 1b4c150..3783cda 100644 --- a/goal_dsl/language.py +++ b/goal_dsl/language.py @@ -2,13 +2,11 @@ from os.path import join from textx import metamodel_from_file, get_children_of_type, metamodel_for_language, get_location import textx.scoping.providers as scoping_providers -from textx import language +from textx import language, get_model import statistics from goal_dsl.definitions import THIS_DIR, MODEL_REPO_PATH, BUILTIN_MODELS -# CURRENT_FPATH = pathlib.Path(__file__).parent.resolve() - class PrimitiveDataType(object): """ @@ -43,17 +41,79 @@ def __str__(self): CUSTOM_CLASSES = [ ] + def class_provider(name): classes = dict(map(lambda x: (x.__name__, x), CUSTOM_CLASSES)) return classes.get(name) +def time_obj_processor(t): + if t.hour > 24 or t.hour < 0: + raise TextXSemanticError("Time.hours must be in range [0, 24]") + if t.minute > 60 or t.minute < 0: + raise TextXSemanticError("Time.minutes must be in range [0, 60]") + if t.second > 60 or t.second < 0: + raise TextXSemanticError("Time.seconds must be in range [0, 60]") + + +def process_time_class(model): + types_time = get_children_of_type("Time", model) + for t in types_time: + if t.hour > 24 or t.hour < 0: + raise TextXSemanticError("Time.hours must be in range [0, 24]") + if t.minute > 60 or t.minute < 0: + raise TextXSemanticError("Time.minutes must be in range [0, 60]") + if t.second > 60 or t.second < 0: + raise TextXSemanticError("Time.seconds must be in range [0, 60]") + + +def verify_broker_names(model): + _ids = [] + brokers = get_children_of_type("MQTTBroker", model) + brokers += get_children_of_type("AMQPBroker", model) + brokers += get_children_of_type("RedisBroker", model) + for b in brokers: + if b.name in _ids: + raise TextXSemanticError( + f"Broker with name <{b.name}> already exists", **get_location(b) + ) + _ids.append(b.name) + + +def verify_entity_names(model): + _ids = [] + entities = get_children_of_type("Entity", model) + for e in entities: + if e.name in _ids: + raise TextXSemanticError( + f"Entity with name <{e.name}> already exists", **get_location(e) + ) + _ids.append(e.name) + verify_entity_attrs(e) + + +def verify_entity_attrs(entity): + _ids = [] + for attr in entity.attributes: + if attr.name in _ids: + raise TextXSemanticError( + f"Entity attribute <{attr.name}> already exists", **get_location(attr) + ) + _ids.append(attr.name) + + def model_proc(model, metamodel): - pass + process_time_class(model) + verify_entity_names(model) + verify_broker_names(model) -def condition_processor(plot): - pass +def condition_processor(cond): + # Adds a cond.cond_expr property to the Condition instance + print("AAAAAS") + build_cond_expr(cond) + print(cond.cond_expr) + def nid_processor(nid): nid = nid.replace("\n", "") @@ -61,7 +121,19 @@ def nid_processor(nid): obj_processors = { - 'Condition': condition_processor + # 'Condition': condition_processor, + 'ConditionList': condition_processor, + # 'ConditionGroup': condition_processor, + # 'PrimitiveCondition': condition_processor, + # 'AdvancedCondition': condition_processor, + # 'BoolCondition': condition_processor, + # 'TimeCondition': condition_processor, + # 'NumericCondition': condition_processor, + # 'StringCondition': condition_processor, + # 'ListCondition': condition_processor, + # 'DictCondition': condition_processor, + # 'InRangeCondition': condition_processor, + # 'MathExpression': condition_processor, } @@ -114,7 +186,7 @@ def build_model(model_path): model = mm.model_from_file(model_path) # Parse the model from the file conds = get_top_level_condition(model) # Get the top-level conditions from the model for cond in conds: - build_cond_expr(cond, model) # Build the condition expressions for each top-level condition + build_cond_expr(cond) # Build the condition expressions for each top-level condition return model # Return the built model @@ -142,8 +214,8 @@ def get_model_goals(model): return goals -def build_cond_expr(cond, model): - cond_def = get_cond_definition(cond, model) +def build_cond_expr(cond): + cond_def = get_cond_definition(cond) cond.cond_expr = cond_def transform_cond(cond) @@ -170,9 +242,10 @@ def is_float(string): return False -def get_cond_definition(cond, model): - line_start, col_start = model._tx_parser.pos_to_linecol(cond._tx_position) - line_end, col_end = model._tx_parser.pos_to_linecol(cond._tx_position_end) +def get_cond_definition(cond): + model = get_model(cond) + line_start, _ = model._tx_parser.pos_to_linecol(cond._tx_position) + line_end, _ = model._tx_parser.pos_to_linecol(cond._tx_position_end) loc = get_location(cond) f = open(loc["filename"]) lines = f.readlines() @@ -181,37 +254,6 @@ def get_cond_definition(cond, model): return cond_def -def evaluate_cond(cond): - # Check if condition has been build using build_expression - if cond.cond_expr not in (None, ''): - # Evaluate condition providing the textX model as global context for evaluation - try: - entities = {} - # print(entities['system_clock'].attributes_dict['time'].value.to_int()) - print(f"Evaluate Condition: {cond.cond_expr}") - if eval( - cond.cond_expr, - { - 'entities': entities - }, - { - 'std': statistics.stdev, - 'var': statistics.variance, - 'mean': statistics.mean, - 'min': min, - 'max': max, - } - ): - return True, f"{cond.parent.name}: triggered." - else: - return False, f"{cond.parent.name}: not triggered." - except Exception as e: - print(e) - return False, f"{cond.parent.name}: not triggered." - else: - return False, f"{cond.parent.name}: condition not built." - - @language('goal_dsl', '*.goal') def goaldsl_language(): "Goal-driven CPS Behavior Verification language"