graphite_binary/nbt/stringified/
from_snbt.rs1use std::str::FromStr;
2
3use anyhow::bail;
4
5use crate::nbt::*;
6
7pub fn from_snbt(mut snbt: &str) -> anyhow::Result<NBT> {
8 let mut nodes = Vec::new();
9
10 let next_char = peek_non_whitespace(&mut snbt)?;
15 if next_char == '{' {
16 snbt = &snbt[1..];
17 } else {
18 bail!("from_snbt: snbt must start with opening brace ({{)")
19 }
20
21 let children = read_compound(&mut snbt, &mut nodes)?;
23
24 for c in snbt.chars() {
26 if !c.is_whitespace() {
27 bail!("from_snbt: expected end of input")
28 }
29 }
30
31 Ok(NBT {
32 root_name: String::new(),
33 root_children: children,
34 nodes,
35 })
36}
37
38fn read_node(snbt: &mut &str, nodes: &mut Vec<NBTNode>) -> anyhow::Result<(usize, TagType)> {
39 let (node, type_id) = match peek_non_whitespace(snbt)? {
40 '0'..='9' | '.' | '-' => read_numeric_node(snbt)?,
41 '{' => {
42 *snbt = &snbt[1..];
43 (
44 NBTNode::Compound(read_compound(snbt, nodes)?),
45 TAG_COMPOUND_ID,
46 )
47 }
48 '[' => {
49 *snbt = &snbt[1..];
50 read_array_node(snbt, nodes)?
51 }
52 '"' => (NBTNode::String(read_string(snbt)?), TAG_STRING_ID),
53 't' => {
54 if snbt.len() >= 4 && &snbt[..4] == "true" {
55 (NBTNode::Byte(1), TAG_BYTE_ID)
56 } else {
57 bail!("unknown start of type: t");
58 }
59 }
60 'f' => {
61 if snbt.len() >= 5 && &snbt[..5] == "false" {
62 (NBTNode::Byte(0), TAG_BYTE_ID)
63 } else {
64 bail!("unknown start of type: f");
65 }
66 }
67 c => bail!("unknown start of type: {}", c),
68 };
69
70 nodes.push(node);
71 Ok((nodes.len() - 1, type_id))
72}
73
74fn peek_non_whitespace(snbt: &mut &str) -> anyhow::Result<char> {
75 for (index, c) in snbt.char_indices() {
76 if !c.is_whitespace() {
77 *snbt = &snbt[index..];
80 return Ok(c);
81 }
82 }
83 bail!("next_char: unexpected end of input");
84}
85
86fn read_compound(snbt: &mut &str, nodes: &mut Vec<NBTNode>) -> anyhow::Result<NBTCompound> {
87 let mut children = NBTCompound(Vec::new());
88
89 let next_char = peek_non_whitespace(snbt)?;
91 if next_char == '}' {
92 *snbt = &snbt[1..];
93 return Ok(children);
94 }
95
96 loop {
97 let name = read_key(snbt)?;
98
99 if peek_non_whitespace(snbt)? == ':' {
100 *snbt = &snbt[1..];
101 } else {
102 bail!("read_compound: key must be followed by a colon (:)")
103 }
104
105 let (idx, _type_id) = read_node(snbt, nodes)?;
106
107 match children.binary_search(name.as_ref()) {
108 Ok(_) => bail!("read_compound: duplicate key"),
109 Err(index) => {
110 children.0.insert(index, (name.into(), idx));
111 }
112 }
113
114 match peek_non_whitespace(snbt)? {
115 '}' => {
116 *snbt = &snbt[1..];
117 return Ok(children);
118 }
119 ',' => *snbt = &snbt[1..],
120 c => bail!("read_compound: unknown continuation: {}", c),
121 }
122 }
123}
124
125fn read_key(snbt: &mut &str) -> anyhow::Result<String> {
126 let first_char = peek_non_whitespace(snbt)?;
127
128 if first_char == '"' {
129 read_string(snbt)
130 } else {
131 for (index, c) in snbt.char_indices() {
132 match c {
133 '0'..='9' | 'A'..='Z' | 'a'..='z' | '.' | '_' | '+' | '-' => continue,
134 ':' => {
135 let string = snbt[..index].into();
136 *snbt = &snbt[index..];
137 return Ok(string);
138 }
139 c => bail!("read_key: invalid character: {}", c),
140 }
141 }
142 bail!("read_key: unexpected end of input");
143 }
144}
145
146fn read_string(snbt: &mut &str) -> anyhow::Result<String> {
147 let first_char = peek_non_whitespace(snbt)?;
148
149 if first_char != '"' {
150 bail!("read_string: first character must be quote literal (\")");
151 } else {
152 *snbt = &snbt[1..];
153
154 let mut string = String::new();
155 let mut start = 0;
156 let mut escaping = false;
157
158 for (index, c) in snbt.char_indices() {
159 match c {
160 '\\' => {
161 if escaping {
162 escaping = false;
163 } else {
164 string.push_str(&snbt[start..index]);
165 start = index + 1;
166 }
167 }
168 '"' => {
169 if escaping {
170 escaping = false;
171 } else {
172 string.push_str(&snbt[start..index]);
173 *snbt = &snbt[(index + 1)..];
174 return Ok(string);
175 }
176 }
177 c => {
178 if escaping {
179 bail!("read_string: unknown escape sequence: \\{}", c);
180 } else {
181 continue;
182 }
183 }
184 }
185 }
186 bail!("read_string: unexpected end of input");
187 }
188}
189
190fn read_numeric_node(snbt: &mut &str) -> anyhow::Result<(NBTNode, TagType)> {
191 let mut has_decimal = false;
192 for (index, c) in snbt.char_indices() {
193 match c {
194 '-' => {
195 if index != 0 {
196 bail!("read_numeric_node: minus literal (-) is only valid at the beginning")
197 }
198 }
199 '0'..='9' => {
200 continue;
201 }
202 '.' => {
203 if has_decimal {
204 bail!("read_numeric_node: found multiple decimal points while parsing number");
205 } else {
206 has_decimal = true;
207 }
208 continue;
209 }
210 'b' | 'B' => {
211 let number_string = &snbt[..index];
212 *snbt = &snbt[(index + 1)..];
213 return Ok((NBTNode::Byte(number_string.parse()?), TAG_BYTE_ID));
214 }
215 's' | 'S' => {
216 let number_string = &snbt[..index];
217 *snbt = &snbt[(index + 1)..];
218 return Ok((NBTNode::Short(number_string.parse()?), TAG_SHORT_ID));
219 }
220 'l' | 'L' => {
221 let number_string = &snbt[..index];
222 *snbt = &snbt[(index + 1)..];
223 return Ok((NBTNode::Long(number_string.parse()?), TAG_LONG_ID));
224 }
225 'f' | 'F' => {
226 let number_string = &snbt[..index];
227 *snbt = &snbt[(index + 1)..];
228 return Ok((NBTNode::Float(number_string.parse()?), TAG_FLOAT_ID));
229 }
230 'd' | 'D' => {
231 let number_string = &snbt[..index];
232 *snbt = &snbt[(index + 1)..];
233 return Ok((NBTNode::Double(number_string.parse()?), TAG_DOUBLE_ID));
234 }
235 _ => {
236 let number_string = &snbt[..index];
237 *snbt = &snbt[index..];
238 if has_decimal {
239 return Ok((NBTNode::Double(number_string.parse()?), TAG_DOUBLE_ID));
240 } else {
241 return Ok((NBTNode::Int(number_string.parse()?), TAG_INT_ID));
242 }
243 }
244 }
245 }
246 bail!("read_numeric_node: unexpected end of input");
247}
248
249enum PrimArrParseState {
250 WaitingForNumber,
251 WaitingForComma,
252 InNumber { start: usize },
253}
254
255fn read_array_node(snbt: &mut &str, nodes: &mut Vec<NBTNode>) -> anyhow::Result<(NBTNode, TagType)> {
256 let next_char = peek_non_whitespace(snbt)?;
257 match next_char {
258 'B' => {
260 *snbt = &snbt[1..];
261 match peek_non_whitespace(snbt)? {
262 ';' => *snbt = &snbt[1..],
263 _ => bail!("read_array_node: expect semicolon (;) after B"),
264 }
265
266 Ok((
267 NBTNode::ByteArray(read_primitive_array(snbt)?),
268 TAG_BYTE_ARRAY_ID,
269 ))
270 }
271 'I' => {
273 *snbt = &snbt[1..];
274 match peek_non_whitespace(snbt)? {
275 ';' => *snbt = &snbt[1..],
276 _ => bail!("read_array_node: expect semicolon (;) after I"),
277 }
278
279 Ok((
280 NBTNode::IntArray(read_primitive_array(snbt)?),
281 TAG_INT_ARRAY_ID,
282 ))
283 }
284 'L' => {
286 *snbt = &snbt[1..];
287 match peek_non_whitespace(snbt)? {
288 ';' => *snbt = &snbt[1..],
289 _ => bail!("read_array_node: expect semicolon (;) after L"),
290 }
291
292 Ok((
293 NBTNode::LongArray(read_primitive_array(snbt)?),
294 TAG_LONG_ARRAY_ID,
295 ))
296 }
297 ']' => {
299 *snbt = &snbt[1..];
300 Ok((
301 NBTNode::List {
302 type_id: TAG_END_ID,
303 children: Vec::new(),
304 },
305 TAG_LIST_ID,
306 ))
307 }
308 _ => {
310 let mut children = Vec::new();
311
312 let (idx, first_type_id) = read_node(snbt, nodes)?;
313 children.push(idx);
314
315 loop {
316 match peek_non_whitespace(snbt)? {
317 ']' => {
318 *snbt = &snbt[1..];
319 return Ok((
320 NBTNode::List {
321 type_id: first_type_id,
322 children,
323 },
324 TAG_LIST_ID,
325 ));
326 }
327 ',' => *snbt = &snbt[1..],
328 c => bail!("read_array_node: unknown continuation: {}", c),
329 }
330
331 let (idx, type_id) = read_node(snbt, nodes)?;
332 children.push(idx);
333
334 if type_id != first_type_id {
335 bail!("read_array_node: elements in array have different type")
336 }
337 }
338 }
339 }
340}
341
342fn read_primitive_array<T: FromStr>(snbt: &mut &str) -> anyhow::Result<Vec<T>> {
343 let mut values = Vec::new();
344 let mut state = PrimArrParseState::WaitingForNumber;
345 for (index, c) in snbt.char_indices() {
346 match c {
347 ']' => {
348 match state {
349 PrimArrParseState::WaitingForComma => (),
350 PrimArrParseState::WaitingForNumber => {
351 bail!("read_primitive_array: expected numeric character, got ]")
352 }
353 PrimArrParseState::InNumber { start } => {
354 let value: T = snbt[start..index].parse().map_err(|_| {
355 anyhow::anyhow!("read_primitive_array: failed to parse")
356 })?;
357 values.push(value);
358 }
359 }
360
361 *snbt = &snbt[(index + 1)..];
362 return Ok(values);
363 }
364 '0'..='9' | '-' => match state {
365 PrimArrParseState::WaitingForNumber => {
366 state = PrimArrParseState::InNumber { start: index }
367 }
368 PrimArrParseState::InNumber { start: _ } => continue,
369 PrimArrParseState::WaitingForComma => {
370 bail!("read_primitive_array: expected comma, got numeric character")
371 }
372 },
373 ',' => {
374 match state {
375 PrimArrParseState::WaitingForComma => (),
376 PrimArrParseState::WaitingForNumber => {
377 bail!("read_primitive_array: expected numeric character, got comma")
378 }
379 PrimArrParseState::InNumber { start } => {
380 let value: T = snbt[start..index].parse().map_err(|_| {
381 anyhow::anyhow!("read_primitive_array: failed to parse")
382 })?;
383 values.push(value);
384 }
385 }
386 state = PrimArrParseState::WaitingForNumber;
387 }
388 ' ' => continue,
389 c => {
390 match state {
393 PrimArrParseState::WaitingForComma => {
394 bail!("read_primitive_array: expected comma, got `{}`", c)
395 }
396 PrimArrParseState::WaitingForNumber => bail!(
397 "read_primitive_array: expected numeric character, got `{}`",
398 c
399 ),
400 PrimArrParseState::InNumber { start } => {
401 let value: T = snbt[start..index].parse().map_err(|_| {
402 anyhow::anyhow!("read_primitive_array: failed to parse")
403 })?;
404 values.push(value);
405 }
406 }
407 state = PrimArrParseState::WaitingForComma;
408 }
409 }
410 }
411 bail!("read_array_node: unexpected end of input");
412}