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