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