1
use std::collections::HashMap;
2
use std::error::Error;
3
use std::io::Read;
4
use std::io::Write;
5
use std::time::Instant;
6

            
7
use log::debug;
8
use log::trace;
9
use regex::Regex;
10
use streaming_iterator::StreamingIterator;
11
use thiserror::Error;
12

            
13
use crate::line_iterator::LineIterator;
14
use crate::progress::Progress;
15
use lts::LabelIndex;
16
use lts::LabelledTransitionSystem;
17

            
18
#[derive(Error, Debug)]
19
pub enum IOError {
20
    #[error("Invalid .aut header {0}")]
21
    InvalidHeader(&'static str),
22

            
23
    #[error("Invalid transition line")]
24
    InvalidTransition(),
25
}
26

            
27
///     `(<from>: Nat, "<label>": Str, <to>: Nat)`
28
///     `(<from>: Nat, <label>: Str, <to>: Nat)`
29
328844
fn read_transition(input: &str) -> Result<(&str, &str, &str), Box<dyn Error>> {
30
328844
    let start_paren = input.find('(').ok_or(IOError::InvalidTransition())?;
31
328844
    let start_comma = input.find(',').ok_or(IOError::InvalidTransition())?;
32

            
33
    // Find the comma in the second part
34
328844
    let start_second_comma = input.rfind(',').ok_or(IOError::InvalidTransition())?;
35
328844
    let end_paren = input.rfind(')').ok_or(IOError::InvalidTransition())?;
36

            
37
328844
    let from = &input[start_paren+1..start_comma].trim();
38
328844
    let label = &input[start_comma+1..start_second_comma].trim();
39
328844
    let to = &input[start_second_comma+1..end_paren].trim();
40
328844
    // Handle the special case where it has quotes.
41
328844
    if label.starts_with('"') && label.ends_with('"') {
42
167172
        return Ok((from, &label[1..label.len()-1], to))
43
161672
    }
44
161672

            
45
161672
    Ok((from, label, to))
46
328844
}
47

            
48
/// Loads a labelled transition system in the Aldebaran format from the given reader.
49
///
50
/// The Aldebaran format consists of a header:
51
///     `des (<initial>: Nat, <num_of_transitions>: Nat, <num_of_states>: Nat)`
52
///     
53
/// And one line for every transition:
54
///     `(<from>: Nat, "<label>": Str, <to>: Nat)`
55
///     `(<from>: Nat, <label>: Str, <to>: Nat)`
56
26
pub fn read_aut(reader: impl Read, mut hidden_labels: Vec<String>) -> Result<LabelledTransitionSystem, Box<dyn Error>> {
57
26
    let start = Instant::now();
58
26
    debug!("Reading LTS in .aut format...");
59

            
60
26
    let mut lines = LineIterator::new(reader);
61
26
    lines.advance();
62
26
    let header = lines
63
26
        .get()
64
26
        .ok_or(IOError::InvalidHeader("The first line should be the header"))?;
65

            
66
    // Regex for des (<initial>: Nat, <num_of_states>: Nat, <num_of_transitions>: Nat)
67
26
    let header_regex = Regex::new(r#"des\s*\(\s*([0-9]*)\s*,\s*([0-9]*)\s*,\s*([0-9]*)\s*\)\s*"#)
68
26
        .expect("Regex compilation should not fail");
69

            
70
26
    let (_, [initial_txt, num_of_transitions_txt, num_of_states_txt]) = header_regex
71
26
        .captures(header)
72
26
        .ok_or(IOError::InvalidHeader(
73
26
            "does not match des (<init>, <num_of_transitions>, <num_of_states>)",
74
26
        ))?
75
24
        .extract();
76

            
77
24
    let initial_state: usize = initial_txt.parse()?;
78
24
    let num_of_transitions: usize = num_of_transitions_txt.parse()?;
79
24
    let num_of_states: usize = num_of_states_txt.parse()?;
80

            
81
    // This is used to keep track of the label to index mapping.
82
24
    let mut labels_index: HashMap<String, LabelIndex> = HashMap::new();
83
24
    let mut labels: Vec<String> = Vec::new();
84
24

            
85
24
    let mut transitions: Vec<(usize, usize, usize)> = Vec::default();
86
24
    let mut progress = Progress::new(
87
24
        |value, increment| debug!("Reading transitions {}%...", value / increment),
88
24
        num_of_transitions,
89
24
    );
90

            
91
164630
    while let Some(line) = lines.next() {
92
164606
        trace!("{}", line);
93
164606
        let (from_txt, label_txt, to_txt) = read_transition(line)?;
94

            
95
        // Parse the from and to states, with the given label.
96
164606
        let from: usize = from_txt.parse()?;
97
164606
        let to: usize = to_txt.parse()?;
98

            
99
164606
        let label_index = *labels_index.entry(label_txt.to_string()).or_insert(labels.len());
100
164606

            
101
164606
        if label_index >= labels.len() {
102
50746
            labels.resize_with(label_index + 1, Default::default);
103
113860
        }
104

            
105
164606
        trace!("Read transition {} --[{}]-> {}", from, label_txt, to);
106

            
107
164606
        transitions.push((from, label_index, to));
108
164606

            
109
164606
        if labels[label_index].is_empty() {
110
50746
            labels[label_index] = label_txt.to_string();
111
113860
        }
112

            
113
164606
        progress.add(1);
114
    }
115

            
116
    // Remove duplicated transitions, it is not clear if they are allowed in the .aut format.
117
24
    transitions.sort_unstable();
118
24
    transitions.dedup();
119
24

            
120
24
    debug!("Finished reading LTS");
121

            
122
24
    hidden_labels.push("tau".to_string());
123
24
    debug!("Time read_aut: {:.3}s", start.elapsed().as_secs_f64());
124
24
    Ok(LabelledTransitionSystem::new(
125
24
        initial_state,
126
24
        Some(num_of_states),
127
48
        || transitions.iter().cloned(),
128
24
        labels,
129
24
        hidden_labels,
130
24
    ))
131
26
}
132

            
133
/// Write a labelled transition system in plain text in Aldebaran format to the given writer.
134
1
pub fn write_aut(writer: &mut impl Write, lts: &LabelledTransitionSystem) -> Result<(), Box<dyn Error>> {
135
1
    writeln!(
136
1
        writer,
137
1
        "des ({}, {}, {})",
138
1
        lts.initial_state_index(),
139
1
        lts.num_of_transitions(),
140
1
        lts.num_of_states()
141
1
    )?;
142

            
143
74
    for state_index in lts.iter_states() {
144
92
        for (label, to) in lts.outgoing_transitions(state_index) {
145
92
            writeln!(
146
92
                writer,
147
92
                "({}, \"{}\", {})",
148
92
                state_index,
149
92
                if lts.is_hidden_label(*label) {
150
                    "tau"
151
                } else {
152
92
                    &lts.labels()[*label]
153
                },
154
                to
155
            )?;
156
        }
157
    }
158

            
159
1
    Ok(())
160
1
}
161

            
162
#[cfg(test)]
163
mod tests {
164
    use super::*;
165

            
166
    use test_log::test;
167

            
168
1
    #[test]
169
    fn test_reading_aut() {
170
        let file = include_str!("../../../examples/lts/abp.aut");
171

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

            
174
        assert_eq!(lts.initial_state_index(), 0);
175
        assert_eq!(lts.num_of_transitions(), 92);
176
        println!("{}", lts);
177
    }
178

            
179
1
    #[test]
180
    fn test_lts_failure() {
181
        let wrong_header = "
182
        des (0,2,                                     
183
            (0,\"r1(d1)\",1)
184
            (0,\"r1(d2)\",2)
185
        ";
186

            
187
        debug_assert!(read_aut(wrong_header.as_bytes(), vec![]).is_err());
188

            
189
        let wrong_transition = "
190
        des (0,2,3)                           
191
            (0,\"r1(d1),1)
192
            (0,\"r1(d2)\",2)
193
        ";
194

            
195
        debug_assert!(read_aut(wrong_transition.as_bytes(), vec![]).is_err());
196
    }
197

            
198
1
    #[test]
199
    fn test_traversal_lts() {
200
        let file = include_str!("../../../examples/lts/abp.aut");
201

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

            
204
        // Check the number of outgoing transitions of the initial state
205
        assert_eq!(lts.outgoing_transitions(lts.initial_state_index()).count(), 2);
206
    }
207

            
208
1
    #[test]
209
    fn test_writing_lts() {
210
        let file = include_str!("../../../examples/lts/abp.aut");
211
        let lts_original = read_aut(file.as_bytes(), vec![]).unwrap();
212

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

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

            
219
        assert!(lts.num_of_states() == lts_original.num_of_states());
220
        assert!(lts.num_of_labels() == lts_original.num_of_labels());
221
    }
222
}