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