Skip to main content

sim_lib_bridge/
runtime.rs

1use std::sync::Arc;
2
3use sim_codec_bridge::{BridgeBook, BridgeFramePayload, expr_to_packet, packet_to_expr};
4use sim_kernel::{
5    AbiVersion, Args, Callable, Cx, Error, Export, Lib, LibManifest, LibTarget, Linker, LoadCx,
6    Object, ObjectCompat, Result, Symbol, Value, Version,
7};
8use sim_shape::{AnyShape, ListShape, OneOfShape, Shape, shape_value};
9
10use crate::{
11    RepairPolicy, ask_packet_with_model_params, bridge_brief, bridge_tx, receipt_packet_for_report,
12    run_ask_with_policy, rx_check,
13};
14
15const BRIDGE_RUN_ASK_NAME: &str = "bridge/run-ask";
16
17/// Loadable BRIDGE runtime library.
18pub struct BridgeLib;
19
20impl Lib for BridgeLib {
21    fn manifest(&self) -> LibManifest {
22        LibManifest {
23            id: manifest_name(),
24            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
25            abi: AbiVersion { major: 0, minor: 1 },
26            target: LibTarget::HostRegistered,
27            requires: Vec::new(),
28            capabilities: Vec::new(),
29            exports: bridge_exports(),
30        }
31    }
32
33    fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
34        for kind in BridgeFunctionKind::ALL {
35            let function = BridgeFunction::value(kind);
36            linker.function_value(function.symbol(), cx.factory().opaque(function)?)?;
37        }
38        Ok(())
39    }
40}
41
42/// Installs the BRIDGE runtime library into a context.
43pub fn install_bridge_lib(cx: &mut Cx) -> Result<()> {
44    cx.load_lib(&BridgeLib).map(|_| ())
45}
46
47/// Manifest symbol for the BRIDGE runtime library.
48pub fn manifest_name() -> Symbol {
49    Symbol::qualified("sim", "bridge")
50}
51
52/// Runtime symbol for `bridge/tx`.
53pub fn bridge_tx_symbol() -> Symbol {
54    Symbol::qualified("bridge", "tx")
55}
56
57/// Runtime symbol for `bridge/rx`.
58pub fn bridge_rx_symbol() -> Symbol {
59    Symbol::qualified("bridge", "rx")
60}
61
62/// Runtime symbol for `bridge/report`.
63pub fn bridge_report_symbol() -> Symbol {
64    Symbol::qualified("bridge", "report")
65}
66
67/// Runtime symbol for `bridge/brief`.
68pub fn bridge_brief_symbol() -> Symbol {
69    Symbol::qualified("bridge", "brief")
70}
71
72/// Runtime symbol for `bridge/ask`.
73pub fn bridge_ask_symbol() -> Symbol {
74    Symbol::qualified("bridge", "ask")
75}
76
77/// Runtime symbol for `bridge/run-ask`.
78pub fn bridge_run_ask_symbol() -> Symbol {
79    Symbol::qualified("bridge", "run-ask")
80}
81
82fn bridge_exports() -> Vec<Export> {
83    BridgeFunctionKind::ALL
84        .iter()
85        .map(|kind| Export::Function {
86            symbol: kind.symbol(),
87            function_id: None,
88        })
89        .collect()
90}
91
92/// Runtime callable kind for BRIDGE exports.
93#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94pub enum BridgeFunctionKind {
95    /// Build a checked eval request from a packet.
96    Tx,
97    /// Decode and check a model response packet.
98    Rx,
99    /// Produce a receive-check report for a packet.
100    Report,
101    /// Produce a receipt packet for a report.
102    Receipt,
103    /// Build a BRIEF request packet from one typed frame.
104    Brief,
105    /// Build an ASK request packet from one typed call.
106    Ask,
107    /// Run a checked ASK packet against an eval fabric.
108    RunAsk,
109}
110
111impl BridgeFunctionKind {
112    /// All exported function kinds.
113    pub const ALL: [Self; 7] = [
114        Self::Tx,
115        Self::Rx,
116        Self::Report,
117        Self::Receipt,
118        Self::Brief,
119        Self::Ask,
120        Self::RunAsk,
121    ];
122
123    /// Runtime symbol for this kind.
124    pub fn symbol(self) -> Symbol {
125        match self {
126            Self::Tx => bridge_tx_symbol(),
127            Self::Rx => bridge_rx_symbol(),
128            Self::Report => bridge_report_symbol(),
129            Self::Receipt => crate::receipt_symbol(),
130            Self::Brief => bridge_brief_symbol(),
131            Self::Ask => bridge_ask_symbol(),
132            Self::RunAsk => bridge_run_ask_symbol(),
133        }
134    }
135}
136
137/// Runtime callable implementing one BRIDGE export.
138#[derive(Clone)]
139pub struct BridgeFunction {
140    kind: BridgeFunctionKind,
141}
142
143impl BridgeFunction {
144    /// Builds a function object for `kind`.
145    pub fn new(kind: BridgeFunctionKind) -> Self {
146        Self { kind }
147    }
148
149    /// Returns this function's runtime symbol.
150    pub fn symbol(&self) -> Symbol {
151        self.kind.symbol()
152    }
153
154    /// Builds a shared function object for `kind`.
155    pub fn value(kind: BridgeFunctionKind) -> Arc<Self> {
156        Arc::new(Self::new(kind))
157    }
158}
159
160impl Object for BridgeFunction {
161    fn display(&self, _cx: &mut Cx) -> Result<String> {
162        Ok(format!("#<function {}>", self.symbol()))
163    }
164
165    fn as_any(&self) -> &dyn std::any::Any {
166        self
167    }
168}
169
170impl ObjectCompat for BridgeFunction {
171    fn as_callable(&self) -> Option<&dyn Callable> {
172        Some(self)
173    }
174}
175
176impl Callable for BridgeFunction {
177    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
178        match self.kind {
179            BridgeFunctionKind::Tx => call_tx(cx, args),
180            BridgeFunctionKind::Rx => call_rx(cx, args),
181            BridgeFunctionKind::Report => call_report(cx, args),
182            BridgeFunctionKind::Receipt => call_receipt(cx, args),
183            BridgeFunctionKind::Brief => call_brief(cx, args),
184            BridgeFunctionKind::Ask => call_ask(cx, args),
185            BridgeFunctionKind::RunAsk => call_run_ask(cx, args),
186        }
187    }
188
189    fn browse_args_shape(&self, _cx: &mut Cx) -> Result<Option<sim_kernel::ShapeRef>> {
190        let shape: Arc<dyn Shape> = match self.kind {
191            BridgeFunctionKind::Tx | BridgeFunctionKind::Report => {
192                Arc::new(ListShape::new(vec![Arc::new(AnyShape)]))
193            }
194            BridgeFunctionKind::Rx => Arc::new(ListShape::new(vec![Arc::new(AnyShape)])),
195            BridgeFunctionKind::Receipt => Arc::new(ListShape::new(vec![Arc::new(AnyShape)])),
196            BridgeFunctionKind::Brief => Arc::new(ListShape::new(vec![
197                Arc::new(AnyShape),
198                Arc::new(AnyShape),
199                Arc::new(AnyShape),
200            ])),
201            BridgeFunctionKind::Ask => {
202                Arc::new(OneOfShape::new(vec![any_args_shape(4), any_args_shape(5)]))
203            }
204            BridgeFunctionKind::RunAsk => {
205                Arc::new(OneOfShape::new(vec![any_args_shape(2), any_args_shape(3)]))
206            }
207        };
208        Ok(Some(shape_value(
209            Symbol::qualified(self.symbol().to_string(), "args"),
210            shape,
211        )))
212    }
213
214    fn browse_result_shape(&self, _cx: &mut Cx) -> Result<Option<sim_kernel::ShapeRef>> {
215        Ok(Some(shape_value(
216            Symbol::qualified(self.symbol().to_string(), "result"),
217            Arc::new(AnyShape),
218        )))
219    }
220}
221
222fn call_tx(cx: &mut Cx, args: Args) -> Result<Value> {
223    let packet = packet_arg(cx, args, "bridge/tx expects one packet expression")?;
224    let request = bridge_tx(cx, &BridgeBook::standard(), &packet)?;
225    cx.factory().opaque(Arc::new(request))
226}
227
228fn call_rx(cx: &mut Cx, args: Args) -> Result<Value> {
229    let response = one_expr_arg(cx, args, "bridge/rx expects one model response expression")?;
230    let (packet, report) = crate::bridge_rx(cx, &BridgeBook::standard(), response, None)?;
231    cx.factory().expr(sim_kernel::Expr::Map(vec![
232        sim_value::build::entry("packet", packet_to_expr(&packet)),
233        sim_value::build::entry("report", report.to_expr()),
234    ]))
235}
236
237fn call_report(cx: &mut Cx, args: Args) -> Result<Value> {
238    let packet = packet_arg(cx, args, "bridge/report expects one packet expression")?;
239    let report = rx_check(cx, &BridgeBook::standard(), &packet, None)?;
240    cx.factory().expr(report.to_expr())
241}
242
243fn call_receipt(cx: &mut Cx, args: Args) -> Result<Value> {
244    let packet = packet_arg(cx, args, "bridge/receipt expects one packet expression")?;
245    let report = rx_check(cx, &BridgeBook::standard(), &packet, None)?;
246    let receipt = receipt_packet_for_report(&report, "sim")?;
247    cx.factory().expr(packet_to_expr(&receipt))
248}
249
250fn call_brief(cx: &mut Cx, args: Args) -> Result<Value> {
251    let mut exprs = expr_args(
252        cx,
253        args,
254        "bridge/brief expects target, frame, and return shape",
255    )?;
256    let [target, frame, return_shape] = take_three(&mut exprs)?;
257    let frame = BridgeFramePayload::from_expr(&frame)?;
258    let packet = bridge_brief(&target_name(&target)?, frame, return_shape)?;
259    cx.factory().expr(packet_to_expr(&packet))
260}
261
262fn call_ask(cx: &mut Cx, args: Args) -> Result<Value> {
263    let mut exprs = expr_args(
264        cx,
265        args,
266        "bridge/ask expects target, call, params, return shape, and optional model params",
267    )?;
268    if !matches!(exprs.len(), 4 | 5) {
269        return Err(Error::Eval(format!(
270            "bridge/ask expects 4 or 5 argument(s), found {}",
271            exprs.len()
272        )));
273    }
274    let model_params = if exprs.len() == 5 {
275        call_params(&exprs.pop().expect("length checked"))?
276    } else {
277        Vec::new()
278    };
279    let return_shape = exprs.pop().expect("length checked");
280    let params = exprs.pop().expect("length checked");
281    let call = exprs.pop().expect("length checked");
282    let target = exprs.pop().expect("length checked");
283    let packet = ask_packet_with_model_params(
284        cx,
285        &call_name(&call)?,
286        call_params(&params)?,
287        model_params,
288        return_shape,
289        &target_name(&target)?,
290    )?;
291    cx.factory().expr(packet_to_expr(&packet))
292}
293
294fn call_run_ask(cx: &mut Cx, args: Args) -> Result<Value> {
295    let mut values = args.into_vec();
296    if !matches!(values.len(), 2 | 3) {
297        return Err(Error::Eval(format!(
298            "{BRIDGE_RUN_ASK_NAME} expects 2 or 3 argument(s), found {}",
299            values.len()
300        )));
301    }
302    let policy = if values.len() == 3 {
303        repair_policy(cx, &values.pop().expect("length checked"))?
304    } else {
305        RepairPolicy::default()
306    };
307    let packet = expr_to_packet(&values.pop().expect("length checked").object().as_expr(cx)?)?;
308    let target = values.pop().expect("length checked");
309    let Some(fabric) = target.object().as_eval_fabric() else {
310        return Err(Error::TypeMismatch {
311            expected: "eval-fabric",
312            found: "non-eval-fabric",
313        });
314    };
315    let reply = run_ask_with_policy(cx, fabric, packet, policy)?;
316    cx.factory().expr(packet_to_expr(&reply))
317}
318
319fn any_args_shape(arity: usize) -> Arc<dyn Shape> {
320    Arc::new(ListShape::new(
321        (0..arity)
322            .map(|_| Arc::new(AnyShape) as Arc<dyn Shape>)
323            .collect(),
324    ))
325}
326
327fn repair_policy(cx: &mut Cx, value: &Value) -> Result<RepairPolicy> {
328    let expr = value.object().as_expr(cx)?;
329    let sim_kernel::Expr::Number(number) = expr else {
330        return Err(Error::Eval(format!(
331            "{BRIDGE_RUN_ASK_NAME} retries must be a non-negative integer"
332        )));
333    };
334    let retries = number.canonical.parse::<u8>().map_err(|_| {
335        Error::Eval(format!(
336            "{BRIDGE_RUN_ASK_NAME} retries must be a non-negative integer"
337        ))
338    })?;
339    Ok(RepairPolicy::new(retries))
340}
341
342fn packet_arg(
343    cx: &mut Cx,
344    args: Args,
345    message: &'static str,
346) -> Result<sim_codec_bridge::BridgePacket> {
347    expr_to_packet(&one_expr_arg(cx, args, message)?)
348}
349
350fn expr_args(cx: &mut Cx, args: Args, message: &'static str) -> Result<Vec<sim_kernel::Expr>> {
351    let values = args.into_vec();
352    if values.is_empty() {
353        return Err(Error::Eval(message.to_owned()));
354    }
355    values
356        .into_iter()
357        .map(|value| value.object().as_expr(cx))
358        .collect()
359}
360
361fn one_expr_arg(cx: &mut Cx, args: Args, message: &'static str) -> Result<sim_kernel::Expr> {
362    let mut values = args.into_vec();
363    if values.len() != 1 {
364        return Err(Error::Eval(message.to_owned()));
365    }
366    values.remove(0).object().as_expr(cx)
367}
368
369fn take_three(exprs: &mut Vec<sim_kernel::Expr>) -> Result<[sim_kernel::Expr; 3]> {
370    let [target, frame, return_shape] =
371        std::mem::take(exprs).try_into().map_err(|values: Vec<_>| {
372            Error::Eval(format!(
373                "bridge/brief expects 3 argument(s), found {}",
374                values.len()
375            ))
376        })?;
377    Ok([target, frame, return_shape])
378}
379
380fn target_name(expr: &sim_kernel::Expr) -> Result<String> {
381    match expr {
382        sim_kernel::Expr::String(target) => Ok(target.clone()),
383        sim_kernel::Expr::Symbol(target) => Ok(target.as_qualified_str().to_owned()),
384        _ => Err(Error::Eval(
385            "bridge/brief target must be a string or symbol".to_owned(),
386        )),
387    }
388}
389
390fn call_name(expr: &sim_kernel::Expr) -> Result<String> {
391    match expr {
392        sim_kernel::Expr::String(name) => Ok(name.clone()),
393        sim_kernel::Expr::Symbol(name) => Ok(name.as_qualified_str()),
394        _ => Err(Error::Eval(
395            "bridge/ask call must be a string or symbol".to_owned(),
396        )),
397    }
398}
399
400fn call_params(expr: &sim_kernel::Expr) -> Result<Vec<(String, sim_kernel::Expr)>> {
401    let sim_kernel::Expr::Map(entries) = expr else {
402        return Err(Error::Eval("bridge/ask params must be a map".to_owned()));
403    };
404    entries
405        .iter()
406        .map(|(key, value)| {
407            let name = match key {
408                sim_kernel::Expr::String(name) => name.clone(),
409                sim_kernel::Expr::Symbol(name) => name.as_qualified_str(),
410                _ => {
411                    return Err(Error::Eval(
412                        "bridge/ask param keys must be strings or symbols".to_owned(),
413                    ));
414                }
415            };
416            Ok((name, value.clone()))
417        })
418        .collect()
419}