123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995 |
- 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
-
- def accept(self, visitor):
- for expression_part in self.expression_parts :
- expression_part.accept(visitor)
- 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
-
- @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
- target_string = self.xml.get("target","").strip()
- if target_string == "" :
- raise CompilerException("Transition from <" + self.parent_node.full_name + "> has empty target.")
- self.target = StateReference(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
-
- 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 <onentry> 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 <onexit> 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
-
- 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 needs one and only one body element.")
- self.body = bodies[0].text
- self.parent_class = parent_class
- self.return_type = xml.get('type',"")
-
- def getParams(self):
- return self.parameters
-
- ###################################
- class Constructor(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 Destructor(Method):
- def __init__(self, xml, parent_class):
- 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):
- self.xml = xml
- self.name = xml.get("name", "")
-
- self.constructors = []
- self.destructors = []
- self.methods = []
- self.statechart = None
- self.inports = []
- self.outports = []
- self.attributes = []
- self.associations = []
- self.super_classes = []
-
- self.process()
-
- def getName(self):
- return self.name
-
- 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 + ">.")
- self.methods.append( Method(method_xml, self))
- 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(a[1], b[1]))
- 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) + ">. Given inheritance order is chosen.")
-
- 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)
- attributes = self.xml.findall("attribute")
- for a in attributes:
- self.processAttribute(a)
- methods = self.xml.findall("method")
- for m in methods:
- self.processMethod(m)
-
- 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))
- 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)
- 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")
- 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 <top> 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)
- 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]
|