sim-kernel 0.1.0-rc.1

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

use crate::{
    Claim, ContentId, Cx, Datum, ExportKind, ExportRecord, ExportState, LoadedLib, Ref,
    RefResolver, RuntimeId, Symbol, TemporaryRefResolver, Value, card, core_call_op_key,
    core_class_symbol_op_key, core_dir_is_dir_op_key, core_expr_snapshot_op_key, core_force_op_key,
    core_list_items_op_key, core_number_domain_symbol_op_key, core_number_value_op_key,
    core_object_encoding_op_key, core_read_construct_op_key, core_realize_start_op_key,
    core_seq_close_op_key, core_seq_next_op_key, core_shape_check_term_op_key,
    core_shape_check_value_op_key, core_shape_describe_op_key, core_table_entries_op_key,
};

use crate::error::Result;
use crate::term::OpKey;

pub(crate) fn publish_loaded_lib_claims(cx: &mut Cx, loaded: &LoadedLib) -> Result<Vec<ContentId>> {
    let mut claim_ids = Vec::new();
    for record in &loaded.exports {
        let subject = Ref::Symbol(record.symbol.clone());
        record_claim_id(
            &mut claim_ids,
            insert_ref_claim(
                cx,
                &subject,
                card::card_kind_predicate(),
                export_kind_ref(&record.kind),
            )?,
        );
        record_claim_id(
            &mut claim_ids,
            insert_ref_claim(
                cx,
                &subject,
                exported_by_predicate(),
                Ref::Symbol(loaded.manifest.id.clone()),
            )?,
        );
        for capability in &loaded.manifest.capabilities {
            record_claim_id(
                &mut claim_ids,
                insert_string_claim(
                    cx,
                    &subject,
                    card::card_requires_predicate(),
                    capability.as_str().to_owned(),
                )?,
            );
        }
        if let Some(value) = export_value(cx, record) {
            claim_ids.extend(publish_callable_shape_claims(cx, &subject, &value)?);
            for op in operation_claims_for_value(&value) {
                record_claim_id(
                    &mut claim_ids,
                    insert_string_claim(cx, &subject, card::card_ops_predicate(), op)?,
                );
            }
        }
    }
    Ok(claim_ids)
}

fn record_claim_id(claim_ids: &mut Vec<ContentId>, claim_id: Option<ContentId>) {
    if let Some(claim_id) = claim_id {
        claim_ids.push(claim_id);
    }
}

pub fn exported_by_predicate() -> Symbol {
    core_symbol("exported-by")
}

fn export_kind_ref(kind: &ExportKind) -> Ref {
    let symbol = match kind.name() {
        Some(name) => core_symbol(name),
        None => kind.symbol().clone(),
    };
    Ref::Symbol(symbol)
}

fn export_value(cx: &Cx, record: &ExportRecord) -> Option<Value> {
    let ExportState::Resolved { id } = record.state else {
        return None;
    };
    match id {
        RuntimeId::Class(id) => cx.registry().class_value(id).cloned(),
        RuntimeId::Function(id) => cx.registry().function_value(id).cloned(),
        RuntimeId::Macro(id) => cx.registry().macro_value(id).cloned(),
        RuntimeId::Shape(id) => cx.registry().shape_value(id).cloned(),
        RuntimeId::Codec(id) => cx.registry().codec_value(id).cloned(),
        RuntimeId::NumberDomain(id) => cx.registry().number_domain_value(id).cloned(),
        RuntimeId::Site(id) => cx.registry().site_value(RuntimeId::Site(id)).cloned(),
        RuntimeId::Value => cx.registry().value_by_symbol(&record.symbol).cloned(),
    }
}

fn operation_claims_for_value(value: &Value) -> Vec<String> {
    let object = value.object();
    let mut ops = BTreeSet::new();

    for key in known_operation_keys() {
        if object.op(&key).is_some() {
            ops.insert(operation_key_text(&key));
        }
    }
    if object.as_callable().is_some() {
        ops.insert(operation_key_text(&core_call_op_key()));
    }
    if object.as_shape().is_some() {
        ops.insert(operation_key_text(&core_shape_check_value_op_key()));
        ops.insert(operation_key_text(&core_shape_check_term_op_key()));
        ops.insert(operation_key_text(&core_shape_describe_op_key()));
    }
    if object.as_class().is_some() {
        ops.insert(operation_key_text(&core_class_symbol_op_key()));
    }
    if object.as_object_encoder().is_some() {
        ops.insert(operation_key_text(&core_object_encoding_op_key()));
    }
    if object.as_read_constructor().is_some() {
        ops.insert(operation_key_text(&core_read_construct_op_key()));
    }
    if object.as_number_domain().is_some() {
        ops.insert(operation_key_text(&core_number_domain_symbol_op_key()));
    }
    if object.as_number_value().is_some() {
        ops.insert(operation_key_text(&core_number_value_op_key()));
    }
    if object.as_eval_fabric().is_some() {
        ops.insert(operation_key_text(&core_realize_start_op_key()));
    }
    if object.as_thunk().is_some() {
        ops.insert(operation_key_text(&core_force_op_key()));
    }
    if object.as_sequence().is_some() || object.as_stream().is_some() {
        ops.insert(operation_key_text(&core_seq_next_op_key()));
        ops.insert(operation_key_text(&core_seq_close_op_key()));
    }
    if object.as_list().is_some() {
        ops.insert(operation_key_text(&core_list_items_op_key()));
    }
    if object.as_table_impl().is_some() {
        ops.insert(operation_key_text(&core_table_entries_op_key()));
    }
    if object.as_dir().is_some() {
        ops.insert(operation_key_text(&core_dir_is_dir_op_key()));
    }

    ops.into_iter().collect()
}

fn publish_callable_shape_claims(
    cx: &mut Cx,
    subject: &Ref,
    value: &Value,
) -> Result<Vec<ContentId>> {
    let Some(callable) = value.object().as_callable() else {
        return Ok(Vec::new());
    };
    let mut claim_ids = Vec::new();
    let args = callable.browse_args_shape(cx)?;
    let result = callable.browse_result_shape(cx)?;
    let args_known = insert_shape_claim(
        cx,
        subject,
        card::card_args_predicate(),
        args.as_ref(),
        &mut claim_ids,
    )?;
    let result_known = insert_shape_claim(
        cx,
        subject,
        card::card_result_predicate(),
        result.as_ref(),
        &mut claim_ids,
    )?;
    record_claim_id(
        &mut claim_ids,
        insert_bool_claim(
            cx,
            subject,
            card::card_shape_known_predicate(),
            args_known && result_known,
        )?,
    );
    Ok(claim_ids)
}

fn insert_shape_claim(
    cx: &mut Cx,
    subject: &Ref,
    predicate: Symbol,
    value: Option<&Value>,
    claim_ids: &mut Vec<ContentId>,
) -> Result<bool> {
    let Some(value) = value else {
        record_claim_id(
            claim_ids,
            insert_ref_claim(cx, subject, predicate, Ref::Symbol(core_symbol("Any")))?,
        );
        return Ok(false);
    };
    let Some(reference) = stable_shape_ref(cx, value)? else {
        record_claim_id(
            claim_ids,
            insert_ref_claim(cx, subject, predicate, Ref::Symbol(core_symbol("Any")))?,
        );
        return Ok(false);
    };
    record_claim_id(
        claim_ids,
        insert_ref_claim(cx, subject, predicate, reference)?,
    );
    Ok(true)
}

fn stable_shape_ref(cx: &mut Cx, value: &Value) -> Result<Option<Ref>> {
    if let Some(symbol) = value.object().as_shape().and_then(|shape| shape.symbol()) {
        return Ok(Some(Ref::Symbol(symbol)));
    }
    let mut resolver = TemporaryRefResolver::new();
    match resolver.ref_for_value(cx, value)? {
        Ref::Handle(_) => Ok(None),
        reference => Ok(Some(reference)),
    }
}

fn known_operation_keys() -> [OpKey; 17] {
    [
        core_call_op_key(),
        core_shape_check_value_op_key(),
        core_shape_check_term_op_key(),
        core_shape_describe_op_key(),
        core_class_symbol_op_key(),
        core_object_encoding_op_key(),
        core_read_construct_op_key(),
        core_number_domain_symbol_op_key(),
        core_number_value_op_key(),
        core_realize_start_op_key(),
        core_force_op_key(),
        core_seq_next_op_key(),
        core_seq_close_op_key(),
        core_list_items_op_key(),
        core_table_entries_op_key(),
        core_dir_is_dir_op_key(),
        core_expr_snapshot_op_key(),
    ]
}

fn operation_key_text(key: &OpKey) -> String {
    format!("{}/{}.v{}", key.namespace, key.name, key.version)
}

fn insert_ref_claim(
    cx: &mut Cx,
    subject: &Ref,
    predicate: Symbol,
    object: Ref,
) -> Result<Option<ContentId>> {
    cx.insert_recorded_fact(Claim::public(subject.clone(), predicate, object))
        .map(|(_, id)| id)
}

fn insert_string_claim(
    cx: &mut Cx,
    subject: &Ref,
    predicate: Symbol,
    object: String,
) -> Result<Option<ContentId>> {
    let claim = Claim::content_object(
        cx.datum_store_mut(),
        subject.clone(),
        predicate,
        Datum::String(object),
    )?;
    cx.insert_recorded_fact(claim).map(|(_, id)| id)
}

fn insert_bool_claim(
    cx: &mut Cx,
    subject: &Ref,
    predicate: Symbol,
    object: bool,
) -> Result<Option<ContentId>> {
    let claim = Claim::content_object(
        cx.datum_store_mut(),
        subject.clone(),
        predicate,
        Datum::Bool(object),
    )?;
    cx.insert_recorded_fact(claim).map(|(_, id)| id)
}

fn core_symbol(name: &str) -> Symbol {
    Symbol::qualified("core", name.to_owned())
}