Skip to main content

steel_utils/nbt/snbt/
error.rs

1use std::{error::Error, fmt};
2
3use text_components::TextComponent;
4
5use crate::translations;
6
7/// Error returned when parsing SNBT text.
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct SnbtError {
10    cursor: usize,
11    kind: SnbtErrorKind,
12}
13
14impl SnbtError {
15    pub(super) const fn new(cursor: usize, kind: SnbtErrorKind) -> Self {
16        Self { cursor, kind }
17    }
18
19    /// Returns the byte cursor where parsing failed.
20    #[must_use]
21    pub const fn cursor(&self) -> usize {
22        self.cursor
23    }
24
25    /// Returns the specific parse failure.
26    #[must_use]
27    pub const fn kind(&self) -> &SnbtErrorKind {
28        &self.kind
29    }
30
31    /// Returns the specific parse failure, consuming this error.
32    #[must_use]
33    pub fn into_kind(self) -> SnbtErrorKind {
34        self.kind
35    }
36
37    /// Returns the parse failure as a translatable text component.
38    #[must_use]
39    pub fn component(&self) -> TextComponent {
40        self.kind.component()
41    }
42}
43
44impl fmt::Display for SnbtError {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        write!(f, "SNBT parse error at byte {}: {}", self.cursor, self.kind)
47    }
48}
49
50impl Error for SnbtError {}
51
52/// Specific reason why SNBT parsing failed.
53#[non_exhaustive]
54#[derive(Clone, Debug, PartialEq, Eq)]
55pub enum SnbtErrorKind {
56    /// Non-whitespace input remained after a complete tag.
57    TrailingData,
58    /// A grammar symbol was required at the cursor.
59    ExpectedSymbol(char),
60    /// An SNBT value was required at the cursor.
61    ExpectedValue,
62    /// A compound key was required at the cursor.
63    ExpectedKey,
64    /// A compound key was present but empty.
65    EmptyKey,
66    /// A typed-array element was not an integer.
67    ExpectedArrayElement,
68    /// A typed-array element used an unsupported integer width.
69    InvalidArrayElementType,
70    /// The `bool` operation received neither a number nor a boolean.
71    ExpectedNumberOrBoolean,
72    /// The `uuid` operation did not receive a valid UUID string.
73    ExpectedStringUuid,
74    /// No built-in operation matched the name and argument count.
75    UnknownOperation {
76        /// Operation name supplied by the input.
77        name: String,
78        /// Number of supplied arguments.
79        argument_count: usize,
80    },
81    /// A number was required at the cursor.
82    ExpectedNumber,
83    /// A binary numeral was required at the cursor.
84    ExpectedBinaryNumeral,
85    /// A decimal numeral was required at the cursor.
86    ExpectedDecimalNumeral,
87    /// A hexadecimal numeral was required at the cursor.
88    ExpectedHexNumeral,
89    /// A quoted string was required at the cursor.
90    ExpectedQuotedString,
91    /// A quoted string was not terminated.
92    UnclosedQuotedString,
93    /// An escape introducer was not followed by an escape value.
94    UnclosedEscapeSequence,
95    /// A quoted string contained an unsupported escape.
96    InvalidEscape(char),
97    /// A Unicode escape did not contain the required hexadecimal digits.
98    ExpectedHexEscape {
99        /// Required number of hexadecimal digits.
100        digits: usize,
101    },
102    /// A Unicode escape resolved to an invalid code point.
103    InvalidCodepoint(u32),
104    /// A named Unicode escape did not begin with a character name.
105    ExpectedCharacterName,
106    /// A named Unicode escape was not terminated.
107    UnclosedCharacterName,
108    /// A named Unicode escape did not identify a character.
109    InvalidCharacterName(String),
110    /// An unquoted string was required at the cursor.
111    ExpectedUnquotedString,
112    /// A floating-point token could not be parsed.
113    InvalidFloatingPoint,
114    /// A non-finite floating-point value was supplied.
115    NonFiniteNumber,
116    /// A number placed underscores outside its digits.
117    InvalidUnderscore,
118    /// An integer token could not be parsed.
119    InvalidInteger,
120    /// A decimal integer contained a leading zero.
121    LeadingZero,
122    /// An unsigned integer was negative.
123    ExpectedNonNegativeNumber,
124    /// An integer exceeded the parser's intermediate representation.
125    IntegerTooLarge,
126    /// A number did not fit its requested NBT integer type.
127    NumberOutOfRange {
128        /// Requested NBT integer type.
129        number_type: SnbtNumberType,
130        /// Whether the literal requested the unsigned range.
131        unsigned: bool,
132    },
133    /// A number token did not contain any digits.
134    InvalidNumber,
135}
136
137impl SnbtErrorKind {
138    /// Returns this failure as a translatable text component.
139    #[must_use]
140    pub fn component(&self) -> TextComponent {
141        match self {
142            Self::TrailingData => TextComponent::from(&translations::ARGUMENT_NBT_TRAILING),
143            Self::ExpectedSymbol(symbol) => translations::ARGUMENT_LITERAL_INCORRECT
144                .message([symbol.to_string()])
145                .component(),
146            Self::ExpectedValue | Self::ExpectedUnquotedString => {
147                TextComponent::from(&translations::SNBT_PARSER_EXPECTED_UNQUOTED_STRING)
148            }
149            Self::ExpectedKey | Self::ExpectedQuotedString => {
150                translations::ARGUMENT_LITERAL_INCORRECT
151                    .message(["\""])
152                    .component()
153            }
154            Self::EmptyKey => TextComponent::from(&translations::SNBT_PARSER_EMPTY_KEY),
155            Self::ExpectedNumber => translations::ARGUMENT_LITERAL_INCORRECT
156                .message(["+"])
157                .component(),
158            Self::ExpectedBinaryNumeral => {
159                TextComponent::from(&translations::SNBT_PARSER_EXPECTED_BINARY_NUMERAL)
160            }
161            Self::ExpectedDecimalNumeral => {
162                TextComponent::from(&translations::SNBT_PARSER_EXPECTED_DECIMAL_NUMERAL)
163            }
164            Self::ExpectedHexNumeral => {
165                TextComponent::from(&translations::SNBT_PARSER_EXPECTED_HEX_NUMERAL)
166            }
167            Self::ExpectedArrayElement => {
168                TextComponent::from(&translations::SNBT_PARSER_EXPECTED_INTEGER_TYPE)
169            }
170            Self::InvalidArrayElementType => {
171                TextComponent::from(&translations::SNBT_PARSER_INVALID_ARRAY_ELEMENT_TYPE)
172            }
173            Self::ExpectedNumberOrBoolean => {
174                TextComponent::from(&translations::SNBT_PARSER_EXPECTED_NUMBER_OR_BOOLEAN)
175            }
176            Self::ExpectedStringUuid => {
177                TextComponent::from(&translations::SNBT_PARSER_EXPECTED_STRING_UUID)
178            }
179            Self::UnknownOperation {
180                name,
181                argument_count,
182            } => translations::SNBT_PARSER_NO_SUCH_OPERATION
183                .message([format!("{name}/{argument_count}")])
184                .component(),
185            Self::UnclosedQuotedString => {
186                TextComponent::from(&translations::SNBT_PARSER_INVALID_STRING_CONTENTS)
187            }
188            Self::UnclosedEscapeSequence | Self::InvalidEscape(_) => {
189                translations::ARGUMENT_LITERAL_INCORRECT
190                    .message(["b"])
191                    .component()
192            }
193            Self::ExpectedCharacterName => translations::ARGUMENT_LITERAL_INCORRECT
194                .message(["{"])
195                .component(),
196            Self::UnclosedCharacterName => translations::ARGUMENT_LITERAL_INCORRECT
197                .message(["}"])
198                .component(),
199            Self::ExpectedHexEscape { digits } => translations::SNBT_PARSER_EXPECTED_HEX_ESCAPE
200                .message([digits.to_string()])
201                .component(),
202            Self::InvalidCodepoint(codepoint) => translations::SNBT_PARSER_INVALID_CODEPOINT
203                .message([format!("U+{codepoint:08X}")])
204                .component(),
205            Self::InvalidCharacterName(_) => {
206                TextComponent::from(&translations::SNBT_PARSER_INVALID_CHARACTER_NAME)
207            }
208            Self::NonFiniteNumber => {
209                TextComponent::from(&translations::SNBT_PARSER_INFINITY_NOT_ALLOWED)
210            }
211            // The shipped assets consistently use Mojang's misspelled `undescore` key.
212            Self::InvalidUnderscore => {
213                TextComponent::from(&translations::SNBT_PARSER_UNDESCORE_NOT_ALLOWED)
214            }
215            Self::LeadingZero => {
216                TextComponent::from(&translations::SNBT_PARSER_LEADING_ZERO_NOT_ALLOWED)
217            }
218            Self::ExpectedNonNegativeNumber => {
219                TextComponent::from(&translations::SNBT_PARSER_EXPECTED_NON_NEGATIVE_NUMBER)
220            }
221            Self::InvalidFloatingPoint
222            | Self::InvalidInteger
223            | Self::IntegerTooLarge
224            | Self::NumberOutOfRange { .. }
225            | Self::InvalidNumber => translations::SNBT_PARSER_NUMBER_PARSE_FAILURE
226                .message([self.to_string()])
227                .component(),
228        }
229    }
230}
231
232impl fmt::Display for SnbtErrorKind {
233    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
234        match self {
235            Self::TrailingData => formatter.write_str("trailing data"),
236            Self::ExpectedSymbol(symbol) => write!(formatter, "expected '{symbol}'"),
237            Self::ExpectedValue => formatter.write_str("expected tag"),
238            Self::ExpectedKey => formatter.write_str("expected compound key"),
239            Self::EmptyKey => formatter.write_str("compound key cannot be empty"),
240            Self::ExpectedArrayElement => formatter.write_str("expected integer array element"),
241            Self::InvalidArrayElementType => {
242                formatter.write_str("invalid typed array element width")
243            }
244            Self::ExpectedNumberOrBoolean => formatter.write_str("bool expects a numeric tag"),
245            Self::ExpectedStringUuid => formatter.write_str("uuid expects a valid string tag"),
246            Self::UnknownOperation {
247                name,
248                argument_count,
249            } => write!(
250                formatter,
251                "unknown SNBT operation '{name}/{argument_count}'"
252            ),
253            Self::ExpectedNumber => formatter.write_str("expected number"),
254            Self::ExpectedBinaryNumeral => formatter.write_str("expected binary numeral"),
255            Self::ExpectedDecimalNumeral => formatter.write_str("expected decimal numeral"),
256            Self::ExpectedHexNumeral => formatter.write_str("expected hexadecimal numeral"),
257            Self::ExpectedQuotedString => formatter.write_str("expected quoted string"),
258            Self::UnclosedQuotedString => formatter.write_str("unclosed quoted string"),
259            Self::UnclosedEscapeSequence => formatter.write_str("unclosed escape sequence"),
260            Self::InvalidEscape(character) => {
261                write!(formatter, "invalid escape '\\{character}'")
262            }
263            Self::ExpectedHexEscape { digits } => {
264                write!(formatter, "expected {digits} hexadecimal escape digits")
265            }
266            Self::InvalidCodepoint(codepoint) => {
267                write!(formatter, "invalid Unicode code point U+{codepoint:08X}")
268            }
269            Self::ExpectedCharacterName => formatter.write_str("expected Unicode character name"),
270            Self::UnclosedCharacterName => formatter.write_str("unclosed Unicode character name"),
271            Self::InvalidCharacterName(name) => {
272                write!(formatter, "unknown Unicode name '{name}'")
273            }
274            Self::ExpectedUnquotedString => formatter.write_str("expected unquoted string"),
275            Self::InvalidFloatingPoint => formatter.write_str("invalid floating-point literal"),
276            Self::NonFiniteNumber => formatter.write_str("floating-point literal must be finite"),
277            Self::InvalidUnderscore => {
278                formatter.write_str("invalid underscore placement in number literal")
279            }
280            Self::InvalidInteger => formatter.write_str("invalid integer literal"),
281            Self::LeadingZero => formatter.write_str("integer literal cannot have leading zeroes"),
282            Self::ExpectedNonNegativeNumber => {
283                formatter.write_str("unsigned integer literal cannot be negative")
284            }
285            Self::IntegerTooLarge => formatter.write_str("integer literal is too large"),
286            Self::NumberOutOfRange {
287                number_type,
288                unsigned,
289            } => {
290                if *unsigned {
291                    write!(formatter, "unsigned {number_type} literal is out of range")
292                } else {
293                    write!(formatter, "{number_type} literal is out of range")
294                }
295            }
296            Self::InvalidNumber => formatter.write_str("invalid number literal"),
297        }
298    }
299}
300
301/// NBT integer type requested by an SNBT number suffix or array.
302#[non_exhaustive]
303#[derive(Clone, Copy, Debug, PartialEq, Eq)]
304pub enum SnbtNumberType {
305    /// Signed or unsigned byte.
306    Byte,
307    /// Signed or unsigned short.
308    Short,
309    /// Signed or unsigned integer.
310    Int,
311    /// Signed or unsigned long.
312    Long,
313}
314
315impl fmt::Display for SnbtNumberType {
316    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
317        formatter.write_str(match self {
318            Self::Byte => "byte",
319            Self::Short => "short",
320            Self::Int => "int",
321            Self::Long => "long",
322        })
323    }
324}