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(
35
101
    reader: impl Read,
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
92613
        let term = reader.read_aterm()?;
70
92613
        match term {
71
92512
            Some(t) => {
72
92512
                if t == transition_marker() {
73
92337
                    let from: ATermInt = reader.read_aterm()?.ok_or("Missing from state")?.into();
74
92337
                    let label = reader.read_aterm()?.ok_or("Missing transition label")?;
75
92337
                    let to: ATermInt = reader.read_aterm()?.ok_or("Missing to state")?.into();
76

            
77
92337
                    if let Some(multi_action) = multi_actions.get(&label) {
78
                        // Multi-action already exists in the cache.
79
92018
                        builder.add_transition(
80
92018
                            StateIndex::new(from.value()),
81
92018
                            multi_action,
82
92018
                            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
92337
                    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>(writer: &mut impl Write, lts: &L) -> Result<(), MercError>
159
100
where
160
100
    L: LTS<Label = LtsMultiAction>,
161
{
162
100
    info!("Writing LTS in .lts format...");
163

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

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

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

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

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

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

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

            
205
92245
            progress.print(written);
206
92245
            written += 1;
207
        }
208
    }
209

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

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

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

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

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

            
234
#[cfg(test)]
235
mod tests {
236
    use super::*;
237

            
238
    use merc_utilities::random_test;
239

            
240
    use crate::LTS;
241
    use crate::random_lts_monolithic;
242

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

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

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

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

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

            
264
100
            crate::check_equivalent(&lts, &result_lts);
265
100
        })
266
1
    }
267
}