sim_kernel/op.rs
1//! The operation contract: keyed, shape-checked calls dispatched on objects.
2//!
3//! The kernel defines the [`Op`] trait, operation specs and keys, and the
4//! resolve/invoke surface plus the well-known core operation keys; libraries
5//! implement the concrete operations.
6
7use crate::{
8 capability::CapabilityName,
9 effect::Effect,
10 env::Cx,
11 error::{Error, Result},
12 id::Symbol,
13 ref_id::Ref,
14 shape_check::check_shape_ref,
15 term::OpKey,
16 value::Value,
17};
18
19/// The declared contract of an operation: its key, shapes, effects, and
20/// required capabilities.
21///
22/// An `OpSpec` is metadata the kernel checks before dispatch -- the
23/// [`args_shape`](OpSpec::args_shape) is validated against the input and the
24/// [`requires`](OpSpec::requires) capabilities are demanded from the [`Cx`] --
25/// while the concrete behavior lives in the [`Op`] that carries it.
26#[derive(Clone, Debug)]
27pub struct OpSpec {
28 /// The interned operation key (`namespace:name@vN`) this spec describes.
29 pub key: OpKey,
30 /// The subject the operation dispatches on.
31 pub subject: Ref,
32 /// The shape the input is checked against before invocation.
33 pub args_shape: Ref,
34 /// The shape the operation promises for its result.
35 pub result_shape: Ref,
36 /// The effect symbols the operation may emit.
37 pub effects: Vec<Symbol>,
38 /// The capabilities that must be granted before the operation runs.
39 pub requires: Vec<CapabilityName>,
40}
41
42impl OpSpec {
43 /// Builds a spec with no declared effects or capability requirements.
44 pub fn new(key: OpKey, subject: Ref, args_shape: Ref, result_shape: Ref) -> Self {
45 Self {
46 key,
47 subject,
48 args_shape,
49 result_shape,
50 effects: Vec::new(),
51 requires: Vec::new(),
52 }
53 }
54
55 /// Returns the spec with its effect set replaced.
56 pub fn with_effects(mut self, effects: Vec<Symbol>) -> Self {
57 self.effects = effects;
58 self
59 }
60
61 /// Returns the spec with one more required capability appended.
62 pub fn requiring(mut self, capability: CapabilityName) -> Self {
63 self.requires.push(capability);
64 self
65 }
66
67 /// Returns the spec with its required-capability set replaced.
68 pub fn with_requirements(mut self, requires: Vec<CapabilityName>) -> Self {
69 self.requires = requires;
70 self
71 }
72}
73
74/// A keyed, shape-checked operation dispatched on an object.
75///
76/// The kernel defines this contract and the well-known core keys; libraries
77/// implement concrete operations against it. Dispatch goes through
78/// [`invoke_op`], which checks the [`OpSpec`] before calling
79/// [`invoke_authorized`](Op::invoke_authorized).
80pub trait Op: Send + Sync {
81 /// Returns the operation's declared contract.
82 fn spec(&self) -> &OpSpec;
83 /// Runs the operation after the kernel has checked shapes and capabilities.
84 fn invoke_authorized(&self, cx: &mut Cx, input: Value) -> Result<Step>;
85}
86
87/// The outcome of invoking an [`Op`]: a value, an event batch, or a suspension.
88#[derive(Clone, Debug)]
89pub enum Step {
90 /// A completed value result.
91 Value(Value),
92 /// A batch of emitted events.
93 Events(Value),
94 /// A suspended computation carrying the pending [`Effect`].
95 Suspended(Box<Effect>),
96}
97
98/// An operation resolved against a target, ready to be invoked.
99///
100/// Produced by [`resolve_op`], this hides whether the operation is a native
101/// [`Op`] registered on the object or a kernel adapter synthesized from one of
102/// the object's protocol surfaces.
103pub struct ResolvedOp<'a> {
104 inner: ResolvedOpKind<'a>,
105}
106
107impl<'a> ResolvedOp<'a> {
108 /// Returns the resolved operation's contract.
109 pub fn spec(&self) -> &OpSpec {
110 match &self.inner {
111 ResolvedOpKind::Native(op) => op.spec(),
112 ResolvedOpKind::Adapter(adapter) => &adapter.spec,
113 }
114 }
115
116 /// Runs the resolved operation; callers must check the spec first.
117 pub fn invoke_authorized(&self, cx: &mut Cx, input: Value) -> Result<Step> {
118 match &self.inner {
119 ResolvedOpKind::Native(op) => op.invoke_authorized(cx, input),
120 ResolvedOpKind::Adapter(adapter) => adapter.invoke(cx, input),
121 }
122 }
123}
124
125enum ResolvedOpKind<'a> {
126 Native(&'a dyn Op),
127 Adapter(Box<crate::op_adapters::AdapterOp<'a>>),
128}
129
130/// Resolves and runs an operation on `target`, enforcing its [`OpSpec`].
131///
132/// This is the kernel dispatch entry point: it resolves the key against the
133/// target (native [`Op`] or synthesized adapter), demands the required
134/// capabilities, checks the input against the declared args shape, and then
135/// invokes the operation. Only explicit `core:Any` and `core:AnyShape` refs opt
136/// out of shape checking.
137pub fn invoke_op(cx: &mut Cx, target: Value, key: &OpKey, input: Value) -> Result<Step> {
138 let resolved = resolve_op(cx, &target, key)?;
139 let requires = resolved.spec().requires.clone();
140 cx.require_all(&requires)?;
141 let args_shape = resolved.spec().args_shape.clone();
142 check_shape_if_available(cx, args_shape, input.clone())?;
143 resolved.invoke_authorized(cx, input)
144}
145
146/// Resolves an operation key against a target without invoking it.
147///
148/// Prefers a native [`Op`] registered on the object; otherwise falls back to a
149/// kernel adapter synthesized from one of the object's protocol surfaces, and
150/// errors when neither is available.
151pub fn resolve_op<'a>(cx: &mut Cx, target: &'a Value, key: &OpKey) -> Result<ResolvedOp<'a>> {
152 if let Some(op) = target.object().op(key) {
153 return Ok(ResolvedOp {
154 inner: ResolvedOpKind::Native(op),
155 });
156 }
157
158 let Some(adapter) = crate::op_adapters::resolve_adapter(cx, target, key)? else {
159 return Err(Error::Eval(format!(
160 "operation {} not available",
161 format_op_key(key)
162 )));
163 };
164
165 Ok(ResolvedOp {
166 inner: ResolvedOpKind::Adapter(Box::new(adapter)),
167 })
168}
169
170/// Checks `input` against a shape reference, passing silently only when the
171/// shape reference explicitly accepts anything.
172///
173/// Used by [`invoke_op`] to validate operation arguments. Missing symbols,
174/// unresolved refs, and registered non-shape values fail closed.
175pub fn check_shape_if_available(cx: &mut Cx, shape_ref: Ref, input: Value) -> Result<()> {
176 check_shape_ref(cx, &shape_ref, input)
177}
178
179/// The well-known key for the core call operation (`core:call@v1`).
180///
181/// # Examples
182///
183/// ```
184/// # use sim_kernel::op::core_call_op_key;
185/// let key = core_call_op_key();
186/// assert_eq!(key.namespace.to_string(), "core");
187/// assert_eq!(key.name.to_string(), "call");
188/// assert_eq!(key.version, 1);
189/// ```
190pub fn core_call_op_key() -> OpKey {
191 core_op_key("call")
192}
193
194/// The well-known key for checking a value against a shape.
195pub fn core_shape_check_value_op_key() -> OpKey {
196 core_op_key("shape-check-value")
197}
198
199/// The well-known key for checking a term against a shape.
200pub fn core_shape_check_term_op_key() -> OpKey {
201 core_op_key("shape-check-term")
202}
203
204/// The well-known key for describing a shape.
205pub fn core_shape_describe_op_key() -> OpKey {
206 core_op_key("shape-describe")
207}
208
209/// The well-known key for reading a class's symbol.
210pub fn core_class_symbol_op_key() -> OpKey {
211 core_op_key("class-symbol")
212}
213
214/// The well-known key for reading an object's encoding.
215pub fn core_object_encoding_op_key() -> OpKey {
216 core_op_key("object-encoding")
217}
218
219/// The well-known key for read-time construction.
220pub fn core_read_construct_op_key() -> OpKey {
221 core_op_key("read-construct")
222}
223
224/// The well-known key for reading a number's domain symbol.
225pub fn core_number_domain_symbol_op_key() -> OpKey {
226 core_op_key("number-domain-symbol")
227}
228
229/// The well-known key for reading a number value.
230pub fn core_number_value_op_key() -> OpKey {
231 core_op_key("number-value")
232}
233
234/// The well-known key for starting a distributed eval realization.
235pub fn core_realize_start_op_key() -> OpKey {
236 core_op_key("realize-start")
237}
238
239/// The well-known key for forcing a thunk.
240pub fn core_force_op_key() -> OpKey {
241 core_op_key("force")
242}
243
244/// The well-known key for advancing a sequence.
245pub fn core_seq_next_op_key() -> OpKey {
246 core_op_key("seq-next")
247}
248
249/// The well-known key for closing a sequence.
250pub fn core_seq_close_op_key() -> OpKey {
251 core_op_key("seq-close")
252}
253
254/// The well-known key for reading a list's items.
255pub fn core_list_items_op_key() -> OpKey {
256 core_op_key("list-items")
257}
258
259/// The well-known key for reading a table's entries.
260pub fn core_table_entries_op_key() -> OpKey {
261 core_op_key("table-entries")
262}
263
264/// The well-known key for testing whether a directory entry is a directory.
265pub fn core_dir_is_dir_op_key() -> OpKey {
266 core_op_key("dir-is-dir")
267}
268
269/// The well-known key for snapshotting an object as an [`Expr`](crate::Expr).
270pub fn core_expr_snapshot_op_key() -> OpKey {
271 core_op_key("expr-snapshot")
272}
273
274/// The shape reference that accepts any value (`core:Any`).
275pub fn core_any_ref() -> Ref {
276 core_ref("Any")
277}
278
279fn core_op_key(name: &str) -> OpKey {
280 OpKey::new(Symbol::new("core"), Symbol::new(name), 1)
281}
282
283pub(crate) fn core_ref(name: &str) -> Ref {
284 Ref::Symbol(core_symbol(name))
285}
286
287pub(crate) fn core_symbol(name: &str) -> Symbol {
288 Symbol::qualified("core", name)
289}
290
291fn format_op_key(key: &OpKey) -> String {
292 format!("{}:{}@v{}", key.namespace, key.name, key.version)
293}