rust.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. from typing import *
  2. from sccd.statechart.static.tree import *
  3. from sccd.util.visit_tree import *
  4. from sccd.statechart.static.statechart import *
  5. from sccd.statechart.static.globals import *
  6. from sccd.util.indenting_writer import *
  7. # Conversion functions from abstract syntax elements to identifiers in Rust
  8. def snake_case(state: State) -> str:
  9. return state.opt.full_name.replace('/', '_');
  10. def ident_var(state: State) -> str:
  11. if state.opt.full_name == "/":
  12. return "root" # no technical reason, it's just clearer than "s_"
  13. else:
  14. return "s" + snake_case(state)
  15. def ident_type(state: State) -> str:
  16. if state.opt.full_name == "/":
  17. return "Root" # no technical reason, it's just clearer than "State_"
  18. else:
  19. return "State" + snake_case(state)
  20. def ident_enum_variant(state: State) -> str:
  21. # We know the direct children of a state must have unique names relative to each other,
  22. # and enum variants are scoped locally, so we can use the short name here:
  23. return state.short_name
  24. def ident_field(state: State) -> str:
  25. return "s" + snake_case(state)
  26. def ident_source_target(state: State) -> str:
  27. # drop the first '_' (this is safe, the root state itself can never be source or target)
  28. return snake_case(state)[1:]
  29. def ident_transition(t: Transition) -> str:
  30. return "transition%d_FROM_%s_TO_%s" % (t.opt.id, ident_source_target(t.source), ident_source_target(t.target))
  31. def ident_arena_label(state: State) -> str:
  32. if state.opt.full_name == "/":
  33. return "arena_root"
  34. else:
  35. return "arena" + snake_case(state)
  36. def ident_history_field(state: HistoryState) -> str:
  37. return "history" + snake_case(state.parent) # A history state records history value for its parent
  38. # Name of the output callback parameter, everywhere
  39. IDENT_OC = "_output" # underscore to keep Rust from warning us for unused variable
  40. def compile_actions(actions: List[Action], w: IndentingWriter):
  41. for a in actions:
  42. if isinstance(a, RaiseOutputEvent):
  43. # TODO: evaluate event parameters
  44. w.writeln("%s(\"%s\", \"%s\");" % (IDENT_OC, a.outport, a.name))
  45. else:
  46. w.writeln("panic!(\"Unimplemented action %s (%s)\");" % (type(a), a.render()))
  47. def compile_statechart(sc: Statechart, globals: Globals, w: IndentingWriter):
  48. # TODO: Do not create a StatechartInstance just to check if priorities are valid:
  49. from sccd.statechart.dynamic.statechart_instance import StatechartInstance
  50. StatechartInstance(sc, None, None, None, None) # may raise error if priorities are invalid
  51. tree = sc.tree
  52. # Note: The reason for the
  53. # #[allow(non_camel_case_types)]
  54. # lines is that we cannot convert the casing of our state's names:
  55. # SCCD allows any combination of upper and lower case symbols, and
  56. # converting to, say, camelcase, as Rust likes it for type names,
  57. # could cause naming collisions.
  58. # (these naming collisions would be detected by the Rust compiler, so the error would not go unnoticed,
  59. # still, it's better to NOT break our model :)
  60. # Write 'current state' types
  61. def write_state_type(state: State, children: List[State]):
  62. if isinstance(state, HistoryState):
  63. return None # we got no time for pseudo-states!
  64. w.writeln("#[allow(non_camel_case_types)]")
  65. w.writeln("#[derive(Copy, Clone)]")
  66. def as_struct():
  67. w.writeln("struct %s {" % ident_type(state))
  68. for child in children:
  69. if child is not None:
  70. w.writeln(" %s: %s," % (ident_field(child), ident_type(child)))
  71. w.writeln("}")
  72. def as_enum():
  73. w.writeln("enum %s {" % ident_type(state))
  74. for child in children:
  75. if child is not None:
  76. w.writeln(" %s(%s)," % (ident_enum_variant(child), ident_type(child)))
  77. w.writeln("}")
  78. if isinstance(state, ParallelState):
  79. w.writeln("// And-state")
  80. as_struct()
  81. elif isinstance(state, State):
  82. if len(state.children) > 0:
  83. w.writeln("// Or-state")
  84. as_enum() # Or-state
  85. else:
  86. # Basic state: write as empty struct
  87. #
  88. # An empty struct in Rust is a type with one possible value.
  89. # An empty struct is a Zero-Sized Type.
  90. #
  91. # An empty enum is also a valid type in Rust, but no instances
  92. # of it can be created. Also called an "uninhabited type".
  93. w.writeln("// Basic state")
  94. as_struct()
  95. # The above if-else construction hints at the fact that we would have
  96. # better used empty And-states to model basic states, instead of empty Or-states...
  97. w.writeln()
  98. return state
  99. # Write "default" constructor
  100. def write_default(state: State, children: List[State]):
  101. if isinstance(state, HistoryState):
  102. return None # we got no time for pseudo-states!
  103. # We use Rust's Default-trait to record default states,
  104. # this way, constructing a state instance without parameters will initialize it as the default state.
  105. w.writeln("impl Default for %s {" % ident_type(state))
  106. w.writeln(" fn default() -> Self {")
  107. if isinstance(state, ParallelState):
  108. w.writeln(" Self {")
  109. for child in children:
  110. if child is not None:
  111. w.writeln(" %s: Default::default()," % (ident_field(child)))
  112. w.writeln(" }")
  113. elif isinstance(state, State):
  114. if state.default_state is not None:
  115. # Or-state
  116. w.writeln(" Self::%s(Default::default())" % (ident_enum_variant(state.default_state)))
  117. else:
  118. # Basic state
  119. w.writeln(" Self{}")
  120. w.writeln(" }")
  121. w.writeln("}")
  122. w.writeln()
  123. return state
  124. # Write "enter/exit state" functions
  125. def write_enter_exit(state: State, children: List[State]):
  126. if isinstance(state, HistoryState):
  127. return None # we got no time for pseudo-states!
  128. w.writeln("impl<'a, OutputCallback: FnMut(&'a str, &'a str)> State<OutputCallback> for %s {" % ident_type(state))
  129. w.writeln(" fn enter_actions(%s: &mut OutputCallback) {" % IDENT_OC)
  130. w.writeln(" println!(\"enter %s\");" % state.opt.full_name);
  131. w.indent(); w.indent()
  132. compile_actions(state.enter, w)
  133. w.dedent(); w.dedent()
  134. w.writeln(" }")
  135. w.writeln(" fn exit_actions(%s: &mut OutputCallback) {" % IDENT_OC)
  136. w.writeln(" println!(\"exit %s\");" % state.opt.full_name);
  137. w.indent(); w.indent()
  138. compile_actions(state.exit, w)
  139. w.dedent(); w.dedent()
  140. w.writeln(" }")
  141. w.writeln(" fn enter_default(%s: &mut OutputCallback) {" % IDENT_OC)
  142. w.writeln(" %s::enter_actions(%s);" % (ident_type(state), IDENT_OC))
  143. if isinstance(state, ParallelState):
  144. for child in children:
  145. if child is not None:
  146. w.writeln(" %s::enter_default(%s);" % (ident_type(child), IDENT_OC))
  147. else:
  148. if state.default_state is not None:
  149. w.writeln(" %s::enter_default(%s);" % (ident_type(state.default_state), IDENT_OC))
  150. w.writeln(" }")
  151. w.writeln(" fn exit_current(&self, %s: &mut OutputCallback) {" % IDENT_OC)
  152. # Children's exit actions
  153. if isinstance(state, ParallelState):
  154. for child in children:
  155. if child is not None:
  156. w.writeln(" self.%s.exit_current(%s);" % (ident_field(child), IDENT_OC))
  157. else:
  158. if len(children) > 0:
  159. w.writeln(" match self {")
  160. for child in children:
  161. if child is not None:
  162. w.writeln(" Self::%s(s) => { s.exit_current(%s); }," % (ident_enum_variant(child), IDENT_OC))
  163. w.writeln(" }")
  164. # Our own exit actions
  165. w.writeln(" %s::exit_actions(%s);" % (ident_type(state), IDENT_OC))
  166. w.writeln(" }")
  167. w.writeln(" fn enter_current(&self, %s: &mut OutputCallback) {" % IDENT_OC)
  168. # Children's enter actions
  169. w.writeln(" %s::enter_actions(%s);" % (ident_type(state), IDENT_OC))
  170. # Our own enter actions
  171. if isinstance(state, ParallelState):
  172. for child in children:
  173. if child is not None:
  174. w.writeln(" self.%s.enter_current(%s);" % (ident_field(child), IDENT_OC))
  175. else:
  176. if len(children) > 0:
  177. w.writeln(" match self {")
  178. for child in children:
  179. if child is not None:
  180. w.writeln(" Self::%s(s) => { s.enter_current(%s); }," % (ident_enum_variant(child), IDENT_OC))
  181. w.writeln(" }")
  182. w.writeln(" }")
  183. w.writeln("}")
  184. w.writeln()
  185. return state
  186. visit_tree(tree.root, lambda s: s.children,
  187. child_first=[
  188. write_state_type,
  189. write_default,
  190. write_enter_exit,
  191. ])
  192. # Write event type
  193. w.writeln("#[allow(non_camel_case_types)]")
  194. w.writeln("#[derive(Copy, Clone)]")
  195. w.writeln("enum Event {")
  196. for event_name in (globals.events.names[i] for i in bm_items(sc.internal_events)):
  197. w.writeln(" %s," % event_name)
  198. w.writeln("}")
  199. w.writeln()
  200. # Write statechart type
  201. w.writeln("pub struct Statechart {")
  202. w.writeln(" current_state: %s," % ident_type(tree.root))
  203. for h in tree.history_states:
  204. w.writeln(" %s: %s," % (ident_history_field(h), ident_type(h.parent)))
  205. w.writeln(" // TODO: timers")
  206. w.writeln("}")
  207. w.writeln()
  208. w.writeln("impl Default for Statechart {")
  209. w.writeln(" fn default() -> Self {")
  210. w.writeln(" Self {")
  211. w.writeln(" current_state: Default::default(),")
  212. for h in tree.history_states:
  213. w.writeln(" %s: Default::default()," % (ident_history_field(h)))
  214. # w.writeln(" timers: Default::default(),")
  215. w.writeln(" }")
  216. w.writeln(" }")
  217. w.writeln("}")
  218. w.writeln()
  219. w.writeln("impl<'a, OutputCallback: FnMut(&'a str, &'a str)> SC<Event, OutputCallback> for Statechart {")
  220. w.writeln(" fn init(%s: &mut OutputCallback) {" % IDENT_OC)
  221. w.writeln(" %s::enter_default(%s);" % (ident_type(tree.root), IDENT_OC))
  222. w.writeln(" }")
  223. w.writeln(" fn fair_step(&mut self, _event: Option<Event>, %s: &mut OutputCallback) -> bool {" % IDENT_OC)
  224. w.writeln(" #![allow(non_snake_case)]")
  225. w.writeln(" #![allow(unused_labels)]")
  226. w.writeln(" #![allow(unused_variables)]")
  227. w.writeln(" println!(\"fair step\");")
  228. w.writeln(" let mut fired = false;")
  229. w.writeln(" let %s = &mut self.current_state;" % ident_var(tree.root))
  230. w.indent()
  231. w.indent()
  232. def write_transitions(state: State):
  233. if isinstance(state, HistoryState):
  234. return None # we got no time for pseudo-states!
  235. # Many of the states to exit can be computed statically (i.e. they are always the same)
  236. # The one we cannot compute statically are:
  237. #
  238. # (1) The descendants of S2, S3, etc. if S1 is part of the "exit path":
  239. #
  240. # A ---> And-state on exit path
  241. # / \ \
  242. # S1 S2 S3 ...
  243. #
  244. # |
  245. # +--> S1 also on exit path
  246. #
  247. # (2) The descendants of S, if S is the transition target
  248. #
  249. # The same applies to entering states.
  250. # Writes statements that perform exit actions
  251. # in the correct order (children (last to first), then parent) for given 'exit path'.
  252. def write_exit(exit_path: List[State]):
  253. # w.writeln("println!(\"exit path = %s\");" % str(exit_path).replace('"', "'"))
  254. if len(exit_path) > 0:
  255. s = exit_path[0] # state to exit
  256. if len(exit_path) == 1:
  257. # Exit s:
  258. w.writeln("%s.exit_current(%s);" % (ident_var(s), IDENT_OC))
  259. else:
  260. # Exit children:
  261. if isinstance(s, ParallelState):
  262. for c in reversed(s.children):
  263. if exit_path[1] is c:
  264. write_exit(exit_path[1:]) # continue recursively
  265. else:
  266. w.writeln("%s.exit_current(%s);" % (ident_var(c), IDENT_OC))
  267. elif isinstance(s, State):
  268. if s.default_state is not None:
  269. # Or-state
  270. write_exit(exit_path[1:]) # continue recursively with the next child on the exit path
  271. # Exit s:
  272. w.writeln("%s::exit_actions(%s);" % (ident_type(s), IDENT_OC))
  273. # Store history
  274. if s.opt.deep_history:
  275. _, _, h = s.opt.deep_history
  276. w.writeln("self.%s = *%s; // Store deep history" % (ident_history_field(h), ident_var(s)))
  277. if s.opt.shallow_history:
  278. _, h = s.opt.shallow_history
  279. if isinstance(s, ParallelState):
  280. raise Exception("Shallow history makes no sense for And-state!")
  281. w.writeln("self.%s = match %s { // Store shallow history" % (ident_history_field(h), ident_var(s)))
  282. for c in s.children:
  283. if not isinstance(c, HistoryState):
  284. w.writeln(" %s::%s(_) => %s::%s(%s::default())," % (ident_type(s), ident_enum_variant(c), ident_type(s), ident_enum_variant(c), ident_type(c)))
  285. w.writeln("};")
  286. # w.writeln("println!(\"recorded history\");")
  287. # Writes statements that perform enter actions
  288. # in the correct order (parent, children (first to last)) for given 'enter path'.
  289. def write_enter(enter_path: List[State]):
  290. if len(enter_path) > 0:
  291. s = enter_path[0] # state to enter
  292. if len(enter_path) == 1:
  293. # Target state.
  294. if isinstance(s, HistoryState):
  295. w.writeln("self.%s.enter_current(%s); // Enter actions for history state" %(ident_history_field(s), IDENT_OC))
  296. else:
  297. w.writeln("%s::enter_default(%s);" % (ident_type(s), IDENT_OC))
  298. else:
  299. # Enter s:
  300. w.writeln("%s::enter_actions(%s);" % (ident_type(s), IDENT_OC))
  301. # Enter children:
  302. if isinstance(s, ParallelState):
  303. for c in s.children:
  304. if enter_path[1] is c:
  305. # if not isinstance(c, HistoryState):
  306. # w.writeln("%s::enter_actions(%s);" % (ident_type(c), IDENT_OC))
  307. write_enter(enter_path[1:]) # continue recursively
  308. else:
  309. w.writeln("%s::enter_default(%s);" % (ident_type(c), IDENT_OC))
  310. elif isinstance(s, State):
  311. if len(s.children) > 0:
  312. write_enter(enter_path[1:]) # continue recursively with the next child on the enter path
  313. else:
  314. # If the following occurs, there's a bug in this source file
  315. raise Exception("Basic state in the middle of enter path")
  316. # The 'state' of a state is just a value in our compiled code.
  317. # When executing a transition, the value of the transition's arena changes.
  318. # This function writes statements that build a new value that can be assigned to the arena.
  319. def write_new_configuration(enter_path: List[State]):
  320. if len(enter_path) > 0:
  321. s = enter_path[0]
  322. if len(enter_path) == 1:
  323. # Construct target state.
  324. # And/Or/Basic state: Just construct the default value:
  325. w.writeln("let new_%s: %s = Default::default();" % (ident_var(s), ident_type(s)))
  326. else:
  327. next_child = enter_path[1]
  328. if isinstance(next_child, HistoryState):
  329. # No recursion
  330. w.writeln("let new_%s = self.%s; // Restore history value" % (ident_var(s), ident_history_field(next_child)))
  331. else:
  332. if isinstance(s, ParallelState):
  333. for c in s.children:
  334. if next_child is c:
  335. write_new_configuration(enter_path[1:]) # recurse
  336. else:
  337. # Other children's default states are constructed
  338. w.writeln("let new_%s: %s = Default::default();" % (ident_var(c), ident_type(c)))
  339. # Construct struct
  340. # if isinstance(next_child, HistoryState):
  341. # w.writeln("let new_%s = %s{%s:%s, ..Default::default()};" % (ident_var(s), ident_type(s), ident_field(next_child), ident_var(next_child)))
  342. # else:
  343. w.writeln("let new_%s = %s{%s:new_%s, ..Default::default()};" % (ident_var(s), ident_type(s), ident_field(next_child), ident_var(next_child)))
  344. elif isinstance(s, State):
  345. if len(s.children) > 0:
  346. # Or-state
  347. write_new_configuration(enter_path[1:]) # recurse
  348. # Construct enum value
  349. w.writeln("let new_%s = %s::%s(new_%s);" % (ident_var(s), ident_type(s), ident_enum_variant(next_child), ident_var(next_child)))
  350. else:
  351. # If the following occurs, there's a bug in this source file
  352. raise Exception("Basic state in the middle of enter path")
  353. def parent():
  354. for i, t in enumerate(state.transitions):
  355. w.writeln("// Outgoing transition %d" % i)
  356. if t.trigger is not EMPTY_TRIGGER:
  357. if len(t.trigger.enabling) > 1:
  358. raise Exception("Multi-event triggers currently unsupported")
  359. w.writeln("if let Some(Event::%s) = _event {" % t.trigger.enabling[0].name)
  360. w.indent()
  361. if t.guard is not None:
  362. raise Exception("Guard conditions currently unsupported")
  363. # 1. Execute transition's actions
  364. # Path from arena to source, including source but not including arena
  365. exit_path_bm = t.opt.arena.opt.descendants & (t.source.opt.state_id_bitmap | t.source.opt.ancestors) # bitmap
  366. exit_path = list(tree.bitmap_to_states(exit_path_bm)) # list of states
  367. # Path from arena to target, including target but not including arena
  368. enter_path_bm = t.opt.arena.opt.descendants & (t.target.opt.state_id_bitmap | t.target.opt.ancestors) # bitmap
  369. enter_path = list(tree.bitmap_to_states(enter_path_bm)) # list of states
  370. w.writeln("println!(\"fire %s\");" % str(t))
  371. w.writeln("// Exit actions")
  372. write_exit(exit_path)
  373. if len(t.actions) > 0:
  374. w.writeln("// Transition's actions")
  375. compile_actions(t.actions, w)
  376. w.writeln("// Enter actions")
  377. write_enter(enter_path)
  378. # 2. Update state
  379. # A state configuration is just a value
  380. w.writeln("// Build new state configuration")
  381. write_new_configuration([t.opt.arena] + enter_path)
  382. w.writeln("// Update arena configuration")
  383. w.writeln("*%s = new_%s;" % (ident_var(t.opt.arena), ident_var(t.opt.arena)))
  384. w.writeln("fired = true;")
  385. # This arena is done:
  386. w.writeln("break '%s;" % (ident_arena_label(t.opt.arena)))
  387. if t.trigger is not EMPTY_TRIGGER:
  388. w.dedent()
  389. w.writeln("}")
  390. def child():
  391. # Here is were we recurse and write the transition code for the children of our 'state'.
  392. if isinstance(state, ParallelState):
  393. for child in state.children:
  394. if not isinstance(child, HistoryState):
  395. w.writeln("// Orthogonal region")
  396. w.writeln("let %s = &mut %s.%s;" % (ident_var(child), ident_var(state), ident_field(child)))
  397. write_transitions(child)
  398. elif isinstance(state, State):
  399. if state.default_state is not None:
  400. w.writeln("'%s: loop {" % ident_arena_label(state))
  401. w.indent()
  402. w.writeln("match %s {" % ident_var(state))
  403. for child in state.children:
  404. if isinstance(child, HistoryState):
  405. continue
  406. w.indent()
  407. w.writeln("%s::%s(%s) => {" % (ident_type(state), ident_enum_variant(child), ident_var(child)))
  408. w.indent()
  409. write_transitions(child)
  410. w.dedent()
  411. w.writeln("},")
  412. w.dedent()
  413. w.writeln("};")
  414. w.writeln("break;")
  415. w.dedent()
  416. w.writeln("}")
  417. if sc.semantics.hierarchical_priority == HierarchicalPriority.SOURCE_PARENT:
  418. parent()
  419. child()
  420. elif sc.semantics.hierarchical_priority == HierarchicalPriority.SOURCE_CHILD:
  421. child()
  422. parent()
  423. elif sc.semantics.hierarchical_priority == HierarchicalPriority.NONE:
  424. # We're free to pick any semantics here, but let's not go too wild
  425. parent()
  426. child()
  427. else:
  428. raise Exception("Unsupported semantics %s" % sc.semantics.hierarchical_priority)
  429. write_transitions(tree.root)
  430. w.dedent()
  431. w.dedent()
  432. w.writeln(" fired")
  433. w.writeln(" }")
  434. def write_stepping_function(name: str, title: str, maximality: Maximality, substep: str, input_whole: bool):
  435. w.writeln(" fn %s(&mut self, event: Option<Event>, %s: &mut OutputCallback) -> bool {" % (name, IDENT_OC))
  436. w.writeln(" println!(\"%s\");" % name)
  437. if maximality == Maximality.TAKE_ONE:
  438. w.writeln(" // %s Maximality: Take One" % title)
  439. w.writeln(" self.fair_step(event, %s)" % IDENT_OC)
  440. elif maximality == Maximality.TAKE_MANY:
  441. w.writeln(" let mut fired = false;")
  442. w.writeln(" // %s Maximality: Take Many" % title)
  443. w.writeln(" let mut e = event;")
  444. w.writeln(" loop {")
  445. w.writeln(" let just_fired = self.%s(e, %s);" % (substep, IDENT_OC))
  446. w.writeln(" if !just_fired {")
  447. w.writeln(" break;")
  448. w.writeln(" }")
  449. w.writeln(" fired |= just_fired;")
  450. if not input_whole:
  451. w.writeln(" // Input Event Lifeline: %s" % sc.semantics.input_event_lifeline)
  452. w.writeln(" e = None;")
  453. w.writeln(" }")
  454. w.writeln(" fired")
  455. else:
  456. raise Exception("Unsupported semantics %s" % sc.semantics.big_step_maximality)
  457. w.writeln(" }")
  458. write_stepping_function("combo_step", "Combo-Step", sc.semantics.combo_step_maximality, substep="fair_step", input_whole = sc.semantics.input_event_lifeline == InputEventLifeline.WHOLE or
  459. sc.semantics.input_event_lifeline == InputEventLifeline.FIRST_COMBO_STEP)
  460. write_stepping_function("big_step", "Big-Step", sc.semantics.big_step_maximality, substep="combo_step",
  461. input_whole = sc.semantics.input_event_lifeline == InputEventLifeline.WHOLE)
  462. # w.writeln(" fn combo_step(&mut self, event: Option<Event>, %s: &mut OutputCallback) {" % IDENT_OC)
  463. # w.writeln(" ")
  464. # w.writeln(" }")
  465. w.writeln("}")
  466. w.writeln()