use super::{
Definition, InterfaceDcl, ModuleDcl, ParserProperties, Specification, TypeDcl,
expand_annotations, include, interface_codegen, parse_xidlc_pragma,
};
use crate::jsonrpc_hir;
use crate::rest_hir::{self, HirProjectionKind, ProjectedHir};
use serde_json::Value;
use std::fs;
use std::path::{Path, PathBuf};
impl From<crate::typed_ast::Specification> for Specification {
fn from(value: crate::typed_ast::Specification) -> Self {
spec_from_typed_ast(value, true)
}
}
impl Specification {
pub fn from_typed_ast_with_properties(
value: crate::typed_ast::Specification,
properties: ParserProperties,
) -> Self {
spec_from_typed_ast(value, expand_interface(&properties))
}
pub fn from_typed_ast_with_properties_and_path(
value: crate::typed_ast::Specification,
properties: ParserProperties,
path: impl AsRef<Path>,
) -> crate::error::ParserResult<Self> {
spec_from_typed_ast_with_path(value, expand_interface(&properties), path.as_ref())
}
pub fn from_typed_ast_with_path(
value: crate::typed_ast::Specification,
path: impl AsRef<Path>,
) -> crate::error::ParserResult<Self> {
spec_from_typed_ast_with_path(value, true, path.as_ref())
}
pub fn project_typed_ast_with_properties_and_path(
value: crate::typed_ast::Specification,
properties: ParserProperties,
path: impl AsRef<Path>,
) -> crate::error::ParserResult<ProjectedHir> {
let spec =
spec_from_typed_ast_with_path(value, expand_interface(&properties), path.as_ref())?;
match hir_projection_kind(&properties) {
HirProjectionKind::Rpc => Ok(ProjectedHir::Rpc(spec)),
HirProjectionKind::Http => rest_hir::project(&spec).map(ProjectedHir::Http),
HirProjectionKind::JsonRpc => jsonrpc_hir::project(&spec).map(ProjectedHir::JsonRpc),
}
}
}
pub(crate) fn spec_from_typed_ast(
value: crate::typed_ast::Specification,
expand_interfaces: bool,
) -> Specification {
let mut definitions = Vec::new();
collect_defs_with_context(
value.0,
&mut Vec::new(),
expand_interfaces,
&mut definitions,
None,
None,
)
.expect("pathless HIR conversion should not fail");
Specification(definitions)
}
fn spec_from_typed_ast_with_path(
value: crate::typed_ast::Specification,
expand_interfaces: bool,
path: &Path,
) -> crate::error::ParserResult<Specification> {
let root = include::normalize_path(path);
let mut definitions = Vec::new();
let mut include_stack = vec![root.clone()];
collect_defs_with_context(
value.0,
&mut Vec::new(),
expand_interfaces,
&mut definitions,
Some(root.as_path()),
Some(&mut include_stack),
)?;
Ok(Specification(definitions))
}
fn collect_defs_with_context(
defs: Vec<crate::typed_ast::Definition>,
modules: &mut Vec<String>,
expand_interfaces: bool,
out: &mut Vec<Definition>,
current_file: Option<&Path>,
mut include_stack: Option<&mut Vec<PathBuf>>,
) -> crate::error::ParserResult<()> {
for def in defs {
match def {
crate::typed_ast::Definition::ModuleDcl(module) => {
let ident = module.ident.0;
let annotations = expand_annotations(module.annotations);
modules.push(ident.clone());
let mut inner = Vec::new();
collect_defs_with_context(
module.definition,
modules,
expand_interfaces,
&mut inner,
current_file,
include_stack.as_deref_mut(),
)?;
modules.pop();
out.push(Definition::ModuleDcl(ModuleDcl {
annotations,
ident,
definition: inner,
}));
}
crate::typed_ast::Definition::PreprocCall(call) => {
if let Some(pragma) = parse_xidlc_pragma(&call) {
out.push(Definition::Pragma(pragma));
}
}
crate::typed_ast::Definition::TypeDcl(value) => {
out.push(Definition::TypeDcl(TypeDcl::from(value)))
}
crate::typed_ast::Definition::ConstDcl(value) => {
out.push(Definition::ConstDcl(value.into()))
}
crate::typed_ast::Definition::ExceptDcl(value) => {
out.push(Definition::ExceptDcl(value.into()))
}
crate::typed_ast::Definition::InterfaceDcl(value) => {
let interface = InterfaceDcl::from(value);
if expand_interfaces {
let generated = interface_codegen::expand_interface(&interface, modules)
.unwrap_or_else(|err| panic!("interface expansion failed: {err}"));
out.extend(generated);
}
out.push(Definition::InterfaceDcl(interface));
}
crate::typed_ast::Definition::PreprocInclude(include_def) => {
let Some(current_file) = current_file else {
continue;
};
let path = include::resolve_include_path(current_file, &include_def)?;
let typed = parse_included_specification(&path)?;
let stack = include_stack
.as_deref_mut()
.expect("include stack must exist when current file path is set");
guard_include_cycle(stack, &path)?;
stack.push(path.clone());
collect_defs_with_context(
typed.0,
modules,
expand_interfaces,
out,
Some(path.as_path()),
Some(stack),
)?;
stack.pop();
}
crate::typed_ast::Definition::TemplateModuleDcl(_)
| crate::typed_ast::Definition::TemplateModuleInst(_)
| crate::typed_ast::Definition::PreprocDefine(_) => {}
}
}
Ok(())
}
fn expand_interface(properties: &ParserProperties) -> bool {
properties
.get("expand_interface")
.and_then(Value::as_bool)
.unwrap_or(true)
}
fn hir_projection_kind(properties: &ParserProperties) -> HirProjectionKind {
match properties.get("hir_kind").and_then(Value::as_str) {
Some(value) if value.eq_ignore_ascii_case("http") => HirProjectionKind::Http,
Some(value)
if value.eq_ignore_ascii_case("jsonrpc") || value.eq_ignore_ascii_case("json-rpc") =>
{
HirProjectionKind::JsonRpc
}
_ => HirProjectionKind::Rpc,
}
}
fn parse_included_specification(
path: &Path,
) -> crate::error::ParserResult<crate::typed_ast::Specification> {
let source = fs::read_to_string(path).map_err(|err| {
crate::error::ParseError::Message(format!(
"failed to read include '{}': {err}",
path.display()
))
})?;
crate::parser::parser_text(&source).map_err(|err| {
crate::error::ParseError::Message(format!(
"failed to parse include '{}': {err}",
path.display()
))
})
}
fn guard_include_cycle(stack: &[PathBuf], path: &Path) -> crate::error::ParserResult<()> {
if stack.contains(&path.to_path_buf()) {
let chain = stack
.iter()
.chain(std::iter::once(&path.to_path_buf()))
.map(|path| path.display().to_string())
.collect::<Vec<_>>()
.join(" -> ");
return Err(crate::error::ParseError::Message(format!(
"cyclic include detected: {chain}"
)));
}
Ok(())
}