Skip to main content

tramlines_core/grammar/
registry.rs

1//! The three-layer registry of line formats and the hole types they are
2//! written with (TL_34 to TL_41).
3
4use std::collections::BTreeMap;
5
6use saphyr::{Mapping, Scalar, Sequence, Yaml as SYaml, YamlEmitter};
7
8use super::{
9    Format, Grammar, RegexCache, TypeDef, TypeRegistry, format::lower_format, patch::merge_patch,
10    validate,
11};
12use crate::{Finding, Problem, RepoPath, Result, Spanned, Yaml, yaml};
13
14/// Where a registry entry comes from.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum FormatSource {
17    /// Defined by the tool itself: the built-in types.
18    BuiltIn,
19    /// The tool's embedded formats.
20    Embedded,
21    /// The host's layer, as a formats file the host reads.
22    Repo,
23    /// A formats file that the caller names, by its path as the caller
24    /// spells it.
25    File(String),
26    /// A schema's `formats`, by schema id.
27    Schema(String),
28}
29
30impl FormatSource {
31    /// The `source` value a printed registry carries.
32    pub fn label(&self) -> &str {
33        match self {
34            FormatSource::BuiltIn => "built-in",
35            FormatSource::Embedded => "embedded",
36            FormatSource::Repo => "repo",
37            FormatSource::File(path) => path,
38            FormatSource::Schema(id) => id,
39        }
40    }
41}
42
43// sokf:begin format-registry
44/// A named set of formats, each with its source, and the hole types the
45/// layers declared.
46#[derive(Debug, Clone, Default)]
47pub struct FormatRegistry {
48    /// The entries, by name.
49    pub entries: BTreeMap<String, (Format, FormatSource)>,
50    /// The types the layers declared, the built-ins apart.
51    pub types: TypeRegistry,
52}
53// sokf:end format-registry
54
55impl FormatRegistry {
56    /// A host's embedded registry: the built-in types it adds, reserved as
57    /// the engine's are, then the formats file `text`, whose entries are
58    /// `embedded`. A file that does not read is a defect of the build.
59    // sokf:begin format-registry
60    pub fn from_text(
61        text: &str,
62        built_ins: &[(&str, &str)],
63        grammar: &Grammar,
64        regexes: &RegexCache,
65    ) -> Result<Self> {
66        // sokf:end format-registry
67        // sokf-implements: CORE-5_AC-9
68        let fail = |m: String| crate::Error::Internal(format!("embedded formats: {m}"));
69        // sokf-implements: CORE-5_AC-7
70        let base = FormatRegistry {
71            entries: BTreeMap::new(),
72            types: TypeRegistry::reserved(built_ins),
73        };
74        let root = yaml::parse(text, 1).map_err(|e| fail(e.to_string()))?;
75        let registry = root
76            .get("registry")
77            .ok_or_else(|| fail("no registry".into()))?;
78        let own = Self::from_yaml_at(
79            registry,
80            root.get("types"),
81            FormatSource::Embedded,
82            grammar,
83            regexes,
84            &RepoPath::root(),
85            Some(&base),
86        )
87        .map_err(|findings| {
88            fail(
89                findings
90                    .iter()
91                    .map(|f| f.message.clone())
92                    .collect::<Vec<_>>()
93                    .join("; "),
94            )
95        })?;
96        Ok(base.layered(&own))
97    }
98
99    /// Build a registry from a `registry` map with no types of its own,
100    /// validating each entry against grammar `format`. Findings are on `/`.
101    // sokf-implements: TL_34
102    // sokf-implements: TL_36
103    pub fn from_yaml(
104        registry: &Spanned,
105        source: FormatSource,
106        grammar: &Grammar,
107        regexes: &RegexCache,
108    ) -> std::result::Result<Self, Vec<Finding>> {
109        Self::from_yaml_at(
110            registry,
111            None,
112            source,
113            grammar,
114            regexes,
115            &RepoPath::root(),
116            None,
117        )
118    }
119
120    /// As `from_yaml`, with the layer's `types` map, findings on `path`,
121    /// and the farther layers in `base`: an entry declaring `patch` is
122    /// merged onto the same-named entry of `base` before it is checked
123    /// (grammar `format`), and every template compiles against the types of
124    /// `base` with this layer's on top.
125    // sokf-implements: TL_27
126    // sokf-implements: CORE-5_AC-1
127    // sokf-implements: CORE-5_AC-2
128    // sokf-implements: TL_35
129    // sokf:begin format-registry
130    pub fn from_yaml_at(
131        registry: &Spanned,
132        types: Option<&Spanned>,
133        source: FormatSource,
134        grammar: &Grammar,
135        regexes: &RegexCache,
136        path: &RepoPath,
137        base: Option<&FormatRegistry>,
138    ) -> std::result::Result<Self, Vec<Finding>> {
139        // sokf:end format-registry
140        let (out, findings) = Self::layer(registry, types, source, grammar, regexes, path, base);
141        if findings.is_empty() {
142            Ok(out)
143        } else {
144            Err(findings)
145        }
146    }
147
148    /// As `from_yaml_at`, keeping what loads beside the findings of what
149    /// does not: each type and format that fails is reported once, and
150    /// what uses it reports nothing more. A type that fails stands as one
151    /// that matches anything, so a template that names it still compiles;
152    /// a format that fails is left out.
153    // sokf-implements: TL_125
154    // sokf-implements: CORE-5_AC-12
155    // sokf:begin format-registry
156    pub fn layer(
157        registry: &Spanned,
158        types: Option<&Spanned>,
159        source: FormatSource,
160        grammar: &Grammar,
161        regexes: &RegexCache,
162        path: &RepoPath,
163        base: Option<&FormatRegistry>,
164    ) -> (Self, Vec<Finding>) {
165        // sokf:end format-registry
166        let fail = |line: usize, problem: Problem| problem.error(path.clone(), Some(line));
167        let mut findings = Vec::new();
168        let below = base.map(|b| b.types.clone()).unwrap_or_default();
169        let own_types = match types {
170            Some(node) => {
171                // sokf-implements: CORE-5_AC-8
172                let (mut own, problems) = TypeRegistry::from_yaml(node, &source, &below);
173                findings.extend(problems.into_iter().map(|(l, m)| fail(l, m)));
174                let failed = node
175                    .as_map()
176                    .unwrap_or(&[])
177                    .iter()
178                    .map(|(name, _)| &name.value)
179                    .filter(|name| !own.entries.contains_key(*name) && !below.reserves(name));
180                for name in failed.collect::<Vec<_>>() {
181                    own.entries
182                        .insert(name.clone(), (TypeDef::Regex(".*?".into()), source.clone()));
183                }
184                own
185            }
186            None => TypeRegistry::default(),
187        };
188        let seen = TypeRegistry::built_ins()
189            .layered(&below)
190            .layered(&own_types);
191        // A layer's registry carries its own types alone; the farther
192        // layers' come back when it is layered on them.
193        let mut out = FormatRegistry {
194            entries: BTreeMap::new(),
195            types: own_types,
196        };
197        // sokf-implements: CORE-5_AC-5
198        let Some(entries) = registry.as_map() else {
199            findings.push(fail(
200                registry.line,
201                Problem::new("TL_101", "the format registry must be a map of formats"),
202            ));
203            return (out, findings);
204        };
205        for (name, whole) in entries {
206            // A patch says how it differs from the entry it shadows, so it
207            // is merged first and checked as the entry it becomes.
208            // sokf-implements: CORE-5_AC-3
209            // sokf-implements: TL_37
210            // sokf-implements: TL_41
211            let merged;
212            let entry = if whole.get("patch").and_then(|p| p.as_bool()) == Some(true) {
213                match base.and_then(|b| b.entries.get(&name.value)) {
214                    Some((f, _)) => {
215                        merged = merge_patch(&f.raw, whole);
216                        &merged
217                    }
218                    None => {
219                        findings.push(fail(
220                            whole.line,
221                            Problem::new(
222                                "TL_41",
223                                format!("format `{}` patches no farther entry", name.value),
224                            ),
225                        ));
226                        continue;
227                    }
228                }
229            } else {
230                whole
231            };
232            // sokf-implements: CORE-5_AC-4
233            let mut problems =
234                validate::validate_map(entry, "format", grammar, regexes, path, None);
235            if problems.is_empty() {
236                match lower_format(entry, &seen, regexes) {
237                    Ok(format) => {
238                        out.entries
239                            .insert(name.value.clone(), (format, source.clone()));
240                        continue;
241                    }
242                    Err(said) => problems.extend(said.into_iter().map(|(l, m)| fail(l, m))),
243                }
244            }
245            // sokf-implements: CORE-5_AC-6
246            for p in &mut problems {
247                p.message = format!("format `{}`: {}", name.value, p.message);
248            }
249            findings.extend(problems);
250        }
251        (out, findings)
252    }
253
254    /// The types a schema under this registry sees: the built-ins, then
255    /// the layers' own.
256    pub fn types_seen(&self) -> TypeRegistry {
257        TypeRegistry::built_ins().layered(&self.types)
258    }
259
260    /// This registry with `over` on top: a same-named entry of `over`
261    /// replaces this one whole, and a same-named type likewise. An entry of
262    /// `over` that declared a patch was merged onto the farther one when it
263    /// was built. Where `over` declares types, this registry's entries are
264    /// compiled again against them, so a type means one thing to every
265    /// format a schema sees.
266    // sokf-implements: TL_34
267    // sokf-implements: TL_36
268    // sokf:begin format-registry
269    // sokf-implements: TL_51
270    pub fn layered(&self, over: &FormatRegistry) -> FormatRegistry {
271        // sokf:end format-registry
272        let types = self.types.layered(&over.types);
273        let seen = TypeRegistry::built_ins().layered(&types);
274        let regexes = RegexCache::new();
275        let mut entries = self.entries.clone();
276        // sokf-implements: CORE-5_AC-11
277        // Only a type this registry already defines can change one of its
278        // formats: a new name is one no farther template uses.
279        let redefined: Vec<&str> = over
280            .types
281            .entries
282            .keys()
283            .filter(|n| self.types.entries.contains_key(*n))
284            .map(String::as_str)
285            .collect();
286        if !redefined.is_empty() {
287            for (format, _) in entries.values_mut() {
288                if format.uses_any(&redefined)
289                    && let Ok(again) = lower_format(&format.raw, &seen, &regexes)
290                {
291                    *format = again;
292                }
293            }
294        }
295        // sokf-implements: CORE-5_AC-10
296        for (name, entry) in &over.entries {
297            entries.insert(name.clone(), entry.clone());
298        }
299        FormatRegistry { entries, types }
300    }
301
302    /// The format named `name`.
303    // sokf:begin format-registry
304    pub fn get(&self, name: &str) -> Option<&Format> {
305        // sokf:end format-registry
306        self.entries.get(name).map(|(f, _)| f)
307    }
308
309    /// The registry as one YAML document in the shape of
310    /// a formats file, each type and entry carrying `source`. The
311    /// built-in types are printed where `built_ins` asks for them.
312    pub fn to_yaml(&self, built_ins: bool) -> String {
313        let types = if built_ins {
314            self.types_seen()
315        } else {
316            self.types.clone()
317        };
318        let mut type_map = Mapping::new();
319        for (name, (def, source)) in &types.entries {
320            let value = match def {
321                TypeDef::Regex(r) => ystr(r),
322                TypeDef::Choices(c) => SYaml::Sequence(c.iter().map(ystr).collect::<Sequence>()),
323            };
324            let mut entry = Mapping::new();
325            entry.insert(ystr("match"), value);
326            entry.insert(ystr("source"), ystr(source.label()));
327            type_map.insert(ystr(name), SYaml::Mapping(entry));
328        }
329        let mut registry = Mapping::new();
330        for (name, (format, source)) in &self.entries {
331            let mut entry = match to_saphyr(&format.raw) {
332                SYaml::Mapping(m) => m,
333                _ => Mapping::new(),
334            };
335            entry.insert(ystr("source"), ystr(source.label()));
336            registry.insert(ystr(name), SYaml::Mapping(entry));
337        }
338        let mut root = Mapping::new();
339        if !type_map.is_empty() {
340            root.insert(ystr("types"), SYaml::Mapping(type_map));
341        }
342        root.insert(ystr("registry"), SYaml::Mapping(registry));
343        let mut out = String::new();
344        let mut emitter = YamlEmitter::new(&mut out);
345        emitter.multiline_strings(true);
346        emitter
347            .dump(&SYaml::Mapping(root))
348            .expect("emitting to a string cannot fail");
349        out.push('\n');
350        out.strip_prefix("---\n").map(str::to_string).unwrap_or(out)
351    }
352}
353
354fn to_saphyr(node: &Spanned) -> SYaml<'static> {
355    match &node.value {
356        Yaml::Null => SYaml::Value(Scalar::Null),
357        Yaml::Bool(b) => SYaml::Value(Scalar::Boolean(*b)),
358        Yaml::Int(i) => SYaml::Value(Scalar::Integer(*i)),
359        Yaml::Float(f) => SYaml::Value(Scalar::FloatingPoint((*f).into())),
360        Yaml::Str(s) => ystr(s),
361        Yaml::Seq(items) => SYaml::Sequence(items.iter().map(to_saphyr).collect::<Sequence>()),
362        Yaml::Map(entries) => {
363            let mut m = Mapping::new();
364            for (k, v) in entries {
365                m.insert(ystr(&k.value), to_saphyr(v));
366            }
367            SYaml::Mapping(m)
368        }
369    }
370}
371
372/// A YAML string scalar of `s`.
373fn ystr(s: impl AsRef<str>) -> SYaml<'static> {
374    SYaml::Value(Scalar::String(s.as_ref().to_string().into()))
375}