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