sccd_constructs.py 43 KB

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