Skip to main content

yog_data/
schema.rs

1//! Schemas: a format is a Rust type implementing [`Schema`] (usually
2//! `#[derive(Schema)]`). One schema reads and writes the format, checks it
3//! with the path of every problem, collects its references for the build to
4//! check, and describes itself ([`Shape`]) for Yog-IDLE's editors and the
5//! docs.
6
7use std::collections::{BTreeMap, HashMap};
8use std::fmt;
9use std::marker::PhantomData;
10
11use crate::text::{self, Doc, LineCol};
12use crate::typed;
13use crate::borrowed::{MapRef, ValueRef};
14use crate::value::{Map, Path, Ref, Res, Seg, Value};
15
16/// A Yog Data format.
17pub trait Schema: Sized {
18    /// What the format looks like.
19    fn shape() -> Shape;
20
21    /// Reads `v`. Problems go to `cx` at its current path; `None` when the
22    /// value couldn't be read (the problem is in `cx`).
23    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self>;
24
25    /// The value, as the format writes it.
26    fn write(&self) -> Value;
27
28    /// The value of a field that isn't there — `None`: the field is required.
29    fn absent() -> Option<Self> {
30        None
31    }
32
33    /// Whether a struct leaves this field out when writing it.
34    fn skip(&self) -> bool {
35        false
36    }
37}
38
39// ── Shapes ──────────────────────────────────────────────────────────────────
40
41/// A shape, built on demand — so shapes of recursive types are fine.
42pub type ShapeFn = fn() -> Shape;
43
44/// What a format looks like: drives Yog-IDLE's property grids and pickers,
45/// completion, hover docs and the docs' field tables.
46#[derive(Debug, Clone)]
47pub enum Shape {
48    /// Any value.
49    Any,
50    Bool,
51    Int {
52        min: i64,
53        max: i64,
54    },
55    Float,
56    /// Plain text.
57    Str,
58    /// Yog text, a `@Set.key` resource, or `@kind:id` (that content's name).
59    Text,
60    /// An image source or a `@Set.key` image resource.
61    Image,
62    /// `#RRGGBB`/`#AARRGGBB` or a `@Set.key` color resource.
63    Color,
64    /// Bytes as they are.
65    Bytes,
66    /// A mod resource, `@Set.key`.
67    Res,
68    /// Content of a kind (`""`: any kind, written `@kind:id`).
69    Ref(&'static [&'static str]),
70    /// A tag of content of a kind.
71    Tag(&'static [&'static str]),
72    /// Content of a kind or a tag of it.
73    RefOrTag(&'static [&'static str]),
74    /// Content of a kind (`@item:…`) or an image source.
75    RefOrImage(&'static [&'static str]),
76    List(ShapeFn),
77    /// An object of any keys, every value this shape.
78    Map(ShapeFn),
79    /// A value or null.
80    Optional(ShapeFn),
81    Struct(StructShape),
82    Enum(EnumShape),
83}
84
85#[derive(Debug, Clone)]
86pub struct StructShape {
87    pub name: &'static str,
88    pub doc: &'static str,
89    pub fields: Vec<Field>,
90}
91
92#[derive(Debug, Clone)]
93pub struct Field {
94    pub name: &'static str,
95    pub doc: &'static str,
96    pub shape: ShapeFn,
97    /// Must be written (no default).
98    pub required: bool,
99    /// The numbers it holds must be in `min..=max` (`#[data(range = "1..=99")]`).
100    pub range: Option<(f64, f64)>,
101}
102
103#[derive(Debug, Clone)]
104pub struct EnumShape {
105    pub name: &'static str,
106    pub doc: &'static str,
107    /// The key naming the variant (`"type"`), `None` for an enum of names.
108    pub tag: Option<&'static str>,
109    pub variants: Vec<Variant>,
110}
111
112#[derive(Debug, Clone)]
113pub struct Variant {
114    pub name: &'static str,
115    pub doc: &'static str,
116    /// The variant's fields (none for a name).
117    pub fields: Vec<Field>,
118}
119
120impl Shape {
121    /// The shape as Yog Data, for tools in other languages (Yog-IDLE).
122    /// Structs and enums are written once in `defs` and referred to by name.
123    pub fn describe(&self) -> Value {
124        let mut defs = Map::new();
125        let root = self.describe_into(&mut defs);
126        let mut m = Map::new();
127        m.insert("root", root);
128        m.insert("defs", Value::Map(defs));
129        Value::Map(m)
130    }
131
132    fn describe_into(&self, defs: &mut Map) -> Value {
133        let kind = |k: &str| {
134            let mut m = Map::new();
135            m.insert("kind", Value::from(k));
136            m
137        };
138        let fields = |fs: &[Field], defs: &mut Map| -> Value {
139            Value::List(
140                fs.iter()
141                    .map(|f| {
142                        let mut m = Map::new();
143                        m.insert("name", Value::from(f.name));
144                        if !f.doc.is_empty() {
145                            m.insert("doc", Value::from(f.doc));
146                        }
147                        m.insert("shape", (f.shape)().describe_into(defs));
148                        m.insert("required", Value::Bool(f.required));
149                        if let Some((lo, hi)) = f.range {
150                            m.insert(
151                                "range",
152                                Value::List(vec![Value::Float(lo), Value::Float(hi)]),
153                            );
154                        }
155                        Value::Map(m)
156                    })
157                    .collect(),
158            )
159        };
160        let m = match self {
161            Shape::Any => kind("any"),
162            Shape::Bool => kind("bool"),
163            Shape::Int { min, max } => {
164                let mut m = kind("int");
165                m.insert("min", Value::Int(*min));
166                m.insert("max", Value::Int(*max));
167                m
168            }
169            Shape::Float => kind("float"),
170            Shape::Str => kind("string"),
171            Shape::Text => kind("text"),
172            Shape::Image => kind("image"),
173            Shape::Color => kind("color"),
174            Shape::Bytes => kind("bytes"),
175            Shape::Res => kind("res"),
176            Shape::Ref(k) | Shape::Tag(k) | Shape::RefOrTag(k) | Shape::RefOrImage(k) => {
177                let mut m = kind(match self {
178                    Shape::Ref(_) => "ref",
179                    Shape::Tag(_) => "tag",
180                    Shape::RefOrTag(_) => "ref_or_tag",
181                    _ => "ref_or_image",
182                });
183                m.insert(
184                    "of",
185                    Value::List(k.iter().map(|k| Value::from(*k)).collect()),
186                );
187                m
188            }
189            Shape::List(s) | Shape::Map(s) | Shape::Optional(s) => {
190                let mut m = kind(match self {
191                    Shape::List(_) => "list",
192                    Shape::Map(_) => "map",
193                    _ => "optional",
194                });
195                m.insert("of", s().describe_into(defs));
196                m
197            }
198            Shape::Struct(s) => {
199                if defs.get(s.name).is_none() {
200                    // Placeholder first: a recursive type finds itself defined.
201                    defs.insert(s.name, Value::Null);
202                    let mut d = kind("struct");
203                    if !s.doc.is_empty() {
204                        d.insert("doc", Value::from(s.doc));
205                    }
206                    d.insert("fields", fields(&s.fields, defs));
207                    defs.insert(s.name, Value::Map(d));
208                }
209                let mut m = kind("def");
210                m.insert("name", Value::from(s.name));
211                m
212            }
213            Shape::Enum(e) => {
214                if defs.get(e.name).is_none() {
215                    defs.insert(e.name, Value::Null);
216                    let mut d = kind("enum");
217                    if !e.doc.is_empty() {
218                        d.insert("doc", Value::from(e.doc));
219                    }
220                    if let Some(tag) = e.tag {
221                        d.insert("tag", Value::from(tag));
222                    }
223                    let variants = e
224                        .variants
225                        .iter()
226                        .map(|v| {
227                            let mut m = Map::new();
228                            m.insert("name", Value::from(v.name));
229                            if !v.doc.is_empty() {
230                                m.insert("doc", Value::from(v.doc));
231                            }
232                            if e.tag.is_some() {
233                                m.insert("fields", fields(&v.fields, defs));
234                            }
235                            Value::Map(m)
236                        })
237                        .collect();
238                    d.insert("variants", Value::List(variants));
239                    defs.insert(e.name, Value::Map(d));
240                }
241                let mut m = kind("def");
242                m.insert("name", Value::from(e.name));
243                m
244            }
245        };
246        Value::Map(m)
247    }
248}
249
250// ── Reading ─────────────────────────────────────────────────────────────────
251
252/// A problem found reading a value.
253#[derive(Debug, Clone, PartialEq, Eq)]
254pub struct Problem {
255    pub path: Path,
256    pub message: String,
257    /// About the key at `path` rather than its value (an unknown field).
258    pub at_key: bool,
259}
260
261/// Where a reference was found.
262#[derive(Debug, Clone, PartialEq, Eq)]
263pub struct Found<T> {
264    pub value: T,
265    pub path: Path,
266}
267
268/// The state of reading a value: where it is, the problems so far, and the
269/// references met (for the build to check against the content there is).
270#[derive(Default)]
271pub struct Reader<'r> {
272    /// Where it is, as steps: a field's name stays `&'static` until a
273    /// problem or a reference needs the whole [`Path`].
274    path: Vec<Step>,
275    /// Don't keep the references, tags and resources met (who only wants the value).
276    value_only: bool,
277    /// What the tool makes of references and texts (none: kept as written).
278    resolver: Option<&'r mut dyn Resolve>,
279    pub problems: Vec<Problem>,
280    pub refs: Vec<Found<Ref>>,
281    pub tags: Vec<Found<Ref>>,
282    pub resources: Vec<Found<Res>>,
283    /// Keys a struct being read doesn't report as unknown (an enum's tag).
284    allowed: Vec<&'static str>,
285}
286
287/// A step of [`Reader`]'s path.
288enum Step {
289    Seg(Seg),
290    Field(&'static str),
291}
292
293/// Source-only keys every object may have: `$…`, `_…`, `comment`.
294pub fn is_metadata_key(key: &str) -> bool {
295    key.starts_with('$') || key.starts_with('_') || key == "comment"
296}
297
298/// What a tool makes of the references and texts it reads: `yog build`
299/// checks each reference against the content there is and turns texts
300/// into translation keys; the IDE shows them.
301pub trait Resolve {
302    /// A reference: the id to keep, or why it's wrong.
303    fn reference(&mut self, r: &Ref, _path: &Path) -> Result<String, String> {
304        Ok(r.id.clone())
305    }
306
307    /// A text: what to keep (its words, a translation key, ...), or why it's wrong.
308    fn text(&mut self, t: &Text, _path: &Path) -> Result<String, String> {
309        Ok(match t {
310            Text::Literal(s) => s.clone(),
311            Text::Res(r) => format!("@{}.{}", r.set, r.key),
312            Text::NameOf(r) => format!("@{}:{}", r.kind, r.id),
313            Text::Lang(k) => format!("@lang:{k}"),
314        })
315    }
316
317    /// The words of a `.yres` text, if known (to check what's in them).
318    fn words(&mut self, _r: &Res) -> Option<String> {
319        None
320    }
321}
322
323impl<'r> Reader<'r> {
324    pub fn new() -> Self {
325        Self::default()
326    }
327
328    /// A reader handing references and texts to `resolver`.
329    pub fn with_resolver(resolver: &'r mut dyn Resolve) -> Self {
330        Self {
331            resolver: Some(resolver),
332            ..Self::default()
333        }
334    }
335
336    /// `r` resolved (a problem if it's wrong); its id without a resolver.
337    pub fn resolve_ref(&mut self, r: &Ref) -> Option<String> {
338        let result = match &mut self.resolver {
339            Some(res) => res.reference(r, &Self::built(&self.path)),
340            None => Ok(r.id.clone()),
341        };
342        result.map_err(|e| self.problem(e)).ok()
343    }
344
345    /// `t` resolved (a problem if it's wrong).
346    pub fn resolve_text(&mut self, t: &Text) -> Option<String> {
347        struct Keep;
348        impl Resolve for Keep {}
349        let result = match &mut self.resolver {
350            Some(res) => res.text(t, &Self::built(&self.path)),
351            None => Keep.text(t, &Path::root()),
352        };
353        result.map_err(|e| self.problem(e)).ok()
354    }
355
356    /// [`Reader::resolve_text`], taking `t`: without a resolver its words
357    /// move into the result instead of being copied.
358    pub fn take_text(&mut self, t: Text) -> Option<String> {
359        match (t, self.resolver.is_some()) {
360            (Text::Literal(s), false) => Some(s),
361            (t, _) => self.resolve_text(&t),
362        }
363    }
364
365    /// [`Reader::resolve_ref`], taking `r`: without a resolver the id moves.
366    pub fn take_ref(&mut self, r: Ref) -> Option<String> {
367        if self.resolver.is_none() {
368            return Some(r.id);
369        }
370        self.resolve_ref(&r)
371    }
372
373    /// Whether references are kept for the caller ([`read_value`] doesn't).
374    pub fn keeps_found(&self) -> bool {
375        !self.value_only
376    }
377
378    /// Whether references and texts go through a resolver (else kept as written).
379    pub fn resolves(&self) -> bool {
380        self.resolver.is_some()
381    }
382
383    /// The words of `.yres` text `r`, if the resolver knows them.
384    pub fn words(&mut self, r: &Res) -> Option<String> {
385        self.resolver.as_mut()?.words(r)
386    }
387
388    /// Where the reader is.
389    pub fn path(&self) -> Path {
390        Self::built(&self.path)
391    }
392
393    fn built(steps: &[Step]) -> Path {
394        let mut p = Path::root();
395        for s in steps {
396            p.push(match s {
397                Step::Seg(s) => s.clone(),
398                Step::Field(k) => Seg::Key((*k).to_string()),
399            });
400        }
401        p
402    }
403
404    /// Reports a problem with the value being read.
405    pub fn problem(&mut self, message: impl Into<String>) {
406        self.problems.push(Problem {
407            path: self.path(),
408            message: message.into(),
409            at_key: false,
410        });
411    }
412
413    /// "expected X, found Y".
414    pub fn expected(&mut self, what: &str, found: &ValueRef) {
415        self.problem(format!("expected {what}, found {}", found_text(found)));
416    }
417
418    /// Runs `f` one step deeper.
419    pub fn at<T>(&mut self, seg: Seg, f: impl FnOnce(&mut Self) -> T) -> T {
420        self.path.push(Step::Seg(seg));
421        let r = f(self);
422        self.path.pop();
423        r
424    }
425
426    /// Runs `f` in field `key` (no allocation for the step).
427    fn in_field<T>(&mut self, key: &'static str, f: impl FnOnce(&mut Self) -> T) -> T {
428        self.path.push(Step::Field(key));
429        let r = f(self);
430        self.path.pop();
431        r
432    }
433
434    pub fn note_ref(&mut self, r: Ref) {
435        if self.value_only {
436            return;
437        }
438        let path = self.path();
439        self.refs.push(Found { value: r, path });
440    }
441
442    pub fn note_tag(&mut self, r: Ref) {
443        if self.value_only {
444            return;
445        }
446        let path = self.path();
447        self.tags.push(Found { value: r, path });
448    }
449
450    pub fn note_res(&mut self, r: Res) {
451        if self.value_only {
452            return;
453        }
454        let path = self.path();
455        self.resources.push(Found { value: r, path });
456    }
457
458    /// `v` as an object, or a problem.
459    pub fn map<'v>(&mut self, v: &ValueRef<'v>) -> Option<MapRef<'v>> {
460        match v {
461            ValueRef::Map(m) => Some(*m),
462            other => {
463                self.expected("an object", other);
464                None
465            }
466        }
467    }
468
469    /// Reports keys of `map` that aren't `known` (nor metadata, nor allowed by an enclosing enum).
470    pub fn unknown_keys(&mut self, map: MapRef, known: &[&str]) {
471        for key in map.keys() {
472            if known.contains(&key) || is_metadata_key(key) || self.allowed.contains(&key) {
473                continue;
474            }
475            let hint = closest(key, known)
476                .map(|k| format!(" — did you mean `{k}`?"))
477                .unwrap_or_else(|| {
478                    if known.is_empty() {
479                        String::new()
480                    } else {
481                        format!(
482                            " (expected {})",
483                            known
484                                .iter()
485                                .map(|k| format!("`{k}`"))
486                                .collect::<Vec<_>>()
487                                .join(", ")
488                        )
489                    }
490                });
491            self.problems.push(Problem {
492                path: self.path().key(key),
493                message: format!("unknown field `{key}`{hint}"),
494                at_key: true,
495            });
496        }
497    }
498
499    /// Reports a name that isn't one of `known` (an enum's variant).
500    pub fn unknown_name(&mut self, name: &str, known: &[&str]) {
501        let hint = match closest(name, known) {
502            Some(k) => format!(" — did you mean `{k}`?"),
503            None => format!(
504                " (expected {})",
505                known
506                    .iter()
507                    .map(|k| format!("`{k}`"))
508                    .collect::<Vec<_>>()
509                    .join(", ")
510            ),
511        };
512        self.problem(format!("unknown `{name}`{hint}"));
513    }
514
515    /// Field `key` of `map`: read, or its absent value, or a problem.
516    pub fn field<T: Schema>(&mut self, map: MapRef, key: &'static str) -> Option<T> {
517        match map.get(key) {
518            Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
519            None => T::absent().or_else(|| {
520                self.problem(format!("missing `{key}`"));
521                None
522            }),
523        }
524    }
525
526    /// Field `key` of `map`, `default()` when it isn't there.
527    pub fn field_or<T: Schema>(
528        &mut self,
529        map: MapRef,
530        key: &'static str,
531        default: impl FnOnce() -> T,
532    ) -> Option<T> {
533        match map.get(key) {
534            Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
535            None => Some(default()),
536        }
537    }
538
539    /// Field `key` of `map` through codec `W`.
540    pub fn field_with<W: crate::with::With<F>, F>(
541        &mut self,
542        map: MapRef,
543        key: &'static str,
544    ) -> Option<F> {
545        match map.get(key) {
546            Some(v) => self.in_field(key, |cx| W::read(v, cx)),
547            None => W::absent().or_else(|| {
548                self.problem(format!("missing `{key}`"));
549                None
550            }),
551        }
552    }
553
554    /// Field `key` of `map` through codec `W`, `default()` when it isn't there.
555    pub fn field_or_with<W: crate::with::With<F>, F>(
556        &mut self,
557        map: MapRef,
558        key: &'static str,
559        default: impl FnOnce() -> F,
560    ) -> Option<F> {
561        match map.get(key) {
562            Some(v) => self.in_field(key, |cx| W::read(v, cx)),
563            None => Some(default()),
564        }
565    }
566
567    /// Reads `v` as `T` with `key` allowed in it — the tag of the enum variant `T` is.
568    pub fn with_allowed<T>(&mut self, key: &'static str, f: impl FnOnce(&mut Self) -> T) -> T {
569        self.allowed.push(key);
570        let r = f(self);
571        self.allowed.pop();
572        r
573    }
574
575    /// Reads with every one of `keys` allowed (the fields of a struct read
576    /// from the same object — see [`read_both`]).
577    pub fn with_allowed_all<T>(
578        &mut self,
579        keys: &[&'static str],
580        f: impl FnOnce(&mut Self) -> T,
581    ) -> T {
582        let before = self.allowed.len();
583        self.allowed.extend_from_slice(keys);
584        let r = f(self);
585        self.allowed.truncate(before);
586        r
587    }
588
589    /// Reads a struct's own keys with no allowance inherited from outside.
590    pub fn own_keys<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
591        let saved = std::mem::take(&mut self.allowed);
592        let r = f(self);
593        self.allowed = saved;
594        r
595    }
596}
597
598fn found_text(v: &ValueRef) -> String {
599    match v {
600        ValueRef::Str(s) if s.chars().count() <= 40 => format!("{s:?}"),
601        ValueRef::Int(n) => n.to_string(),
602        ValueRef::Float(f) => f.to_string(),
603        ValueRef::Bool(b) => b.to_string(),
604        other => other.kind_name().to_string(),
605    }
606}
607
608/// The `known` name closest to `key`, if it's close enough to be a typo
609/// (`txt` → `text`, `img` → `image`, `colr` → `color`).
610fn closest<'k>(key: &str, known: &[&'k str]) -> Option<&'k str> {
611    let lower = key.to_ascii_lowercase();
612    known
613        .iter()
614        .map(|k| (edit_distance(&lower, k), *k))
615        .filter(|(d, k)| *d <= if k.len() <= 3 { 1 } else { k.len() / 2 })
616        .min_by_key(|(d, _)| *d)
617        .map(|(_, k)| k)
618}
619
620fn edit_distance(a: &str, b: &str) -> usize {
621    let b: Vec<char> = b.chars().collect();
622    let mut prev: Vec<usize> = (0..=b.len()).collect();
623    for (i, ca) in a.chars().enumerate() {
624        let mut cur = vec![i + 1; b.len() + 1];
625        for (j, cb) in b.iter().enumerate() {
626            cur[j + 1] = (prev[j] + usize::from(ca != *cb))
627                .min(prev[j + 1] + 1)
628                .min(cur[j] + 1);
629        }
630        prev = cur;
631    }
632    prev[b.len()]
633}
634
635/// Problems found reading a document, with where each is.
636#[derive(Debug, Clone, PartialEq, Eq)]
637pub struct Errors(pub Vec<Located>);
638
639/// A problem and its place in the source.
640#[derive(Debug, Clone, PartialEq, Eq)]
641pub struct Located {
642    pub at: Option<LineCol>,
643    pub path: Path,
644    pub message: String,
645}
646
647impl fmt::Display for Located {
648    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
649        if let Some(at) = self.at {
650            write!(f, "{at}: ")?;
651        }
652        if !self.path.is_root() {
653            write!(f, "{}: ", self.path)?;
654        }
655        f.write_str(&self.message)
656    }
657}
658
659impl fmt::Display for Errors {
660    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
661        for (i, e) in self.0.iter().enumerate() {
662            if i > 0 {
663                f.write_str("\n")?;
664            }
665            write!(f, "{e}")?;
666        }
667        Ok(())
668    }
669}
670
671impl std::error::Error for Errors {}
672
673/// What reading a document gives: the value and the references in it.
674#[derive(Debug)]
675pub struct Read<T> {
676    pub value: T,
677    pub refs: Vec<Found<Ref>>,
678    pub tags: Vec<Found<Ref>>,
679    pub resources: Vec<Found<Res>>,
680}
681
682/// The numbers a value holds — what `#[data(range = …)]` checks.
683pub trait Numbers {
684    fn numbers(&self, out: &mut Vec<f64>);
685}
686
687macro_rules! numbers {
688    ($($t:ty),*) => {$(
689        impl Numbers for $t {
690            fn numbers(&self, out: &mut Vec<f64>) {
691                out.push(*self as f64);
692            }
693        }
694    )*};
695}
696numbers!(i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64);
697
698impl<T: Numbers> Numbers for Option<T> {
699    fn numbers(&self, out: &mut Vec<f64>) {
700        if let Some(x) = self {
701            x.numbers(out);
702        }
703    }
704}
705
706impl<T: Numbers> Numbers for Vec<T> {
707    fn numbers(&self, out: &mut Vec<f64>) {
708        self.iter().for_each(|x| x.numbers(out));
709    }
710}
711
712impl<T: Numbers, const N: usize> Numbers for [T; N] {
713    fn numbers(&self, out: &mut Vec<f64>) {
714        self.iter().for_each(|x| x.numbers(out));
715    }
716}
717
718impl Reader<'_> {
719    /// `value` (field `key`) checked against `lo..=hi`; `None` (a problem) if it's out.
720    pub fn in_range<T: Numbers>(
721        &mut self,
722        key: &str,
723        value: Option<T>,
724        lo: f64,
725        hi: f64,
726    ) -> Option<T> {
727        let v = value?;
728        let mut ns = Vec::new();
729        v.numbers(&mut ns);
730        match ns.into_iter().find(|n| !(lo..=hi).contains(n)) {
731            None => Some(v),
732            Some(n) => {
733                let show = |x: f64| {
734                    if x.fract() == 0.0 {
735                        format!("{}", x as i64)
736                    } else {
737                        x.to_string()
738                    }
739                };
740                let bad = show(n);
741                self.at(Seg::Key(key.to_string()), |cx| {
742                    cx.problem(format!(
743                        "{bad} is out of range ({}..={})",
744                        show(lo),
745                        show(hi)
746                    ))
747                });
748                None
749            }
750        }
751    }
752}
753
754/// The field names of a struct's shape (none for anything else).
755pub fn field_names(shape: &Shape) -> Vec<&'static str> {
756    match shape {
757        Shape::Struct(s) => s.fields.iter().map(|f| f.name).collect(),
758        _ => Vec::new(),
759    }
760}
761
762/// `A` and `B` read from the same object — each allowing the other's
763/// fields, like serde's `flatten`: a recipe's common fields and its kind's.
764pub fn read_both<A: Schema, B: Schema>(
765    v: &ValueRef,
766    cx: &mut Reader,
767    also: &[&'static str],
768) -> Option<(A, B)> {
769    let (an, bn) = (field_names(&A::shape()), field_names(&B::shape()));
770    let mut b_keys = bn.clone();
771    b_keys.extend_from_slice(also);
772    let mut a_keys = an;
773    a_keys.extend_from_slice(also);
774    let a = cx.with_allowed_all(&b_keys, |cx| A::read(v, cx));
775    let b = cx.with_allowed_all(&a_keys, |cx| B::read(v, cx));
776    Some((a?, b?))
777}
778
779/// Reads `v` as `T`: the value and its references, or every problem.
780pub fn from_value<T: Schema>(v: &Value) -> Result<Read<T>, Vec<Problem>> {
781    let arena = bumpalo::Bump::new();
782    read_with(&v.borrow(&arena), Reader::new())
783}
784
785/// Reads `v` as `T`, nothing else: what the game wants of content (the
786/// references met aren't kept).
787pub fn read_value<T: Schema>(v: &Value) -> Result<T, Vec<Problem>> {
788    let arena = bumpalo::Bump::new();
789    read_ref_value(&v.borrow(&arena))
790}
791
792/// [`read_value`] of a borrowed value.
793pub fn read_ref_value<T: Schema>(v: &ValueRef) -> Result<T, Vec<Problem>> {
794    let reader = Reader { value_only: true, ..Reader::new() };
795    read_with(v, reader).map(|r| r.value)
796}
797
798/// Reads the binary form as `T`, borrowed all the way: no string is
799/// copied but into `T` itself — content as the runtime takes it.
800pub fn read_bytes<T: Schema>(bytes: &[u8]) -> Result<T, Vec<Problem>> {
801    let arena = bumpalo::Bump::new();
802    let v = crate::binary::decode_ref(bytes, &arena).map_err(|e| {
803        vec![Problem { path: Path::root(), message: e.to_string(), at_key: false }]
804    })?;
805    read_ref_value(&v)
806}
807
808/// `from_value`, references and texts going through `resolver`.
809pub fn from_value_with<T: Schema>(
810    v: &Value,
811    resolver: &mut dyn Resolve,
812) -> Result<Read<T>, Vec<Problem>> {
813    let arena = bumpalo::Bump::new();
814    read_with(&v.borrow(&arena), Reader::with_resolver(resolver))
815}
816
817fn read_with<T: Schema>(v: &ValueRef, mut cx: Reader<'_>) -> Result<Read<T>, Vec<Problem>> {
818    let value = T::read(v, &mut cx);
819    match value {
820        Some(value) if cx.problems.is_empty() => Ok(Read {
821            value,
822            refs: cx.refs,
823            tags: cx.tags,
824            resources: cx.resources,
825        }),
826        _ => Err(cx.problems),
827    }
828}
829
830/// Parses `text` and reads it as `T`; problems come with their line and column.
831pub fn from_text<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
832    let doc = text::parse(text).map_err(|e| {
833        Errors(vec![Located {
834            at: Some(e.at),
835            path: Path::root(),
836            message: e.message,
837        }])
838    })?;
839    from_doc(text, &doc)
840}
841
842/// Parses TOML `text` and reads it as `T`.
843#[cfg(feature = "toml")]
844pub fn from_toml<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
845    let doc = crate::toml::parse(text).map_err(|e| {
846        Errors(vec![Located {
847            at: Some(e.at),
848            path: Path::root(),
849            message: e.message,
850        }])
851    })?;
852    from_doc(text, &doc)
853}
854
855/// Reads a parsed document as `T`; `text` is its source, for positions.
856pub fn from_doc<T: Schema>(text: &str, doc: &Doc) -> Result<Read<T>, Errors> {
857    locate(text, doc, from_value(&doc.value))
858}
859
860/// `from_doc`, references and texts going through `resolver`.
861pub fn from_doc_with<T: Schema>(
862    text: &str,
863    doc: &Doc,
864    resolver: &mut dyn Resolve,
865) -> Result<Read<T>, Errors> {
866    locate(text, doc, from_value_with(&doc.value, resolver))
867}
868
869fn locate<T>(
870    text: &str,
871    doc: &Doc,
872    read: Result<Read<T>, Vec<Problem>>,
873) -> Result<Read<T>, Errors> {
874    read.map_err(|problems| {
875        let mut located: Vec<(u32, Located)> = problems
876            .into_iter()
877            .map(|p| {
878                let spans = doc.spans_at(&p.path);
879                let span = if p.at_key {
880                    spans.key.unwrap_or(spans.span)
881                } else {
882                    spans.span
883                };
884                (
885                    span.start,
886                    Located {
887                        at: Some(LineCol::of(text, span.start as usize)),
888                        path: p.path,
889                        message: p.message,
890                    },
891                )
892            })
893            .collect();
894        // In file order, as an editor lists them.
895        located.sort_by_key(|(offset, _)| *offset);
896        Errors(located.into_iter().map(|(_, l)| l).collect())
897    })
898}
899
900/// `value` as text, indented.
901pub fn to_text<T: Schema>(value: &T) -> String {
902    text::to_string_pretty(&value.write())
903}
904
905// ── Plain types ─────────────────────────────────────────────────────────────
906
907impl Schema for Value {
908    fn shape() -> Shape {
909        Shape::Any
910    }
911    fn read(v: &ValueRef<'_>, _: &mut Reader) -> Option<Self> {
912        Some(v.to_value())
913    }
914    fn write(&self) -> Value {
915        self.clone()
916    }
917}
918
919impl Schema for bool {
920    fn shape() -> Shape {
921        Shape::Bool
922    }
923    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
924        v.as_bool().or_else(|| {
925            cx.expected("true or false", v);
926            None
927        })
928    }
929    fn write(&self) -> Value {
930        Value::Bool(*self)
931    }
932}
933
934macro_rules! int_schema {
935    ($($t:ty),*) => {$(
936        impl Schema for $t {
937            fn shape() -> Shape {
938                Shape::Int { min: <$t>::MIN as i64, max: (<$t>::MAX as u64).min(i64::MAX as u64) as i64 }
939            }
940            fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
941                let Some(n) = v.as_i64() else {
942                    cx.expected("an integer", v);
943                    return None;
944                };
945                <$t>::try_from(n).ok().or_else(|| {
946                    cx.problem(format!("{n} is out of range ({}..={})", <$t>::MIN, <$t>::MAX));
947                    None
948                })
949            }
950            fn write(&self) -> Value {
951                Value::Int(*self as i64)
952            }
953        }
954    )*};
955}
956int_schema!(i8, i16, i32, i64, u8, u16, u32, u64, usize);
957
958macro_rules! float_schema {
959    ($($t:ty),*) => {$(
960        impl Schema for $t {
961            fn shape() -> Shape {
962                Shape::Float
963            }
964            fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
965                v.as_f64().map(|f| f as $t).or_else(|| {
966                    cx.expected("a number", v);
967                    None
968                })
969            }
970            fn write(&self) -> Value {
971                Value::Float(*self as f64)
972            }
973        }
974    )*};
975}
976float_schema!(f32, f64);
977
978impl Schema for String {
979    fn shape() -> Shape {
980        Shape::Str
981    }
982    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
983        match v {
984            // Plain text, the usual case: copied once, into the result.
985            ValueRef::Str(s) if !s.starts_with('@') => Some(s.to_string()),
986            ValueRef::Str(s) => match typed::classify(s) {
987                Ok(Value::Str(s)) => Some(s),
988                Ok(other) => {
989                    cx.problem(format!(
990                        "expected plain text, found {} (write `@@` for a literal `@`)",
991                        other.kind_name()
992                    ));
993                    None
994                }
995                Err(e) => {
996                    cx.problem(e);
997                    None
998                }
999            },
1000            other => {
1001                cx.expected("a string", other);
1002                None
1003            }
1004        }
1005    }
1006    fn write(&self) -> Value {
1007        Value::Str(self.clone())
1008    }
1009}
1010
1011impl<T: Schema> Schema for Option<T> {
1012    fn shape() -> Shape {
1013        Shape::Optional(T::shape)
1014    }
1015    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1016        if v.is_null() {
1017            Some(None)
1018        } else {
1019            T::read(v, cx).map(Some)
1020        }
1021    }
1022    fn write(&self) -> Value {
1023        self.as_ref().map_or(Value::Null, T::write)
1024    }
1025    fn absent() -> Option<Self> {
1026        Some(None)
1027    }
1028    fn skip(&self) -> bool {
1029        self.is_none()
1030    }
1031}
1032
1033impl<T: Schema> Schema for Box<T> {
1034    fn shape() -> Shape {
1035        T::shape()
1036    }
1037    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1038        T::read(v, cx).map(Box::new)
1039    }
1040    fn write(&self) -> Value {
1041        (**self).write()
1042    }
1043    fn absent() -> Option<Self> {
1044        T::absent().map(Box::new)
1045    }
1046    fn skip(&self) -> bool {
1047        (**self).skip()
1048    }
1049}
1050
1051impl<T: Schema> Schema for Vec<T> {
1052    fn shape() -> Shape {
1053        Shape::List(T::shape)
1054    }
1055    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1056        let ValueRef::List(items) = v else {
1057            cx.expected("a list", v);
1058            return None;
1059        };
1060        let mut out = Vec::with_capacity(items.len());
1061        let mut ok = true;
1062        for (i, item) in items.iter().enumerate() {
1063            match cx.at(Seg::Index(i), |cx| T::read(item, cx)) {
1064                Some(x) => out.push(x),
1065                None => ok = false,
1066            }
1067        }
1068        ok.then_some(out)
1069    }
1070    fn write(&self) -> Value {
1071        Value::List(self.iter().map(T::write).collect())
1072    }
1073}
1074
1075impl<T: Schema, const N: usize> Schema for [T; N] {
1076    fn shape() -> Shape {
1077        Shape::List(T::shape)
1078    }
1079    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1080        let items = Vec::<T>::read(v, cx)?;
1081        let len = items.len();
1082        items.try_into().ok().or_else(|| {
1083            cx.problem(format!("expected {N} items, found {len}"));
1084            None
1085        })
1086    }
1087    fn write(&self) -> Value {
1088        Value::List(self.iter().map(T::write).collect())
1089    }
1090}
1091
1092/// A pair, written `[a, b]` (a point, a size).
1093impl<A: Schema, B: Schema> Schema for (A, B) {
1094    fn shape() -> Shape {
1095        Shape::List(Value::shape)
1096    }
1097    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1098        let ValueRef::List(items) = v else {
1099            cx.expected("a pair [a, b]", v);
1100            return None;
1101        };
1102        if items.len() != 2 {
1103            cx.problem(format!("expected 2 items, found {}", items.len()));
1104            return None;
1105        }
1106        let a = cx.at(Seg::Index(0), |cx| A::read(&items[0], cx));
1107        let b = cx.at(Seg::Index(1), |cx| B::read(&items[1], cx));
1108        Some((a?, b?))
1109    }
1110    fn write(&self) -> Value {
1111        Value::List(vec![self.0.write(), self.1.write()])
1112    }
1113}
1114
1115fn read_map<T: Schema>(v: &ValueRef, cx: &mut Reader, mut put: impl FnMut(String, T)) -> Option<()> {
1116    let map = cx.map(v)?;
1117    let mut ok = true;
1118    for (k, item) in map.iter() {
1119        if is_metadata_key(k) {
1120            continue;
1121        }
1122        match cx.at(Seg::Key(k.to_string()), |cx| T::read(item, cx)) {
1123            Some(x) => put(k.to_string(), x),
1124            None => ok = false,
1125        }
1126    }
1127    ok.then_some(())
1128}
1129
1130impl<T: Schema> Schema for BTreeMap<String, T> {
1131    fn shape() -> Shape {
1132        Shape::Map(T::shape)
1133    }
1134    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1135        let mut out = BTreeMap::new();
1136        read_map(v, cx, |k, x| {
1137            out.insert(k, x);
1138        })?;
1139        Some(out)
1140    }
1141    fn write(&self) -> Value {
1142        Value::Map(self.iter().map(|(k, v)| (k.clone(), v.write())).collect())
1143    }
1144}
1145
1146impl<T: Schema> Schema for HashMap<String, T> {
1147    fn shape() -> Shape {
1148        Shape::Map(T::shape)
1149    }
1150    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1151        let mut out = HashMap::new();
1152        read_map(v, cx, |k, x| {
1153            out.insert(k, x);
1154        })?;
1155        Some(out)
1156    }
1157    fn write(&self) -> Value {
1158        let mut keys: Vec<&String> = self.keys().collect();
1159        keys.sort();
1160        Value::Map(
1161            keys.into_iter()
1162                .map(|k| (k.clone(), self[k].write()))
1163                .collect(),
1164        )
1165    }
1166}
1167
1168/// An object kept in file order, values of one schema.
1169#[derive(Debug, Clone, PartialEq)]
1170pub struct Ordered<T>(pub Vec<(String, T)>);
1171
1172impl<T> Default for Ordered<T> {
1173    fn default() -> Self {
1174        Self(Vec::new())
1175    }
1176}
1177
1178impl<T: Schema> Schema for Ordered<T> {
1179    fn shape() -> Shape {
1180        Shape::Map(T::shape)
1181    }
1182    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1183        let mut out = Vec::new();
1184        read_map(v, cx, |k, x| out.push((k, x)))?;
1185        Some(Ordered(out))
1186    }
1187    fn write(&self) -> Value {
1188        Value::Map(self.0.iter().map(|(k, v)| (k.clone(), v.write())).collect())
1189    }
1190}
1191
1192// ── Typed values ────────────────────────────────────────────────────────────
1193
1194/// A kind of content: `item`, `block`, ... — a mod declares its own with
1195/// [`kind!`](crate::kind).
1196pub trait Kind {
1197    const NAME: &'static str;
1198}
1199
1200/// One kind or several (`(Item, Block)`): what a reference field takes.
1201pub trait Kinds {
1202    const NAMES: &'static [&'static str];
1203}
1204
1205impl<K: Kind> Kinds for K {
1206    const NAMES: &'static [&'static str] = &[K::NAME];
1207}
1208
1209impl<A: Kind, B: Kind> Kinds for (A, B) {
1210    const NAMES: &'static [&'static str] = &[A::NAME, B::NAME];
1211}
1212
1213impl<A: Kind, B: Kind, C: Kind> Kinds for (A, B, C) {
1214    const NAMES: &'static [&'static str] = &[A::NAME, B::NAME, C::NAME];
1215}
1216
1217/// Declares kinds of content: `yog_data::kind!(Pattern = "pattern");`.
1218#[macro_export]
1219macro_rules! kind {
1220    ($($name:ident = $kind:expr),* $(,)?) => {$(
1221        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
1222        pub struct $name;
1223        impl $crate::Kind for $name {
1224            const NAME: &'static str = $kind;
1225        }
1226    )*};
1227}
1228
1229/// The kinds every Yog format knows.
1230pub mod kinds {
1231    crate::kind! {
1232        Item = yog_ids::kind::ITEM,
1233        Block = yog_ids::kind::BLOCK,
1234        Entity = yog_ids::kind::ENTITY,
1235        Recipe = yog_ids::kind::RECIPE,
1236        Entry = yog_ids::kind::ENTRY,
1237        Category = yog_ids::kind::CATEGORY,
1238        Fluid = yog_ids::kind::FLUID,
1239        Biome = yog_ids::kind::BIOME,
1240        Dimension = yog_ids::kind::DIMENSION,
1241        Texture = yog_ids::kind::TEXTURE,
1242        Material = yog_ids::kind::MATERIAL,
1243    }
1244}
1245
1246/// The id of content of kind `K`: written `@item:minecraft:stick` or, in a
1247/// field that can only be an item, just `minecraft:stick` (`stick`).
1248pub struct Id<K> {
1249    pub id: String,
1250    kind: PhantomData<K>,
1251}
1252
1253pub type ItemId = Id<kinds::Item>;
1254pub type BlockId = Id<kinds::Block>;
1255pub type EntityId = Id<kinds::Entity>;
1256pub type RecipeId = Id<kinds::Recipe>;
1257
1258impl<K> Id<K> {
1259    pub fn new(id: impl Into<String>) -> Self {
1260        Self {
1261            id: id.into(),
1262            kind: PhantomData,
1263        }
1264    }
1265}
1266
1267impl<K> Clone for Id<K> {
1268    fn clone(&self) -> Self {
1269        Self::new(self.id.clone())
1270    }
1271}
1272impl<K> PartialEq for Id<K> {
1273    fn eq(&self, o: &Self) -> bool {
1274        self.id == o.id
1275    }
1276}
1277impl<K> Eq for Id<K> {}
1278impl<K> std::hash::Hash for Id<K> {
1279    fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
1280        self.id.hash(h);
1281    }
1282}
1283impl<K: Kind> fmt::Debug for Id<K> {
1284    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1285        write!(f, "@{}:{}", K::NAME, self.id)
1286    }
1287}
1288impl<K> fmt::Display for Id<K> {
1289    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1290        f.write_str(&self.id)
1291    }
1292}
1293
1294/// `v` as a reference to content of one of `kinds` (any kind if empty),
1295/// written `@kind:id` — a bare id is an error that says how to write it.
1296pub fn read_ref(v: &ValueRef, cx: &mut Reader, kinds: &[&str]) -> Option<Ref> {
1297    let wanted = || {
1298        kinds
1299            .iter()
1300            .map(|k| format!("`@{k}:`"))
1301            .collect::<Vec<_>>()
1302            .join(" or ")
1303    };
1304    let r = match v {
1305        ValueRef::Ref { kind, id } => Ref::new(*kind, *id),
1306        ValueRef::Str(s) if s.starts_with('@') => match typed::classify(s) {
1307            Ok(Value::Ref(r)) => r,
1308            Ok(other) => {
1309                cx.problem(format!(
1310                    "expected a reference ({}), found {}",
1311                    if kinds.is_empty() {
1312                        "`@kind:id`".into()
1313                    } else {
1314                        wanted()
1315                    },
1316                    other.kind_name()
1317                ));
1318                return None;
1319            }
1320            Err(e) => {
1321                cx.problem(e);
1322                return None;
1323            }
1324        },
1325        ValueRef::Str(s) => {
1326            let kind = kinds.first().copied().unwrap_or(yog_ids::kind::ITEM);
1327            cx.problem(format!("`{s}`: name it by a reference — `@{kind}:{s}`"));
1328            return None;
1329        }
1330        other => {
1331            cx.expected("a reference `@kind:id`", other);
1332            return None;
1333        }
1334    };
1335    if !kinds.is_empty() && !kinds.contains(&r.kind.as_str()) {
1336        cx.problem(format!(
1337            "`@{}:{}` is a reference to {} — this takes {}",
1338            r.kind,
1339            r.id,
1340            r.kind,
1341            wanted()
1342        ));
1343        return None;
1344    }
1345    cx.note_ref(r.clone());
1346    Some(r)
1347}
1348
1349fn read_tag(v: &ValueRef, cx: &mut Reader, kind: &str) -> Option<Ref> {
1350    let r = match v {
1351        ValueRef::Tag { kind, id } => Ref::new(*kind, *id),
1352        ValueRef::Str(s) => match typed::classify(s) {
1353            Ok(Value::Tag(r)) => r,
1354            Ok(_) => {
1355                cx.problem(format!("expected a tag `@tag:{kind}:id`, found {s:?}"));
1356                return None;
1357            }
1358            Err(e) => {
1359                cx.problem(e);
1360                return None;
1361            }
1362        },
1363        other => {
1364            cx.expected("a tag", other);
1365            return None;
1366        }
1367    };
1368    if !kind.is_empty() && r.kind != kind {
1369        cx.problem(format!(
1370            "expected a tag of {kind}, found a tag of {}",
1371            r.kind
1372        ));
1373        return None;
1374    }
1375    cx.note_tag(r.clone());
1376    Some(r)
1377}
1378
1379impl<K: Kind> Schema for Id<K> {
1380    fn shape() -> Shape {
1381        Shape::Ref(<K as Kinds>::NAMES)
1382    }
1383    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1384        read_ref(v, cx, <K as Kinds>::NAMES).map(|r| Id::new(r.id))
1385    }
1386    fn write(&self) -> Value {
1387        Value::Ref(Ref::new(K::NAME, self.id.clone()))
1388    }
1389}
1390
1391impl Schema for Ref {
1392    fn shape() -> Shape {
1393        Shape::Ref(&[])
1394    }
1395    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1396        if let ValueRef::Str(s) = v
1397            && !s.starts_with('@')
1398        {
1399            cx.problem(format!("expected a reference `@kind:id`, found {s:?}"));
1400            return None;
1401        }
1402        read_ref(v, cx, &[])
1403    }
1404    fn write(&self) -> Value {
1405        Value::Ref(self.clone())
1406    }
1407}
1408
1409/// Content of any kind or a tag of it: `@entity:minecraft:pig`,
1410/// `@tag:entity:c:bosses` — a tag's entries, whose kind the tag says.
1411#[derive(Debug, Clone, PartialEq)]
1412pub enum AnyRefOrTag {
1413    One(Ref),
1414    Tag(Ref),
1415}
1416
1417impl AnyRefOrTag {
1418    /// The kind it names (`entity`).
1419    pub fn kind(&self) -> &str {
1420        match self {
1421            AnyRefOrTag::One(r) | AnyRefOrTag::Tag(r) => &r.kind,
1422        }
1423    }
1424}
1425
1426impl fmt::Display for AnyRefOrTag {
1427    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1428        match self {
1429            AnyRefOrTag::One(r) => write!(f, "@{}:{}", r.kind, r.id),
1430            AnyRefOrTag::Tag(r) => write!(f, "@tag:{}:{}", r.kind, r.id),
1431        }
1432    }
1433}
1434
1435impl Schema for AnyRefOrTag {
1436    fn shape() -> Shape {
1437        Shape::RefOrTag(&[])
1438    }
1439    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1440        let is_tag = match v {
1441            ValueRef::Tag { .. } => true,
1442            ValueRef::Str(s) => s.starts_with("@tag:"),
1443            _ => false,
1444        };
1445        if is_tag {
1446            let r = match v {
1447                ValueRef::Tag { kind, id } => Ref::new(*kind, *id),
1448                ValueRef::Str(s) => match typed::classify(s) {
1449                    Ok(Value::Tag(r)) => r,
1450                    Ok(_) => {
1451                        cx.problem(format!("expected a tag `@tag:kind:id`, found {s:?}"));
1452                        return None;
1453                    }
1454                    Err(e) => {
1455                        cx.problem(e);
1456                        return None;
1457                    }
1458                },
1459                _ => return None,
1460            };
1461            return Some(AnyRefOrTag::Tag(r));
1462        }
1463        if let ValueRef::Str(s) = v
1464            && !s.starts_with('@')
1465        {
1466            cx.problem(format!("`{s}`: write `@<kind>:{s}` or a tag `@tag:<kind>:{s}`"));
1467            return None;
1468        }
1469        read_ref(v, cx, &[]).map(AnyRefOrTag::One)
1470    }
1471    fn write(&self) -> Value {
1472        match self {
1473            AnyRefOrTag::One(r) => Value::Ref(r.clone()),
1474            AnyRefOrTag::Tag(r) => Value::Tag(r.clone()),
1475        }
1476    }
1477}
1478
1479/// A tag of content of kind `K`: `@tag:item:minecraft:planks`.
1480pub struct TagId<K> {
1481    pub id: String,
1482    kind: PhantomData<K>,
1483}
1484
1485impl<K> TagId<K> {
1486    pub fn new(id: impl Into<String>) -> Self {
1487        Self {
1488            id: id.into(),
1489            kind: PhantomData,
1490        }
1491    }
1492}
1493impl<K> Clone for TagId<K> {
1494    fn clone(&self) -> Self {
1495        Self::new(self.id.clone())
1496    }
1497}
1498impl<K> PartialEq for TagId<K> {
1499    fn eq(&self, o: &Self) -> bool {
1500        self.id == o.id
1501    }
1502}
1503impl<K: Kind> fmt::Debug for TagId<K> {
1504    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1505        write!(f, "@tag:{}:{}", K::NAME, self.id)
1506    }
1507}
1508
1509impl<K: Kind> Schema for TagId<K> {
1510    fn shape() -> Shape {
1511        Shape::Tag(<K as Kinds>::NAMES)
1512    }
1513    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1514        read_tag(v, cx, K::NAME).map(|r| TagId::new(r.id))
1515    }
1516    fn write(&self) -> Value {
1517        Value::Tag(Ref::new(K::NAME, self.id.clone()))
1518    }
1519}
1520
1521/// Content of kind `K` or a tag of it — a recipe ingredient.
1522#[derive(Debug, Clone, PartialEq)]
1523pub enum OneOrTag<K: Kind> {
1524    One(Id<K>),
1525    Tag(TagId<K>),
1526}
1527
1528/// An item or an item tag: `"minecraft:stick"`, `"@tag:item:minecraft:planks"`.
1529pub type ItemIdOrTag = OneOrTag<kinds::Item>;
1530
1531impl<K: Kind> Schema for OneOrTag<K> {
1532    fn shape() -> Shape {
1533        Shape::RefOrTag(<K as Kinds>::NAMES)
1534    }
1535    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1536        if let ValueRef::Str(s) = v
1537            && let Some(tag) = s.strip_prefix('#')
1538        {
1539            cx.problem(format!("`{s}`: write a tag as `@tag:{}:{tag}`", K::NAME));
1540            return None;
1541        }
1542        let is_tag = match v {
1543            ValueRef::Tag { .. } => true,
1544            ValueRef::Str(s) => s.starts_with("@tag:"),
1545            _ => false,
1546        };
1547        if is_tag {
1548            TagId::read(v, cx).map(OneOrTag::Tag)
1549        } else {
1550            Id::read(v, cx).map(OneOrTag::One)
1551        }
1552    }
1553    fn write(&self) -> Value {
1554        match self {
1555            OneOrTag::One(i) => i.write(),
1556            OneOrTag::Tag(t) => t.write(),
1557        }
1558    }
1559}
1560
1561impl Schema for Res {
1562    fn shape() -> Shape {
1563        Shape::Res
1564    }
1565    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1566        let r = match v {
1567            ValueRef::Res { set, key } => Res { set: set.to_string(), key: key.to_string() },
1568            ValueRef::Str(s) => match typed::classify(s) {
1569                Ok(Value::Res(r)) => r,
1570                Ok(_) => {
1571                    cx.problem(format!("expected a resource `@Set.key`, found {s:?}"));
1572                    return None;
1573                }
1574                Err(e) => {
1575                    cx.problem(e);
1576                    return None;
1577                }
1578            },
1579            other => {
1580                cx.expected("a resource `@Set.key`", other);
1581                return None;
1582            }
1583        };
1584        cx.note_res(r.clone());
1585        Some(r)
1586    }
1587    fn write(&self) -> Value {
1588        Value::Res(self.clone())
1589    }
1590}
1591
1592/// A text a player reads: Yog text as written, a `.yres` text, the name
1593/// of some content (`@item:minecraft:stick` shows "Stick"), or any
1594/// translation key (`@lang:gui.done`).
1595#[derive(Debug, Clone, PartialEq)]
1596pub enum Text {
1597    Literal(String),
1598    Res(Res),
1599    NameOf(Ref),
1600    Lang(String),
1601}
1602
1603impl Default for Text {
1604    fn default() -> Self {
1605        Text::Literal(String::new())
1606    }
1607}
1608
1609impl Schema for Text {
1610    fn shape() -> Shape {
1611        Shape::Text
1612    }
1613    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1614        let typed = match v {
1615            // Words as they are, the usual case: copied once.
1616            ValueRef::Str(s) if !s.starts_with('@') => return Some(Text::Literal(s.to_string())),
1617            ValueRef::Text(t) => return Some(Text::Literal(t.to_string())),
1618            ValueRef::Lang(k) => return Some(Text::Lang(k.to_string())),
1619            ValueRef::Str(s) => typed::classify(s).map_err(|e| cx.problem(e)).ok()?,
1620            ValueRef::Res { .. } | ValueRef::Ref { .. } => v.to_value(),
1621            other => {
1622                cx.expected("a text", other);
1623                return None;
1624            }
1625        };
1626        match typed {
1627            Value::Str(s) => Some(Text::Literal(s)),
1628            Value::Res(r) => {
1629                cx.note_res(r.clone());
1630                Some(Text::Res(r))
1631            }
1632            Value::Ref(r) => {
1633                cx.note_ref(r.clone());
1634                Some(Text::NameOf(r))
1635            }
1636            Value::Lang(k) => Some(Text::Lang(k)),
1637            other => {
1638                cx.problem(format!("expected a text, found {}", other.kind_name()));
1639                None
1640            }
1641        }
1642    }
1643    fn write(&self) -> Value {
1644        match self {
1645            Text::Literal(s) => Value::Text(s.clone()),
1646            Text::Res(r) => Value::Res(r.clone()),
1647            Text::NameOf(r) => Value::Ref(r.clone()),
1648            Text::Lang(k) => Value::Lang(k.clone()),
1649        }
1650    }
1651}
1652
1653/// An image: a source (`sprite:button`, `mymod:gui/logo`, `item:minecraft:stick`)
1654/// or a `.yres` image.
1655#[derive(Debug, Clone, PartialEq)]
1656pub enum Image {
1657    Source(String),
1658    Res(Res),
1659}
1660
1661impl Schema for Image {
1662    fn shape() -> Shape {
1663        Shape::Image
1664    }
1665    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1666        match v {
1667            ValueRef::Image(s) => Some(Image::Source(s.to_string())),
1668            ValueRef::Res { .. } => Res::read(v, cx).map(Image::Res),
1669            ValueRef::Str(s) if !s.starts_with('@') => Some(Image::Source(s.to_string())),
1670            ValueRef::Str(s) => match typed::classify(s) {
1671                Ok(Value::Image(src)) => Some(Image::Source(src)),
1672                Ok(Value::Res(r)) => {
1673                    cx.note_res(r.clone());
1674                    Some(Image::Res(r))
1675                }
1676                Ok(Value::Str(s)) => Some(Image::Source(s)),
1677                Ok(other) => {
1678                    cx.problem(format!("expected an image, found {}", other.kind_name()));
1679                    None
1680                }
1681                Err(e) => {
1682                    cx.problem(e);
1683                    None
1684                }
1685            },
1686            other => {
1687                cx.expected("an image", other);
1688                None
1689            }
1690        }
1691    }
1692    fn write(&self) -> Value {
1693        match self {
1694            Image::Source(s) => Value::Image(s.clone()),
1695            Image::Res(r) => Value::Res(r.clone()),
1696        }
1697    }
1698}
1699
1700/// A color: `#RRGGBB`, `#AARRGGBB`, or a `.yres` color.
1701#[derive(Debug, Clone, PartialEq)]
1702pub enum Color {
1703    Argb(u32),
1704    Res(Res),
1705}
1706
1707impl Schema for Color {
1708    fn shape() -> Shape {
1709        Shape::Color
1710    }
1711    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
1712        match v {
1713            ValueRef::Color(c) => Some(Color::Argb(*c)),
1714            ValueRef::Res { .. } => Res::read(v, cx).map(Color::Res),
1715            ValueRef::Str(s) if s.starts_with('#') => {
1716                typed::parse_color(s).map(Color::Argb).or_else(|| {
1717                    cx.problem(format!("`{s}` isn't a color (#RRGGBB or #AARRGGBB)"));
1718                    None
1719                })
1720            }
1721            ValueRef::Str(s) if s.starts_with('@') => Res::read(v, cx).map(Color::Res),
1722            other => {
1723                cx.expected("a color (#RRGGBB or #AARRGGBB)", other);
1724                None
1725            }
1726        }
1727    }
1728    fn write(&self) -> Value {
1729        match self {
1730            Color::Argb(c) => Value::Color(*c),
1731            Color::Res(r) => Value::Res(r.clone()),
1732        }
1733    }
1734}