Skip to main content

xidl_parser/hir/
spec.rs

1use super::{
2    Definition, InterfaceDcl, ModuleDcl, ParserProperties, Specification, TypeDcl,
3    expand_annotations, interface_codegen, parse_xidlc_pragma,
4};
5use crate::jsonrpc_hir;
6use crate::rest_hir::{self, HirProjectionKind, ProjectedHir};
7use crate::semantic;
8use serde_json::Value;
9use std::path::Path;
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    )
66    .expect("HIR conversion should not fail");
67    let mut spec = Specification(definitions);
68    semantic::analyze(&mut spec);
69    spec
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 mut definitions = Vec::new();
78    collect_defs_with_context(
79        value.0,
80        &mut Vec::new(),
81        expand_interfaces,
82        &mut definitions,
83    )?;
84    let mut spec = Specification(definitions);
85    semantic::analyze(&mut spec);
86    Ok(spec)
87}
88
89fn collect_defs_with_context(
90    defs: Vec<crate::typed_ast::Definition>,
91    modules: &mut Vec<String>,
92    expand_interfaces: bool,
93    out: &mut Vec<Definition>,
94) -> crate::error::ParserResult<()> {
95    for def in defs {
96        match def {
97            crate::typed_ast::Definition::ModuleDcl(module) => {
98                let ident = module.ident.0;
99                let annotations = expand_annotations(module.annotations);
100                modules.push(ident.clone());
101                let mut inner = Vec::new();
102                collect_defs_with_context(
103                    module.definition,
104                    modules,
105                    expand_interfaces,
106                    &mut inner,
107                )?;
108                modules.pop();
109                out.push(Definition::ModuleDcl(ModuleDcl {
110                    annotations,
111                    ident,
112                    definition: inner,
113                }));
114            }
115            crate::typed_ast::Definition::PreprocCall(call) => {
116                if let Some(pragma) = parse_xidlc_pragma(&call) {
117                    out.push(Definition::Pragma(pragma));
118                }
119            }
120            crate::typed_ast::Definition::TypeDcl(value) => {
121                out.push(Definition::TypeDcl(TypeDcl::from(value)))
122            }
123            crate::typed_ast::Definition::ConstDcl(value) => {
124                out.push(Definition::ConstDcl(value.into()))
125            }
126            crate::typed_ast::Definition::ExceptDcl(value) => {
127                out.push(Definition::ExceptDcl(value.into()))
128            }
129            crate::typed_ast::Definition::InterfaceDcl(value) => {
130                let interface = InterfaceDcl::from(value);
131                if expand_interfaces {
132                    let generated = interface_codegen::expand_interface(&interface, modules)
133                        .unwrap_or_else(|err| panic!("interface expansion failed: {err}"));
134                    out.extend(generated);
135                }
136                out.push(Definition::InterfaceDcl(interface));
137            }
138            crate::typed_ast::Definition::PreprocInclude(_) => {
139                // Includes are now handled at the tree-sitter stage.
140                // If we see a PreprocInclude here, it means it was not expanded.
141            }
142            crate::typed_ast::Definition::TemplateModuleDcl(_)
143            | crate::typed_ast::Definition::TemplateModuleInst(_)
144            | crate::typed_ast::Definition::PreprocDefine(_) => {}
145        }
146    }
147
148    Ok(())
149}
150
151fn expand_interface(properties: &ParserProperties) -> bool {
152    if let Some(expand) = properties.get("expand_interface").and_then(Value::as_bool) {
153        return expand;
154    }
155
156    matches!(hir_projection_kind(properties), HirProjectionKind::Rpc)
157}
158
159fn hir_projection_kind(properties: &ParserProperties) -> HirProjectionKind {
160    match properties.get("hir_kind").and_then(Value::as_str) {
161        Some(value) if value.eq_ignore_ascii_case("http") => HirProjectionKind::Http,
162        Some(value)
163            if value.eq_ignore_ascii_case("jsonrpc") || value.eq_ignore_ascii_case("json-rpc") =>
164        {
165            HirProjectionKind::JsonRpc
166        }
167        _ => HirProjectionKind::Rpc,
168    }
169}