Skip to main content

scan_scxml/
tracer.rs

1use crate::parser::{OmgBaseType, OmgType, OmgTypeDef, OmgTypes};
2
3use super::ScxmlModel;
4use scan_core::channel_system::{Action, Event, EventType};
5use scan_core::{Time, TraceWriter, Tracer, Val};
6
7#[derive(Debug)]
8pub struct TracePrinter {
9    writer: csv::Writer<TraceWriter>,
10}
11
12impl Drop for TracePrinter {
13    fn drop(&mut self) {
14        self.writer.flush().expect("flush writer");
15    }
16}
17
18impl TracePrinter {
19    const HEADER: [&'static str; 5] = ["Time", "Event", "Origin", "Target", "Params"];
20
21    fn format_state(&self, model: &ScxmlModel, event: &Event, ports: &[Vec<Val>]) -> Vec<String> {
22        // Assumes model.port_vals are ordered
23        model
24            .port_vars
25            .iter()
26            .map(move |(_, omg_type, exprs)| {
27                format_val(
28                    exprs
29                        .iter()
30                        .map(|expr| {
31                            expr.eval_deterministic(&|atom| match atom {
32                                scan_core::Atom::State(channel, i) => ports
33                                    .get(model.ports.binary_search(&channel).unwrap())
34                                    .unwrap()[i],
35                                scan_core::Atom::Event(channel) => {
36                                    if event.channel == channel
37                                        && let EventType::Send(_) = event.event_type
38                                    {
39                                        Val::from(true)
40                                    } else {
41                                        Val::from(false)
42                                    }
43                                }
44                            })
45                        })
46                        .collect::<Vec<_>>()
47                        .as_slice(),
48                    omg_type,
49                    &model.omg_types,
50                )
51            })
52            .collect()
53    }
54}
55
56impl Tracer for TracePrinter {
57    const EXTENSION: &'static str = "csv";
58
59    type ModelData = ScxmlModel;
60
61    fn init(writer: TraceWriter, data: &ScxmlModel) -> Self {
62        let mut writer = csv::Writer::from_writer(writer);
63        writer
64            .write_record(
65                Self::HEADER.into_iter().map(String::from).chain(
66                    data.port_vars.iter().map(|(name, omg_type, _)| {
67                        format!("{name}: {}", format_omg_type(omg_type))
68                    }),
69                ),
70            )
71            .expect("write header");
72
73        Self { writer }
74    }
75
76    fn trace(
77        &mut self,
78        data: &ScxmlModel,
79        _action: Action,
80        event: &Event,
81        time: Time,
82        ports: &[Vec<Val>],
83    ) {
84        let mut fields = Vec::new();
85        let time = time.to_string();
86        let origin_name;
87        let target_name;
88        let event_name;
89        let param_types;
90        let mut params = String::new();
91        fields.push(time.as_str());
92
93        if let Some((src, trg, event_idx)) = data.parameters.get(&event.channel) {
94            origin_name = data.fsm_names.get(&(*src).into()).unwrap().to_owned();
95            target_name = data.fsm_names.get(&(*trg).into()).unwrap().to_owned();
96            (event_name, param_types) = data.events.get(*event_idx).unwrap().clone();
97            if let EventType::Send(ref vals) = event.event_type {
98                params =
99                    format_val_from_def(vals, param_types.as_ref().unwrap(), &data.omg_types, true);
100            } else {
101                return;
102            }
103        } else if let Some(trg) = data.ext_queues.get(&event.channel) {
104            target_name = data.fsm_names.get(&(*trg).into()).unwrap().to_owned();
105            if let EventType::Send(ref vals) = event.event_type {
106                if let (Val::Natural(sent_event), Val::Natural(origin)) = (vals[0], vals[1]) {
107                    origin_name = data.fsm_names.get(&(origin as u16)).unwrap().to_owned();
108                    (event_name, param_types) = data.events[sent_event as usize].clone();
109                    if param_types.is_some() {
110                        // No need to trace this as parameters event already traced
111                        return;
112                    }
113                } else {
114                    panic!("events should be pairs");
115                }
116            } else {
117                return;
118            }
119        } else if data.int_queues.contains(&event.channel) {
120            origin_name = data.fsm_names.get(&event.pg_id.into()).unwrap().to_owned();
121            target_name = origin_name.clone();
122            if let EventType::Send(ref vals) = event.event_type {
123                if let Val::Natural(sent_event) = vals[0] {
124                    (event_name, param_types) = data.events[sent_event as usize].clone();
125                    if param_types.is_some() {
126                        // No need to trace this as parameters event already traced
127                        return;
128                    }
129                } else {
130                    panic!("events should be indexed by natural");
131                }
132            } else {
133                return;
134            }
135        } else {
136            panic!("Events should all be either internal or external events");
137        }
138
139        let state = self.format_state(data, event, ports);
140        self.writer
141            .write_record(
142                [time, event_name, origin_name, target_name, params]
143                    .into_iter()
144                    .chain(state),
145            )
146            .expect("write record");
147    }
148}
149
150fn format_omg_type(omg_type: &OmgType) -> String {
151    match omg_type {
152        OmgType::Base(omg_base_type) => format_omg_base_type(*omg_base_type).to_string(),
153        OmgType::Array(omg_base_type, _) => {
154            format!("[{}]", format_omg_base_type(*omg_base_type))
155        }
156        OmgType::Custom(name) => name.clone(),
157    }
158}
159
160fn format_omg_base_type(omg_base_type: OmgBaseType) -> &'static str {
161    match omg_base_type {
162        OmgBaseType::Boolean => "bool",
163        OmgBaseType::Int64 => "int32",
164        OmgBaseType::F64 => "float64",
165        OmgBaseType::Uri => "uri",
166        OmgBaseType::String => "string",
167        OmgBaseType::Uint64 => "uint64",
168    }
169}
170
171fn format_val(vals: &[Val], omg_type: &OmgType, omg_types: &OmgTypes) -> String {
172    match omg_type {
173        OmgType::Base(OmgBaseType::String) => {
174            if let Val::Natural(index) = vals
175                .first()
176                .expect("strings should be encoded as exactly one natural number variable")
177            {
178                format!(
179                    "'{}'",
180                    omg_types
181                        .get_string(*index as usize)
182                        .expect("all string codes should correspond to a string")
183                )
184            } else {
185                panic!("string not encoded as a natural number")
186            }
187        }
188        OmgType::Base(_omg_base_type) => format_base_val(vals[0]),
189        OmgType::Array(_omg_base_type, _len) => format!(
190            "{:?}",
191            vals.iter()
192                .map(|val: &Val| format_base_val(*val))
193                .collect::<Vec<String>>()
194        )
195        .replace("\"", ""),
196        OmgType::Custom(omg_name) => format!(
197            "{omg_name}: {}",
198            format_val_from_def(
199                vals,
200                omg_types.type_defs.get(omg_name).expect("type def"),
201                omg_types,
202                false
203            )
204        ),
205    }
206}
207
208fn format_val_from_def(
209    vals: &[Val],
210    omg_type: &OmgTypeDef,
211    omg_types: &OmgTypes,
212    spread_structs: bool,
213) -> String {
214    match omg_type {
215        OmgTypeDef::Enumeration(items) => {
216            if let Val::Natural(int) = vals[0] {
217                items[int as usize].clone()
218            } else {
219                panic!("enumeration is not represented as Natural")
220            }
221        }
222        OmgTypeDef::Structure(btree_map) => {
223            let mut prev_size_acc = 0;
224            let mut size_acc = 0;
225            let fields = btree_map
226                .iter()
227                .map(|(name, omg_type)| {
228                    let size = omg_type.size(omg_types).unwrap();
229                    prev_size_acc = size_acc;
230                    size_acc += size;
231                    let field = format_val(&vals[prev_size_acc..size_acc], omg_type, omg_types);
232                    format!("{name}: {field}")
233                })
234                .collect::<Vec<_>>();
235            if spread_structs {
236                // print on multiple lines
237                fields.join("\n")
238            } else {
239                format!("{fields:?}")
240            }
241            .replace("\"", "")
242        }
243    }
244}
245
246fn format_base_val(val: Val) -> String {
247    match val {
248        Val::Boolean(true) => "true".to_string(),
249        Val::Boolean(false) => "false".to_string(),
250        Val::Integer(i) => i.to_string(),
251        Val::Float(ordered_float) => ordered_float.to_string(),
252        Val::Natural(n) => n.to_string(),
253    }
254}