vertep_core/order.rs
1//! Document order over the chart IR.
2//!
3//! The IR keeps every ordered collection (children, transitions, targets, executable content
4//! blocks) in source order, so document order is a pre-order walk of the tree. The walk visits a
5//! state, then its `<initial>` transition, its `onentry` and `onexit` blocks, its transitions
6//! (each followed by its content), its invokes' `finalize` blocks, and finally its child states.
7//! Positions are the indices of the returned sequence.
8//!
9//! States alone keep the same relative order in the compiled chart, whose dense state indices are
10//! document positions restricted to states.
11
12use alloc::vec::Vec;
13
14use crate::ir::{Block, Chart, Exec, ExecKind, State, Transition};
15
16/// A node visited by the document-order walk.
17#[derive(Clone, Copy, Debug)]
18pub enum DocNode<'a> {
19 /// A state of any kind.
20 State(&'a State),
21 /// A transition declared on a state, with its index in the state's transition list.
22 Transition {
23 /// Identifier of the source state.
24 source: &'a str,
25 /// Index in `State::transitions`.
26 index: usize,
27 /// The transition.
28 transition: &'a Transition,
29 },
30 /// The transition of a state's `<initial>` element.
31 InitialTransition {
32 /// Identifier of the owning compound state.
33 state: &'a str,
34 /// The transition.
35 transition: &'a Transition,
36 },
37 /// One executable content element.
38 Exec(&'a Exec),
39}
40
41/// Every state, transition and executable content element of `chart` in document order; the
42/// position of a node is its index in the result.
43// @tg: implements:: [[openspec:chart-model#Document Order]]
44pub fn document_order(chart: &Chart) -> Vec<DocNode<'_>> {
45 let mut out = Vec::new();
46 for s in &chart.states {
47 walk_state(s, &mut out);
48 }
49 out
50}
51
52/// The document position of every state, in the same pre-order as [`document_order`]. The
53/// result lists state identifiers; a state's position is its index.
54pub fn state_order(chart: &Chart) -> Vec<&str> {
55 let mut out = Vec::new();
56 fn go<'a>(s: &'a State, out: &mut Vec<&'a str>) {
57 out.push(s.id.as_str());
58 for c in &s.children {
59 go(c, out);
60 }
61 }
62 for s in &chart.states {
63 go(s, &mut out);
64 }
65 out
66}
67
68fn walk_state<'a>(s: &'a State, out: &mut Vec<DocNode<'a>>) {
69 out.push(DocNode::State(s));
70 if let Some(init) = &s.initial_element {
71 out.push(DocNode::InitialTransition {
72 state: s.id.as_str(),
73 transition: &init.transition,
74 });
75 walk_block(&init.transition.body, out);
76 }
77 for b in &s.onentry {
78 walk_block(b, out);
79 }
80 for b in &s.onexit {
81 walk_block(b, out);
82 }
83 for (index, t) in s.transitions.iter().enumerate() {
84 out.push(DocNode::Transition {
85 source: s.id.as_str(),
86 index,
87 transition: t,
88 });
89 walk_block(&t.body, out);
90 }
91 for inv in &s.invokes {
92 walk_block(&inv.finalize, out);
93 }
94 for c in &s.children {
95 walk_state(c, out);
96 }
97}
98
99fn walk_block<'a>(b: &'a Block, out: &mut Vec<DocNode<'a>>) {
100 for e in b {
101 out.push(DocNode::Exec(e));
102 match &e.kind {
103 ExecKind::If { branches } => {
104 for br in branches {
105 walk_block(&br.body, out);
106 }
107 }
108 ExecKind::Foreach { body, .. } => walk_block(body, out),
109 _ => {}
110 }
111 }
112}
113
114/// Structural chart equality: every node, attribute, expression text, extension node and
115/// document order is compared, source locations are not (a [`crate::ir::Span`] equals every
116/// other span). Two charts are equal exactly when every surface serializes them identically.
117// @tg: implements:: [[openspec:chart-model#Chart Equality]]
118pub fn same_chart(a: &Chart, b: &Chart) -> bool {
119 a == b
120}