854 lines
32 KiB
Rust
854 lines
32 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use crate::errors::error_types::{CommandErrorType, LITERAL_INCORRECT};
|
|
use crate::parser::Parser;
|
|
use crate::snbt::markers::{
|
|
ArrayPrefix, Base, IntegerLiteral, IntegerSuffix, Sign, Signed, SignedPrefix, TypeSuffix,
|
|
};
|
|
use crate::snbt::operations::SnbtOperations;
|
|
use crate::snbt::{NUMBER_PARSE_FAILURE, SnbtParser};
|
|
use pumpkin_codecs::{DynamicOps, Number};
|
|
use pumpkin_data::translation;
|
|
use pumpkin_nbt::compound::NbtCompound;
|
|
use pumpkin_nbt::nbt_ops::NbtOps;
|
|
use pumpkin_nbt::tag::NbtTag;
|
|
|
|
pub const INVALID_CODEPOINT: CommandErrorType<1> = CommandErrorType::new(
|
|
translation::java::SNBT_PARSER_INVALID_CODEPOINT,
|
|
translation::java::SNBT_PARSER_INVALID_CODEPOINT,
|
|
);
|
|
|
|
pub const NO_SUCH_OPERATION: CommandErrorType<1> = CommandErrorType::new(
|
|
translation::java::SNBT_PARSER_NO_SUCH_OPERATION,
|
|
translation::java::SNBT_PARSER_NO_SUCH_OPERATION,
|
|
);
|
|
|
|
pub const EXPECTED_FLOAT_TYPE: CommandErrorType<0> = CommandErrorType::new(
|
|
translation::java::SNBT_PARSER_EXPECTED_FLOAT_TYPE,
|
|
translation::java::SNBT_PARSER_EXPECTED_FLOAT_TYPE,
|
|
);
|
|
|
|
pub const INVALID_CHARACTER_NAME: CommandErrorType<0> = CommandErrorType::new(
|
|
translation::java::SNBT_PARSER_INVALID_CHARACTER_NAME,
|
|
translation::java::SNBT_PARSER_INVALID_CHARACTER_NAME,
|
|
);
|
|
|
|
pub const INVALID_UNQUOTED_START: CommandErrorType<0> = CommandErrorType::new(
|
|
translation::java::SNBT_PARSER_INVALID_UNQUOTED_START,
|
|
translation::java::SNBT_PARSER_INVALID_UNQUOTED_START,
|
|
);
|
|
|
|
pub const EXPECTED_UNQUOTED_STRING: CommandErrorType<0> = CommandErrorType::new(
|
|
translation::java::SNBT_PARSER_EXPECTED_UNQUOTED_STRING,
|
|
translation::java::SNBT_PARSER_EXPECTED_UNQUOTED_STRING,
|
|
);
|
|
|
|
pub const INVALID_STRING_CONTENTS: CommandErrorType<0> = CommandErrorType::new(
|
|
translation::java::SNBT_PARSER_INVALID_STRING_CONTENTS,
|
|
translation::java::SNBT_PARSER_INVALID_STRING_CONTENTS,
|
|
);
|
|
|
|
pub const EXPECTED_BINARY_NUMERAL: CommandErrorType<0> = CommandErrorType::new(
|
|
translation::java::SNBT_PARSER_EXPECTED_BINARY_NUMERAL,
|
|
translation::java::SNBT_PARSER_EXPECTED_BINARY_NUMERAL,
|
|
);
|
|
|
|
pub const EXPECTED_DECIMAL_NUMERAL: CommandErrorType<0> = CommandErrorType::new(
|
|
translation::java::SNBT_PARSER_EXPECTED_DECIMAL_NUMERAL,
|
|
translation::java::SNBT_PARSER_EXPECTED_DECIMAL_NUMERAL,
|
|
);
|
|
|
|
pub const EXPECTED_HEX_NUMERAL: CommandErrorType<0> = CommandErrorType::new(
|
|
translation::java::SNBT_PARSER_EXPECTED_HEX_NUMERAL,
|
|
translation::java::SNBT_PARSER_EXPECTED_HEX_NUMERAL,
|
|
);
|
|
|
|
pub const EMPTY_KEY: CommandErrorType<0> = CommandErrorType::new(
|
|
translation::java::SNBT_PARSER_EMPTY_KEY,
|
|
translation::java::SNBT_PARSER_EMPTY_KEY,
|
|
);
|
|
|
|
pub const LEADING_ZERO_NOT_ALLOWED: CommandErrorType<0> = CommandErrorType::new(
|
|
translation::java::SNBT_PARSER_LEADING_ZERO_NOT_ALLOWED,
|
|
translation::java::SNBT_PARSER_LEADING_ZERO_NOT_ALLOWED,
|
|
);
|
|
|
|
pub const INFINITY_NOT_ALLOWED: CommandErrorType<0> = CommandErrorType::new(
|
|
translation::java::SNBT_PARSER_INFINITY_NOT_ALLOWED,
|
|
translation::java::SNBT_PARSER_INFINITY_NOT_ALLOWED,
|
|
);
|
|
|
|
/// Parses a string literal.
|
|
macro_rules! parse_string_literal {
|
|
($parser:expr, $quote:literal) => {{
|
|
let mut buffer = String::new();
|
|
let mut high_surrogate_queue: u32 = 0;
|
|
|
|
loop {
|
|
match $parser.reader.read() {
|
|
Some($quote) => break Some(buffer),
|
|
Some('\\') => {
|
|
let i = $parser.escape_sequence()?;
|
|
if let Some(c) = char::from_u32(i) {
|
|
buffer.push(c);
|
|
} else if high_surrogate_queue == 0 && matches!(i, 0xD800..=0xDBFF) {
|
|
// High surrogate incoming.
|
|
high_surrogate_queue = i;
|
|
} else if high_surrogate_queue != 0 && matches!(i, 0xDC00..=0xDFFF) {
|
|
// Low surrogate incoming.
|
|
let high_bits = high_surrogate_queue - 0xD800;
|
|
let low_bits = i - 0xDC00;
|
|
let bits = high_bits << 10 | low_bits;
|
|
let i = bits + 0x10000;
|
|
// This really shouldn't fail though.
|
|
if let Some(c) = char::from_u32(i) {
|
|
buffer.push(c);
|
|
} else {
|
|
buffer.push('\u{FFFD}');
|
|
}
|
|
high_surrogate_queue = 0;
|
|
} else {
|
|
// Add replacement character.
|
|
buffer.push('\u{FFFD}');
|
|
if high_surrogate_queue != 0 {
|
|
buffer.push('\u{FFFD}');
|
|
}
|
|
high_surrogate_queue = 0;
|
|
}
|
|
}
|
|
Some(ch) => {
|
|
if high_surrogate_queue != 0 {
|
|
// Add replacement character.
|
|
buffer.push('\u{FFFD}');
|
|
high_surrogate_queue = 0;
|
|
}
|
|
buffer.push(ch);
|
|
}
|
|
None => {
|
|
// reached EOL
|
|
$parser.store_simple_error_and_suggest(
|
|
&INVALID_STRING_CONTENTS,
|
|
&["'", "\"", "\\"],
|
|
);
|
|
break None;
|
|
}
|
|
}
|
|
}
|
|
}};
|
|
}
|
|
|
|
impl SnbtParser<'_, '_> {
|
|
fn sign(&mut self) -> Option<Sign> {
|
|
self.parse_or_revert(|parser| {
|
|
parser.reader.skip_whitespace();
|
|
match parser.reader.peek() {
|
|
Some('+') => {
|
|
parser.reader.skip();
|
|
Some(Sign::Plus)
|
|
}
|
|
Some('-') => {
|
|
parser.reader.skip();
|
|
Some(Sign::Minus)
|
|
}
|
|
_ => {
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, "+", &["+", "-"]);
|
|
None
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
fn integer_suffix(&mut self) -> Option<IntegerSuffix> {
|
|
self.parse_or_revert(|parser| {
|
|
parser.reader.skip_whitespace();
|
|
match parser.reader.peek() {
|
|
Some('u' | 'U') => {
|
|
parser.reader.skip();
|
|
Some(IntegerSuffix(
|
|
SignedPrefix::Unsigned,
|
|
parser.integer_type_suffix()?,
|
|
))
|
|
}
|
|
Some('s' | 'S') => {
|
|
// Can mean signed or short.
|
|
Some(
|
|
if let Some(suffix) = parser.parse_or_revert(|parser| {
|
|
parser.reader.skip();
|
|
parser.integer_type_suffix()
|
|
}) {
|
|
IntegerSuffix(SignedPrefix::Signed, suffix)
|
|
} else {
|
|
IntegerSuffix(SignedPrefix::None, parser.integer_type_suffix()?)
|
|
},
|
|
)
|
|
}
|
|
Some('b' | 'B' | 'i' | 'I' | 'l' | 'L') => Some(IntegerSuffix(
|
|
SignedPrefix::None,
|
|
parser.integer_type_suffix()?,
|
|
)),
|
|
_ => {
|
|
parser.store_dynamic_error_and_suggest(
|
|
&LITERAL_INCORRECT,
|
|
"u|U",
|
|
&["u", "U", "s", "S", "b", "B", "s", "S", "i", "I", "l", "L"],
|
|
);
|
|
None
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
fn binary_numeral(&mut self) -> Option<String> {
|
|
self.parse_numeral(Base::Binary)
|
|
}
|
|
|
|
fn decimal_numeral(&mut self) -> Option<String> {
|
|
self.parse_numeral(Base::Decimal)
|
|
}
|
|
|
|
fn hexadecimal_numeral(&mut self) -> Option<String> {
|
|
self.parse_numeral(Base::Hexadecimal)
|
|
}
|
|
|
|
/// Parses an integer literal.
|
|
fn integer_literal(&mut self) -> Option<IntegerLiteral> {
|
|
let mut result = self.parse_or_revert(|parser| {
|
|
let sign = parser.parse_or_revert(Self::sign).unwrap_or(Sign::Plus);
|
|
parser.reader.skip_whitespace();
|
|
|
|
// We need to be careful to make sure that
|
|
// `0b` parses as a byte literal and NOT a prefix.
|
|
let after_sign_cursor = parser.reader.cursor();
|
|
if parser.reader.peek() == Some('0') {
|
|
parser.reader.skip();
|
|
parser.reader.skip_whitespace();
|
|
match parser.reader.peek() {
|
|
Some('x' | 'X') => {
|
|
parser.reader.skip();
|
|
return parser.hexadecimal_numeral().map(|number| IntegerLiteral {
|
|
sign,
|
|
base: Base::Hexadecimal,
|
|
suffix: IntegerSuffix::EMPTY,
|
|
digits: number,
|
|
});
|
|
}
|
|
Some('b' | 'B') => {
|
|
parser.reader.skip();
|
|
if let Some(number) = parser.binary_numeral() {
|
|
return Some(IntegerLiteral {
|
|
sign,
|
|
base: Base::Binary,
|
|
suffix: IntegerSuffix::EMPTY,
|
|
digits: number,
|
|
});
|
|
}
|
|
parser.reader.set_cursor(after_sign_cursor);
|
|
}
|
|
_ => {
|
|
return if parser.decimal_numeral().is_none() {
|
|
Some(IntegerLiteral {
|
|
sign,
|
|
base: Base::Decimal,
|
|
suffix: IntegerSuffix::EMPTY,
|
|
digits: "0".to_string(),
|
|
})
|
|
} else {
|
|
parser.store_simple_error(&LEADING_ZERO_NOT_ALLOWED);
|
|
None
|
|
};
|
|
}
|
|
}
|
|
}
|
|
if let Some(number) = parser.decimal_numeral() {
|
|
return Some(IntegerLiteral {
|
|
sign,
|
|
base: Base::Decimal,
|
|
suffix: IntegerSuffix::EMPTY,
|
|
digits: number,
|
|
});
|
|
}
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, "0", &["0"]);
|
|
None
|
|
})?;
|
|
|
|
result.suffix = self
|
|
.parse_or_revert(Self::integer_suffix)
|
|
.unwrap_or(IntegerSuffix::EMPTY);
|
|
|
|
Some(result)
|
|
}
|
|
|
|
fn float_type_suffix(&mut self) -> Option<TypeSuffix> {
|
|
self.parse_or_revert(|parser| {
|
|
parser.reader.skip_whitespace();
|
|
match parser.reader.peek() {
|
|
Some('f' | 'F') => {
|
|
parser.reader.skip();
|
|
Some(TypeSuffix::Float)
|
|
}
|
|
Some('d' | 'D') => {
|
|
parser.reader.skip();
|
|
Some(TypeSuffix::Double)
|
|
}
|
|
_ => {
|
|
parser.store_dynamic_error_and_suggest(
|
|
&LITERAL_INCORRECT,
|
|
"f|F",
|
|
&["f", "F", "d", "D"],
|
|
);
|
|
None
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
fn float_exponent_part(&mut self) -> Option<Signed<String>> {
|
|
self.parse_or_revert(|parser| {
|
|
parser.reader.skip_whitespace();
|
|
if matches!(parser.reader.peek(), Some('e' | 'E')) {
|
|
parser.reader.skip();
|
|
let sign = parser.parse_or_revert(Self::sign).unwrap_or(Sign::Plus);
|
|
let value = parser.decimal_numeral()?;
|
|
|
|
Some(Signed { sign, value })
|
|
} else {
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, "e|E", &["e", "E"]);
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
fn float_literal(&mut self) -> Option<NbtTag> {
|
|
struct FloatingPointIntermediate {
|
|
whole_part: String,
|
|
fraction_part: Option<String>,
|
|
exponent_part: Option<Signed<String>>,
|
|
type_suffix: Option<TypeSuffix>,
|
|
}
|
|
|
|
// Paths:
|
|
// A --- XXX.[yyy][eZZZ][suffix]
|
|
// B --- .yyy[eZZZ][suffix]
|
|
// C --- XXXeZZZ[suffix]
|
|
// D --- XXX[eZZZ]suffix
|
|
//
|
|
// where [a] means 'optionally parse a',
|
|
// XXX is the whole part, yyy is the decimal part,
|
|
// eZZZ is the float exponent path, and
|
|
// suffix is float type suffix.
|
|
//
|
|
// Ruleset:
|
|
// If we encounter a digit, we must parse a decimal number. Then:
|
|
// If we encounter a decimal point, we must choose path A.
|
|
// Try to parse [eZZZ] AND [suffix]:
|
|
// if [eZZZ] parses, then irrespective of [suffix], choose path D.
|
|
// if ONLY [suffix] parses, choose path C.
|
|
// if none parse, FAIL.
|
|
// If we encounter a decimal point, we must choose path B.
|
|
// FAIL if nether a period or a digit
|
|
|
|
let sign = self.parse_or_revert(Self::sign).unwrap_or(Sign::Plus);
|
|
|
|
let intermediate = self.parse_or_revert(|parser| {
|
|
parser.reader.skip_whitespace();
|
|
if let Some(whole_part) = parser.parse_or_revert(Self::decimal_numeral) {
|
|
// Must be pathway A, C, or D.
|
|
parser.reader.skip_whitespace();
|
|
if parser.reader.peek() == Some('.') {
|
|
// We choose pathway A.
|
|
parser.reader.skip();
|
|
|
|
let fraction_part = parser.decimal_numeral();
|
|
let exponent_part = parser.float_exponent_part();
|
|
let type_suffix = parser.float_type_suffix();
|
|
|
|
Some(FloatingPointIntermediate {
|
|
whole_part,
|
|
fraction_part,
|
|
exponent_part,
|
|
type_suffix,
|
|
})
|
|
} else {
|
|
// This error won't actually matter if the following part
|
|
// parses successfully.
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, ".", &["."]);
|
|
|
|
// Must be pathway C or D.
|
|
let exponent_part = parser.float_exponent_part();
|
|
let type_suffix = parser.float_type_suffix();
|
|
|
|
(exponent_part.is_some() || type_suffix.is_some()).then_some(
|
|
FloatingPointIntermediate {
|
|
whole_part,
|
|
fraction_part: None,
|
|
exponent_part,
|
|
type_suffix,
|
|
},
|
|
)
|
|
}
|
|
} else {
|
|
// We must parse a decimal point.
|
|
parser.reader.skip_whitespace();
|
|
if parser.reader.peek() == Some('.') {
|
|
parser.reader.skip();
|
|
// We choose pathway B.
|
|
let fraction_part = parser.decimal_numeral()?;
|
|
let exponent_part = parser.float_exponent_part();
|
|
let type_suffix = parser.float_type_suffix();
|
|
|
|
Some(FloatingPointIntermediate {
|
|
whole_part: String::new(),
|
|
fraction_part: Some(fraction_part),
|
|
exponent_part,
|
|
type_suffix,
|
|
})
|
|
} else {
|
|
// We cannot choose a pathway.
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, ".", &["."]);
|
|
None
|
|
}
|
|
}
|
|
})?;
|
|
|
|
// Parsing the float:
|
|
let mut buffer = String::with_capacity(
|
|
sign.minimum_size_parsable()
|
|
+ intermediate.whole_part.len()
|
|
+ intermediate
|
|
.fraction_part
|
|
.as_ref()
|
|
.map_or(0, |s| 1 + s.len())
|
|
+ intermediate
|
|
.exponent_part
|
|
.as_ref()
|
|
.map_or(0, |s| 1 + s.sign.minimum_size_parsable() + s.value.len()),
|
|
);
|
|
|
|
sign.append_minimum_str_parsable(&mut buffer);
|
|
Self::clean_and_append(&mut buffer, &intermediate.whole_part);
|
|
if let Some(fraction) = &intermediate.fraction_part {
|
|
buffer.push('.');
|
|
Self::clean_and_append(&mut buffer, fraction);
|
|
}
|
|
if let Some(exponent) = &intermediate.exponent_part {
|
|
buffer.push('e');
|
|
exponent.sign.append_minimum_str_parsable(&mut buffer);
|
|
Self::clean_and_append(&mut buffer, &exponent.value);
|
|
}
|
|
|
|
match intermediate.type_suffix {
|
|
None | Some(TypeSuffix::Double) => match buffer.parse::<f64>() {
|
|
Err(_) => self.store_dynamic_error(&NUMBER_PARSE_FAILURE, "Invalid float literal"),
|
|
Ok(value) if value.is_finite() => {
|
|
return Some(NbtTag::Double(value));
|
|
}
|
|
Ok(_) => self.store_simple_error(&INFINITY_NOT_ALLOWED),
|
|
},
|
|
Some(TypeSuffix::Float) => match buffer.parse::<f32>() {
|
|
Err(_) => {
|
|
self.store_dynamic_error(&NUMBER_PARSE_FAILURE, "Invalid float literal");
|
|
}
|
|
Ok(value) if value.is_finite() => {
|
|
return Some(NbtTag::Float(value));
|
|
}
|
|
Ok(_) => self.store_simple_error(&INFINITY_NOT_ALLOWED),
|
|
},
|
|
_ => self.store_simple_error(&EXPECTED_FLOAT_TYPE),
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn string_hex_2(&mut self) -> Option<String> {
|
|
self.hex_literal(2)
|
|
}
|
|
|
|
fn string_hex_4(&mut self) -> Option<String> {
|
|
self.hex_literal(4)
|
|
}
|
|
|
|
fn string_hex_8(&mut self) -> Option<String> {
|
|
self.hex_literal(8)
|
|
}
|
|
|
|
/// Parses a unicode name pattern.
|
|
fn string_unicode_name(&mut self) -> Option<String> {
|
|
self.parse_or_revert(|parser| {
|
|
let start = parser.reader.cursor();
|
|
let mut end = start;
|
|
|
|
// Since the only characters allowed are all ASCII, it should
|
|
// be fine to go byte by byte.
|
|
let bytes = parser.reader.string().as_bytes();
|
|
|
|
while end < bytes.len() {
|
|
let b = bytes[end];
|
|
if matches!(b, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b' ' | b'-') {
|
|
end += 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if start == end {
|
|
parser.store_simple_error(&INVALID_CHARACTER_NAME);
|
|
None
|
|
} else {
|
|
parser.reader.set_cursor(end);
|
|
Some(parser.reader.string()[start..end].to_string())
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Parses an escape sequence (without the \)
|
|
/// The returned character will be expressed as a `u32`
|
|
/// due to Rust's strictness on `char` of surrogate codepoints.
|
|
fn escape_sequence(&mut self) -> Option<u32> {
|
|
enum EscapeSequenceBranch {
|
|
Return(char),
|
|
CheckValidity(u32),
|
|
UnicodeName(String),
|
|
}
|
|
|
|
let cursor_at_escaping_char = self.reader.cursor();
|
|
let branch = match self.reader.read() {
|
|
Some('b') => Some(EscapeSequenceBranch::Return('\x08')),
|
|
Some('s') => Some(EscapeSequenceBranch::Return(' ')),
|
|
Some('t') => Some(EscapeSequenceBranch::Return('\t')),
|
|
Some('n') => Some(EscapeSequenceBranch::Return('\n')),
|
|
Some('f') => Some(EscapeSequenceBranch::Return('\x0C')),
|
|
Some('r') => Some(EscapeSequenceBranch::Return('\r')),
|
|
Some('\\') => Some(EscapeSequenceBranch::Return('\\')),
|
|
Some('\'') => Some(EscapeSequenceBranch::Return('\'')),
|
|
Some('"') => Some(EscapeSequenceBranch::Return('"')),
|
|
Some('x') => Some(EscapeSequenceBranch::CheckValidity(
|
|
u32::from_str_radix(&self.string_hex_2()?, 16).ok()?,
|
|
)),
|
|
Some('u') => Some(EscapeSequenceBranch::CheckValidity(
|
|
u32::from_str_radix(&self.string_hex_4()?, 16).ok()?,
|
|
)),
|
|
Some('U') => Some(EscapeSequenceBranch::CheckValidity(
|
|
u32::from_str_radix(&self.string_hex_8()?, 16).ok()?,
|
|
)),
|
|
Some('N') => {
|
|
if self.reader.peek() != Some('{') {
|
|
self.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, "{", &["}"]);
|
|
return None;
|
|
}
|
|
self.reader.skip();
|
|
let string_unicode_name = self.string_unicode_name()?;
|
|
if self.reader.peek() != Some('}') {
|
|
self.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, "}", &["}"]);
|
|
return None;
|
|
}
|
|
self.reader.skip();
|
|
Some(EscapeSequenceBranch::UnicodeName(string_unicode_name))
|
|
}
|
|
_ => {
|
|
self.reader.set_cursor(cursor_at_escaping_char);
|
|
self.store_dynamic_error_and_suggest(
|
|
&LITERAL_INCORRECT,
|
|
"b",
|
|
&[
|
|
"b", "s", "t", "n", "f", "r", "\\", "'", "\"", "x", "u", "U", "N",
|
|
],
|
|
);
|
|
None
|
|
}
|
|
}?;
|
|
|
|
match branch {
|
|
EscapeSequenceBranch::Return(ch) => Some(ch as u32),
|
|
EscapeSequenceBranch::CheckValidity(value) => {
|
|
// Value must be <= 0x10FFFF to be a valid codepoint.
|
|
// (Surrogates are handled outside this function)
|
|
if value <= 0x10FFFF {
|
|
Some(value)
|
|
} else {
|
|
self.store_dynamic_error(&INVALID_CODEPOINT, format!("U+{value:08X}"));
|
|
None
|
|
}
|
|
}
|
|
EscapeSequenceBranch::UnicodeName(_name) => None,
|
|
}
|
|
}
|
|
|
|
fn quoted_string_literal(&mut self) -> Option<String> {
|
|
self.parse_or_revert(|parser| {
|
|
parser.reader.skip_whitespace();
|
|
match parser.reader.peek() {
|
|
Some('\'') => {
|
|
parser.reader.skip();
|
|
parse_string_literal!(parser, '\'')
|
|
}
|
|
Some('"') => {
|
|
parser.reader.skip();
|
|
parse_string_literal!(parser, '"')
|
|
}
|
|
_ => {
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, "\"", &["'", "\""]);
|
|
None
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
fn unquoted_string_literal(&mut self) -> Option<String> {
|
|
self.parse_or_revert(|parser| {
|
|
parser.reader.skip_whitespace();
|
|
let value = parser.reader.read_unquoted_string();
|
|
if value.is_empty() {
|
|
parser.store_simple_error(&EXPECTED_UNQUOTED_STRING);
|
|
None
|
|
} else {
|
|
Some(value)
|
|
}
|
|
})
|
|
}
|
|
|
|
fn arguments(&mut self) -> Vec<NbtTag> {
|
|
self.repeated_with_trailing_comma_vec(Self::literal)
|
|
}
|
|
|
|
fn unquoted_string_or_built_in(&mut self) -> Option<NbtTag> {
|
|
let literal = self.unquoted_string_literal()?;
|
|
// Trying to match the same behaviour of storing arguments
|
|
// in the scope even if the right bracket failed to parse:
|
|
let mut arguments = None;
|
|
|
|
let _ = self.parse_or_revert(|parser| {
|
|
parser.reader.skip_whitespace();
|
|
if parser.reader.peek() != Some('(') {
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, "(", &["("]);
|
|
return None;
|
|
}
|
|
parser.reader.skip();
|
|
arguments = Some(parser.arguments());
|
|
parser.reader.skip_whitespace();
|
|
if parser.reader.peek() == Some(')') {
|
|
parser.reader.skip();
|
|
Some(())
|
|
} else {
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, ")", &[")"]);
|
|
None
|
|
}
|
|
});
|
|
|
|
// This should be fine as the characters in the predicate are all ASCII.
|
|
if literal.is_empty() || matches!(literal.as_bytes()[0], b'0'..=b'9' | b'+' | b'-' | b'.') {
|
|
self.store_simple_error_and_suggest(
|
|
&INVALID_UNQUOTED_START,
|
|
SnbtOperations::BUILTIN_IDS,
|
|
);
|
|
return None;
|
|
}
|
|
|
|
if let Some(arguments) = arguments {
|
|
let count = arguments.len();
|
|
if let Some(operation) = SnbtOperations::search(&literal, count) {
|
|
operation(self, &arguments[..])
|
|
} else {
|
|
self.store_dynamic_error(&NO_SUCH_OPERATION, format!("{literal}/{count}"));
|
|
None
|
|
}
|
|
} else if literal.eq_ignore_ascii_case("true") {
|
|
Some(NbtTag::Byte(1))
|
|
} else if literal.eq_ignore_ascii_case("false") {
|
|
Some(NbtTag::Byte(0))
|
|
} else {
|
|
Some(NbtTag::String(literal.into()))
|
|
}
|
|
}
|
|
|
|
fn map_key(&mut self) -> Option<String> {
|
|
self.parse_or_revert(Self::quoted_string_literal)
|
|
.map_or_else(|| self.unquoted_string_literal(), Some)
|
|
}
|
|
|
|
fn map_entry(&mut self) -> Option<(String, NbtTag)> {
|
|
let entry = self.parse_or_revert(|parser| {
|
|
let key = parser.map_key()?;
|
|
parser.reader.skip_whitespace();
|
|
if parser.reader.peek() == Some(':') {
|
|
parser.reader.skip();
|
|
Some((key, parser.literal()?))
|
|
} else {
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, ":", &[":"]);
|
|
None
|
|
}
|
|
})?;
|
|
|
|
if entry.0.is_empty() {
|
|
self.store_simple_error(&EMPTY_KEY);
|
|
None
|
|
} else {
|
|
Some(entry)
|
|
}
|
|
}
|
|
|
|
fn map_entries(&mut self) -> HashMap<String, NbtTag> {
|
|
self.repeated_with_trailing_comma(Self::map_entry, HashMap::new(), |map, element| {
|
|
map.insert(element.0, element.1);
|
|
})
|
|
}
|
|
|
|
fn map_literal(&mut self) -> Option<NbtTag> {
|
|
let entries = self.parse_or_revert(|parser| {
|
|
parser.reader.skip_whitespace();
|
|
if parser.reader.peek() != Some('{') {
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, "{", &["}"]);
|
|
return None;
|
|
}
|
|
parser.reader.skip();
|
|
let entries = parser.map_entries();
|
|
parser.reader.skip_whitespace();
|
|
if parser.reader.peek() != Some('}') {
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, "}", &["}"]);
|
|
return None;
|
|
}
|
|
parser.reader.skip();
|
|
Some(entries)
|
|
})?;
|
|
|
|
Some(NbtTag::Compound(NbtCompound {
|
|
child_tags: entries.into_iter().map(|(k, v)| (k.into(), v)).collect(),
|
|
}))
|
|
}
|
|
|
|
fn list_entries(&mut self) -> Vec<NbtTag> {
|
|
self.repeated_with_trailing_comma_vec(Self::literal)
|
|
}
|
|
|
|
fn array_prefix(&mut self) -> Option<ArrayPrefix> {
|
|
self.parse_or_revert(|parser| {
|
|
parser.reader.skip_whitespace();
|
|
match parser.reader.peek() {
|
|
Some('B') => {
|
|
parser.reader.skip();
|
|
Some(ArrayPrefix::Byte)
|
|
}
|
|
Some('I') => {
|
|
parser.reader.skip();
|
|
Some(ArrayPrefix::Int)
|
|
}
|
|
Some('L') => {
|
|
parser.reader.skip();
|
|
Some(ArrayPrefix::Long)
|
|
}
|
|
_ => {
|
|
parser.store_dynamic_error_and_suggest(
|
|
&LITERAL_INCORRECT,
|
|
"B",
|
|
&["B", "I", "L"],
|
|
);
|
|
None
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
fn int_array_entries(&mut self) -> Vec<IntegerLiteral> {
|
|
self.repeated_with_trailing_comma_vec(Self::integer_literal)
|
|
}
|
|
|
|
fn list_literal(&mut self) -> Option<NbtTag> {
|
|
self.parse_or_revert(|parser| {
|
|
parser.reader.skip_whitespace();
|
|
if parser.reader.peek() != Some('[') {
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, "[", &["["]);
|
|
return None;
|
|
}
|
|
parser.reader.skip();
|
|
|
|
if let Some((prefix, literals)) = parser.parse_or_revert(|parser| {
|
|
let prefix = parser.array_prefix()?;
|
|
parser.reader.skip_whitespace();
|
|
if parser.reader.peek() == Some(';') {
|
|
parser.reader.skip();
|
|
let entries = parser.int_array_entries();
|
|
parser.reader.skip_whitespace();
|
|
if parser.reader.peek() != Some(']') {
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, "]", &["]"]);
|
|
return None;
|
|
}
|
|
parser.reader.skip();
|
|
|
|
Some((prefix, entries))
|
|
} else {
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, ";", &[";"]);
|
|
None
|
|
}
|
|
}) {
|
|
parser.create_prefixed_array(&literals[..], prefix)
|
|
} else {
|
|
let entries = parser.list_entries();
|
|
|
|
parser.reader.skip_whitespace();
|
|
if parser.reader.peek() != Some(']') {
|
|
parser.store_dynamic_error_and_suggest(&LITERAL_INCORRECT, "]", &["]"]);
|
|
return None;
|
|
}
|
|
parser.reader.skip();
|
|
|
|
Some(NbtOps.create_list(entries))
|
|
}
|
|
})
|
|
}
|
|
|
|
fn literal(&mut self) -> Option<NbtTag> {
|
|
enum Literal {
|
|
Tag(NbtTag),
|
|
Integer(IntegerLiteral),
|
|
String(String),
|
|
}
|
|
|
|
self.reader.skip_whitespace();
|
|
|
|
// This has to match the actual rules, so this code is pretty awkward.
|
|
let mut result = None;
|
|
|
|
if matches!(self.reader.peek(), Some('0'..='9' | '+' | '-' | '.')) {
|
|
if let Some(tag) = self.parse_or_revert(Self::float_literal) {
|
|
result = Some(Literal::Tag(tag));
|
|
} else if let Some(literal) = self.parse_or_revert(Self::integer_literal) {
|
|
result = Some(Literal::Integer(literal));
|
|
}
|
|
}
|
|
|
|
let result = if let Some(result) = result {
|
|
result
|
|
} else {
|
|
match self.reader.peek() {
|
|
Some('"' | '\'') => Literal::String(self.quoted_string_literal()?),
|
|
Some('{') => Literal::Tag(self.map_literal()?),
|
|
Some('[') => Literal::Tag(self.list_literal()?),
|
|
_ => Literal::Tag(self.unquoted_string_or_built_in()?),
|
|
}
|
|
};
|
|
|
|
Some(match result {
|
|
Literal::Tag(tag) => tag,
|
|
Literal::Integer(int) => {
|
|
match self.parse_integer_literal(&int, int.suffix.1.or(TypeSuffix::Int))? {
|
|
Number::Byte(byte) => NbtTag::Byte(byte),
|
|
Number::Short(short) => NbtTag::Short(short),
|
|
Number::Int(int) => NbtTag::Int(int),
|
|
Number::Long(long) => NbtTag::Long(long),
|
|
_ => {
|
|
self.store_dynamic_error(
|
|
&INVALID_CODEPOINT,
|
|
"Expected integer".to_string(),
|
|
);
|
|
return None;
|
|
}
|
|
}
|
|
}
|
|
Literal::String(string) => NbtTag::String(string.into()),
|
|
})
|
|
}
|
|
|
|
pub(super) fn parse(&mut self) -> Option<NbtTag> {
|
|
self.literal()
|
|
}
|
|
}
|