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}