Skip to main content

xidl_parser/hir/
spec.rs

1use super::{
2    Definition, InterfaceDcl, ModuleDcl, ParserProperties, Specification, TypeDcl,
3    expand_annotations, include, interface_codegen, parse_xidlc_pragma,
4};
5use serde_json::Value;
6use std::fs;
7use std::path::{Path, PathBuf};
8
9impl From<crate::typed_ast::Specification> for Specification {
10    fn from(value: crate::typed_ast::Specification) -> Self {
11        spec_from_typed_ast(value, true)
12    }
13}
14
15impl Specification {
16    pub fn from_typed_ast_with_properties(
17        value: crate::typed_ast::Specification,
18        properties: ParserProperties,
19    ) -> Self {
20        spec_from_typed_ast(value, expand_interface(&properties))
21    }
22
23    pub fn from_typed_ast_with_properties_and_path(
24        value: crate::typed_ast::Specification,
25        properties: ParserProperties,
26        path: impl AsRef<Path>,
27    ) -> crate::error::ParserResult<Self> {
28        spec_from_typed_ast_with_path(value, expand_interface(&properties), path.as_ref())
29    }
30
31    pub fn from_typed_ast_with_path(
32        value: crate::typed_ast::Specification,
33        path: impl AsRef<Path>,
34    ) -> crate::error::ParserResult<Self> {
35        spec_from_typed_ast_with_path(value, true, path.as_ref())
36    }
37}
38
39pub(crate) fn spec_from_typed_ast(
40    value: crate::typed_ast::Specification,
41    expand_interfaces: bool,
42) -> Specification {
43    let mut definitions = Vec::new();
44    collect_defs_with_context(
45        value.0,
46        &mut Vec::new(),
47        expand_interfaces,
48        &mut definitions,
49        None,
50        None,
51    )
52    .expect("pathless HIR conversion should not fail");
53    Specification(definitions)
54}
55
56fn spec_from_typed_ast_with_path(
57    value: crate::typed_ast::Specification,
58    expand_interfaces: bool,
59    path: &Path,
60) -> crate::error::ParserResult<Specification> {
61    let root = include::normalize_path(path);
62    let mut definitions = Vec::new();
63    let mut include_stack = vec![root.clone()];
64    collect_defs_with_context(
65        value.0,
66        &mut Vec::new(),
67        expand_interfaces,
68        &mut definitions,
69        Some(root.as_path()),
70        Some(&mut include_stack),
71    )?;
72    Ok(Specification(definitions))
73}
74
75fn collect_defs_with_context(
76    defs: Vec<crate::typed_ast::Definition>,
77    modules: &mut Vec<String>,
78    expand_interfaces: bool,
79    out: &mut Vec<Definition>,
80    current_file: Option<&Path>,
81    mut include_stack: Option<&mut Vec<PathBuf>>,
82) -> crate::error::ParserResult<()> {
83    for def in defs {
84        match def {
85            crate::typed_ast::Definition::ModuleDcl(module) => {
86                let ident = module.ident.0;
87                let annotations = expand_annotations(module.annotations);
88                modules.push(ident.clone());
89                let mut inner = Vec::new();
90                collect_defs_with_context(
91                    module.definition,
92                    modules,
93                    expand_interfaces,
94                    &mut inner,
95                    current_file,
96                    include_stack.as_deref_mut(),
97                )?;
98                modules.pop();
99                out.push(Definition::ModuleDcl(ModuleDcl {
100                    annotations,
101                    ident,
102                    definition: inner,
103                }));
104            }
105            crate::typed_ast::Definition::PreprocCall(call) => {
106                if let Some(pragma) = parse_xidlc_pragma(&call) {
107                    out.push(Definition::Pragma(pragma));
108                }
109            }
110            crate::typed_ast::Definition::TypeDcl(value) => {
111                out.push(Definition::TypeDcl(TypeDcl::from(value)))
112            }
113            crate::typed_ast::Definition::ConstDcl(value) => {
114                out.push(Definition::ConstDcl(value.into()))
115            }
116            crate::typed_ast::Definition::ExceptDcl(value) => {
117                out.push(Definition::ExceptDcl(value.into()))
118            }
119            crate::typed_ast::Definition::InterfaceDcl(value) => {
120                let interface = InterfaceDcl::from(value);
121                if expand_interfaces {
122                    let generated = interface_codegen::expand_interface(&interface, modules)
123                        .unwrap_or_else(|err| panic!("interface expansion failed: {err}"));
124                    out.extend(generated);
125                }
126                out.push(Definition::InterfaceDcl(interface));
127            }
128            crate::typed_ast::Definition::PreprocInclude(include_def) => {
129                let Some(current_file) = current_file else {
130                    continue;
131                };
132                let path = include::resolve_include_path(current_file, &include_def)?;
133                let typed = parse_included_specification(&path)?;
134                let stack = include_stack
135                    .as_deref_mut()
136                    .expect("include stack must exist when current file path is set");
137                guard_include_cycle(stack, &path)?;
138                stack.push(path.clone());
139                collect_defs_with_context(
140                    typed.0,
141                    modules,
142                    expand_interfaces,
143                    out,
144                    Some(path.as_path()),
145                    Some(stack),
146                )?;
147                stack.pop();
148            }
149            crate::typed_ast::Definition::TemplateModuleDcl(_)
150            | crate::typed_ast::Definition::TemplateModuleInst(_)
151            | crate::typed_ast::Definition::PreprocDefine(_) => {}
152        }
153    }
154
155    Ok(())
156}
157
158fn expand_interface(properties: &ParserProperties) -> bool {
159    properties
160        .get("expand_interface")
161        .and_then(Value::as_bool)
162        .unwrap_or(true)
163}
164
165fn parse_included_specification(
166    path: &Path,
167) -> crate::error::ParserResult<crate::typed_ast::Specification> {
168    let source = fs::read_to_string(path).map_err(|err| {
169        crate::error::ParseError::Message(format!(
170            "failed to read include '{}': {err}",
171            path.display()
172        ))
173    })?;
174    crate::parser::parser_text(&source).map_err(|err| {
175        crate::error::ParseError::Message(format!(
176            "failed to parse include '{}': {err}",
177            path.display()
178        ))
179    })
180}
181
182fn guard_include_cycle(stack: &[PathBuf], path: &Path) -> crate::error::ParserResult<()> {
183    if stack.contains(&path.to_path_buf()) {
184        let chain = stack
185            .iter()
186            .chain(std::iter::once(&path.to_path_buf()))
187            .map(|path| path.display().to_string())
188            .collect::<Vec<_>>()
189            .join(" -> ");
190        return Err(crate::error::ParseError::Message(format!(
191            "cyclic include detected: {chain}"
192        )));
193    }
194    Ok(())
195}