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