1
#![forbid(unsafe_code)]
2

            
3
use std::io::BufWriter;
4
use std::io::Read;
5
use std::io::Write;
6

            
7
use log::info;
8
use merc_io::LargeFormatter;
9
use regex::Regex;
10
use streaming_iterator::StreamingIterator;
11
use thiserror::Error;
12

            
13
use merc_io::LineIterator;
14
use merc_io::TimeProgress;
15
use merc_utilities::MercError;
16
use merc_utilities::debug_trace;
17

            
18
use crate::LTS;
19
use crate::LabelledTransitionSystem;
20
use crate::LtsBuilder;
21
use crate::LtsBuilderMem;
22
use crate::StateIndex;
23
use crate::TransitionLabel;
24

            
25
#[derive(Error, Debug)]
26
pub enum IOError {
27
    #[error("Invalid .aut header {0}")]
28
    InvalidHeader(&'static str),
29

            
30
    #[error("Invalid transition {0}")]
31
    InvalidTransition(String),
32
}
33

            
34
/// Loads a labelled transition system in the [Aldebaran
35
/// format](https://cadp.inria.fr/man/aldebaran.html) from the given reader.
36
///
37
/// Note that the reader has a buffer in the form of  `BufReader`` internally.
38
///
39
/// The Aldebaran format consists of a header: `des (<initial>: Nat,
40
///     <num_of_transitions>: Nat, <num_of_states>: Nat)`
41
///     
42
/// And one line for every transition either one of these cases:
43
///  `(<from>: Nat, "<label>": Str, <to>: Nat)`
44
///  `(<from>: Nat, <label>: Str, <to>: Nat)`
45
209
pub fn read_aut(reader: impl Read, hidden_labels: Vec<String>) -> Result<LabelledTransitionSystem<String>, MercError> {
46
209
    info!("Reading LTS in .aut format...");
47

            
48
209
    let mut lines = LineIterator::new(reader);
49
209
    lines.advance();
50
209
    let header = lines
51
209
        .get()
52
209
        .ok_or(IOError::InvalidHeader("The first line should be the header"))?;
53

            
54
    // Regex for des (<initial>: Nat, <num_of_states>: Nat, <num_of_transitions>: Nat)
55
209
    let header_regex = Regex::new(r#"des\s*\(\s*([0-9]*)\s*,\s*([0-9]*)\s*,\s*([0-9]*)\s*\)\s*"#)
56
209
        .expect("Regex compilation should not fail");
57

            
58
209
    let (_, [initial_txt, num_of_transitions_txt, num_of_states_txt]) = header_regex
59
209
        .captures(header)
60
209
        .ok_or(IOError::InvalidHeader(
61
209
            "does not match des (<init>, <num_of_transitions>, <num_of_states>)",
62
209
        ))?
63
207
        .extract();
64

            
65
207
    let initial_state = StateIndex::new(initial_txt.parse()?);
66
207
    let num_of_transitions: usize = num_of_transitions_txt.parse()?;
67
207
    let num_of_states: usize = num_of_states_txt.parse()?;
68

            
69
207
    let mut builder = LtsBuilderMem::with_capacity(Vec::new(), hidden_labels, num_of_states, 16, num_of_transitions);
70
207
    let progress = TimeProgress::new(
71
        move |read: usize| {
72
            info!(
73
                "Read {} transitions {}%...",
74
                LargeFormatter(read),
75
                read * 100 / num_of_transitions
76
            )
77
        },
78
        1,
79
    );
80

            
81
188285
    while let Some(line) = lines.next() {
82
188078
        let (from_txt, label_txt, to_txt) =
83
188078
            read_transition(line).ok_or_else(|| IOError::InvalidTransition(line.clone()))?;
84

            
85
        // Parse the from and to states, with the given label.
86
188078
        let from = StateIndex::new(from_txt.parse()?);
87
188078
        let to = StateIndex::new(to_txt.parse()?);
88

            
89
188078
        debug_trace!("Read transition {from} --[{label_txt}]-> {to}");
90
188078
        builder.add_transition(from, label_txt, to)?;
91

            
92
188078
        progress.print(builder.num_of_transitions());
93
    }
94

            
95
207
    info!("Finished reading LTS");
96

            
97
207
    builder.finish(initial_state)
98
209
}
99

            
100
/// Write a labelled transition system in plain text in Aldebaran format to the
101
/// given writer, see [read_aut].
102
///
103
/// Note that the writer is buffered internally using a `BufWriter`.
104
101
pub fn write_aut(writer: &mut impl Write, lts: &impl LTS) -> Result<(), MercError> {
105
101
    info!("Writing LTS in .aut format...");
106

            
107
101
    let mut writer = BufWriter::new(writer);
108
101
    writeln!(
109
101
        writer,
110
        "des ({}, {}, {})",
111
101
        lts.initial_state_index(),
112
101
        lts.num_of_transitions(),
113
101
        lts.num_of_states()
114
    )?;
115

            
116
101
    let num_of_transitions = lts.num_of_transitions();
117
101
    let progress = TimeProgress::new(
118
        move |written: usize| {
119
            info!(
120
                "Wrote {} transitions {}%...",
121
                LargeFormatter(written),
122
                written * 100 / num_of_transitions
123
            )
124
        },
125
        1,
126
    );
127
101
    let mut transitions_written = 0usize;
128
10074
    for state_index in lts.iter_states() {
129
93569
        for transition in lts.outgoing_transitions(state_index) {
130
93569
            writeln!(
131
93569
                writer,
132
                "({}, \"{}\", {})",
133
                state_index,
134
93569
                lts.labels()[transition.label.value()],
135
                transition.to
136
            )?;
137

            
138
93569
            progress.print(transitions_written);
139
93569
            transitions_written += 1;
140
        }
141
    }
142

            
143
101
    info!("Finished writing LTS.");
144
101
    Ok(())
145
101
}
146

            
147
/// Dedicated function to parse the following transition formats:
148
///
149
/// # Details
150
///
151
/// One of the following formats:
152
///     `(<from>: Nat, "<label>": Str, <to>: Nat)`
153
///     `(<from>: Nat, <label>: Str, <to>: Nat)`
154
///
155
/// This was generally faster than the regex variant, since that one has to backtrack after
156
192491
fn read_transition(input: &str) -> Option<(&str, &str, &str)> {
157
192491
    let start_paren = input.find('(')?;
158
192491
    let start_comma = input.find(',')?;
159

            
160
    // Find the comma in the second part
161
192491
    let start_second_comma = input.rfind(',')?;
162
192491
    let end_paren = input.rfind(')')?;
163

            
164
192491
    let from = input.get(start_paren + 1..start_comma)?.trim();
165
192491
    let label = input.get(start_comma + 1..start_second_comma)?.trim();
166
192491
    let to = input.get(start_second_comma + 1..end_paren)?.trim();
167
    // Handle the special case where it has quotes.
168
192491
    if label.starts_with('"') && label.ends_with('"') {
169
192491
        return Some((from, &label[1..label.len() - 1], to));
170
    }
171

            
172
    Some((from, label, to))
173
192491
}
174

            
175
/// A trait for labels that can be used in transitions.
176
impl TransitionLabel for String {
177
144100
    fn is_tau_label(&self) -> bool {
178
144100
        self == "i"
179
144100
    }
180

            
181
46340
    fn tau_label() -> Self {
182
46340
        "i".to_string()
183
46340
    }
184

            
185
    fn matches_label(&self, label: &str) -> bool {
186
        self == label
187
    }
188

            
189
68300
    fn from_index(i: usize) -> Self {
190
68300
        char::from_digit(i as u32 + 10, 36)
191
68300
            .expect("Radix is less than 37, so should not panic")
192
68300
            .to_string()
193
68300
    }
194
}
195

            
196
#[cfg(test)]
197
mod tests {
198
    use crate::random_lts_monolithic;
199

            
200
    use super::*;
201

            
202
    use merc_utilities::random_test;
203
    use test_log::test;
204

            
205
    #[test]
206
    fn test_reading_aut() {
207
        let file = include_str!("../../../examples/lts/abp.aut");
208

            
209
        let lts = read_aut(file.as_bytes(), vec![]).unwrap();
210

            
211
        assert_eq!(lts.initial_state_index().value(), 0);
212
        assert_eq!(lts.num_of_transitions(), 92);
213
    }
214

            
215
    #[test]
216
    fn test_lts_failure() {
217
        let wrong_header = "
218
        des (0,2,                                     
219
            (0,\"r1(d1)\",1)
220
            (0,\"r1(d2)\",2)
221
        ";
222

            
223
        debug_assert!(read_aut(wrong_header.as_bytes(), vec![]).is_err());
224

            
225
        let wrong_transition = "
226
        des (0,2,3)                           
227
            (0,\"r1(d1),1)
228
            (0,\"r1(d2)\",2)
229
        ";
230

            
231
        debug_assert!(read_aut(wrong_transition.as_bytes(), vec![]).is_err());
232
    }
233

            
234
    #[test]
235
    fn test_traversal_lts() {
236
        let file = include_str!("../../../examples/lts/abp.aut");
237

            
238
        let lts = read_aut(file.as_bytes(), vec![]).unwrap();
239

            
240
        // Check the number of outgoing transitions of the initial state
241
        assert_eq!(lts.outgoing_transitions(lts.initial_state_index()).count(), 2);
242
    }
243

            
244
    #[test]
245
    fn test_writing_lts() {
246
        let file = include_str!("../../../examples/lts/abp.aut");
247
        let lts_original = read_aut(file.as_bytes(), vec![]).unwrap();
248

            
249
        // Check that it can be read after writing, and results in the same LTS.
250
        let mut buffer: Vec<u8> = Vec::new();
251
        write_aut(&mut buffer, &lts_original).unwrap();
252

            
253
        let lts = read_aut(&buffer[0..], vec![]).unwrap();
254

            
255
        assert!(lts.num_of_states() == lts_original.num_of_states());
256
        assert!(lts.num_of_labels() == lts_original.num_of_labels());
257
        assert!(lts.num_of_transitions() == lts_original.num_of_transitions());
258
    }
259

            
260
    #[test]
261
    #[cfg_attr(miri, ignore)] // Test is too slow under Miri
262
    fn test_random_aut_io() {
263
100
        random_test(100, |rng| {
264
100
            let lts = random_lts_monolithic::<String>(rng, 100, 3, 20);
265

            
266
100
            let mut buffer: Vec<u8> = Vec::new();
267
100
            write_aut(&mut buffer, &lts).unwrap();
268

            
269
100
            let result_lts = read_aut(&buffer[0..], vec![]).unwrap();
270

            
271
100
            crate::check_equivalent(&lts, &result_lts);
272
100
        })
273
    }
274
}