Skip to main content

vertep_core/
ir.rs

1//! The chart IR: the canonical, surface-independent representation of a statechart.
2//!
3//! Every surface (SCXML, RDF, and later YAML and the Rust DSL) reads into and writes from
4//! these types. The IR keeps expression text uninterpreted; it is evaluated only by a
5//! [`crate::datamodel::Datamodel`]. Source positions are carried in [`Span`] values, which
6//! compare equal to every other span so that derived equality is structural
7//! ([[openspec:chart-model#Chart Equality]]).
8
9use alloc::boxed::Box;
10use alloc::string::String;
11use alloc::vec::Vec;
12
13/// Uninterpreted expression text, evaluated by the chart's datamodel.
14pub type Expr = String;
15
16/// A source location in the document a node was read from.
17///
18/// Spans never take part in equality or hashing: two spans always compare equal.
19#[derive(Clone, Copy, Debug, Default)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct Span {
22    /// 1-based line, or 0 when unknown.
23    pub line: u32,
24    /// 1-based column, or 0 when unknown.
25    pub column: u32,
26}
27
28impl Span {
29    /// The span of a node that was not read from a document.
30    pub const NONE: Span = Span { line: 0, column: 0 };
31
32    /// A span at the given line and column.
33    pub const fn new(line: u32, column: u32) -> Self {
34        Span { line, column }
35    }
36
37    /// Whether this span carries a known position.
38    pub const fn is_known(&self) -> bool {
39        self.line != 0
40    }
41}
42
43impl PartialEq for Span {
44    fn eq(&self, _other: &Self) -> bool {
45        true
46    }
47}
48impl Eq for Span {}
49impl core::hash::Hash for Span {
50    fn hash<H: core::hash::Hasher>(&self, _state: &mut H) {}
51}
52
53/// The Vertep extension namespace, used in SCXML documents and as the ontology namespace.
54pub const VT_NS: &str = "https://w3id.org/vertep/vt#";
55/// The SCXML namespace.
56pub const SCXML_NS: &str = "http://www.w3.org/2005/07/scxml";
57
58/// An attribute or element from the `vt:` namespace or a foreign namespace, kept on its owner.
59// @tg: implements:: [[openspec:chart-model#Extension Nodes]]
60#[derive(Clone, Debug, PartialEq, Eq, Hash)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
62pub struct Extension {
63    /// Namespace IRI.
64    pub ns: String,
65    /// Local name.
66    pub local: String,
67    /// Attribute value or element content.
68    pub value: ExtensionValue,
69    /// For elements: the index among the owner's element children at which it appeared.
70    /// For attributes: 0.
71    pub position: u32,
72    /// Source location.
73    pub span: Span,
74}
75
76/// The content of an extension node.
77#[derive(Clone, Debug, PartialEq, Eq, Hash)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79pub enum ExtensionValue {
80    /// An attribute and its value.
81    Attribute(String),
82    /// An element, kept as its serialized XML (including its own start and end tags).
83    Element(String),
84}
85
86/// Binding mode for data items (W3C SCXML 5.3).
87#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89pub enum Binding {
90    /// All data items are created and initialized at session start.
91    #[default]
92    Early,
93    /// Data items are created at session start and initialized on first entry of their state.
94    Late,
95}
96
97/// The root of a chart: the `<scxml>` element.
98// @tg: implements:: [[openspec:chart-model#Chart Document Structure]]
99#[derive(Clone, Debug, PartialEq, Eq, Hash)]
100#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
101pub struct Chart {
102    /// The `name` attribute.
103    pub name: Option<String>,
104    /// The datamodel name; `"null"` when the document names none.
105    pub datamodel: String,
106    /// Whether the document stated the `datamodel` attribute explicitly.
107    pub datamodel_explicit: bool,
108    /// Binding mode.
109    pub binding: Binding,
110    /// Whether the document stated the `binding` attribute explicitly.
111    pub binding_explicit: bool,
112    /// The `initial` attribute: state identifiers, possibly empty.
113    pub initial: Vec<String>,
114    /// The top-level `<datamodel>` block.
115    pub data: Vec<Data>,
116    /// The optional top-level `<script>`.
117    pub script: Option<Script>,
118    /// Child states in document order.
119    pub states: Vec<State>,
120    /// Extension attributes and elements on `<scxml>`.
121    pub extensions: Vec<Extension>,
122    /// Source location.
123    pub span: Span,
124}
125
126impl Default for Chart {
127    fn default() -> Self {
128        Chart {
129            name: None,
130            datamodel: String::from("null"),
131            datamodel_explicit: false,
132            binding: Binding::Early,
133            binding_explicit: false,
134            initial: Vec::new(),
135            data: Vec::new(),
136            script: None,
137            states: Vec::new(),
138            extensions: Vec::new(),
139            span: Span::NONE,
140        }
141    }
142}
143
144/// History depth.
145#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
147pub enum HistoryType {
148    /// Remembers the active children of the parent.
149    #[default]
150    Shallow,
151    /// Remembers the active atomic descendants of the parent.
152    Deep,
153}
154
155/// The element a state was written as.
156#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
157#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
158pub enum StateElement {
159    /// `<state>`; atomic or compound depending on its children.
160    State,
161    /// `<parallel>`.
162    Parallel,
163    /// `<final>`.
164    Final,
165    /// `<history>` with its type.
166    History(HistoryType),
167}
168
169/// The classification of a state (W3C SCXML 3.1).
170// @tg: implements:: [[openspec:chart-model#State Kinds]]
171#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
172#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
173pub enum StateKind {
174    /// A `<state>` without child states.
175    Atomic,
176    /// A `<state>` with child states.
177    Compound,
178    /// A `<parallel>`.
179    Parallel,
180    /// A `<final>`.
181    Final,
182    /// A `<history>`.
183    History(HistoryType),
184}
185
186/// A state node of any kind.
187#[derive(Clone, Debug, PartialEq, Eq, Hash)]
188#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
189pub struct State {
190    /// The state identifier, declared or generated.
191    pub id: String,
192    /// True when `id` was generated because the document declared none.
193    pub id_generated: bool,
194    /// The element the state was written as.
195    pub element: StateElement,
196    /// The `initial` attribute (compound states only), possibly empty.
197    pub initial: Vec<String>,
198    /// The `<initial>` child element, if any.
199    pub initial_element: Option<Initial>,
200    /// Child states in document order (excluding the `<initial>` pseudo-state).
201    pub children: Vec<State>,
202    /// Outgoing transitions in document order. A history state has at most one: its default.
203    pub transitions: Vec<Transition>,
204    /// `<onentry>` handlers in document order.
205    pub onentry: Vec<Block>,
206    /// `<onexit>` handlers in document order.
207    pub onexit: Vec<Block>,
208    /// The state's `<datamodel>` block.
209    pub data: Vec<Data>,
210    /// `<invoke>` elements in document order.
211    pub invokes: Vec<Invoke>,
212    /// `<donedata>` (final states only).
213    pub donedata: Option<DoneData>,
214    /// Extension attributes and elements.
215    pub extensions: Vec<Extension>,
216    /// Source location.
217    pub span: Span,
218}
219
220impl State {
221    /// A new state with the given identifier and element and nothing else.
222    pub fn new(id: impl Into<String>, element: StateElement) -> Self {
223        State {
224            id: id.into(),
225            id_generated: false,
226            element,
227            initial: Vec::new(),
228            initial_element: None,
229            children: Vec::new(),
230            transitions: Vec::new(),
231            onentry: Vec::new(),
232            onexit: Vec::new(),
233            data: Vec::new(),
234            invokes: Vec::new(),
235            donedata: None,
236            extensions: Vec::new(),
237            span: Span::NONE,
238        }
239    }
240
241    /// The state's kind, derived from its element and children.
242    pub fn kind(&self) -> StateKind {
243        match self.element {
244            StateElement::State if self.children.is_empty() => StateKind::Atomic,
245            StateElement::State => StateKind::Compound,
246            StateElement::Parallel => StateKind::Parallel,
247            StateElement::Final => StateKind::Final,
248            StateElement::History(h) => StateKind::History(h),
249        }
250    }
251}
252
253/// The `<initial>` pseudo-state of a compound state.
254#[derive(Clone, Debug, PartialEq, Eq, Hash)]
255#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
256pub struct Initial {
257    /// Its single transition (no event, no condition).
258    pub transition: Transition,
259    /// Extension attributes and elements.
260    pub extensions: Vec<Extension>,
261    /// Source location.
262    pub span: Span,
263}
264
265/// Transition type (W3C SCXML 3.13).
266#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
267#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
268pub enum TransitionType {
269    /// Exits the source state when targets are its descendants (the default).
270    #[default]
271    External,
272    /// Does not exit a compound source when all targets are its descendants.
273    Internal,
274}
275
276/// A transition.
277// @tg: implements:: [[openspec:chart-model#Transition Structure]]
278#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
279#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
280pub struct Transition {
281    /// Event descriptors, possibly empty (an eventless transition).
282    pub events: Vec<String>,
283    /// The guard.
284    pub cond: Option<Expr>,
285    /// Target state identifiers, possibly empty (a targetless transition).
286    pub targets: Vec<String>,
287    /// Internal or external.
288    pub kind: TransitionType,
289    /// Whether the document stated `type` explicitly.
290    pub kind_explicit: bool,
291    /// The transition's executable content.
292    pub body: Block,
293    /// Extension attributes and elements.
294    pub extensions: Vec<Extension>,
295    /// Source location.
296    pub span: Span,
297}
298
299/// An ordered block of executable content.
300pub type Block = Vec<Exec>;
301
302/// One executable content element.
303// @tg: implements:: [[openspec:chart-model#Executable Content Model]]
304#[derive(Clone, Debug, PartialEq, Eq, Hash)]
305#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
306pub struct Exec {
307    /// The element.
308    pub kind: ExecKind,
309    /// Extension attributes on the element.
310    pub extensions: Vec<Extension>,
311    /// Source location.
312    pub span: Span,
313}
314
315impl Exec {
316    /// An element without extensions or span.
317    pub fn new(kind: ExecKind) -> Self {
318        Exec {
319            kind,
320            extensions: Vec::new(),
321            span: Span::NONE,
322        }
323    }
324}
325
326/// The executable content elements of SCXML.
327#[derive(Clone, Debug, PartialEq, Eq, Hash)]
328#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
329pub enum ExecKind {
330    /// `<raise event>`.
331    Raise {
332        /// Event name.
333        event: String,
334    },
335    /// `<send>`.
336    Send(Box<Send>),
337    /// `<cancel>`.
338    Cancel {
339        /// `sendid`.
340        sendid: Option<String>,
341        /// `sendidexpr`.
342        sendidexpr: Option<Expr>,
343    },
344    /// `<log>`.
345    Log {
346        /// `label`.
347        label: Option<String>,
348        /// `expr`.
349        expr: Option<Expr>,
350    },
351    /// `<assign>`.
352    Assign {
353        /// `location`.
354        location: Expr,
355        /// `expr`.
356        expr: Option<Expr>,
357        /// Inline content (raw text or XML).
358        content: Option<String>,
359    },
360    /// `<script>`.
361    Script(Script),
362    /// `<if>` with its `<elseif>` and `<else>` branches, in document order. The `else`
363    /// branch, if present, is last and has no condition.
364    If {
365        /// Branches.
366        branches: Vec<IfBranch>,
367    },
368    /// `<foreach>`.
369    Foreach {
370        /// `array`.
371        array: Expr,
372        /// `item`.
373        item: String,
374        /// `index`.
375        index: Option<String>,
376        /// Body.
377        body: Block,
378    },
379    /// An executable content element from the `vt:` or a foreign namespace.
380    Extension(Extension),
381}
382
383/// One branch of an `<if>`.
384#[derive(Clone, Debug, PartialEq, Eq, Hash)]
385#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
386pub struct IfBranch {
387    /// The condition; `None` for `<else>`.
388    pub cond: Option<Expr>,
389    /// The branch body.
390    pub body: Block,
391    /// Source location of the `<if>`, `<elseif>` or `<else>`.
392    pub span: Span,
393}
394
395/// `<send>` (W3C SCXML 6.2).
396#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
397#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
398pub struct Send {
399    /// `event`.
400    pub event: Option<String>,
401    /// `eventexpr`.
402    pub eventexpr: Option<Expr>,
403    /// `target`.
404    pub target: Option<String>,
405    /// `targetexpr`.
406    pub targetexpr: Option<Expr>,
407    /// `type`.
408    pub type_: Option<String>,
409    /// `typeexpr`.
410    pub typeexpr: Option<Expr>,
411    /// `id`.
412    pub id: Option<String>,
413    /// `idlocation`.
414    pub idlocation: Option<Expr>,
415    /// `delay`.
416    pub delay: Option<String>,
417    /// `delayexpr`.
418    pub delayexpr: Option<Expr>,
419    /// `namelist`, split on whitespace.
420    pub namelist: Vec<String>,
421    /// `<param>` children.
422    pub params: Vec<Param>,
423    /// `<content>` child.
424    pub content: Option<Content>,
425}
426
427/// `<param>`.
428#[derive(Clone, Debug, PartialEq, Eq, Hash)]
429#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
430pub struct Param {
431    /// `name`.
432    pub name: String,
433    /// `expr`.
434    pub expr: Option<Expr>,
435    /// `location`.
436    pub location: Option<Expr>,
437    /// Source location.
438    pub span: Span,
439}
440
441/// `<content>`.
442#[derive(Clone, Debug, PartialEq, Eq, Hash)]
443#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
444pub struct Content {
445    /// `expr`.
446    pub expr: Option<Expr>,
447    /// Inline children as raw text or XML.
448    pub body: Option<String>,
449    /// Source location.
450    pub span: Span,
451}
452
453/// `<script>`.
454#[derive(Clone, Debug, PartialEq, Eq, Hash)]
455#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
456pub struct Script {
457    /// `src`, resolved by the reader's resolver into `body` when possible.
458    pub src: Option<String>,
459    /// Inline script text.
460    pub body: Option<String>,
461    /// Source location.
462    pub span: Span,
463}
464
465/// A `<data>` item.
466// @tg: implements:: [[openspec:chart-model#Datamodel Declaration]]
467#[derive(Clone, Debug, PartialEq, Eq, Hash)]
468#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
469pub struct Data {
470    /// `id`.
471    pub id: String,
472    /// `src`.
473    pub src: Option<String>,
474    /// `expr`.
475    pub expr: Option<Expr>,
476    /// Inline content as raw text or XML.
477    pub content: Option<String>,
478    /// Extension attributes and elements.
479    pub extensions: Vec<Extension>,
480    /// Source location.
481    pub span: Span,
482}
483
484/// `<donedata>`.
485#[derive(Clone, Debug, PartialEq, Eq, Hash)]
486#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
487pub struct DoneData {
488    /// `<content>`.
489    pub content: Option<Content>,
490    /// `<param>` children.
491    pub params: Vec<Param>,
492    /// Source location.
493    pub span: Span,
494}
495
496/// `<invoke>` (W3C SCXML 6.4).
497// @tg: implements:: [[openspec:chart-model#Invoke Declaration]]
498#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
499#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
500pub struct Invoke {
501    /// `type`.
502    pub type_: Option<String>,
503    /// `typeexpr`.
504    pub typeexpr: Option<Expr>,
505    /// `src`.
506    pub src: Option<String>,
507    /// `srcexpr`.
508    pub srcexpr: Option<Expr>,
509    /// `id`.
510    pub id: Option<String>,
511    /// `idlocation`.
512    pub idlocation: Option<Expr>,
513    /// `namelist`, split on whitespace.
514    pub namelist: Vec<String>,
515    /// `autoforward`.
516    pub autoforward: bool,
517    /// `<param>` children.
518    pub params: Vec<Param>,
519    /// `<content>` child.
520    pub content: Option<Content>,
521    /// `<finalize>` block.
522    pub finalize: Block,
523    /// Extension attributes and elements.
524    pub extensions: Vec<Extension>,
525    /// Source location.
526    pub span: Span,
527}