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!