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
212
pub fn read_aut<R: Read>(reader: R, hidden_labels: Vec<String>) -> Result<LabelledTransitionSystem<String>, MercError> {
46
212
    info!("Reading LTS in .aut format...");
47

            
48
212
    let mut lines = LineIterator::new(reader);
49
212
    lines.advance();
50
212
    let header = lines
51
212
        .get()
52
212
        .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
212
    let header_regex = Regex::new(r#"des\s*\(\s*([0-9]*)\s*,\s*([0-9]*)\s*,\s*([0-9]*)\s*\)\s*"#)
56
212
        .expect("Regex compilation should not fail");
57

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

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

            
69
210
    let mut builder = LtsBuilderMem::with_capacity(Vec::new(), hidden_labels, 16, num_of_states, num_of_transitions);
70
210
    builder.require_num_of_states(num_of_states);
71

            
72
210
    let progress = TimeProgress::new(
73
        move |read: usize| {
74
            info!(
75
                "Read {} transitions {}%...",
76
                LargeFormatter(read),
77
                read * 100 / num_of_transitions
78
            )
79
        },
80
        1,
81
    );
82

            
83
187441
    while let Some(line) = lines.next() {
84
187231
        let (from_txt, label_txt, to_txt) =
85
187231
            read_transition(line).ok_or_else(|| IOError::InvalidTransition(line.clone()))?;
86

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

            
91
187231
        debug_trace!("Read transition {from} --[{label_txt}]-> {to}");
92
187231
        builder.add_transition(from, label_txt, to)?;
93

            
94
187231
        progress.print(builder.num_of_transitions());
95
    }
96

            
97
210
    info!("Finished reading LTS");
98

            
99
210
    builder.finish(initial_state)
100
212
}
101

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

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

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

            
140
93601
            progress.print(transitions_written);
141
93601
            transitions_written += 1;
142
        }
143
    }
144

            
145
101
    info!("Finished writing LTS.");
146
101
    Ok(())
147
101
}
148

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

            
162
    // Find the comma in the second part
163
191680
    let start_second_comma = input.rfind(',')?;
164
191680
    let end_paren = input.rfind(')')?;
165

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

            
174
    Some((from, label, to))
175
191680
}
176

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

            
183
28264
    fn tau_label() -> Self {
184
28264
        "i".to_string()
185
28264
    }
186

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

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

            
198
#[cfg(test)]
199
mod tests {
200
    use merc_utilities::random_test;
201
    use test_log::test;
202

            
203
    use crate::LTS;
204
    use crate::random_lts_monolithic;
205
    use crate::read_aut;
206
    use crate::write_aut;
207

            
208
    #[test]
209
    fn test_reading_aut() {
210
        let file = include_str!("../../../examples/lts/abp.aut");
211

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

            
214
        assert_eq!(lts.initial_state_index().value(), 0);
215
        assert_eq!(lts.num_of_transitions(), 92);
216
    }
217

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

            
226
        debug_assert!(read_aut(wrong_header.as_bytes(), vec![]).is_err());
227

            
228
        let wrong_transition = "
229
        des (0,2,3)                           
230
            (0,\"r1(d1),1)
231
            (0,\"r1(d2)\",2)
232
        ";
233

            
234
        debug_assert!(read_aut(wrong_transition.as_bytes(), vec![]).is_err());
235
    }
236

            
237
    #[test]
238
    fn test_traversal_lts() {
239
        let file = include_str!("../../../examples/lts/abp.aut");
240

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

            
243
        // Check the number of outgoing transitions of the initial state
244
        assert_eq!(lts.outgoing_transitions(lts.initial_state_index()).count(), 2);
245
    }
246

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

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

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

            
258
        assert!(lts.num_of_states() == lts_original.num_of_states());
259
        assert!(lts.num_of_labels() == lts_original.num_of_labels());
260
        assert!(lts.num_of_transitions() == lts_original.num_of_transitions());
261
    }
262

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

            
269
100
            let mut buffer: Vec<u8> = Vec::new();
270
100
            write_aut(&mut buffer, &lts).unwrap();
271

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

            
274
100
            crate::check_equivalent(&lts, &result_lts);
275
100
        })
276
    }
277
}