1use super::{LineCol, MapRef, Path, Ref, Res, Schema, Seg, Text, ValueRef, fmt};
3
4#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct Problem {
9 pub path: Path,
10 pub code: &'static str,
12 pub message: String,
13 pub at_key: bool,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Found<T> {
20 pub value: T,
21 pub path: Path,
22}
23
24#[derive(Default)]
27pub struct Reader<'r> {
28 pub(super) path: Vec<Step>,
31 pub(super) value_only: bool,
33 pub(super) resolver: Option<&'r mut dyn Resolve>,
35 pub problems: Vec<Problem>,
36 pub refs: Vec<Found<Ref>>,
37 pub tags: Vec<Found<Ref>>,
38 pub resources: Vec<Found<Res>>,
39 pub(super) allowed: Vec<&'static str>,
41}
42
43pub(super) enum Step {
45 Seg(Seg),
46 Field(&'static str),
47}
48
49pub const COMMENT_KEY: &str = "comment";
51pub const META_PREFIX: char = '$';
53pub const PRIVATE_PREFIX: char = '_';
54
55pub fn is_metadata_key(key: &str) -> bool {
57 key.starts_with(META_PREFIX) || key.starts_with(PRIVATE_PREFIX) || key == COMMENT_KEY
58}
59
60pub trait Resolve {
64 fn reference(&mut self, r: &Ref, _path: &Path) -> Result<String, String> {
66 Ok(r.id.clone())
67 }
68
69 fn text(&mut self, t: &Text, _path: &Path) -> Result<String, String> {
71 Ok(match t {
72 Text::Literal(s) => s.clone(),
73 Text::Res(r) => r.to_string(),
74 Text::NameOf(r) => format!("@{}:{}", r.kind, r.id),
75 Text::Lang(k) => format!("@lang:{k}"),
76 })
77 }
78
79 fn words(&mut self, _r: &Res) -> Option<String> {
81 None
82 }
83}
84
85impl<'r> Reader<'r> {
86 pub fn new() -> Self {
87 Self::default()
88 }
89
90 pub fn with_resolver(resolver: &'r mut dyn Resolve) -> Self {
92 Self {
93 resolver: Some(resolver),
94 ..Self::default()
95 }
96 }
97
98 pub fn resolve_ref(&mut self, r: &Ref) -> Option<String> {
100 let result = match &mut self.resolver {
101 Some(res) => res.reference(r, &Self::built(&self.path)),
102 None => Ok(r.id.clone()),
103 };
104 result.map_err(|e| self.problem(e)).ok()
105 }
106
107 pub fn resolve_text(&mut self, t: &Text) -> Option<String> {
109 struct Keep;
110 impl Resolve for Keep {}
111 let result = match &mut self.resolver {
112 Some(res) => res.text(t, &Self::built(&self.path)),
113 None => Keep.text(t, &Path::root()),
114 };
115 result.map_err(|e| self.problem(e)).ok()
116 }
117
118 pub fn take_text(&mut self, t: Text) -> Option<String> {
121 match (t, self.resolver.is_some()) {
122 (Text::Literal(s), false) => Some(s),
123 (t, _) => self.resolve_text(&t),
124 }
125 }
126
127 pub fn take_ref(&mut self, r: Ref) -> Option<String> {
129 if self.resolver.is_none() {
130 return Some(r.id);
131 }
132 self.resolve_ref(&r)
133 }
134
135 pub fn keeps_found(&self) -> bool {
137 !self.value_only
138 }
139
140 pub fn resolves(&self) -> bool {
142 self.resolver.is_some()
143 }
144
145 pub fn words(&mut self, r: &Res) -> Option<String> {
147 self.resolver.as_mut()?.words(r)
148 }
149
150 pub fn path(&self) -> Path {
152 Self::built(&self.path)
153 }
154
155 fn built(steps: &[Step]) -> Path {
156 let mut p = Path::root();
157 for s in steps {
158 p.push(match s {
159 Step::Seg(s) => s.clone(),
160 Step::Field(k) => Seg::Key((*k).to_string()),
161 });
162 }
163 p
164 }
165
166 pub fn problem(&mut self, message: impl Into<String>) {
168 self.problem_of(yog_ids::problem_code::INVALID, message);
169 }
170
171 pub fn problem_of(&mut self, code: &'static str, message: impl Into<String>) {
173 self.problems.push(Problem {
174 path: self.path(),
175 code,
176 message: message.into(),
177 at_key: false,
178 });
179 }
180
181 pub fn expected(&mut self, what: &str, found: &ValueRef) {
183 self.problem_of(
184 yog_ids::problem_code::EXPECTED,
185 format!("expected {what}, found {}", found_text(found)),
186 );
187 }
188
189 pub fn at<T>(&mut self, seg: Seg, f: impl FnOnce(&mut Self) -> T) -> T {
191 self.path.push(Step::Seg(seg));
192 let r = f(self);
193 self.path.pop();
194 r
195 }
196
197 fn in_field<T>(&mut self, key: &'static str, f: impl FnOnce(&mut Self) -> T) -> T {
199 self.path.push(Step::Field(key));
200 let r = f(self);
201 self.path.pop();
202 r
203 }
204
205 pub fn note_ref(&mut self, r: Ref) {
206 if self.value_only {
207 return;
208 }
209 let path = self.path();
210 self.refs.push(Found { value: r, path });
211 }
212
213 pub fn note_tag(&mut self, r: Ref) {
214 if self.value_only {
215 return;
216 }
217 let path = self.path();
218 self.tags.push(Found { value: r, path });
219 }
220
221 pub fn note_res(&mut self, r: Res) {
222 if self.value_only {
223 return;
224 }
225 let path = self.path();
226 self.resources.push(Found { value: r, path });
227 }
228
229 pub fn map<'v>(&mut self, v: &ValueRef<'v>) -> Option<MapRef<'v>> {
231 match v {
232 ValueRef::Map(m) => Some(*m),
233 other => {
234 self.expected("an object", other);
235 None
236 }
237 }
238 }
239
240 pub fn unknown_keys(&mut self, map: MapRef, known: &[&str]) {
242 for key in map.keys() {
243 if known.contains(&key) || is_metadata_key(key) || self.allowed.contains(&key) {
244 continue;
245 }
246 let hint = closest(key, known)
247 .map(|k| format!(" — did you mean `{k}`?"))
248 .unwrap_or_else(|| {
249 if known.is_empty() {
250 String::new()
251 } else {
252 format!(
253 " (expected {})",
254 known
255 .iter()
256 .map(|k| format!("`{k}`"))
257 .collect::<Vec<_>>()
258 .join(", ")
259 )
260 }
261 });
262 self.problems.push(Problem {
263 path: self.path().key(key),
264 code: yog_ids::problem_code::UNKNOWN_FIELD,
265 message: format!("unknown field `{key}`{hint}"),
266 at_key: true,
267 });
268 }
269 }
270
271 pub fn unknown_name(&mut self, name: &str, known: &[&str]) {
273 let hint = match closest(name, known) {
274 Some(k) => format!(" — did you mean `{k}`?"),
275 None => format!(
276 " (expected {})",
277 known
278 .iter()
279 .map(|k| format!("`{k}`"))
280 .collect::<Vec<_>>()
281 .join(", ")
282 ),
283 };
284 self.problem(format!("unknown `{name}`{hint}"));
285 }
286
287 pub fn field<T: Schema>(&mut self, map: MapRef, key: &'static str) -> Option<T> {
289 match map.get(key) {
290 Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
291 None => T::absent().or_else(|| {
292 self.problem_of(
293 yog_ids::problem_code::MISSING_FIELD,
294 format!("missing `{key}`"),
295 );
296 None
297 }),
298 }
299 }
300
301 pub fn field_or<T: Schema>(
303 &mut self,
304 map: MapRef,
305 key: &'static str,
306 default: impl FnOnce() -> T,
307 ) -> Option<T> {
308 match map.get(key) {
309 Some(v) => self.at(Seg::Key(key.to_string()), |cx| T::read(v, cx)),
310 None => Some(default()),
311 }
312 }
313
314 pub fn field_with<W: crate::with::With<F>, F>(
316 &mut self,
317 map: MapRef,
318 key: &'static str,
319 ) -> Option<F> {
320 match map.get(key) {
321 Some(v) => self.in_field(key, |cx| W::read(v, cx)),
322 None => W::absent().or_else(|| {
323 self.problem_of(
324 yog_ids::problem_code::MISSING_FIELD,
325 format!("missing `{key}`"),
326 );
327 None
328 }),
329 }
330 }
331
332 pub fn field_or_with<W: crate::with::With<F>, F>(
334 &mut self,
335 map: MapRef,
336 key: &'static str,
337 default: impl FnOnce() -> F,
338 ) -> Option<F> {
339 match map.get(key) {
340 Some(v) => self.in_field(key, |cx| W::read(v, cx)),
341 None => Some(default()),
342 }
343 }
344
345 pub fn with_allowed<T>(&mut self, key: &'static str, f: impl FnOnce(&mut Self) -> T) -> T {
347 self.allowed.push(key);
348 let r = f(self);
349 self.allowed.pop();
350 r
351 }
352
353 pub fn with_allowed_all<T>(
356 &mut self,
357 keys: &[&'static str],
358 f: impl FnOnce(&mut Self) -> T,
359 ) -> T {
360 let before = self.allowed.len();
361 self.allowed.extend_from_slice(keys);
362 let r = f(self);
363 self.allowed.truncate(before);
364 r
365 }
366
367 pub fn own_keys<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
369 let saved = std::mem::take(&mut self.allowed);
370 let r = f(self);
371 self.allowed = saved;
372 r
373 }
374}
375
376fn found_text(v: &ValueRef) -> String {
377 match v {
378 ValueRef::Str(s) if s.chars().count() <= 40 => format!("{s:?}"),
379 ValueRef::Int(n) => n.to_string(),
380 ValueRef::Float(f) => f.to_string(),
381 ValueRef::Bool(b) => b.to_string(),
382 other => other.kind_name().to_string(),
383 }
384}
385
386fn closest<'k>(key: &str, known: &[&'k str]) -> Option<&'k str> {
389 let lower = key.to_ascii_lowercase();
390 known
391 .iter()
392 .map(|k| (edit_distance(&lower, k), *k))
393 .filter(|(d, k)| *d <= if k.len() <= 3 { 1 } else { k.len() / 2 })
394 .min_by_key(|(d, _)| *d)
395 .map(|(_, k)| k)
396}
397
398fn edit_distance(a: &str, b: &str) -> usize {
399 let b: Vec<char> = b.chars().collect();
400 let mut prev: Vec<usize> = (0..=b.len()).collect();
401 for (i, ca) in a.chars().enumerate() {
402 let mut cur = vec![i + 1; b.len() + 1];
403 for (j, cb) in b.iter().enumerate() {
404 cur[j + 1] = (prev[j] + usize::from(ca != *cb))
405 .min(prev[j + 1] + 1)
406 .min(cur[j] + 1);
407 }
408 prev = cur;
409 }
410 prev[b.len()]
411}
412
413#[derive(Debug, Clone, PartialEq, Eq)]
415pub struct Errors(pub Vec<Located>);
416
417#[derive(Debug, Clone, PartialEq, Eq)]
419pub struct Located {
420 pub at: Option<LineCol>,
421 pub path: Path,
422 pub message: String,
423}
424
425impl fmt::Display for Located {
426 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
427 if let Some(at) = self.at {
428 write!(f, "{at}: ")?;
429 }
430 if !self.path.is_root() {
431 write!(f, "{}: ", self.path)?;
432 }
433 f.write_str(&self.message)
434 }
435}
436
437impl fmt::Display for Errors {
438 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439 for (i, e) in self.0.iter().enumerate() {
440 if i > 0 {
441 f.write_str("\n")?;
442 }
443 write!(f, "{e}")?;
444 }
445 Ok(())
446 }
447}
448
449impl std::error::Error for Errors {}
450
451#[derive(Debug)]
453pub struct Read<T> {
454 pub value: T,
455 pub refs: Vec<Found<Ref>>,
456 pub tags: Vec<Found<Ref>>,
457 pub resources: Vec<Found<Res>>,
458}
459
460pub trait Numbers {
462 fn numbers(&self, out: &mut Vec<f64>);
463}
464
465macro_rules! numbers {
466 ($($t:ty),*) => {$(
467 impl Numbers for $t {
468 fn numbers(&self, out: &mut Vec<f64>) {
469 out.push(*self as f64);
470 }
471 }
472 )*};
473}
474numbers!(i8, i16, i32, i64, u8, u16, u32, u64, usize, f32, f64);
475
476impl<T: Numbers> Numbers for Option<T> {
477 fn numbers(&self, out: &mut Vec<f64>) {
478 if let Some(x) = self {
479 x.numbers(out);
480 }
481 }
482}
483
484impl<T: Numbers> Numbers for Vec<T> {
485 fn numbers(&self, out: &mut Vec<f64>) {
486 self.iter().for_each(|x| x.numbers(out));
487 }
488}
489
490impl<T: Numbers, const N: usize> Numbers for [T; N] {
491 fn numbers(&self, out: &mut Vec<f64>) {
492 self.iter().for_each(|x| x.numbers(out));
493 }
494}
495
496impl Reader<'_> {
497 pub fn in_range<T: Numbers>(
499 &mut self,
500 key: &str,
501 value: Option<T>,
502 lo: f64,
503 hi: f64,
504 ) -> Option<T> {
505 let v = value?;
506 let mut ns = Vec::new();
507 v.numbers(&mut ns);
508 match ns.into_iter().find(|n| !(lo..=hi).contains(n)) {
509 None => Some(v),
510 Some(n) => {
511 let show = |x: f64| {
512 if x.fract() == 0.0 {
513 format!("{}", x as i64)
514 } else {
515 x.to_string()
516 }
517 };
518 let bad = show(n);
519 self.at(Seg::Key(key.to_string()), |cx| {
520 cx.problem_of(
521 yog_ids::problem_code::OUT_OF_RANGE,
522 format!("{bad} is out of range ({}..={})", show(lo), show(hi)),
523 )
524 });
525 None
526 }
527 }
528 }
529}