use crate::{
capability::CapabilityName,
effect::Effect,
env::Cx,
error::{Error, Result},
id::{ShapeId, Symbol},
ref_id::Ref,
term::OpKey,
value::Value,
};
#[derive(Clone, Debug)]
pub struct OpSpec {
pub key: OpKey,
pub subject: Ref,
pub args_shape: Ref,
pub result_shape: Ref,
pub effects: Vec<Symbol>,
pub requires: Vec<CapabilityName>,
}
impl OpSpec {
pub fn new(key: OpKey, subject: Ref, args_shape: Ref, result_shape: Ref) -> Self {
Self {
key,
subject,
args_shape,
result_shape,
effects: Vec::new(),
requires: Vec::new(),
}
}
pub fn with_effects(mut self, effects: Vec<Symbol>) -> Self {
self.effects = effects;
self
}
pub fn requiring(mut self, capability: CapabilityName) -> Self {
self.requires.push(capability);
self
}
pub fn with_requirements(mut self, requires: Vec<CapabilityName>) -> Self {
self.requires = requires;
self
}
}
pub trait Op: Send + Sync {
fn spec(&self) -> &OpSpec;
fn invoke_authorized(&self, cx: &mut Cx, input: Value) -> Result<Step>;
}
#[derive(Clone, Debug)]
pub enum Step {
Value(Value),
Events(Value),
Suspended(Box<Effect>),
}
pub struct ResolvedOp<'a> {
inner: ResolvedOpKind<'a>,
}
impl<'a> ResolvedOp<'a> {
pub fn spec(&self) -> &OpSpec {
match &self.inner {
ResolvedOpKind::Native(op) => op.spec(),
ResolvedOpKind::Adapter(adapter) => &adapter.spec,
}
}
pub fn invoke_authorized(&self, cx: &mut Cx, input: Value) -> Result<Step> {
match &self.inner {
ResolvedOpKind::Native(op) => op.invoke_authorized(cx, input),
ResolvedOpKind::Adapter(adapter) => adapter.invoke(cx, input),
}
}
}
enum ResolvedOpKind<'a> {
Native(&'a dyn Op),
Adapter(Box<crate::op_adapters::AdapterOp<'a>>),
}
pub fn invoke_op(cx: &mut Cx, target: Value, key: &OpKey, input: Value) -> Result<Step> {
let resolved = resolve_op(cx, &target, key)?;
let requires = resolved.spec().requires.clone();
cx.require_all(&requires)?;
let args_shape = resolved.spec().args_shape.clone();
check_shape_if_available(cx, args_shape, input.clone())?;
resolved.invoke_authorized(cx, input)
}
pub fn resolve_op<'a>(cx: &mut Cx, target: &'a Value, key: &OpKey) -> Result<ResolvedOp<'a>> {
if let Some(op) = target.object().op(key) {
return Ok(ResolvedOp {
inner: ResolvedOpKind::Native(op),
});
}
let Some(adapter) = crate::op_adapters::resolve_adapter(cx, target, key)? else {
return Err(Error::Eval(format!(
"operation {} not available",
format_op_key(key)
)));
};
Ok(ResolvedOp {
inner: ResolvedOpKind::Adapter(Box::new(adapter)),
})
}
pub fn check_shape_if_available(cx: &mut Cx, shape_ref: Ref, input: Value) -> Result<()> {
let Ref::Symbol(symbol) = shape_ref else {
return Ok(());
};
if is_any_shape_symbol(&symbol) {
return Ok(());
}
let Some(shape_value) = cx.registry().shape_by_symbol(&symbol).cloned() else {
return Ok(());
};
let Some(shape) = shape_value.object().as_shape() else {
return Ok(());
};
let matched = shape.check_value(cx, input)?;
if matched.accepted {
Ok(())
} else {
Err(Error::WrongShape {
expected: shape.id().unwrap_or(ShapeId(0)),
diagnostics: matched.diagnostics,
})
}
}
pub fn core_call_op_key() -> OpKey {
core_op_key("call")
}
pub fn core_shape_check_value_op_key() -> OpKey {
core_op_key("shape-check-value")
}
pub fn core_shape_check_term_op_key() -> OpKey {
core_op_key("shape-check-term")
}
pub fn core_shape_describe_op_key() -> OpKey {
core_op_key("shape-describe")
}
pub fn core_class_symbol_op_key() -> OpKey {
core_op_key("class-symbol")
}
pub fn core_object_encoding_op_key() -> OpKey {
core_op_key("object-encoding")
}
pub fn core_read_construct_op_key() -> OpKey {
core_op_key("read-construct")
}
pub fn core_number_domain_symbol_op_key() -> OpKey {
core_op_key("number-domain-symbol")
}
pub fn core_number_value_op_key() -> OpKey {
core_op_key("number-value")
}
pub fn core_realize_start_op_key() -> OpKey {
core_op_key("realize-start")
}
pub fn core_force_op_key() -> OpKey {
core_op_key("force")
}
pub fn core_seq_next_op_key() -> OpKey {
core_op_key("seq-next")
}
pub fn core_seq_close_op_key() -> OpKey {
core_op_key("seq-close")
}
pub fn core_list_items_op_key() -> OpKey {
core_op_key("list-items")
}
pub fn core_table_entries_op_key() -> OpKey {
core_op_key("table-entries")
}
pub fn core_dir_is_dir_op_key() -> OpKey {
core_op_key("dir-is-dir")
}
pub fn core_expr_snapshot_op_key() -> OpKey {
core_op_key("expr-snapshot")
}
pub fn core_any_ref() -> Ref {
core_ref("Any")
}
fn is_any_shape_symbol(symbol: &Symbol) -> bool {
*symbol == core_symbol("Any") || *symbol == core_symbol("AnyShape")
}
fn core_op_key(name: &str) -> OpKey {
OpKey::new(Symbol::new("core"), Symbol::new(name), 1)
}
pub(crate) fn core_ref(name: &str) -> Ref {
Ref::Symbol(core_symbol(name))
}
pub(crate) fn core_symbol(name: &str) -> Symbol {
Symbol::qualified("core", name)
}
fn format_op_key(key: &OpKey) -> String {
format!("{}:{}@v{}", key.namespace, key.name, key.version)
}