Skip to main content

scan_scxml/
builder.rs

1//! Model builder for SCAN's XML specification format.
2
3mod expression;
4
5use self::expression::{expression, infer_type};
6use crate::parser::{
7    Executable, If, OmgBaseType, OmgType, OmgTypeDef, OmgTypes, Param, Parser, Scxml, Send, Target,
8};
9use anyhow::{Context, anyhow, bail};
10use boa_interner::Interner;
11use log::{info, trace, warn};
12use scan_core::{channel_system::*, *};
13use std::collections::{BTreeMap, HashMap, HashSet};
14
15// TODO:
16//
17// -[ ] WARN FIXME System is fragile if name/id/path do not coincide
18
19#[derive(Debug, Clone)]
20pub struct ScxmlModel {
21    // u16 here represents PgId
22    pub fsm_names: HashMap<u16, String>,
23    // usize here represents event index
24    pub parameters: HashMap<Channel, (PgId, PgId, usize)>,
25    pub int_queues: HashSet<Channel>,
26    pub ext_queues: HashMap<Channel, PgId>,
27    pub events: Vec<(String, Option<OmgTypeDef>)>,
28    pub ports: Vec<(String, OmgType, Vec<Type>)>,
29    pub assumes: Vec<String>,
30    pub guarantees: Vec<String>,
31    pub omg_types: OmgTypes,
32}
33
34#[derive(Debug, Clone)]
35struct FsmBuilder {
36    pg_id: PgId,
37    ext_queue: Channel,
38}
39
40#[derive(Debug, Clone)]
41struct EventBuilder {
42    // Associates parameter's name with its type's name.
43    name: String,
44    params: BTreeMap<String, Option<OmgType>>,
45    senders: HashSet<PgId>,
46    receivers: HashSet<PgId>,
47}
48
49/// Builder turning a [`Parser`] into a [`ChannelSystem`].
50#[derive(Default)]
51pub struct ModelBuilder {
52    cs: ChannelSystemBuilder,
53    // Associates a struct's id and field id with the index it is assigned in the struct's representation as a product.
54    // NOTE: This is decided arbitrarily and not imposed by the OMG type definition.
55    // QUESTION: Is there a better way?
56    // structs: HashMap<(String, String), usize>,
57    // Each State Chart has an associated Program Graph,
58    // and an arbitrary, progressive index
59    fsm_names: HashMap<u16, String>,
60    fsm_builders: HashMap<String, FsmBuilder>,
61    // Each event is associated to a unique global index and parameter(s).
62    // WARN FIXME TODO: name clashes
63    events: Vec<EventBuilder>,
64    event_indexes: HashMap<String, usize>,
65    parameter_channels: HashMap<(PgId, PgId, usize), Channel>,
66    // Properties
67    guarantees: Vec<(String, Pmtl<usize>)>,
68    assumes: Vec<(String, Pmtl<usize>)>,
69    predicates: Vec<Expression<Atom>>,
70    ports: HashMap<String, (OmgType, Vec<(Atom, Val)>)>,
71    // extra data
72    int_queues: HashSet<Channel>,
73}
74
75impl ModelBuilder {
76    /// Turns the [`Parser`] into a [`ChannelSystem`].
77    ///
78    /// Can fail if the model specification contains semantic errors
79    /// (particularly type mismatches)
80    /// or references to non-existing items.
81    pub fn build(
82        mut parser: Parser,
83        properties: &[String],
84        all_properties: bool,
85    ) -> anyhow::Result<(CsModel, PmtlOracle, ScxmlModel)> {
86        let mut model_builder = ModelBuilder::default();
87        model_builder
88            .prebuild_processes(&mut parser)
89            .context("failed prebuilding processes")?;
90
91        info!(target: "build", "Visit process list");
92        for (id, fsm) in parser.processes.iter() {
93            model_builder
94                .build_fsm(fsm, &mut parser.interner, &mut parser.types)
95                .with_context(|| format!("failed building FSM '{id}'"))?;
96        }
97
98        model_builder
99            .build_ports(&mut parser)
100            .context("failed building ports")?;
101        model_builder
102            .build_properties(&mut parser, properties, all_properties)
103            .context("failed building properties")?;
104
105        let model = model_builder.build_model(parser);
106
107        Ok(model)
108    }
109
110    fn event_index(&mut self, id: &str) -> usize {
111        self.event_indexes.get(id).cloned().unwrap_or_else(|| {
112            let index = self.events.len();
113            self.events.push(EventBuilder {
114                name: id.to_string(),
115                params: BTreeMap::new(),
116                senders: HashSet::new(),
117                receivers: HashSet::new(),
118            });
119            self.event_indexes.insert(id.to_owned(), index);
120            index
121        })
122    }
123
124    fn add_fsm_builder(&mut self, id: &str) -> anyhow::Result<&FsmBuilder> {
125        if self.fsm_builders.contains_key(id) {
126            bail!("FSM {id} aready exists");
127        } else {
128            let pg_id = self.cs.new_program_graph();
129            let ext_queue = self
130                .cs
131                .new_channel(vec![Type::Natural, Type::Natural], None);
132            let fsm = FsmBuilder { pg_id, ext_queue };
133            self.fsm_builders.insert(id.to_string(), fsm);
134            self.fsm_names.insert(pg_id.into(), id.to_string());
135        }
136        Ok(self.fsm_builders.get(id).expect("just inserted"))
137    }
138
139    fn prebuild_processes(&mut self, parser: &mut Parser) -> anyhow::Result<()> {
140        for (id, _fsm) in parser.processes.iter_mut() {
141            let _ = self.add_fsm_builder(id).expect("add FSM builder");
142        }
143        for (id, fsm) in parser.processes.iter_mut() {
144            let pg_id = self.fsm_builders.get(id).expect("just inserted").pg_id;
145            self.prebuild_fsm(pg_id, fsm, &parser.interner, &parser.types)
146                .with_context(|| format!("failed pre-processing of fsm {id}",))?;
147        }
148        for eb in &self.events {
149            for (param, t) in &eb.params {
150                if t.is_none() {
151                    bail!("param {param} of event {} needs type annotation", eb.name);
152                }
153            }
154        }
155        Ok(())
156    }
157
158    fn prebuild_fsm(
159        &mut self,
160        pg_id: PgId,
161        fmt: &mut Scxml,
162        interner: &Interner,
163        omg_types: &OmgTypes,
164    ) -> anyhow::Result<()> {
165        let mut vars: HashMap<String, OmgType> = HashMap::new();
166        for data in &fmt.datamodel {
167            if let Some(r#type) = &data.omg_type
168                // need to know len of arrays
169                && !matches!(r#type, OmgType::Array(_, None))
170            {
171                vars.insert(data.id.to_owned(), r#type.clone());
172            } else if let Some(expr) = data.expression.as_ref() {
173                let r#type = infer_type(expr, &vars, interner, data.omg_type.as_ref(), omg_types)?;
174                vars.insert(data.id.to_owned(), r#type);
175            }
176        }
177        for (_, state) in fmt.states.iter_mut() {
178            for exec in state.on_entry.iter_mut() {
179                self.prebuild_exec(pg_id, exec, &vars, interner, omg_types)
180                    .with_context(|| {
181                        format!(
182                            "failed pre-processing of executable on entry of state {}",
183                            state.id
184                        )
185                    })?;
186            }
187            for (index, transition) in state.transitions.iter_mut().enumerate() {
188                if let Some(ref event) = transition.event {
189                    // Event may or may not have been processed before
190                    let event_index = self.event_index(event);
191                    let builder = self.events.get_mut(event_index).expect("index must exist");
192                    builder.receivers.insert(pg_id);
193                }
194                for exec in transition.effects.iter_mut() {
195                    self.prebuild_exec(pg_id, exec, &vars, interner, omg_types).with_context(|| {
196                        format!("failed pre-processing of executable in transition {index} of state {}", state.id)
197                    })?;
198                }
199            }
200            for exec in state.on_exit.iter_mut() {
201                self.prebuild_exec(pg_id, exec, &vars, interner, omg_types)
202                    .with_context(|| {
203                        format!(
204                            "failed pre-processing of executable on exit of state {}",
205                            state.id
206                        )
207                    })?;
208            }
209        }
210        Ok(())
211    }
212
213    fn prebuild_exec(
214        &mut self,
215        pg_id: PgId,
216        executable: &mut Executable,
217        vars: &HashMap<String, OmgType>,
218        interner: &Interner,
219        omg_types: &OmgTypes,
220    ) -> anyhow::Result<()> {
221        match executable {
222            Executable::Assign {
223                location: _,
224                expr: _,
225            } => Ok(()),
226            Executable::Raise { event } => {
227                // Treat raised events as sent and received by the FSM itself.
228                // Raised events cannot have params.
229                let event_index = self.event_index(event);
230                let builder = self.events.get_mut(event_index).expect("index must exist");
231                builder.senders.insert(pg_id);
232                builder.receivers.insert(pg_id);
233                Ok(())
234            }
235            Executable::Send(Send {
236                event,
237                target,
238                delay: _,
239                params,
240            }) => {
241                let event_index = self.event_index(event);
242                // add FSM to event's senders
243                self.events
244                    .get_mut(event_index)
245                    .expect("index must exist")
246                    .senders
247                    .insert(pg_id);
248                // If target is given by Id, add it to event's receivers
249                // This is not possible with dynamic targets
250                if let Some(Target::Id(target)) = target {
251                    let target_id = if let Some(fsm) = self.fsm_builders.get(target) {
252                        fsm.pg_id
253                    } else {
254                        // WARN: If target FSM does not exist, we create a new one (which will only receive events)
255                        // as this might be the intended behavior for the model specification,
256                        // but we also raise a warning because the missing FSM might also be due to a mispelling of the target.
257                        let fsm_name = self
258                            .fsm_names
259                            .get(&pg_id.into())
260                            .ok_or_else(|| anyhow!("FSM {pg_id:?} not found"))?;
261                        warn!(
262                            "target FSM '{target}' for sent event '{event}' in FSM '{fsm_name}' does not exist; creating a new one",
263                        );
264                        self.add_fsm_builder(target)
265                            .expect("add new fsm builder")
266                            .pg_id
267                    };
268                    self.events
269                        .get_mut(event_index)
270                        .expect("index must exist")
271                        .receivers
272                        .insert(target_id);
273                }
274                for param in params {
275                    // Update OMG_type value so that it contains its type for sure
276                    let builder = self.events.get(event_index).expect("index must exist");
277                    if let Some(Some(t)) = builder.params.get(&param.name)
278                        // need to know len of arrays
279                        && !matches!(t, OmgType::Array(_, None))
280                    {
281                        if let Some(omg) = param.omg_type.as_ref() {
282                            if t != omg {
283                                bail!(
284                                    "type parameter mismatch: {t:?} != {omg:?} for parameter {}",
285                                    param.name
286                                );
287                            }
288                        } else {
289                            let _ = param.omg_type.insert(t.clone());
290                        }
291                    } else if let Some(t) = param.omg_type.as_ref()
292                        // need to know len of arrays
293                        && !matches!(t, OmgType::Array(_, None))
294                    {
295                        let builder = self.events.get_mut(event_index).expect("index must exist");
296                        builder.params.insert(param.name.clone(), Some(t.clone()));
297                    } else if let Ok(t) = infer_type(
298                        &param.expr,
299                        vars,
300                        interner,
301                        param.omg_type.as_ref(),
302                        omg_types,
303                    ) {
304                        let _ = param.omg_type.insert(t.clone());
305                        let builder = self.events.get_mut(event_index).expect("index must exist");
306                        builder.params.insert(param.name.clone(), Some(t));
307                    } else {
308                        // Mark with None parameters without known type
309                        let builder = self.events.get_mut(event_index).expect("index must exist");
310                        builder.params.insert(param.name.clone(), None);
311                    }
312                }
313                Ok(())
314            }
315            Executable::If(If {
316                r#elif: elifs,
317                r#else,
318                ..
319            }) => {
320                // preprocess all executables
321                for (_, executables) in elifs {
322                    for executable in executables {
323                        self.prebuild_exec(pg_id, executable, vars, interner, omg_types)
324                            .context("failed pre-processing executable content in <if> element")?;
325                    }
326                }
327                for executable in r#else.iter_mut().flatten() {
328                    self.prebuild_exec(pg_id, executable, vars, interner, omg_types)
329                        .context("failed pre-processing executable content in <else> element")?;
330                }
331                Ok(())
332            }
333        }
334    }
335
336    fn build_fsm(
337        &mut self,
338        scxml: &Scxml,
339        interner: &mut Interner,
340        omg_types: &mut OmgTypes,
341    ) -> anyhow::Result<()> {
342        trace!(target: "build", "build FSM {}", scxml.name);
343        // Initialize FSM.
344        let pg_builder = self
345            .fsm_builders
346            .get(&scxml.name)
347            .unwrap_or_else(|| panic!("builder for {} must already exist", scxml.name));
348        let pg_id = pg_builder.pg_id;
349        let ext_queue = pg_builder.ext_queue;
350        // Initialize variables from datamodel
351        // NOTE vars cannot be initialized using previously defined vars because datamodel is an HashMap
352        let mut vars: HashMap<String, (OmgType, Vec<(Var, Type)>)> = HashMap::new();
353        for data in scxml.datamodel.iter() {
354            let mut omg_type = data
355                .omg_type
356                .clone()
357                .ok_or_else(|| anyhow!("data {} has unknown type", data.id))?;
358            // Need to know len of array
359            if matches!(omg_type, OmgType::Array(_, None)) {
360                omg_type = data
361                    .expression
362                    .as_ref()
363                    .ok_or_else(|| anyhow!("expression for data '{}' required", data.id))
364                    .and_then(|expr| {
365                        infer_type(expr, &HashMap::new(), interner, Some(&omg_type), omg_types)
366                    })?;
367            }
368            let vars_types = if let Some(expr) = data.expression.as_ref() {
369                expression(expr, interner, &vars, Some(&omg_type), omg_types)?
370                    .iter()
371                    .map(|expr| {
372                        expr.eval_constant().map_err(CsError::Type).and_then(|val| {
373                            self.cs.new_var(pg_id, val).map(|var| (var, expr.r#type()))
374                        })
375                    })
376                    .collect::<Result<Vec<(Var, Type)>, _>>()?
377            } else {
378                omg_type
379                    .to_scan_types(omg_types).with_context(|| format!("failed converting type {omg_type:?} of location {} to Scan native types", data.id))?
380                    .into_iter()
381                    .map(|t| {
382                        (
383                            self.cs
384                                .new_var(pg_id, t.default_value())
385                                .expect("new var"),
386                            t,
387                        )
388                    })
389                    .collect::<Vec<(Var, Type)>>()
390            };
391            vars.insert(data.id.to_owned(), (omg_type.clone(), vars_types));
392        }
393        // Initial location of Program Graph.
394        let initial_loc = self
395            .cs
396            .new_initial_location(pg_id)
397            .expect("program graph must exist");
398        // Transition initializing datamodel variables.
399        // After initializing datamodel, transition to location representing point-of-entry of initial state of State Chart.
400        // Map FSM's state ids to corresponding CS's locations.
401        let mut states = HashMap::new();
402        // Conventionally, the entry-point for a state is a location associated to the id of the state.
403        states.insert(scxml.initial.to_owned(), initial_loc);
404        // Var representing the current event
405        // (use Integer::MAX as no-event flag)
406        let current_event_var = self
407            .cs
408            .new_var(pg_id, Val::from(Natural::MAX))
409            .expect("program graph exists!");
410        // Variable that will store origin of last processed event.
411        // (use Integer::MAX as no-origin flag)
412        let origin_var = self
413            .cs
414            .new_var(pg_id, Val::from(Natural::MAX))
415            .expect("program graph exists!");
416        // Implement internal queue
417        let int_queue = self.cs.new_channel(vec![Type::Natural], None);
418        // This we only need for backtracking.
419        let _ = self.int_queues.insert(int_queue);
420        let dequeue_int = self
421            .cs
422            .new_receive(pg_id, int_queue, vec![current_event_var])
423            .expect("hand-coded args");
424        // For events from the internal queue, origin is self
425        let set_int_origin = self.cs.new_action(pg_id).expect("program graph exists!");
426        self.cs
427            .add_effect(
428                pg_id,
429                set_int_origin,
430                origin_var,
431                CsExpression::from(u16::from(pg_id) as Natural),
432            )
433            .expect("hand-coded args");
434        // Implement external queue
435        let dequeue_ext = self
436            .cs
437            .new_receive(pg_id, ext_queue, vec![current_event_var, origin_var])
438            .expect("hand-coded args");
439
440        // Create variables and channels for the storage of the parameters sent by external events.
441        // Use BTreeMap to iter in fixed order
442        let mut params_vars: BTreeMap<(usize, String), (OmgType, Vec<(Var, Type)>)> =
443            BTreeMap::new(); // maps (event_idx, param_name) -> (omg_type, (param_vars, var_types))
444        let mut params_actions: HashMap<(PgId, usize), Action> = HashMap::new(); // maps (sender_pg_id, event) -> param_action
445        for (event_index, event_builder) in self
446            .events
447            .iter()
448            .enumerate()
449            // only consider events that can activate some transition and that some other process is sending.
450            .filter(|(_, eb)| eb.receivers.contains(&pg_id) && !eb.senders.is_empty())
451            .map(|(index, eb)| (index, eb.clone()))
452            // WARN TODO Necessary to satisfy the borrow checker but it should be possible to avoid cloning.
453            .collect::<Vec<_>>()
454        {
455            let mut param_vars_vec = Vec::new();
456            let mut param_types_vec = Vec::new();
457            // sorted in alphabetical order because of BTreeMap
458            for (param_name, param_type) in event_builder.params.iter() {
459                let param_omg_type = param_type
460                    .as_ref()
461                    .ok_or_else(|| anyhow!("type of param {param_name} not found"))?;
462                // Variables where to store parameter.
463                let param_vars_types = param_omg_type
464                    .to_scan_types(omg_types).with_context(|| format!("failed converting type {param_omg_type:?} of param {param_name} to Scan native types"))?
465                    .into_iter()
466                    .map(|t| {
467                        (
468                            self.cs
469                                .new_var(pg_id, t.default_value())
470                                .expect("new var"),
471                            t,
472                        )
473                    })
474                    .collect::<Vec<(Var, Type)>>();
475                param_vars_vec.extend(param_vars_types.iter().map(|(v, _)| *v));
476                param_types_vec.extend(param_vars_types.iter().map(|(_, t)| *t));
477                let old = params_vars.insert(
478                    (event_index, param_name.to_owned()),
479                    (param_omg_type.clone(), param_vars_types),
480                );
481                assert!(old.is_none());
482            }
483            if !param_vars_vec.is_empty() {
484                for &sender_id in event_builder.senders.iter() {
485                    // params_channel could have already been created by event sender
486                    let params_channel = *self
487                        .parameter_channels
488                        .entry((sender_id, pg_id, event_index))
489                        .or_insert_with(|| self.cs.new_channel(param_types_vec.clone(), None));
490                    // this will fail if params_channel has already been created by event sender with inconsistent typing
491                    let read = self
492                        .cs
493                        .new_receive(pg_id, params_channel, param_vars_vec.clone())
494                        .with_context(|| {
495                            format!(
496                                "failed building receiver for params of event '{}'",
497                                event_builder.name
498                            )
499                        })?;
500                    let old = params_actions.insert((sender_id, event_index), read);
501                    assert!(old.is_none());
502                }
503            }
504        }
505        // Make non-mut
506        let param_vars = params_vars;
507        let param_actions = params_actions;
508
509        // Consider each of the FSM's states
510        for (state_id, state) in scxml.states.iter() {
511            trace!(target: "build", "build state {state_id}");
512            // Each state is modeled by multiple locations connected by transitions
513            // A starting location is used as a point-of-entry to the execution of the state.
514            let start_loc = *states
515                .entry(state_id.to_owned())
516                .or_insert_with(|| self.cs.new_location(pg_id).expect("program graph exists!"));
517            let mut onentry_loc = start_loc;
518            // Execute the state's `onentry` executable content
519            for executable in state.on_entry.iter() {
520                // Each executable content attaches suitable transitions to the point-of-entry location
521                // and returns the target of such transitions as updated point-of-entry location.
522                onentry_loc = self
523                    .add_executable(
524                        executable,
525                        pg_id,
526                        int_queue,
527                        onentry_loc,
528                        &vars,
529                        interner,
530                        omg_types,
531                    )
532                    .with_context(|| {
533                        format!(
534                            "failed building executable content on entry of state {}",
535                            state.id
536                        )
537                    })?;
538            }
539            // Make immutable
540            let onentry_loc = onentry_loc;
541
542            // Location where autonomous/eventless/NULL transitions activate
543            let mut null_trans = onentry_loc;
544            // Location where internal events are dequeued
545            let int_queue_loc = self.cs.new_location(pg_id).expect("program graph exists!");
546            // Location where external events are dequeued
547            let ext_queue_loc = self.cs.new_location(pg_id).expect("program graph exists!");
548            // Location where eventful transitions activate
549            let mut eventful_trans = self.cs.new_location(pg_id).expect("program graph exists!");
550            // int_origin_loc will not be needed outside of this scope
551            {
552                // Location where the origin of internal events is set as own.
553                let int_origin_loc = self.cs.new_location(pg_id).expect("program graph exists!");
554                // Transition dequeueing a new internal event and searching for first active eventful transition
555                self.cs
556                    .add_transition(pg_id, int_queue_loc, dequeue_int, int_origin_loc, None)
557                    .expect("hand-coded args");
558                // Transition dequeueing a new internal event and searching for first active eventful transition
559                self.cs
560                    .add_transition(pg_id, int_origin_loc, set_int_origin, eventful_trans, None)
561                    .expect("hand-coded args");
562            }
563            // Action denoting checking if internal queue is empty;
564            // if so, move to external queue.
565            // Notice that one and only one of `int_dequeue` and `empty_int_queue` can be executed at a given time.
566            // empty_int_queue will not be needed outside of this scope
567            {
568                let empty_int_queue = self
569                    .cs
570                    .new_probe_empty_queue(pg_id, int_queue)
571                    .expect("hand-coded args");
572                self.cs
573                    .add_transition(pg_id, int_queue_loc, empty_int_queue, ext_queue_loc, None)
574                    .expect("hand-coded args");
575            }
576            // Location where parameters of events are read into suitable variables.
577            let ext_event_processing_param =
578                self.cs.new_location(pg_id).expect("program graph exists!");
579            // Dequeue a new external event and search for first active named transition.
580            self.cs
581                .add_transition(
582                    pg_id,
583                    ext_queue_loc,
584                    dequeue_ext,
585                    ext_event_processing_param,
586                    None,
587                )
588                .expect("hand-coded args");
589            // Keep track of all known events.
590            let mut known_events = Vec::new();
591            // Retrieve external event's parameters
592            // We need to set up the parameter-passing channel for every possible event that could be sent,
593            // from any possible other FSM,
594            // and for any parameter of the event.
595            for (event_index, event_builder) in self
596                .events
597                .iter()
598                .enumerate()
599                // only consider events that can activate some transition and that some other process is sending.
600                .filter(|(_, eb)| eb.receivers.contains(&pg_id) && !eb.senders.is_empty())
601            {
602                for &sender_id in &event_builder.senders {
603                    // Expression checking event and sender correspond to the given ones.
604                    let is_event_sender = BooleanExpr::NatEqual(
605                        NaturalExpr::from(event_index as Natural),
606                        NaturalExpr::Var(current_event_var),
607                    ) & BooleanExpr::NatEqual(
608                        NaturalExpr::from(u16::from(sender_id) as Natural),
609                        NaturalExpr::Var(origin_var),
610                    );
611                    // Add event (and sender) to list of known events.
612                    known_events.push(is_event_sender.to_owned());
613                    if let Some(&read_params) = param_actions.get(&(sender_id, event_index)) {
614                        self.cs
615                            .add_transition(
616                                pg_id,
617                                ext_event_processing_param,
618                                read_params,
619                                eventful_trans,
620                                Some(is_event_sender),
621                            )
622                            .expect("hand-coded args");
623                    } else {
624                        self.cs
625                            .add_autonomous_transition(
626                                pg_id,
627                                ext_event_processing_param,
628                                eventful_trans,
629                                Some(is_event_sender),
630                            )
631                            .expect("hand-coded args");
632                    }
633                }
634            }
635            // Proceed if event is unknown (without retrieving parameters).
636            let unknown_event = if known_events.is_empty() {
637                None
638            } else {
639                Some(!(BooleanExpr::Or(known_events)))
640            };
641            self.cs
642                .add_autonomous_transition(
643                    pg_id,
644                    ext_event_processing_param,
645                    eventful_trans,
646                    unknown_event,
647                )
648                .expect("has to work");
649
650            // Consider each of the state's transitions.
651            for (transition_index, transition) in state.transitions.iter().enumerate() {
652                // Skip if event is never sent/raised
653                if let Some(ref event_name) = transition.event {
654                    let event_index = *self
655                        .event_indexes
656                        .get(event_name)
657                        .expect("event must be registered");
658                    if self.events[event_index].senders.is_empty() {
659                        warn!(
660                            "event '{event_name}' in FSM '{}' is never sent, skipping",
661                            self.fsm_names.get(&pg_id.into()).expect("PG name")
662                        );
663                        continue;
664                    }
665                }
666                trace!(
667                    target: "build",
668                    "build {} transition to {}",
669                    transition
670                        .event.as_deref()
671                        .unwrap_or("eventless"),
672                    transition.target
673                );
674                // Get or create the location corresponding to the target state.
675                let target_loc = *states
676                    .entry(transition.target.to_owned())
677                    .or_insert_with(|| self.cs.new_location(pg_id).expect("pg_id should exist"));
678
679                // Set up origin and parameters for conditional/executable content.
680                if let Some(event_name) = transition.event.as_ref() {
681                    let event_index = *self
682                        .event_indexes
683                        .get(event_name)
684                        .expect("event must be registered");
685                    omg_types.type_defs.extend([
686                        (
687                            String::from("_EventDataType"),
688                            OmgTypeDef::Structure(BTreeMap::from_iter(
689                                param_vars
690                                    .iter()
691                                    .filter(|((ev_ix, _), _)| *ev_ix == event_index)
692                                    .map(|((_, param_name), (t, _))| {
693                                        (param_name.to_owned(), t.clone())
694                                    }),
695                            )),
696                        ),
697                        (
698                            String::from("_EventType"),
699                            OmgTypeDef::Structure(BTreeMap::from_iter([
700                                (String::from("origin"), OmgType::Base(OmgBaseType::Uri)),
701                                (
702                                    String::from("data"),
703                                    OmgType::Custom(String::from("_EventDataType")),
704                                ),
705                            ])),
706                        ),
707                    ]);
708                    let mut event_vars = self.events[event_index]
709                        .params
710                        .keys()
711                        .flat_map(|param_name| {
712                            &param_vars
713                                .get(&(event_index, param_name.clone()))
714                                .expect("param")
715                                .1
716                        })
717                        .cloned()
718                        .collect::<Vec<_>>();
719                    event_vars.push((origin_var, Type::Natural));
720                    vars.insert(
721                        String::from("_event"),
722                        (OmgType::Custom(String::from("_EventType")), event_vars),
723                    );
724                }
725                // Condition activating the transition.
726                // It has to be parsed/built as a Boolean expression.
727                // Could fail if `expr` is invalid.
728                let cond: Option<Vec<CsExpression>> = transition
729                    .cond
730                    .as_ref()
731                    .map(|cond| {
732                        expression(
733                            cond,
734                            interner,
735                            &vars,
736                            Some(&OmgBaseType::Boolean.into()),
737                            omg_types,
738                        ).with_context(|| format!("failed building conditional expression for transition #{transition_index} in state {}", state.id))
739                    })
740                    .transpose()?;
741                if cond.as_ref().is_some_and(|cond| cond.len() != 1) {
742                    bail!("condition is not a boolean expression");
743                }
744                let cond = cond.map(|cond| cond.first().expect("length 1").clone());
745                let cond = cond
746                    .map(|cond| {
747                        if let Expression::Boolean(bool_expr) = cond {
748                            Ok(bool_expr)
749                        } else {
750                            bail!("condition is not a boolean expression")
751                        }
752                    })
753                    .transpose()?;
754
755                // Location corresponding to checking if the transition is active.
756                // Has to be defined depending on the type of transition.
757                let check_trans_loc;
758                // Location corresponding to verifying the transition is not active and moving to next one.
759                let next_trans_loc = self.cs.new_location(pg_id).expect("{pg_id:?} exists");
760
761                // Guard for transition.
762                // Has to be defined depending on the type of transition, etc...
763                let guard;
764                // Proceed depending on whether the transition is eventless or activated by event.
765                if let Some(event_name) = transition.event.as_ref() {
766                    let event_index = *self
767                        .event_indexes
768                        .get(event_name)
769                        .expect("event must be registered");
770                    // Check if the current event (internal or external) corresponds to the event activating the transition.
771                    let event_match = BooleanExpr::NatEqual(
772                        NaturalExpr::Var(current_event_var),
773                        NaturalExpr::from(event_index as Natural),
774                    );
775                    // TODO FIXME: optimize And/Or expressions
776                    guard = Some(cond.map_or(event_match.clone(), |cond| event_match & cond));
777                    // Check this transition after the other eventful transitions.
778                    check_trans_loc = eventful_trans;
779                    // Move location of next eventful transitions to a new location.
780                    eventful_trans = next_trans_loc;
781                } else {
782                    // NULL (autonomous/eventless) transition
783                    // No event needs to happen in order to trigger this transition.
784                    guard = cond;
785                    // Check this transition after the other eventless transitions.
786                    check_trans_loc = null_trans;
787                    // Move location of next eventless transitions to a new location.
788                    null_trans = next_trans_loc;
789                }
790
791                // If transition is active, execute the relevant executable content and then the transition to the target.
792                // Could fail if 'cond' expression was not acceptable as guard.
793                let mut exec_trans_loc = self.cs.new_location(pg_id)?;
794                self.cs.add_autonomous_transition(
795                    pg_id,
796                    check_trans_loc,
797                    exec_trans_loc,
798                    guard.to_owned(),
799                )?;
800                // First execute the executable content of the state's `on_exit` tag,
801                // then that of the `transition` tag, following the specs.
802                for exec in state.on_exit.iter() {
803                    exec_trans_loc = self
804                        .add_executable(
805                            exec,
806                            pg_id,
807                            int_queue,
808                            exec_trans_loc,
809                            &vars,
810                            interner,
811                            omg_types,
812                        )
813                        .with_context(|| {
814                            format!(
815                                "failed building executable content on exit of state {}",
816                                state.id
817                            )
818                        })?;
819                }
820                for exec in transition.effects.iter() {
821                    exec_trans_loc = self
822                        .add_executable(
823                            exec,
824                            pg_id,
825                            int_queue,
826                            exec_trans_loc,
827                            &vars,
828                            interner,
829                            omg_types,
830                        )
831                        .with_context(|| {
832                            format!(
833                                "failed building executable content of transition #{transition_index} of state {}",
834                                state.id
835                            )
836                        })?;
837                }
838                // Transitioning to the target state/location.
839                // At this point, the transition cannot be stopped so there can be no guard.
840                self.cs
841                    .add_autonomous_transition(pg_id, exec_trans_loc, target_loc, None)
842                    .expect("has to work");
843                // If the current transition is not active, move on to check the next one.
844                // NOTE: an autonomous transition without cond is always active so there is no point processing further transitions.
845                // This happens in State Charts already, so we model it faithfully without optimizations.
846                if let Some(guard) = guard {
847                    self.cs
848                        .add_autonomous_transition(
849                            pg_id,
850                            check_trans_loc,
851                            next_trans_loc,
852                            Some(!guard),
853                        )
854                        .expect("cannot fail because guard was already checked");
855                }
856            }
857
858            // Connect NULL events with named events
859            // by transitioning from last "NUll" location to dequeuing event location.
860            self.cs
861                .add_autonomous_transition(pg_id, null_trans, int_queue_loc, None)?;
862            // Return to dequeue a new (internal or external) event.
863            self.cs
864                .add_autonomous_transition(pg_id, eventful_trans, int_queue_loc, None)?;
865        }
866        Ok(())
867    }
868
869    // WARN: vars and params have the same type so they could be easily swapped by mistake when calling the function.
870    fn add_executable(
871        &mut self,
872        executable: &Executable,
873        pg_id: PgId,
874        int_queue: Channel,
875        loc: Location,
876        vars: &HashMap<String, (OmgType, Vec<(Var, Type)>)>,
877        interner: &Interner,
878        omg_types: &mut OmgTypes,
879    ) -> Result<Location, anyhow::Error> {
880        match executable {
881            Executable::Raise { event } => {
882                // Create event, if it does not exist already.
883                let event_idx = self.event_index(event);
884                let raise = self.cs.new_send(
885                    pg_id,
886                    int_queue,
887                    vec![CsExpression::from(event_idx as Natural)],
888                )?;
889                let next_loc = self.cs.new_location(pg_id)?;
890                // queue the internal event
891                self.cs.add_transition(pg_id, loc, raise, next_loc, None)?;
892                Ok(next_loc)
893            }
894            Executable::Send(Send {
895                event,
896                target,
897                delay,
898                params: send_params,
899            }) => {
900                // params have to be ordered by name!
901                let mut send_params = send_params.clone();
902                send_params.sort_unstable_by_key(|p| p.name.clone());
903
904                let event_idx = *self
905                    .event_indexes
906                    .get(event)
907                    .ok_or(anyhow!("event not found"))?;
908                let mut loc = loc;
909                if let Some(delay) = delay {
910                    // WARN NOTE FIXME: here we could reuse some other clock instead of creating a new one every time.
911                    let clock = self.cs.new_clock(pg_id).expect("new clock");
912                    let reset = self.cs.new_action(pg_id).expect("action");
913                    self.cs.add_reset(pg_id, reset, clock).expect("add reset");
914                    let next_loc = self
915                        .cs
916                        .new_timed_location(pg_id, &[(clock, None, Some(*delay + 1))])
917                        .expect("PG exists");
918                    self.cs
919                        .add_transition(pg_id, loc, reset, next_loc, None)
920                        .expect("params are right");
921                    loc = next_loc;
922                    let next_loc = self.cs.new_location(pg_id).expect("PG exists");
923                    self.cs
924                        .add_autonomous_timed_transition(
925                            pg_id,
926                            loc,
927                            next_loc,
928                            None,
929                            &[(clock, Some(*delay), None)],
930                        )
931                        .expect("autonomous timed transition");
932                    loc = next_loc;
933                }
934                if let Some(target) = target {
935                    let done_loc = self.cs.new_location(pg_id)?;
936                    let targets;
937                    let target_expr;
938                    match target {
939                        Target::Id(target) => {
940                            let target_builder = self
941                                .fsm_builders
942                                .get(target)
943                                .ok_or(anyhow!(format!("target {target} not found")))?;
944                            targets = vec![target_builder.pg_id];
945                            target_expr =
946                                Some(NaturalExpr::from(u16::from(target_builder.pg_id) as Natural));
947                        }
948                        Target::Expr(targetexpr) => {
949                            let target_exprs = expression(
950                                targetexpr,
951                                interner,
952                                vars,
953                                Some(&OmgBaseType::Uri.into()),
954                                omg_types,
955                            ).with_context(|| format!("failed building target expression of <send event=\"{event}\"> element"))?;
956                            if target_exprs.len() != 1 {
957                                bail!("epression is not a target");
958                            }
959                            target_expr = if let Some(Expression::Natural(nat_expr)) =
960                                target_exprs.first().cloned()
961                            {
962                                Some(nat_expr)
963                            } else {
964                                bail!("targetexpr not a target expression")
965                            };
966                            targets = self.events[event_idx].receivers.iter().cloned().collect();
967                        }
968                    }
969                    for target_id in targets {
970                        let target_name = self.fsm_names.get(&target_id.into()).unwrap();
971                        let target_builder =
972                            self.fsm_builders.get(target_name).expect("it must exist");
973                        let target_ext_queue = target_builder.ext_queue;
974                        let send_event = self
975                            .cs
976                            .new_send(
977                                pg_id,
978                                target_ext_queue,
979                                vec![
980                                    CsExpression::from(event_idx as Natural),
981                                    CsExpression::from(u16::from(pg_id) as Natural),
982                                ],
983                            )
984                            .expect("params are hard-coded");
985
986                        // Send event and event origin before moving on to next location.
987                        let mut next_loc = self.cs.new_location(pg_id).expect("PG exists");
988                        self.cs
989                            .add_transition(
990                                pg_id,
991                                loc,
992                                send_event,
993                                next_loc,
994                                target_expr.as_ref().map(|target_expr| {
995                                    BooleanExpr::NatEqual(
996                                        NaturalExpr::from(u16::from(target_id) as Natural),
997                                        target_expr.to_owned(),
998                                    )
999                                }),
1000                            )
1001                            .expect("params are right");
1002
1003                        // Pass parameters. This could fail due to param content.
1004                        if !send_params.is_empty() {
1005                            // Updates next location.
1006                            next_loc = self
1007                                .send_params(
1008                                    pg_id,
1009                                    target_id,
1010                                    &send_params,
1011                                    event_idx,
1012                                    next_loc,
1013                                    vars,
1014                                    interner,
1015                                    omg_types,
1016                                )
1017                                .with_context(|| {
1018                                    format!("failed sending params for event '{event}'")
1019                                })?;
1020                        }
1021                        // Once sending event and args done, get to exit-point
1022                        self.cs
1023                            .add_autonomous_transition(pg_id, next_loc, done_loc, None)
1024                            .expect("hand-made args");
1025                    }
1026                    // Return exit point
1027                    Ok(done_loc)
1028                } else {
1029                    // WARN: This behavior is non-compliant with the SCXML specification
1030                    // An event sent without specifying the target is sent to all FSMs that can process it
1031                    let targets = self.events[event_idx]
1032                        .receivers
1033                        .iter()
1034                        .cloned()
1035                        .collect::<Vec<_>>();
1036                    let mut next_loc = loc;
1037                    for target in targets {
1038                        let target_name = self.fsm_names.get(&target.into()).cloned();
1039                        next_loc = self.add_executable(
1040                            &Executable::Send(Send {
1041                                event: event.to_owned(),
1042                                target: target_name.map(Target::Id),
1043                                delay: *delay,
1044                                params: send_params.to_owned(),
1045                            }),
1046                            pg_id,
1047                            int_queue,
1048                            next_loc,
1049                            vars,
1050                            interner,
1051                            omg_types,
1052                        )?;
1053                    }
1054                    Ok(next_loc)
1055                }
1056            }
1057            Executable::Assign { location, expr } => {
1058                // Add a transition that perform the assignment via the effect of the `assign` action.
1059                let (omg_type, scan_vars) =
1060                    vars.get(location).ok_or(anyhow!("undefined variable"))?;
1061                let expr = expression(expr, interner, vars, Some(omg_type), omg_types)
1062                    .with_context(|| {
1063                        format!(
1064                            "failed building expression in <assign> element for location {location}"
1065                        )
1066                    })?;
1067                let assign = self.cs.new_action(pg_id).expect("PG exists");
1068                scan_vars
1069                    .iter()
1070                    .zip(expr)
1071                    .try_for_each(|((var, scan_type), expr)| {
1072                        if expr.r#type() == *scan_type {
1073                            self.cs.add_effect(pg_id, assign, *var, expr)
1074                        } else {
1075                            Err(CsError::Type(TypeError::TypeMismatch))
1076                        }
1077                    })
1078                    .with_context(|| {
1079                        format!("failed building assignments for location '{location}'")
1080                    })?;
1081                let next_loc = self.cs.new_location(pg_id).unwrap();
1082                self.cs.add_transition(pg_id, loc, assign, next_loc, None)?;
1083                Ok(next_loc)
1084            }
1085            Executable::If(If { r#elif, r#else, .. }) => {
1086                // We go to this location after the if/elif/else block
1087                let end_loc = self.cs.new_location(pg_id).unwrap();
1088                let mut curr_loc = loc;
1089                for (cond, execs) in r#elif {
1090                    let mut next_loc = self.cs.new_location(pg_id).unwrap();
1091                    let cond = expression(
1092                        cond,
1093                        interner,
1094                        vars,
1095                        Some(&OmgBaseType::Boolean.into()),
1096                        omg_types,
1097                    )
1098                    .context("failed building condition expression in <if> element")?;
1099                    if cond.len() != 1 {
1100                        bail!("<cond> is not a boolean expression");
1101                    }
1102                    let cond = if let Expression::Boolean(bool_expr) =
1103                        cond.first().expect("len equals 1").clone()
1104                    {
1105                        bool_expr
1106                    } else {
1107                        bail!("targetexpr not a target expression")
1108                    };
1109                    self.cs.add_autonomous_transition(
1110                        pg_id,
1111                        curr_loc,
1112                        next_loc,
1113                        Some(cond.to_owned()),
1114                    )?;
1115                    for exec in execs {
1116                        next_loc = self
1117                            .add_executable(
1118                                exec, pg_id, int_queue, next_loc, vars, interner, omg_types,
1119                            )
1120                            .context("failed building executable content in <if> element")?;
1121                    }
1122                    // end of `if` branch, go to end_loc
1123                    self.cs
1124                        .add_autonomous_transition(pg_id, next_loc, end_loc, None)?;
1125                    // `elif/else` branch
1126                    let old_loc = curr_loc;
1127                    curr_loc = self.cs.new_location(pg_id).unwrap();
1128                    self.cs
1129                        .add_autonomous_transition(pg_id, old_loc, curr_loc, Some(!cond))
1130                        .unwrap();
1131                }
1132                // Add executables for `else` (if any)
1133                for executable in r#else.iter().flatten() {
1134                    curr_loc = self
1135                        .add_executable(
1136                            executable, pg_id, int_queue, curr_loc, vars, interner, omg_types,
1137                        )
1138                        .context("failed building executable content in <else> element")?;
1139                }
1140                self.cs
1141                    .add_autonomous_transition(pg_id, curr_loc, end_loc, None)?;
1142                Ok(end_loc)
1143            }
1144        }
1145    }
1146
1147    // WARN: vars and params have the same type so they could be easily swapped by mistake when calling the function.
1148    fn send_params(
1149        &mut self,
1150        pg_id: PgId,
1151        target_id: PgId,
1152        params: &[Param],
1153        event_idx: usize,
1154        param_loc: Location,
1155        vars: &HashMap<String, (OmgType, Vec<(Var, Type)>)>,
1156        interner: &Interner,
1157        omg_types: &mut OmgTypes,
1158    ) -> Result<Location, anyhow::Error> {
1159        // assert!(!params.is_empty());
1160        let mut exprs = Vec::new();
1161        for (p, param_type) in self.events[event_idx].params.clone().iter() {
1162            // Check that param is not missing
1163            if let Some(param) = params.iter().find(|param| param.name == *p) {
1164                assert_eq!(&param.omg_type, param_type);
1165                let param_type = param_type
1166                    .as_ref()
1167                    .ok_or_else(|| anyhow!("unknown type for param {p}"))?;
1168                // Build expression from ECMAScript expression.
1169                let expr = expression(&param.expr, interner, vars, Some(param_type), omg_types)
1170                    .with_context(|| {
1171                        format!(
1172                            "failed building expr for param '{}' of type {param_type:?}",
1173                            param.name
1174                        )
1175                    })?;
1176                exprs.extend_from_slice(&expr);
1177            } else {
1178                warn!("missing param {p}, sending default value");
1179                // bail!("missing param {p}");
1180                let expr = param_type
1181                    .as_ref()
1182                    .ok_or_else(|| anyhow!("unknown type for param {p}"))?
1183                    .to_scan_types(omg_types)
1184                    .with_context(|| format!("failed converting param '{p}' type to Scan types"))?;
1185                exprs.extend(
1186                    expr.iter()
1187                        .map(|scan_type| Expression::from(scan_type.default_value())),
1188                );
1189            }
1190        }
1191        // Retreive or create channel for parameter passing.
1192        let scan_types = exprs.iter().map(|expr| expr.r#type()).collect::<Vec<_>>();
1193        let param_chn = *self
1194            .parameter_channels
1195            .entry((pg_id, target_id, event_idx))
1196            .or_insert_with(|| self.cs.new_channel(scan_types, None));
1197        // Can return error if expr is badly typed
1198        let pass_param = self.cs.new_send(pg_id, param_chn, exprs)?;
1199        let next_loc = self.cs.new_location(pg_id).expect("PG exists");
1200        self.cs
1201            .add_transition(pg_id, param_loc, pass_param, next_loc, None)
1202            .expect("hand-made params are correct");
1203        Ok(next_loc)
1204    }
1205
1206    fn build_ports(&mut self, parser: &mut Parser) -> anyhow::Result<()> {
1207        for (port_id, port) in parser.properties.ports.iter() {
1208            let origin_builder = self
1209                .fsm_builders
1210                .get(&port.origin)
1211                .ok_or_else(|| anyhow!("missing origin fsm {} for port {port_id}", port.origin))?;
1212            let origin = origin_builder.pg_id;
1213            let target_builder = self
1214                .fsm_builders
1215                .get(&port.target)
1216                .ok_or_else(|| anyhow!("missing target fsm {} for port {port_id}", port.target))?;
1217            let target = target_builder.pg_id;
1218            let event_id = *self
1219                .event_indexes
1220                .get(&port.event)
1221                .ok_or_else(|| anyhow!("missing event {} for port {port_id}", port.event))?;
1222            let event_builder = &self.events[event_id];
1223            if let Some((param, init)) = &port.param {
1224                let param_start_idx = event_builder
1225                    .params
1226                    .range(..param.clone())
1227                    .map(|(_, omg_type)| {
1228                        omg_type
1229                            .as_ref()
1230                            .ok_or_else(|| {
1231                                anyhow!("type of param {param} for port {port_id} not found")
1232                            })
1233                            .and_then(|omg_type| omg_type.size(&parser.types))
1234                    })
1235                    .sum::<anyhow::Result<usize>>()?;
1236                let param_type = event_builder
1237                    .params
1238                    .get(param)
1239                    .ok_or_else(|| {
1240                        anyhow!(
1241                            "param {param} of event {} in port {port_id} not found",
1242                            port.event
1243                        )
1244                    })?
1245                    .clone();
1246                let init = expression::<Var>(
1247                    init,
1248                    &parser.interner,
1249                    &HashMap::new(),
1250                    param_type.as_ref(),
1251                    &mut parser.types,
1252                )
1253                .with_context(|| {
1254                    format!("failed building default value expression for port {port_id}")
1255                })?
1256                .iter()
1257                .map(|expr| expr.eval_constant())
1258                .collect::<Result<Vec<_>, _>>()
1259                .with_context(|| format!("failed evaluating default value for port {port_id}"))?;
1260                let channel = self
1261                    .parameter_channels
1262                    .get(&(origin, target, event_id))
1263                    .expect("parameters' channel for event in port");
1264                self.ports.insert(
1265                    port_id.to_owned(),
1266                    (
1267                        param_type.ok_or_else(|| {
1268                            anyhow!("type of param {param} of event {} not found", port.event)
1269                        })?,
1270                        init.iter()
1271                            .enumerate()
1272                            .map(|(param_idx, &init)| {
1273                                (Atom::State(*channel, param_start_idx + param_idx), init)
1274                            })
1275                            .collect(),
1276                    ),
1277                );
1278            } else {
1279                let channel = target_builder.ext_queue;
1280                self.ports.insert(
1281                    port_id.to_owned(),
1282                    (
1283                        OmgType::Base(OmgBaseType::Boolean),
1284                        vec![(
1285                            Atom::Event(Event {
1286                                pg_id: origin,
1287                                channel,
1288                                event_type: EventType::Send(
1289                                    vec![
1290                                        Val::Natural(event_id as Natural),
1291                                        Val::Natural(u16::from(origin) as Natural),
1292                                    ]
1293                                    .into(),
1294                                ),
1295                            }),
1296                            Val::Boolean(false),
1297                        )],
1298                    ),
1299                );
1300            }
1301        }
1302        Ok(())
1303    }
1304
1305    fn build_properties(
1306        &mut self,
1307        parser: &mut Parser,
1308        properties: &[String],
1309        all_properties: bool,
1310    ) -> anyhow::Result<()> {
1311        for predicate in parser.properties.predicates.iter() {
1312            let predicate = expression(
1313                predicate,
1314                &parser.interner,
1315                &self
1316                    .ports
1317                    .iter()
1318                    .map(|(name, (omg_type, atoms))| {
1319                        (
1320                            name.clone(),
1321                            (
1322                                omg_type.clone(),
1323                                atoms
1324                                    .iter()
1325                                    .map(|(atom, val)| (atom.clone(), val.r#type()))
1326                                    .collect(),
1327                            ),
1328                        )
1329                    })
1330                    .collect(),
1331                Some(&OmgType::Base(OmgBaseType::Boolean)),
1332                &mut parser.types,
1333            )
1334            .with_context(|| format!("failed building predicate {predicate:?}"))?;
1335            if predicate.len() != 1 {
1336                bail!("predicate must be a boolean expression");
1337            }
1338            let predicate = predicate[0].clone();
1339            self.predicates.push(predicate);
1340        }
1341        self.guarantees = parser
1342            .properties
1343            .guarantees
1344            .iter()
1345            .filter(|(name, _)| all_properties || properties.contains(name))
1346            .cloned()
1347            .collect();
1348        self.assumes = parser.properties.assumes.clone();
1349        Ok(())
1350    }
1351
1352    fn build_model(self, parser: Parser) -> (CsModel, PmtlOracle, ScxmlModel) {
1353        let mut model = CsModel::new(self.cs);
1354        let mut ports = Vec::new();
1355        for (port_name, (_omg_type, atoms)) in self.ports {
1356            // TODO FIXME handle error.
1357            // NOTE: all atoms in the same port must have the same channel
1358            if let Some((Atom::State(channel, _), _)) = atoms.first().cloned() {
1359                let (param_idxs, types): (Vec<usize>, _) = atoms
1360                    .into_iter()
1361                    .map(|(atom, init)| {
1362                        if let Atom::State(c, param_idx) = atom {
1363                            assert_eq!(channel, c);
1364                            model.add_port(channel, param_idx, init);
1365                            (param_idx, init.r#type())
1366                        } else {
1367                            panic!("all atoms are of state type")
1368                        }
1369                    })
1370                    .unzip();
1371                ports.push((channel, param_idxs, port_name, _omg_type, types));
1372            }
1373        }
1374        // Ports need to be sorted by channel or will not match state iterator
1375        ports.sort_by_key(|(c, idxs, ..)| (*c, *idxs.first().expect("at least a value")));
1376        let ports = ports
1377            .into_iter()
1378            .map(|(_, _, name, omg_type, types)| (name, omg_type, types))
1379            .collect();
1380        for pred_expr in self.predicates {
1381            // TODO FIXME handle error.
1382            let _id = model.add_predicate(pred_expr);
1383        }
1384        let (guarantee_names, guarantees): (Vec<_>, Vec<_>) = self.guarantees.into_iter().unzip();
1385        let (assume_names, assumes): (Vec<_>, Vec<_>) = self.assumes.into_iter().unzip();
1386        let oracle = PmtlOracle::new(assumes.as_slice(), guarantees.as_slice());
1387        let mut events = Vec::from_iter(self.event_indexes);
1388        events.sort_unstable_by_key(|(_, idx)| *idx);
1389        let events = events
1390            .into_iter()
1391            .enumerate()
1392            .map(|(enum_i, (name, idx))| {
1393                assert_eq!(enum_i, idx);
1394                let params = &self.events[idx].params;
1395                let params_types =
1396                    (!params.is_empty()).then_some(OmgTypeDef::Structure(BTreeMap::from_iter(
1397                        params
1398                            .iter()
1399                            .map(|(name, t)| (name.clone(), t.as_ref().unwrap().clone())),
1400                    )));
1401                (name, params_types)
1402            })
1403            .collect();
1404
1405        (
1406            model,
1407            oracle,
1408            ScxmlModel {
1409                fsm_names: self.fsm_names,
1410                parameters: self
1411                    .parameter_channels
1412                    .into_iter()
1413                    .map(|((src, trg, event), chn)| (chn, (src, trg, event)))
1414                    .collect(),
1415                ext_queues: self
1416                    .fsm_builders
1417                    .values()
1418                    .map(|b| (b.ext_queue, b.pg_id))
1419                    .collect(),
1420                int_queues: self.int_queues,
1421                events,
1422                ports,
1423                assumes: assume_names,
1424                guarantees: guarantee_names,
1425                omg_types: parser.types,
1426            },
1427        )
1428    }
1429}