use alloc::boxed::Box;
use alloc::string::ToString;
use alloc::sync::Arc;
use miden_core::mast::MastNodeExt;
use miden_mast_package::Package;
use miden_mast_package::debug_info::PackageDebugInfo;
use miden_processor::LoadedMastForest;
use thiserror::Error;
use crate::assembly::Path;
use crate::package::{loaded_mast_forest, package_debug_info};
use crate::utils::create_external_node_forest;
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use crate::vm::AdviceMap;
use crate::{MastForest, MastNodeId, Word};
#[derive(Debug, Error)]
pub enum MastForestScriptError {
#[error("entrypoint node {0} is not in the provided MAST forest")]
EntrypointNotInForest(MastNodeId),
#[error("package does not contain a procedure with '@{0}' attribute")]
NoProcedureWithAttribute(Box<str>),
#[error("package contains multiple procedures with '@{0}' attribute")]
MultipleProceduresWithAttribute(Box<str>),
#[error("procedure at path '{0}' not found in package")]
ProcedureNotFound(Box<str>),
#[error("procedure at path '{0}' does not have the specified attribute")]
ProcedureMissingAttribute(Box<str>),
#[error("expected a library package, but the provided package is an executable")]
ExecutablePackage,
}
#[derive(Debug, Clone)]
pub(crate) struct MastForestScript {
mast: Arc<MastForest>,
entrypoint: MastNodeId,
package_debug_info: Option<Arc<PackageDebugInfo>>,
}
impl MastForestScript {
pub fn from_parts(
mast: Arc<MastForest>,
entrypoint: MastNodeId,
) -> Result<Self, MastForestScriptError> {
if mast.get_node_by_id(entrypoint).is_none() {
return Err(MastForestScriptError::EntrypointNotInForest(entrypoint));
}
Ok(Self {
mast,
entrypoint,
package_debug_info: None,
})
}
pub(crate) fn from_package(
package: &Package,
attribute: &str,
) -> Result<Self, MastForestScriptError> {
if package.is_program() {
return Err(MastForestScriptError::ExecutablePackage);
}
let mut entrypoint = None;
for export in package.manifest.exports() {
if let Some(proc_export) = export.as_procedure()
&& proc_export.attributes.has(attribute)
{
if entrypoint.is_some() {
return Err(MastForestScriptError::MultipleProceduresWithAttribute(
attribute.into(),
));
}
entrypoint = Some(proc_export.node.ok_or_else(|| {
MastForestScriptError::NoProcedureWithAttribute(attribute.into())
})?);
}
}
let entrypoint = entrypoint
.ok_or_else(|| MastForestScriptError::NoProcedureWithAttribute(attribute.into()))?;
Ok(Self::from_parts(package.mast_forest().clone(), entrypoint)?
.with_package_debug_info(package))
}
pub(crate) fn from_package_reference(
package: &Package,
path: &Path,
attribute: &str,
) -> Result<Self, MastForestScriptError> {
let export = package
.manifest
.exports()
.find(|e| e.path().as_ref() == path)
.ok_or_else(|| MastForestScriptError::ProcedureNotFound(path.to_string().into()))?;
let proc_export = export
.as_procedure()
.ok_or_else(|| MastForestScriptError::ProcedureNotFound(path.to_string().into()))?;
if !proc_export.attributes.has(attribute) {
return Err(MastForestScriptError::ProcedureMissingAttribute(path.to_string().into()));
}
let digest = proc_export.digest;
let (mast, entrypoint) = create_external_node_forest(digest);
Ok(Self::from_parts(Arc::new(mast), entrypoint)?.with_package_debug_info(package))
}
pub fn mast(&self) -> Arc<MastForest> {
self.mast.clone()
}
pub fn loaded_mast_forest(&self) -> LoadedMastForest {
loaded_mast_forest(self.mast.clone(), self.package_debug_info.clone())
}
pub fn digest(&self) -> Word {
self.mast[self.entrypoint].digest()
}
pub fn entrypoint(&self) -> MastNodeId {
self.entrypoint
}
pub fn clear_debug_info(&mut self) {
self.package_debug_info = None;
}
pub fn with_package_debug_info(mut self, package: &Package) -> Self {
self.package_debug_info = package_debug_info(package);
self
}
pub fn with_advice_map(mut self, advice_map: AdviceMap) -> Self {
if advice_map.is_empty() {
return self;
}
let mast = (*self.mast).clone().with_advice_map(advice_map);
self.mast = Arc::new(mast);
self
}
}
impl PartialEq for MastForestScript {
fn eq(&self, other: &Self) -> bool {
self.mast == other.mast && self.entrypoint == other.entrypoint
}
}
impl Eq for MastForestScript {}
impl Serializable for MastForestScript {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.mast.write_into(target);
target.write_u32(u32::from(self.entrypoint));
}
fn get_size_hint(&self) -> usize {
let mast_size = self.mast.to_bytes().len();
let u32_size = 0u32.get_size_hint();
mast_size + u32_size
}
}
impl Deserializable for MastForestScript {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let mast = MastForest::read_from(source)?;
let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
Self::from_parts(Arc::new(mast), entrypoint)
.map_err(|e| DeserializationError::InvalidValue(e.to_string()))
}
}