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