prison_architect_savefile/
parse.rs1use core::fmt;
2use std::{collections::HashMap, error::Error, str::FromStr};
3
4use crate::Node;
5
6#[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 {}