1
//! Authors: Maurice Laveaux and Sjef van Loo
2

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

            
6
use itertools::Itertools;
7
use log::info;
8
use regex::Regex;
9
use streaming_iterator::StreamingIterator;
10
use thiserror::Error;
11

            
12
use merc_io::LineIterator;
13
use merc_io::TimeProgress;
14
use merc_utilities::MercError;
15

            
16
use crate::PG;
17
use crate::ParityGame;
18
use crate::Player;
19
use crate::Priority;
20
use crate::VertexIndex;
21

            
22
#[derive(Error, Debug)]
23
pub enum IOError {
24
    #[error("Invalid .pg header {0}")]
25
    InvalidHeader(&'static str),
26

            
27
    #[error("Invalid line {0}")]
28
    InvalidLine(&'static str),
29
}
30

            
31
/// Reads a parity game in textual PGSolver `.pg` format from the given reader.
32
///
33
/// # Details
34
///
35
/// The format starts with a header, followed by the vertices
36
///
37
/// `parity <num_of_vertices>;`
38
/// `<index> <priority> <owner> <outgoing_vertex>, <outgoing_vertex>, ...;`
39
1
pub fn read_pg(reader: impl Read) -> Result<ParityGame, MercError> {
40
1
    info!("Reading parity game in .pg format...");
41

            
42
1
    let mut lines = LineIterator::new(reader);
43
1
    lines.advance();
44
1
    let header = lines
45
1
        .get()
46
1
        .ok_or(IOError::InvalidHeader("The first line should be the header"))?;
47

            
48
    // Read the header
49
1
    let header_regex = Regex::new(r#"parity\s+([0-9]+)\s*;"#).expect("Regex compilation should not fail");
50

            
51
1
    let (_, [num_of_vertices_txt]) = header_regex
52
1
        .captures(header)
53
1
        .ok_or(IOError::InvalidHeader("does not match parity <num_of_vertices>;"))?
54
1
        .extract();
55

            
56
1
    let num_of_vertices: usize = num_of_vertices_txt.parse()?;
57
1
    let progress = TimeProgress::new(
58
        |(amount, total): (usize, usize)| info!("Read {} vertices ({}%)...", amount, amount * 100 / total),
59
        1,
60
    );
61

            
62
    // Collect that data into the parity game structure
63
1
    let mut owner: Vec<Player> = vec![Player::Even; num_of_vertices];
64
1
    let mut priority: Vec<Priority> = vec![Priority::new(0); num_of_vertices];
65

            
66
1
    let mut vertices: Vec<usize> = Vec::with_capacity(num_of_vertices + 1);
67
1
    let mut transitions_to: Vec<VertexIndex> = Vec::with_capacity(num_of_vertices);
68

            
69
1
    let mut vertex_count = 0;
70
3003
    while let Some(line) = lines.next() {
71
        // Parse the line: <index> <priority> <owner> <outgoing_vertex>, <outgoing_vertex>, ...;
72
3002
        let mut parts = line.split_whitespace();
73

            
74
3002
        let index: usize = parts
75
3002
            .next()
76
3002
            .ok_or(IOError::InvalidLine("Expected at least <index> ...;"))?
77
3002
            .parse()?;
78
3002
        let vertex_priority: usize = parts
79
3002
            .next()
80
3002
            .ok_or(IOError::InvalidLine("Expected at least <index> <priority> ...;"))?
81
3002
            .parse()?;
82
3002
        let vertex_owner: u8 = parts
83
3002
            .next()
84
3002
            .ok_or(IOError::InvalidLine(
85
3002
                "Expected at least <index> <priority> <owner> ...;",
86
3002
            ))?
87
3002
            .parse()?;
88

            
89
3002
        owner[index] = Player::from_index(vertex_owner);
90
3002
        priority[index] = Priority::new(vertex_priority);
91

            
92
        // Store the offset for the vertex
93
3002
        vertices.push(transitions_to.len());
94

            
95
3968
        for successors in parts {
96
            // Parse successors (remaining parts, removing trailing semicolon)
97
3968
            for successor in successors
98
3968
                .trim_end_matches(';')
99
3968
                .split(',')
100
4934
                .filter(|s| !s.trim().is_empty())
101
3968
                .map(|s| s.trim().parse())
102
            {
103
3968
                let successor = successor?;
104
3968
                transitions_to.push(VertexIndex::new(successor));
105
            }
106
        }
107

            
108
3002
        progress.print((vertex_count + 1, num_of_vertices));
109
3002
        vertex_count += 1;
110
    }
111

            
112
    // Add the sentinel state.
113
1
    vertices.push(transitions_to.len());
114

            
115
1
    Ok(ParityGame::new(
116
1
        VertexIndex::new(0),
117
1
        owner,
118
1
        priority,
119
1
        vertices,
120
1
        transitions_to,
121
1
    ))
122
1
}
123

            
124
/// Writes the given parity game to the given writer in .pg format.
125
pub fn write_pg(mut writer: impl Write, game: &ParityGame) -> Result<(), MercError> {
126
    info!("Writing parity game to .pg format...");
127

            
128
    let progress = TimeProgress::new(
129
        |(index, total): (usize, usize)| info!("Wrote {} vertices ({}%)...", index, index * 100 / total),
130
        1,
131
    );
132

            
133
    writeln!(writer, "parity {};", game.num_of_vertices())?;
134
    for v in game.iter_vertices() {
135
        let prio = game.priority(v);
136
        let owner = game.owner(v).to_index();
137

            
138
        write!(writer, "{} {} {} ", v.value(), prio.value(), owner)?;
139
        write!(writer, "{}", game.outgoing_edges(v).map(|to| to.value()).format(", "))?;
140
        writeln!(writer, ";")?;
141
        progress.print((v.value() + 1, game.num_of_vertices()));
142
    }
143

            
144
    Ok(())
145
}
146

            
147
#[cfg(test)]
148
mod tests {
149
    use super::*;
150

            
151
    #[test]
152
    #[cfg_attr(miri, ignore)]
153
1
    fn test_read_pg() {
154
1
        let parity_game = read_pg(include_bytes!("../../../../examples/vpg/example.pg") as &[u8]).unwrap();
155
1
        assert_eq!(parity_game.num_of_vertices(), 3002);
156
1
        assert_eq!(parity_game.num_of_edges(), 3968);
157
1
    }
158
}