1
#![forbid(unsafe_code)]
2

            
3
use std::collections::HashMap;
4
use std::io::BufReader;
5
use std::io::BufWriter;
6
use std::io::Read;
7
use std::io::Write;
8

            
9
use log::debug;
10
use log::info;
11
use merc_aterm::ATerm;
12
use merc_aterm::ATermInt;
13
use merc_aterm::ATermList;
14
use merc_aterm::ATermRead;
15
use merc_aterm::ATermStreamable;
16
use merc_aterm::ATermWrite;
17
use merc_aterm::BinaryATermReader;
18
use merc_aterm::BinaryATermWriter;
19
use merc_aterm::Symbol;
20
use merc_aterm::is_list_term;
21
use merc_data::DataSpecification;
22
use merc_io::LargeFormatter;
23
use merc_io::TimeProgress;
24
use merc_utilities::MercError;
25

            
26
use crate::LTS;
27
use crate::LabelledTransitionSystem;
28
use crate::LtsBuilder;
29
use crate::LtsBuilderMem;
30
use crate::LtsMultiAction;
31
use crate::StateIndex;
32

            
33
/// Loads a labelled transition system from the binary 'lts' format of the mCRL2 toolset.
34
101
pub fn read_lts<R: Read>(
35
101
    reader: R,
36
101
    hidden_labels: Vec<String>,
37
101
    read_state_labels: bool,
38
101
) -> Result<LabelledTransitionSystem<LtsMultiAction>, MercError> {
39
101
    info!("Reading LTS in .lts format...");
40

            
41
101
    let mut reader = BinaryATermReader::new(BufReader::new(reader))?;
42

            
43
101
    if reader.read_aterm()? != Some(lts_marker()) {
44
        return Err("Stream does not contain a labelled transition system (LTS).".into());
45
101
    }
46

            
47
    // Read the data specification, parameters, and actions.
48
101
    let _data_spec = DataSpecification::read(&mut reader)?;
49
101
    let _parameters = reader.read_aterm()?;
50
101
    let _actions = reader.read_aterm()?;
51

            
52
    // Use a cache to avoid translating the same multi-action multiple times.
53
101
    let mut multi_actions: HashMap<ATerm, LtsMultiAction> = HashMap::new();
54

            
55
    // The initial state is not known yet.
56
101
    let mut initial_state: Option<StateIndex> = None;
57
101
    let mut builder = LtsBuilderMem::new(Vec::new(), hidden_labels);
58

            
59
101
    let progress = TimeProgress::new(
60
        |num_of_transitions| {
61
            info!("Read {num_of_transitions} transitions...");
62
        },
63
        1,
64
    );
65

            
66
101
    let mut state_labels = Vec::new();
67

            
68
    loop {
69
93211
        let term = reader.read_aterm()?;
70
93211
        match term {
71
93110
            Some(t) => {
72
93110
                if t == transition_marker() {
73
92935
                    let from: ATermInt = reader.read_aterm()?.ok_or("Missing from state")?.into();
74
92935
                    let label = reader.read_aterm()?.ok_or("Missing transition label")?;
75
92935
                    let to: ATermInt = reader.read_aterm()?.ok_or("Missing to state")?.into();
76

            
77
92935
                    if let Some(multi_action) = multi_actions.get(&label) {
78
                        // Multi-action already exists in the cache.
79
92616
                        builder.add_transition(
80
92616
                            StateIndex::new(from.value()),
81
92616
                            multi_action,
82
92616
                            StateIndex::new(to.value()),
83
                        )?;
84
                    } else {
85
                        // New multi-action found, add it to the builder.
86
319
                        let multi_action = LtsMultiAction::from_mcrl2_aterm(label.clone())?;
87
319
                        multi_actions.insert(label.clone(), multi_action.clone());
88
319
                        builder.add_transition(
89
319
                            StateIndex::new(from.value()),
90
319
                            &multi_action,
91
319
                            StateIndex::new(to.value()),
92
                        )?;
93
                    }
94

            
95
92935
                    progress.print(builder.num_of_transitions());
96
175
                } else if t == probabilistic_transition_mark() {
97
                    return Err("Probabilistic transitions are not supported yet.".into());
98
175
                } else if is_list_term(&t) {
99
74
                    if read_state_labels {
100
74
                        let t: ATermList<ATerm> = t.into();
101
74
                        debug!("Read state label: {:?}", t.to_vec());
102
74
                        state_labels.push(t);
103
                    }
104
101
                } else if t == initial_state_marker() {
105
101
                    let length = ATermInt::from(reader.read_aterm()?.ok_or("Missing initial state length")?).value();
106
101
                    if length != 1 {
107
                        return Err("Initial state length greater than 1 is not supported.".into());
108
101
                    }
109

            
110
101
                    initial_state = Some(StateIndex::new(
111
101
                        ATermInt::from(reader.read_aterm()?.ok_or("Missing initial state index")?).value(),
112
                    ));
113
101
                    debug!("Initial state: {:?}", initial_state);
114
                } else {
115
                    return Err(format!("Unexpected term in LTS stream: {}", t).into());
116
                }
117
            }
118
101
            None => break, // The default constructed term indicates the end of the stream.
119
        }
120
    }
121
101
    info!("Finished reading LTS.");
122

            
123
101
    builder.finish(initial_state.ok_or("Missing initial state")?)
124
101
}
125

            
126
/// Write a labelled transition system in binary 'lts' format to the given
127
/// writer. Requires that the labels are ATerm streamable. Note that the writer
128
/// is buffered internally using a `BufWriter`.
129
///
130
/// # Details
131
///
132
/// This format is built on top the ATerm binary format. The structure is as
133
/// follows:
134
///
135
/// ```plain
136
///     lts_marker: ATerm
137
///     data_spec: see [`merc_data::DataSpecification::write`]
138
///     parameters: ATermList
139
///     action_labels: ATermList
140
/// ```
141
///
142
/// Afterwards we can write the following elements in any order:
143
///
144
/// ```plain
145
/// initial state:
146
///    initial_state_marker: ATerm
147
///    state: ATermInt
148
///
149
/// transition:
150
///     transition_marker: ATerm
151
///     from: ATermInt
152
///     label: ATerm (the multi_action)
153
///     to: ATermInt
154
/// ```
155
///
156
/// state_label (index derived from order of appearance):
157
///    `state_label: ATermList::<DataExpression>`
158
100
pub fn write_lts<L, W>(writer: &mut W, lts: &L) -> Result<(), MercError>
159
100
where
160
100
    L: LTS<Label = LtsMultiAction>,
161
100
    W: Write,
162
{
163
100
    info!("Writing LTS in .lts format...");
164

            
165
100
    let mut writer = BinaryATermWriter::new(BufWriter::new(writer))?;
166

            
167
100
    writer.write_aterm(&lts_marker())?;
168

            
169
    // Write the data specification, parameters, and actions.
170
100
    DataSpecification::default().write(&mut writer)?;
171
100
    writer.write_aterm(&ATermList::<ATerm>::empty().into())?; // Empty parameters
172
100
    writer.write_aterm(&ATermList::<ATerm>::empty().into())?; // Empty action labels
173

            
174
    // Convert the internal multi-actions to the ATerm representation that mCRL2 expects.
175
100
    let label_terms = lts
176
100
        .labels()
177
100
        .iter()
178
300
        .map(|label| label.to_mcrl2_aterm())
179
100
        .collect::<Result<Vec<ATerm>, MercError>>()?;
180

            
181
    // Write the initial state.
182
100
    writer.write_aterm(&initial_state_marker())?;
183
100
    writer.write_aterm(&ATermInt::new(1))?; // Length of initial state is 1.
184
100
    writer.write_aterm(&ATermInt::new(*lts.initial_state_index()))?;
185

            
186
100
    let num_of_transitions = lts.num_of_transitions();
187
100
    let progress = TimeProgress::new(
188
        move |written: usize| {
189
            info!(
190
                "Wrote {} transitions ({}%)...",
191
                LargeFormatter(written),
192
                written * 100 / num_of_transitions
193
            );
194
        },
195
        1,
196
    );
197

            
198
100
    let mut written = 0;
199
10000
    for state in lts.iter_states() {
200
92843
        for transition in lts.outgoing_transitions(state) {
201
92843
            writer.write_aterm(&transition_marker())?;
202
92843
            writer.write_aterm(&ATermInt::new(*state))?;
203
92843
            writer.write_aterm(&label_terms[transition.label.value()])?;
204
92843
            writer.write_aterm(&ATermInt::new(*transition.to))?;
205

            
206
92843
            progress.print(written);
207
92843
            written += 1;
208
        }
209
    }
210

            
211
100
    info!("Finished writing LTS.");
212
100
    Ok(())
213
100
}
214

            
215
/// Returns the ATerm marker for a labelled transition system.
216
201
fn lts_marker() -> ATerm {
217
201
    ATerm::constant(&Symbol::new("labelled_transition_system", 0))
218
201
}
219

            
220
/// Returns the ATerm marker for a transition.
221
185953
fn transition_marker() -> ATerm {
222
185953
    ATerm::constant(&Symbol::new("transition", 0))
223
185953
}
224

            
225
/// Returns the ATerm marker for the initial state.
226
201
fn initial_state_marker() -> ATerm {
227
201
    ATerm::constant(&Symbol::new("initial_state", 0))
228
201
}
229

            
230
/// Returns the ATerm marker for the probabilistic transition.
231
175
fn probabilistic_transition_mark() -> ATerm {
232
175
    ATerm::constant(&Symbol::new("probabilistic_transition", 0))
233
175
}
234

            
235
#[cfg(test)]
236
mod tests {
237
    use merc_utilities::random_test;
238

            
239
    use crate::LTS;
240
    use crate::LtsMultiAction;
241
    use crate::random_lts_monolithic;
242
    use crate::read_lts;
243
    use crate::write_lts;
244

            
245
    #[test]
246
    #[cfg_attr(miri, ignore)] // Tests are too slow under miri.
247
1
    fn test_read_lts() {
248
1
        let lts = read_lts(include_bytes!("../../../examples/lts/abp.lts").as_ref(), vec![], true).unwrap();
249

            
250
1
        assert_eq!(lts.num_of_states(), 74);
251
1
        assert_eq!(lts.num_of_transitions(), 92);
252
1
        assert_eq!(*lts.initial_state_index(), 0);
253
1
    }
254

            
255
    #[test]
256
    #[cfg_attr(miri, ignore)]
257
1
    fn test_random_lts_io() {
258
100
        random_test(100, |rng| {
259
100
            let lts = random_lts_monolithic::<LtsMultiAction, _>(rng, 100, 3, 20);
260

            
261
100
            let mut buffer: Vec<u8> = Vec::new();
262
100
            write_lts(&mut buffer, &lts).unwrap();
263

            
264
100
            let result_lts = read_lts(&buffer[0..], vec![], false).unwrap();
265

            
266
100
            crate::check_equivalent(&lts, &result_lts);
267
100
        })
268
1
    }
269
}