|
@@ -0,0 +1,810 @@
|
|
|
+include "primitives.alh"
|
|
|
+include "modelling.alh"
|
|
|
+include "object_operations.alh"
|
|
|
+include "utils.alh"
|
|
|
+include "random.alh"
|
|
|
+include "library.alh"
|
|
|
+include "io.alh"
|
|
|
+
|
|
|
+Boolean afap = False
|
|
|
+
|
|
|
+Element function resolve_function(location : String, data : Element):
|
|
|
+ if (bool_not(dict_in(data["cache_operations"], location))):
|
|
|
+ dict_add(data["cache_operations"], location, get_func_AL_model(import_node(location)))
|
|
|
+ return data["cache_operations"][location]!
|
|
|
+
|
|
|
+Void function print_states(model : Element, data : Element):
|
|
|
+ Element classes
|
|
|
+ Element states
|
|
|
+ Element class
|
|
|
+ String state
|
|
|
+
|
|
|
+ log("Current states:")
|
|
|
+ classes = dict_keys(data["classes"])
|
|
|
+ while (set_len(classes) > 0):
|
|
|
+ class = set_pop(classes)
|
|
|
+ class = data["classes"][class]
|
|
|
+ log(string_join(string_join(string_join(" ", class["ID"]), " : "), read_attribute(model, class["type"], "name")))
|
|
|
+ log(" Attributes: " + dict_to_string(class["attributes"]))
|
|
|
+
|
|
|
+ states = set_copy(class["states"])
|
|
|
+ log(" States:")
|
|
|
+ while (set_len(states) > 0):
|
|
|
+ state = set_pop(states)
|
|
|
+ log(string_join(" ", read_attribute(model, state, "name")))
|
|
|
+
|
|
|
+ return!
|
|
|
+
|
|
|
+Element function filter(model : Element, set : Element, attribute_name : String, attribute_value : Element):
|
|
|
+ Element keys
|
|
|
+ String key
|
|
|
+ Element result
|
|
|
+
|
|
|
+ result = set_create()
|
|
|
+ while (set_len(set) > 0):
|
|
|
+ key = set_pop(set)
|
|
|
+ if (value_eq(read_attribute(model, key, attribute_name), attribute_value)):
|
|
|
+ set_add(result, key)
|
|
|
+
|
|
|
+ return result!
|
|
|
+
|
|
|
+Element function filter_exists(model : Element, set : Element, attribute_name : String):
|
|
|
+ Element keys
|
|
|
+ String key
|
|
|
+ Element result
|
|
|
+
|
|
|
+ result = set_create()
|
|
|
+ while (set_len(set) > 0):
|
|
|
+ key = set_pop(set)
|
|
|
+ if (element_neq(read_attribute(model, key, attribute_name), read_root())):
|
|
|
+ set_add(result, key)
|
|
|
+
|
|
|
+ return result!
|
|
|
+
|
|
|
+Element function expand_current_state(model : Element, state : String, data : Element):
|
|
|
+ // Find the hierarchy of all current states, and select those that contain the currently selected state
|
|
|
+ Element result
|
|
|
+ Element current_states
|
|
|
+ result = set_create()
|
|
|
+ current_states = set_copy(data["current_class_handle"]["states"])
|
|
|
+
|
|
|
+ Element hierarchy
|
|
|
+ String deep_state
|
|
|
+ while (set_len(current_states) > 0):
|
|
|
+ deep_state = set_pop(current_states)
|
|
|
+ hierarchy = find_hierarchy(model, deep_state, data)
|
|
|
+ // Got the hierarchy of one of the states
|
|
|
+
|
|
|
+ if (list_in(hierarchy, state)):
|
|
|
+ // This hierarchy contains the root state we are checking for, so add to set
|
|
|
+ set_add(result, deep_state)
|
|
|
+
|
|
|
+ return result!
|
|
|
+
|
|
|
+Element function expand_initial_state(model : Element, state : String, data : Element):
|
|
|
+ String t
|
|
|
+ t = read_type(model, state)
|
|
|
+ if (t == "SCCD/CompositeState"):
|
|
|
+ // Recurse further in the composite
|
|
|
+ return expand_composite_state(model, state, data)!
|
|
|
+ elif (t == "SCCD/ParallelState"):
|
|
|
+ // Split up all components
|
|
|
+ return expand_parallel_state(model, state, data)!
|
|
|
+ elif (t == "SCCD/HistoryState"):
|
|
|
+ // Reset the history
|
|
|
+ // This is not really an initial state, but it is called in exactly the same places
|
|
|
+ return data["current_class_handle"]["history"][get_parent(model, state)]!
|
|
|
+ else:
|
|
|
+ // Probably just an atomic, so return this one only
|
|
|
+ Element result
|
|
|
+ result = set_create()
|
|
|
+ set_add(result, state)
|
|
|
+ return result!
|
|
|
+
|
|
|
+Element function expand_composite_state(model : Element, composite_state : String, data : Element):
|
|
|
+ // Resolve all initial states from a single composite state
|
|
|
+ String initial
|
|
|
+
|
|
|
+ // Fetch the initial state
|
|
|
+ initial = set_pop(filter(model, allAssociationDestinations(model, composite_state, "SCCD/composite_children"), "isInitial", True))
|
|
|
+
|
|
|
+ // Expand the initial state, depending on what it is
|
|
|
+ return expand_initial_state(model, initial, data)!
|
|
|
+
|
|
|
+Element function expand_parallel_state(model : Element, parallel_state : String, data : Element):
|
|
|
+ // Resolve all initial states from a single parallel state
|
|
|
+ Element children
|
|
|
+ Element result
|
|
|
+ Element expanded_children
|
|
|
+
|
|
|
+ children = allAssociationDestinations(model, parallel_state, "SCCD/parallel_children")
|
|
|
+ result = set_create()
|
|
|
+
|
|
|
+ while (set_len(children) > 0):
|
|
|
+ set_merge(result, expand_initial_state(model, set_pop(children), data))
|
|
|
+
|
|
|
+ return result!
|
|
|
+
|
|
|
+Void function delete_class(model : Element, data : Element, identifier : String):
|
|
|
+ // Stop a specific class instance, with attached statechart, from executing
|
|
|
+ dict_delete(data["classes"], identifier)
|
|
|
+
|
|
|
+String function start_class(model : Element, data : Element, class : String, parameters : Element):
|
|
|
+ // Start up the class and assign its initial state to it
|
|
|
+
|
|
|
+ // First find an empty identifier
|
|
|
+ String identifier
|
|
|
+ identifier = cast_string(dict_len(data["classes"]))
|
|
|
+
|
|
|
+ // Create the data structure for a running class
|
|
|
+ Element class_handle
|
|
|
+ class_handle = dict_create()
|
|
|
+ dict_add(class_handle, "type", class)
|
|
|
+ dict_add(class_handle, "ID", identifier)
|
|
|
+ dict_add(class_handle, "events", set_create())
|
|
|
+ dict_add(class_handle, "new_events", set_create())
|
|
|
+ dict_add(class_handle, "timers", dict_create())
|
|
|
+ dict_add(data["classes"], class_handle["ID"], class_handle)
|
|
|
+
|
|
|
+ String prev_class
|
|
|
+ prev_class = data["current_class"]
|
|
|
+ dict_overwrite(data, "current_class", identifier)
|
|
|
+ dict_overwrite(data, "current_class_handle", data["classes"][identifier])
|
|
|
+
|
|
|
+ // Add the current state of the class
|
|
|
+ String initial_state
|
|
|
+ // Should only be one behaviour linked to it!
|
|
|
+ initial_state = set_pop(allAssociationDestinations(model, class, "SCCD/behaviour"))
|
|
|
+ dict_add(class_handle, "states", expand_initial_state(model, initial_state, class_handle))
|
|
|
+
|
|
|
+ // Initialize history for all composite states
|
|
|
+ Element history
|
|
|
+ Element cstates
|
|
|
+ String cstate
|
|
|
+
|
|
|
+ history = dict_create()
|
|
|
+ dict_add(class_handle, "history", history)
|
|
|
+ cstates = allInstances(model, "SCCD/CompositeState")
|
|
|
+ while (set_len(cstates) > 0):
|
|
|
+ cstate = set_pop(cstates)
|
|
|
+ dict_add(history, cstate, expand_initial_state(model, cstate, class_handle))
|
|
|
+
|
|
|
+ // Add all attributes
|
|
|
+ Element attributes
|
|
|
+ attributes = dict_create()
|
|
|
+ Element attrs
|
|
|
+ attrs = allAssociationDestinations(model, class, "SCCD/class_attributes")
|
|
|
+ while (set_len(attrs) > 0):
|
|
|
+ dict_add(attributes, read_attribute(model, set_pop(attrs), "name"), read_root())
|
|
|
+ dict_add(class_handle, "attributes", attributes)
|
|
|
+
|
|
|
+ // Invoke constructor
|
|
|
+ Element constructor
|
|
|
+ constructor = read_attribute(model, class, "constructor_body")
|
|
|
+ if (element_neq(constructor, read_root())):
|
|
|
+ // Constructor, so execute
|
|
|
+ constructor = resolve_function(constructor, data)
|
|
|
+ constructor(attributes, parameters)
|
|
|
+
|
|
|
+ // Execute all entry actions
|
|
|
+ Element init
|
|
|
+ init = set_create()
|
|
|
+ set_add(init, "")
|
|
|
+ // Initial state before initialization is the set with an empty hierarchy
|
|
|
+ // Empty set would not find any difference between the source and target
|
|
|
+
|
|
|
+ execute_actions(model, init, set_copy(class_handle["states"]), data, "")
|
|
|
+
|
|
|
+ dict_overwrite(data, "current_class", prev_class)
|
|
|
+ dict_overwrite(data, "current_class_handle", data["classes"][prev_class])
|
|
|
+
|
|
|
+ return identifier!
|
|
|
+
|
|
|
+Element function get_enabled_transitions(model : Element, state : String, data : Element):
|
|
|
+ // Returns all enabled transitions
|
|
|
+ Element result
|
|
|
+ Element to_filter
|
|
|
+ String attr
|
|
|
+ String transition
|
|
|
+ Element cond
|
|
|
+ String evt_name
|
|
|
+ Element evt
|
|
|
+ Element events
|
|
|
+ Element event_names
|
|
|
+ Element event_parameters
|
|
|
+
|
|
|
+ result = set_create()
|
|
|
+ to_filter = allOutgoingAssociationInstances(model, state, "SCCD/transition")
|
|
|
+
|
|
|
+ event_names = set_create()
|
|
|
+ event_parameters = set_create()
|
|
|
+ events = set_copy(data["current_class_handle"]["events"])
|
|
|
+ while (set_len(events) > 0):
|
|
|
+ evt = set_pop(events)
|
|
|
+ evt_name = list_read(evt, 0)
|
|
|
+
|
|
|
+ if (bool_not(set_in(event_names, evt_name))):
|
|
|
+ // Not yet registered the event
|
|
|
+ set_add(event_names, evt_name)
|
|
|
+ dict_add(event_parameters, evt_name, set_create())
|
|
|
+ // Add event parameters
|
|
|
+ set_add_node(event_parameters[evt_name], list_read(evt, 1))
|
|
|
+
|
|
|
+ while (set_len(to_filter) > 0):
|
|
|
+ transition = set_pop(to_filter)
|
|
|
+
|
|
|
+ // Check event
|
|
|
+ attr = read_attribute(model, transition, "event")
|
|
|
+ if (bool_not(bool_or(element_eq(attr, read_root()), set_in(event_names, attr)))):
|
|
|
+ // At least one enabled event is found
|
|
|
+ continue!
|
|
|
+
|
|
|
+ // Check after
|
|
|
+ // Only an after if there was no event!
|
|
|
+ if (bool_and(element_eq(attr, read_root()), read_attribute(model, transition, "after"))):
|
|
|
+ if (dict_in(data["current_class_handle"]["timers"], transition)):
|
|
|
+ // Registered timer already, let's check if it has expired
|
|
|
+ if (float_gt(data["current_class_handle"]["timers"][transition], data["time_sim"])):
|
|
|
+ // Not enabled yet
|
|
|
+ continue!
|
|
|
+ else:
|
|
|
+ // Not registered even, so not enabled either
|
|
|
+ continue!
|
|
|
+
|
|
|
+ // Check condition, but depends on whether there was an event or not
|
|
|
+ cond = read_attribute(model, transition, "cond")
|
|
|
+ if (element_neq(cond, read_root())):
|
|
|
+ // Got a condition, so resolve
|
|
|
+ cond = resolve_function(cond, data)
|
|
|
+
|
|
|
+ if (element_neq(attr, read_root())):
|
|
|
+ // We have an event to take into account!
|
|
|
+ Element params
|
|
|
+ Element param
|
|
|
+ params = set_copy(event_parameters[attr])
|
|
|
+ while (set_len(params) > 0):
|
|
|
+ param = set_pop(params)
|
|
|
+ if (element_neq(cond, read_root())):
|
|
|
+ // Got a condition to check first
|
|
|
+ if (bool_not(cond(data["current_class_handle"]["attributes"], param))):
|
|
|
+ // Condition failed, so skip
|
|
|
+ continue!
|
|
|
+ // Fine to add this one with the specified parameters
|
|
|
+ set_add_node(result, create_tuple(transition, param))
|
|
|
+ else:
|
|
|
+ // No event to think about, just add the transition
|
|
|
+ if (element_neq(cond, read_root())):
|
|
|
+ // Check the condition first
|
|
|
+ if (bool_not(cond(data["current_class_handle"]["attributes"], read_root()))):
|
|
|
+ // Condition false, so skip
|
|
|
+ continue!
|
|
|
+ // Fine to add this one without event parameters (no event)
|
|
|
+ set_add_node(result, create_tuple(transition, read_root()))
|
|
|
+
|
|
|
+ return result!
|
|
|
+
|
|
|
+Void function process_raised_event(model : Element, event : Element, parameter_action : Element, data : Element):
|
|
|
+ String scope
|
|
|
+ scope = read_attribute(model, event, "scope")
|
|
|
+ if (scope == "cd"):
|
|
|
+ // Is an event for us internally, so don't append
|
|
|
+ // Instead, we process it directly
|
|
|
+ String operation
|
|
|
+ operation = read_attribute(model, event, "event")
|
|
|
+ if (operation == "create_instance"):
|
|
|
+ // Start up a new class of the desired type
|
|
|
+
|
|
|
+ // Parameters of this call:
|
|
|
+ // class -- type of the class to instantiate
|
|
|
+ // identifier -- name of this instance, for future reference
|
|
|
+ // parameters -- parameters for constructor
|
|
|
+ String class
|
|
|
+ String identifier
|
|
|
+ Element parameters
|
|
|
+ class = set_pop(filter(model, allInstances(model, "SCCD/Class"), "name", list_read(parameter_action, 1)))
|
|
|
+ parameters = list_read(parameter_action, 2)
|
|
|
+ identifier = start_class(model, data, class, parameters)
|
|
|
+
|
|
|
+ // Notify the creator of the ID of the created instance
|
|
|
+ Element lst
|
|
|
+ lst = list_create()
|
|
|
+ list_append(lst, identifier)
|
|
|
+ set_add_node(data["current_class_handle"]["new_events"], create_tuple("instance_created", lst))
|
|
|
+
|
|
|
+ elif (operation == "delete_instance"):
|
|
|
+ // Delete the requested class
|
|
|
+ String identifier
|
|
|
+ identifier = list_read(parameter_action, 0)
|
|
|
+ delete_class(model, data, identifier)
|
|
|
+ set_add_node(data["current_class_handle"]["new_events"], create_tuple("instance_deleted", parameter_action))
|
|
|
+
|
|
|
+ elif (scope == "broad"):
|
|
|
+ // Send to all classes
|
|
|
+ Element classes
|
|
|
+ classes = dict_keys(data["classes"])
|
|
|
+ while(set_len(classes) > 0):
|
|
|
+ set_add_node(data["classes"][set_pop(classes)]["new_events"], create_tuple(read_attribute(model, event, "event"), parameter_action))
|
|
|
+
|
|
|
+ elif (scope == "narrow"):
|
|
|
+ // Send to the specified class only
|
|
|
+ Element func
|
|
|
+ func = resolve_function(read_attribute(model, event, "target"), data)
|
|
|
+ String dest
|
|
|
+ dest = func(data["current_class_handle"]["attributes"])
|
|
|
+ set_add_node(data["classes"][dest]["new_events"], create_tuple(read_attribute(model, event, "event"), parameter_action))
|
|
|
+
|
|
|
+ elif (scope == "output"):
|
|
|
+ // Store it in the output event model
|
|
|
+ String evt
|
|
|
+
|
|
|
+ if (bool_not(afap)):
|
|
|
+ Element val
|
|
|
+ val = dict_create()
|
|
|
+ dict_add(val, "timestamp", data["time_sim"])
|
|
|
+ dict_add(val, "name", read_attribute(model, event, "event"))
|
|
|
+ dict_add(val, "parameter", parameter_action)
|
|
|
+
|
|
|
+ output("EVENT: " + dict_to_string(val))
|
|
|
+
|
|
|
+ evt = instantiate_node(model, "trace/Event", "")
|
|
|
+ instantiate_attribute(model, evt, "timestamp", data["time_sim"])
|
|
|
+ instantiate_attribute(model, evt, "name", read_attribute(model, event, "event"))
|
|
|
+ instantiate_attribute(model, evt, "parameter", parameter_action)
|
|
|
+
|
|
|
+ else:
|
|
|
+ // Same as local
|
|
|
+ set_add_node(data["current_class_handle"]["new_events"], create_tuple(read_attribute(model, event, "event"), parameter_action))
|
|
|
+
|
|
|
+ return !
|
|
|
+
|
|
|
+Element function execute_transition(model : Element, data : Element, transition_tuple : Element):
|
|
|
+ // Execute the script (if any)
|
|
|
+ Element script
|
|
|
+ String transition
|
|
|
+ Element event_parameter
|
|
|
+ transition = list_read(transition_tuple, 0)
|
|
|
+ event_parameter = list_read(transition_tuple, 1)
|
|
|
+
|
|
|
+ script = read_attribute(model, transition, "script")
|
|
|
+ if (element_neq(script, read_root())):
|
|
|
+ script = resolve_function(script, data)
|
|
|
+ script(data["current_class_handle"]["attributes"], event_parameter)
|
|
|
+
|
|
|
+ // Raise events (if any)
|
|
|
+ Element events
|
|
|
+ String event
|
|
|
+ events = allAssociationDestinations(model, transition, "SCCD/transition_raises")
|
|
|
+ while (set_len(events) > 0):
|
|
|
+ event = set_pop(events)
|
|
|
+
|
|
|
+ Element parameter_action
|
|
|
+ parameter_action = read_attribute(model, event, "parameter")
|
|
|
+ if (element_neq(parameter_action, read_root())):
|
|
|
+ // Got a parameter to evaluate
|
|
|
+ parameter_action = resolve_function(parameter_action, data)
|
|
|
+ parameter_action = parameter_action(data["current_class_handle"]["attributes"], event_parameter)
|
|
|
+
|
|
|
+ process_raised_event(model, event, parameter_action, data)
|
|
|
+
|
|
|
+ // Find new set of states
|
|
|
+ Element target_states
|
|
|
+ Element source_states
|
|
|
+ source_states = expand_current_state(model, readAssociationSource(model, transition), data)
|
|
|
+ target_states = expand_initial_state(model, readAssociationDestination(model, transition), data)
|
|
|
+
|
|
|
+ execute_actions(model, source_states, target_states, data, readAssociationSource(model, transition))
|
|
|
+
|
|
|
+ return target_states!
|
|
|
+
|
|
|
+Boolean function step_class(model : Element, data : Element, class : String):
|
|
|
+ // Find enabled transitions in a class and execute it, updating the state
|
|
|
+ // Iterate over all current states, searching for enabled transitions
|
|
|
+ // Search for enabled transitions in higher levels as well!
|
|
|
+ Element states
|
|
|
+ Element new_states
|
|
|
+ String state
|
|
|
+ Element transitions
|
|
|
+ String transition
|
|
|
+ Boolean transitioned
|
|
|
+ Element hierarchy
|
|
|
+ String current_state
|
|
|
+ Boolean found
|
|
|
+
|
|
|
+ if (bool_not(dict_in(data["classes"], class))):
|
|
|
+ // Seems like this class was removed, so stop execution
|
|
|
+ return False!
|
|
|
+
|
|
|
+ // Notify everyone of the current class
|
|
|
+ dict_overwrite(data, "current_class", class)
|
|
|
+ dict_overwrite(data, "current_class_handle", data["classes"][class])
|
|
|
+
|
|
|
+ states = set_copy(data["current_class_handle"]["states"])
|
|
|
+ new_states = set_create()
|
|
|
+ transitioned = False
|
|
|
+
|
|
|
+ while (set_len(states) > 0):
|
|
|
+ state = set_pop(states)
|
|
|
+ found = False
|
|
|
+
|
|
|
+ // Loop over the hierarchy of this state and try to apply transitions
|
|
|
+ hierarchy = find_hierarchy(model, state, data)
|
|
|
+ while (list_len(hierarchy) > 0):
|
|
|
+ current_state = list_pop(hierarchy, 0)
|
|
|
+ transitions = get_enabled_transitions(model, current_state, data)
|
|
|
+
|
|
|
+ if (set_len(transitions) > 0):
|
|
|
+ // Found an enabled transition, so store that one
|
|
|
+ transition = random_choice(set_to_list(transitions))
|
|
|
+
|
|
|
+ // Execute transition
|
|
|
+ set_merge(new_states, execute_transition(model, data, transition))
|
|
|
+
|
|
|
+ // When leaving an orthogonal component, we must also pop all related states that might be processed in the future!
|
|
|
+ Element leaving
|
|
|
+ leaving = expand_current_state(model, current_state, data)
|
|
|
+ set_subtract(states, leaving)
|
|
|
+
|
|
|
+ transitioned = True
|
|
|
+ found = True
|
|
|
+ break!
|
|
|
+
|
|
|
+ if (bool_not(found)):
|
|
|
+ // Nothing found, so stay in the current state
|
|
|
+ set_add(new_states, state)
|
|
|
+
|
|
|
+ // Update states
|
|
|
+ dict_overwrite(data["current_class_handle"], "states", new_states)
|
|
|
+
|
|
|
+ return transitioned!
|
|
|
+
|
|
|
+String function get_parent(model : Element, state : String):
|
|
|
+ Element tmp_set
|
|
|
+ tmp_set = allAssociationOrigins(model, state, "SCCD/composite_children")
|
|
|
+ set_merge(tmp_set, allAssociationOrigins(model, state, "SCCD/parallel_children"))
|
|
|
+ if (set_len(tmp_set) > 0):
|
|
|
+ return set_pop(tmp_set)!
|
|
|
+ else:
|
|
|
+ return ""!
|
|
|
+
|
|
|
+Element function find_hierarchy(model : Element, state : String, data : Element):
|
|
|
+ // Try to cache as much as possible!
|
|
|
+ if (bool_not(dict_in(data["cache_hierarchy"], state))):
|
|
|
+ Element result
|
|
|
+ if (state == ""):
|
|
|
+ result = list_create()
|
|
|
+ else:
|
|
|
+ String parent
|
|
|
+ parent = get_parent(model, state)
|
|
|
+ // We have a parent, so take the parent list first
|
|
|
+ result = find_hierarchy(model, parent, data)
|
|
|
+ list_append(result, state)
|
|
|
+ dict_add(data["cache_hierarchy"], state, result)
|
|
|
+ return dict_copy(data["cache_hierarchy"][state])!
|
|
|
+
|
|
|
+Void function execute_actions(model : Element, source_states : Element, target_states : Element, data : Element, transition_source : String):
|
|
|
+ Element exit
|
|
|
+ Element entry
|
|
|
+ exit = list_create()
|
|
|
+ entry = list_create()
|
|
|
+
|
|
|
+ source_states = set_copy(source_states)
|
|
|
+ target_states = set_copy(target_states)
|
|
|
+
|
|
|
+ // Add all exit and entry actions to the list of actions to execute
|
|
|
+ // Do this by finding the common parent, and then doing all exit actions up to that node, and all entry actions up to the target_state
|
|
|
+
|
|
|
+ // First, find the hierarchy!
|
|
|
+ Element hierarchy_sources
|
|
|
+ Element hierarchy_targets
|
|
|
+ Element all_hierarchies
|
|
|
+
|
|
|
+ hierarchy_sources = set_create()
|
|
|
+ while (set_len(source_states) > 0):
|
|
|
+ set_add_node(hierarchy_sources, find_hierarchy(model, set_pop(source_states), data))
|
|
|
+
|
|
|
+ hierarchy_targets = set_create()
|
|
|
+ while (set_len(target_states) > 0):
|
|
|
+ set_add_node(hierarchy_targets, find_hierarchy(model, set_pop(target_states), data))
|
|
|
+
|
|
|
+ all_hierarchies = set_copy(hierarchy_sources)
|
|
|
+ set_merge(all_hierarchies, hierarchy_targets)
|
|
|
+
|
|
|
+ // Difference these all lists, finding the first common entry
|
|
|
+ Element iter_hierarchies
|
|
|
+ Integer i
|
|
|
+ String current
|
|
|
+ Element hierarchy
|
|
|
+ Boolean finished
|
|
|
+ Integer transition_depth
|
|
|
+ Integer lca_depth
|
|
|
+ lca_depth = 0
|
|
|
+ finished = False
|
|
|
+
|
|
|
+ if (transition_source != ""):
|
|
|
+ // Find out the level of the transition_source by fetching its hierarchy
|
|
|
+ transition_depth = list_len(find_hierarchy(model, transition_source, data)) - 1
|
|
|
+
|
|
|
+ // Now check for the least common ancestor
|
|
|
+ while (bool_not(finished)):
|
|
|
+ // Check the i-th element in both and see if they are equal
|
|
|
+ current = ""
|
|
|
+ iter_hierarchies = set_copy(all_hierarchies)
|
|
|
+ while (set_len(iter_hierarchies) > 0):
|
|
|
+ hierarchy = set_pop(iter_hierarchies)
|
|
|
+
|
|
|
+ // Exhausted one of the lists
|
|
|
+ if (lca_depth >= list_len(hierarchy)):
|
|
|
+ finished = True
|
|
|
+ break!
|
|
|
+
|
|
|
+ // Reached the same level as transition depth already, so no need to increase
|
|
|
+ if (lca_depth == transition_depth):
|
|
|
+ finished = True
|
|
|
+ break!
|
|
|
+
|
|
|
+ // First entry, so read out value as reference
|
|
|
+ if (current == ""):
|
|
|
+ current = list_read(hierarchy, lca_depth)
|
|
|
+
|
|
|
+ // Check with reference element
|
|
|
+ if (bool_not(value_eq(list_read(hierarchy, lca_depth), current))):
|
|
|
+ finished = True
|
|
|
+ break!
|
|
|
+
|
|
|
+ // i-th element equal for all hierarchies, so go to next element
|
|
|
+ if (bool_not(finished)):
|
|
|
+ lca_depth = lca_depth + 1
|
|
|
+
|
|
|
+ if (lca_depth < transition_depth):
|
|
|
+ i = lca_depth
|
|
|
+ else:
|
|
|
+ i = transition_depth
|
|
|
+
|
|
|
+ else:
|
|
|
+ // Initial, so just set i to zero
|
|
|
+ i = 0
|
|
|
+
|
|
|
+ // Found the first differing element at position i
|
|
|
+
|
|
|
+ // All elements remaining in hierarchy_source are to be traversed in REVERSE order for the exit actions
|
|
|
+ // All elements remaining in hierarchy_target are to be traversed in NORMAL order for the entry actions
|
|
|
+ // This is not that simple either, as we need to consider that some actions might already have been added to the list...
|
|
|
+
|
|
|
+ // Add hierarchy_sources actions
|
|
|
+ String state
|
|
|
+ Element visited
|
|
|
+ Element action
|
|
|
+ Element spliced_hierarchy
|
|
|
+ Element hierarchy_source
|
|
|
+ Element hierarchy_target
|
|
|
+ visited = set_create()
|
|
|
+ while (set_len(hierarchy_sources) > 0):
|
|
|
+ // Get one of these hierarchies
|
|
|
+ hierarchy_source = set_pop(hierarchy_sources)
|
|
|
+ spliced_hierarchy = list_splice(hierarchy_source, i, list_len(hierarchy_source))
|
|
|
+ while (list_len(spliced_hierarchy) > 0):
|
|
|
+ state = list_pop(spliced_hierarchy, list_len(spliced_hierarchy) - 1)
|
|
|
+ if (set_in(visited, state)):
|
|
|
+ // Already added this state, so don't bother
|
|
|
+ continue!
|
|
|
+ else:
|
|
|
+ // New state, so prepend it to the list
|
|
|
+ // Prepend, instead of append, as we want to do these operations in reverse order!
|
|
|
+ list_insert(exit, state, 0)
|
|
|
+
|
|
|
+ // Add this state as visited
|
|
|
+ set_add(visited, state)
|
|
|
+
|
|
|
+ // Add hierarchy_targets actions
|
|
|
+ // Clear visited, just to be safe, though it should not matter
|
|
|
+ visited = set_create()
|
|
|
+ while (set_len(hierarchy_targets) > 0):
|
|
|
+ // Get one of these hierarchies
|
|
|
+ hierarchy_target = set_pop(hierarchy_targets)
|
|
|
+ spliced_hierarchy = list_splice(hierarchy_target, i, list_len(hierarchy_target))
|
|
|
+ while (list_len(spliced_hierarchy) > 0):
|
|
|
+ state = list_pop(spliced_hierarchy, list_len(spliced_hierarchy) - 1)
|
|
|
+ if (set_in(visited, state)):
|
|
|
+ // Already added this state, so don't bother
|
|
|
+ continue!
|
|
|
+ else:
|
|
|
+ // New state, so append it to the list
|
|
|
+ // Append, instead of prepend, as we want to do these operations in normal order!
|
|
|
+ list_append(entry, state)
|
|
|
+
|
|
|
+ // Add this state as visited, even though there might not have been an associated action
|
|
|
+ set_add(visited, state)
|
|
|
+
|
|
|
+ // Now we have a list of traversed states!
|
|
|
+ // Start executing all their operations in order
|
|
|
+
|
|
|
+ Element events
|
|
|
+ String event
|
|
|
+ // First do exit actions
|
|
|
+ while (list_len(exit) > 0):
|
|
|
+ state = list_pop(exit, 0)
|
|
|
+
|
|
|
+ // Set history when leaving
|
|
|
+ if (read_type(model, state) == "SCCD/CompositeState"):
|
|
|
+ dict_overwrite(data["current_class_handle"]["history"], state, expand_current_state(model, state, data))
|
|
|
+
|
|
|
+ // Do exit actions
|
|
|
+ action = read_attribute(model, state, "onExitScript")
|
|
|
+ if (element_neq(action, read_root())):
|
|
|
+ // Got a script, so execute!
|
|
|
+ action = resolve_function(action, data)
|
|
|
+ action(data["current_class_handle"]["attributes"])
|
|
|
+
|
|
|
+ // Raise events
|
|
|
+ events = allAssociationDestinations(model, state, "SCCD/onExitRaise")
|
|
|
+ while (set_len(events) > 0):
|
|
|
+ event = set_pop(events)
|
|
|
+
|
|
|
+ Element parameter_action
|
|
|
+ parameter_action = read_attribute(model, event, "parameter")
|
|
|
+ if (element_neq(parameter_action, read_root())):
|
|
|
+ // Got a parameter to evaluate
|
|
|
+ parameter_action = resolve_function(parameter_action, data)
|
|
|
+ parameter_action = parameter_action(data["current_class_handle"]["attributes"])
|
|
|
+
|
|
|
+ process_raised_event(model, event, parameter_action, data)
|
|
|
+
|
|
|
+ // Unschedule after events
|
|
|
+ Element timed_transitions
|
|
|
+ timed_transitions = filter_exists(model, allOutgoingAssociationInstances(model, state, "SCCD/transition"), "after")
|
|
|
+ while (set_len(timed_transitions) > 0):
|
|
|
+ dict_delete(data["current_class_handle"]["timers"], set_pop(timed_transitions))
|
|
|
+
|
|
|
+ // Then do entry actions
|
|
|
+ while (list_len(entry) > 0):
|
|
|
+ state = list_pop(entry, 0)
|
|
|
+
|
|
|
+ // Do entry actions
|
|
|
+ action = read_attribute(model, state, "onEntryScript")
|
|
|
+ if (element_neq(action, read_root())):
|
|
|
+ // Got a script, so execute!
|
|
|
+ action = resolve_function(action, data)
|
|
|
+ action(data["current_class_handle"]["attributes"])
|
|
|
+
|
|
|
+ // Raise events
|
|
|
+ events = allAssociationDestinations(model, state, "SCCD/onEntryRaise")
|
|
|
+ while (set_len(events) > 0):
|
|
|
+ event = set_pop(events)
|
|
|
+
|
|
|
+ Element parameter_action
|
|
|
+ parameter_action = read_attribute(model, event, "parameter")
|
|
|
+ if (element_neq(parameter_action, read_root())):
|
|
|
+ // Got a parameter to evaluate
|
|
|
+ parameter_action = resolve_function(parameter_action, data)
|
|
|
+ parameter_action = parameter_action(data["current_class_handle"]["attributes"])
|
|
|
+
|
|
|
+ process_raised_event(model, event, parameter_action, data)
|
|
|
+
|
|
|
+ // Schedule after events
|
|
|
+ Element timed_transitions
|
|
|
+ String transition
|
|
|
+ Element after
|
|
|
+ timed_transitions = filter_exists(model, allOutgoingAssociationInstances(model, state, "SCCD/transition"), "after")
|
|
|
+ while (set_len(timed_transitions) > 0):
|
|
|
+ transition = set_pop(timed_transitions)
|
|
|
+ after = resolve_function(read_attribute(model, transition, "after"), data)
|
|
|
+ dict_add(data["current_class_handle"]["timers"], transition, float_addition(data["time_sim"], after(data["current_class_handle"]["attributes"])))
|
|
|
+
|
|
|
+ return !
|
|
|
+
|
|
|
+Float function step(model : Element, data : Element):
|
|
|
+ // Step through all classes
|
|
|
+ Element classes
|
|
|
+ Element class
|
|
|
+ Float t_min
|
|
|
+ Float t_current
|
|
|
+ Boolean transitioned
|
|
|
+ Element keys
|
|
|
+ String key
|
|
|
+
|
|
|
+ t_min = 999999.0
|
|
|
+ classes = dict_keys(data["classes"])
|
|
|
+
|
|
|
+ transitioned = False
|
|
|
+ while (set_len(classes) > 0):
|
|
|
+ class = set_pop(classes)
|
|
|
+ if (step_class(model, data, class)):
|
|
|
+ transitioned = True
|
|
|
+
|
|
|
+ if (bool_not(transitioned)):
|
|
|
+ // Find minimum timer for this class, and store that
|
|
|
+ keys = dict_keys(data["classes"][class]["timers"])
|
|
|
+ while (set_len(keys) > 0):
|
|
|
+ key = set_pop(keys)
|
|
|
+ t_current = data["classes"][class]["timers"][key]
|
|
|
+ if (t_current < t_min):
|
|
|
+ t_min = t_current
|
|
|
+
|
|
|
+ if (transitioned):
|
|
|
+ // Do another step, as we can transition
|
|
|
+ return data["time_sim"]!
|
|
|
+ else:
|
|
|
+ return t_min!
|
|
|
+
|
|
|
+Boolean function main(model : Element):
|
|
|
+ // Executes the provided SCCD model
|
|
|
+ Element data
|
|
|
+ data = dict_create()
|
|
|
+ dict_add(data, "classes", dict_create())
|
|
|
+ dict_add(data, "cache_operations", dict_create())
|
|
|
+ dict_add(data, "cache_hierarchy", dict_create())
|
|
|
+ dict_add(data, "current_class", "")
|
|
|
+
|
|
|
+ Float time_0
|
|
|
+ Float time_sim
|
|
|
+ Float time_wallclock
|
|
|
+ time_0 = time()
|
|
|
+ time_sim = 0.0
|
|
|
+ dict_add(data, "time_sim", 0.0)
|
|
|
+
|
|
|
+ // Prepare for input
|
|
|
+ output("Ready for input!")
|
|
|
+
|
|
|
+ // Find initial
|
|
|
+ String default_class
|
|
|
+ default_class = set_pop(filter(model, allInstances(model, "SCCD/Class"), "default", True))
|
|
|
+
|
|
|
+ // Start up the default class
|
|
|
+ String identifier
|
|
|
+ identifier = start_class(model, data, default_class, read_root())
|
|
|
+
|
|
|
+ // Notify this class itself of its ID
|
|
|
+ Element lst
|
|
|
+ lst = list_create()
|
|
|
+ list_append(lst, identifier)
|
|
|
+ set_add_node(data["current_class_handle"]["new_events"], create_tuple("instance_created", lst))
|
|
|
+
|
|
|
+ Float timeout
|
|
|
+ Element interrupt
|
|
|
+ timeout = 0.0
|
|
|
+ while (True):
|
|
|
+ if (afap):
|
|
|
+ timeout = 0.0
|
|
|
+ interrupt = input_timeout(timeout)
|
|
|
+
|
|
|
+ if (value_eq(interrupt, "#EXIT#")):
|
|
|
+ // Stop execution
|
|
|
+ return True!
|
|
|
+
|
|
|
+ if (element_neq(interrupt, read_root())):
|
|
|
+ // Send out, as otherwise the client doesn't get a dialog
|
|
|
+ output("Processed event, ready for more!")
|
|
|
+
|
|
|
+ // Update the simulated time to the time of interrupt
|
|
|
+ time_sim = time() - time_0
|
|
|
+
|
|
|
+ Element classes
|
|
|
+ classes = dict_keys(data["classes"])
|
|
|
+ while(set_len(classes) > 0):
|
|
|
+ String class
|
|
|
+ class = set_pop(classes)
|
|
|
+ dict_overwrite(data["classes"][class], "events", data["classes"][class]["new_events"])
|
|
|
+ dict_overwrite(data["classes"][class], "new_events", set_create())
|
|
|
+
|
|
|
+ if (element_neq(interrupt, read_root())):
|
|
|
+ // Got interrupt, so append it already
|
|
|
+ set_add_node(data["classes"][class]["events"], create_tuple(interrupt, read_root()))
|
|
|
+
|
|
|
+ // Else we timeout, and thus keep the time_sim
|
|
|
+ dict_overwrite(data, "time_sim", time_sim)
|
|
|
+
|
|
|
+ time_sim = step(model, data)
|
|
|
+
|
|
|
+ if (float_gt(time_sim, data["time_sim"])):
|
|
|
+ print_states(model, data)
|
|
|
+
|
|
|
+ if (dict_len(data["classes"]) == 0):
|
|
|
+ // No more active classes left: terminate!
|
|
|
+ log("Finished SCCD execution")
|
|
|
+ break!
|
|
|
+
|
|
|
+ time_wallclock = time() - time_0
|
|
|
+ timeout = time_sim - time_wallclock
|
|
|
+
|
|
|
+ // We have finished without error
|
|
|
+ return True!
|