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