constructs.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995
  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. def accept(self, visitor):
  85. for expression_part in self.expression_parts :
  86. expression_part.accept(visitor)
  87. class LValue(Expression):
  88. def __init__(self, input):
  89. if not input :
  90. raise CompilerException("Empty LValue.")
  91. self.parse(input, [INSTATE_SEQ])
  92. #do some validation, provide parameters to processString to make the function more efficient
  93. ##################################
  94. class FormalEventParameter(Visitable):
  95. def __init__(self, name, ptype = ""):
  96. self.name = name
  97. self.type = ptype
  98. def getName(self):
  99. return self.name
  100. def getType(self):
  101. return self.type
  102. ##################################
  103. class TriggerEvent:
  104. def __init__(self, xml_element):
  105. self.is_uc = False;
  106. self.is_after = False
  107. self.after_index = -1
  108. self.params = []
  109. self.event = xml_element.get("event", "").strip()
  110. self.after = xml_element.get("after", "").strip()
  111. self.port = xml_element.get("port", "").strip()
  112. if self.event and self.after :
  113. raise CompilerException("Cannot have both the event and after attribute set for a transition.")
  114. if not self.event and self.port:
  115. raise CompilerException("A transition without event can not have a port.")
  116. if self.after :
  117. if self.port :
  118. raise CompilerException("After event can not have a port.")
  119. self.is_after = True
  120. self.after = Expression(self.after)
  121. return
  122. elif not self.event :
  123. self.is_uc = True
  124. return
  125. self.params = []
  126. parameters = xml_element.findall('parameter')
  127. for p in parameters :
  128. name = p.get("name","")
  129. if not name :
  130. raise CompilerException("Parameter without name detected.")
  131. self.params.append(FormalEventParameter(name, p.get("type","")))
  132. def getEvent(self):
  133. return self.event
  134. def setEvent(self, event):
  135. self.event = event
  136. def getParameters(self):
  137. return self.params
  138. def getPort(self):
  139. return self.port
  140. def isUC(self):
  141. return self.is_uc;
  142. def isAfter(self):
  143. return self.is_after
  144. def getAfterIndex(self):
  145. return self.after_index
  146. def setAfterIndex(self, after):
  147. self.after_index = after
  148. ##################################
  149. class SubAction(Visitable):
  150. __metaclass__ = abc.ABCMeta
  151. @abc.abstractmethod
  152. def check(self):
  153. pass
  154. @classmethod
  155. def create(cls, xml_element):
  156. for subcls in cls.__subclasses__():
  157. tag = xml_element.tag.lower()
  158. if subcls.check(tag):
  159. return subcls(xml_element)
  160. raise CompilerException("Invalid subaction.")
  161. ##################################
  162. """
  163. Is a possible subaction; generates an event.
  164. """
  165. class RaiseEvent(SubAction):
  166. tag = "raise"
  167. LOCAL_SCOPE = 1
  168. BROAD_SCOPE = 2
  169. OUTPUT_SCOPE = 3
  170. NARROW_SCOPE = 4
  171. CD_SCOPE = 5
  172. def __init__(self, xml_element):
  173. self.event = xml_element.get("event","").strip()
  174. scope_string = xml_element.get("scope","").strip().lower()
  175. self.target = xml_element.get("target","").strip()
  176. self.port = xml_element.get("port","").strip()
  177. if scope_string == "local" :
  178. self.scope = self.LOCAL_SCOPE
  179. elif scope_string == "broad" :
  180. self.scope = self.BROAD_SCOPE
  181. elif scope_string == "output" :
  182. self.scope = self.OUTPUT_SCOPE
  183. elif scope_string == "narrow" :
  184. self.scope = self.NARROW_SCOPE
  185. elif scope_string == "cd" :
  186. self.scope = self.CD_SCOPE
  187. elif scope_string == "" :
  188. #Calculate scope depending on present attributes
  189. if self.target and self.port :
  190. raise CompilerException("Both target and port attribute detected without a scope defined.")
  191. elif self.port :
  192. self.scope = self.OUTPUT_SCOPE
  193. elif self.target :
  194. self.scope = self.NARROW_SCOPE
  195. else :
  196. self.scope = self.LOCAL_SCOPE
  197. else :
  198. raise CompilerException("Illegal scope attribute; needs to be one of the following : local, broad, narrow, output, cd or nothing.");
  199. if self.scope == self.LOCAL_SCOPE or self.scope == self.BROAD_SCOPE or self.scope == self.CD_SCOPE:
  200. if self.target :
  201. Logger.showWarning("Raise event target detected, not matching with scope. Ignored.")
  202. self.target = ""
  203. if self.port :
  204. Logger.showWarning("Raise event port detected, not matching with scope. Ignored.")
  205. self.port = ""
  206. if self.scope == self.NARROW_SCOPE and self.port :
  207. Logger.showWarning("Raise event port detected, not matching with scope. Ignored.")
  208. self.port = ""
  209. if self.scope == self.OUTPUT_SCOPE and self.target :
  210. Logger.showWarning("Raise event target detected, not matching with scope. Ignored.")
  211. self.target = ""
  212. self.params = []
  213. parameters = xml_element.findall('parameter')
  214. for p in parameters :
  215. value = p.get("expr","")
  216. if not value :
  217. raise CompilerException("Parameter without value detected.")
  218. self.params.append(Expression(value))
  219. @staticmethod
  220. def check(tag):
  221. return tag == RaiseEvent.tag
  222. def getPort(self):
  223. return self.port
  224. def isLocal(self):
  225. return self.scope == self.LOCAL_SCOPE
  226. def isNarrow(self):
  227. return self.scope == self.NARROW_SCOPE
  228. def isBroad(self):
  229. return self.scope == self.BROAD_SCOPE
  230. def isOutput(self):
  231. return self.scope == self.OUTPUT_SCOPE
  232. def isCD(self):
  233. return self.scope == self.CD_SCOPE
  234. def getTarget(self):
  235. return self.target
  236. def getEventName(self):
  237. return self.event
  238. def getParameters(self):
  239. return self.params
  240. def getScope(self):
  241. return self.scope
  242. class Script(SubAction):
  243. tag = "script"
  244. def __init__(self, xml_element):
  245. self.code = xml_element.text
  246. @staticmethod
  247. def check(tag):
  248. return tag == Script.tag
  249. class Log(SubAction):
  250. tag = "log"
  251. def __init__(self, xml_element):
  252. self.message = xml_element.text.strip()
  253. @staticmethod
  254. def check(tag):
  255. return tag == Log.tag
  256. class Assign(SubAction):
  257. tag = "assign"
  258. def __init__(self, xml_element):
  259. self.lvalue = LValue(xml_element.get("ident",""))
  260. self.expression = Expression(xml_element.get("expr",""))
  261. @staticmethod
  262. def check(tag):
  263. return tag == Assign.tag
  264. ##################################
  265. """
  266. Exists out of multiple subactions
  267. """
  268. class Action(Visitable):
  269. def __init__(self, xml_element):
  270. self.sub_actions = []
  271. for subaction in list(xml_element) :
  272. if subaction.tag not in ["parameter"] :
  273. self.sub_actions.append(SubAction.create(subaction))
  274. def accept(self, visitor):
  275. for subaction in self.sub_actions :
  276. subaction.accept(visitor)
  277. ##################################
  278. class StateChartTransition(Visitable):
  279. def __init__(self,xml_element,parent):
  280. self.xml = xml_element
  281. self.parent_node = parent
  282. self.trigger = TriggerEvent(self.xml)
  283. guard_string = self.xml.get("cond","").strip()
  284. if guard_string != "" :
  285. self.guard = Expression(guard_string)
  286. else :
  287. self.guard = None
  288. target_string = self.xml.get("target","").strip()
  289. if target_string == "" :
  290. raise CompilerException("Transition from <" + self.parent_node.full_name + "> has empty target.")
  291. self.target = StateReference(target_string)
  292. self.action = Action(self.xml)
  293. self.enter_nodes = None #Ordered list of nodes to be entered upon taking the transition, set by the path calculator
  294. self.exit_nodes = None #Ordered list of nodes to be exited upon taking the transition, set by the path calculator
  295. def getEnterNodes(self):
  296. if self.enter_nodes is None :
  297. raise UnprocessedException("Enter path not calculated yet.")
  298. return self.enter_nodes
  299. def getExitNodes(self):
  300. if self.exit_nodes is None :
  301. raise UnprocessedException("Exit path not calculated yet.")
  302. return self.exit_nodes
  303. def isUCTransition(self):
  304. """ Returns true iff is an unconditional transition (i.e. no trigger)
  305. """
  306. return self.trigger.isUC()
  307. def getParentNode(self):
  308. return self.parent_node
  309. def getTrigger(self):
  310. return self.trigger
  311. def getGuard(self):
  312. return self.guard
  313. def getTargetNodes(self):
  314. return self.target.getNodes()
  315. def hasGuard(self):
  316. return self.guard != None
  317. def getAction(self):
  318. return self.action
  319. ##################################
  320. class EnterExitAction(Visitable):
  321. def __init__(self, parent_node, xml_element = None):
  322. self.parent_node = parent_node
  323. if xml_element is not None:
  324. self.action = Action(xml_element)
  325. else :
  326. self.action = None
  327. class EnterAction(EnterExitAction):
  328. def __init__(self, parent_node, xml_element = None):
  329. EnterExitAction.__init__(self, parent_node, xml_element)
  330. class ExitAction(EnterExitAction):
  331. def __init__(self, parent_node, xml_element = None):
  332. EnterExitAction.__init__(self, parent_node, xml_element)
  333. ##################################
  334. class StateChartNode(Visitable):
  335. def __init__(self, statechart, xml_element, parent = None):
  336. self.statechart = statechart
  337. self.parent = parent
  338. self.children = []
  339. self.is_root = False
  340. self.is_basic = False
  341. self.is_composite = False
  342. self.is_history = False
  343. self.is_history_deep = False
  344. self.is_parallel_state = False
  345. self.save_state_on_exit = False
  346. if xml_element.tag == "scxml" :
  347. self.is_root = True
  348. self.is_composite = True
  349. elif xml_element.tag == "parallel" :
  350. self.is_composite = True
  351. self.is_parallel_state = True
  352. elif xml_element.tag == "state" :
  353. if len(xml_element.findall("state")) > 0 or (len(xml_element.findall("parallel")) > 0) :
  354. self.is_composite = True
  355. else :
  356. self.is_basic = True
  357. if self.parent.is_parallel_state :
  358. if (self.is_basic) :
  359. raise CompilerException("Orthogonal nodes (nodes that are immediate children of parallel nodes) can't be basic.")
  360. elif xml_element.tag == "history" :
  361. history_type = xml_element.get("type","shallow")
  362. if history_type == "deep" :
  363. self.is_history_deep = True
  364. elif history_type != "shallow" :
  365. raise CompilerException("Invalid history type.")
  366. self.is_history = True
  367. else :
  368. return
  369. self.resolveName(xml_element)
  370. self.parseConflictAttribute(xml_element)
  371. self.parseEnterActions(xml_element)
  372. self.parseExitActions(xml_element)
  373. #transitions
  374. self.transitions = []
  375. for transition_xml in xml_element.findall("transition"):
  376. self.transitions.append(StateChartTransition(transition_xml,self))
  377. self.optimizeTransitions()
  378. self.generateChildren(xml_element)
  379. self.calculateDefaults(xml_element)
  380. def resolveName(self, xml):
  381. if self.is_root :
  382. self.name = "Root"
  383. self.full_name = "Root"
  384. else :
  385. self.name = xml.get("id","")
  386. self.full_name = self.parent.full_name + "_" + self.name
  387. def parseConflictAttribute(self, xml):
  388. conflict = xml.get("conflict","")
  389. if conflict == "outer" :
  390. self.solves_conflict_outer = True
  391. elif conflict == "inner" :
  392. self.solves_conflict_outer = False
  393. else :
  394. if not (conflict == "" or conflict == "inherit") :
  395. raise CompilerException("Unknown conflict attribute for " + self.full_name + ".")
  396. #Do our default inherit action
  397. if self.is_root or self.parent.solves_conflict_outer:
  398. self.solves_conflict_outer = True
  399. else :
  400. self.solves_conflict_outer = False
  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. self.extractFromHierarchy(self.root) #recursively extracts the basics, composites, histories and nr_of_after_transitions
  505. # Calculate the history that needs to be taken care of.
  506. self.shallow_history_parents = []
  507. self.deep_history_parents = []
  508. self.combined_history_parents = [] #All nodes that need state saved on leaving
  509. for node in self.histories:
  510. self.calculateHistory(node.parent, node.is_history_deep)
  511. def extractFromHierarchy(self, node):
  512. # For each AFTER event, give it a name so that it can be triggered.
  513. for transition in node.transitions:
  514. trigger = transition.trigger
  515. if trigger.isAfter() :
  516. trigger.setAfterIndex(self.nr_of_after_transitions)
  517. value = "_" + str(trigger.getAfterIndex()) + "after"
  518. trigger.setEvent(value)
  519. self.nr_of_after_transitions += 1
  520. if node.is_basic :
  521. self.basics.append(node)
  522. elif node.is_composite :
  523. self.composites.append(node)
  524. elif node.is_history :
  525. self.histories.append(node)
  526. for child in node.children :
  527. self.extractFromHierarchy(child)
  528. def calculateHistory(self, parent, is_deep):
  529. """ Figures out which components need to be kept track of for history.
  530. """
  531. if parent == self.root:
  532. raise CompilerException("Root component cannot contain a history state.")
  533. if parent not in self.combined_history_parents:
  534. self.combined_history_parents.append(parent)
  535. parent.save_state_on_exit = True
  536. if is_deep :
  537. if parent not in self.deep_history_parents:
  538. self.deep_history_parents.append(parent)
  539. else :
  540. if parent not in self.shallow_history_parents:
  541. self.shallow_history_parents.append(parent)
  542. if parent.is_parallel_state or is_deep :
  543. for i in parent.children:
  544. if i.is_composite :
  545. self.calculateHistory(i, is_deep)
  546. ###################################
  547. class Association(Visitable):
  548. def __init__(self, to_class, min_card, max_card, name):
  549. self.min = min_card
  550. self.max = max_card #N is represented as -1
  551. self.to_class = to_class
  552. self.name = name
  553. ###################################
  554. class FormalParameter(Visitable):
  555. def __init__(self, param_ident, param_type, default = None):
  556. self.param_type = param_type
  557. self.identifier = param_ident
  558. self.default = default
  559. def getType(self):
  560. return self.param_type
  561. def getIdent(self):
  562. return self.identifier
  563. def hasDefault(self):
  564. return self.default is not None
  565. def getDefault(self):
  566. return self.default
  567. #slight hack because of lacking multiple constructors
  568. class XMLFormalParameter(FormalParameter):
  569. def __init__(self, xml):
  570. self.param_type = xml.get("type", "")
  571. self.identifier = xml.get("name","")
  572. self.default = xml.get("default",None)
  573. ###################################
  574. class Method(Visitable):
  575. def __init__(self, xml, parent_class):
  576. self.name = xml.get("name", "")
  577. self.access = xml.get("access", "public")
  578. parameters = xml.findall("parameter")
  579. self.parameters = []
  580. for p in parameters:
  581. self.parameters.append(XMLFormalParameter(p))
  582. bodies = xml.findall("body")
  583. if len(bodies) != 1 :
  584. raise CompilerException("Method needs one and only one body element.")
  585. self.body = bodies[0].text
  586. self.parent_class = parent_class
  587. self.return_type = xml.get('type',"")
  588. def getParams(self):
  589. return self.parameters
  590. ###################################
  591. class Constructor(Method):
  592. def __init__(self, xml, parent_class):
  593. if xml is None :
  594. self.body = ""
  595. self.name = ""
  596. self.access = "public"
  597. self.parent_class = parent_class
  598. self.return_type = ""
  599. self.parameters = []
  600. else :
  601. Method.__init__(self, xml, parent_class)
  602. class Destructor(Method):
  603. def __init__(self, xml, parent_class):
  604. Method.__init__(self, xml, parent_class)
  605. ###################################
  606. class Attribute(Visitable):
  607. def __init__(self, xml):
  608. self.name = xml.get('name',"")
  609. self.type = xml.get('type',"")
  610. self.init_value = xml.get("init-value", None)
  611. def getIdent(self):
  612. return self.name
  613. def getType(self):
  614. return self.type
  615. def getInit(self):
  616. return self.init_value
  617. ###################################
  618. class Class(Visitable):
  619. def __init__(self, xml):
  620. self.xml = xml
  621. self.name = xml.get("name", "")
  622. self.constructors = []
  623. self.destructors = []
  624. self.methods = []
  625. self.statechart = None
  626. self.inports = []
  627. self.outports = []
  628. self.attributes = []
  629. self.associations = []
  630. self.super_classes = []
  631. self.process()
  632. def getName(self):
  633. return self.name
  634. def processMethod(self, method_xml) :
  635. name = method_xml.get("name", "")
  636. if name == self.name :
  637. self.constructors.append(Constructor(method_xml, self))
  638. elif name == '~' + self.name:
  639. self.destructors.append(Destructor(method_xml, self))
  640. else :
  641. if name in reserved:
  642. raise CompilerException("Reserved word \"" + name + "\" used as method in class <" + self.name + ">.")
  643. self.methods.append( Method(method_xml, self))
  644. def processAttribute(self, attribute_xml):
  645. attribute = Attribute(attribute_xml)
  646. if attribute.name in reserved:
  647. raise CompilerException("Reserved word \"" + attribute.name + "\" used as variable in class <" + self.name + ">.")
  648. self.attributes.append(attribute)
  649. def processInheritances(self, inheritances):
  650. # process each inheritance, stores a dict with each subclass as the key
  651. # and a list of tuples (superclass, priority) as the value. The priority
  652. # tells us which class to inherit from first for multiple inheritance. Gives
  653. # a WARNING with a given inheritance order if two priorities are the same
  654. for i in inheritances :
  655. self.super_classes.append((i.get("class",""),i.get("priority",1)))
  656. self.super_classes.sort(lambda a, b: cmp(a[1], b[1]))
  657. priorityChecker = {}
  658. for super_class, priority in self.super_classes:
  659. if priority in priorityChecker:
  660. checkIt = priorityChecker[priority]
  661. else:
  662. checkIt = []
  663. if super_class not in checkIt:
  664. checkIt.append(super_class)
  665. priorityChecker[priority] = checkIt
  666. for priority, checkIt in priorityChecker.iteritems():
  667. if len(checkIt) > 1:
  668. Logger.showWarning("Class <" + self.name + "> inherits from classes <" + ", ".join(checkIt) + "> with same priority <" + str(priority) + ">. Given inheritance order is chosen.")
  669. self.super_classes = [entry[0] for entry in self.super_classes]
  670. def processAssociations(self, associations):
  671. for a in associations :
  672. class_name = a.get("class","")
  673. if not class_name :
  674. raise CompilerException("Faulty association.")
  675. card_min_string = a.get("min","0")
  676. try :
  677. card_min = int(card_min_string)
  678. if card_min < 0 :
  679. raise ValueError()
  680. except ValueError :
  681. raise CompilerException("Faulty card-min value in association.")
  682. card_max_string = a.get("max","N")
  683. if card_max_string == "N" :
  684. card_max = -1
  685. else :
  686. try :
  687. card_max = int(card_max_string)
  688. if card_max < card_min :
  689. raise ValueError()
  690. except ValueError :
  691. raise CompilerException("Faulty card-max value in association.")
  692. association_name = a.get("name","")
  693. if not association_name :
  694. raise CompilerException("Faulty association. No name.")
  695. if association_name in reserved :
  696. raise CompilerException("Reserved word \"" + association_name + "\" used as association name in class <" + self.name + ">.")
  697. self.associations.append(
  698. Association(class_name, card_min, card_max, association_name)
  699. )
  700. def process(self):
  701. inports = self.xml.findall("inport")
  702. for i in inports:
  703. name = i.get("name")
  704. if name in self.inports:
  705. raise CompilerException("Found 2 inports with the same name : " + name + ".")
  706. self.inports.append(name)
  707. outports = self.xml.findall("outport")
  708. for i in outports:
  709. name = i.get("name")
  710. if name in self.outports:
  711. raise CompilerException("Found 2 outports with the same name : " + name + ".")
  712. self.outports.append(name)
  713. attributes = self.xml.findall("attribute")
  714. for a in attributes:
  715. self.processAttribute(a)
  716. methods = self.xml.findall("method")
  717. for m in methods:
  718. self.processMethod(m)
  719. if len(self.destructors) > 1 :
  720. raise CompilerException("Multiple destructors defined for class <" + self.name + ">.")
  721. if len(self.constructors) < 1 :
  722. #add a default constructor
  723. self.constructors.append(Constructor(None,self))
  724. associations = []
  725. inheritances = []
  726. relationships = self.xml.findall("relationships")
  727. for relationship_wrapper in relationships :
  728. associations.extend(relationship_wrapper.findall("association"))
  729. inheritances.extend(relationship_wrapper.findall("inheritance"))
  730. self.processAssociations(associations)
  731. self.processInheritances(inheritances)
  732. statecharts = self.xml.findall("scxml")
  733. if len(statecharts) > 1 :
  734. raise CompilerException("Multiple statecharts found in class <" + self.name + ">.")
  735. if len(statecharts) == 1 :
  736. self.statechart = StateChart(self, statecharts[0])
  737. ###################################
  738. class ClassDiagram(Visitable):
  739. def __init__(self, input_file):
  740. diagram_dir = os.path.dirname(input_file)
  741. tree = ET.parse(input_file)
  742. self.root = tree.getroot()
  743. self.name = self.root.get("name", "")
  744. self.author = self.root.get("author", "")
  745. descriptions = self.root.findall("description")
  746. if descriptions :
  747. self.description = descriptions[0].text
  748. else :
  749. self.description = ""
  750. xml_classes = self.root.findall("class")
  751. # make sure at least one class is given
  752. if not xml_classes :
  753. raise CompilerException("Found no classes to compile.")
  754. # check if class diagram is valid
  755. # unique class names
  756. self.class_names = []
  757. substituted_xml_classes = []
  758. for xml_class in xml_classes :
  759. class_src = xml_class.get("src", "")
  760. class_default = xml_class.get("default", "")
  761. if class_src != "":
  762. if not os.path.isabs(class_src):
  763. class_src = os.path.join(diagram_dir, class_src)
  764. substituted_xml_class = ET.parse(class_src).getroot()
  765. else:
  766. substituted_xml_class = xml_class
  767. substituted_xml_class.is_default = (class_default.lower() == "true")
  768. name = substituted_xml_class.get("name", "")
  769. if name == "" :
  770. raise CompilerException("Missing or emtpy class name.")
  771. if name in self.class_names :
  772. raise CompilerException("Found 2 classes with the same name : " + name + ".")
  773. self.class_names.append(name)
  774. substituted_xml_classes.append(substituted_xml_class)
  775. # process in and output ports
  776. inports = self.root.findall("inport")
  777. names = []
  778. for xml_inport in inports :
  779. name = xml_inport.get("name", "")
  780. if name in names :
  781. raise CompilerException("Found 2 INPorts with the same name : " + name + ".")
  782. names.append(name)
  783. self.inports = names
  784. outports = self.root.findall("outport")
  785. names = []
  786. for xml_outport in outports :
  787. name = xml_outport.get("name", "")
  788. if name in names :
  789. raise CompilerException("Found 2 OUTPorts with the same name : " + name + ".")
  790. names.append(name)
  791. self.outports = names
  792. # any inital import code that has to come at the top of the generate file
  793. tops = self.root.findall("top")
  794. self.includes = []
  795. if len(tops) == 1 :
  796. self.top = tops[0].text
  797. elif len(tops) > 1 :
  798. raise CompilerException("Class diagram can only have one <top> element.")
  799. else :
  800. self.top = ""
  801. # process each class in diagram
  802. self.classes = []
  803. default_classes = []
  804. for xml_class in substituted_xml_classes:
  805. processed_class = None
  806. try :
  807. processed_class = Class(xml_class)
  808. except CompilerException as e :
  809. e.message = "Class <" + xml_class.get("name", "") + "> failed compilation. " + e.message
  810. raise e
  811. # let user know this class was successfully loaded
  812. Logger.showInfo("Class <" + processed_class.name + "> has been successfully loaded.")
  813. self.classes.append(processed_class)
  814. if xml_class.is_default :
  815. default_classes.append(processed_class)
  816. if not default_classes or len(default_classes) > 1:
  817. if len(self.classes) == 1 :
  818. Logger.showInfo("Only one class given. Using <" + self.classes[0].getName() + "> as the default class.")
  819. default_classes.append(self.classes[0])
  820. else :
  821. raise CompilerException("Provide one and only one default class to instantiate on start up.")
  822. self.default_class = default_classes[0]