import abc import re import xml.etree.ElementTree as ET import os.path from utils import Logger from visitor import Visitable from compiler_exceptions import CompilerException, TransitionException, UnprocessedException from lexer import Lexer, Token, TokenType # http://docs.python.org/2/library/xml.etree.elementtree.html # list of reserved words reserved = ["__init__", "__del__", "init", "transition", "microstep", "step", "inState", "event", "addEvent", "broadcast", "getEarliestEvent", "__str__", "controller", "current_state", "timers", "eventQueue", "Controller", "state_changed", "history_state", "root", "narrowcast", "object_manager", "update"] SELF_REFERENCE_SEQ = 'SELF' INSTATE_SEQ = 'INSTATE' ################################## class StateReference(Visitable): def __init__(self, input_string): self.path_string = input_string self.target_nodes = None #calculated in state linker def getNodes(self): #if no target nodes are set, it means that the visitor corresponding for that hasn't visited yet if self.target_nodes is None: raise UnprocessedException("State reference not resolved yet.") return self.target_nodes ################################## class ExpressionPart(Visitable): __metaclass__ = abc.ABCMeta class ExpressionPartString(ExpressionPart): def __init__(self, string): self.string = string class SelfReference(ExpressionPart): pass class InStateCall(ExpressionPart): def __init__(self, state_string): self.target = StateReference(state_string) ################################## class Expression(Visitable): lexer = Lexer(False, True) def __init__(self, input): if not input : raise CompilerException("Empty Expression.") self.parse(input) def parse(self, input, dont_parse = []): self.expression_parts = [] self.lexer.input(input) processed_bare_expression = "" for token in self.lexer.tokens() : created_object = None if token.type == TokenType.WORD : if token.val in dont_parse : raise CompilerException("Macro \"" + token.val + "\" not allowed here.") elif token.val == SELF_REFERENCE_SEQ : created_object = SelfReference() elif token.val == INSTATE_SEQ : created_object = self.parseInStateCall() if created_object is None : raise CompilerException("Illegal use of \"" + INSTATE_SEQ + "\" macro.") if created_object is None: processed_bare_expression += token.val else : if processed_bare_expression != "" : self.expression_parts.append(ExpressionPartString(processed_bare_expression)) processed_bare_expression = "" self.expression_parts.append(created_object) #Process part of input after the last created macro object if processed_bare_expression != "" : self.expression_parts.append(ExpressionPartString(processed_bare_expression)) def parseInStateCall(self): token = self.lexer.nextToken() if token is None or token.type != TokenType.LBRACKET : return None token = self.lexer.nextToken() if token is None or token.type != TokenType.QUOTED : return None else : created_object = InStateCall(token.val[1:-1]) token = self.lexer.nextToken() if token is None or token.type != TokenType.RBRACKET : return None else : return created_object class LValue(Expression): def __init__(self, input): if not input : raise CompilerException("Empty LValue.") self.parse(input, [INSTATE_SEQ]) #do some validation, provide parameters to processString to make the function more efficient ################################## class FormalEventParameter(Visitable): def __init__(self, name, ptype = ""): self.name = name self.type = ptype def getName(self): return self.name def getType(self): return self.type ################################## class TriggerEvent: def __init__(self, xml_element): self.is_uc = False; self.is_after = False self.after_index = -1 self.params = [] self.event = xml_element.get("event", "").strip() self.after = xml_element.get("after", "").strip() self.port = xml_element.get("port", "").strip() if self.event and self.after : raise CompilerException("Cannot have both the event and after attribute set for a transition.") if not self.event and self.port: raise CompilerException("A transition without event can not have a port.") if self.after : if self.port : raise CompilerException("After event can not have a port.") self.is_after = True self.after = Expression(self.after) return elif not self.event : self.is_uc = True return self.params = [] parameters = xml_element.findall('parameter') for p in parameters : name = p.get("name","") if not name : raise CompilerException("Parameter without name detected.") self.params.append(FormalEventParameter(name, p.get("type",""))) def getEvent(self): return self.event def setEvent(self, event): self.event = event def getParameters(self): return self.params def getPort(self): return self.port def isUC(self): return self.is_uc; def isAfter(self): return self.is_after def getAfterIndex(self): return self.after_index def setAfterIndex(self, after): self.after_index = after ################################## class SubAction(Visitable): __metaclass__ = abc.ABCMeta @abc.abstractmethod def check(self): pass @classmethod def create(cls, xml_element): for subcls in cls.__subclasses__(): tag = xml_element.tag.lower() if subcls.check(tag): return subcls(xml_element) raise CompilerException("Invalid subaction.") ################################## """ Is a possible subaction; generates an event. """ class RaiseEvent(SubAction): tag = "raise" LOCAL_SCOPE = 1 BROAD_SCOPE = 2 OUTPUT_SCOPE = 3 NARROW_SCOPE = 4 CD_SCOPE = 5 def __init__(self, xml_element): self.event = xml_element.get("event","").strip() scope_string = xml_element.get("scope","").strip().lower() self.target = xml_element.get("target","").strip() self.port = xml_element.get("port","").strip() if scope_string == "local" : self.scope = self.LOCAL_SCOPE elif scope_string == "broad" : self.scope = self.BROAD_SCOPE elif scope_string == "output" : self.scope = self.OUTPUT_SCOPE elif scope_string == "narrow" : self.scope = self.NARROW_SCOPE elif scope_string == "cd" : self.scope = self.CD_SCOPE elif scope_string == "" : #Calculate scope depending on present attributes if self.target and self.port : raise CompilerException("Both target and port attribute detected without a scope defined.") elif self.port : self.scope = self.OUTPUT_SCOPE elif self.target : self.scope = self.NARROW_SCOPE else : self.scope = self.LOCAL_SCOPE else : raise CompilerException("Illegal scope attribute; needs to be one of the following : local, broad, narrow, output, cd or nothing."); if self.scope == self.LOCAL_SCOPE or self.scope == self.BROAD_SCOPE or self.scope == self.CD_SCOPE: if self.target : Logger.showWarning("Raise event target detected, not matching with scope. Ignored.") self.target = "" if self.port : Logger.showWarning("Raise event port detected, not matching with scope. Ignored.") self.port = "" if self.scope == self.NARROW_SCOPE and self.port : Logger.showWarning("Raise event port detected, not matching with scope. Ignored.") self.port = "" if self.scope == self.OUTPUT_SCOPE and self.target : Logger.showWarning("Raise event target detected, not matching with scope. Ignored.") self.target = "" self.params = [] parameters = xml_element.findall('parameter') for p in parameters : value = p.get("expr","") if not value : raise CompilerException("Parameter without value detected.") self.params.append(Expression(value)) @staticmethod def check(tag): return tag == RaiseEvent.tag def getPort(self): return self.port def isLocal(self): return self.scope == self.LOCAL_SCOPE def isNarrow(self): return self.scope == self.NARROW_SCOPE def isBroad(self): return self.scope == self.BROAD_SCOPE def isOutput(self): return self.scope == self.OUTPUT_SCOPE def isCD(self): return self.scope == self.CD_SCOPE def getTarget(self): return self.target def getEventName(self): return self.event def getParameters(self): return self.params def getScope(self): return self.scope class Script(SubAction): tag = "script" def __init__(self, xml_element): self.code = xml_element.text if xml_element.text else "" @staticmethod def check(tag): return tag == Script.tag class Log(SubAction): tag = "log" def __init__(self, xml_element): self.message = xml_element.text.strip() @staticmethod def check(tag): return tag == Log.tag class Assign(SubAction): tag = "assign" def __init__(self, xml_element): self.lvalue = LValue(xml_element.get("ident","")) self.expression = Expression(xml_element.get("expr","")) @staticmethod def check(tag): return tag == Assign.tag ################################## """ Exists out of multiple subactions """ class Action(Visitable): def __init__(self, xml_element): self.sub_actions = [] for subaction in list(xml_element) : if subaction.tag not in ["parameter"] : self.sub_actions.append(SubAction.create(subaction)) def accept(self, visitor): for subaction in self.sub_actions : subaction.accept(visitor) ################################## class StateChartTransition(Visitable): def __init__(self,xml_element,parent): self.xml = xml_element self.parent_node = parent self.trigger = TriggerEvent(self.xml) guard_string = self.xml.get("cond","").strip() if guard_string != "" : self.guard = Expression(guard_string) else : self.guard = None self.target_string = self.xml.get("target","").strip() if self.target_string == "" : raise CompilerException("Transition from <" + self.parent_node.full_name + "> has empty target.") self.target = StateReference(self.target_string) self.action = Action(self.xml) self.enter_nodes = None # Ordered list of nodes to be entered upon taking the transition, set by the path calculator self.exit_nodes = None # Ordered list of nodes to be exited upon taking the transition, set by the path calculator self.arena = None # Lowest common Or-state ancestor of source and destination def getEnterNodes(self): if self.enter_nodes is None : raise UnprocessedException("Enter path not calculated yet.") return self.enter_nodes def getExitNodes(self): if self.exit_nodes is None : raise UnprocessedException("Exit path not calculated yet.") return self.exit_nodes def isUCTransition(self): """ Returns true iff is an unconditional transition (i.e. no trigger) """ return self.trigger.isUC() def getParentNode(self): return self.parent_node def getTrigger(self): return self.trigger def getGuard(self): return self.guard def getTargetNodes(self): return self.target.getNodes() def hasGuard(self): return self.guard != None def getAction(self): return self.action ################################## class EnterExitAction(Visitable): def __init__(self, parent_node, xml_element = None): self.parent_node = parent_node if xml_element is not None: self.action = Action(xml_element) else : self.action = None class EnterAction(EnterExitAction): def __init__(self, parent_node, xml_element = None): EnterExitAction.__init__(self, parent_node, xml_element) class ExitAction(EnterExitAction): def __init__(self, parent_node, xml_element = None): EnterExitAction.__init__(self, parent_node, xml_element) ################################## class StateChartNode(Visitable): def __init__(self, statechart, xml_element, parent = None): self.statechart = statechart self.parent = parent self.children = [] self.is_root = False self.is_basic = False self.is_composite = False self.is_history = False self.is_history_deep = False self.is_parallel_state = False self.save_state_on_exit = False if xml_element.tag == "scxml" : self.is_root = True self.is_composite = True elif xml_element.tag == "parallel" : self.is_composite = True self.is_parallel_state = True elif xml_element.tag == "state" : if len(xml_element.findall("state")) > 0 or (len(xml_element.findall("parallel")) > 0) : self.is_composite = True else : self.is_basic = True if self.parent.is_parallel_state : if (self.is_basic) : raise CompilerException("Orthogonal nodes (nodes that are immediate children of parallel nodes) can't be basic.") elif xml_element.tag == "history" : history_type = xml_element.get("type","shallow") if history_type == "deep" : self.is_history_deep = True elif history_type != "shallow" : raise CompilerException("Invalid history type.") self.is_history = True else : return self.resolveName(xml_element) #self.parseConflictAttribute(xml_element) self.parseEnterActions(xml_element) self.parseExitActions(xml_element) #transitions self.transitions = [] for transition_xml in xml_element.findall("transition"): self.transitions.append(StateChartTransition(transition_xml,self)) self.optimizeTransitions() self.generateChildren(xml_element) self.calculateDefaults(xml_element) def resolveName(self, xml): if self.is_root : self.name = "Root" self.full_name = "Root" else : self.name = xml.get("id","") self.full_name = self.parent.full_name + "_" + self.name """ def parseConflictAttribute(self, xml): conflict = xml.get("conflict","") if conflict == "outer" : self.solves_conflict_outer = True elif conflict == "inner" : self.solves_conflict_outer = False else : if not (conflict == "" or conflict == "inherit") : raise CompilerException("Unknown conflict attribute for " + self.full_name + ".") #Do our default inherit action if self.is_root or self.parent.solves_conflict_outer: self.solves_conflict_outer = True else : self.solves_conflict_outer = False """ def parseEnterActions(self, xml): on_entries = xml.findall("onentry") if on_entries : if len(on_entries) > 1: raise CompilerException("Multiple tags detected for "+ self.full_name + ", only 1 allowed.") self.enter_action = EnterAction(self, on_entries[0]) else : self.enter_action = EnterAction(self) def parseExitActions(self, xml): on_exits = xml.findall("onexit") if on_exits : if len(on_exits) > 1: raise CompilerException("Multiple tags detected for "+ self.full_name + ", only 1 allowed.") self.exit_action = ExitAction(self, on_exits[0]) else : self.exit_action = ExitAction(self) def optimizeTransitions(self): """If a transition with no trigger and no guard is found then it is considered as the only transition. Otherwise the list is ordered by placing transitions having guards only first.""" onlyguards = [] withtriggers = [] optimized = [] for transition in self.transitions: if transition.isUCTransition(): if not transition.hasGuard(): if optimized : raise TransitionException("More than one transition found at a single node, that has no trigger and no guard.") optimized.append(transition) else: onlyguards.append(transition) else: withtriggers.append(transition) if not optimized : optimized = onlyguards + withtriggers self.transitions = optimized def generateChildren(self, xml): children_names = [] for child_xml in list(xml) : child = StateChartNode(self.statechart, child_xml, self) if not (child.is_composite or child.is_basic or child.is_history) : continue self.children.append(child) #Check if the name of the child is valid child_name = child.name if child_name == "" : raise CompilerException("Found state with no id") if child_name in children_names : raise CompilerException("Found 2 equivalent id's : " + child_name + ".") children_names.append(child_name) def calculateDefaults(self, xml): initial_state = xml.get("initial","") if self.is_parallel_state : self.defaults = [child for child in self.children if not child.is_history] if initial_state != "" : raise CompilerException("Component <" + self.full_name + "> contains an initial state while being parallel.") elif initial_state == "" : if self.is_basic or self.is_history: pass elif len(self.children) == 1 : self.defaults = self.children else : raise CompilerException("Component <" + self.full_name + "> contains no default state.") else : if self.is_basic : raise CompilerException("Component <" + self.full_name + "> contains a default state while being a basic state.") self.defaults = [] for child in self.children : if child.name == initial_state : self.defaults.append(child) if len(self.defaults) < 1 : raise CompilerException("Initial state '"+ initial_state + "' referred to, is missing in " + self.full_name) elif len(self.defaults) > 1 : raise CompilerException("Multiple states with the name '" + initial_state + " found in " + self.full_name + " which is referred to as initial state.") def getAncestors(self): """ Returns a list representing the containment hierarchy of node. node is always the first element, and its outermost parent is the last. """ current = self while not current.is_root : current = current.parent yield current def isDescendantOf(self, anc): current = self while not current.is_root : current = current.parent if current == anc : return True return False def isDescendantOrAncestorOf(self, node): return self.isDescendantOf(node) or node.isDescendantOf(self) ################################## class StateChart(Visitable): def __init__(self, class_obj, statechart_xml): """ Gives the module information on the statechart by listing its basic, orthogonal, composite and history states as well as mapping triggers to names making the appropriate conversion from AFTER() triggers to event names """ self.class_obj = class_obj self.root = StateChartNode(self, statechart_xml); #creates the whole statechart structure recursively self.basics = [] self.composites = [] self.histories = [] self.nr_of_after_transitions = 0 def getSemanticOption(name, allowed_values, default_value): result = statechart_xml.get(name, default_value) if result not in allowed_values: raise CompilerException("Illegal value for semantic option " + name + ": '" + result + "'. Allowed values are ['" + "', '".join(allowed_values) + "'], default value is '" + default_value + "'.") return result self.big_step_maximality = getSemanticOption("big_step_maximality", ["take_one", "take_many"], "take_many") self.internal_event_lifeline = getSemanticOption("internal_event_lifeline", ["next_small_step", "next_combo_step", "queue"], "queue") self.input_event_lifeline = getSemanticOption("input_event_lifeline", ["first_small_step", "first_combo_step", "whole"], "first_combo_step") self.priority = getSemanticOption("priority", ["source_parent", "source_child"], "source_parent") self.concurrency = getSemanticOption("concurrency", ["single", "many"], "single") if self.internal_event_lifeline == "next_combo_step": if self.big_step_maximality == "take_one": Logger.showWarning("Using 'Next Combo Step' internal event lifeline semantics and 'Take One' big step maximality semantics simultaneously doesn't make sense.") self.extractFromHierarchy(self.root) #recursively extracts the basics, composites, histories and nr_of_after_transitions # Calculate the history that needs to be taken care of. self.shallow_history_parents = [] self.deep_history_parents = [] self.combined_history_parents = [] #All nodes that need state saved on leaving for node in self.histories: self.calculateHistory(node.parent, node.is_history_deep) def extractFromHierarchy(self, node): # For each AFTER event, give it a name so that it can be triggered. for transition in node.transitions: trigger = transition.trigger if trigger.isAfter() : trigger.setAfterIndex(self.nr_of_after_transitions) value = "_" + str(trigger.getAfterIndex()) + "after" trigger.setEvent(value) self.nr_of_after_transitions += 1 if node.is_basic : self.basics.append(node) elif node.is_composite : self.composites.append(node) elif node.is_history : self.histories.append(node) for child in node.children : self.extractFromHierarchy(child) def calculateHistory(self, parent, is_deep): """ Figures out which components need to be kept track of for history. """ if parent == self.root: raise CompilerException("Root component cannot contain a history state.") if parent not in self.combined_history_parents: self.combined_history_parents.append(parent) parent.save_state_on_exit = True if is_deep : if parent not in self.deep_history_parents: self.deep_history_parents.append(parent) else : if parent not in self.shallow_history_parents: self.shallow_history_parents.append(parent) if parent.is_parallel_state or is_deep : for i in parent.children: if i.is_composite : self.calculateHistory(i, is_deep) ################################### class Association(Visitable): def __init__(self, to_class, min_card, max_card, name): self.min = min_card self.max = max_card #N is represented as -1 self.to_class = to_class self.name = name ################################### class FormalParameter(Visitable): def __init__(self, param_ident, param_type, default = None): self.param_type = param_type self.identifier = param_ident self.default = default def getType(self): return self.param_type def getIdent(self): return self.identifier def hasDefault(self): return self.default is not None def getDefault(self): return self.default #slight hack because of lacking multiple constructors class XMLFormalParameter(FormalParameter): def __init__(self, xml): self.param_type = xml.get("type", "") self.identifier = xml.get("name","") self.default = xml.get("default",None) ################################### class Method(Visitable): def __init__(self, xml, parent_class): self.name = xml.get("name", "") self.access = xml.get("access", "public") parameters = xml.findall("parameter") self.parameters = [] for p in parameters: self.parameters.append(XMLFormalParameter(p)) bodies = xml.findall("body") if len(bodies) > 1 : raise CompilerException("Method can have at most one body.") elif len(bodies) == 1: self.body = bodies[0].text else: self.body = "" self.parent_class = parent_class self.return_type = xml.get('type',"") self.is_abstract = xml.get('abstract', False) def getParams(self): return self.parameters def isAbstract(self): return self.is_abstract ################################### class Constructor(Method): def __init__(self, xml, parent_class): self.super_class_parameters = {}; if xml is None : self.body = "" self.name = "" self.access = "public" self.parent_class = parent_class self.return_type = "" self.parameters = [] else : Method.__init__(self, xml, parent_class) super_class_parameters = xml.findall("super") for s in super_class_parameters: class_name = s.get("class") self.super_class_parameters[class_name] = [] params = s.findall("parameter") for p in params: self.super_class_parameters[class_name].append(p.get("expr")) class Destructor(Method): def __init__(self, xml, parent_class): if xml is None : self.body = "" self.name = "" self.access = "public" self.parent_class = parent_class self.return_type = "" self.parameters = [] else : Method.__init__(self, xml, parent_class) ################################### class Attribute(Visitable): def __init__(self, xml): self.name = xml.get('name',"") self.type = xml.get('type',"") self.init_value = xml.get("init-value", None) def getIdent(self): return self.name def getType(self): return self.type def getInit(self): return self.init_value ################################### class Class(Visitable): def __init__(self, xml, class_diagram): self.xml = xml self.class_diagram = class_diagram self.name = xml.get("name", "") self.abstract_method_names = [] # this value will be written to by super_class_linker self.constructors = [] self.destructors = [] self.methods = [] self.statechart = None self.inports = [] self.outports = [] self.attributes = [] self.associations = [] self.super_classes = [] self.super_class_objs = {} # maps super class names to super class objects self.process() def getName(self): return self.name def isAbstract(self): return len(self.abstract_method_names) > 0 def processMethod(self, method_xml) : name = method_xml.get("name", "") if name == self.name : self.constructors.append(Constructor(method_xml, self)) elif name == '~' + self.name: self.destructors.append(Destructor(method_xml, self)) else : if name in reserved: raise CompilerException("Reserved word \"" + name + "\" used as method in class <" + self.name + ">.") new_method = Method(method_xml, self) self.methods.append(new_method) def processAttribute(self, attribute_xml): attribute = Attribute(attribute_xml) if attribute.name in reserved: raise CompilerException("Reserved word \"" + attribute.name + "\" used as variable in class <" + self.name + ">.") self.attributes.append(attribute) def processInheritances(self, inheritances): # process each inheritance, stores a dict with each subclass as the key # and a list of tuples (superclass, priority) as the value. The priority # tells us which class to inherit from first for multiple inheritance. Gives # a WARNING with a given inheritance order if two priorities are the same for i in inheritances : self.super_classes.append((i.get("class",""),i.get("priority",1))) self.super_classes.sort(lambda a, b: cmp(b[1], a[1])) # sort from high priority to low priority priorityChecker = {} for super_class, priority in self.super_classes: if priority in priorityChecker: checkIt = priorityChecker[priority] else: checkIt = [] if super_class not in checkIt: checkIt.append(super_class) priorityChecker[priority] = checkIt for priority, checkIt in priorityChecker.iteritems(): if len(checkIt) > 1: Logger.showWarning("Class <" + self.name + "> inherits from classes <" + ", ".join(checkIt) + "> with same priority <" + str(priority) + ">. Document inheritance order is used.") self.super_classes = [entry[0] for entry in self.super_classes] def processAssociations(self, associations): for a in associations : class_name = a.get("class","") if not class_name : raise CompilerException("Faulty association.") card_min_string = a.get("min","0") try : card_min = int(card_min_string) if card_min < 0 : raise ValueError() except ValueError : raise CompilerException("Faulty card-min value in association.") card_max_string = a.get("max","N") if card_max_string == "N" : card_max = -1 else : try : card_max = int(card_max_string) if card_max < card_min : raise ValueError() except ValueError : raise CompilerException("Faulty card-max value in association.") association_name = a.get("name","") if not association_name : raise CompilerException("Faulty association. No name.") if association_name in reserved : raise CompilerException("Reserved word \"" + association_name + "\" used as association name in class <" + self.name + ">.") self.associations.append( Association(class_name, card_min, card_max, association_name) ) def process(self): inports = self.xml.findall("inport") for i in inports: name = i.get("name") if name in self.inports: raise CompilerException("Found 2 inports with the same name : " + name + ".") self.inports.append(name) outports = self.xml.findall("outport") for i in outports: name = i.get("name") if name in self.outports: raise CompilerException("Found 2 outports with the same name : " + name + ".") self.outports.append(name) associations = [] inheritances = [] relationships = self.xml.findall("relationships") for relationship_wrapper in relationships : associations.extend(relationship_wrapper.findall("association")) inheritances.extend(relationship_wrapper.findall("inheritance")) self.processAssociations(associations) self.processInheritances(inheritances) attributes = self.xml.findall("attribute") for a in attributes: self.processAttribute(a) methods = self.xml.findall("method") for m in methods: self.processMethod(m) constructors = self.xml.findall("constructor") for c in constructors: self.constructors.append(Constructor(c, self)) destructors = self.xml.findall("destructor") for d in destructors: self.destructors.append(Destructor(d, self)) if len(self.constructors) > 1 : raise CompilerException("Multiple constructors no longer supported!") if len(self.destructors) > 1 : raise CompilerException("Multiple destructors defined for class <" + self.name + ">.") if len(self.constructors) < 1 : # add a default constructor self.constructors.append(Constructor(None,self)) if len(self.destructors) < 1 : # add a default destructor self.destructors.append(Destructor(None,self)) statecharts = self.xml.findall("scxml") if len(statecharts) > 1 : raise CompilerException("Multiple statecharts found in class <" + self.name + ">.") if len(statecharts) == 1 : self.statechart = StateChart(self, statecharts[0]) ################################### class ClassDiagram(Visitable): def __init__(self, input_file): diagram_dir = os.path.dirname(input_file) tree = ET.parse(input_file) self.root = tree.getroot() self.name = self.root.get("name", "") self.author = self.root.get("author", "") descriptions = self.root.findall("description") self.language = self.root.get("language", "") if descriptions : self.description = descriptions[0].text else : self.description = "" xml_classes = self.root.findall("class") # make sure at least one class is given if not xml_classes : raise CompilerException("Found no classes to compile.") # check if class diagram is valid # unique class names self.class_names = [] substituted_xml_classes = [] for xml_class in xml_classes : class_src = xml_class.get("src", "") class_default = xml_class.get("default", "") if class_src != "": if not os.path.isabs(class_src): class_src = os.path.join(diagram_dir, class_src) substituted_xml_class = ET.parse(class_src).getroot() else: substituted_xml_class = xml_class substituted_xml_class.is_default = (class_default.lower() == "true") name = substituted_xml_class.get("name", "") if name == "" : raise CompilerException("Missing or emtpy class name.") if name in self.class_names : raise CompilerException("Found 2 classes with the same name : " + name + ".") self.class_names.append(name) substituted_xml_classes.append(substituted_xml_class) # process in and output ports inports = self.root.findall("inport") names = [] for xml_inport in inports : name = xml_inport.get("name", "") if name in names : raise CompilerException("Found 2 INPorts with the same name : " + name + ".") names.append(name) self.inports = names outports = self.root.findall("outport") names = [] for xml_outport in outports : name = xml_outport.get("name", "") if name in names : raise CompilerException("Found 2 OUTPorts with the same name : " + name + ".") names.append(name) self.outports = names # any inital import code that has to come at the top of the generate file tops = self.root.findall("top") self.includes = [] if len(tops) == 1 : self.top = tops[0].text elif len(tops) > 1 : raise CompilerException("Class diagram can only have one element.") else : self.top = "" # process each class in diagram self.classes = [] default_classes = [] for xml_class in substituted_xml_classes: processed_class = None try : processed_class = Class(xml_class, self) except CompilerException as e : e.message = "Class <" + xml_class.get("name", "") + "> failed compilation. " + e.message raise e # let user know this class was successfully loaded Logger.showInfo("Class <" + processed_class.name + "> has been successfully loaded.") self.classes.append(processed_class) if xml_class.is_default : default_classes.append(processed_class) if not default_classes or len(default_classes) > 1: if len(self.classes) == 1 : Logger.showInfo("Only one class given. Using <" + self.classes[0].getName() + "> as the default class.") default_classes.append(self.classes[0]) else : raise CompilerException("Provide one and only one default class to instantiate on start up.") self.default_class = default_classes[0] # check if there's a test self.test = None test_nodes = self.root.findall("test") if test_nodes: test_node = test_nodes[0] input_nodes = test_node.findall("input") if input_nodes: input_node = input_nodes[0] test_input = DiagramTestInput(input_node) else: test_input = None expected_nodes = test_node.findall("expected") if expected_nodes: expected_node = expected_nodes[0] test_expected = DiagramTestExpected(expected_node) else: test_expected = None self.test = DiagramTest(test_input, test_expected) class DiagramTest(Visitable): def __init__(self, i, expected): self.input = i self.expected = expected class DiagramTestEvent(Visitable): def __init__(self, xml): self.name = xml.get("name") self.port = xml.get("port") self.parameters = [] parameter_nodes = xml.findall("parameter") for parameter_node in parameter_nodes: val = parameter_node.get("value") expr = parameter_node.get("expr") if val: self.parameters.append(val) # expected events use 'val' elif expr: self.parameters.append(expr) # input events use 'expr' else: raise CompilerException("Parameter has no value/expr.") class DiagramTestInputEvent(DiagramTestEvent): def __init__(self, xml): DiagramTestEvent.__init__(self, xml) self.time = xml.get("time") class DiagramTestInput(Visitable): def __init__(self, xml): self.input_events = [] event_nodes = xml.findall("event") for event_node in event_nodes: e = DiagramTestInputEvent(event_node) self.input_events.append(e) class DiagramTestExpectedSlot(Visitable): def __init__(self, xml): self.expected_events = [] event_nodes = xml.findall("event") for event_node in event_nodes: e = DiagramTestEvent(event_node) self.expected_events.append(e) class DiagramTestExpected(Visitable): def __init__(self, xml): self.slots = [] slot_nodes = xml.findall("slot") for slot_node in slot_nodes: s = DiagramTestExpectedSlot(slot_node) self.slots.append(s)