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