Skip to main content

chipi_core/bindings/
resolve.rs

1//! Bindings include resolution.
2//!
3//! Parses every `*.chipi` spec referenced by the bindings file. Resolves
4//! includes transitively. Offers lookup by decoder name.
5
6use std::collections::HashMap;
7use std::path::PathBuf;
8
9use crate::error::Error;
10use crate::parser::parse_file_with_deps;
11use crate::types::{ValidatedDef, ValidatedSubDecoder};
12use crate::validate::validate;
13
14use super::types::BindingsFile;
15
16/// A bindings file with all includes resolved and every spec parsed and
17/// validated.
18pub struct ResolvedBindings {
19    pub file: BindingsFile,
20    /// Every spec or bindings file touched. Used for
21    /// `cargo:rerun-if-changed`.
22    pub all_files: Vec<PathBuf>,
23    /// One entry per included spec file. Each entry is post-validate.
24    pub specs: Vec<ResolvedSpec>,
25}
26
27pub struct ResolvedSpec {
28    pub path: PathBuf,
29    pub def: ValidatedDef,
30}
31
32impl ResolvedBindings {
33    /// Find the spec that defines a top-level `decoder` named `name`.
34    /// Returns `None` if no spec defines it.
35    pub fn find_decoder(&self, name: &str) -> Option<&ResolvedSpec> {
36        self.specs.iter().find(|s| s.def.config.name == name)
37    }
38
39    /// Find the parent spec and sub-decoder where a sub-decoder named
40    /// `name` is defined.
41    pub fn find_subdecoder(&self, name: &str) -> Option<(&ResolvedSpec, &ValidatedSubDecoder)> {
42        for spec in &self.specs {
43            for sd in &spec.def.sub_decoders {
44                if sd.name == name {
45                    return Some((spec, sd));
46                }
47            }
48        }
49        None
50    }
51
52    /// Find a decoder by name. Accepts a top-level decoder or a sub-decoder.
53    pub fn find_decoder_or_sub(
54        &self,
55        name: &str,
56    ) -> Option<(&ResolvedSpec, Option<&ValidatedSubDecoder>)> {
57        if let Some(s) = self.find_decoder(name) {
58            return Some((s, None));
59        }
60        if let Some((s, sd)) = self.find_subdecoder(name) {
61            return Some((s, Some(sd)));
62        }
63        None
64    }
65
66    /// Names of every reachable top-level decoder and sub-decoder.
67    pub fn all_decoder_names(&self) -> Vec<String> {
68        let mut names = Vec::new();
69        for spec in &self.specs {
70            names.push(spec.def.config.name.clone());
71            for sd in &spec.def.sub_decoders {
72                names.push(sd.name.clone());
73            }
74        }
75        names
76    }
77}
78
79/// Resolve a parsed bindings file. Loads and validates every referenced
80/// spec. Returns everything packaged together.
81pub fn resolve(file: BindingsFile) -> Result<ResolvedBindings, Vec<Error>> {
82    let mut all_files: Vec<PathBuf> = Vec::new();
83    all_files.push(file.path.clone());
84    for (p, _) in &file.bindings_includes {
85        all_files.push(p.clone());
86    }
87
88    // De-duplicate spec includes by canonical path
89    let mut seen: HashMap<PathBuf, ()> = HashMap::new();
90    let mut specs: Vec<ResolvedSpec> = Vec::new();
91    let mut errors: Vec<Error> = Vec::new();
92
93    for (path, _span) in &file.spec_includes {
94        if seen.contains_key(path) {
95            continue;
96        }
97        seen.insert(path.clone(), ());
98
99        let (def, deps) = match parse_file_with_deps(path) {
100            Ok(v) => v,
101            Err(errs) => {
102                errors.extend(errs);
103                continue;
104            }
105        };
106
107        for dep in deps {
108            if !all_files.contains(&dep) {
109                all_files.push(dep);
110            }
111        }
112        if !all_files.contains(path) {
113            all_files.push(path.clone());
114        }
115
116        match validate(&def) {
117            Ok(v) => specs.push(ResolvedSpec {
118                path: path.clone(),
119                def: v,
120            }),
121            Err(errs) => errors.extend(errs),
122        }
123    }
124
125    if !errors.is_empty() {
126        return Err(errors);
127    }
128
129    Ok(ResolvedBindings {
130        file,
131        all_files,
132        specs,
133    })
134}