Skip to main content

steel_utils/nbt/snbt/
parser.rs

1use simdnbt::{
2    Mutf8String,
3    owned::{NbtCompound, NbtList, NbtTag},
4};
5use uuid::Uuid;
6
7use crate::{UuidExt, java};
8
9use super::{
10    error::{SnbtError, SnbtErrorKind},
11    number::{
12        DefaultIntegerKind, IntegerKind, bool_tag_value, can_start_number, integer_tag_value,
13        is_allowed_in_unicode_name, is_allowed_in_unquoted_string,
14        is_unsuffixed_decimal_integer_token, parse_number_token, scan_number_token,
15    },
16};
17
18/// Parses one complete SNBT tag.
19///
20/// # Errors
21///
22/// Returns an error when the input is not valid SNBT or has trailing data.
23pub fn parse_snbt(input: &str) -> Result<NbtTag, SnbtError> {
24    let (tag, cursor) = parse_snbt_argument(input)?;
25    let mut parser = Parser::new(input);
26    parser.cursor = cursor;
27    parser.skip_whitespace();
28    if parser.can_read() {
29        return Err(parser.error(SnbtErrorKind::TrailingData));
30    }
31
32    Ok(tag)
33}
34
35/// Parses one SNBT tag and returns the byte cursor consumed by that tag.
36///
37/// Unlike [`parse_snbt`], this does not consume trailing whitespace after the
38/// tag. Command parsers use the returned cursor so the command graph can own
39/// node-separating whitespace.
40///
41/// # Errors
42///
43/// Returns an error when the input does not start with a valid SNBT tag.
44pub fn parse_snbt_argument(input: &str) -> Result<(NbtTag, usize), SnbtError> {
45    let mut parser = Parser::new(input);
46    match parser.parse_tag() {
47        Ok(tag) => Ok((tag, parser.cursor)),
48        Err(error) => Err(parser.resolve_error(error)),
49    }
50}
51
52/// Parses one complete SNBT compound.
53///
54/// # Errors
55///
56/// Returns an error when the input is not a valid SNBT compound or has trailing
57/// data.
58pub fn parse_snbt_compound(input: &str) -> Result<NbtCompound, SnbtError> {
59    let (compound, cursor) = parse_snbt_compound_argument(input)?;
60    let mut parser = Parser::new(input);
61    parser.cursor = cursor;
62    parser.skip_whitespace();
63    if parser.can_read() {
64        return Err(parser.error(SnbtErrorKind::TrailingData));
65    }
66
67    Ok(compound)
68}
69
70/// Parses one SNBT compound and returns the byte cursor consumed by it.
71///
72/// # Errors
73///
74/// Returns an error when the input does not start with a valid SNBT compound.
75pub fn parse_snbt_compound_argument(input: &str) -> Result<(NbtCompound, usize), SnbtError> {
76    let mut parser = Parser::new(input);
77    match parser.parse_compound() {
78        Ok(compound) => Ok((compound, parser.cursor)),
79        Err(error) => Err(parser.resolve_error(error)),
80    }
81}
82struct Parser<'a> {
83    input: &'a str,
84    cursor: usize,
85    recorded_error: Option<SnbtError>,
86}
87
88impl<'a> Parser<'a> {
89    const fn new(input: &'a str) -> Self {
90        Self {
91            input,
92            cursor: 0,
93            recorded_error: None,
94        }
95    }
96
97    const fn can_read(&self) -> bool {
98        self.cursor < self.input.len()
99    }
100
101    const fn error(&self, kind: SnbtErrorKind) -> SnbtError {
102        SnbtError::new(self.cursor, kind)
103    }
104
105    const fn error_at(cursor: usize, kind: SnbtErrorKind) -> SnbtError {
106        SnbtError::new(cursor, kind)
107    }
108
109    fn record_error(&mut self, cursor: usize, kind: SnbtErrorKind) {
110        if self
111            .recorded_error
112            .as_ref()
113            .is_none_or(|error| cursor > error.cursor())
114        {
115            self.recorded_error = Some(Self::error_at(cursor, kind));
116        }
117    }
118
119    fn resolve_error(&mut self, error: SnbtError) -> SnbtError {
120        match self.recorded_error.take() {
121            Some(recorded) if recorded.cursor() >= error.cursor() => recorded,
122            _ => error,
123        }
124    }
125
126    fn peek(&self) -> Option<char> {
127        self.input[self.cursor..].chars().next()
128    }
129
130    fn read(&mut self) -> Option<char> {
131        let ch = self.peek()?;
132        self.cursor += ch.len_utf8();
133        Some(ch)
134    }
135
136    fn skip_whitespace(&mut self) {
137        while self.peek().is_some_and(java::is_whitespace) {
138            self.read();
139        }
140    }
141
142    fn consume_char(&mut self, expected: char) -> bool {
143        self.skip_whitespace();
144        if self.peek() == Some(expected) {
145            self.read();
146            return true;
147        }
148
149        false
150    }
151
152    fn consume_repeated_separator(&mut self, separator: char) -> bool {
153        self.skip_whitespace();
154        if self.peek() == Some(separator) {
155            self.read();
156            return true;
157        }
158
159        self.record_error(self.cursor, SnbtErrorKind::ExpectedSymbol(separator));
160        false
161    }
162
163    fn expect_char(&mut self, expected: char) -> Result<(), SnbtError> {
164        if self.consume_char(expected) {
165            return Ok(());
166        }
167
168        Err(self.error(SnbtErrorKind::ExpectedSymbol(expected)))
169    }
170
171    fn parse_tag(&mut self) -> Result<NbtTag, SnbtError> {
172        self.skip_whitespace();
173        let Some(ch) = self.peek() else {
174            return Err(self.error(SnbtErrorKind::ExpectedValue));
175        };
176
177        match ch {
178            '{' => Ok(NbtTag::Compound(self.parse_compound()?)),
179            '[' => self.parse_list_or_array(),
180            '"' | '\'' => Ok(NbtTag::String(self.parse_quoted_string()?.into())),
181            ch if can_start_number(ch) => self.parse_number(DefaultIntegerKind::Int, true),
182            ch if is_allowed_in_unquoted_string(ch) => self.parse_unquoted_value(),
183            _ => Err(self.error(SnbtErrorKind::ExpectedValue)),
184        }
185    }
186
187    fn parse_compound(&mut self) -> Result<NbtCompound, SnbtError> {
188        self.expect_char('{')?;
189        let mut compound = NbtCompound::new();
190        if self.consume_char('}') {
191            return Ok(compound);
192        }
193
194        loop {
195            let key = self.parse_map_key()?;
196            self.expect_char(':')?;
197            let tag = self.parse_tag()?;
198            compound.remove(&key);
199            compound.insert(key, tag);
200
201            if self.consume_repeated_separator(',') {
202                if self.consume_char('}') {
203                    return Ok(compound);
204                }
205                continue;
206            }
207
208            self.expect_char('}')?;
209            return Ok(compound);
210        }
211    }
212
213    fn parse_map_key(&mut self) -> Result<String, SnbtError> {
214        self.skip_whitespace();
215        let key = match self.peek() {
216            Some('"' | '\'') => self.parse_quoted_string()?,
217            Some(ch) if is_allowed_in_unquoted_string(ch) => self.parse_unquoted_string()?,
218            _ => return Err(self.error(SnbtErrorKind::ExpectedKey)),
219        };
220
221        if key.is_empty() {
222            return Err(self.error(SnbtErrorKind::EmptyKey));
223        }
224
225        Ok(key)
226    }
227
228    fn parse_list_or_array(&mut self) -> Result<NbtTag, SnbtError> {
229        self.expect_char('[')?;
230        if self.consume_char(']') {
231            return Ok(NbtTag::List(NbtList::Empty));
232        }
233
234        let prefix_cursor = self.cursor;
235        self.skip_whitespace();
236        let array_type = match self.peek() {
237            Some('B') => Some(TypedArrayKind::Byte),
238            Some('I') => Some(TypedArrayKind::Int),
239            Some('L') => Some(TypedArrayKind::Long),
240            _ => None,
241        };
242        if let Some(array_type) = array_type {
243            self.read();
244            if self.consume_char(';') {
245                return self.parse_typed_array(array_type);
246            }
247        }
248        self.cursor = prefix_cursor;
249
250        let mut tags = Vec::new();
251        loop {
252            tags.push(self.parse_tag()?);
253            if self.consume_repeated_separator(',') {
254                if self.consume_char(']') {
255                    break;
256                }
257                continue;
258            }
259
260            self.expect_char(']')?;
261            break;
262        }
263
264        Ok(NbtTag::List(NbtList::from(tags)))
265    }
266
267    fn parse_typed_array(&mut self, array_type: TypedArrayKind) -> Result<NbtTag, SnbtError> {
268        match array_type {
269            TypedArrayKind::Byte => {
270                let values =
271                    self.parse_integer_array(DefaultIntegerKind::Byte, &[IntegerKind::Byte])?;
272                Ok(NbtTag::ByteArray(
273                    values.into_iter().map(|value| value as u8).collect(),
274                ))
275            }
276            TypedArrayKind::Int => {
277                let values = self.parse_integer_array(
278                    DefaultIntegerKind::Int,
279                    &[IntegerKind::Byte, IntegerKind::Short, IntegerKind::Int],
280                )?;
281                Ok(NbtTag::IntArray(
282                    values.into_iter().map(|value| value as i32).collect(),
283                ))
284            }
285            TypedArrayKind::Long => Ok(NbtTag::LongArray(self.parse_integer_array(
286                DefaultIntegerKind::Long,
287                &[
288                    IntegerKind::Byte,
289                    IntegerKind::Short,
290                    IntegerKind::Int,
291                    IntegerKind::Long,
292                ],
293            )?)),
294        }
295    }
296
297    fn parse_integer_array(
298        &mut self,
299        default_kind: DefaultIntegerKind,
300        allowed_kinds: &[IntegerKind],
301    ) -> Result<Vec<i64>, SnbtError> {
302        let mut values = Vec::new();
303        if self.consume_char(']') {
304            return Ok(values);
305        }
306
307        loop {
308            let cursor = self.cursor;
309            let tag = self.parse_number(default_kind, false)?;
310            let Some((kind, value)) = integer_tag_value(&tag) else {
311                return Err(Self::error_at(cursor, SnbtErrorKind::ExpectedArrayElement));
312            };
313            if !allowed_kinds.contains(&kind) {
314                return Err(Self::error_at(
315                    cursor,
316                    SnbtErrorKind::InvalidArrayElementType,
317                ));
318            }
319            values.push(value);
320
321            if self.consume_repeated_separator(',') {
322                if self.consume_char(']') {
323                    return Ok(values);
324                }
325                continue;
326            }
327
328            self.expect_char(']')?;
329            return Ok(values);
330        }
331    }
332
333    fn parse_unquoted_value(&mut self) -> Result<NbtTag, SnbtError> {
334        let value = self.parse_unquoted_string()?;
335        let after_value = self.cursor;
336
337        self.skip_whitespace();
338        if self.peek() == Some('(') {
339            self.read();
340            return self.parse_builtin(&value);
341        }
342        self.record_error(self.cursor, SnbtErrorKind::ExpectedSymbol('('));
343        self.cursor = after_value;
344
345        if value.eq_ignore_ascii_case("true") {
346            Ok(NbtTag::Byte(1))
347        } else if value.eq_ignore_ascii_case("false") {
348            Ok(NbtTag::Byte(0))
349        } else {
350            Ok(NbtTag::String(Mutf8String::from(value)))
351        }
352    }
353
354    fn parse_builtin(&mut self, name: &str) -> Result<NbtTag, SnbtError> {
355        let arguments = self.parse_builtin_arguments()?;
356        let error_cursor = self.cursor;
357
358        if name == "bool" && arguments.len() == 1 {
359            let Some(value) = arguments.first() else {
360                return Err(Self::error_at(
361                    error_cursor,
362                    SnbtErrorKind::ExpectedNumberOrBoolean,
363                ));
364            };
365            return bool_tag_value(value)
366                .map(|value| NbtTag::Byte(i8::from(value)))
367                .ok_or_else(|| {
368                    Self::error_at(error_cursor, SnbtErrorKind::ExpectedNumberOrBoolean)
369                });
370        }
371
372        if name == "uuid" && arguments.len() == 1 {
373            let Some(NbtTag::String(uuid)) = arguments.first() else {
374                return Err(Self::error_at(
375                    error_cursor,
376                    SnbtErrorKind::ExpectedStringUuid,
377                ));
378            };
379            // Steel intentionally accepts the `uuid` crate's formats instead of Java's
380            // legacy `UUID.fromString` edge cases. Canonical dashed UUIDs are compatible.
381            let uuid = Uuid::parse_str(uuid.as_str().to_str().as_ref())
382                .map_err(|_| Self::error_at(error_cursor, SnbtErrorKind::ExpectedStringUuid))?;
383            return Ok(NbtTag::IntArray(uuid.to_int_array().to_vec()));
384        }
385
386        Err(Self::error_at(
387            error_cursor,
388            SnbtErrorKind::UnknownOperation {
389                name: name.to_owned(),
390                argument_count: arguments.len(),
391            },
392        ))
393    }
394
395    fn parse_builtin_arguments(&mut self) -> Result<Vec<NbtTag>, SnbtError> {
396        let mut arguments = Vec::new();
397        if self.consume_char(')') {
398            return Ok(arguments);
399        }
400
401        loop {
402            arguments.push(self.parse_tag()?);
403            if self.consume_repeated_separator(',') {
404                if self.consume_char(')') {
405                    return Ok(arguments);
406                }
407                continue;
408            }
409
410            self.expect_char(')')?;
411            return Ok(arguments);
412        }
413    }
414
415    fn parse_number(
416        &mut self,
417        default_kind: DefaultIntegerKind,
418        allow_float: bool,
419    ) -> Result<NbtTag, SnbtError> {
420        let start = self.cursor;
421        let token_len = scan_number_token(&self.input[start..], allow_float)
422            .map_err(|error| Self::error_at(start + error.cursor, error.kind))?;
423        self.cursor += token_len;
424
425        let token = &self.input[start..self.cursor];
426        let result = parse_number_token(token, default_kind);
427        let records_float_candidate =
428            allow_float && result.is_ok() && is_unsuffixed_decimal_integer_token(token);
429        if records_float_candidate {
430            self.record_error(self.cursor, SnbtErrorKind::ExpectedSymbol('.'));
431        }
432
433        result.map_err(|kind| self.error(kind))
434    }
435
436    fn parse_quoted_string(&mut self) -> Result<String, SnbtError> {
437        let Some(terminator @ ('"' | '\'')) = self.read() else {
438            return Err(self.error(SnbtErrorKind::ExpectedQuotedString));
439        };
440
441        let mut value = String::new();
442        while let Some(ch) = self.read() {
443            match ch {
444                ch if ch == terminator => return Ok(value),
445                '\\' => value.push(self.parse_escape()?),
446                _ => value.push(ch),
447            }
448        }
449
450        Err(self.error(SnbtErrorKind::UnclosedQuotedString))
451    }
452
453    fn parse_escape(&mut self) -> Result<char, SnbtError> {
454        let escape_cursor = self.cursor;
455        let Some(ch) = self.read() else {
456            return Err(Self::error_at(
457                escape_cursor,
458                SnbtErrorKind::UnclosedEscapeSequence,
459            ));
460        };
461
462        match ch {
463            'b' => Ok('\u{0008}'),
464            's' => Ok(' '),
465            't' => Ok('\t'),
466            'n' => Ok('\n'),
467            'f' => Ok('\u{000C}'),
468            'r' => Ok('\r'),
469            '\\' | '\'' | '"' => Ok(ch),
470            'x' => self.parse_code_point_escape(2, self.cursor),
471            'u' => self.parse_code_point_escape(4, self.cursor),
472            'U' => self.parse_code_point_escape(8, self.cursor),
473            'N' => self.parse_named_escape(),
474            _ => Err(Self::error_at(
475                escape_cursor,
476                SnbtErrorKind::InvalidEscape(ch),
477            )),
478        }
479    }
480
481    fn parse_code_point_escape(
482        &mut self,
483        digits: usize,
484        digit_cursor: usize,
485    ) -> Result<char, SnbtError> {
486        let mut value = 0_u32;
487        for _ in 0..digits {
488            let Some(ch) = self.read() else {
489                return Err(Self::error_at(
490                    digit_cursor,
491                    SnbtErrorKind::ExpectedHexEscape { digits },
492                ));
493            };
494            let Some(digit) = ch.to_digit(16) else {
495                return Err(Self::error_at(
496                    digit_cursor,
497                    SnbtErrorKind::ExpectedHexEscape { digits },
498                ));
499            };
500            value = value * 16 + digit;
501        }
502
503        char::from_u32(value).ok_or_else(|| self.error(SnbtErrorKind::InvalidCodepoint(value)))
504    }
505
506    fn parse_named_escape(&mut self) -> Result<char, SnbtError> {
507        let brace_cursor = self.cursor;
508        if self.read() != Some('{') {
509            return Err(Self::error_at(
510                brace_cursor,
511                SnbtErrorKind::ExpectedCharacterName,
512            ));
513        }
514
515        let name_start = self.cursor;
516        while self.peek().is_some_and(is_allowed_in_unicode_name) {
517            self.read();
518        }
519        if self.cursor == name_start {
520            return Err(Self::error_at(
521                name_start,
522                SnbtErrorKind::InvalidCharacterName(String::new()),
523            ));
524        }
525        if self.peek() != Some('}') {
526            return Err(self.error(SnbtErrorKind::UnclosedCharacterName));
527        }
528
529        let name = self.input[name_start..self.cursor].to_owned();
530        self.read();
531        unicode_names2::character(&name)
532            .ok_or_else(|| Self::error_at(self.cursor, SnbtErrorKind::InvalidCharacterName(name)))
533    }
534
535    fn parse_unquoted_string(&mut self) -> Result<String, SnbtError> {
536        let start = self.cursor;
537        while self.peek().is_some_and(is_allowed_in_unquoted_string) {
538            self.read();
539        }
540
541        if self.cursor == start {
542            return Err(Self::error_at(start, SnbtErrorKind::ExpectedUnquotedString));
543        }
544
545        Ok(self.input[start..self.cursor].to_owned())
546    }
547}
548
549#[derive(Clone, Copy, Debug, PartialEq, Eq)]
550enum TypedArrayKind {
551    Byte,
552    Int,
553    Long,
554}