1use std::num::{IntErrorKind, ParseIntError};
2
3use simdnbt::owned::NbtTag;
4
5use super::error::{SnbtErrorKind, SnbtNumberType};
6
7pub(super) fn parse_number_token(
8 token: &str,
9 default_kind: DefaultIntegerKind,
10) -> Result<NbtTag, SnbtErrorKind> {
11 if should_parse_as_float(token) {
12 return parse_float_token(token);
13 }
14
15 parse_integer_token(token, default_kind)
16}
17
18fn should_parse_as_float(token: &str) -> bool {
19 if has_radix_prefix(token) {
20 return false;
21 }
22
23 token.contains('.')
24 || token.contains('e')
25 || token.contains('E')
26 || token.ends_with(['f', 'F', 'd', 'D'])
27}
28
29fn has_radix_prefix(token: &str) -> bool {
30 let stripped = token
31 .strip_prefix(['+', '-'])
32 .map_or(token, |stripped| stripped);
33 stripped.starts_with("0x")
34 || stripped.starts_with("0X")
35 || stripped.starts_with("0b")
36 || stripped.starts_with("0B")
37}
38
39fn parse_float_token(token: &str) -> Result<NbtTag, SnbtErrorKind> {
40 let (body, kind) = if token.ends_with(['f', 'F']) {
41 (&token[..token.len() - 1], FloatKind::Float)
42 } else if token.ends_with(['d', 'D']) {
43 (&token[..token.len() - 1], FloatKind::Double)
44 } else {
45 (token, FloatKind::Double)
46 };
47 validate_float_underscore_placement(body)?;
48 let body = normalize_number_digits(body)?;
49 let value = body
50 .parse::<f64>()
51 .map_err(|_| SnbtErrorKind::InvalidFloatingPoint)?;
52 if !value.is_finite() {
53 return Err(SnbtErrorKind::NonFiniteNumber);
54 }
55
56 match kind {
57 FloatKind::Float => {
58 let value = value as f32;
59 if !value.is_finite() {
60 return Err(SnbtErrorKind::NonFiniteNumber);
61 }
62 Ok(NbtTag::Float(value))
63 }
64 FloatKind::Double => Ok(NbtTag::Double(value)),
65 }
66}
67
68const fn validate_float_underscore_placement(input: &str) -> Result<(), SnbtErrorKind> {
69 let bytes = input.as_bytes();
70 let mut index = 0;
71 while index < bytes.len() {
72 if bytes[index] != b'_' {
73 index += 1;
74 continue;
75 }
76
77 let run_start = index;
78 while index < bytes.len() && bytes[index] == b'_' {
79 index += 1;
80 }
81 let surrounded_by_digits = run_start > 0
82 && index < bytes.len()
83 && bytes[run_start - 1].is_ascii_digit()
84 && bytes[index].is_ascii_digit();
85 if !surrounded_by_digits {
86 return Err(SnbtErrorKind::InvalidUnderscore);
87 }
88 }
89 Ok(())
90}
91
92fn parse_integer_token(
93 token: &str,
94 default_kind: DefaultIntegerKind,
95) -> Result<NbtTag, SnbtErrorKind> {
96 const SUFFIXES: &[(&str, IntegerKind, IntegerSignedness)] = &[
97 ("ub", IntegerKind::Byte, IntegerSignedness::Unsigned),
98 ("us", IntegerKind::Short, IntegerSignedness::Unsigned),
99 ("ui", IntegerKind::Int, IntegerSignedness::Unsigned),
100 ("ul", IntegerKind::Long, IntegerSignedness::Unsigned),
101 ("sb", IntegerKind::Byte, IntegerSignedness::Signed),
102 ("ss", IntegerKind::Short, IntegerSignedness::Signed),
103 ("si", IntegerKind::Int, IntegerSignedness::Signed),
104 ("sl", IntegerKind::Long, IntegerSignedness::Signed),
105 ("b", IntegerKind::Byte, IntegerSignedness::Default),
106 ("s", IntegerKind::Short, IntegerSignedness::Default),
107 ("i", IntegerKind::Int, IntegerSignedness::Default),
108 ("l", IntegerKind::Long, IntegerSignedness::Default),
109 ];
110
111 let lower = token.to_ascii_lowercase();
112 for &(suffix, kind, signedness) in SUFFIXES {
113 if suffix == "b" && has_hex_radix_prefix(token) {
115 continue;
116 }
117 let Some(body) = lower.strip_suffix(suffix) else {
118 continue;
119 };
120 let original_body = &token[..body.len()];
121 if original_body.is_empty() {
122 continue;
123 }
124 return parse_integer_body(original_body, kind, signedness);
125 }
126
127 parse_integer_body(token, default_kind.into(), IntegerSignedness::Default)
128}
129
130fn parse_integer_body(
131 body: &str,
132 kind: IntegerKind,
133 signedness: IntegerSignedness,
134) -> Result<NbtTag, SnbtErrorKind> {
135 let (negative, body) = match body.as_bytes().first().copied() {
136 Some(b'-') => (true, &body[1..]),
137 Some(b'+') => (false, &body[1..]),
138 _ => (false, body),
139 };
140 if body.is_empty() {
141 return Err(SnbtErrorKind::InvalidInteger);
142 }
143
144 let (radix, digits) = if body.starts_with("0x") || body.starts_with("0X") {
145 (16, &body[2..])
146 } else if body.starts_with("0b") || body.starts_with("0B") {
147 (2, &body[2..])
148 } else {
149 (10, body)
150 };
151 if digits.is_empty() {
152 return Err(SnbtErrorKind::InvalidInteger);
153 }
154 if radix == 10 && digits.len() > 1 && digits.starts_with('0') {
155 return Err(SnbtErrorKind::LeadingZero);
156 }
157
158 let digits = normalize_number_digits(digits)?;
159 let signed = signedness == IntegerSignedness::Signed
160 || (radix == 10 && signedness != IntegerSignedness::Unsigned);
161 if negative && !signed {
162 return Err(SnbtErrorKind::ExpectedNonNegativeNumber);
163 }
164
165 if signed {
166 let magnitude = i128::from_str_radix(&digits, radix).map_err(integer_parse_error_kind)?;
167 let value = if negative { -magnitude } else { magnitude };
168 return kind.to_signed_tag(value);
169 }
170
171 let value = u128::from_str_radix(&digits, radix).map_err(integer_parse_error_kind)?;
172 kind.to_unsigned_tag(value)
173}
174
175const fn integer_parse_error_kind(error: ParseIntError) -> SnbtErrorKind {
176 match error.kind() {
177 IntErrorKind::PosOverflow | IntErrorKind::NegOverflow => SnbtErrorKind::IntegerTooLarge,
178 _ => SnbtErrorKind::InvalidInteger,
179 }
180}
181
182fn normalize_number_digits(input: &str) -> Result<String, SnbtErrorKind> {
183 if input.is_empty() {
184 return Err(SnbtErrorKind::InvalidNumber);
185 }
186 if input.starts_with('_') || input.ends_with('_') {
187 return Err(SnbtErrorKind::InvalidUnderscore);
188 }
189
190 Ok(input.chars().filter(|ch| *ch != '_').collect())
191}
192
193fn has_hex_radix_prefix(token: &str) -> bool {
194 let stripped = token
195 .strip_prefix(['+', '-'])
196 .map_or(token, |stripped| stripped);
197 stripped.starts_with("0x") || stripped.starts_with("0X")
198}
199
200pub(super) fn integer_tag_value(tag: &NbtTag) -> Option<(IntegerKind, i64)> {
201 match tag {
202 NbtTag::Byte(value) => Some((IntegerKind::Byte, i64::from(*value))),
203 NbtTag::Short(value) => Some((IntegerKind::Short, i64::from(*value))),
204 NbtTag::Int(value) => Some((IntegerKind::Int, i64::from(*value))),
205 NbtTag::Long(value) => Some((IntegerKind::Long, *value)),
206 _ => None,
207 }
208}
209
210pub(super) fn bool_tag_value(tag: &NbtTag) -> Option<bool> {
211 match tag {
212 NbtTag::Byte(value) => Some(*value != 0),
213 NbtTag::Short(value) => Some(*value != 0),
214 NbtTag::Int(value) => Some(*value != 0),
215 NbtTag::Long(value) => Some(*value != 0),
216 NbtTag::Float(value) => Some(*value != 0.0),
217 NbtTag::Double(value) => Some(*value != 0.0),
218 _ => None,
219 }
220}
221
222pub(super) const fn can_start_number(ch: char) -> bool {
223 matches!(ch, '+' | '-' | '.' | '0'..='9')
224}
225
226pub(super) struct NumberScanError {
227 pub(super) cursor: usize,
228 pub(super) kind: SnbtErrorKind,
229}
230
231impl NumberScanError {
232 const fn new(cursor: usize, kind: SnbtErrorKind) -> Self {
233 Self { cursor, kind }
234 }
235}
236
237pub(super) fn scan_number_token(input: &str, allow_float: bool) -> Result<usize, NumberScanError> {
238 let bytes = input.as_bytes();
239 let has_sign = matches!(bytes.first(), Some(b'+' | b'-'));
240 let mut cursor = usize::from(has_sign);
241 let Some(&first) = bytes.get(cursor) else {
242 return Err(NumberScanError::new(
243 cursor,
244 SnbtErrorKind::ExpectedDecimalNumeral,
245 ));
246 };
247
248 if first == b'.' {
249 if !allow_float {
250 return Err(NumberScanError::new(0, SnbtErrorKind::ExpectedNumber));
251 }
252
253 cursor += 1;
254 cursor = scan_required_numeral(
255 bytes,
256 cursor,
257 |byte| byte.is_ascii_digit(),
258 SnbtErrorKind::ExpectedDecimalNumeral,
259 )?;
260 cursor = scan_optional_exponent(bytes, cursor);
261 return Ok(cursor + float_suffix_len(&bytes[cursor..]));
262 }
263
264 if !first.is_ascii_digit() {
265 return Err(NumberScanError::new(
266 cursor,
267 if has_sign {
268 SnbtErrorKind::ExpectedDecimalNumeral
269 } else {
270 SnbtErrorKind::ExpectedNumber
271 },
272 ));
273 }
274
275 if first == b'0' {
276 if matches!(bytes.get(cursor + 1), Some(b'x' | b'X')) {
277 cursor += 2;
278 cursor = scan_required_numeral(
279 bytes,
280 cursor,
281 |byte| byte.is_ascii_hexdigit(),
282 SnbtErrorKind::ExpectedHexNumeral,
283 )?;
284 return Ok(cursor + integer_suffix_len(&bytes[cursor..]));
285 }
286 if matches!(bytes.get(cursor + 1), Some(b'b' | b'B'))
287 && matches!(bytes.get(cursor + 2), Some(b'0' | b'1' | b'_'))
288 {
289 cursor += 2;
290 cursor = scan_required_numeral(
291 bytes,
292 cursor,
293 |byte| matches!(byte, b'0' | b'1'),
294 SnbtErrorKind::ExpectedBinaryNumeral,
295 )?;
296 return Ok(cursor + integer_suffix_len(&bytes[cursor..]));
297 }
298 }
299
300 let numeral_start = cursor;
301 cursor = scan_required_numeral(
302 bytes,
303 cursor,
304 |byte| byte.is_ascii_digit(),
305 SnbtErrorKind::ExpectedDecimalNumeral,
306 )?;
307
308 if allow_float {
309 match bytes.get(cursor) {
310 Some(b'.') => {
311 cursor += 1;
312 cursor =
313 try_scan_numeral(bytes, cursor, |byte| byte.is_ascii_digit()).unwrap_or(cursor);
314 cursor = scan_optional_exponent(bytes, cursor);
315 return Ok(cursor + float_suffix_len(&bytes[cursor..]));
316 }
317 Some(b'e' | b'E') => {
318 if let Some(exponent_end) = try_scan_exponent(bytes, cursor) {
319 cursor = exponent_end;
320 return Ok(cursor + float_suffix_len(&bytes[cursor..]));
321 }
322 }
323 Some(b'f' | b'F' | b'd' | b'D') => return Ok(cursor + 1),
324 _ => {}
325 }
326 }
327
328 let digit_count = bytes[numeral_start..cursor]
329 .iter()
330 .filter(|byte| **byte != b'_')
331 .count();
332 if first == b'0' && digit_count > 1 {
333 return Err(NumberScanError::new(cursor, SnbtErrorKind::LeadingZero));
334 }
335
336 Ok(cursor + integer_suffix_len(&bytes[cursor..]))
337}
338
339fn scan_required_numeral(
340 bytes: &[u8],
341 start: usize,
342 accepts_digit: impl Fn(u8) -> bool,
343 expected: SnbtErrorKind,
344) -> Result<usize, NumberScanError> {
345 let mut cursor = start;
346 while bytes
347 .get(cursor)
348 .is_some_and(|byte| accepts_digit(*byte) || *byte == b'_')
349 {
350 cursor += 1;
351 }
352
353 if cursor == start {
354 return Err(NumberScanError::new(start, expected));
355 }
356 if bytes[start] == b'_' || bytes[cursor - 1] == b'_' {
357 return Err(NumberScanError::new(
358 start,
359 SnbtErrorKind::InvalidUnderscore,
360 ));
361 }
362
363 Ok(cursor)
364}
365
366fn try_scan_numeral(
367 bytes: &[u8],
368 start: usize,
369 accepts_digit: impl Fn(u8) -> bool,
370) -> Option<usize> {
371 scan_required_numeral(
372 bytes,
373 start,
374 accepts_digit,
375 SnbtErrorKind::ExpectedDecimalNumeral,
376 )
377 .ok()
378}
379
380fn scan_optional_exponent(bytes: &[u8], cursor: usize) -> usize {
381 try_scan_exponent(bytes, cursor).unwrap_or(cursor)
382}
383
384fn try_scan_exponent(bytes: &[u8], cursor: usize) -> Option<usize> {
385 if !matches!(bytes.get(cursor), Some(b'e' | b'E')) {
386 return None;
387 }
388
389 let numeral_start =
390 cursor + 1 + usize::from(matches!(bytes.get(cursor + 1), Some(b'+' | b'-')));
391 try_scan_numeral(bytes, numeral_start, |byte| byte.is_ascii_digit())
392}
393
394const fn float_suffix_len(bytes: &[u8]) -> usize {
395 matches!(bytes.first(), Some(b'f' | b'F' | b'd' | b'D')) as usize
396}
397
398fn integer_suffix_len(bytes: &[u8]) -> usize {
399 if matches!(bytes.first(), Some(b'u' | b'U' | b's' | b'S'))
400 && matches!(
401 bytes.get(1),
402 Some(b'b' | b'B' | b's' | b'S' | b'i' | b'I' | b'l' | b'L')
403 )
404 {
405 return 2;
406 }
407 usize::from(matches!(
408 bytes.first(),
409 Some(b'b' | b'B' | b's' | b'S' | b'i' | b'I' | b'l' | b'L')
410 ))
411}
412
413pub(super) fn is_unsuffixed_decimal_integer_token(token: &str) -> bool {
414 let digits = token
415 .strip_prefix(['+', '-'])
416 .map_or(token, |stripped| stripped);
417 !digits.is_empty()
418 && digits
419 .bytes()
420 .all(|byte| byte.is_ascii_digit() || byte == b'_')
421}
422
423pub(super) const fn is_allowed_in_unquoted_string(ch: char) -> bool {
424 matches!(ch, '0'..='9' | 'A'..='Z' | 'a'..='z' | '_' | '-' | '.' | '+')
425}
426
427pub(super) const fn is_allowed_in_unicode_name(ch: char) -> bool {
428 ch.is_ascii_alphanumeric() || matches!(ch, '-' | ' ')
429}
430
431#[derive(Clone, Copy, Debug, PartialEq, Eq)]
432pub(super) enum DefaultIntegerKind {
433 Byte,
434 Int,
435 Long,
436}
437
438impl From<DefaultIntegerKind> for IntegerKind {
439 fn from(value: DefaultIntegerKind) -> Self {
440 match value {
441 DefaultIntegerKind::Byte => Self::Byte,
442 DefaultIntegerKind::Int => Self::Int,
443 DefaultIntegerKind::Long => Self::Long,
444 }
445 }
446}
447
448#[derive(Clone, Copy, Debug, PartialEq, Eq)]
449pub(super) enum IntegerKind {
450 Byte,
451 Short,
452 Int,
453 Long,
454}
455
456impl IntegerKind {
457 fn to_signed_tag(self, value: i128) -> Result<NbtTag, SnbtErrorKind> {
458 match self {
459 Self::Byte => {
460 let value = i8::try_from(value).map_err(|_| SnbtErrorKind::NumberOutOfRange {
461 number_type: self.into(),
462 unsigned: false,
463 })?;
464 Ok(NbtTag::Byte(value))
465 }
466 Self::Short => {
467 let value = i16::try_from(value).map_err(|_| SnbtErrorKind::NumberOutOfRange {
468 number_type: self.into(),
469 unsigned: false,
470 })?;
471 Ok(NbtTag::Short(value))
472 }
473 Self::Int => {
474 let value = i32::try_from(value).map_err(|_| SnbtErrorKind::NumberOutOfRange {
475 number_type: self.into(),
476 unsigned: false,
477 })?;
478 Ok(NbtTag::Int(value))
479 }
480 Self::Long => {
481 let value = i64::try_from(value).map_err(|_| SnbtErrorKind::NumberOutOfRange {
482 number_type: self.into(),
483 unsigned: false,
484 })?;
485 Ok(NbtTag::Long(value))
486 }
487 }
488 }
489
490 fn to_unsigned_tag(self, value: u128) -> Result<NbtTag, SnbtErrorKind> {
491 match self {
492 Self::Byte => {
493 if value > u128::from(u8::MAX) {
494 return Err(SnbtErrorKind::NumberOutOfRange {
495 number_type: self.into(),
496 unsigned: true,
497 });
498 }
499 Ok(NbtTag::Byte(value as u8 as i8))
500 }
501 Self::Short => {
502 if value > u128::from(u16::MAX) {
503 return Err(SnbtErrorKind::NumberOutOfRange {
504 number_type: self.into(),
505 unsigned: true,
506 });
507 }
508 Ok(NbtTag::Short(value as u16 as i16))
509 }
510 Self::Int => {
511 if value > u128::from(u32::MAX) {
512 return Err(SnbtErrorKind::NumberOutOfRange {
513 number_type: self.into(),
514 unsigned: true,
515 });
516 }
517 Ok(NbtTag::Int(value as u32 as i32))
518 }
519 Self::Long => {
520 if value > u128::from(u64::MAX) {
521 return Err(SnbtErrorKind::NumberOutOfRange {
522 number_type: self.into(),
523 unsigned: true,
524 });
525 }
526 Ok(NbtTag::Long(value as u64 as i64))
527 }
528 }
529 }
530}
531
532impl From<IntegerKind> for SnbtNumberType {
533 fn from(value: IntegerKind) -> Self {
534 match value {
535 IntegerKind::Byte => Self::Byte,
536 IntegerKind::Short => Self::Short,
537 IntegerKind::Int => Self::Int,
538 IntegerKind::Long => Self::Long,
539 }
540 }
541}
542
543#[derive(Clone, Copy, Debug, PartialEq, Eq)]
544enum IntegerSignedness {
545 Default,
546 Signed,
547 Unsigned,
548}
549
550#[derive(Clone, Copy, Debug, PartialEq, Eq)]
551enum FloatKind {
552 Float,
553 Double,
554}