sim-kernel 0.1.0-rc.1

SIM workspace package for sim kernel.
Documentation
use std::collections::BTreeSet;

use crate::{
    Error, ExportKind, ExportRecord, ExportState, LibManifest, Result, RuntimeId, Symbol, Value,
    library::{Export, Registry},
};

use super::catalog::CatalogRuntimeValue;
use crate::library::transaction::PendingExports;

pub(super) struct LoadedLibPlan {
    pub(super) export_records: Vec<ExportRecord>,
    pub(super) runtime_values: Vec<CatalogRuntimeValue>,
}

pub(super) fn resolve_loaded_lib_plan(
    manifest: &LibManifest,
    pending: &PendingExports,
) -> Result<LoadedLibPlan> {
    let mut used_exports = BTreeSet::new();
    let mut used_records = BTreeSet::new();
    let mut plan = LoadedLibPlan {
        export_records: Vec::with_capacity(
            manifest.exports.len() + pending.exports.len() + pending.export_records.len(),
        ),
        runtime_values: Vec::new(),
    };

    for export in &manifest.exports {
        let kind = export.kind_symbol();
        let symbol = export.symbol().clone();
        if let Some((index, record)) = find_export_record(pending, &kind, &symbol) {
            used_records.insert(index);
            plan.export_records.push(record.clone());
            continue;
        }

        match find_pending_export(pending, &kind, &symbol) {
            Some((index, pending_export)) => {
                used_exports.insert(index);
                push_export_plan(&mut plan, pending_export, pending)?;
            }
            None => push_manifest_declaration(&mut plan, export)?,
        }
    }

    for (index, export) in pending.exports.iter().enumerate() {
        if !used_exports.contains(&index) {
            push_export_plan(&mut plan, export, pending)?;
        }
    }
    for (index, record) in pending.export_records.iter().enumerate() {
        if !used_records.contains(&index) {
            plan.export_records.push(record.clone());
        }
    }
    for record in &plan.export_records {
        Registry::validate_export_record_against_manifest(manifest, record)?;
    }
    Ok(plan)
}

fn find_export_record<'a>(
    pending: &'a PendingExports,
    kind: &ExportKind,
    symbol: &Symbol,
) -> Option<(usize, &'a ExportRecord)> {
    pending
        .export_records
        .iter()
        .enumerate()
        .find(|(_, record)| &record.kind == kind && &record.symbol == symbol)
}

fn find_pending_export<'a>(
    pending: &'a PendingExports,
    kind: &ExportKind,
    symbol: &Symbol,
) -> Option<(usize, &'a Export)> {
    pending
        .exports
        .iter()
        .enumerate()
        .find(|(_, export)| &export.kind_symbol() == kind && export.symbol() == symbol)
}

fn push_manifest_declaration(plan: &mut LoadedLibPlan, export: &Export) -> Result<()> {
    let kind = export.kind_symbol();
    let symbol = export.symbol().clone();
    match export {
        Export::Class {
            class_id: Some(_), ..
        } => missing_value(ExportKind::CLASS, &symbol),
        Export::Function {
            function_id: Some(_),
            ..
        } => missing_value(ExportKind::FUNCTION, &symbol),
        Export::Macro {
            macro_id: Some(_), ..
        } => missing_value(ExportKind::MACRO, &symbol),
        Export::Shape {
            shape_id: Some(_), ..
        } => missing_value(ExportKind::SHAPE, &symbol),
        Export::Codec {
            codec_id: Some(_), ..
        } => missing_value(ExportKind::CODEC, &symbol),
        Export::NumberDomain {
            number_domain_id: Some(_),
            ..
        } => missing_value(ExportKind::NUMBER_DOMAIN, &symbol),
        Export::Site {
            runtime_id: Some(_),
            ..
        } => missing_value(ExportKind::SITE, &symbol),
        _ => {
            plan.export_records.push(declared_record(kind, symbol));
            Ok(())
        }
    }
}

fn push_export_plan(
    plan: &mut LoadedLibPlan,
    export: &Export,
    pending: &PendingExports,
) -> Result<()> {
    let kind = export.kind_symbol();
    let symbol = export.symbol().clone();
    match export {
        Export::Class {
            class_id: Some(id), ..
        } => push_resolved(
            plan,
            kind,
            symbol.clone(),
            RuntimeId::Class(*id),
            pending_class_value(pending, *id, &symbol)?,
        ),
        Export::Function {
            function_id: Some(id),
            ..
        } => push_resolved(
            plan,
            kind,
            symbol.clone(),
            RuntimeId::Function(*id),
            pending_function_value(pending, *id, &symbol)?,
        ),
        Export::Macro {
            macro_id: Some(id), ..
        } => push_resolved(
            plan,
            kind,
            symbol.clone(),
            RuntimeId::Macro(*id),
            pending_macro_value(pending, *id, &symbol)?,
        ),
        Export::Shape {
            shape_id: Some(id), ..
        } => push_resolved(
            plan,
            kind,
            symbol.clone(),
            RuntimeId::Shape(*id),
            pending_shape_value(pending, *id, &symbol)?,
        ),
        Export::Codec {
            codec_id: Some(id), ..
        } => push_resolved(
            plan,
            kind,
            symbol.clone(),
            RuntimeId::Codec(*id),
            pending_codec_value(pending, *id, &symbol)?,
        ),
        Export::NumberDomain {
            number_domain_id: Some(id),
            ..
        } => push_resolved(
            plan,
            kind,
            symbol.clone(),
            RuntimeId::NumberDomain(*id),
            pending_number_domain_value(pending, *id, &symbol)?,
        ),
        Export::Value { .. } => push_resolved(
            plan,
            kind,
            symbol.clone(),
            RuntimeId::Value,
            pending_value(pending, &symbol)?,
        ),
        Export::Site {
            runtime_id: Some(RuntimeId::Site(id)),
            ..
        } => push_resolved(
            plan,
            kind,
            symbol.clone(),
            RuntimeId::Site(*id),
            pending_site_value(pending, *id, &symbol)?,
        ),
        Export::Site {
            runtime_id: Some(other),
            ..
        } => Err(Error::Lib(format!(
            "site export {symbol} has non-site runtime id {other:?}"
        ))),
        _ => {
            plan.export_records.push(declared_record(kind, symbol));
            Ok(())
        }
    }
}

fn push_resolved(
    plan: &mut LoadedLibPlan,
    kind: ExportKind,
    symbol: Symbol,
    runtime_id: RuntimeId,
    value: Value,
) -> Result<()> {
    plan.export_records
        .push(resolved_record(kind.clone(), symbol.clone(), runtime_id));
    plan.runtime_values.push(CatalogRuntimeValue {
        kind,
        symbol,
        runtime_id,
        value,
    });
    Ok(())
}

fn resolved_record(kind: ExportKind, symbol: Symbol, id: RuntimeId) -> ExportRecord {
    ExportRecord {
        kind,
        symbol,
        state: ExportState::Resolved { id },
    }
}

fn declared_record(kind: ExportKind, symbol: Symbol) -> ExportRecord {
    ExportRecord {
        kind,
        symbol,
        state: ExportState::Declared,
    }
}

fn pending_class_value(
    pending: &PendingExports,
    id: crate::ClassId,
    symbol: &Symbol,
) -> Result<Value> {
    pending
        .class_value_cache
        .iter()
        .find_map(|(candidate, value)| (*candidate == id).then(|| value.clone()))
        .ok_or_else(|| missing_value_error(ExportKind::CLASS, symbol))
}

fn pending_function_value(
    pending: &PendingExports,
    id: crate::FunctionId,
    symbol: &Symbol,
) -> Result<Value> {
    pending
        .function_value_cache
        .iter()
        .find_map(|(candidate, value)| (*candidate == id).then(|| value.clone()))
        .ok_or_else(|| missing_value_error(ExportKind::FUNCTION, symbol))
}

fn pending_macro_value(
    pending: &PendingExports,
    id: crate::MacroId,
    symbol: &Symbol,
) -> Result<Value> {
    pending
        .macro_value_cache
        .iter()
        .find_map(|(candidate, value)| (*candidate == id).then(|| value.clone()))
        .ok_or_else(|| missing_value_error(ExportKind::MACRO, symbol))
}

fn pending_shape_value(
    pending: &PendingExports,
    id: crate::ShapeId,
    symbol: &Symbol,
) -> Result<Value> {
    pending
        .shape_value_cache
        .iter()
        .find_map(|(candidate, value)| (*candidate == id).then(|| value.clone()))
        .ok_or_else(|| missing_value_error(ExportKind::SHAPE, symbol))
}

fn pending_codec_value(
    pending: &PendingExports,
    id: crate::CodecId,
    symbol: &Symbol,
) -> Result<Value> {
    pending
        .codec_value_cache
        .iter()
        .find_map(|(candidate, value)| (*candidate == id).then(|| value.clone()))
        .ok_or_else(|| missing_value_error(ExportKind::CODEC, symbol))
}

fn pending_number_domain_value(
    pending: &PendingExports,
    id: crate::NumberDomainId,
    symbol: &Symbol,
) -> Result<Value> {
    pending
        .number_domain_value_cache
        .iter()
        .find_map(|(candidate, value)| (*candidate == id).then(|| value.clone()))
        .ok_or_else(|| missing_value_error(ExportKind::NUMBER_DOMAIN, symbol))
}

fn pending_value(pending: &PendingExports, symbol: &Symbol) -> Result<Value> {
    pending
        .values
        .iter()
        .find_map(|(candidate, value)| (candidate == symbol).then(|| value.clone()))
        .ok_or_else(|| missing_value_error(ExportKind::VALUE, symbol))
}

fn pending_site_value(
    pending: &PendingExports,
    id: crate::SiteId,
    symbol: &Symbol,
) -> Result<Value> {
    pending
        .site_value_cache
        .iter()
        .find_map(|(candidate, value)| (*candidate == id).then(|| value.clone()))
        .ok_or_else(|| missing_value_error(ExportKind::SITE, symbol))
}

fn missing_value(kind: &'static str, symbol: &Symbol) -> Result<()> {
    Err(missing_value_error(kind, symbol))
}

fn missing_value_error(kind: &'static str, symbol: &Symbol) -> Error {
    Error::Lib(format!("{kind} export {symbol} has no value"))
}