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