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(|(amount, total): (usize, usize)| info!("Read {} vertices ({}%)...", amount, amount * 100 / total), 1);
58

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

            
63
1
    let mut vertices: Vec<usize> = Vec::with_capacity(num_of_vertices + 1);
64
1
    let mut transitions_to: Vec<VertexIndex> = Vec::with_capacity(num_of_vertices);
65

            
66
1
    let mut vertex_count = 0;
67
61015
    while let Some(line) = lines.next() {
68
        // Parse the line: <index> <priority> <owner> <outgoing_vertex>, <outgoing_vertex>, ...;
69
61014
        let mut parts = line.split_whitespace();
70

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

            
86
61014
        owner[index] = Player::from_index(vertex_owner);
87
61014
        priority[index] = Priority::new(vertex_priority);
88

            
89
        // Store the offset for the vertex
90
61014
        vertices.push(transitions_to.len());
91

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

            
105
61014
        progress.print((vertex_count + 1, num_of_vertices));
106
61014
        vertex_count += 1;
107
    }
108

            
109
    // Add the sentinel state.
110
1
    vertices.push(transitions_to.len());
111

            
112
1
    Ok(ParityGame::new(
113
1
        VertexIndex::new(0),
114
1
        owner,
115
1
        priority,
116
1
        vertices,
117
1
        transitions_to,
118
1
    ))
119
1
}
120

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

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

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

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

            
141
    Ok(())
142
}
143

            
144
#[cfg(test)]
145
mod tests {
146
    use super::*;
147

            
148
    #[test]
149
    #[cfg_attr(miri, ignore)]
150
1
    fn test_read_pg() {
151
1
        let parity_game = read_pg(include_bytes!("../../../../examples/vpg/example.pg") as &[u8]).unwrap();
152
1
        assert_eq!(parity_game.num_of_vertices(), 61014);
153
1
    }
154
}