sccd_constructs.py 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132
  1. import abc
  2. import re
  3. import xml.etree.ElementTree as ET
  4. import os.path
  5. from utils import Logger
  6. from visitor import Visitable
  7. from compiler_exceptions import CompilerException, TransitionException, UnprocessedException
  8. from lexer import Lexer, Token, TokenType
  9. # http://docs.python.org/2/library/xml.etree.elementtree.html
  10. # list of reserved words
  11. reserved = ["__init__", "__del__", "init", "transition", "microstep", "step", "inState", "event", "addEvent",
  12. "broadcast", "getEarliestEvent", "__str__", "controller",
  13. "current_state", "timers", "eventQueue", "Controller", "state_changed", "history_state",
  14. "root", "narrowcast", "object_manager", "update"]
  15. SELF_REFERENCE_SEQ = 'SELF'
  16. INSTATE_SEQ = 'INSTATE'
  17. ##################################
  18. class StateReference(Visitable):
  19. def __init__(self, input_string):
  20. self.path_string = input_string
  21. self.target_nodes = None #calculated in state linker
  22. def getNodes(self):
  23. #if no target nodes are set, it means that the visitor corresponding for that hasn't visited yet
  24. if self.target_nodes is None:
  25. raise UnprocessedException("State reference not resolved yet.")
  26. return self.target_nodes
  27. ##################################
  28. class ExpressionPart(Visitable):
  29. __metaclass__ = abc.ABCMeta
  30. class ExpressionPartString(ExpressionPart):
  31. def __init__(self, string):
  32. self.string = string
  33. class SelfReference(ExpressionPart):
  34. pass
  35. class InStateCall(ExpressionPart):
  36. def __init__(self, state_string):
  37. self.target = StateReference(state_string)
  38. ##################################
  39. class Expression(Visitable):
  40. lexer = Lexer(False, True)
  41. def __init__(self, input):
  42. if not input :
  43. raise CompilerException("Empty Expression.")
  44. self.parse(input)
  45. def parse(self, input, dont_parse = []):
  46. self.expression_parts = []
  47. self.lexer.input(input)
  48. processed_bare_expression = ""
  49. for token in self.lexer.tokens() :
  50. created_object = None
  51. if token.type == TokenType.WORD :
  52. if token.val in dont_parse :
  53. raise CompilerException("Macro \"" + token.val + "\" not allowed here.")
  54. elif token.val == SELF_REFERENCE_SEQ :
  55. created_object = SelfReference()
  56. elif token.val == INSTATE_SEQ :
  57. created_object = self.parseInStateCall()
  58. if created_object is None :
  59. raise CompilerException("Illegal use of \"" + INSTATE_SEQ + "\" macro.")
  60. if created_object is None:
  61. processed_bare_expression += token.val
  62. else :
  63. if processed_bare_expression != "" :
  64. self.expression_parts.append(ExpressionPartString(processed_bare_expression))
  65. processed_bare_expression = ""
  66. self.expression_parts.append(created_object)
  67. #Process part of input after the last created macro object
  68. if processed_bare_expression != "" :
  69. self.expression_parts.append(ExpressionPartString(processed_bare_expression))
  70. def parseInStateCall(self):
  71. token = self.lexer.nextToken()
  72. if token is None or token.type != TokenType.LBRACKET :
  73. return None
  74. token = self.lexer.nextToken()
  75. if token is None or token.type != TokenType.QUOTED :
  76. return None
  77. else :
  78. created_object = InStateCall(token.val[1:-1])
  79. token = self.lexer.nextToken()
  80. if token is None or token.type != TokenType.RBRACKET :
  81. return None
  82. else :
  83. return created_object
  84. class LValue(Expression):
  85. def __init__(self, input):
  86. if not input :
  87. raise CompilerException("Empty LValue.")
  88. self.parse(input, [INSTATE_SEQ])
  89. #do some validation, provide parameters to processString to make the function more efficient
  90. ##################################
  91. class FormalEventParameter(Visitable):
  92. def __init__(self, name, ptype = ""):
  93. self.name = name
  94. self.type = ptype
  95. def getName(self):
  96. return self.name
  97. def getType(self):
  98. return self.type
  99. ##################################
  100. class TriggerEvent:
  101. def __init__(self, xml_element):
  102. self.is_uc = False;
  103. self.is_after = False
  104. self.after_index = -1
  105. self.params = []
  106. self.event = xml_element.get("event", "").strip()
  107. self.after = xml_element.get("after", "").strip()
  108. self.port = xml_element.get("port", "").strip()
  109. if self.event and self.after :
  110. raise CompilerException("Cannot have both the event and after attribute set for a transition.")
  111. if not self.event and self.port:
  112. raise CompilerException("A transition without event can not have a port.")
  113. if self.after :
  114. if self.port :
  115. raise CompilerException("After event can not have a port.")
  116. self.is_after = True
  117. self.after = Expression(self.after)
  118. return
  119. elif not self.event :
  120. self.is_uc = True
  121. return
  122. self.params = []
  123. parameters = xml_element.findall('parameter')
  124. for p in parameters :
  125. name = p.get("name","")
  126. if not name :
  127. raise CompilerException("Parameter without name detected.")
  128. self.params.append(FormalEventParameter(name, p.get("type","")))
  129. def getEvent(self):
  130. return self.event
  131. def setEvent(self, event):
  132. self.event = event
  133. def getParameters(self):
  134. return self.params
  135. def getPort(self):
  136. return self.port
  137. def isUC(self):
  138. return self.is_uc;
  139. def isAfter(self):
  140. return self.is_after
  141. def getAfterIndex(self):
  142. return self.after_index
  143. def setAfterIndex(self, after):
  144. self.after_index = after
  145. ##################################
  146. class SubAction(Visitable):
  147. __metaclass__ = abc.ABCMeta
  148. @abc.abstractmethod
  149. def check(self):
  150. pass
  151. @classmethod
  152. def create(cls, xml_element):
  153. for subcls in cls.__subclasses__():
  154. tag = xml_element.tag.lower()
  155. if subcls.check(tag):
  156. return subcls(xml_element)
  157. raise CompilerException("Invalid subaction.")
  158. ##################################
  159. """
  160. Is a possible subaction; generates an event.
  161. """
  162. class RaiseEvent(SubAction):
  163. tag = "raise"
  164. LOCAL_SCOPE = 1
  165. BROAD_SCOPE = 2
  166. OUTPUT_SCOPE = 3
  167. NARROW_SCOPE = 4
  168. CD_SCOPE = 5
  169. def __init__(self, xml_element):
  170. self.event = xml_element.get("event","").strip()
  171. scope_string = xml_element.get("scope","").strip().lower()
  172. self.target = xml_element.get("target","").strip()
  173. self.port = xml_element.get("port","").strip()
  174. if scope_string == "local" :
  175. self.scope = self.LOCAL_SCOPE
  176. elif scope_string == "broad" :
  177. self.scope = self.BROAD_SCOPE
  178. elif scope_string == "output" :
  179. self.scope = self.OUTPUT_SCOPE
  180. elif scope_string == "narrow" :
  181. self.scope = self.NARROW_SCOPE
  182. elif scope_string == "cd" :
  183. self.scope = self.CD_SCOPE
  184. elif scope_string == "" :
  185. #Calculate scope depending on present attributes
  186. if self.target and self.port :
  187. raise CompilerException("Both target and port attribute detected without a scope defined.")
  188. elif self.port :
  189. self.scope = self.OUTPUT_SCOPE
  190. elif self.target :
  191. self.scope = self.NARROW_SCOPE
  192. else :
  193. self.scope = self.LOCAL_SCOPE
  194. else :
  195. raise CompilerException("Illegal scope attribute; needs to be one of the following : local, broad, narrow, output, cd or nothing.");
  196. if self.scope == self.LOCAL_SCOPE or self.scope == self.BROAD_SCOPE or self.scope == self.CD_SCOPE:
  197. if self.target :
  198. Logger.showWarning("Raise event target detected, not matching with scope. Ignored.")
  199. self.target = ""
  200. if self.port :
  201. Logger.showWarning("Raise event port detected, not matching with scope. Ignored.")
  202. self.port = ""
  203. if self.scope == self.NARROW_SCOPE and self.port :
  204. Logger.showWarning("Raise event port detected, not matching with scope. Ignored.")
  205. self.port = ""
  206. if self.scope == self.OUTPUT_SCOPE and self.target :
  207. Logger.showWarning("Raise event target detected, not matching with scope. Ignored.")
  208. self.target = ""
  209. self.params = []
  210. parameters = xml_element.findall('parameter')
  211. for p in parameters :
  212. value = p.get("expr","")
  213. if not value :
  214. raise CompilerException("Parameter without value detected.")
  215. self.params.append(Expression(value))
  216. @staticmethod
  217. def check(tag):
  218. return tag == RaiseEvent.tag
  219. def getPort(self):
  220. return self.port
  221. def isLocal(self):
  222. return self.scope == self.LOCAL_SCOPE
  223. def isNarrow(self):
  224. return self.scope == self.NARROW_SCOPE
  225. def isBroad(self):
  226. return self.scope == self.BROAD_SCOPE
  227. def isOutput(self):
  228. return self.scope == self.OUTPUT_SCOPE
  229. def isCD(self):
  230. return self.scope == self.CD_SCOPE
  231. def getTarget(self):
  232. return self.target
  233. def getEventName(self):
  234. return self.event
  235. def getParameters(self):
  236. return self.params
  237. def getScope(self):
  238. return self.scope
  239. class Script(SubAction):
  240. tag = "script"
  241. def __init__(self, xml_element):
  242. self.code = xml_element.text if xml_element.text else ""
  243. @staticmethod
  244. def check(tag):
  245. return tag == Script.tag
  246. class Log(SubAction):
  247. tag = "log"
  248. def __init__(self, xml_element):
  249. self.message = xml_element.text.strip()
  250. @staticmethod
  251. def check(tag):
  252. return tag == Log.tag
  253. class Assign(SubAction):
  254. tag = "assign"
  255. def __init__(self, xml_element):
  256. self.lvalue = LValue(xml_element.get("ident",""))
  257. self.expression = Expression(xml_element.get("expr",""))
  258. @staticmethod
  259. def check(tag):
  260. return tag == Assign.tag
  261. ##################################
  262. """
  263. Exists out of multiple subactions
  264. """
  265. class Action(Visitable):
  266. def __init__(self, xml_element):
  267. self.sub_actions = []
  268. for subaction in list(xml_element) :
  269. if subaction.tag not in ["parameter"] :
  270. self.sub_actions.append(SubAction.create(subaction))
  271. def accept(self, visitor):
  272. for subaction in self.sub_actions :
  273. subaction.accept(visitor)
  274. ##################################
  275. class StateChartTransition(Visitable):
  276. def __init__(self,xml_element,parent):
  277. self.xml = xml_element
  278. self.parent_node = parent
  279. self.trigger = TriggerEvent(self.xml)
  280. guard_string = self.xml.get("cond","").strip()
  281. if guard_string != "" :
  282. self.guard = Expression(guard_string)
  283. else :
  284. self.guard = None
  285. self.target_string = self.xml.get("target","").strip()
  286. if self.target_string == "" :
  287. raise CompilerException("Transition from <" + self.parent_node.full_name + "> has empty target.")
  288. self.target = StateReference(self.target_string)
  289. self.action = Action(self.xml)
  290. self.enter_nodes = None # Ordered list of nodes to be entered upon taking the transition, set by the path calculator
  291. self.exit_nodes = None # Ordered list of nodes to be exited upon taking the transition, set by the path calculator
  292. self.arena = None # Lowest common Or-state ancestor of source and destination
  293. def getEnterNodes(self):
  294. if self.enter_nodes is None :
  295. raise UnprocessedException("Enter path not calculated yet.")
  296. return self.enter_nodes
  297. def getExitNodes(self):
  298. if self.exit_nodes is None :
  299. raise UnprocessedException("Exit path not calculated yet.")
  300. return self.exit_nodes
  301. def isUCTransition(self):
  302. """ Returns true iff is an unconditional transition (i.e. no trigger)
  303. """
  304. return self.trigger.isUC()
  305. def getParentNode(self):
  306. return self.parent_node
  307. def getTrigger(self):
  308. return self.trigger
  309. def getGuard(self):
  310. return self.guard
  311. def getTargetNodes(self):
  312. return self.target.getNodes()
  313. def hasGuard(self):
  314. return self.guard != None
  315. def getAction(self):
  316. return self.action
  317. ##################################
  318. class EnterExitAction(Visitable):
  319. def __init__(self, parent_node, xml_element = None):
  320. self.parent_node = parent_node
  321. if xml_element is not None:
  322. self.action = Action(xml_element)
  323. else :
  324. self.action = None
  325. class EnterAction(EnterExitAction):
  326. def __init__(self, parent_node, xml_element = None):
  327. EnterExitAction.__init__(self, parent_node, xml_element)
  328. class ExitAction(EnterExitAction):
  329. def __init__(self, parent_node, xml_element = None):
  330. EnterExitAction.__init__(self, parent_node, xml_element)
  331. ##################################
  332. class StateChartNode(Visitable):
  333. def __init__(self, statechart, xml_element, parent = None):
  334. self.statechart = statechart
  335. self.parent = parent
  336. self.children = []
  337. self.is_root = False
  338. self.is_basic = False
  339. self.is_composite = False
  340. self.is_history = False
  341. self.is_history_deep = False
  342. self.is_parallel_state = False
  343. self.save_state_on_exit = False
  344. if xml_element.tag == "scxml" :
  345. self.is_root = True
  346. self.is_composite = True
  347. elif xml_element.tag == "parallel" :
  348. self.is_composite = True
  349. self.is_parallel_state = True
  350. elif xml_element.tag == "state" :
  351. if len(xml_element.findall("state")) > 0 or (len(xml_element.findall("parallel")) > 0) :
  352. self.is_composite = True
  353. else :
  354. self.is_basic = True
  355. if self.parent.is_parallel_state :
  356. if (self.is_basic) :
  357. raise CompilerException("Orthogonal nodes (nodes that are immediate children of parallel nodes) can't be basic.")
  358. elif xml_element.tag == "history" :
  359. history_type = xml_element.get("type","shallow")
  360. if history_type == "deep" :
  361. self.is_history_deep = True
  362. elif history_type != "shallow" :
  363. raise CompilerException("Invalid history type.")
  364. self.is_history = True
  365. else :
  366. return
  367. self.resolveName(xml_element)
  368. #self.parseConflictAttribute(xml_element)
  369. self.parseEnterActions(xml_element)
  370. self.parseExitActions(xml_element)
  371. #transitions
  372. self.transitions = []
  373. for transition_xml in xml_element.findall("transition"):
  374. self.transitions.append(StateChartTransition(transition_xml,self))
  375. self.optimizeTransitions()
  376. self.generateChildren(xml_element)
  377. self.calculateDefaults(xml_element)
  378. def resolveName(self, xml):
  379. if self.is_root :
  380. self.name = "Root"
  381. self.full_name = "Root"
  382. else :
  383. self.name = xml.get("id","")
  384. self.full_name = self.parent.full_name + "_" + self.name
  385. """
  386. def parseConflictAttribute(self, xml):
  387. conflict = xml.get("conflict","")
  388. if conflict == "outer" :
  389. self.solves_conflict_outer = True
  390. elif conflict == "inner" :
  391. self.solves_conflict_outer = False
  392. else :
  393. if not (conflict == "" or conflict == "inherit") :
  394. raise CompilerException("Unknown conflict attribute for " + self.full_name + ".")
  395. #Do our default inherit action
  396. if self.is_root or self.parent.solves_conflict_outer:
  397. self.solves_conflict_outer = True
  398. else :
  399. self.solves_conflict_outer = False
  400. """
  401. def parseEnterActions(self, xml):
  402. on_entries = xml.findall("onentry")
  403. if on_entries :
  404. if len(on_entries) > 1:
  405. raise CompilerException("Multiple <onentry> tags detected for "+ self.full_name + ", only 1 allowed.")
  406. self.enter_action = EnterAction(self, on_entries[0])
  407. else :
  408. self.enter_action = EnterAction(self)
  409. def parseExitActions(self, xml):
  410. on_exits = xml.findall("onexit")
  411. if on_exits :
  412. if len(on_exits) > 1:
  413. raise CompilerException("Multiple <onexit> tags detected for "+ self.full_name + ", only 1 allowed.")
  414. self.exit_action = ExitAction(self, on_exits[0])
  415. else :
  416. self.exit_action = ExitAction(self)
  417. def optimizeTransitions(self):
  418. """If a transition with no trigger and no guard is found then it is considered as the only transition.
  419. Otherwise the list is ordered by placing transitions having guards only first."""
  420. onlyguards = []
  421. withtriggers = []
  422. optimized = []
  423. for transition in self.transitions:
  424. if transition.isUCTransition():
  425. if not transition.hasGuard():
  426. if optimized :
  427. raise TransitionException("More than one transition found at a single node, that has no trigger and no guard.")
  428. optimized.append(transition)
  429. else:
  430. onlyguards.append(transition)
  431. else:
  432. withtriggers.append(transition)
  433. if not optimized :
  434. optimized = onlyguards + withtriggers
  435. self.transitions = optimized
  436. def generateChildren(self, xml):
  437. children_names = []
  438. for child_xml in list(xml) :
  439. child = StateChartNode(self.statechart, child_xml, self)
  440. if not (child.is_composite or child.is_basic or child.is_history) :
  441. continue
  442. self.children.append(child)
  443. #Check if the name of the child is valid
  444. child_name = child.name
  445. if child_name == "" :
  446. raise CompilerException("Found state with no id")
  447. if child_name in children_names :
  448. raise CompilerException("Found 2 equivalent id's : " + child_name + ".")
  449. children_names.append(child_name)
  450. def calculateDefaults(self, xml):
  451. initial_state = xml.get("initial","")
  452. if self.is_parallel_state :
  453. self.defaults = [child for child in self.children if not child.is_history]
  454. if initial_state != "" :
  455. raise CompilerException("Component <" + self.full_name + "> contains an initial state while being parallel.")
  456. elif initial_state == "" :
  457. if self.is_basic or self.is_history:
  458. pass
  459. elif len(self.children) == 1 :
  460. self.defaults = self.children
  461. else :
  462. raise CompilerException("Component <" + self.full_name + "> contains no default state.")
  463. else :
  464. if self.is_basic :
  465. raise CompilerException("Component <" + self.full_name + "> contains a default state while being a basic state.")
  466. self.defaults = []
  467. for child in self.children :
  468. if child.name == initial_state :
  469. self.defaults.append(child)
  470. if len(self.defaults) < 1 :
  471. raise CompilerException("Initial state '"+ initial_state + "' referred to, is missing in " + self.full_name)
  472. elif len(self.defaults) > 1 :
  473. raise CompilerException("Multiple states with the name '" + initial_state + " found in " + self.full_name + " which is referred to as initial state.")
  474. def getAncestors(self):
  475. """ Returns a list representing the containment hierarchy of node.
  476. node is always the first element, and its outermost parent is the last.
  477. """
  478. current = self
  479. while not current.is_root :
  480. current = current.parent
  481. yield current
  482. def isDescendantOf(self, anc):
  483. current = self
  484. while not current.is_root :
  485. current = current.parent
  486. if current == anc :
  487. return True
  488. return False
  489. def isDescendantOrAncestorOf(self, node):
  490. return self.isDescendantOf(node) or node.isDescendantOf(self)
  491. ##################################
  492. class StateChart(Visitable):
  493. def __init__(self, class_obj, statechart_xml):
  494. """ Gives the module information on the statechart by listing its basic, orthogonal,
  495. composite and history states as well as mapping triggers to names making the
  496. appropriate conversion from AFTER() triggers to event names
  497. """
  498. self.class_obj = class_obj
  499. self.root = StateChartNode(self, statechart_xml); #creates the whole statechart structure recursively
  500. self.basics = []
  501. self.composites = []
  502. self.histories = []
  503. self.nr_of_after_transitions = 0
  504. def getSemanticOption(name, allowed_values, default_value):
  505. result = statechart_xml.get(name, default_value)
  506. if result not in allowed_values:
  507. raise CompilerException("Illegal value for semantic option " + name + ": '" + result + "'. Allowed values are ['" + "', '".join(allowed_values) + "'], default value is '" + default_value + "'.")
  508. return result
  509. self.big_step_maximality = getSemanticOption("big_step_maximality", ["take_one", "take_many"], "take_many")
  510. self.internal_event_lifeline = getSemanticOption("internal_event_lifeline", ["next_small_step", "next_combo_step", "queue"], "queue")
  511. self.input_event_lifeline = getSemanticOption("input_event_lifeline", ["first_small_step", "first_combo_step", "whole"], "first_combo_step")
  512. self.priority = getSemanticOption("priority", ["source_parent", "source_child"], "source_parent")
  513. self.concurrency = getSemanticOption("concurrency", ["single", "many"], "single")
  514. if self.internal_event_lifeline == "next_combo_step":
  515. if self.big_step_maximality == "take_one":
  516. Logger.showWarning("Using 'Next Combo Step' internal event lifeline semantics and 'Take One' big step maximality semantics simultaneously doesn't make sense.")
  517. self.extractFromHierarchy(self.root) #recursively extracts the basics, composites, histories and nr_of_after_transitions
  518. # Calculate the history that needs to be taken care of.
  519. self.shallow_history_parents = []
  520. self.deep_history_parents = []
  521. self.combined_history_parents = [] #All nodes that need state saved on leaving
  522. for node in self.histories:
  523. self.calculateHistory(node.parent, node.is_history_deep)
  524. def extractFromHierarchy(self, node):
  525. # For each AFTER event, give it a name so that it can be triggered.
  526. for transition in node.transitions:
  527. trigger = transition.trigger
  528. if trigger.isAfter() :
  529. trigger.setAfterIndex(self.nr_of_after_transitions)
  530. value = "_" + str(trigger.getAfterIndex()) + "after"
  531. trigger.setEvent(value)
  532. self.nr_of_after_transitions += 1
  533. if node.is_basic :
  534. self.basics.append(node)
  535. elif node.is_composite :
  536. self.composites.append(node)
  537. elif node.is_history :
  538. self.histories.append(node)
  539. for child in node.children :
  540. self.extractFromHierarchy(child)
  541. def calculateHistory(self, parent, is_deep):
  542. """ Figures out which components need to be kept track of for history.
  543. """
  544. if parent == self.root:
  545. raise CompilerException("Root component cannot contain a history state.")
  546. if parent not in self.combined_history_parents:
  547. self.combined_history_parents.append(parent)
  548. parent.save_state_on_exit = True
  549. if is_deep :
  550. if parent not in self.deep_history_parents:
  551. self.deep_history_parents.append(parent)
  552. else :
  553. if parent not in self.shallow_history_parents:
  554. self.shallow_history_parents.append(parent)
  555. if parent.is_parallel_state or is_deep :
  556. for i in parent.children:
  557. if i.is_composite :
  558. self.calculateHistory(i, is_deep)
  559. ###################################
  560. class Association(Visitable):
  561. def __init__(self, to_class, min_card, max_card, name):
  562. self.min = min_card
  563. self.max = max_card #N is represented as -1
  564. self.to_class = to_class
  565. self.name = name
  566. ###################################
  567. class FormalParameter(Visitable):
  568. def __init__(self, param_ident, param_type, default = None):
  569. self.param_type = param_type
  570. self.identifier = param_ident
  571. self.default = default
  572. def getType(self):
  573. return self.param_type
  574. def getIdent(self):
  575. return self.identifier
  576. def hasDefault(self):
  577. return self.default is not None
  578. def getDefault(self):
  579. return self.default
  580. #slight hack because of lacking multiple constructors
  581. class XMLFormalParameter(FormalParameter):
  582. def __init__(self, xml):
  583. self.param_type = xml.get("type", "")
  584. self.identifier = xml.get("name","")
  585. self.default = xml.get("default",None)
  586. ###################################
  587. class Method(Visitable):
  588. def __init__(self, xml, parent_class):
  589. self.name = xml.get("name", "")
  590. self.access = xml.get("access", "public")
  591. parameters = xml.findall("parameter")
  592. self.parameters = []
  593. for p in parameters:
  594. self.parameters.append(XMLFormalParameter(p))
  595. bodies = xml.findall("body")
  596. if len(bodies) > 1 :
  597. raise CompilerException("Method can have at most one body.")
  598. elif len(bodies) == 1:
  599. self.body = bodies[0].text
  600. else:
  601. self.body = ""
  602. self.parent_class = parent_class
  603. self.return_type = xml.get('type',"")
  604. self.is_abstract = xml.get('abstract', False)
  605. def getParams(self):
  606. return self.parameters
  607. def isAbstract(self):
  608. return self.is_abstract
  609. ###################################
  610. class Constructor(Method):
  611. def __init__(self, xml, parent_class):
  612. self.super_class_parameters = {};
  613. if xml is None :
  614. self.body = ""
  615. self.name = ""
  616. self.access = "public"
  617. self.parent_class = parent_class
  618. self.return_type = ""
  619. self.parameters = []
  620. else :
  621. Method.__init__(self, xml, parent_class)
  622. super_class_parameters = xml.findall("super")
  623. for s in super_class_parameters:
  624. class_name = s.get("class")
  625. self.super_class_parameters[class_name] = []
  626. params = s.findall("parameter")
  627. for p in params:
  628. self.super_class_parameters[class_name].append(p.get("expr"))
  629. class Destructor(Method):
  630. def __init__(self, xml, parent_class):
  631. if xml is None :
  632. self.body = ""
  633. self.name = ""
  634. self.access = "public"
  635. self.parent_class = parent_class
  636. self.return_type = ""
  637. self.parameters = []
  638. else :
  639. Method.__init__(self, xml, parent_class)
  640. ###################################
  641. class Attribute(Visitable):
  642. def __init__(self, xml):
  643. self.name = xml.get('name',"")
  644. self.type = xml.get('type',"")
  645. self.init_value = xml.get("init-value", None)
  646. def getIdent(self):
  647. return self.name
  648. def getType(self):
  649. return self.type
  650. def getInit(self):
  651. return self.init_value
  652. ###################################
  653. class Class(Visitable):
  654. def __init__(self, xml, class_diagram):
  655. self.xml = xml
  656. self.class_diagram = class_diagram
  657. self.name = xml.get("name", "")
  658. self.abstract_method_names = [] # this value will be written to by super_class_linker
  659. self.constructors = []
  660. self.destructors = []
  661. self.methods = []
  662. self.statechart = None
  663. self.inports = []
  664. self.outports = []
  665. self.attributes = []
  666. self.associations = []
  667. self.super_classes = []
  668. self.super_class_objs = {} # maps super class names to super class objects
  669. self.process()
  670. def getName(self):
  671. return self.name
  672. def isAbstract(self):
  673. return len(self.abstract_method_names) > 0
  674. def processMethod(self, method_xml) :
  675. name = method_xml.get("name", "")
  676. if name == self.name :
  677. self.constructors.append(Constructor(method_xml, self))
  678. elif name == '~' + self.name:
  679. self.destructors.append(Destructor(method_xml, self))
  680. else :
  681. if name in reserved:
  682. raise CompilerException("Reserved word \"" + name + "\" used as method in class <" + self.name + ">.")
  683. new_method = Method(method_xml, self)
  684. self.methods.append(new_method)
  685. def processAttribute(self, attribute_xml):
  686. attribute = Attribute(attribute_xml)
  687. if attribute.name in reserved:
  688. raise CompilerException("Reserved word \"" + attribute.name + "\" used as variable in class <" + self.name + ">.")
  689. self.attributes.append(attribute)
  690. def processInheritances(self, inheritances):
  691. # process each inheritance, stores a dict with each subclass as the key
  692. # and a list of tuples (superclass, priority) as the value. The priority
  693. # tells us which class to inherit from first for multiple inheritance. Gives
  694. # a WARNING with a given inheritance order if two priorities are the same
  695. for i in inheritances :
  696. self.super_classes.append((i.get("class",""),i.get("priority",1)))
  697. self.super_classes.sort(lambda a, b: cmp(b[1], a[1])) # sort from high priority to low priority
  698. priorityChecker = {}
  699. for super_class, priority in self.super_classes:
  700. if priority in priorityChecker:
  701. checkIt = priorityChecker[priority]
  702. else:
  703. checkIt = []
  704. if super_class not in checkIt:
  705. checkIt.append(super_class)
  706. priorityChecker[priority] = checkIt
  707. for priority, checkIt in priorityChecker.iteritems():
  708. if len(checkIt) > 1:
  709. Logger.showWarning("Class <" + self.name + "> inherits from classes <" + ", ".join(checkIt) + "> with same priority <" + str(priority) + ">. Document inheritance order is used.")
  710. self.super_classes = [entry[0] for entry in self.super_classes]
  711. def processAssociations(self, associations):
  712. for a in associations :
  713. class_name = a.get("class","")
  714. if not class_name :
  715. raise CompilerException("Faulty association.")
  716. card_min_string = a.get("min","0")
  717. try :
  718. card_min = int(card_min_string)
  719. if card_min < 0 :
  720. raise ValueError()
  721. except ValueError :
  722. raise CompilerException("Faulty card-min value in association.")
  723. card_max_string = a.get("max","N")
  724. if card_max_string == "N" :
  725. card_max = -1
  726. else :
  727. try :
  728. card_max = int(card_max_string)
  729. if card_max < card_min :
  730. raise ValueError()
  731. except ValueError :
  732. raise CompilerException("Faulty card-max value in association.")
  733. association_name = a.get("name","")
  734. if not association_name :
  735. raise CompilerException("Faulty association. No name.")
  736. if association_name in reserved :
  737. raise CompilerException("Reserved word \"" + association_name + "\" used as association name in class <" + self.name + ">.")
  738. self.associations.append(
  739. Association(class_name, card_min, card_max, association_name)
  740. )
  741. def process(self):
  742. inports = self.xml.findall("inport")
  743. for i in inports:
  744. name = i.get("name")
  745. if name in self.inports:
  746. raise CompilerException("Found 2 inports with the same name : " + name + ".")
  747. self.inports.append(name)
  748. outports = self.xml.findall("outport")
  749. for i in outports:
  750. name = i.get("name")
  751. if name in self.outports:
  752. raise CompilerException("Found 2 outports with the same name : " + name + ".")
  753. self.outports.append(name)
  754. associations = []
  755. inheritances = []
  756. relationships = self.xml.findall("relationships")
  757. for relationship_wrapper in relationships :
  758. associations.extend(relationship_wrapper.findall("association"))
  759. inheritances.extend(relationship_wrapper.findall("inheritance"))
  760. self.processAssociations(associations)
  761. self.processInheritances(inheritances)
  762. attributes = self.xml.findall("attribute")
  763. for a in attributes:
  764. self.processAttribute(a)
  765. methods = self.xml.findall("method")
  766. for m in methods:
  767. self.processMethod(m)
  768. constructors = self.xml.findall("constructor")
  769. for c in constructors:
  770. self.constructors.append(Constructor(c, self))
  771. destructors = self.xml.findall("destructor")
  772. for d in destructors:
  773. self.destructors.append(Destructor(d, self))
  774. if len(self.constructors) > 1 :
  775. raise CompilerException("Multiple constructors no longer supported!")
  776. if len(self.destructors) > 1 :
  777. raise CompilerException("Multiple destructors defined for class <" + self.name + ">.")
  778. if len(self.constructors) < 1 :
  779. # add a default constructor
  780. self.constructors.append(Constructor(None,self))
  781. if len(self.destructors) < 1 :
  782. # add a default destructor
  783. self.destructors.append(Destructor(None,self))
  784. statecharts = self.xml.findall("scxml")
  785. if len(statecharts) > 1 :
  786. raise CompilerException("Multiple statecharts found in class <" + self.name + ">.")
  787. if len(statecharts) == 1 :
  788. self.statechart = StateChart(self, statecharts[0])
  789. ###################################
  790. class ClassDiagram(Visitable):
  791. def __init__(self, input_file):
  792. diagram_dir = os.path.dirname(input_file)
  793. tree = ET.parse(input_file)
  794. self.root = tree.getroot()
  795. self.name = self.root.get("name", "")
  796. self.author = self.root.get("author", "")
  797. descriptions = self.root.findall("description")
  798. self.language = self.root.get("language", "")
  799. if descriptions :
  800. self.description = descriptions[0].text
  801. else :
  802. self.description = ""
  803. xml_classes = self.root.findall("class")
  804. # make sure at least one class is given
  805. if not xml_classes :
  806. raise CompilerException("Found no classes to compile.")
  807. # check if class diagram is valid
  808. # unique class names
  809. self.class_names = []
  810. substituted_xml_classes = []
  811. for xml_class in xml_classes :
  812. class_src = xml_class.get("src", "")
  813. class_default = xml_class.get("default", "")
  814. if class_src != "":
  815. if not os.path.isabs(class_src):
  816. class_src = os.path.join(diagram_dir, class_src)
  817. substituted_xml_class = ET.parse(class_src).getroot()
  818. else:
  819. substituted_xml_class = xml_class
  820. substituted_xml_class.is_default = (class_default.lower() == "true")
  821. name = substituted_xml_class.get("name", "")
  822. if name == "" :
  823. raise CompilerException("Missing or emtpy class name.")
  824. if name in self.class_names :
  825. raise CompilerException("Found 2 classes with the same name : " + name + ".")
  826. self.class_names.append(name)
  827. substituted_xml_classes.append(substituted_xml_class)
  828. # process in and output ports
  829. inports = self.root.findall("inport")
  830. names = []
  831. for xml_inport in inports :
  832. name = xml_inport.get("name", "")
  833. if name in names :
  834. raise CompilerException("Found 2 INPorts with the same name : " + name + ".")
  835. names.append(name)
  836. self.inports = names
  837. outports = self.root.findall("outport")
  838. names = []
  839. for xml_outport in outports :
  840. name = xml_outport.get("name", "")
  841. if name in names :
  842. raise CompilerException("Found 2 OUTPorts with the same name : " + name + ".")
  843. names.append(name)
  844. self.outports = names
  845. # any inital import code that has to come at the top of the generate file
  846. tops = self.root.findall("top")
  847. self.includes = []
  848. if len(tops) == 1 :
  849. self.top = tops[0].text
  850. elif len(tops) > 1 :
  851. raise CompilerException("Class diagram can only have one <top> element.")
  852. else :
  853. self.top = ""
  854. # process each class in diagram
  855. self.classes = []
  856. default_classes = []
  857. for xml_class in substituted_xml_classes:
  858. processed_class = None
  859. try :
  860. processed_class = Class(xml_class, self)
  861. except CompilerException as e :
  862. e.message = "Class <" + xml_class.get("name", "") + "> failed compilation. " + e.message
  863. raise e
  864. # let user know this class was successfully loaded
  865. Logger.showInfo("Class <" + processed_class.name + "> has been successfully loaded.")
  866. self.classes.append(processed_class)
  867. if xml_class.is_default :
  868. default_classes.append(processed_class)
  869. if not default_classes or len(default_classes) > 1:
  870. if len(self.classes) == 1 :
  871. Logger.showInfo("Only one class given. Using <" + self.classes[0].getName() + "> as the default class.")
  872. default_classes.append(self.classes[0])
  873. else :
  874. raise CompilerException("Provide one and only one default class to instantiate on start up.")
  875. self.default_class = default_classes[0]
  876. # check if there's a test
  877. self.test = None
  878. test_nodes = self.root.findall("test")
  879. if test_nodes:
  880. test_node = test_nodes[0]
  881. input_nodes = test_node.findall("input")
  882. if input_nodes:
  883. input_node = input_nodes[0]
  884. test_input = DiagramTestInput(input_node)
  885. else:
  886. test_input = None
  887. expected_nodes = test_node.findall("expected")
  888. if expected_nodes:
  889. expected_node = expected_nodes[0]
  890. test_expected = DiagramTestExpected(expected_node)
  891. else:
  892. test_expected = None
  893. self.test = DiagramTest(test_input, test_expected)
  894. class DiagramTest(Visitable):
  895. def __init__(self, i, expected):
  896. self.input = i
  897. self.expected = expected
  898. class DiagramTestEvent(Visitable):
  899. def __init__(self, xml):
  900. self.name = xml.get("name")
  901. self.port = xml.get("port")
  902. self.parameters = []
  903. parameter_nodes = xml.findall("parameter")
  904. for parameter_node in parameter_nodes:
  905. val = parameter_node.get("value")
  906. expr = parameter_node.get("expr")
  907. if val:
  908. self.parameters.append(val) # expected events use 'val'
  909. elif expr:
  910. self.parameters.append(expr) # input events use 'expr'
  911. else:
  912. raise CompilerException("Parameter has no value/expr.")
  913. class DiagramTestInputEvent(DiagramTestEvent):
  914. def __init__(self, xml):
  915. DiagramTestEvent.__init__(self, xml)
  916. self.time = xml.get("time")
  917. class DiagramTestInput(Visitable):
  918. def __init__(self, xml):
  919. self.input_events = []
  920. event_nodes = xml.findall("event")
  921. for event_node in event_nodes:
  922. e = DiagramTestInputEvent(event_node)
  923. self.input_events.append(e)
  924. class DiagramTestExpectedSlot(Visitable):
  925. def __init__(self, xml):
  926. self.expected_events = []
  927. event_nodes = xml.findall("event")
  928. for event_node in event_nodes:
  929. e = DiagramTestEvent(event_node)
  930. self.expected_events.append(e)
  931. class DiagramTestExpected(Visitable):
  932. def __init__(self, xml):
  933. self.slots = []
  934. slot_nodes = xml.findall("slot")
  935. for slot_node in slot_nodes:
  936. s = DiagramTestExpectedSlot(slot_node)
  937. self.slots.append(s)