mod declarations;
mod emit;
mod loading;
mod profiled;
pub(crate) use emit::prune_analysis;
pub use emit::{Instantiated, Pruned, instantiate};
pub(crate) use loading::validate_record;
pub use profiled::{
ProfiledRefusal, compile_profiled, compile_profiled_with, parameters_for_subject,
};
mod overlay;
mod sites;
mod template;
mod values;
pub use declarations::check_declarations;
pub use overlay::{Overlay, ParameterValue, resolve_profiles};
use rustev_contract::{parameter::ParameterDecl, plan::Variant};
pub use sites::{Item, Site, SiteKind, reference_sites};
use std::fmt;
pub use template::{Selection, Template, parse_template, select_template};
pub use values::{CanonicalValue, ValueError, check_bounds, check_value};
macro_rules! codes {
($($name:ident => $text:literal),* $(,)?) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InstantiationCode { $($name),* }
impl InstantiationCode {
pub fn as_str(self) -> &'static str { match self { $(Self::$name => $text),* } }
}
};
}
codes! {
InvalidDefinitionDocument => "invalid-definition-document",
DefinitionAmbiguous => "definition-ambiguous",
DefinitionUnsatisfied => "definition-unsatisfied",
DefinitionPinOutsideRange => "definition-pin-outside-range",
InvalidParameterization => "invalid-parameterization",
InvalidProfile => "invalid-profile",
ProfileMissing => "profile-missing",
ProfileDuplicate => "profile-duplicate",
ProfileCycle => "profile-cycle",
ProfileChainTooDeep => "profile-chain-too-deep",
ParameterUnknown => "parameter-unknown",
ParameterLocked => "parameter-locked",
ParameterType => "parameter-type",
ParameterOutOfBounds => "parameter-out-of-bounds",
ParameterUnbound => "parameter-unbound",
VariantRequired => "variant-required",
VariantUnknown => "variant-unknown",
DanglingReference => "dangling-reference",
InstantiationBound => "instantiation-bound",
}
impl fmt::Display for InstantiationCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl std::error::Error for InstantiationCode {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstantiationRefusal {
pub stage: u8,
pub code: InstantiationCode,
pub subject: String,
pub detail: String,
}
impl fmt::Display for InstantiationRefusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}: {}", self.code, self.subject, self.detail)
}
}
impl std::error::Error for InstantiationRefusal {}
fn refusal(
stage: u8,
code: InstantiationCode,
subject: impl Into<String>,
detail: impl ToString,
) -> InstantiationRefusal {
InstantiationRefusal {
stage,
code,
subject: subject.into(),
detail: detail.to_string(),
}
}
#[derive(Debug, Clone)]
pub struct Analysis {
pub selection: Selection,
pub declarations: Vec<ParameterDecl>,
pub overlay: Overlay,
pub variant: Variant,
}
pub fn analyze(
definitions: &[&[u8]],
profile: Option<&[u8]>,
bases: &[&[u8]],
variant: Option<&str>,
pin: Option<&str>,
) -> Result<Analysis, InstantiationRefusal> {
let selection = select_template(definitions, profile, pin)?;
let declarations = check_declarations(&selection.template)?;
let overlay = resolve_profiles(&selection, &declarations, profile, bases)?;
let variant = check_variant(&selection.template, variant)?;
Ok(Analysis {
selection,
declarations,
overlay,
variant,
})
}
pub fn check_variant(
template: &Template,
variant: Option<&str>,
) -> Result<Variant, InstantiationRefusal> {
let names = template
.json
.get("variants")
.and_then(serde_json::Value::as_array);
match variant {
Some(name) if names.is_some_and(|ns| ns.iter().any(|n| n == name)) => {
Ok(Variant::Named(name.into()))
}
Some(name) => Err(refusal(
4,
InstantiationCode::VariantUnknown,
format!("variant:{name}"),
"variant not declared",
)),
None if names.is_some_and(|ns| !ns.is_empty()) => Err(refusal(
4,
InstantiationCode::VariantRequired,
template.subject(),
"a declared variant is required",
)),
None => Ok(Variant::None),
}
}