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 a struct's own keys with no allowance inherited from outside.
504    pub fn own_keys<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
505        let saved = std::mem::take(&mut self.allowed);
506        let r = f(self);
507        self.allowed = saved;
508        r
509    }
510}
511
512fn found_text(v: &Value) -> String {
513    match v {
514        Value::Str(s) if s.chars().count() <= 40 => format!("{s:?}"),
515        Value::Int(n) => n.to_string(),
516        Value::Float(f) => f.to_string(),
517        Value::Bool(b) => b.to_string(),
518        other => other.kind_name().to_string(),
519    }
520}
521
522/// The `known` name closest to `key`, if it's close enough to be a typo
523/// (`txt` → `text`, `img` → `image`, `colour` → `color`).
524fn closest<'k>(key: &str, known: &[&'k str]) -> Option<&'k str> {
525    let lower = key.to_ascii_lowercase();
526    known
527        .iter()
528        .map(|k| (edit_distance(&lower, k), *k))
529        .filter(|(d, k)| *d <= if k.len() <= 3 { 1 } else { k.len() / 2 })
530        .min_by_key(|(d, _)| *d)
531        .map(|(_, k)| k)
532}
533
534fn edit_distance(a: &str, b: &str) -> usize {
535    let b: Vec<char> = b.chars().collect();
536    let mut prev: Vec<usize> = (0..=b.len()).collect();
537    for (i, ca) in a.chars().enumerate() {
538        let mut cur = vec![i + 1; b.len() + 1];
539        for (j, cb) in b.iter().enumerate() {
540            cur[j + 1] = (prev[j] + usize::from(ca != *cb))
541                .min(prev[j + 1] + 1)
542                .min(cur[j] + 1);
543        }
544        prev = cur;
545    }
546    prev[b.len()]
547}
548
549/// Problems found reading a document, with where each is.
550#[derive(Debug, Clone, PartialEq, Eq)]
551pub struct Errors(pub Vec<Located>);
552
553/// A problem and its place in the source.
554#[derive(Debug, Clone, PartialEq, Eq)]
555pub struct Located {
556    pub at: Option<LineCol>,
557    pub path: Path,
558    pub message: String,
559}
560
561impl fmt::Display for Located {
562    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563        if let Some(at) = self.at {
564            write!(f, "{at}: ")?;
565        }
566        if !self.path.is_root() {
567            write!(f, "{}: ", self.path)?;
568        }
569        f.write_str(&self.message)
570    }
571}
572
573impl fmt::Display for Errors {
574    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
575        for (i, e) in self.0.iter().enumerate() {
576            if i > 0 {
577                f.write_str("\n")?;
578            }
579            write!(f, "{e}")?;
580        }
581        Ok(())
582    }
583}
584
585impl std::error::Error for Errors {}
586
587/// What reading a document gives: the value and the references in it.
588#[derive(Debug)]
589pub struct Read<T> {
590    pub value: T,
591    pub refs: Vec<Found<Ref>>,
592    pub tags: Vec<Found<Ref>>,
593    pub resources: Vec<Found<Res>>,
594}
595
596/// Reads `v` as `T`: the value and its references, or every problem.
597pub fn from_value<T: Schema>(v: &Value) -> Result<Read<T>, Vec<Problem>> {
598    read_with(v, Reader::new())
599}
600
601/// `from_value`, references and texts going through `resolver`.
602pub fn from_value_with<T: Schema>(
603    v: &Value,
604    resolver: &mut dyn Resolve,
605) -> Result<Read<T>, Vec<Problem>> {
606    read_with(v, Reader::with_resolver(resolver))
607}
608
609fn read_with<T: Schema>(v: &Value, mut cx: Reader<'_>) -> Result<Read<T>, Vec<Problem>> {
610    let value = T::read(v, &mut cx);
611    match value {
612        Some(value) if cx.problems.is_empty() => Ok(Read {
613            value,
614            refs: cx.refs,
615            tags: cx.tags,
616            resources: cx.resources,
617        }),
618        _ => Err(cx.problems),
619    }
620}
621
622/// Parses `text` and reads it as `T`; problems come with their line and column.
623pub fn from_text<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
624    let doc = text::parse(text).map_err(|e| {
625        Errors(vec![Located {
626            at: Some(e.at),
627            path: Path::root(),
628            message: e.message,
629        }])
630    })?;
631    from_doc(text, &doc)
632}
633
634/// Parses TOML `text` and reads it as `T`.
635#[cfg(feature = "toml")]
636pub fn from_toml<T: Schema>(text: &str) -> Result<Read<T>, Errors> {
637    let doc = crate::toml::parse(text).map_err(|e| {
638        Errors(vec![Located {
639            at: Some(e.at),
640            path: Path::root(),
641            message: e.message,
642        }])
643    })?;
644    from_doc(text, &doc)
645}
646
647/// Reads a parsed document as `T`; `text` is its source, for positions.
648pub fn from_doc<T: Schema>(text: &str, doc: &Doc) -> Result<Read<T>, Errors> {
649    locate(text, doc, from_value(&doc.value))
650}
651
652/// `from_doc`, references and texts going through `resolver`.
653pub fn from_doc_with<T: Schema>(
654    text: &str,
655    doc: &Doc,
656    resolver: &mut dyn Resolve,
657) -> Result<Read<T>, Errors> {
658    locate(text, doc, from_value_with(&doc.value, resolver))
659}
660
661fn locate<T>(
662    text: &str,
663    doc: &Doc,
664    read: Result<Read<T>, Vec<Problem>>,
665) -> Result<Read<T>, Errors> {
666    read.map_err(|problems| {
667        let mut located: Vec<(u32, Located)> = problems
668            .into_iter()
669            .map(|p| {
670                let spans = doc.spans_at(&p.path);
671                let span = if p.at_key {
672                    spans.key.unwrap_or(spans.span)
673                } else {
674                    spans.span
675                };
676                (
677                    span.start,
678                    Located {
679                        at: Some(LineCol::of(text, span.start as usize)),
680                        path: p.path,
681                        message: p.message,
682                    },
683                )
684            })
685            .collect();
686        // In file order, as an editor lists them.
687        located.sort_by_key(|(offset, _)| *offset);
688        Errors(located.into_iter().map(|(_, l)| l).collect())
689    })
690}
691
692/// `value` as text, indented.
693pub fn to_text<T: Schema>(value: &T) -> String {
694    text::to_string_pretty(&value.write())
695}
696
697// ── Plain types ─────────────────────────────────────────────────────────────
698
699impl Schema for Value {
700    fn shape() -> Shape {
701        Shape::Any
702    }
703    fn read(v: &Value, _: &mut Reader) -> Option<Self> {
704        Some(v.clone())
705    }
706    fn write(&self) -> Value {
707        self.clone()
708    }
709}
710
711impl Schema for bool {
712    fn shape() -> Shape {
713        Shape::Bool
714    }
715    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
716        v.as_bool().or_else(|| {
717            cx.expected("true or false", v);
718            None
719        })
720    }
721    fn write(&self) -> Value {
722        Value::Bool(*self)
723    }
724}
725
726macro_rules! int_schema {
727    ($($t:ty),*) => {$(
728        impl Schema for $t {
729            fn shape() -> Shape {
730                Shape::Int { min: <$t>::MIN as i64, max: (<$t>::MAX as u64).min(i64::MAX as u64) as i64 }
731            }
732            fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
733                let Some(n) = v.as_i64() else {
734                    cx.expected("an integer", v);
735                    return None;
736                };
737                <$t>::try_from(n).ok().or_else(|| {
738                    cx.problem(format!("{n} is out of range ({}..={})", <$t>::MIN, <$t>::MAX));
739                    None
740                })
741            }
742            fn write(&self) -> Value {
743                Value::Int(*self as i64)
744            }
745        }
746    )*};
747}
748int_schema!(i8, i16, i32, i64, u8, u16, u32, u64, usize);
749
750macro_rules! float_schema {
751    ($($t:ty),*) => {$(
752        impl Schema for $t {
753            fn shape() -> Shape {
754                Shape::Float
755            }
756            fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
757                v.as_f64().map(|f| f as $t).or_else(|| {
758                    cx.expected("a number", v);
759                    None
760                })
761            }
762            fn write(&self) -> Value {
763                Value::Float(*self as f64)
764            }
765        }
766    )*};
767}
768float_schema!(f32, f64);
769
770impl Schema for String {
771    fn shape() -> Shape {
772        Shape::Str
773    }
774    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
775        match v {
776            Value::Str(s) => match typed::classify(s) {
777                Ok(Value::Str(s)) => Some(s),
778                Ok(other) => {
779                    cx.problem(format!(
780                        "expected plain text, found {} (write `@@` for a literal `@`)",
781                        other.kind_name()
782                    ));
783                    None
784                }
785                Err(e) => {
786                    cx.problem(e);
787                    None
788                }
789            },
790            other => {
791                cx.expected("a string", other);
792                None
793            }
794        }
795    }
796    fn write(&self) -> Value {
797        Value::Str(self.clone())
798    }
799}
800
801impl<T: Schema> Schema for Option<T> {
802    fn shape() -> Shape {
803        Shape::Optional(T::shape)
804    }
805    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
806        if v.is_null() {
807            Some(None)
808        } else {
809            T::read(v, cx).map(Some)
810        }
811    }
812    fn write(&self) -> Value {
813        self.as_ref().map_or(Value::Null, T::write)
814    }
815    fn absent() -> Option<Self> {
816        Some(None)
817    }
818    fn skip(&self) -> bool {
819        self.is_none()
820    }
821}
822
823impl<T: Schema> Schema for Box<T> {
824    fn shape() -> Shape {
825        T::shape()
826    }
827    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
828        T::read(v, cx).map(Box::new)
829    }
830    fn write(&self) -> Value {
831        (**self).write()
832    }
833    fn absent() -> Option<Self> {
834        T::absent().map(Box::new)
835    }
836    fn skip(&self) -> bool {
837        (**self).skip()
838    }
839}
840
841impl<T: Schema> Schema for Vec<T> {
842    fn shape() -> Shape {
843        Shape::List(T::shape)
844    }
845    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
846        let Value::List(items) = v else {
847            cx.expected("a list", v);
848            return None;
849        };
850        let mut out = Vec::with_capacity(items.len());
851        let mut ok = true;
852        for (i, item) in items.iter().enumerate() {
853            match cx.at(Seg::Index(i), |cx| T::read(item, cx)) {
854                Some(x) => out.push(x),
855                None => ok = false,
856            }
857        }
858        ok.then_some(out)
859    }
860    fn write(&self) -> Value {
861        Value::List(self.iter().map(T::write).collect())
862    }
863}
864
865impl<T: Schema, const N: usize> Schema for [T; N] {
866    fn shape() -> Shape {
867        Shape::List(T::shape)
868    }
869    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
870        let items = Vec::<T>::read(v, cx)?;
871        let len = items.len();
872        items.try_into().ok().or_else(|| {
873            cx.problem(format!("expected {N} items, found {len}"));
874            None
875        })
876    }
877    fn write(&self) -> Value {
878        Value::List(self.iter().map(T::write).collect())
879    }
880}
881
882fn read_map<T: Schema>(v: &Value, cx: &mut Reader, mut put: impl FnMut(String, T)) -> Option<()> {
883    let map = cx.map(v)?;
884    let mut ok = true;
885    for (k, item) in map.iter() {
886        if is_metadata_key(k) {
887            continue;
888        }
889        match cx.at(Seg::Key(k.to_string()), |cx| T::read(item, cx)) {
890            Some(x) => put(k.to_string(), x),
891            None => ok = false,
892        }
893    }
894    ok.then_some(())
895}
896
897impl<T: Schema> Schema for BTreeMap<String, T> {
898    fn shape() -> Shape {
899        Shape::Map(T::shape)
900    }
901    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
902        let mut out = BTreeMap::new();
903        read_map(v, cx, |k, x| {
904            out.insert(k, x);
905        })?;
906        Some(out)
907    }
908    fn write(&self) -> Value {
909        Value::Map(self.iter().map(|(k, v)| (k.clone(), v.write())).collect())
910    }
911}
912
913impl<T: Schema> Schema for HashMap<String, T> {
914    fn shape() -> Shape {
915        Shape::Map(T::shape)
916    }
917    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
918        let mut out = HashMap::new();
919        read_map(v, cx, |k, x| {
920            out.insert(k, x);
921        })?;
922        Some(out)
923    }
924    fn write(&self) -> Value {
925        let mut keys: Vec<&String> = self.keys().collect();
926        keys.sort();
927        Value::Map(
928            keys.into_iter()
929                .map(|k| (k.clone(), self[k].write()))
930                .collect(),
931        )
932    }
933}
934
935/// An object kept in file order, values of one schema.
936#[derive(Debug, Clone, PartialEq)]
937pub struct Ordered<T>(pub Vec<(String, T)>);
938
939impl<T> Default for Ordered<T> {
940    fn default() -> Self {
941        Self(Vec::new())
942    }
943}
944
945impl<T: Schema> Schema for Ordered<T> {
946    fn shape() -> Shape {
947        Shape::Map(T::shape)
948    }
949    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
950        let mut out = Vec::new();
951        read_map(v, cx, |k, x| out.push((k, x)))?;
952        Some(Ordered(out))
953    }
954    fn write(&self) -> Value {
955        Value::Map(self.0.iter().map(|(k, v)| (k.clone(), v.write())).collect())
956    }
957}
958
959// ── Typed values ────────────────────────────────────────────────────────────
960
961/// A kind of content: `item`, `block`, ... — a mod declares its own with
962/// [`kind!`](crate::kind).
963pub trait Kind {
964    const NAME: &'static str;
965}
966
967/// One kind or several (`(Item, Block)`): what a reference field takes.
968pub trait Kinds {
969    const NAMES: &'static [&'static str];
970}
971
972impl<K: Kind> Kinds for K {
973    const NAMES: &'static [&'static str] = &[K::NAME];
974}
975
976impl<A: Kind, B: Kind> Kinds for (A, B) {
977    const NAMES: &'static [&'static str] = &[A::NAME, B::NAME];
978}
979
980impl<A: Kind, B: Kind, C: Kind> Kinds for (A, B, C) {
981    const NAMES: &'static [&'static str] = &[A::NAME, B::NAME, C::NAME];
982}
983
984/// Declares kinds of content: `yog_data::kind!(Pattern = "pattern");`.
985#[macro_export]
986macro_rules! kind {
987    ($($name:ident = $kind:literal),* $(,)?) => {$(
988        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
989        pub struct $name;
990        impl $crate::Kind for $name {
991            const NAME: &'static str = $kind;
992        }
993    )*};
994}
995
996/// The kinds every Yog format knows.
997pub mod kinds {
998    crate::kind! {
999        Item = "item",
1000        Block = "block",
1001        Entity = "entity",
1002        Recipe = "recipe",
1003        Entry = "entry",
1004        Category = "category",
1005        Fluid = "fluid",
1006        Biome = "biome",
1007        Dimension = "dimension",
1008    }
1009}
1010
1011/// The id of content of kind `K`: written `@item:minecraft:stick` or, in a
1012/// field that can only be an item, just `minecraft:stick` (`stick`).
1013pub struct Id<K> {
1014    pub id: String,
1015    kind: PhantomData<K>,
1016}
1017
1018pub type ItemId = Id<kinds::Item>;
1019pub type BlockId = Id<kinds::Block>;
1020pub type EntityId = Id<kinds::Entity>;
1021pub type RecipeId = Id<kinds::Recipe>;
1022
1023impl<K> Id<K> {
1024    pub fn new(id: impl Into<String>) -> Self {
1025        Self {
1026            id: id.into(),
1027            kind: PhantomData,
1028        }
1029    }
1030}
1031
1032impl<K> Clone for Id<K> {
1033    fn clone(&self) -> Self {
1034        Self::new(self.id.clone())
1035    }
1036}
1037impl<K> PartialEq for Id<K> {
1038    fn eq(&self, o: &Self) -> bool {
1039        self.id == o.id
1040    }
1041}
1042impl<K> Eq for Id<K> {}
1043impl<K> std::hash::Hash for Id<K> {
1044    fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
1045        self.id.hash(h);
1046    }
1047}
1048impl<K: Kind> fmt::Debug for Id<K> {
1049    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1050        write!(f, "@{}:{}", K::NAME, self.id)
1051    }
1052}
1053impl<K> fmt::Display for Id<K> {
1054    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1055        f.write_str(&self.id)
1056    }
1057}
1058
1059/// `v` as a reference to content of one of `kinds` (any kind if empty),
1060/// written `@kind:id` — a bare id is an error that says how to write it.
1061pub fn read_ref(v: &Value, cx: &mut Reader, kinds: &[&str]) -> Option<Ref> {
1062    let wanted = || {
1063        kinds
1064            .iter()
1065            .map(|k| format!("`@{k}:`"))
1066            .collect::<Vec<_>>()
1067            .join(" or ")
1068    };
1069    let r = match v {
1070        Value::Ref(r) => r.clone(),
1071        Value::Str(s) if s.starts_with('@') => match typed::classify(s) {
1072            Ok(Value::Ref(r)) => r,
1073            Ok(other) => {
1074                cx.problem(format!(
1075                    "expected a reference ({}), found {}",
1076                    if kinds.is_empty() {
1077                        "`@kind:id`".into()
1078                    } else {
1079                        wanted()
1080                    },
1081                    other.kind_name()
1082                ));
1083                return None;
1084            }
1085            Err(e) => {
1086                cx.problem(e);
1087                return None;
1088            }
1089        },
1090        Value::Str(s) => {
1091            let kind = kinds.first().copied().unwrap_or("item");
1092            cx.problem(format!("`{s}`: name it by a reference — `@{kind}:{s}`"));
1093            return None;
1094        }
1095        other => {
1096            cx.expected("a reference `@kind:id`", other);
1097            return None;
1098        }
1099    };
1100    if !kinds.is_empty() && !kinds.contains(&r.kind.as_str()) {
1101        cx.problem(format!(
1102            "`@{}:{}` is a reference to {} — this takes {}",
1103            r.kind,
1104            r.id,
1105            r.kind,
1106            wanted()
1107        ));
1108        return None;
1109    }
1110    cx.note_ref(r.clone());
1111    Some(r)
1112}
1113
1114fn read_tag(v: &Value, cx: &mut Reader, kind: &str) -> Option<Ref> {
1115    let r = match v {
1116        Value::Tag(r) => r.clone(),
1117        Value::Str(s) => match typed::classify(s) {
1118            Ok(Value::Tag(r)) => r,
1119            Ok(_) => {
1120                cx.problem(format!("expected a tag `@tag:{kind}:id`, found {s:?}"));
1121                return None;
1122            }
1123            Err(e) => {
1124                cx.problem(e);
1125                return None;
1126            }
1127        },
1128        other => {
1129            cx.expected("a tag", other);
1130            return None;
1131        }
1132    };
1133    if !kind.is_empty() && r.kind != kind {
1134        cx.problem(format!(
1135            "expected a tag of {kind}, found a tag of {}",
1136            r.kind
1137        ));
1138        return None;
1139    }
1140    cx.note_tag(r.clone());
1141    Some(r)
1142}
1143
1144impl<K: Kind> Schema for Id<K> {
1145    fn shape() -> Shape {
1146        Shape::Ref(<K as Kinds>::NAMES)
1147    }
1148    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1149        read_ref(v, cx, <K as Kinds>::NAMES).map(|r| Id::new(r.id))
1150    }
1151    fn write(&self) -> Value {
1152        Value::Ref(Ref::new(K::NAME, self.id.clone()))
1153    }
1154}
1155
1156impl Schema for Ref {
1157    fn shape() -> Shape {
1158        Shape::Ref(&[])
1159    }
1160    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1161        if let Value::Str(s) = v
1162            && !s.starts_with('@')
1163        {
1164            cx.problem(format!("expected a reference `@kind:id`, found {s:?}"));
1165            return None;
1166        }
1167        read_ref(v, cx, &[])
1168    }
1169    fn write(&self) -> Value {
1170        Value::Ref(self.clone())
1171    }
1172}
1173
1174/// A tag of content of kind `K`: `@tag:item:minecraft:planks`.
1175pub struct TagId<K> {
1176    pub id: String,
1177    kind: PhantomData<K>,
1178}
1179
1180impl<K> TagId<K> {
1181    pub fn new(id: impl Into<String>) -> Self {
1182        Self {
1183            id: id.into(),
1184            kind: PhantomData,
1185        }
1186    }
1187}
1188impl<K> Clone for TagId<K> {
1189    fn clone(&self) -> Self {
1190        Self::new(self.id.clone())
1191    }
1192}
1193impl<K> PartialEq for TagId<K> {
1194    fn eq(&self, o: &Self) -> bool {
1195        self.id == o.id
1196    }
1197}
1198impl<K: Kind> fmt::Debug for TagId<K> {
1199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1200        write!(f, "@tag:{}:{}", K::NAME, self.id)
1201    }
1202}
1203
1204impl<K: Kind> Schema for TagId<K> {
1205    fn shape() -> Shape {
1206        Shape::Tag(<K as Kinds>::NAMES)
1207    }
1208    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1209        read_tag(v, cx, K::NAME).map(|r| TagId::new(r.id))
1210    }
1211    fn write(&self) -> Value {
1212        Value::Tag(Ref::new(K::NAME, self.id.clone()))
1213    }
1214}
1215
1216/// Content of kind `K` or a tag of it — a recipe ingredient.
1217#[derive(Debug, Clone, PartialEq)]
1218pub enum OneOrTag<K: Kind> {
1219    One(Id<K>),
1220    Tag(TagId<K>),
1221}
1222
1223/// An item or an item tag: `"minecraft:stick"`, `"@tag:item:minecraft:planks"`.
1224pub type ItemIdOrTag = OneOrTag<kinds::Item>;
1225
1226impl<K: Kind> Schema for OneOrTag<K> {
1227    fn shape() -> Shape {
1228        Shape::RefOrTag(<K as Kinds>::NAMES)
1229    }
1230    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1231        let is_tag = match v {
1232            Value::Tag(_) => true,
1233            Value::Str(s) => s.starts_with("@tag:"),
1234            _ => false,
1235        };
1236        if is_tag {
1237            TagId::read(v, cx).map(OneOrTag::Tag)
1238        } else {
1239            Id::read(v, cx).map(OneOrTag::One)
1240        }
1241    }
1242    fn write(&self) -> Value {
1243        match self {
1244            OneOrTag::One(i) => i.write(),
1245            OneOrTag::Tag(t) => t.write(),
1246        }
1247    }
1248}
1249
1250impl Schema for Res {
1251    fn shape() -> Shape {
1252        Shape::Res
1253    }
1254    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1255        let r = match v {
1256            Value::Res(r) => r.clone(),
1257            Value::Str(s) => match typed::classify(s) {
1258                Ok(Value::Res(r)) => r,
1259                Ok(_) => {
1260                    cx.problem(format!("expected a resource `@Set.key`, found {s:?}"));
1261                    return None;
1262                }
1263                Err(e) => {
1264                    cx.problem(e);
1265                    return None;
1266                }
1267            },
1268            other => {
1269                cx.expected("a resource `@Set.key`", other);
1270                return None;
1271            }
1272        };
1273        cx.note_res(r.clone());
1274        Some(r)
1275    }
1276    fn write(&self) -> Value {
1277        Value::Res(self.clone())
1278    }
1279}
1280
1281/// A text a player reads: Yog text as written, a `.yres` text, the name
1282/// of some content (`@item:minecraft:stick` shows "Stick"), or any
1283/// translation key (`@lang:gui.done`).
1284#[derive(Debug, Clone, PartialEq)]
1285pub enum Text {
1286    Literal(String),
1287    Res(Res),
1288    NameOf(Ref),
1289    Lang(String),
1290}
1291
1292impl Default for Text {
1293    fn default() -> Self {
1294        Text::Literal(String::new())
1295    }
1296}
1297
1298impl Schema for Text {
1299    fn shape() -> Shape {
1300        Shape::Text
1301    }
1302    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1303        let typed = match v {
1304            Value::Str(s) => typed::classify(s).map_err(|e| cx.problem(e)).ok()?,
1305            Value::Text(t) => return Some(Text::Literal(t.clone())),
1306            other => other.clone(),
1307        };
1308        match typed {
1309            Value::Str(s) => Some(Text::Literal(s)),
1310            Value::Res(r) => {
1311                cx.note_res(r.clone());
1312                Some(Text::Res(r))
1313            }
1314            Value::Ref(r) => {
1315                cx.note_ref(r.clone());
1316                Some(Text::NameOf(r))
1317            }
1318            Value::Lang(k) => Some(Text::Lang(k)),
1319            other => {
1320                cx.expected("a text", &other);
1321                None
1322            }
1323        }
1324    }
1325    fn write(&self) -> Value {
1326        match self {
1327            Text::Literal(s) => Value::Text(s.clone()),
1328            Text::Res(r) => Value::Res(r.clone()),
1329            Text::NameOf(r) => Value::Ref(r.clone()),
1330            Text::Lang(k) => Value::Lang(k.clone()),
1331        }
1332    }
1333}
1334
1335/// An image: a source (`sprite:button`, `mymod:gui/logo`, `item:minecraft:stick`)
1336/// or a `.yres` image.
1337#[derive(Debug, Clone, PartialEq)]
1338pub enum Image {
1339    Source(String),
1340    Res(Res),
1341}
1342
1343impl Schema for Image {
1344    fn shape() -> Shape {
1345        Shape::Image
1346    }
1347    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1348        match v {
1349            Value::Image(s) => Some(Image::Source(s.clone())),
1350            Value::Res(r) => {
1351                cx.note_res(r.clone());
1352                Some(Image::Res(r.clone()))
1353            }
1354            Value::Str(s) if !s.starts_with('@') => Some(Image::Source(s.clone())),
1355            Value::Str(s) => match typed::classify(s) {
1356                Ok(Value::Image(src)) => Some(Image::Source(src)),
1357                Ok(Value::Res(r)) => {
1358                    cx.note_res(r.clone());
1359                    Some(Image::Res(r))
1360                }
1361                Ok(Value::Str(s)) => Some(Image::Source(s)),
1362                Ok(other) => {
1363                    cx.expected("an image", &other);
1364                    None
1365                }
1366                Err(e) => {
1367                    cx.problem(e);
1368                    None
1369                }
1370            },
1371            other => {
1372                cx.expected("an image", other);
1373                None
1374            }
1375        }
1376    }
1377    fn write(&self) -> Value {
1378        match self {
1379            Image::Source(s) => Value::Image(s.clone()),
1380            Image::Res(r) => Value::Res(r.clone()),
1381        }
1382    }
1383}
1384
1385/// A color: `#RRGGBB`, `#AARRGGBB`, or a `.yres` color.
1386#[derive(Debug, Clone, PartialEq)]
1387pub enum Color {
1388    Argb(u32),
1389    Res(Res),
1390}
1391
1392impl Schema for Color {
1393    fn shape() -> Shape {
1394        Shape::Color
1395    }
1396    fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
1397        match v {
1398            Value::Color(c) => Some(Color::Argb(*c)),
1399            Value::Res(r) => {
1400                cx.note_res(r.clone());
1401                Some(Color::Res(r.clone()))
1402            }
1403            Value::Str(s) if s.starts_with('#') => {
1404                typed::parse_color(s).map(Color::Argb).or_else(|| {
1405                    cx.problem(format!("`{s}` isn't a color (#RRGGBB or #AARRGGBB)"));
1406                    None
1407                })
1408            }
1409            Value::Str(s) if s.starts_with('@') => Res::read(v, cx).map(Color::Res),
1410            other => {
1411                cx.expected("a color (#RRGGBB or #AARRGGBB)", other);
1412                None
1413            }
1414        }
1415    }
1416    fn write(&self) -> Value {
1417        match self {
1418            Color::Argb(c) => Value::Color(*c),
1419            Color::Res(r) => Value::Res(r.clone()),
1420        }
1421    }
1422}