prison_architect_savefile/
parse.rs

1use core::fmt;
2use std::{collections::HashMap, error::Error, str::FromStr};
3
4use crate::Node;
5
6/// An error when parsing an invalid savefile.
7#[derive(Debug)]
8pub struct ParseError {
9    message: String,
10    line: u32,
11    col: u32,
12}
13
14impl FromStr for Node {
15    type Err = ParseError;
16
17    fn from_str(input: &str) -> Result<Self, Self::Err> {
18        Parser { input, pos: 0 }.parse_node(false)
19    }
20}
21
22struct Parser<'a> {
23    input: &'a str,
24    pos: usize,
25}
26
27impl<'a> Parser<'a> {
28    fn parse_node(&mut self, child: bool) -> Result<Node, ParseError> {
29        let mut node = Node {
30            properties: HashMap::new(),
31            children: HashMap::new(),
32        };
33
34        loop {
35            self.skip_whitespace();
36            if self.eat("BEGIN") {
37                self.skip_whitespace();
38                let key = self.parse_string()?;
39                node.children
40                    .entry(key.clone())
41                    .or_default()
42                    .push(self.parse_node(true)?);
43                if !self.eat("END") {
44                    return Err(self.error(format!("unterminated object '{}'", key)));
45                }
46            } else if self.eof() || (child && self.remainder().starts_with("END")) {
47                return Ok(node);
48            } else {
49                let (key, value) = self.parse_attribute()?;
50                node.properties.entry(key).or_default().push(value);
51            }
52        }
53    }
54
55    fn parse_attribute(&mut self) -> Result<(String, String), ParseError> {
56        let key = self.parse_string()?;
57        self.skip_whitespace();
58        let value = self.parse_string()?;
59        Ok((key, value))
60    }
61
62    fn parse_string(&mut self) -> Result<String, ParseError> {
63        if self.remainder().starts_with('"') {
64            self.parse_quoted_string()
65        } else if self.eof() {
66            Err(self.error("unexpected eof"))
67        } else {
68            self.parse_plain_string()
69        }
70    }
71
72    fn parse_quoted_string(&mut self) -> Result<String, ParseError> {
73        assert!(self.eat("\""));
74        let mut result = String::new();
75
76        let remainder = self.remainder();
77        let mut pos = 0;
78        for (start, ch) in self.remainder().match_indices(['\"', '\\']) {
79            match ch {
80                "\"" => {
81                    result.push_str(&remainder[pos..start]);
82                    self.pos += start + 1;
83                    return Ok(result);
84                }
85                "\\" => {
86                    result.push_str(&remainder[pos..start]);
87                    match remainder.as_bytes().get(start + 1) {
88                        Some(b'n') => {
89                            result.push('\n');
90                            pos = start + 2;
91                        }
92                        Some(b'"') => {
93                            result.push('"');
94                            pos = start + 2;
95                        }
96                        Some(_) => {
97                            pos = start + 1;
98                        }
99                        None => return Err(self.error("incomplete escape")),
100                    }
101                }
102                _ => unreachable!(),
103            }
104        }
105
106        Err(self.error("unterminated string"))
107    }
108
109    fn parse_plain_string(&mut self) -> Result<String, ParseError> {
110        match self.remainder().find(|ch: char| ch.is_ascii_whitespace()) {
111            Some(end) => {
112                let token = self.remainder()[..end].to_owned();
113                self.pos += end;
114                Ok(token)
115            }
116            None => Err(self.error("expected string")),
117        }
118    }
119
120    fn error(&self, message: impl Into<String>) -> ParseError {
121        let line = self.input[..self.pos]
122            .chars()
123            .filter(|&ch| ch == '\n')
124            .count();
125        let col = self.input[..self.pos]
126            .lines()
127            .last()
128            .map(|line| line.len())
129            .unwrap_or(0);
130
131        ParseError {
132            line: line as u32,
133            col: col as u32,
134            message: message.into(),
135        }
136    }
137
138    fn eat(&mut self, token: &str) -> bool {
139        if self.remainder().starts_with(token) {
140            self.pos += token.len();
141            true
142        } else {
143            false
144        }
145    }
146
147    fn skip_whitespace(&mut self) {
148        while let Some(ch) = self.peek() {
149            if ch.is_ascii_whitespace() {
150                self.bump();
151            } else {
152                break;
153            }
154        }
155    }
156
157    fn remainder(&self) -> &'a str {
158        &self.input[self.pos..]
159    }
160
161    fn eof(&self) -> bool {
162        self.pos == self.input.len()
163    }
164
165    fn bump(&mut self) {
166        let ch = self.peek().expect("no char");
167        self.pos += ch.len_utf8();
168    }
169
170    fn peek(&self) -> Option<char> {
171        self.input[self.pos..].chars().next()
172    }
173}
174
175impl fmt::Display for ParseError {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        write!(f, "{}:{}: {}", self.line + 1, self.col + 1, self.message)
178    }
179}
180
181impl Error for ParseError {}