sccd_constructs.py 43 KB

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