graphite_binary/nbt/stringified/
from_snbt.rs

1use 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    // todo: check if using peekable gives perf
11    // let snbt = snbt.chars().peekable();
12
13    // Make sure snbt starts with an opening brace
14    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    // Parse the root compound
22    let children = read_compound(&mut snbt, &mut nodes)?;
23
24    // Make sure there is no more input
25    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            // Skip the whitespace
78            // snbt[0] will be c
79            *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    // Special case for empty compound `{}`
90    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        // Primitive ByteArray
259        '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        // Primitive IntArray
272        '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        // Primitive LongArray
285        '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        // Special case for empty list `[]`
298        ']' => {
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        // Normal list
309        _ => {
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                // todo: this is very permissive
391                // this should only allow b/B (for byte arrays), l/L (for long arrays) and nothing for int arrays
392                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}