Skip to main content

steel_core/command/execution/
item_predicate.rs

1//! Vanilla item-predicate command argument parsing and matching.
2
3use std::cmp::Ordering;
4
5use simdnbt::owned::{NbtCompound, NbtList, NbtTag};
6use steel_registry::{
7    REGISTRY, RegistryExt as _, TaggedRegistryExt as _,
8    attribute::{AttributeModifierOperation, AttributeRef},
9    data_component_predicate as registered,
10    data_component_predicate::DataComponentPredicateCodec as _,
11    data_components::{
12        Component, ComponentData, ComponentEntry, DataComponentType, PotionContents,
13        vanilla_components,
14    },
15    enchantment::EnchantmentRef,
16    equipment::EquipmentSlotGroup,
17    item_predicate::{DoubleBounds, IntBounds, ItemPredicate as RegisteredItemPredicate},
18    item_stack::ItemStack,
19    items::ItemRef,
20};
21use steel_utils::DowncastType;
22use steel_utils::{Identifier, nbt::parse_snbt_argument};
23use text_components::TextComponent;
24
25use crate::command::brigadier::{
26    CommandSyntaxError, CommandSyntaxErrorKind, ReaderCursor, StringReader, SuggestionsBuilder,
27};
28
29use super::{
30    argument::parse_identifier,
31    item::{component_value_is_valid, numeric_i32, read_component_value},
32    suggestions::matches_substring,
33};
34
35const VANILLA_DATA_COMPONENT_PREDICATE_KEYS: &[&str] = &[
36    "damage",
37    "enchantments",
38    "stored_enchantments",
39    "potion_contents",
40    "custom_data",
41    "container",
42    "bundle_contents",
43    "firework_explosion",
44    "fireworks",
45    "writable_book_content",
46    "written_book_content",
47    "attribute_modifiers",
48    "trim",
49    "jukebox_playable",
50    "villager/variant",
51];
52
53/// A fully decoded item predicate retained until command execution.
54#[derive(Clone, Debug, PartialEq)]
55pub(crate) struct ItemPredicate {
56    target: ItemPredicateTarget,
57    conditions: Vec<ItemPredicateCondition>,
58}
59
60#[derive(Clone, Debug, PartialEq)]
61enum ItemPredicateTarget {
62    Any,
63    Item(ItemRef),
64    Tag(Identifier),
65}
66
67#[derive(Clone, Debug, PartialEq)]
68struct ItemPredicateCondition {
69    alternatives: Vec<ItemPredicateTerm>,
70}
71
72#[derive(Clone, Debug, PartialEq)]
73enum ItemPredicateTerm {
74    Always,
75    ComponentPresence(Identifier),
76    ComponentValue {
77        key: Identifier,
78        value: Box<ComponentData>,
79    },
80    Count(IntRange),
81    DataPredicate(DataComponentPredicate),
82    Not(Box<Self>),
83}
84
85#[derive(Clone, Debug, PartialEq)]
86enum DataComponentPredicate {
87    CustomData(vanilla_components::CustomData),
88    Damage(DamagePredicate),
89    Enchantments {
90        stored: bool,
91        predicates: Vec<EnchantmentPredicate>,
92    },
93    AttributeModifiers(AttributeModifiersPredicate),
94    Potions(registered::PotionsPredicate),
95    Container(registered::ContainerPredicate),
96    Bundle(registered::BundlePredicate),
97    FireworkExplosion(registered::FireworkExplosionPredicate),
98    Fireworks(registered::FireworksPredicate),
99    WritableBook(registered::WritableBookPredicate),
100    WrittenBook(registered::WrittenBookPredicate),
101    Trim(registered::TrimPredicate),
102    JukeboxPlayable(registered::JukeboxPlayablePredicate),
103    VillagerType(registered::VillagerTypePredicate),
104}
105
106trait ItemInstanceView {
107    fn item_ref(&self) -> ItemRef;
108    fn item_count(&self) -> i32;
109    fn effective_value_raw(&self, key: &Identifier) -> Option<&ComponentData>;
110
111    fn component<T: Component + DowncastType>(
112        &self,
113        component: DataComponentType<T>,
114    ) -> Option<&T> {
115        self.effective_value_raw(component.key())
116            .and_then(ComponentData::downcast_ref::<T>)
117    }
118}
119
120impl ItemInstanceView for ItemStack {
121    fn item_ref(&self) -> ItemRef {
122        self.item()
123    }
124
125    fn item_count(&self) -> i32 {
126        self.count()
127    }
128
129    fn effective_value_raw(&self, key: &Identifier) -> Option<&ComponentData> {
130        self.get_effective_value_raw(key)
131    }
132}
133
134impl ItemInstanceView for steel_registry::ItemStackTemplate {
135    fn item_ref(&self) -> ItemRef {
136        self.item()
137    }
138
139    fn item_count(&self) -> i32 {
140        self.count()
141    }
142
143    fn effective_value_raw(&self, key: &Identifier) -> Option<&ComponentData> {
144        self.get_effective_value_raw(key)
145    }
146}
147
148impl ItemPredicate {
149    #[must_use]
150    pub(crate) fn matches(&self, stack: &ItemStack) -> bool {
151        self.target.matches(stack)
152            && self
153                .conditions
154                .iter()
155                .all(|condition| condition.matches(stack))
156    }
157}
158
159impl ItemPredicateTarget {
160    fn matches(&self, stack: &ItemStack) -> bool {
161        match self {
162            Self::Any => true,
163            Self::Item(item) => stack.is(item),
164            Self::Tag(tag) => REGISTRY.items.is_in_tag(stack.item(), tag),
165        }
166    }
167}
168
169impl ItemPredicateCondition {
170    fn matches(&self, stack: &ItemStack) -> bool {
171        self.alternatives
172            .iter()
173            .any(|alternative| alternative.matches(stack))
174    }
175}
176
177impl ItemPredicateTerm {
178    fn matches(&self, stack: &ItemStack) -> bool {
179        match self {
180            Self::Always => true,
181            Self::ComponentPresence(key) => stack.has_component(key),
182            Self::ComponentValue { key, value } => stack
183                .get_effective_value_raw(key)
184                .is_some_and(|actual| actual == value.as_ref()),
185            Self::Count(range) => range.matches(stack.count()),
186            Self::DataPredicate(predicate) => predicate.matches(stack),
187            Self::Not(term) => !term.matches(stack),
188        }
189    }
190}
191
192impl DataComponentPredicate {
193    #[expect(
194        clippy::too_many_lines,
195        reason = "keeping every registered predicate variant in one match makes coverage auditable"
196    )]
197    fn matches<S: ItemInstanceView + ?Sized>(&self, stack: &S) -> bool {
198        match self {
199            Self::CustomData(expected) => stack
200                .component(vanilla_components::CUSTOM_DATA)
201                .cloned()
202                .unwrap_or_default()
203                .matched_by(expected.as_compound()),
204            Self::Damage(predicate) => predicate.matches(stack),
205            Self::Enchantments { stored, predicates } => {
206                let enchantments = if *stored {
207                    stack.component(vanilla_components::STORED_ENCHANTMENTS)
208                } else {
209                    stack.component(vanilla_components::ENCHANTMENTS)
210                };
211                enchantments.is_some_and(|enchantments| {
212                    predicates
213                        .iter()
214                        .all(|predicate| predicate.matches(enchantments))
215                })
216            }
217            Self::AttributeModifiers(predicate) => stack
218                .component(vanilla_components::ATTRIBUTE_MODIFIERS)
219                .is_some_and(|modifiers| predicate.matches(modifiers)),
220            Self::Potions(predicate) => stack
221                .component(vanilla_components::POTION_CONTENTS)
222                .and_then(PotionContents::potion)
223                .is_some_and(|potion| predicate.potions().contains(potion.value())),
224            Self::Container(predicate) => stack
225                .component(vanilla_components::CONTAINER)
226                .is_some_and(|contents| {
227                    predicate.items().is_none_or(|items| {
228                        collection_matches(
229                            items,
230                            contents.items().iter().filter_map(Option::as_ref),
231                            registered_item_predicate_matches,
232                        )
233                    })
234                }),
235            Self::Bundle(predicate) => stack
236                .component(vanilla_components::BUNDLE_CONTENTS)
237                .is_some_and(|contents| {
238                    predicate.items().is_none_or(|items| {
239                        collection_matches(
240                            items,
241                            contents.items().iter(),
242                            registered_item_predicate_matches,
243                        )
244                    })
245                }),
246            Self::FireworkExplosion(predicate) => stack
247                .component(vanilla_components::FIREWORK_EXPLOSION)
248                .is_some_and(|explosion| firework_matches(predicate.predicate(), explosion)),
249            Self::Fireworks(predicate) => stack
250                .component(vanilla_components::FIREWORKS)
251                .is_some_and(|fireworks| {
252                    int_bounds_matches(predicate.flight_duration(), fireworks.flight_duration())
253                        && predicate.explosions().is_none_or(|explosions| {
254                            collection_matches(
255                                explosions,
256                                fireworks.explosions().iter(),
257                                firework_matches,
258                            )
259                        })
260                }),
261            Self::WritableBook(predicate) => stack
262                .component(vanilla_components::WRITABLE_BOOK_CONTENT)
263                .is_some_and(|book| {
264                    predicate.pages().is_none_or(|pages| {
265                        collection_matches(pages, book.pages().iter(), |predicate, page| {
266                            predicate.contents() == page.raw()
267                        })
268                    })
269                }),
270            Self::WrittenBook(predicate) => stack
271                .component(vanilla_components::WRITTEN_BOOK_CONTENT)
272                .is_some_and(|book| {
273                    predicate
274                        .author()
275                        .is_none_or(|author| author == book.author())
276                        && predicate
277                            .title()
278                            .is_none_or(|title| title == book.title().raw())
279                        && int_bounds_matches(predicate.generation(), book.generation())
280                        && predicate
281                            .resolved()
282                            .is_none_or(|resolved| resolved == book.resolved())
283                        && predicate.pages().is_none_or(|pages| {
284                            collection_matches(pages, book.pages().iter(), |predicate, page| {
285                                predicate.contents() == page.raw()
286                            })
287                        })
288                }),
289            Self::Trim(predicate) => {
290                stack
291                    .component(vanilla_components::TRIM)
292                    .is_some_and(|trim| {
293                        predicate.material().is_none_or(|materials| {
294                            trim.material()
295                                .as_reference()
296                                .is_some_and(|material| materials.contains(material))
297                        }) && predicate.pattern().is_none_or(|patterns| {
298                            trim.pattern()
299                                .as_reference()
300                                .is_some_and(|pattern| patterns.contains(pattern))
301                        })
302                    })
303            }
304            Self::JukeboxPlayable(predicate) => stack
305                .component(vanilla_components::JUKEBOX_PLAYABLE)
306                .is_some_and(|playable| {
307                    predicate.song().is_none_or(|songs| {
308                        playable
309                            .song()
310                            .as_reference()
311                            .is_some_and(|song| songs.contains(song))
312                    })
313                }),
314            Self::VillagerType(predicate) => stack
315                .component(vanilla_components::VILLAGER_VARIANT)
316                .is_some_and(|villager_type| {
317                    predicate.villager_types().contains(villager_type.value())
318                }),
319        }
320    }
321}
322
323fn int_bounds_matches(bounds: IntBounds, value: i32) -> bool {
324    bounds.min().is_none_or(|minimum| value >= minimum)
325        && bounds.max().is_none_or(|maximum| value <= maximum)
326}
327
328fn double_bounds_matches(bounds: DoubleBounds, value: f64) -> bool {
329    bounds.min().is_none_or(|minimum| value >= minimum)
330        && bounds.max().is_none_or(|maximum| value <= maximum)
331}
332
333fn collection_matches<'a, P, T: 'a>(
334    predicate: &registered::CollectionPredicate<P>,
335    values: impl IntoIterator<Item = &'a T>,
336    matches: impl Fn(&P, &T) -> bool + Copy,
337) -> bool {
338    let values = values.into_iter().collect::<Vec<_>>();
339    predicate.contains().is_none_or(|predicates| {
340        predicates
341            .iter()
342            .all(|predicate| values.iter().any(|value| matches(predicate, value)))
343    }) && predicate.counts().is_none_or(|predicates| {
344        predicates.iter().all(|predicate| {
345            let count = values
346                .iter()
347                .filter(|value| matches(predicate.test(), value))
348                .count();
349            i32::try_from(count).is_ok_and(|count| int_bounds_matches(predicate.count(), count))
350        })
351    }) && predicate.size().is_none_or(|size| {
352        i32::try_from(values.len()).is_ok_and(|length| int_bounds_matches(*size, length))
353    })
354}
355
356fn registered_item_predicate_matches(
357    predicate: &RegisteredItemPredicate,
358    template: &steel_registry::ItemStackTemplate,
359) -> bool {
360    predicate
361        .items()
362        .is_none_or(|items| items.contains(template.item_ref()))
363        && int_bounds_matches(predicate.count(), template.item_count())
364        && predicate
365            .components()
366            .exact()
367            .values()
368            .iter()
369            .all(|(entry, expected)| template.effective_value_raw(&entry.key) == Some(expected))
370        && predicate
371            .components()
372            .partial()
373            .iter()
374            .all(|predicate| registered_partial_matches(predicate, template))
375}
376
377fn registered_partial_matches<S: ItemInstanceView + ?Sized>(
378    predicate: &registered::DataComponentPredicateData,
379    stack: &S,
380) -> bool {
381    if let Some(component) = predicate.any_component() {
382        return stack.effective_value_raw(&component.key).is_some();
383    }
384    macro_rules! match_registered {
385        ($type:ty, $variant:ident) => {
386            if let Some(value) = predicate.downcast_ref::<$type>() {
387                return DataComponentPredicate::$variant(value.clone()).matches(stack);
388            }
389        };
390    }
391    match_registered!(registered::PotionsPredicate, Potions);
392    match_registered!(registered::ContainerPredicate, Container);
393    match_registered!(registered::BundlePredicate, Bundle);
394    match_registered!(registered::FireworkExplosionPredicate, FireworkExplosion);
395    match_registered!(registered::FireworksPredicate, Fireworks);
396    match_registered!(registered::WritableBookPredicate, WritableBook);
397    match_registered!(registered::WrittenBookPredicate, WrittenBook);
398    match_registered!(registered::TrimPredicate, Trim);
399    match_registered!(registered::JukeboxPlayablePredicate, JukeboxPlayable);
400    match_registered!(registered::VillagerTypePredicate, VillagerType);
401
402    if let Some(value) = predicate.downcast_ref::<registered::CustomDataPredicate>() {
403        return stack
404            .component(vanilla_components::CUSTOM_DATA)
405            .cloned()
406            .unwrap_or_default()
407            .matched_by(value.value().tag());
408    }
409    if let Some(value) = predicate.downcast_ref::<registered::DamagePredicate>() {
410        let Some(damage) = stack.component(vanilla_components::DAMAGE).copied() else {
411            return false;
412        };
413        let maximum = stack
414            .component(vanilla_components::MAX_DAMAGE)
415            .copied()
416            .unwrap_or(0);
417        return int_bounds_matches(value.durability(), maximum - damage)
418            && int_bounds_matches(value.damage(), damage);
419    }
420    if let Some(value) = predicate.downcast_ref::<registered::EnchantmentsPredicate>() {
421        return stack
422            .component(vanilla_components::ENCHANTMENTS)
423            .is_some_and(|enchantments| {
424                value
425                    .enchantments()
426                    .iter()
427                    .all(|predicate| registered_enchantment_matches(predicate, enchantments))
428            });
429    }
430    if let Some(value) = predicate.downcast_ref::<registered::StoredEnchantmentsPredicate>() {
431        return stack
432            .component(vanilla_components::STORED_ENCHANTMENTS)
433            .is_some_and(|enchantments| {
434                value
435                    .enchantments()
436                    .iter()
437                    .all(|predicate| registered_enchantment_matches(predicate, enchantments))
438            });
439    }
440    if let Some(value) = predicate.downcast_ref::<registered::AttributeModifiersPredicate>() {
441        return stack
442            .component(vanilla_components::ATTRIBUTE_MODIFIERS)
443            .is_some_and(|modifiers| {
444                value.modifiers().is_none_or(|predicate| {
445                    collection_matches(
446                        predicate,
447                        modifiers.modifiers.iter(),
448                        registered_attribute_modifier_matches,
449                    )
450                })
451            });
452    }
453    false
454}
455
456fn registered_enchantment_matches(
457    predicate: &registered::EnchantmentPredicate,
458    enchantments: &vanilla_components::ItemEnchantments,
459) -> bool {
460    enchantments.iter().any(|(key, level)| {
461        predicate.enchantments().is_none_or(|expected| {
462            REGISTRY
463                .enchantments
464                .by_key(key)
465                .is_some_and(|enchantment| expected.contains(enchantment))
466        }) && predicate
467            .levels()
468            .min()
469            .is_none_or(|minimum| i64::from(*level) >= i64::from(minimum))
470            && predicate
471                .levels()
472                .max()
473                .is_none_or(|maximum| i64::from(*level) <= i64::from(maximum))
474    })
475}
476
477fn registered_attribute_modifier_matches(
478    predicate: &registered::AttributeModifierEntryPredicate,
479    modifier: &vanilla_components::ItemAttributeModifierEntry,
480) -> bool {
481    predicate
482        .attribute()
483        .is_none_or(|attributes| attributes.contains(modifier.attribute))
484        && predicate.id().is_none_or(|id| id == &modifier.id)
485        && double_bounds_matches(predicate.amount(), modifier.amount)
486        && predicate
487            .operation()
488            .is_none_or(|operation| operation == modifier.operation)
489        && predicate.slot().is_none_or(|slot| slot == modifier.slot)
490}
491
492fn firework_matches(
493    predicate: &registered::FireworkPredicate,
494    explosion: &vanilla_components::FireworkExplosion,
495) -> bool {
496    predicate
497        .shape()
498        .is_none_or(|shape| shape == explosion.shape())
499        && predicate
500            .has_twinkle()
501            .is_none_or(|twinkle| twinkle == explosion.has_twinkle())
502        && predicate
503            .has_trail()
504            .is_none_or(|trail| trail == explosion.has_trail())
505}
506
507pub(super) fn parse_item_predicate(
508    reader: &mut StringReader<'_>,
509) -> Result<ItemPredicate, CommandSyntaxError> {
510    let start = reader.checkpoint();
511    let result = parse_item_predicate_inner(reader);
512    if result.is_err() {
513        reader.restore(start);
514    }
515    result
516}
517
518fn parse_item_predicate_inner(
519    reader: &mut StringReader<'_>,
520) -> Result<ItemPredicate, CommandSyntaxError> {
521    let target = parse_target(reader)?;
522    let before_whitespace = reader.checkpoint();
523    reader.skip_whitespace();
524    if reader.peek() != Some('[') {
525        reader.restore(before_whitespace);
526        return Ok(ItemPredicate {
527            target,
528            conditions: Vec::new(),
529        });
530    }
531
532    reader.skip();
533    reader.skip_whitespace();
534    if reader.peek() == Some(']') {
535        reader.skip();
536        return Ok(ItemPredicate {
537            target,
538            conditions: Vec::new(),
539        });
540    }
541
542    let mut conditions = Vec::new();
543    loop {
544        conditions.push(parse_condition(reader)?);
545        reader.skip_whitespace();
546        if reader.peek() != Some(',') {
547            reader.expect(']')?;
548            break;
549        }
550        reader.skip();
551    }
552
553    Ok(ItemPredicate { target, conditions })
554}
555
556fn parse_target(reader: &mut StringReader<'_>) -> Result<ItemPredicateTarget, CommandSyntaxError> {
557    reader.skip_whitespace();
558    if reader.peek() == Some('*') {
559        reader.skip();
560        return Ok(ItemPredicateTarget::Any);
561    }
562
563    if reader.peek() == Some('#') {
564        let start = reader.checkpoint();
565        reader.skip();
566        let key = parse_identifier(reader)?;
567        if REGISTRY.items.get_tag(&key).is_some() {
568            return Ok(ItemPredicateTarget::Tag(key));
569        }
570        reader.restore(start);
571        return Err(dynamic_error(reader, format!("Unknown item tag '#{key}'")));
572    }
573
574    let start = reader.checkpoint();
575    let key = parse_identifier(reader)?;
576    let Some(item) = REGISTRY.items.by_key(&key) else {
577        reader.restore(start);
578        return Err(dynamic_error(reader, format!("Unknown item '{key}'")));
579    };
580    Ok(ItemPredicateTarget::Item(item))
581}
582
583fn parse_condition(
584    reader: &mut StringReader<'_>,
585) -> Result<ItemPredicateCondition, CommandSyntaxError> {
586    let mut alternatives = Vec::new();
587    loop {
588        alternatives.push(parse_term(reader)?);
589        reader.skip_whitespace();
590        if reader.peek() != Some('|') {
591            break;
592        }
593        reader.skip();
594    }
595    Ok(ItemPredicateCondition { alternatives })
596}
597
598fn parse_term(reader: &mut StringReader<'_>) -> Result<ItemPredicateTerm, CommandSyntaxError> {
599    reader.skip_whitespace();
600    if reader.peek() == Some('!') {
601        reader.skip();
602        return parse_test(reader).map(|term| ItemPredicateTerm::Not(Box::new(term)));
603    }
604    parse_test(reader)
605}
606
607fn parse_test(reader: &mut StringReader<'_>) -> Result<ItemPredicateTerm, CommandSyntaxError> {
608    reader.skip_whitespace();
609    let key_start = reader.checkpoint();
610    let key = parse_identifier(reader)?;
611    reader.skip_whitespace();
612
613    match reader.peek() {
614        Some('=') => {
615            reader.skip();
616            parse_component_value_test(reader, key_start, key)
617        }
618        Some('~') => {
619            reader.skip();
620            parse_predicate_value_test(reader, key_start, key)
621        }
622        _ => parse_component_presence_test(reader, key_start, key),
623    }
624}
625
626fn parse_component_presence_test(
627    reader: &mut StringReader<'_>,
628    key_start: ReaderCursor,
629    key: Identifier,
630) -> Result<ItemPredicateTerm, CommandSyntaxError> {
631    if is_count_key(&key) {
632        return Ok(ItemPredicateTerm::Always);
633    }
634    if persistent_component(&key).is_some() {
635        return Ok(ItemPredicateTerm::ComponentPresence(key));
636    }
637    reader.restore(key_start);
638    Err(dynamic_error(
639        reader,
640        format!("Unknown item component '{key}'"),
641    ))
642}
643
644fn parse_component_value_test(
645    reader: &mut StringReader<'_>,
646    key_start: ReaderCursor,
647    key: Identifier,
648) -> Result<ItemPredicateTerm, CommandSyntaxError> {
649    let tag = read_nbt(reader, "component", &key)?;
650    if is_count_key(&key) {
651        return parse_int_range(&tag)
652            .map(ItemPredicateTerm::Count)
653            .ok_or_else(|| malformed_component(reader, &key));
654    }
655
656    let Some(entry) = persistent_component(&key) else {
657        reader.restore(key_start);
658        return Err(dynamic_error(
659            reader,
660            format!("Unknown item component '{key}'"),
661        ));
662    };
663    let Some(value) = read_component_value(entry, &tag) else {
664        return Err(malformed_component(reader, &key));
665    };
666    if !component_value_is_valid(&key, &value) {
667        return Err(malformed_component(reader, &key));
668    }
669    Ok(ItemPredicateTerm::ComponentValue {
670        key,
671        value: Box::new(value),
672    })
673}
674
675fn parse_predicate_value_test(
676    reader: &mut StringReader<'_>,
677    key_start: ReaderCursor,
678    key: Identifier,
679) -> Result<ItemPredicateTerm, CommandSyntaxError> {
680    let tag = read_nbt(reader, "predicate", &key)?;
681    if is_count_key(&key) {
682        return parse_int_range(&tag)
683            .map(ItemPredicateTerm::Count)
684            .ok_or_else(|| malformed_predicate(reader, &key));
685    }
686
687    if is_vanilla_predicate_key(&key) {
688        let predicate = parse_supported_data_predicate(&key, &tag)
689            .ok_or_else(|| malformed_predicate(reader, &key))?;
690        return Ok(ItemPredicateTerm::DataPredicate(predicate));
691    }
692
693    if REGISTRY.data_components.by_key(&key).is_some() {
694        if !matches!(tag, NbtTag::Compound(_)) {
695            return Err(malformed_predicate(reader, &key));
696        }
697        return Ok(ItemPredicateTerm::ComponentPresence(key));
698    }
699
700    reader.restore(key_start);
701    Err(dynamic_error(
702        reader,
703        format!("Unknown item predicate '{key}'"),
704    ))
705}
706
707fn read_nbt(
708    reader: &mut StringReader<'_>,
709    description: &str,
710    key: &Identifier,
711) -> Result<NbtTag, CommandSyntaxError> {
712    reader.skip_whitespace();
713    let (tag, consumed) = parse_snbt_argument(reader.remaining()).map_err(|error| {
714        reader.advance_bytes(error.cursor());
715        dynamic_error(reader, error.component())
716    })?;
717    if !reader.advance_bytes(consumed) {
718        return Err(dynamic_error(
719            reader,
720            format!("Malformed item {description} '{key}'"),
721        ));
722    }
723    Ok(tag)
724}
725
726fn persistent_component(key: &Identifier) -> Option<&'static ComponentEntry> {
727    let entry = REGISTRY.data_components.by_key(key)?;
728    entry.is_persistent().then_some(entry)
729}
730
731fn malformed_component(reader: &StringReader<'_>, key: &Identifier) -> CommandSyntaxError {
732    dynamic_error(reader, format!("Malformed item component '{key}'"))
733}
734
735fn malformed_predicate(reader: &StringReader<'_>, key: &Identifier) -> CommandSyntaxError {
736    dynamic_error(reader, format!("Malformed item predicate '{key}'"))
737}
738
739fn dynamic_error(
740    reader: &StringReader<'_>,
741    message: impl Into<TextComponent>,
742) -> CommandSyntaxError {
743    reader.error(CommandSyntaxErrorKind::Dynamic(Box::new(message.into())))
744}
745
746fn is_count_key(key: &Identifier) -> bool {
747    key.namespace == Identifier::VANILLA_NAMESPACE && key.path == "count"
748}
749
750fn is_vanilla_predicate_key(key: &Identifier) -> bool {
751    key.namespace == Identifier::VANILLA_NAMESPACE
752        && VANILLA_DATA_COMPONENT_PREDICATE_KEYS
753            .iter()
754            .any(|path| key.path == *path)
755}
756
757fn parse_supported_data_predicate(
758    key: &Identifier,
759    tag: &NbtTag,
760) -> Option<DataComponentPredicate> {
761    match key.path.as_ref() {
762        "custom_data" => vanilla_components::CustomData::from_nbt_value(tag)
763            .map(DataComponentPredicate::CustomData),
764        "damage" => parse_damage_predicate(tag).map(DataComponentPredicate::Damage),
765        "enchantments" => parse_enchantment_predicates(tag).map(|predicates| {
766            DataComponentPredicate::Enchantments {
767                stored: false,
768                predicates,
769            }
770        }),
771        "stored_enchantments" => parse_enchantment_predicates(tag).map(|predicates| {
772            DataComponentPredicate::Enchantments {
773                stored: true,
774                predicates,
775            }
776        }),
777        "attribute_modifiers" => {
778            parse_attribute_modifiers_predicate(tag).map(DataComponentPredicate::AttributeModifiers)
779        }
780        "potion_contents" => {
781            registered::PotionsPredicate::from_nbt_value(tag).map(DataComponentPredicate::Potions)
782        }
783        "container" => registered::ContainerPredicate::from_nbt_value(tag)
784            .map(DataComponentPredicate::Container),
785        "bundle_contents" => {
786            registered::BundlePredicate::from_nbt_value(tag).map(DataComponentPredicate::Bundle)
787        }
788        "firework_explosion" => registered::FireworkExplosionPredicate::from_nbt_value(tag)
789            .map(DataComponentPredicate::FireworkExplosion),
790        "fireworks" => registered::FireworksPredicate::from_nbt_value(tag)
791            .map(DataComponentPredicate::Fireworks),
792        "writable_book_content" => registered::WritableBookPredicate::from_nbt_value(tag)
793            .map(DataComponentPredicate::WritableBook),
794        "written_book_content" => registered::WrittenBookPredicate::from_nbt_value(tag)
795            .map(DataComponentPredicate::WrittenBook),
796        "trim" => registered::TrimPredicate::from_nbt_value(tag).map(DataComponentPredicate::Trim),
797        "jukebox_playable" => registered::JukeboxPlayablePredicate::from_nbt_value(tag)
798            .map(DataComponentPredicate::JukeboxPlayable),
799        "villager/variant" => registered::VillagerTypePredicate::from_nbt_value(tag)
800            .map(DataComponentPredicate::VillagerType),
801        _ => None,
802    }
803}
804
805#[derive(Clone, Copy, Debug, PartialEq)]
806struct IntRange {
807    min: Option<i32>,
808    max: Option<i32>,
809}
810
811impl IntRange {
812    const ANY: Self = Self {
813        min: None,
814        max: None,
815    };
816
817    fn matches(self, value: i32) -> bool {
818        self.min.is_none_or(|minimum| value >= minimum)
819            && self.max.is_none_or(|maximum| value <= maximum)
820    }
821
822    fn matches_u32(self, value: u32) -> bool {
823        let value = i64::from(value);
824        self.min.is_none_or(|minimum| value >= i64::from(minimum))
825            && self.max.is_none_or(|maximum| value <= i64::from(maximum))
826    }
827
828    fn matches_usize(self, value: usize) -> bool {
829        let minimum_matches = self.min.is_none_or(|minimum| {
830            minimum <= 0 || usize::try_from(minimum).is_ok_and(|minimum| value >= minimum)
831        });
832        let maximum_matches = self
833            .max
834            .is_none_or(|maximum| usize::try_from(maximum).is_ok_and(|maximum| value <= maximum));
835        minimum_matches && maximum_matches
836    }
837
838    const fn is_any(self) -> bool {
839        self.min.is_none() && self.max.is_none()
840    }
841}
842
843fn parse_int_range(tag: &NbtTag) -> Option<IntRange> {
844    if let Some(value) = numeric_i32(tag) {
845        return Some(IntRange {
846            min: Some(value),
847            max: Some(value),
848        });
849    }
850    let NbtTag::Compound(compound) = tag else {
851        return None;
852    };
853    let min = parse_optional_field(compound, "min", numeric_i32).ok()?;
854    let max = parse_optional_field(compound, "max", numeric_i32).ok()?;
855    if min.zip(max).is_some_and(|(min, max)| min > max) {
856        return None;
857    }
858    Some(IntRange { min, max })
859}
860
861#[derive(Clone, Copy, Debug, PartialEq)]
862struct DamagePredicate {
863    durability: IntRange,
864    damage: IntRange,
865}
866
867impl DamagePredicate {
868    fn matches<S: ItemInstanceView + ?Sized>(self, stack: &S) -> bool {
869        let Some(damage) = stack.component(vanilla_components::DAMAGE).copied() else {
870            return false;
871        };
872        let maximum = stack
873            .component(vanilla_components::MAX_DAMAGE)
874            .copied()
875            .unwrap_or(0);
876        self.durability.matches(maximum - damage) && self.damage.matches(damage)
877    }
878}
879
880fn parse_damage_predicate(tag: &NbtTag) -> Option<DamagePredicate> {
881    let NbtTag::Compound(compound) = tag else {
882        return None;
883    };
884    let durability = parse_optional_field(compound, "durability", parse_int_range)
885        .ok()?
886        .unwrap_or(IntRange::ANY);
887    let damage = parse_optional_field(compound, "damage", parse_int_range)
888        .ok()?
889        .unwrap_or(IntRange::ANY);
890    Some(DamagePredicate { durability, damage })
891}
892
893#[derive(Clone, Debug, PartialEq)]
894struct EnchantmentPredicate {
895    enchantments: Option<Vec<EnchantmentRef>>,
896    levels: IntRange,
897}
898
899impl EnchantmentPredicate {
900    fn matches(&self, enchantments: &vanilla_components::ItemEnchantments) -> bool {
901        if let Some(expected) = &self.enchantments {
902            return expected.iter().any(|enchantment| {
903                let level = enchantments.get_level(&enchantment.key);
904                level != 0 && self.levels.matches_u32(level)
905            });
906        }
907        if !self.levels.is_any() {
908            return enchantments
909                .iter()
910                .any(|(_, level)| self.levels.matches_u32(*level));
911        }
912        !enchantments.is_empty()
913    }
914}
915
916fn parse_enchantment_predicates(tag: &NbtTag) -> Option<Vec<EnchantmentPredicate>> {
917    match tag {
918        NbtTag::List(NbtList::Empty) => Some(Vec::new()),
919        NbtTag::List(NbtList::Compound(compounds)) => {
920            compounds.iter().map(parse_enchantment_predicate).collect()
921        }
922        _ => None,
923    }
924}
925
926fn parse_enchantment_predicate(compound: &NbtCompound) -> Option<EnchantmentPredicate> {
927    let enchantments =
928        parse_optional_field(compound, "enchantments", parse_enchantment_holder_set).ok()?;
929    let levels = parse_optional_field(compound, "levels", parse_int_range)
930        .ok()?
931        .unwrap_or(IntRange::ANY);
932    Some(EnchantmentPredicate {
933        enchantments,
934        levels,
935    })
936}
937
938fn parse_enchantment_holder_set(tag: &NbtTag) -> Option<Vec<EnchantmentRef>> {
939    match tag {
940        NbtTag::String(value) => parse_enchantment_holder(&value.to_string()),
941        NbtTag::List(NbtList::Empty) => Some(Vec::new()),
942        NbtTag::List(NbtList::String(values)) => {
943            let mut enchantments = Vec::new();
944            for value in values {
945                enchantments.push(parse_enchantment_reference(&value.to_string())?);
946            }
947            Some(enchantments)
948        }
949        _ => None,
950    }
951}
952
953fn parse_enchantment_holder(value: &str) -> Option<Vec<EnchantmentRef>> {
954    if let Some(tag) = value.strip_prefix('#') {
955        let key = parse_identifier_with_default_namespace(tag)?;
956        return REGISTRY.enchantments.get_tag(&key);
957    }
958    parse_enchantment_reference(value).map(|enchantment| vec![enchantment])
959}
960
961fn parse_enchantment_reference(value: &str) -> Option<EnchantmentRef> {
962    let key = parse_identifier_with_default_namespace(value)?;
963    REGISTRY.enchantments.by_key(&key)
964}
965
966#[derive(Clone, Debug, PartialEq)]
967struct AttributeModifiersPredicate {
968    modifiers: Option<AttributeModifierCollectionPredicate>,
969}
970
971impl AttributeModifiersPredicate {
972    fn matches(&self, modifiers: &vanilla_components::ItemAttributeModifiers) -> bool {
973        self.modifiers
974            .as_ref()
975            .is_none_or(|predicate| predicate.matches(&modifiers.modifiers))
976    }
977}
978
979fn parse_attribute_modifiers_predicate(tag: &NbtTag) -> Option<AttributeModifiersPredicate> {
980    let NbtTag::Compound(compound) = tag else {
981        return None;
982    };
983    let modifiers =
984        parse_optional_field(compound, "modifiers", parse_attribute_modifier_collection).ok()?;
985    Some(AttributeModifiersPredicate { modifiers })
986}
987
988#[derive(Clone, Debug, PartialEq)]
989struct AttributeModifierCollectionPredicate {
990    contains: Vec<AttributeModifierEntryPredicate>,
991    counts: Vec<AttributeModifierCountPredicate>,
992    size: Option<IntRange>,
993}
994
995impl AttributeModifierCollectionPredicate {
996    fn matches(&self, modifiers: &[vanilla_components::ItemAttributeModifierEntry]) -> bool {
997        let mut matched = vec![false; self.contains.len()];
998        for modifier in modifiers {
999            for (matched, predicate) in matched.iter_mut().zip(&self.contains) {
1000                if !*matched && predicate.matches(modifier) {
1001                    *matched = true;
1002                }
1003            }
1004        }
1005        matched.into_iter().all(|matched| matched)
1006            && self.counts.iter().all(|predicate| {
1007                let count = modifiers
1008                    .iter()
1009                    .filter(|modifier| predicate.test.matches(modifier))
1010                    .count();
1011                predicate.count.matches_usize(count)
1012            })
1013            && self
1014                .size
1015                .is_none_or(|range| range.matches_usize(modifiers.len()))
1016    }
1017}
1018
1019fn parse_attribute_modifier_collection(
1020    tag: &NbtTag,
1021) -> Option<AttributeModifierCollectionPredicate> {
1022    let NbtTag::Compound(compound) = tag else {
1023        return None;
1024    };
1025    let contains = parse_optional_field(
1026        compound,
1027        "contains",
1028        parse_attribute_modifier_predicate_list,
1029    )
1030    .ok()?
1031    .unwrap_or_default();
1032    let counts = parse_optional_field(compound, "count", parse_attribute_modifier_count_list)
1033        .ok()?
1034        .unwrap_or_default();
1035    let size = parse_optional_field(compound, "size", parse_int_range).ok()?;
1036    Some(AttributeModifierCollectionPredicate {
1037        contains,
1038        counts,
1039        size,
1040    })
1041}
1042
1043fn parse_attribute_modifier_predicate_list(
1044    tag: &NbtTag,
1045) -> Option<Vec<AttributeModifierEntryPredicate>> {
1046    match tag {
1047        NbtTag::List(NbtList::Empty) => Some(Vec::new()),
1048        NbtTag::List(NbtList::Compound(compounds)) => compounds
1049            .iter()
1050            .map(parse_attribute_modifier_entry)
1051            .collect(),
1052        _ => None,
1053    }
1054}
1055
1056fn parse_attribute_modifier_count_list(
1057    tag: &NbtTag,
1058) -> Option<Vec<AttributeModifierCountPredicate>> {
1059    match tag {
1060        NbtTag::List(NbtList::Empty) => Some(Vec::new()),
1061        NbtTag::List(NbtList::Compound(compounds)) => compounds
1062            .iter()
1063            .map(parse_attribute_modifier_count)
1064            .collect(),
1065        _ => None,
1066    }
1067}
1068
1069#[derive(Clone, Debug, PartialEq)]
1070struct AttributeModifierCountPredicate {
1071    test: AttributeModifierEntryPredicate,
1072    count: IntRange,
1073}
1074
1075fn parse_attribute_modifier_count(
1076    compound: &NbtCompound,
1077) -> Option<AttributeModifierCountPredicate> {
1078    let NbtTag::Compound(test) = compound.get("test")? else {
1079        return None;
1080    };
1081    let count = parse_int_range(compound.get("count")?)?;
1082    Some(AttributeModifierCountPredicate {
1083        test: parse_attribute_modifier_entry(test)?,
1084        count,
1085    })
1086}
1087
1088#[derive(Clone, Debug, PartialEq)]
1089struct AttributeModifierEntryPredicate {
1090    attributes: Option<Vec<AttributeRef>>,
1091    id: Option<Identifier>,
1092    amount: DoubleRange,
1093    operation: Option<AttributeModifierOperation>,
1094    slot: Option<EquipmentSlotGroup>,
1095}
1096
1097impl AttributeModifierEntryPredicate {
1098    fn matches(&self, modifier: &vanilla_components::ItemAttributeModifierEntry) -> bool {
1099        self.attributes.as_ref().is_none_or(|attributes| {
1100            attributes
1101                .iter()
1102                .any(|attribute| attribute.key == modifier.attribute.key)
1103        }) && self.id.as_ref().is_none_or(|id| id == &modifier.id)
1104            && self.amount.matches(modifier.amount)
1105            && self
1106                .operation
1107                .is_none_or(|operation| operation == modifier.operation)
1108            && self.slot.is_none_or(|slot| slot == modifier.slot)
1109    }
1110}
1111
1112fn parse_attribute_modifier_entry(
1113    compound: &NbtCompound,
1114) -> Option<AttributeModifierEntryPredicate> {
1115    let attributes =
1116        parse_optional_field(compound, "attribute", parse_attribute_holder_set).ok()?;
1117    let id = parse_optional_field(compound, "id", parse_identifier_tag).ok()?;
1118    let amount = parse_optional_field(compound, "amount", parse_double_range)
1119        .ok()?
1120        .unwrap_or(DoubleRange::ANY);
1121    let operation =
1122        parse_optional_field(compound, "operation", parse_attribute_modifier_operation).ok()?;
1123    let slot = parse_optional_field(compound, "slot", parse_equipment_slot_group).ok()?;
1124    Some(AttributeModifierEntryPredicate {
1125        attributes,
1126        id,
1127        amount,
1128        operation,
1129        slot,
1130    })
1131}
1132
1133fn parse_attribute_holder_set(tag: &NbtTag) -> Option<Vec<AttributeRef>> {
1134    match tag {
1135        NbtTag::String(value) => parse_attribute_holder(&value.to_string()),
1136        NbtTag::List(NbtList::Empty) => Some(Vec::new()),
1137        NbtTag::List(NbtList::String(values)) => {
1138            let mut attributes = Vec::new();
1139            for value in values {
1140                attributes.extend(parse_attribute_holder(&value.to_string())?);
1141            }
1142            Some(attributes)
1143        }
1144        _ => None,
1145    }
1146}
1147
1148fn parse_attribute_holder(value: &str) -> Option<Vec<AttributeRef>> {
1149    if value.starts_with('#') {
1150        // TODO: Support attribute tags once Steel's attribute registry stores them.
1151        return None;
1152    }
1153    let key = parse_identifier_with_default_namespace(value)?;
1154    REGISTRY
1155        .attributes
1156        .by_key(&key)
1157        .map(|attribute| vec![attribute])
1158}
1159
1160fn parse_identifier_tag(tag: &NbtTag) -> Option<Identifier> {
1161    let NbtTag::String(value) = tag else {
1162        return None;
1163    };
1164    parse_identifier_with_default_namespace(&value.to_string())
1165}
1166
1167fn parse_attribute_modifier_operation(tag: &NbtTag) -> Option<AttributeModifierOperation> {
1168    let NbtTag::String(value) = tag else {
1169        return None;
1170    };
1171    AttributeModifierOperation::by_name(&value.to_string())
1172}
1173
1174fn parse_equipment_slot_group(tag: &NbtTag) -> Option<EquipmentSlotGroup> {
1175    let NbtTag::String(value) = tag else {
1176        return None;
1177    };
1178    let value = value.to_string();
1179    EquipmentSlotGroup::by_name(&value).filter(|slot| slot.name() == value)
1180}
1181
1182#[derive(Clone, Copy, Debug, PartialEq)]
1183struct DoubleRange {
1184    min: Option<f64>,
1185    max: Option<f64>,
1186}
1187
1188impl DoubleRange {
1189    const ANY: Self = Self {
1190        min: None,
1191        max: None,
1192    };
1193
1194    fn matches(self, value: f64) -> bool {
1195        self.min
1196            .is_none_or(|minimum| minimum.partial_cmp(&value) != Some(Ordering::Greater))
1197            && self
1198                .max
1199                .is_none_or(|maximum| maximum.partial_cmp(&value) != Some(Ordering::Less))
1200    }
1201}
1202
1203fn parse_double_range(tag: &NbtTag) -> Option<DoubleRange> {
1204    if let Some(value) = numeric_f64(tag) {
1205        return Some(DoubleRange {
1206            min: Some(value),
1207            max: Some(value),
1208        });
1209    }
1210    let NbtTag::Compound(compound) = tag else {
1211        return None;
1212    };
1213    let min = parse_optional_field(compound, "min", numeric_f64).ok()?;
1214    let max = parse_optional_field(compound, "max", numeric_f64).ok()?;
1215    if min.zip(max).is_some_and(|(min, max)| min > max) {
1216        return None;
1217    }
1218    Some(DoubleRange { min, max })
1219}
1220
1221fn numeric_f64(tag: &NbtTag) -> Option<f64> {
1222    match tag {
1223        NbtTag::Byte(value) => Some(f64::from(*value)),
1224        NbtTag::Short(value) => Some(f64::from(*value)),
1225        NbtTag::Int(value) => Some(f64::from(*value)),
1226        NbtTag::Long(value) => Some(*value as f64),
1227        NbtTag::Float(value) => Some(f64::from(*value)),
1228        NbtTag::Double(value) => Some(*value),
1229        _ => None,
1230    }
1231}
1232
1233fn parse_optional_field<T>(
1234    compound: &NbtCompound,
1235    key: &str,
1236    parser: impl FnOnce(&NbtTag) -> Option<T>,
1237) -> Result<Option<T>, ()> {
1238    match compound.get(key) {
1239        Some(tag) => parser(tag).map(Some).ok_or(()),
1240        None => Ok(None),
1241    }
1242}
1243
1244fn parse_identifier_with_default_namespace(value: &str) -> Option<Identifier> {
1245    let (namespace, path) = value.split_once(':').map_or(
1246        (Identifier::VANILLA_NAMESPACE, value),
1247        |(namespace, path)| {
1248            if namespace.is_empty() {
1249                (Identifier::VANILLA_NAMESPACE, path)
1250            } else {
1251                (namespace, path)
1252            }
1253        },
1254    );
1255    (!namespace.is_empty() && !path.is_empty() && Identifier::validate(namespace, path))
1256        .then(|| Identifier::new(namespace.to_owned(), path.to_owned()))
1257}
1258
1259pub(super) fn suggest_item_predicate(builder: &mut SuggestionsBuilder<'_>) {
1260    let input = builder.remaining();
1261    let Some(component_start) = input.find('[') else {
1262        suggest_item_targets(input, builder);
1263        if valid_target(input) {
1264            builder.suggest(format!("{input}["));
1265        }
1266        return;
1267    };
1268    if !valid_target(input[..component_start].trim_end()) {
1269        return;
1270    }
1271
1272    let Some((current_start, current)) = current_term(&input[component_start + 1..]) else {
1273        return;
1274    };
1275    if current.contains(['=', '~']) {
1276        return;
1277    }
1278    let trimmed = current.trim_start();
1279    let whitespace = current.len() - trimmed.len();
1280    let prefix_end = component_start + 1 + current_start + whitespace;
1281    let prefix = &input[..prefix_end];
1282    let resource_prefix = trimmed.strip_prefix('!').unwrap_or(trimmed);
1283
1284    for entry in
1285        (0..REGISTRY.data_components.len()).filter_map(|id| REGISTRY.data_components.by_id(id))
1286    {
1287        if entry.is_persistent() && resource_matches(resource_prefix, &entry.key) {
1288            builder.suggest(format!("{prefix}{}", entry.key));
1289        }
1290    }
1291    let count = Identifier::vanilla_static("count");
1292    if resource_matches(resource_prefix, &count) {
1293        builder.suggest(format!("{prefix}{count}"));
1294    }
1295    for path in VANILLA_DATA_COMPONENT_PREDICATE_KEYS {
1296        let key = Identifier::vanilla_static(path);
1297        if resource_matches(resource_prefix, &key) {
1298            builder.suggest(format!("{prefix}{key}"));
1299        }
1300    }
1301}
1302
1303fn suggest_item_targets(input: &str, builder: &mut SuggestionsBuilder<'_>) {
1304    if let Some(prefix) = input.strip_prefix('#') {
1305        for key in REGISTRY.items.tag_keys() {
1306            if resource_matches(prefix, key) {
1307                builder.suggest(format!("#{key}"));
1308            }
1309        }
1310        return;
1311    }
1312    for (_, item) in REGISTRY.items.iter() {
1313        if resource_matches(input, &item.key) {
1314            builder.suggest(item.key.to_string());
1315        }
1316    }
1317    if "*".starts_with(input) {
1318        builder.suggest("*");
1319    }
1320}
1321
1322fn valid_target(input: &str) -> bool {
1323    if input == "*" {
1324        return true;
1325    }
1326    if let Some(tag) = input.strip_prefix('#') {
1327        return parse_identifier_text(tag)
1328            .is_some_and(|tag| REGISTRY.items.get_tag(&tag).is_some());
1329    }
1330    parse_identifier_text(input).is_some_and(|key| REGISTRY.items.by_key(&key).is_some())
1331}
1332
1333fn parse_identifier_text(input: &str) -> Option<Identifier> {
1334    let mut reader = StringReader::new(input);
1335    let key = parse_identifier(&mut reader).ok()?;
1336    (!reader.can_read()).then_some(key)
1337}
1338
1339fn resource_matches(pattern: &str, key: &Identifier) -> bool {
1340    let pattern = pattern.strip_prefix("minecraft:").unwrap_or(pattern);
1341    if pattern.contains(':') {
1342        return matches_substring(pattern, &key.to_string());
1343    }
1344    matches_substring(pattern, key.namespace.as_ref())
1345        || matches_substring(pattern, key.path.as_ref())
1346}
1347
1348fn current_term(conditions: &str) -> Option<(usize, &str)> {
1349    let mut depth = 0usize;
1350    let mut quote = None;
1351    let mut escaped = false;
1352    let mut start = 0usize;
1353
1354    for (index, character) in conditions.char_indices() {
1355        if let Some(terminator) = quote {
1356            if escaped {
1357                escaped = false;
1358            } else if character == '\\' {
1359                escaped = true;
1360            } else if character == terminator {
1361                quote = None;
1362            }
1363            continue;
1364        }
1365        match character {
1366            '"' | '\'' => quote = Some(character),
1367            '{' | '[' => depth += 1,
1368            '}' | ']' if depth > 0 => depth -= 1,
1369            ']' => return None,
1370            ',' | '|' if depth == 0 => start = index + character.len_utf8(),
1371            '!' if depth == 0 && conditions[start..index].trim().is_empty() => {
1372                start = index + character.len_utf8();
1373            }
1374            _ => {}
1375        }
1376    }
1377    Some((start, &conditions[start..]))
1378}