1use std::{error::Error, fmt};
2
3use simdnbt::owned::{NbtCompound, NbtList, NbtTag};
4use text_components::TextComponent;
5
6use super::{
7 SnbtErrorKind, compare_nbt, nbt_list_values as list_as_tags, parse_snbt_compound_argument,
8};
9use crate::translations;
10
11#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct NbtPathError {
14 cursor: usize,
15 kind: NbtPathErrorKind,
16}
17
18impl NbtPathError {
19 const fn new(cursor: usize, kind: NbtPathErrorKind) -> Self {
20 Self { cursor, kind }
21 }
22
23 #[must_use]
25 pub const fn cursor(&self) -> usize {
26 self.cursor
27 }
28
29 #[must_use]
31 pub const fn kind(&self) -> &NbtPathErrorKind {
32 &self.kind
33 }
34
35 #[must_use]
37 pub fn component(&self) -> TextComponent {
38 self.kind.component()
39 }
40}
41
42impl fmt::Display for NbtPathError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 write!(
45 f,
46 "NBT path parse error at byte {}: {}",
47 self.cursor, self.kind
48 )
49 }
50}
51
52impl Error for NbtPathError {}
53
54#[non_exhaustive]
56#[derive(Clone, Debug, PartialEq, Eq)]
57pub enum NbtPathErrorKind {
58 TrailingData,
60 ExpectedPath,
62 InvalidNode,
64 ExpectedSymbol(char),
66 ExpectedQuotedString,
68 UnclosedQuotedString,
70 InvalidEscape(char),
72 ExpectedIndex,
74 InvalidIndex(String),
76 InvalidSnbt(SnbtErrorKind),
78}
79
80impl NbtPathErrorKind {
81 fn component(&self) -> TextComponent {
82 match self {
83 Self::TrailingData => TextComponent::from(&translations::ARGUMENT_NBT_TRAILING),
84 Self::ExpectedPath | Self::InvalidNode => {
85 TextComponent::from(&translations::ARGUMENTS_NBTPATH_NODE_INVALID)
86 }
87 Self::ExpectedIndex => TextComponent::from(&translations::PARSING_INT_EXPECTED),
88 Self::InvalidIndex(value) => translations::PARSING_INT_INVALID
89 .message([value.to_owned()])
90 .component(),
91 Self::ExpectedSymbol(symbol) => translations::PARSING_EXPECTED
92 .message([symbol.to_string()])
93 .component(),
94 Self::ExpectedQuotedString => {
95 TextComponent::from(&translations::PARSING_QUOTE_EXPECTED_START)
96 }
97 Self::UnclosedQuotedString => {
98 TextComponent::from(&translations::PARSING_QUOTE_EXPECTED_END)
99 }
100 Self::InvalidEscape(character) => translations::PARSING_QUOTE_ESCAPE
101 .message([character.to_string()])
102 .component(),
103 Self::InvalidSnbt(kind) => kind.component(),
104 }
105 }
106}
107
108impl fmt::Display for NbtPathErrorKind {
109 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
110 match self {
111 Self::TrailingData => formatter.write_str("trailing data"),
112 Self::ExpectedPath => formatter.write_str("expected NBT path"),
113 Self::InvalidNode => formatter.write_str("invalid NBT path node"),
114 Self::ExpectedSymbol(symbol) => write!(formatter, "expected '{symbol}'"),
115 Self::ExpectedQuotedString => formatter.write_str("expected quoted string"),
116 Self::UnclosedQuotedString => formatter.write_str("unclosed quoted string"),
117 Self::InvalidEscape(character) => write!(formatter, "invalid escape '{character}'"),
118 Self::ExpectedIndex => formatter.write_str("expected list index"),
119 Self::InvalidIndex(value) => write!(formatter, "invalid list index '{value}'"),
120 Self::InvalidSnbt(kind) => fmt::Display::fmt(kind, formatter),
121 }
122 }
123}
124
125#[derive(Clone, Debug, PartialEq, Eq)]
127pub enum NbtPathMutationError {
128 NothingFound(String),
130 TooDeep,
132}
133
134impl fmt::Display for NbtPathMutationError {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 match self {
137 Self::NothingFound(path) => write!(f, "nothing found at NBT path '{path}'"),
138 Self::TooDeep => write!(f, "NBT path mutation would exceed maximum depth"),
139 }
140 }
141}
142
143impl Error for NbtPathMutationError {}
144
145#[derive(Clone, Debug, PartialEq)]
147pub struct NbtPath {
148 original: String,
149 nodes: Vec<NbtPathNodeEntry>,
150}
151
152#[derive(Clone, Debug, PartialEq)]
153struct NbtPathNodeEntry {
154 node: NbtPathNode,
155 original_end: usize,
156}
157
158impl NbtPath {
159 #[must_use]
161 pub fn as_str(&self) -> &str {
162 &self.original
163 }
164
165 #[must_use]
167 pub fn get(&self, tag: &NbtTag) -> Vec<NbtTag> {
168 let mut tags = vec![tag.clone()];
169 for entry in &self.nodes {
170 tags = entry.node.get(&tags);
171 if tags.is_empty() {
172 break;
173 }
174 }
175 tags
176 }
177
178 #[must_use]
180 pub fn count_matching(&self, tag: &NbtTag) -> usize {
181 let mut tags = vec![tag.clone()];
182 for entry in &self.nodes {
183 tags = entry.node.get(&tags);
184 if tags.is_empty() {
185 return 0;
186 }
187 }
188 tags.len()
189 }
190
191 pub fn set(&self, tag: &mut NbtTag, value: NbtTag) -> Result<usize, NbtPathMutationError> {
202 if is_too_deep(&value, self.nodes.len()) {
203 return Err(NbtPathMutationError::TooDeep);
204 }
205
206 let Some((last, parents)) = self.nodes.split_last() else {
207 return Ok(0);
208 };
209 if parents.is_empty() {
210 return Ok(last.node.set_tag(tag, &value));
211 }
212
213 self.set_at(tag, 0, &value).map(|outcome| outcome.modified)
214 }
215
216 fn set_at(
217 &self,
218 tag: &mut NbtTag,
219 node_index: usize,
220 value: &NbtTag,
221 ) -> Result<SetOutcome, NbtPathMutationError> {
222 let entry = &self.nodes[node_index];
223 if node_index + 1 == self.nodes.len() {
224 return Ok(SetOutcome {
225 found: true,
226 modified: entry.node.set_tag(tag, value),
227 });
228 }
229
230 let preferred_child = self.nodes[node_index + 1]
231 .node
232 .create_preferred_parent_tag();
233 let mut modified = 0;
234 let mut deeper_found = false;
235 let found = entry
236 .node
237 .get_or_create_tag(tag, &preferred_child, |child| {
238 let outcome = self.set_at(child, node_index + 1, value)?;
239 modified += outcome.modified;
240 deeper_found |= outcome.found;
241 Ok(outcome.found)
242 })?;
243 if !found {
244 return Err(NbtPathMutationError::NothingFound(
245 self.original[..entry.original_end].to_owned(),
246 ));
247 }
248
249 Ok(SetOutcome {
250 found: deeper_found,
251 modified,
252 })
253 }
254}
255
256struct SetOutcome {
257 found: bool,
258 modified: usize,
259}
260
261pub fn parse_nbt_path(input: &str) -> Result<NbtPath, NbtPathError> {
267 let (path, cursor) = parse_nbt_path_argument(input)?;
268 if cursor != input.len() {
269 return Err(NbtPathError::new(cursor, NbtPathErrorKind::TrailingData));
270 }
271 Ok(path)
272}
273
274pub fn parse_nbt_path_argument(input: &str) -> Result<(NbtPath, usize), NbtPathError> {
280 let mut parser = Parser::new(input);
281 let path = parser.parse()?;
282 Ok((path, parser.cursor))
283}
284
285#[derive(Clone, Debug, PartialEq)]
286enum NbtPathNode {
287 CompoundChild(String),
288 MatchObject { name: String, pattern: NbtCompound },
289 MatchRootObject(NbtCompound),
290 AllElements,
291 IndexedElement(i32),
292 MatchElement(NbtCompound),
293}
294
295impl NbtPathNode {
296 fn get(&self, input: &[NbtTag]) -> Vec<NbtTag> {
297 let mut output = Vec::new();
298 for tag in input {
299 match self {
300 Self::CompoundChild(name) => {
301 if let NbtTag::Compound(compound) = tag
302 && let Some(child) = compound.get(name)
303 {
304 output.push(child.clone());
305 }
306 }
307 Self::MatchObject { name, pattern } => {
308 if let NbtTag::Compound(compound) = tag
309 && let Some(child) = compound.get(name)
310 && compound_pattern_matches(pattern, child)
311 {
312 output.push(child.clone());
313 }
314 }
315 Self::MatchRootObject(pattern) => {
316 if compound_pattern_matches(pattern, tag) {
317 output.push(tag.clone());
318 }
319 }
320 Self::AllElements => {
321 output.extend(collection_elements(tag));
322 }
323 Self::IndexedElement(index) => {
324 let elements = collection_elements(tag);
325 if let Some(element) = indexed_element(&elements, *index) {
326 output.push(element);
327 }
328 }
329 Self::MatchElement(pattern) => {
330 output.extend(
331 collection_elements(tag)
332 .into_iter()
333 .filter(|tag| compound_pattern_matches(pattern, tag)),
334 );
335 }
336 }
337 }
338 output
339 }
340
341 fn get_or_create_tag(
342 &self,
343 parent: &mut NbtTag,
344 child: &NbtTag,
345 mut visitor: impl FnMut(&mut NbtTag) -> Result<bool, NbtPathMutationError>,
346 ) -> Result<bool, NbtPathMutationError> {
347 match self {
348 Self::CompoundChild(name) => {
349 let NbtTag::Compound(compound) = parent else {
350 return Ok(false);
351 };
352 ensure_compound_child(compound, name, child.clone());
353 let Some(tag) = compound.get_mut(name) else {
354 return Ok(false);
355 };
356 visitor(tag)
357 }
358 Self::MatchObject { name, pattern } => {
359 let NbtTag::Compound(compound) = parent else {
360 return Ok(false);
361 };
362 if !compound.contains(name) {
363 compound.insert(name.as_str(), NbtTag::Compound(pattern.clone()));
364 }
365 let Some(tag) = compound.get_mut(name) else {
366 return Ok(false);
367 };
368 if compound_pattern_matches(pattern, tag) {
369 visitor(tag)
370 } else {
371 Ok(false)
372 }
373 }
374 Self::MatchRootObject(pattern) => {
375 if compound_pattern_matches(pattern, parent) {
376 visitor(parent)
377 } else {
378 Ok(false)
379 }
380 }
381 Self::AllElements => visit_all_collection_elements(parent, child, visitor),
382 Self::IndexedElement(index) => {
383 visit_indexed_collection_element(parent, *index, visitor)
384 }
385 Self::MatchElement(pattern) => visit_matching_list_elements(parent, pattern, visitor),
386 }
387 }
388
389 fn create_preferred_parent_tag(&self) -> NbtTag {
390 match self {
391 Self::CompoundChild(_) | Self::MatchObject { .. } | Self::MatchRootObject(_) => {
392 NbtTag::Compound(NbtCompound::new())
393 }
394 Self::AllElements | Self::IndexedElement(_) | Self::MatchElement(_) => {
395 NbtTag::List(NbtList::default())
396 }
397 }
398 }
399
400 fn set_tag(&self, parent: &mut NbtTag, value: &NbtTag) -> usize {
401 match self {
402 Self::CompoundChild(name) => set_compound_child(parent, name, value),
403 Self::MatchObject { name, pattern } => {
404 set_matching_compound_child(parent, name, pattern, value)
405 }
406 Self::MatchRootObject(_) => 0,
407 Self::AllElements => set_all_collection_elements(parent, value),
408 Self::IndexedElement(index) => set_indexed_collection_element(parent, *index, value),
409 Self::MatchElement(pattern) => set_matching_list_elements(parent, pattern, value),
410 }
411 }
412}
413
414fn compound_pattern_matches(pattern: &NbtCompound, tag: &NbtTag) -> bool {
415 compare_nbt(Some(&NbtTag::Compound(pattern.clone())), Some(tag), true)
416}
417
418fn ensure_compound_child(compound: &mut NbtCompound, name: &str, child: NbtTag) {
419 if !compound.contains(name) {
420 compound.insert(name, child);
421 }
422}
423
424fn set_compound_child(parent: &mut NbtTag, name: &str, value: &NbtTag) -> usize {
425 let NbtTag::Compound(compound) = parent else {
426 return 0;
427 };
428
429 if compound.get(name).is_some_and(|current| current == value) {
430 return 0;
431 }
432 if let Some(current) = compound.get_mut(name) {
433 *current = value.clone();
434 } else {
435 compound.insert(name, value.clone());
436 }
437 1
438}
439
440fn set_matching_compound_child(
441 parent: &mut NbtTag,
442 name: &str,
443 pattern: &NbtCompound,
444 value: &NbtTag,
445) -> usize {
446 let NbtTag::Compound(compound) = parent else {
447 return 0;
448 };
449 let Some(current) = compound.get_mut(name) else {
450 return 0;
451 };
452 if !compound_pattern_matches(pattern, current) || current == value {
453 return 0;
454 }
455
456 *current = value.clone();
457 1
458}
459
460fn visit_all_collection_elements(
461 parent: &mut NbtTag,
462 child: &NbtTag,
463 mut visitor: impl FnMut(&mut NbtTag) -> Result<bool, NbtPathMutationError>,
464) -> Result<bool, NbtPathMutationError> {
465 ensure_non_empty_collection(parent, child);
466 let Some(mut elements) = collection_elements_for_mutation(parent) else {
467 return Ok(false);
468 };
469 if elements.is_empty() {
470 return Ok(false);
471 }
472
473 let mut found = false;
474 let mut changed = false;
475 for element in &mut elements {
476 let before = element.clone();
477 found |= visitor(element)?;
478 changed |= *element != before;
479 }
480 if changed {
481 replace_collection(parent, elements);
482 }
483 Ok(found)
484}
485
486fn visit_indexed_collection_element(
487 parent: &mut NbtTag,
488 index: i32,
489 mut visitor: impl FnMut(&mut NbtTag) -> Result<bool, NbtPathMutationError>,
490) -> Result<bool, NbtPathMutationError> {
491 let Some(mut elements) = collection_elements_for_mutation(parent) else {
492 return Ok(false);
493 };
494 let Some(actual_index) = actual_collection_index(elements.len(), index) else {
495 return Ok(false);
496 };
497
498 let before = elements[actual_index].clone();
499 let found = visitor(&mut elements[actual_index])?;
500 if elements[actual_index] != before {
501 replace_collection(parent, elements);
502 }
503 Ok(found)
504}
505
506fn visit_matching_list_elements(
507 parent: &mut NbtTag,
508 pattern: &NbtCompound,
509 mut visitor: impl FnMut(&mut NbtTag) -> Result<bool, NbtPathMutationError>,
510) -> Result<bool, NbtPathMutationError> {
511 let NbtTag::List(list) = parent else {
512 return Ok(false);
513 };
514
515 ensure_matching_list_element(list, pattern);
516 let mut elements = list_as_tags(list);
517 let mut found = false;
518 let mut changed = false;
519 for element in elements
520 .iter_mut()
521 .filter(|element| compound_pattern_matches(pattern, element))
522 {
523 let before = element.clone();
524 found |= visitor(element)?;
525 changed |= *element != before;
526 }
527 if changed {
528 *list = NbtList::from(elements);
529 }
530 Ok(found)
531}
532
533fn ensure_non_empty_collection(parent: &mut NbtTag, child: &NbtTag) {
534 if collection_len(parent) != Some(0) {
535 return;
536 }
537 add_collection_element(parent, child.clone());
538}
539
540fn ensure_matching_list_element(list: &mut NbtList, pattern: &NbtCompound) {
541 let mut elements = list_as_tags(list);
542 if elements
543 .iter()
544 .any(|element| compound_pattern_matches(pattern, element))
545 {
546 return;
547 }
548
549 elements.push(NbtTag::Compound(pattern.clone()));
550 *list = NbtList::from(elements);
551}
552
553fn set_all_collection_elements(parent: &mut NbtTag, value: &NbtTag) -> usize {
554 let Some(len) = collection_len(parent) else {
555 return 0;
556 };
557 if len == 0 {
558 add_collection_element(parent, value.clone());
559 return 1;
560 }
561
562 let Some(elements) = collection_elements_for_mutation(parent) else {
563 return 0;
564 };
565 let changed_count = elements.iter().filter(|element| *element != value).count();
566 if changed_count == 0 {
567 return 0;
568 }
569
570 if replace_collection_with_repeated(parent, len, value) {
571 changed_count
572 } else {
573 clear_collection(parent);
574 0
575 }
576}
577
578fn set_indexed_collection_element(parent: &mut NbtTag, index: i32, value: &NbtTag) -> usize {
579 let Some(len) = collection_len(parent) else {
580 return 0;
581 };
582 let Some(actual_index) = actual_collection_index(len, index) else {
583 return 0;
584 };
585 if collection_element(parent, actual_index).is_some_and(|current| ¤t == value) {
586 return 0;
587 }
588
589 usize::from(set_collection_element(parent, actual_index, value.clone()))
590}
591
592fn set_matching_list_elements(parent: &mut NbtTag, pattern: &NbtCompound, value: &NbtTag) -> usize {
593 let NbtTag::List(list) = parent else {
594 return 0;
595 };
596 if list_is_empty(list) {
597 return usize::from(add_list_element(list, value.clone()));
598 }
599
600 let mut elements = list_as_tags(list);
601 let mut changed = 0;
602 for element in &mut elements {
603 if !compound_pattern_matches(pattern, element) || element == value {
604 continue;
605 }
606 *element = value.clone();
607 changed += 1;
608 }
609 if changed > 0 {
610 *list = NbtList::from(elements);
611 }
612 changed
613}
614
615const fn collection_len(tag: &NbtTag) -> Option<usize> {
616 match tag {
617 NbtTag::List(list) => Some(list_len(list)),
618 NbtTag::ByteArray(values) => Some(values.len()),
619 NbtTag::IntArray(values) => Some(values.len()),
620 NbtTag::LongArray(values) => Some(values.len()),
621 _ => None,
622 }
623}
624
625const fn list_len(list: &NbtList) -> usize {
626 match list {
627 NbtList::Empty => 0,
628 NbtList::Byte(values) => values.len(),
629 NbtList::Short(values) => values.len(),
630 NbtList::Int(values) => values.len(),
631 NbtList::Long(values) => values.len(),
632 NbtList::Float(values) => values.len(),
633 NbtList::Double(values) => values.len(),
634 NbtList::ByteArray(values) => values.len(),
635 NbtList::String(values) => values.len(),
636 NbtList::List(values) => values.len(),
637 NbtList::Compound(values) => values.len(),
638 NbtList::IntArray(values) => values.len(),
639 NbtList::LongArray(values) => values.len(),
640 }
641}
642
643const fn list_is_empty(list: &NbtList) -> bool {
644 list_len(list) == 0
645}
646
647fn actual_collection_index(len: usize, index: i32) -> Option<usize> {
648 let actual_index = if index < 0 {
649 len.checked_add_signed(index as isize)?
650 } else {
651 usize::try_from(index).ok()?
652 };
653 (actual_index < len).then_some(actual_index)
654}
655
656fn collection_element(tag: &NbtTag, index: usize) -> Option<NbtTag> {
657 match tag {
658 NbtTag::List(list) => list_as_tags(list).get(index).cloned(),
659 NbtTag::ByteArray(values) => values.get(index).map(|value| NbtTag::Byte(*value as i8)),
660 NbtTag::IntArray(values) => values.get(index).copied().map(NbtTag::Int),
661 NbtTag::LongArray(values) => values.get(index).copied().map(NbtTag::Long),
662 _ => None,
663 }
664}
665
666fn collection_elements_for_mutation(tag: &NbtTag) -> Option<Vec<NbtTag>> {
667 match tag {
668 NbtTag::List(list) => Some(list_as_tags(list)),
669 NbtTag::ByteArray(values) => Some(
670 values
671 .iter()
672 .map(|value| NbtTag::Byte(*value as i8))
673 .collect(),
674 ),
675 NbtTag::IntArray(values) => Some(values.iter().copied().map(NbtTag::Int).collect()),
676 NbtTag::LongArray(values) => Some(values.iter().copied().map(NbtTag::Long).collect()),
677 _ => None,
678 }
679}
680
681fn replace_collection(parent: &mut NbtTag, elements: Vec<NbtTag>) {
682 match parent {
683 NbtTag::List(list) => *list = NbtList::from(elements),
684 NbtTag::ByteArray(values) => {
685 if let Some(replacement) = tags_to_byte_array(elements) {
686 *values = replacement;
687 }
688 }
689 NbtTag::IntArray(values) => {
690 if let Some(replacement) = tags_to_int_array(elements) {
691 *values = replacement;
692 }
693 }
694 NbtTag::LongArray(values) => {
695 if let Some(replacement) = tags_to_long_array(elements) {
696 *values = replacement;
697 }
698 }
699 _ => {}
700 }
701}
702
703fn replace_collection_with_repeated(parent: &mut NbtTag, len: usize, value: &NbtTag) -> bool {
704 match parent {
705 NbtTag::List(list) => {
706 *list = NbtList::from(vec![value.clone(); len]);
707 true
708 }
709 NbtTag::ByteArray(values) => {
710 let Some(value) = nbt_byte_value(value) else {
711 return false;
712 };
713 *values = vec![value as u8; len];
714 true
715 }
716 NbtTag::IntArray(values) => {
717 let Some(value) = nbt_int_value(value) else {
718 return false;
719 };
720 *values = vec![value; len];
721 true
722 }
723 NbtTag::LongArray(values) => {
724 let Some(value) = nbt_long_value(value) else {
725 return false;
726 };
727 *values = vec![value; len];
728 true
729 }
730 _ => false,
731 }
732}
733
734fn clear_collection(parent: &mut NbtTag) {
735 match parent {
736 NbtTag::List(list) => clear_list(list),
737 NbtTag::ByteArray(values) => values.clear(),
738 NbtTag::IntArray(values) => values.clear(),
739 NbtTag::LongArray(values) => values.clear(),
740 _ => {}
741 }
742}
743
744fn clear_list(list: &mut NbtList) {
745 match list {
746 NbtList::Empty => {}
747 NbtList::Byte(values) => values.clear(),
748 NbtList::Short(values) => values.clear(),
749 NbtList::Int(values) => values.clear(),
750 NbtList::Long(values) => values.clear(),
751 NbtList::Float(values) => values.clear(),
752 NbtList::Double(values) => values.clear(),
753 NbtList::ByteArray(values) => values.clear(),
754 NbtList::String(values) => values.clear(),
755 NbtList::List(values) => values.clear(),
756 NbtList::Compound(values) => values.clear(),
757 NbtList::IntArray(values) => values.clear(),
758 NbtList::LongArray(values) => values.clear(),
759 }
760}
761
762fn add_collection_element(parent: &mut NbtTag, value: NbtTag) -> bool {
763 match parent {
764 NbtTag::List(list) => add_list_element(list, value),
765 NbtTag::ByteArray(values) => {
766 let Some(value) = nbt_byte_value(&value) else {
767 return false;
768 };
769 values.push(value as u8);
770 true
771 }
772 NbtTag::IntArray(values) => {
773 let Some(value) = nbt_int_value(&value) else {
774 return false;
775 };
776 values.push(value);
777 true
778 }
779 NbtTag::LongArray(values) => {
780 let Some(value) = nbt_long_value(&value) else {
781 return false;
782 };
783 values.push(value);
784 true
785 }
786 _ => false,
787 }
788}
789
790fn add_list_element(list: &mut NbtList, value: NbtTag) -> bool {
791 let mut elements = list_as_tags(list);
792 elements.push(value);
793 *list = NbtList::from(elements);
794 true
795}
796
797fn set_collection_element(parent: &mut NbtTag, index: usize, value: NbtTag) -> bool {
798 match parent {
799 NbtTag::List(list) => set_list_element(list, index, value),
800 NbtTag::ByteArray(values) => {
801 let Some(value) = nbt_byte_value(&value) else {
802 return false;
803 };
804 let Some(current) = values.get_mut(index) else {
805 return false;
806 };
807 *current = value as u8;
808 true
809 }
810 NbtTag::IntArray(values) => {
811 let Some(value) = nbt_int_value(&value) else {
812 return false;
813 };
814 let Some(current) = values.get_mut(index) else {
815 return false;
816 };
817 *current = value;
818 true
819 }
820 NbtTag::LongArray(values) => {
821 let Some(value) = nbt_long_value(&value) else {
822 return false;
823 };
824 let Some(current) = values.get_mut(index) else {
825 return false;
826 };
827 *current = value;
828 true
829 }
830 _ => false,
831 }
832}
833
834fn set_list_element(list: &mut NbtList, index: usize, value: NbtTag) -> bool {
835 let mut elements = list_as_tags(list);
836 let Some(current) = elements.get_mut(index) else {
837 return false;
838 };
839 *current = value;
840 *list = NbtList::from(elements);
841 true
842}
843
844fn tags_to_byte_array(tags: Vec<NbtTag>) -> Option<Vec<u8>> {
845 tags.into_iter()
846 .map(|tag| {
847 let value = nbt_byte_value(&tag)?;
848 Some(value as u8)
849 })
850 .collect()
851}
852
853fn tags_to_int_array(tags: Vec<NbtTag>) -> Option<Vec<i32>> {
854 tags.into_iter().map(|tag| nbt_int_value(&tag)).collect()
855}
856
857fn tags_to_long_array(tags: Vec<NbtTag>) -> Option<Vec<i64>> {
858 tags.into_iter().map(|tag| nbt_long_value(&tag)).collect()
859}
860
861const fn nbt_byte_value(tag: &NbtTag) -> Option<i8> {
862 Some(match tag {
863 NbtTag::Byte(value) => *value,
864 NbtTag::Short(value) => *value as u8 as i8,
865 NbtTag::Int(value) => *value as u8 as i8,
866 NbtTag::Long(value) => *value as u8 as i8,
867 NbtTag::Float(value) => value.floor() as i32 as u8 as i8,
868 NbtTag::Double(value) => value.floor() as i32 as u8 as i8,
869 _ => return None,
870 })
871}
872
873fn nbt_int_value(tag: &NbtTag) -> Option<i32> {
874 Some(match tag {
875 NbtTag::Byte(value) => i32::from(*value),
876 NbtTag::Short(value) => i32::from(*value),
877 NbtTag::Int(value) => *value,
878 NbtTag::Long(value) => *value as i32,
879 NbtTag::Float(value) => value.floor() as i32,
880 NbtTag::Double(value) => value.floor() as i32,
881 _ => return None,
882 })
883}
884
885fn nbt_long_value(tag: &NbtTag) -> Option<i64> {
886 Some(match tag {
887 NbtTag::Byte(value) => i64::from(*value),
888 NbtTag::Short(value) => i64::from(*value),
889 NbtTag::Int(value) => i64::from(*value),
890 NbtTag::Long(value) => *value,
891 NbtTag::Float(value) => *value as i64,
892 NbtTag::Double(value) => value.floor() as i64,
893 _ => return None,
894 })
895}
896
897fn is_too_deep(tag: &NbtTag, depth: usize) -> bool {
898 if depth >= 512 {
899 return true;
900 }
901
902 match tag {
903 NbtTag::Compound(compound) => compound.values().any(|child| is_too_deep(child, depth + 1)),
904 NbtTag::List(list) => list_as_tags(list)
905 .iter()
906 .any(|child| is_too_deep(child, depth + 1)),
907 _ => false,
908 }
909}
910
911fn indexed_element(elements: &[NbtTag], index: i32) -> Option<NbtTag> {
912 let actual_index = if index < 0 {
913 elements.len().checked_add_signed(index as isize)?
914 } else {
915 usize::try_from(index).ok()?
916 };
917 elements.get(actual_index).cloned()
918}
919
920fn collection_elements(tag: &NbtTag) -> Vec<NbtTag> {
921 match tag {
922 NbtTag::List(list) => list_as_tags(list),
923 NbtTag::ByteArray(values) => values
924 .iter()
925 .map(|value| NbtTag::Byte(*value as i8))
926 .collect(),
927 NbtTag::IntArray(values) => values.iter().copied().map(NbtTag::Int).collect(),
928 NbtTag::LongArray(values) => values.iter().copied().map(NbtTag::Long).collect(),
929 _ => Vec::new(),
930 }
931}
932
933struct Parser<'a> {
934 input: &'a str,
935 cursor: usize,
936}
937
938impl<'a> Parser<'a> {
939 const fn new(input: &'a str) -> Self {
940 Self { input, cursor: 0 }
941 }
942
943 fn parse(&mut self) -> Result<NbtPath, NbtPathError> {
944 let start = self.cursor;
945 let mut nodes = Vec::new();
946 let mut first_node = true;
947
948 while self.can_read() && self.peek() != Some(' ') {
949 let node = self.parse_node(first_node)?;
950 nodes.push(NbtPathNodeEntry {
951 node,
952 original_end: self.cursor - start,
953 });
954 first_node = false;
955
956 if self.can_read() {
957 let Some(next) = self.peek() else {
958 break;
959 };
960 if next != ' ' && next != '[' && next != '{' {
961 self.expect_char('.')?;
962 }
963 }
964 }
965
966 if nodes.is_empty() {
967 return Err(self.error(NbtPathErrorKind::ExpectedPath));
968 }
969
970 Ok(NbtPath {
971 original: self.input[start..self.cursor].to_owned(),
972 nodes,
973 })
974 }
975
976 fn parse_node(&mut self, first_node: bool) -> Result<NbtPathNode, NbtPathError> {
977 match self.peek() {
978 Some('"' | '\'') => {
979 let name = self.parse_quoted_string()?;
980 self.read_object_node(name)
981 }
982 Some('[') => self.parse_element_node(),
983 Some('{') => {
984 if !first_node {
985 return Err(self.error(NbtPathErrorKind::InvalidNode));
986 }
987 let pattern = self.parse_compound_pattern()?;
988 Ok(NbtPathNode::MatchRootObject(pattern))
989 }
990 Some(_) => {
991 let name = self.parse_unquoted_name()?;
992 self.read_object_node(name)
993 }
994 None => Err(self.error(NbtPathErrorKind::ExpectedPath)),
995 }
996 }
997
998 fn read_object_node(&mut self, name: String) -> Result<NbtPathNode, NbtPathError> {
999 if name.is_empty() {
1000 return Err(self.error(NbtPathErrorKind::ExpectedPath));
1001 }
1002 if self.peek() == Some('{') {
1003 let pattern = self.parse_compound_pattern()?;
1004 Ok(NbtPathNode::MatchObject { name, pattern })
1005 } else {
1006 Ok(NbtPathNode::CompoundChild(name))
1007 }
1008 }
1009
1010 fn parse_element_node(&mut self) -> Result<NbtPathNode, NbtPathError> {
1011 self.expect_char('[')?;
1012 match self.peek() {
1013 Some('{') => {
1014 let pattern = self.parse_compound_pattern()?;
1015 self.expect_char(']')?;
1016 Ok(NbtPathNode::MatchElement(pattern))
1017 }
1018 Some(']') => {
1019 self.read();
1020 Ok(NbtPathNode::AllElements)
1021 }
1022 _ => {
1023 let index = self.parse_i32()?;
1024 self.expect_char(']')?;
1025 Ok(NbtPathNode::IndexedElement(index))
1026 }
1027 }
1028 }
1029
1030 fn parse_compound_pattern(&mut self) -> Result<NbtCompound, NbtPathError> {
1031 let start = self.cursor;
1032 let (compound, consumed) =
1033 parse_snbt_compound_argument(&self.input[start..]).map_err(|error| {
1034 let cursor = error.cursor();
1035 NbtPathError::new(
1036 start + cursor,
1037 NbtPathErrorKind::InvalidSnbt(error.into_kind()),
1038 )
1039 })?;
1040 self.cursor += consumed;
1041 Ok(compound)
1042 }
1043
1044 fn parse_unquoted_name(&mut self) -> Result<String, NbtPathError> {
1045 let start = self.cursor;
1046 while self.peek().is_some_and(is_allowed_in_unquoted_name) {
1047 self.read();
1048 }
1049 if self.cursor == start {
1050 return Err(self.error(NbtPathErrorKind::InvalidNode));
1051 }
1052 Ok(self.input[start..self.cursor].to_owned())
1053 }
1054
1055 fn parse_quoted_string(&mut self) -> Result<String, NbtPathError> {
1056 let Some(terminator) = self.peek().filter(|ch| matches!(ch, '"' | '\'')) else {
1057 return Err(self.error(NbtPathErrorKind::ExpectedQuotedString));
1058 };
1059 self.read();
1060
1061 let mut value = String::new();
1062 while let Some(ch) = self.read() {
1063 match ch {
1064 ch if ch == terminator => return Ok(value),
1065 '\\' => {
1066 let escape_cursor = self.cursor;
1067 let escaped = self
1068 .read()
1069 .ok_or_else(|| self.error(NbtPathErrorKind::UnclosedQuotedString))?;
1070 if escaped != terminator && escaped != '\\' {
1071 return Err(Self::error_at(
1072 escape_cursor,
1073 NbtPathErrorKind::InvalidEscape(escaped),
1074 ));
1075 }
1076 value.push(escaped);
1077 }
1078 _ => value.push(ch),
1079 }
1080 }
1081
1082 Err(self.error(NbtPathErrorKind::UnclosedQuotedString))
1083 }
1084
1085 fn parse_i32(&mut self) -> Result<i32, NbtPathError> {
1086 let start = self.cursor;
1087 while self
1088 .peek()
1089 .is_some_and(|ch| ch.is_ascii_digit() || matches!(ch, '.' | '-'))
1090 {
1091 self.read();
1092 }
1093 let value = &self.input[start..self.cursor];
1094 if value.is_empty() {
1095 return Err(Self::error_at(start, NbtPathErrorKind::ExpectedIndex));
1096 }
1097 if let Ok(value) = value.parse() {
1098 return Ok(value);
1099 }
1100 let value = value.to_owned();
1101 self.cursor = start;
1102 Err(Self::error_at(start, NbtPathErrorKind::InvalidIndex(value)))
1103 }
1104
1105 const fn can_read(&self) -> bool {
1106 self.cursor < self.input.len()
1107 }
1108
1109 fn peek(&self) -> Option<char> {
1110 self.input[self.cursor..].chars().next()
1111 }
1112
1113 fn read(&mut self) -> Option<char> {
1114 let ch = self.peek()?;
1115 self.cursor += ch.len_utf8();
1116 Some(ch)
1117 }
1118
1119 fn expect_char(&mut self, expected: char) -> Result<(), NbtPathError> {
1120 if self.peek() == Some(expected) {
1121 self.read();
1122 Ok(())
1123 } else {
1124 Err(self.error(NbtPathErrorKind::ExpectedSymbol(expected)))
1125 }
1126 }
1127
1128 const fn error(&self, kind: NbtPathErrorKind) -> NbtPathError {
1129 NbtPathError::new(self.cursor, kind)
1130 }
1131
1132 const fn error_at(cursor: usize, kind: NbtPathErrorKind) -> NbtPathError {
1133 NbtPathError::new(cursor, kind)
1134 }
1135}
1136
1137const fn is_allowed_in_unquoted_name(ch: char) -> bool {
1138 !matches!(ch, ' ' | '"' | '\'' | '[' | ']' | '.' | '{' | '}')
1139}
1140
1141#[cfg(test)]
1142mod tests {
1143 use simdnbt::owned::{NbtCompound, NbtList, NbtTag};
1144
1145 use super::*;
1146
1147 fn compound(entries: impl IntoIterator<Item = (&'static str, NbtTag)>) -> NbtTag {
1148 let mut compound = NbtCompound::new();
1149 for (key, tag) in entries {
1150 compound.insert(key, tag);
1151 }
1152 NbtTag::Compound(compound)
1153 }
1154
1155 fn list(entries: impl IntoIterator<Item = NbtTag>) -> NbtTag {
1156 NbtTag::List(NbtList::from(entries.into_iter().collect::<Vec<_>>()))
1157 }
1158
1159 #[test]
1160 fn parses_path_argument_without_consuming_separator() {
1161 let (path, cursor) = parse_nbt_path_argument("foo.bar run").expect("path parses");
1162
1163 assert_eq!(path.as_str(), "foo.bar");
1164 assert_eq!(cursor, 7);
1165 }
1166
1167 #[test]
1168 fn counts_compound_child_matches() {
1169 let path = parse_nbt_path("foo.bar").expect("path parses");
1170 let tag = compound([("foo", compound([("bar", NbtTag::Int(3))]))]);
1171
1172 assert_eq!(path.count_matching(&tag), 1);
1173 assert_eq!(path.get(&tag), vec![NbtTag::Int(3)]);
1174 }
1175
1176 #[test]
1177 fn counts_list_index_and_wildcard_matches() {
1178 let path = parse_nbt_path("items[1].id").expect("path parses");
1179 let wildcard = parse_nbt_path("items[].id").expect("path parses");
1180 let tag = compound([(
1181 "items",
1182 list([
1183 compound([("id", NbtTag::String("first".into()))]),
1184 compound([("id", NbtTag::String("second".into()))]),
1185 ]),
1186 )]);
1187
1188 assert_eq!(path.get(&tag), vec![NbtTag::String("second".into())]);
1189 assert_eq!(wildcard.count_matching(&tag), 2);
1190 }
1191
1192 #[test]
1193 fn negative_indices_select_from_end() {
1194 let path = parse_nbt_path("items[-1]").expect("path parses");
1195 let tag = compound([(
1196 "items",
1197 list([NbtTag::Int(1), NbtTag::Int(2), NbtTag::Int(3)]),
1198 )]);
1199
1200 assert_eq!(path.get(&tag), vec![NbtTag::Int(3)]);
1201 }
1202
1203 #[test]
1204 fn predicate_nodes_use_partial_compound_matching() {
1205 let path = parse_nbt_path("items[{id:\"minecraft:stone\"}].Count").expect("path parses");
1206 let tag = compound([(
1207 "items",
1208 list([
1209 compound([
1210 ("id", NbtTag::String("minecraft:dirt".into())),
1211 ("Count", NbtTag::Byte(1)),
1212 ]),
1213 compound([
1214 ("id", NbtTag::String("minecraft:stone".into())),
1215 ("Count", NbtTag::Byte(4)),
1216 ("Slot", NbtTag::Byte(0)),
1217 ]),
1218 ]),
1219 )]);
1220
1221 assert_eq!(path.get(&tag), vec![NbtTag::Byte(4)]);
1222 }
1223
1224 #[test]
1225 fn root_predicate_matches_root_compound() {
1226 let path = parse_nbt_path("{id:\"minecraft:barrel\"}").expect("path parses");
1227 let tag = compound([
1228 ("id", NbtTag::String("minecraft:barrel".into())),
1229 ("x", NbtTag::Int(4)),
1230 ]);
1231
1232 assert_eq!(path.count_matching(&tag), 1);
1233 }
1234
1235 #[test]
1236 fn preserves_embedded_snbt_error_kind() {
1237 let error = parse_nbt_path("items[{id:}]").expect_err("invalid pattern should fail");
1238
1239 assert_eq!(
1240 error.kind(),
1241 &NbtPathErrorKind::InvalidSnbt(SnbtErrorKind::ExpectedValue)
1242 );
1243 assert_eq!(
1244 error.component(),
1245 TextComponent::from(&translations::SNBT_PARSER_EXPECTED_UNQUOTED_STRING)
1246 );
1247 }
1248
1249 #[test]
1250 fn quoted_key_errors_use_brigadier_cursors() {
1251 let unclosed = parse_nbt_path(r#""items"#).expect_err("unclosed key should fail");
1252 assert_eq!(unclosed.cursor(), r#""items"#.len());
1253 assert_eq!(unclosed.kind(), &NbtPathErrorKind::UnclosedQuotedString);
1254
1255 let invalid_escape =
1256 parse_nbt_path(r#""a\q""#).expect_err("invalid key escape should fail");
1257 assert_eq!(invalid_escape.cursor(), r#""a\"#.len());
1258 assert_eq!(invalid_escape.kind(), &NbtPathErrorKind::InvalidEscape('q'));
1259 }
1260
1261 #[test]
1262 fn index_errors_use_brigadier_integer_components() {
1263 let expected = parse_nbt_path("items[x]").expect_err("missing integer should fail");
1264 assert_eq!(expected.cursor(), "items[".len());
1265 assert_eq!(expected.kind(), &NbtPathErrorKind::ExpectedIndex);
1266 assert_eq!(
1267 expected.component(),
1268 TextComponent::from(&translations::PARSING_INT_EXPECTED)
1269 );
1270
1271 for value in ["-", "1.2", "999999999999999999999"] {
1272 let input = format!("items[{value}]");
1273 let invalid = parse_nbt_path(&input).expect_err("invalid integer should fail");
1274 assert_eq!(invalid.cursor(), "items[".len());
1275 assert_eq!(
1276 invalid.kind(),
1277 &NbtPathErrorKind::InvalidIndex(value.to_owned())
1278 );
1279 assert_eq!(
1280 invalid.component(),
1281 translations::PARSING_INT_INVALID
1282 .message([value.to_owned()])
1283 .component()
1284 );
1285 }
1286 }
1287
1288 #[test]
1289 fn set_creates_missing_compound_parents() {
1290 let path = parse_nbt_path("foo.bar").expect("path parses");
1291 let mut tag = compound([]);
1292
1293 assert_eq!(
1294 path.set(&mut tag, NbtTag::Int(7))
1295 .expect("path set should succeed"),
1296 1
1297 );
1298 assert_eq!(path.get(&tag), vec![NbtTag::Int(7)]);
1299 }
1300
1301 #[test]
1302 fn set_updates_all_list_elements() {
1303 let path = parse_nbt_path("items[].Count").expect("path parses");
1304 let mut tag = compound([(
1305 "items",
1306 list([
1307 compound([("Count", NbtTag::Byte(1))]),
1308 compound([("Count", NbtTag::Byte(2))]),
1309 ]),
1310 )]);
1311
1312 assert_eq!(
1313 path.set(&mut tag, NbtTag::Byte(4))
1314 .expect("path set should succeed"),
1315 2
1316 );
1317 assert_eq!(path.get(&tag), vec![NbtTag::Byte(4), NbtTag::Byte(4)]);
1318 }
1319
1320 #[test]
1321 fn set_updates_indexed_array_elements() {
1322 let path = parse_nbt_path("bytes[1]").expect("path parses");
1323 let mut tag = compound([("bytes", NbtTag::ByteArray(vec![1, 2, 3]))]);
1324
1325 assert_eq!(
1326 path.set(&mut tag, NbtTag::Byte(9))
1327 .expect("path set should succeed"),
1328 1
1329 );
1330 assert_eq!(path.get(&tag), vec![NbtTag::Byte(9)]);
1331 }
1332
1333 #[test]
1334 fn set_coerces_numeric_array_values_like_vanilla() {
1335 let mut tag = compound([
1336 ("bytes", NbtTag::ByteArray(vec![0])),
1337 ("ints", NbtTag::IntArray(vec![0, 0])),
1338 ("longs", NbtTag::LongArray(vec![0])),
1339 ]);
1340
1341 assert_eq!(
1342 parse_nbt_path("bytes[0]")
1343 .expect("path parses")
1344 .set(&mut tag, NbtTag::Short(258))
1345 .expect("path set should succeed"),
1346 1
1347 );
1348 assert_eq!(
1349 parse_nbt_path("ints[]")
1350 .expect("path parses")
1351 .set(&mut tag, NbtTag::Float(3.9))
1352 .expect("path set should succeed"),
1353 2
1354 );
1355 assert_eq!(
1356 parse_nbt_path("longs[0]")
1357 .expect("path parses")
1358 .set(&mut tag, NbtTag::Double(-1.2))
1359 .expect("path set should succeed"),
1360 1
1361 );
1362
1363 assert_eq!(
1364 parse_nbt_path("bytes[0]").expect("path parses").get(&tag),
1365 vec![NbtTag::Byte(2)]
1366 );
1367 assert_eq!(
1368 parse_nbt_path("ints[]").expect("path parses").get(&tag),
1369 vec![NbtTag::Int(3), NbtTag::Int(3)]
1370 );
1371 assert_eq!(
1372 parse_nbt_path("longs[0]").expect("path parses").get(&tag),
1373 vec![NbtTag::Long(-2)]
1374 );
1375 }
1376
1377 #[test]
1378 fn empty_array_wildcard_reports_vanilla_change_count_for_rejected_value() {
1379 let path = parse_nbt_path("bytes[]").expect("path parses");
1380 let mut tag = compound([("bytes", NbtTag::ByteArray(vec![]))]);
1381
1382 assert_eq!(
1383 path.set(&mut tag, NbtTag::String("not numeric".into()))
1384 .expect("path set should succeed"),
1385 1
1386 );
1387 assert_eq!(path.get(&tag), Vec::<NbtTag>::new());
1388 }
1389
1390 #[test]
1391 fn set_regular_list_elements_can_change_type_like_vanilla() {
1392 let path = parse_nbt_path("values[1]").expect("path parses");
1393 let all_values = parse_nbt_path("values[]").expect("path parses");
1394 let mut tag = compound([("values", list([NbtTag::Int(1), NbtTag::Int(2)]))]);
1395
1396 assert_eq!(
1397 path.set(&mut tag, NbtTag::String("two".into()))
1398 .expect("path set should succeed"),
1399 1
1400 );
1401 assert_eq!(
1402 all_values.get(&tag),
1403 vec![NbtTag::Int(1), NbtTag::String("two".into())]
1404 );
1405 }
1406
1407 #[test]
1408 fn predicate_parent_creates_match_in_non_compound_list() {
1409 let path = parse_nbt_path("items[{id:\"minecraft:stone\"}].Count").expect("path parses");
1410 let mut tag = compound([("items", list([NbtTag::String("existing".into())]))]);
1411
1412 assert_eq!(
1413 path.set(&mut tag, NbtTag::Byte(5))
1414 .expect("path set should succeed"),
1415 1
1416 );
1417 assert_eq!(path.get(&tag), vec![NbtTag::Byte(5)]);
1418 assert_eq!(
1419 parse_nbt_path("items[]").expect("path parses").get(&tag),
1420 vec![
1421 NbtTag::String("existing".into()),
1422 compound([
1423 ("id", NbtTag::String("minecraft:stone".into())),
1424 ("Count", NbtTag::Byte(5)),
1425 ]),
1426 ]
1427 );
1428 }
1429
1430 #[test]
1431 fn set_predicate_parent_creates_matching_compound() {
1432 let path = parse_nbt_path("items[{id:\"minecraft:stone\"}].Count").expect("path parses");
1433 let mut tag = compound([("items", list([]))]);
1434
1435 assert_eq!(
1436 path.set(&mut tag, NbtTag::Byte(5))
1437 .expect("path set should succeed"),
1438 1
1439 );
1440 assert_eq!(path.get(&tag), vec![NbtTag::Byte(5)]);
1441 assert_eq!(
1442 parse_nbt_path("items[0].id")
1443 .expect("id path parses")
1444 .get(&tag),
1445 vec![NbtTag::String("minecraft:stone".into())]
1446 );
1447 }
1448
1449 #[test]
1450 fn set_reports_missing_intermediate_path() {
1451 let path = parse_nbt_path("items[0].Count").expect("path parses");
1452 let mut tag = compound([("items", list([]))]);
1453
1454 assert_eq!(
1455 path.set(&mut tag, NbtTag::Byte(1)),
1456 Err(NbtPathMutationError::NothingFound("items[0]".to_owned()))
1457 );
1458 }
1459}