netidx_bscript/
lib.rs

1#[macro_use]
2extern crate netidx_core;
3#[macro_use]
4extern crate combine;
5#[macro_use]
6extern crate serde_derive;
7
8pub mod env;
9pub mod expr;
10pub mod node;
11pub mod rt;
12pub mod stdfn;
13pub mod typ;
14
15use crate::{
16    env::Env,
17    expr::{ExprId, ExprKind, ModPath},
18    typ::{FnType, Type},
19};
20use anyhow::{bail, Result};
21use arcstr::ArcStr;
22use expr::Expr;
23use fxhash::FxHashMap;
24use netidx::{
25    path::Path,
26    publisher::{Id, Val, WriteRequest},
27    subscriber::{self, Dval, SubId, UpdatesFlags, Value},
28};
29use netidx_protocols::rpc::server::{ArgSpec, RpcCall};
30use node::compiler;
31use parking_lot::RwLock;
32use std::{
33    collections::{hash_map::Entry, HashMap},
34    fmt::Debug,
35    sync::{self, LazyLock},
36    time::Duration,
37};
38use triomphe::Arc;
39
40#[cfg(test)]
41#[macro_use]
42mod tests;
43
44atomic_id!(BindId);
45atomic_id!(LambdaId);
46
47impl BindId {
48    pub(crate) fn from_u64(v: u64) -> Self {
49        BindId(v)
50    }
51}
52
53#[macro_export]
54macro_rules! errf {
55    ($pat:expr, $($arg:expr),*) => {
56        Some(Value::Error(ArcStr::from(format_compact!($pat, $($arg),*).as_str())))
57    };
58    ($pat:expr) => { Some(Value::Error(ArcStr::from(format_compact!($pat).as_str()))) };
59}
60
61#[macro_export]
62macro_rules! err {
63    ($pat:literal) => {
64        Some(Value::Error(literal!($pat)))
65    };
66}
67
68pub trait UserEvent: Clone + Debug + 'static {
69    fn clear(&mut self);
70}
71
72#[derive(Debug, Clone)]
73pub struct NoUserEvent;
74
75impl UserEvent for NoUserEvent {
76    fn clear(&mut self) {}
77}
78
79/// Event represents all the things that happened simultaneously in a
80/// given execution cycle. Event may contain only one update for each
81/// variable and netidx subscription in a given cycle, if more updates
82/// happen simultaneously they must be queued and deferred to later
83/// cycles.
84#[derive(Debug)]
85pub struct Event<E: UserEvent> {
86    pub init: bool,
87    pub variables: FxHashMap<BindId, Value>,
88    pub netidx: FxHashMap<SubId, subscriber::Event>,
89    pub writes: FxHashMap<Id, WriteRequest>,
90    pub rpc_calls: FxHashMap<BindId, RpcCall>,
91    pub user: E,
92}
93
94impl<E: UserEvent> Event<E> {
95    pub fn new(user: E) -> Self {
96        Event {
97            init: false,
98            variables: HashMap::default(),
99            netidx: HashMap::default(),
100            writes: HashMap::default(),
101            rpc_calls: HashMap::default(),
102            user,
103        }
104    }
105
106    pub fn clear(&mut self) {
107        let Self { init, variables, netidx, rpc_calls, writes, user } = self;
108        *init = false;
109        variables.clear();
110        netidx.clear();
111        rpc_calls.clear();
112        writes.clear();
113        user.clear();
114    }
115}
116
117pub type Node<C, E> = Box<dyn Update<C, E>>;
118
119pub type BuiltInInitFn<C, E> = sync::Arc<
120    dyn for<'a, 'b, 'c> Fn(
121            &'a mut ExecCtx<C, E>,
122            &'a FnType,
123            &'b ModPath,
124            &'c [Node<C, E>],
125            ExprId,
126        ) -> Result<Box<dyn Apply<C, E>>>
127        + Send
128        + Sync
129        + 'static,
130>;
131
132pub type InitFn<C, E> = sync::Arc<
133    dyn for<'a, 'b> Fn(
134            &'a mut ExecCtx<C, E>,
135            &'b [Node<C, E>],
136            ExprId,
137        ) -> Result<Box<dyn Apply<C, E>>>
138        + Send
139        + Sync
140        + 'static,
141>;
142
143/// Apply is a kind of node that represents a function application. It
144/// does not hold ownership of it's arguments, instead those are held
145/// by a CallSite node. This allows us to change the function called
146/// at runtime without recompiling the arguments.
147pub trait Apply<C: Ctx, E: UserEvent>: Debug + Send + Sync + 'static {
148    fn update(
149        &mut self,
150        ctx: &mut ExecCtx<C, E>,
151        from: &mut [Node<C, E>],
152        event: &mut Event<E>,
153    ) -> Option<Value>;
154
155    /// delete any internally generated nodes, only needed for
156    /// builtins that dynamically generate code at runtime
157    fn delete(&mut self, _ctx: &mut ExecCtx<C, E>) {
158        ()
159    }
160
161    /// apply custom typechecking to the lambda, only needed for
162    /// builtins that take lambdas as arguments
163    fn typecheck(
164        &mut self,
165        _ctx: &mut ExecCtx<C, E>,
166        _from: &mut [Node<C, E>],
167    ) -> Result<()> {
168        Ok(())
169    }
170
171    /// return the lambdas type, builtins do not need to implement
172    /// this, it is implemented by the BuiltIn wrapper
173    fn typ(&self) -> Arc<FnType> {
174        const EMPTY: LazyLock<Arc<FnType>> = LazyLock::new(|| {
175            Arc::new(FnType {
176                args: Arc::from_iter([]),
177                constraints: Arc::new(RwLock::new(vec![])),
178                rtype: Type::Bottom,
179                vargs: None,
180            })
181        });
182        Arc::clone(&*EMPTY)
183    }
184
185    /// push a list of variables the lambda references in addition to
186    /// it's arguments. Builtins only need to implement this if they
187    /// lookup and reference variables from the environment that were
188    /// not explicitly passed in.
189    fn refs<'a>(&'a self, _f: &'a mut (dyn FnMut(BindId) + 'a)) {
190        ()
191    }
192}
193
194/// Update represents a regular graph node, as opposed to a function
195/// application represented by Apply. Regular graph nodes are used for
196/// every built in node except for builtin functions.
197pub trait Update<C: Ctx, E: UserEvent>: Debug + Send + Sync + 'static {
198    /// update the node with the specified event and return any output
199    /// it might generate
200    fn update(&mut self, ctx: &mut ExecCtx<C, E>, event: &mut Event<E>) -> Option<Value>;
201
202    /// delete the node and it's children from the specified context
203    fn delete(&mut self, ctx: &mut ExecCtx<C, E>);
204
205    /// type check the node and it's children
206    fn typecheck(&mut self, ctx: &mut ExecCtx<C, E>) -> Result<()>;
207
208    /// return the node type
209    fn typ(&self) -> &Type;
210
211    /// record any variables the node references by calling f
212    fn refs<'a>(&'a self, f: &'a mut (dyn FnMut(BindId) + 'a));
213
214    /// return the original expression used to compile this node
215    fn spec(&self) -> &Expr;
216}
217
218pub trait BuiltIn<C: Ctx, E: UserEvent> {
219    const NAME: &str;
220    const TYP: LazyLock<FnType>;
221
222    fn init(ctx: &mut ExecCtx<C, E>) -> BuiltInInitFn<C, E>;
223}
224
225pub trait Ctx: Debug + 'static {
226    fn clear(&mut self);
227
228    /// Subscribe to the specified netidx path. When the subscription
229    /// updates you are expected to deliver Netidx events to the
230    /// expression specified by ref_by.
231    fn subscribe(&mut self, flags: UpdatesFlags, path: Path, ref_by: ExprId) -> Dval;
232
233    /// Called when a subscription is no longer needed
234    fn unsubscribe(&mut self, path: Path, dv: Dval, ref_by: ExprId);
235
236    /// List the netidx path, return Value::Null if the path did not
237    /// change. When the path did update you should send the output
238    /// back as a properly formatted struct with two fields, rows and
239    /// columns both containing string arrays.
240    fn list(&mut self, id: BindId, path: Path);
241
242    /// List the table at path, return Value::Null if the path did not
243    /// change
244    fn list_table(&mut self, id: BindId, path: Path);
245
246    /// list or table will no longer be called on this BindId, and
247    /// related resources can be cleaned up.
248    fn stop_list(&mut self, id: BindId);
249
250    /// Publish the specified value, returning it's Id, which must be
251    /// used to update the value and unpublish it. If the path is
252    /// already published, return an error.
253    fn publish(&mut self, path: Path, value: Value, ref_by: ExprId) -> Result<Val>;
254
255    /// Update the specified value
256    fn update(&mut self, id: &Val, value: Value);
257
258    /// Stop publishing the specified id
259    fn unpublish(&mut self, id: Val, ref_by: ExprId);
260
261    /// This will be called by the compiler whenever a bound variable
262    /// is referenced. The ref_by is the toplevel expression that
263    /// contains the variable reference. When a variable event
264    /// happens, you should update all the toplevel expressions that
265    /// ref that variable.
266    ///
267    /// ref_var will also be called when a bound lambda expression is
268    /// referenced, in that case the ref_by id will be the toplevel
269    /// expression containing the call site.
270    fn ref_var(&mut self, id: BindId, ref_by: ExprId);
271    fn unref_var(&mut self, id: BindId, ref_by: ExprId);
272
273    /// Called by the ExecCtx when set_var is called on it. All
274    /// expressions that ref the id should be updated when this
275    /// happens.
276    ///
277    /// The runtime must deliver all set_vars in a single event except
278    /// that set_vars for the same variable in the same cycle must be
279    /// queued and deferred to the next cycle.
280    ///
281    /// The runtime MUST NOT change event while a cycle is in
282    /// progress. set_var must be queued until the cycle ends and then
283    /// presented as a new batch.
284    fn set_var(&mut self, id: BindId, value: Value);
285
286    /// This must return results from the same path in the call order.
287    ///
288    /// when the rpc returns you are expected to deliver a Variable
289    /// event with the specified id to the expression specified by
290    /// ref_by.
291    fn call_rpc(&mut self, name: Path, args: Vec<(ArcStr, Value)>, id: BindId);
292
293    /// Publish an rpc at the specified path with the specified
294    /// procedure level doc and arg spec.
295    ///
296    /// When the RPC is called the rpc table in event will be
297    /// populated under the specified bind id.
298    ///
299    /// If the procedure is already published an error will be
300    /// returned
301    fn publish_rpc(
302        &mut self,
303        name: Path,
304        doc: Value,
305        spec: Vec<ArgSpec>,
306        id: BindId,
307    ) -> Result<()>;
308
309    /// unpublish the rpc identified by the bind id.
310    fn unpublish_rpc(&mut self, name: Path);
311
312    /// arrange to have a Timer event delivered after timeout. When
313    /// the timer expires you are expected to deliver a Variable event
314    /// for the id, containing the current time.
315    fn set_timer(&mut self, id: BindId, timeout: Duration);
316}
317
318pub struct ExecCtx<C: Ctx, E: UserEvent> {
319    builtins: FxHashMap<&'static str, (FnType, BuiltInInitFn<C, E>)>,
320    std: Vec<Node<C, E>>,
321    pub env: Env<C, E>,
322    pub cached: FxHashMap<BindId, Value>,
323    pub user: C,
324}
325
326impl<C: Ctx, E: UserEvent> ExecCtx<C, E> {
327    pub fn clear(&mut self) {
328        self.env.clear();
329        self.user.clear();
330    }
331
332    /// build a new context with only the core library
333    pub fn new_no_std(user: C) -> Self {
334        let mut t = ExecCtx {
335            env: Env::new(),
336            builtins: FxHashMap::default(),
337            std: vec![],
338            cached: HashMap::default(),
339            user,
340        };
341        let core = stdfn::core::register(&mut t);
342        let root = ModPath(Path::root());
343        let node = compile(&mut t, &root, core).expect("error compiling core");
344        t.std.push(node);
345        let node = compile(
346            &mut t,
347            &root,
348            ExprKind::Use { name: ModPath::from(["core"]) }.to_expr(Default::default()),
349        )
350        .expect("error compiling use core");
351        t.std.push(node);
352        t
353    }
354
355    /// build a new context with the full standard library
356    pub fn new(user: C) -> Self {
357        let mut t = Self::new_no_std(user);
358        let root = ModPath(Path::root());
359        let str = stdfn::str::register(&mut t);
360        let node = compile(&mut t, &root, str).expect("failed to compile the str module");
361        t.std.push(node);
362        let re = stdfn::re::register(&mut t);
363        let node = compile(&mut t, &root, re).expect("failed to compile the re module");
364        t.std.push(node);
365        let time = stdfn::time::register(&mut t);
366        let node =
367            compile(&mut t, &root, time).expect("failed to compile the time module");
368        t.std.push(node);
369        let rand = stdfn::rand::register(&mut t);
370        let node = compile(&mut t, &root, rand).expect("failed to compile rand module");
371        t.std.push(node);
372        let net = stdfn::net::register(&mut t);
373        let node = compile(&mut t, &root, net).expect("failed to compile the net module");
374        t.std.push(node);
375        t
376    }
377
378    pub fn register_builtin<T: BuiltIn<C, E>>(&mut self) -> Result<()> {
379        let f = T::init(self);
380        match self.builtins.entry(T::NAME) {
381            Entry::Vacant(e) => {
382                e.insert((T::TYP.clone(), f));
383            }
384            Entry::Occupied(_) => bail!("builtin {} is already registered", T::NAME),
385        }
386        Ok(())
387    }
388
389    /// Built in functions should call this when variables are set
390    /// unless they are sure the variable does not need to be
391    /// cached. This will also call the user ctx set_var.
392    pub fn set_var(&mut self, id: BindId, v: Value) {
393        self.cached.insert(id, v.clone());
394        self.user.set_var(id, v)
395    }
396}
397
398/// compile the expression into a node graph in the specified context
399/// and scope, return the root node or an error if compilation failed.
400pub fn compile<C: Ctx, E: UserEvent>(
401    ctx: &mut ExecCtx<C, E>,
402    scope: &ModPath,
403    spec: Expr,
404) -> Result<Node<C, E>> {
405    let top_id = spec.id;
406    let env = ctx.env.clone();
407    let mut node = match compiler::compile(ctx, spec, scope, top_id) {
408        Ok(n) => n,
409        Err(e) => {
410            ctx.env = env;
411            return Err(e);
412        }
413    };
414    if let Err(e) = node.typecheck(ctx) {
415        ctx.env = env;
416        return Err(e);
417    }
418    Ok(node)
419}