valence_nbt/
snbt.rs

1use std::error::Error;
2use std::fmt::{Display, Formatter};
3use std::iter::Peekable;
4use std::str::Chars;
5
6use crate::{Compound, List, Value};
7
8const STRING_MAX_LEN: usize = 32767;
9/// Maximum recursion depth to prevent overflowing the call stack.
10const MAX_DEPTH: usize = 512;
11
12#[derive(Debug, Clone, PartialEq, Eq, Copy)]
13pub enum SnbtErrorKind {
14    ReachEndOfStream,
15    InvalidEscapeSequence,
16    EmptyKeyInCompound,
17    ExpectColon,
18    ExpectValue,
19    ExpectComma,
20    WrongTypeInArray,
21    DifferentTypesInList,
22    LongString,
23    TrailingData,
24    DepthLimitExceeded,
25}
26
27impl Display for SnbtErrorKind {
28    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
29        use SnbtErrorKind::*;
30        match self {
31            ReachEndOfStream => write!(f, "reach end of stream"),
32            InvalidEscapeSequence => write!(f, "invalid escape sequence"),
33            EmptyKeyInCompound => write!(f, "empty key in compound"),
34            ExpectColon => write!(f, "expect colon"),
35            ExpectValue => write!(f, "expect value"),
36            ExpectComma => write!(f, "expect comma"),
37            WrongTypeInArray => write!(f, "wrong type in array"),
38            DifferentTypesInList => write!(f, "different types in list"),
39            LongString => write!(f, "long string"),
40            TrailingData => write!(f, "extra data after end"),
41            DepthLimitExceeded => write!(f, "depth limit exceeded"),
42        }
43    }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Copy)]
47pub struct SnbtError {
48    pub kind: SnbtErrorKind,
49    pub line: usize,
50    pub column: usize,
51}
52
53impl SnbtError {
54    pub fn new(kind: SnbtErrorKind, line: usize, column: usize) -> Self {
55        Self { kind, line, column }
56    }
57}
58
59impl Display for SnbtError {
60    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
61        write!(f, "@ {},{}: {}", self.line, self.column, self.kind)
62    }
63}
64
65impl Error for SnbtError {}
66
67type Result<T> = std::result::Result<T, SnbtError>;
68
69#[derive(Debug)]
70pub struct SnbtReader<'a> {
71    line: usize,
72    column: usize,
73    index: usize,
74    depth: usize,
75    iter: Peekable<Chars<'a>>,
76    pushed_back: Option<char>,
77}
78
79impl<'a> SnbtReader<'a> {
80    pub fn new(input: &'a str) -> Self {
81        Self {
82            line: 1,
83            column: 1,
84            index: 0,
85            depth: 0,
86            iter: input.chars().peekable(),
87            pushed_back: None,
88        }
89    }
90
91    fn check_depth<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T>) -> Result<T> {
92        if self.depth >= MAX_DEPTH {
93            Err(self.make_error(SnbtErrorKind::DepthLimitExceeded))
94        } else {
95            self.depth += 1;
96            let res = f(self);
97            self.depth -= 1;
98            res
99        }
100    }
101
102    fn make_error(&self, kind: SnbtErrorKind) -> SnbtError {
103        SnbtError::new(kind, self.line, self.column)
104    }
105
106    fn peek(&mut self) -> Result<char> {
107        if let Some(c) = self.pushed_back {
108            Ok(c)
109        } else {
110            self.iter
111                .peek()
112                .copied()
113                .ok_or_else(|| self.make_error(SnbtErrorKind::ReachEndOfStream))
114        }
115    }
116
117    fn next(&mut self) {
118        if self.pushed_back.is_some() {
119            self.pushed_back = None;
120            return;
121        }
122
123        let result = self.iter.next();
124
125        if let Some(c) = result {
126            if c == '\n' {
127                self.line += 1;
128                self.column = 1;
129            } else {
130                self.column += 1;
131            }
132            self.index += c.len_utf8();
133        }
134    }
135
136    /// Push back a char, only one char can be pushed back
137    fn push_back(&mut self, c: char) {
138        if c == '\n' {
139            self.line -= 1;
140            self.column = 1;
141        } else {
142            self.column -= 1;
143        }
144
145        self.index -= c.len_utf8();
146
147        match self.pushed_back {
148            Some(_) => panic!("Can't push back two chars"),
149            None => self.pushed_back = Some(c),
150        };
151    }
152
153    fn skip_whitespace(&mut self) {
154        loop {
155            match self.peek() {
156                Ok(c) if c.is_whitespace() => self.next(),
157                _ => break,
158            };
159        }
160    }
161
162    fn read_string(&mut self) -> Result<String> {
163        let first = self.peek()?;
164
165        let str = match first {
166            '\"' | '\'' => self.read_quoted_string(),
167            _ => self.read_unquoted_string(),
168        }?;
169
170        if str.len() > STRING_MAX_LEN {
171            return Err(self.make_error(SnbtErrorKind::LongString));
172        }
173
174        Ok(str)
175    }
176
177    fn read_unquoted_string(&mut self) -> Result<String> {
178        let mut result = String::new();
179
180        loop {
181            let input = self.peek();
182            match input {
183                Ok('a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '+' | '.') => {
184                    result.push(input?);
185                    self.next();
186                }
187                _ => break,
188            }
189        }
190
191        Ok(result)
192    }
193
194    fn read_quoted_string(&mut self) -> Result<String> {
195        let quote = self.peek()?;
196        self.next();
197
198        let mut result = String::new();
199        loop {
200            let input = self.peek();
201            match input {
202                Ok(c) if c == quote => {
203                    self.next();
204                    break;
205                }
206                Ok('\\') => {
207                    self.next();
208
209                    let escape = self.peek()?;
210                    if escape == quote || escape == '\\' {
211                        result.push(escape);
212                    } else {
213                        return Err(self.make_error(SnbtErrorKind::InvalidEscapeSequence));
214                    }
215
216                    self.next();
217                }
218                Ok(c) => {
219                    result.push(c);
220                    self.next();
221                }
222                Err(e) => return Err(e),
223            }
224        }
225        if result.len() > STRING_MAX_LEN {
226            return Err(self.make_error(SnbtErrorKind::LongString));
227        }
228        Ok(result)
229    }
230
231    fn parse_compound(&mut self) -> Result<Compound> {
232        self.next();
233        self.skip_whitespace();
234
235        let mut cpd = Compound::new();
236        while self.peek()? != '}' {
237            let key = self.read_string()?;
238
239            self.skip_whitespace();
240
241            if key.is_empty() {
242                return Err(self.make_error(SnbtErrorKind::EmptyKeyInCompound));
243            }
244
245            if self.peek()? != ':' {
246                return Err(self.make_error(SnbtErrorKind::ExpectColon));
247            }
248
249            self.next();
250            self.skip_whitespace();
251
252            let value = self.parse_element()?;
253
254            self.skip_whitespace();
255            if self.peek()? == ',' {
256                self.next();
257                self.skip_whitespace();
258            } else if self.peek()? != '}' {
259                return Err(self.make_error(SnbtErrorKind::ExpectComma));
260            }
261
262            cpd.insert(key, value);
263        }
264        self.next();
265        Ok(cpd)
266    }
267
268    fn continue_parse_list(&mut self) -> Result<List> {
269        self.skip_whitespace();
270
271        let mut list = List::End;
272
273        while self.peek()? != ']' {
274            let value = self.parse_element()?;
275            self.skip_whitespace();
276
277            match (&mut list, value) {
278                (list @ List::End, value) => *list = value.into(),
279                (List::Byte(l), Value::Byte(v)) => l.push(v),
280                (List::Short(l), Value::Short(v)) => l.push(v),
281                (List::Int(l), Value::Int(v)) => l.push(v),
282                (List::Long(l), Value::Long(v)) => l.push(v),
283                (List::Float(l), Value::Float(v)) => l.push(v),
284                (List::Double(l), Value::Double(v)) => l.push(v),
285                (List::ByteArray(l), Value::ByteArray(v)) => l.push(v),
286                (List::String(l), Value::String(v)) => l.push(v),
287                (List::List(l), Value::List(v)) => l.push(v),
288                (List::Compound(l), Value::Compound(v)) => l.push(v),
289                (List::IntArray(l), Value::IntArray(v)) => l.push(v),
290                (List::LongArray(l), Value::LongArray(v)) => l.push(v),
291                _ => return Err(self.make_error(SnbtErrorKind::DifferentTypesInList)),
292            }
293
294            if self.peek()? == ',' {
295                self.next();
296                self.skip_whitespace();
297            } else if self.peek()? != ']' {
298                return Err(self.make_error(SnbtErrorKind::ExpectComma));
299            }
300        }
301        self.next();
302
303        Ok(list)
304    }
305
306    fn parse_list_like(&mut self) -> Result<Value> {
307        self.next();
308
309        let type_char = self.peek()?;
310
311        let mut values = match type_char {
312            'B' => Value::ByteArray(vec![]),
313            'I' => Value::IntArray(vec![]),
314            'L' => Value::LongArray(vec![]),
315            _ => return self.check_depth(|v| Ok(v.continue_parse_list()?.into())),
316        };
317
318        self.next();
319
320        if self.peek()? != ';' {
321            self.push_back(type_char);
322            return self.check_depth(|v| Ok(v.continue_parse_list()?.into()));
323        }
324
325        self.next();
326        self.skip_whitespace();
327
328        while self.peek()? != ']' {
329            let value = self.parse_element()?;
330
331            match (&mut values, value) {
332                (Value::ByteArray(l), Value::Byte(v)) => l.push(v),
333                (Value::IntArray(l), Value::Int(v)) => l.push(v),
334                (Value::LongArray(l), Value::Long(v)) => l.push(v),
335                _ => return Err(self.make_error(SnbtErrorKind::WrongTypeInArray)),
336            }
337
338            self.skip_whitespace();
339            if self.peek()? == ',' {
340                self.next();
341                self.skip_whitespace();
342            } else if self.peek()? != ']' {
343                return Err(self.make_error(SnbtErrorKind::ExpectComma));
344            }
345        }
346
347        self.next();
348
349        Ok(values)
350    }
351
352    fn parse_primitive(&mut self) -> Result<Value> {
353        macro_rules! try_ret {
354            // Try possible solution until one works
355            ($v:expr) => {{
356                match $v {
357                    Ok(v) => return Ok(v.into()),
358                    Err(_) => (),
359                }
360            }};
361        }
362
363        let target = self.read_unquoted_string()?;
364
365        match target
366            .bytes()
367            .last()
368            .ok_or_else(|| self.make_error(SnbtErrorKind::ExpectValue))?
369        {
370            b'b' | b'B' => try_ret!(target[..target.len() - 1].parse::<i8>()),
371            b's' | b'S' => try_ret!(target[..target.len() - 1].parse::<i16>()),
372            b'l' | b'L' => try_ret!(target[..target.len() - 1].parse::<i64>()),
373            b'f' | b'F' => try_ret!(target[..target.len() - 1].parse::<f32>()),
374            b'd' | b'D' => try_ret!(target[..target.len() - 1].parse::<f64>()),
375            _ => (),
376        }
377
378        match target.as_str() {
379            "true" => return Ok(Value::Byte(1)),
380            "false" => return Ok(Value::Byte(0)),
381            _ => {
382                try_ret!(target.parse::<i32>());
383                try_ret!(target.parse::<f64>());
384            }
385        };
386
387        if target.len() > STRING_MAX_LEN {
388            return Err(self.make_error(SnbtErrorKind::LongString));
389        }
390
391        Ok(Value::String(target))
392    }
393
394    /// Read the next element in the SNBT string.
395    /// [`SnbtErrorKind::TrailingData`] cannot be returned because it is not
396    /// considered to be an error.
397    pub fn parse_element(&mut self) -> Result<Value> {
398        self.skip_whitespace();
399
400        match self.peek()? {
401            '{' => self.check_depth(|v| Ok(v.parse_compound()?.into())),
402            '[' => self.parse_list_like(),
403            '"' | '\'' => self.read_quoted_string().map(|s| s.into()),
404            _ => self.parse_primitive(),
405        }
406    }
407
408    pub fn read(&mut self) -> Result<Value> {
409        let value = self.parse_element()?;
410
411        self.skip_whitespace();
412        if self.peek().is_ok() {
413            return Err(self.make_error(SnbtErrorKind::TrailingData));
414        }
415
416        Ok(value)
417    }
418
419    /// Get the number of bytes read.
420    /// It's useful when you want to read a SNBT string from an command argument
421    /// since there may be trailing data.
422    pub fn bytes_read(&self) -> usize {
423        self.index
424    }
425}
426/// Parse a string in SNBT format into a `Value`.
427/// Assert that the string has no trailing data.
428/// SNBT is quite similar to JSON, but with some differences.
429/// See [the wiki](https://minecraft.wiki/w/NBT_format#SNBT_format) for more information.
430///
431/// # Example
432///
433/// ```
434/// use valence_nbt::snbt::from_snbt_str;
435/// use valence_nbt::Value;
436///
437/// let value = from_snbt_str("1f").unwrap();
438/// assert_eq!(value, Value::Float(1.0));
439/// ```
440pub fn from_snbt_str(snbt: &str) -> Result<Value> {
441    SnbtReader::new(snbt).read()
442}
443
444#[derive(Debug)]
445pub struct SnbtWriter<'a> {
446    output: &'a mut String,
447}
448
449impl<'a> SnbtWriter<'a> {
450    pub fn new(output: &'a mut String) -> Self {
451        Self { output }
452    }
453
454    fn write_string(&mut self, s: &str) {
455        let mut need_quote = false;
456        for c in s.chars() {
457            if !matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '+' | '.') {
458                need_quote = true;
459                break;
460            }
461        }
462
463        if need_quote {
464            self.output.push('"');
465            for c in s.chars() {
466                match c {
467                    '"' => self.output.push_str("\\\""),
468                    '\\' => self.output.push_str("\\\\"),
469                    _ => self.output.push(c),
470                }
471            }
472            self.output.push('"');
473        } else {
474            self.output.push_str(s);
475        }
476    }
477
478    fn write_primitive_array<'b>(
479        &mut self,
480        prefix: &str,
481        iter: impl Iterator<Item = &'b (impl Into<Value> + 'b + Copy)>,
482    ) {
483        self.output.push('[');
484        self.output.push_str(prefix);
485
486        let mut first = true;
487
488        for v in iter {
489            if !first {
490                self.output.push(',');
491            }
492            first = false;
493
494            self.write_element(&(*v).into());
495        }
496
497        self.output.push(']');
498    }
499
500    fn write_primitive(&mut self, postfix: &str, value: impl ToString) {
501        self.output.push_str(&value.to_string());
502        self.output.push_str(postfix);
503    }
504
505    fn write_list(&mut self, list: &List) {
506        macro_rules! variant_impl {
507            ($v:expr, $handle:expr) => {{
508                self.output.push('[');
509
510                let mut first = true;
511                for v in $v.iter() {
512                    if !first {
513                        self.output.push(',');
514                    }
515                    first = false;
516                    $handle(v);
517                }
518
519                self.output.push(']');
520            }};
521        }
522        #[allow(clippy::redundant_closure_call)]
523        match list {
524            List::Byte(v) => variant_impl!(v, |v| self.write_primitive("b", v)),
525            List::Short(v) => variant_impl!(v, |v| self.write_primitive("s", v)),
526            List::Int(v) => variant_impl!(v, |v| self.write_primitive("", v)),
527            List::Long(v) => variant_impl!(v, |v| self.write_primitive("l", v)),
528            List::Float(v) => variant_impl!(v, |v| self.write_primitive("f", v)),
529            List::Double(v) => variant_impl!(v, |v| self.write_primitive("d", v)),
530            List::ByteArray(v) => {
531                variant_impl!(v, |v: &Vec<i8>| self.write_primitive_array("B", v.iter()))
532            }
533            List::IntArray(v) => {
534                variant_impl!(v, |v: &Vec<i32>| self.write_primitive_array("", v.iter()))
535            }
536            List::LongArray(v) => {
537                variant_impl!(v, |v: &Vec<i64>| self.write_primitive_array("L", v.iter()))
538            }
539            List::String(v) => variant_impl!(v, |v| self.write_string(v)),
540            List::List(v) => variant_impl!(v, |v| self.write_list(v)),
541            List::Compound(v) => variant_impl!(v, |v| self.write_compound(v)),
542            List::End => self.output.push_str("[]"),
543        }
544    }
545
546    fn write_compound(&mut self, compound: &Compound) {
547        self.output.push('{');
548
549        let mut first = true;
550        for (k, v) in compound {
551            if !first {
552                self.output.push(',');
553            }
554
555            first = false;
556
557            self.write_string(k);
558            self.output.push(':');
559            self.write_element(v);
560        }
561
562        self.output.push('}');
563    }
564
565    /// Write a value to the output.
566    pub fn write_element(&mut self, value: &Value) {
567        use Value::*;
568        match value {
569            Byte(v) => self.write_primitive("b", v),
570            Short(v) => self.write_primitive("s", v),
571            Int(v) => self.write_primitive("", v),
572            Long(v) => self.write_primitive("l", v),
573            Float(v) => self.write_primitive("f", v),
574            Double(v) => self.write_primitive("d", v),
575            ByteArray(v) => self.write_primitive_array("B;", v.iter()),
576            IntArray(v) => self.write_primitive_array("I;", v.iter()),
577            LongArray(v) => self.write_primitive_array("L;", v.iter()),
578            String(v) => self.write_string(v),
579            List(v) => self.write_list(v),
580            Compound(v) => self.write_compound(v),
581        }
582    }
583}
584
585/// Convert a value to a string in SNBT format.
586pub fn to_snbt_string(value: &Value) -> String {
587    let mut output = String::new();
588    let mut writer = SnbtWriter::new(&mut output);
589
590    writer.write_element(value);
591
592    output
593}
594
595impl Display for SnbtWriter<'_> {
596    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
597        write!(f, "{}", self.output)
598    }
599}
600
601#[cfg(test)]
602mod tests {
603
604    use super::*;
605
606    #[test]
607    fn test_parse() {
608        let str = r#"
609			{
610				foo: 1,
611				'bar': 1.0,
612				"baz": 1.0f,
613				"hello'": "hello world",
614				"world": "hello\"world",
615				1.5f: 1.5d,
616				3b: 2f,
617				bool: false,
618				more: {
619					iarr: [I; 1, 2, 3],
620					larr: [L; 1L, 2L, 3L],
621				},
622				empty: [Bibabo ],
623			}
624		"#;
625
626        let value = from_snbt_str(str).unwrap();
627        let Value::Compound(cpd) = &value else {
628            unreachable!()
629        };
630
631        assert_eq!(*cpd.get("foo").unwrap(), 1_i32.into());
632        assert_eq!(*cpd.get("bar").unwrap(), 1_f64.into());
633        assert_eq!(*cpd.get("baz").unwrap(), 1_f32.into());
634        assert_eq!(*cpd.get("hello'").unwrap(), "hello world".into());
635        assert_eq!(*cpd.get("world").unwrap(), "hello\"world".into());
636        assert_eq!(*cpd.get("1.5f").unwrap(), 1.5_f64.into());
637        assert_eq!(*cpd.get("3b").unwrap(), 2_f32.into());
638        assert_eq!(*cpd.get("bool").unwrap(), 0_i8.into());
639
640        let Some(Value::Compound(more)) = cpd.get("more") else {
641            unreachable!()
642        };
643
644        assert_eq!(*more.get("iarr").unwrap(), vec![1, 2, 3].into());
645
646        assert_eq!(*more.get("larr").unwrap(), vec![1_i64, 2, 3].into());
647
648        let Value::List(List::String(list)) = cpd.get("empty").unwrap() else {
649            unreachable!()
650        };
651
652        assert_eq!(list[0], "Bibabo");
653
654        assert_eq!(
655            from_snbt_str("\"\\n\"").unwrap_err().kind,
656            SnbtErrorKind::InvalidEscapeSequence
657        );
658
659        assert_eq!(
660            from_snbt_str("[L; 1]").unwrap_err().kind,
661            SnbtErrorKind::WrongTypeInArray
662        );
663
664        assert_eq!(
665            from_snbt_str("[L; 1L, 2L, 3L").unwrap_err().kind,
666            SnbtErrorKind::ReachEndOfStream
667        );
668
669        assert_eq!(
670            from_snbt_str("[L; 1L, 2L, 3L,]dewdwe").unwrap_err().kind,
671            SnbtErrorKind::TrailingData
672        );
673
674        assert_eq!(
675            from_snbt_str("{ foo: }").unwrap_err().kind,
676            SnbtErrorKind::ExpectValue
677        );
678
679        assert_eq!(
680            from_snbt_str("{ {}, }").unwrap_err().kind,
681            SnbtErrorKind::EmptyKeyInCompound
682        );
683
684        assert_eq!(
685            from_snbt_str("{ foo 1 }").unwrap_err().kind,
686            SnbtErrorKind::ExpectColon
687        );
688
689        assert_eq!(
690            from_snbt_str("{ foo: 1 bar: 2 }").unwrap_err().kind,
691            SnbtErrorKind::ExpectComma
692        );
693
694        assert_eq!(
695            from_snbt_str("[{}, []]").unwrap_err().kind,
696            SnbtErrorKind::DifferentTypesInList
697        );
698
699        assert_eq!(
700            from_snbt_str(&String::from_utf8(vec![b'e'; 32768]).unwrap())
701                .unwrap_err()
702                .kind,
703            SnbtErrorKind::LongString
704        );
705
706        assert_eq!(
707            from_snbt_str(
708                &String::from_utf8([[b'['; MAX_DEPTH + 1], [b']'; MAX_DEPTH + 1]].concat())
709                    .unwrap()
710            )
711            .unwrap_err()
712            .kind,
713            SnbtErrorKind::DepthLimitExceeded
714        );
715
716        #[cfg(feature = "preserve_order")]
717        assert_eq!(
718            to_snbt_string(&value),
719            r#"{foo:1,bar:1d,baz:1f,"hello'":"hello world",world:"hello\"world",1.5f:1.5d,3b:2f,bool:0b,more:{iarr:[I;1,2,3],larr:[L;1l,2l,3l]},empty:[Bibabo]}"#
720        );
721    }
722}