#![forbid(unsafe_code)]
#![warn(missing_docs)]
mod expr;
mod lower;
use std::path::{Path, PathBuf};
use zerodds_ast::Ast;
use zerodds_types::resolve::TypeRegistry;
pub use lower::{Diagnostic, Severity};
pub use zerodds_idl_lower::{HashedIdCounter, HashedNameSpelling, KeyOnUnionDiscriminator};
pub struct Composed {
pub ast: Ast,
pub registry: TypeRegistry,
pub diagnostics: Vec<Diagnostic>,
pub without_type_objects: Vec<(String, String)>,
}
impl Composed {
#[must_use]
pub fn failed(&self) -> bool {
self.diagnostics
.iter()
.any(|d| d.severity == Severity::Error)
}
}
#[derive(Debug, Clone, Default)]
pub struct ComposeOptions {
pub default_extensibility: Option<zerodds_ast::Extensibility>,
pub hashed_id_advances_the_counter: HashedIdCounter,
pub hashed_name_spelling: HashedNameSpelling,
pub key_on_union_discriminator: KeyOnUnionDiscriminator,
pub include_paths: Vec<PathBuf>,
pub zero_bound_is_unbounded: bool,
}
pub fn compose(path: &str) -> Result<Composed, String> {
compose_with(path, ComposeOptions::default())
}
pub fn compose_with(path: &str, options: ComposeOptions) -> Result<Composed, String> {
let source = std::fs::read_to_string(path).map_err(|error| format!("{path}: {error}"))?;
Ok(compose_source_with(path, &source, options))
}
#[must_use]
pub fn compose_source(name: &str, source: &str) -> Composed {
compose_source_with(name, source, ComposeOptions::default())
}
#[must_use]
pub fn compose_source_with(name: &str, source: &str, options: ComposeOptions) -> Composed {
compose_all_sources(&[(name.to_owned(), source.to_owned())], options)
}
pub fn compose_all(
paths: &[impl AsRef<Path>],
options: ComposeOptions,
) -> Result<Composed, String> {
let mut sources = Vec::with_capacity(paths.len());
for path in paths {
let path: &Path = path.as_ref();
let name = path.to_string_lossy().into_owned();
let text = std::fs::read_to_string(path).map_err(|error| format!("{name}: {error}"))?;
sources.push((name, text));
}
Ok(compose_all_sources(&sources, options))
}
#[must_use]
pub fn compose_all_sources(sources: &[(String, String)], options: ComposeOptions) -> Composed {
let mut ast = Ast::new();
let diagnostics = lower::lower_all(&mut ast, sources, &options);
let mut registry = TypeRegistry::new();
let mut diagnostics = diagnostics;
let without_type_objects = match zerodds_types::from_ast::register_all(&ast, &mut registry) {
Ok(skipped) => skipped
.into_iter()
.map(|(name, error)| (name, format!("{error:?}")))
.collect(),
Err(error) => {
diagnostics.push(Diagnostic {
severity: Severity::Error,
message: format!("no type object could be built for this model: {error:?}"),
at: sources
.first()
.map_or_else(|| String::from("<bundle>"), |(name, _)| name.clone()),
spec_reference: "DDS-XTypes 1.3 §7.3.4",
});
Vec::new()
}
};
Composed {
ast,
registry,
diagnostics,
without_type_objects,
}
}
pub fn create_type_w_document(
document: &str,
type_name: &str,
include_paths: &[&str],
) -> Result<zerodds_types::dynamic::DynamicType, String> {
let options = ComposeOptions {
include_paths: include_paths.iter().map(PathBuf::from).collect(),
..ComposeOptions::default()
};
let composed = compose_source_with("<document>", document, options);
type_of_composed(composed, type_name)
}
pub fn create_type_w_uri(
document_url: &str,
type_name: &str,
include_paths: &[&str],
) -> Result<zerodds_types::dynamic::DynamicType, String> {
let path = match document_url.split_once("://") {
Some(("file", rest)) => rest,
Some((scheme, _)) => {
return Err(format!(
"`{scheme}://` is not supported; only file:// URLs are read (§7.5.2.2.13)"
));
}
None => document_url,
};
let options = ComposeOptions {
include_paths: include_paths.iter().map(PathBuf::from).collect(),
..ComposeOptions::default()
};
let composed = compose_with(path, options)?;
type_of_composed(composed, type_name)
}
fn type_of_composed(
composed: Composed,
type_name: &str,
) -> Result<zerodds_types::dynamic::DynamicType, String> {
if composed.failed() {
return Err(composed
.diagnostics
.iter()
.filter(|d| d.severity == Severity::Error)
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("; "));
}
let ast = std::sync::Arc::new(composed.ast);
zerodds_types::dynamic::DynamicType::find(&ast, type_name).map_err(|e| e.to_string())
}
#[must_use]
pub fn xml_path(folder: &str, file: &str) -> PathBuf {
Path::new(folder).join("xml").join(format!("{file}.xml"))
}