Skip to main content

graphix_compiler/
lib.rs

1#![doc(
2    html_logo_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg",
3    html_favicon_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg"
4)]
5#[macro_use]
6extern crate netidx_core;
7#[macro_use]
8extern crate combine;
9#[macro_use]
10extern crate serde_derive;
11
12pub mod env;
13pub mod expr;
14pub mod node;
15pub mod typ;
16
17use crate::{
18    env::Env,
19    expr::{ExprId, ModPath},
20    node::lambda::LambdaDef,
21    typ::{FnType, Type},
22};
23use anyhow::{bail, Result};
24use arcstr::ArcStr;
25use enumflags2::{bitflags, BitFlags};
26use expr::Expr;
27use futures::channel::mpsc;
28use fxhash::{FxHashMap, FxHashSet};
29use log::info;
30use netidx::{
31    path::Path,
32    publisher::{Id, Val, WriteRequest},
33    subscriber::{self, Dval, SubId, UpdatesFlags, Value},
34};
35use netidx_protocols::rpc::server::{ArgSpec, RpcCall};
36use netidx_value::{abstract_type::AbstractWrapper, Abstract};
37use node::compiler;
38use parking_lot::{Mutex, RwLock};
39use poolshark::{
40    global::{GPooled, Pool},
41    local::LPooled,
42};
43use std::{
44    any::{Any, TypeId},
45    cell::Cell,
46    collections::{
47        hash_map::{self, Entry},
48        HashMap,
49    },
50    fmt::Debug,
51    mem,
52    sync::{
53        self,
54        atomic::{AtomicBool, Ordering},
55        LazyLock,
56    },
57    time::Duration,
58};
59use tokio::{task, time::Instant};
60use triomphe::Arc;
61use uuid::Uuid;
62
63#[derive(Debug, Clone, Copy)]
64#[bitflags]
65#[repr(u64)]
66pub enum CFlag {
67    WarnUnhandled,
68    WarnUnused,
69    WarningsAreErrors,
70}
71
72#[allow(dead_code)]
73static TRACE: AtomicBool = AtomicBool::new(false);
74
75#[allow(dead_code)]
76fn set_trace(b: bool) {
77    TRACE.store(b, Ordering::Relaxed)
78}
79
80#[allow(dead_code)]
81fn with_trace<F: FnOnce() -> Result<R>, R>(enable: bool, spec: &Expr, f: F) -> Result<R> {
82    let set = if enable {
83        eprintln!("trace enabled at {}, spec: {}", spec.pos, spec);
84        let prev = trace();
85        set_trace(true);
86        !prev
87    } else {
88        false
89    };
90    let r = match f() {
91        Err(e) => {
92            eprintln!("traced at {} failed with {e:?}", spec.pos);
93            Err(e)
94        }
95        r => r,
96    };
97    if set {
98        eprintln!("trace disabled at {}", spec.pos);
99        set_trace(false)
100    }
101    r
102}
103
104#[allow(dead_code)]
105fn trace() -> bool {
106    TRACE.load(Ordering::Relaxed)
107}
108
109#[macro_export]
110macro_rules! tdbg {
111    ($e:expr) => {
112        if $crate::trace() {
113            dbg!($e)
114        } else {
115            $e
116        }
117    };
118}
119
120#[macro_export]
121macro_rules! err {
122    ($tag:expr, $err:literal) => {{
123        let e: Value = ($tag.clone(), ::arcstr::literal!($err)).into();
124        Value::Error(::triomphe::Arc::new(e))
125    }};
126}
127
128#[macro_export]
129macro_rules! errf {
130    ($tag:expr, $fmt:expr, $($args:expr),*) => {{
131        let msg: ArcStr = ::compact_str::format_compact!($fmt, $($args),*).as_str().into();
132        let e: Value = ($tag.clone(), msg).into();
133        Value::Error(::triomphe::Arc::new(e))
134    }};
135    ($tag:expr, $fmt:expr) => {{
136        let msg: ArcStr = ::compact_str::format_compact!($fmt).as_str().into();
137        let e: Value = ($tag.clone(), msg).into();
138        Value::Error(::triomphe::Arc::new(e))
139    }};
140}
141
142#[macro_export]
143macro_rules! defetyp {
144    ($name:ident, $tag_name:ident, $tag:literal, $typ:expr) => {
145        static $tag_name: ArcStr = ::arcstr::literal!($tag);
146        static $name: ::std::sync::LazyLock<$crate::typ::Type> =
147            ::std::sync::LazyLock::new(|| {
148                let scope = $crate::expr::ModPath::root();
149                $crate::expr::parser::parse_type(&format!($typ, $tag))
150                    .expect("failed to parse type")
151                    .scope_refs(&scope)
152            });
153    };
154}
155
156defetyp!(CAST_ERR, CAST_ERR_TAG, "InvalidCast", "Error<`{}(string)>");
157
158atomic_id!(LambdaId);
159
160impl From<u64> for LambdaId {
161    fn from(v: u64) -> Self {
162        LambdaId(v)
163    }
164}
165
166atomic_id!(BindId);
167
168impl From<u64> for BindId {
169    fn from(v: u64) -> Self {
170        BindId(v)
171    }
172}
173
174impl TryFrom<Value> for BindId {
175    type Error = anyhow::Error;
176
177    fn try_from(value: Value) -> Result<Self> {
178        match value {
179            Value::U64(id) => Ok(BindId(id)),
180            v => bail!("invalid bind id {v}"),
181        }
182    }
183}
184
185pub trait UserEvent: Clone + Debug + Any {
186    fn clear(&mut self);
187}
188
189pub trait CustomBuiltinType: Debug + Any + Send + Sync {}
190
191impl CustomBuiltinType for Value {}
192impl CustomBuiltinType for Option<Value> {}
193
194#[derive(Debug, Clone)]
195pub struct NoUserEvent;
196
197impl UserEvent for NoUserEvent {
198    fn clear(&mut self) {}
199}
200
201#[derive(Debug, Clone, Copy)]
202#[bitflags]
203#[repr(u64)]
204pub enum PrintFlag {
205    /// Dereference type variables and print both the tvar name and the bound
206    /// type or "unbound".
207    DerefTVars,
208    /// Replace common primitives with shorter type names as defined
209    /// in core. e.g. Any, instead of the set of every primitive type.
210    ReplacePrims,
211    /// When formatting an Origin don't print the source, just the location
212    NoSource,
213    /// When formatting an Origin don't print the origin's parents
214    NoParents,
215}
216
217thread_local! {
218    static PRINT_FLAGS: Cell<BitFlags<PrintFlag>> = Cell::new(PrintFlag::ReplacePrims.into());
219}
220
221/// global pool of channel watch batches
222pub static CBATCH_POOL: LazyLock<Pool<Vec<(BindId, Box<dyn CustomBuiltinType>)>>> =
223    LazyLock::new(|| Pool::new(10000, 1000));
224
225/// For the duration of the closure F change the way type variables
226/// are formatted (on this thread only) according to the specified
227/// flags.
228pub fn format_with_flags<G: Into<BitFlags<PrintFlag>>, R, F: FnOnce() -> R>(
229    flags: G,
230    f: F,
231) -> R {
232    let prev = PRINT_FLAGS.replace(flags.into());
233    let res = f();
234    PRINT_FLAGS.set(prev);
235    res
236}
237
238/// Event represents all the things that happened simultaneously in a
239/// given execution cycle. Event may contain only one update for each
240/// variable and netidx subscription in a given cycle, if more updates
241/// happen simultaneously they must be queued and deferred to later
242/// cycles.
243#[derive(Debug)]
244pub struct Event<E: UserEvent> {
245    pub init: bool,
246    pub variables: FxHashMap<BindId, Value>,
247    pub netidx: FxHashMap<SubId, subscriber::Event>,
248    pub writes: FxHashMap<Id, WriteRequest>,
249    pub rpc_calls: FxHashMap<BindId, RpcCall>,
250    pub custom: FxHashMap<BindId, Box<dyn CustomBuiltinType>>,
251    pub user: E,
252}
253
254impl<E: UserEvent> Event<E> {
255    pub fn new(user: E) -> Self {
256        Event {
257            init: false,
258            variables: HashMap::default(),
259            netidx: HashMap::default(),
260            writes: HashMap::default(),
261            rpc_calls: HashMap::default(),
262            custom: HashMap::default(),
263            user,
264        }
265    }
266
267    pub fn clear(&mut self) {
268        let Self { init, variables, netidx, rpc_calls, writes, custom, user } = self;
269        *init = false;
270        variables.clear();
271        netidx.clear();
272        rpc_calls.clear();
273        custom.clear();
274        writes.clear();
275        user.clear();
276    }
277}
278
279#[derive(Debug, Clone, Default)]
280pub struct Refs {
281    refed: LPooled<FxHashSet<BindId>>,
282    bound: LPooled<FxHashSet<BindId>>,
283}
284
285impl Refs {
286    pub fn clear(&mut self) {
287        self.refed.clear();
288        self.bound.clear();
289    }
290
291    pub fn with_external_refs(&self, mut f: impl FnMut(BindId)) {
292        for id in &*self.refed {
293            if !self.bound.contains(id) {
294                f(*id);
295            }
296        }
297    }
298}
299
300pub type Node<R, E> = Box<dyn Update<R, E>>;
301
302pub type InitFn<R, E> = sync::Arc<
303    dyn for<'a, 'b, 'c> Fn(
304            &'a Scope,
305            &'b mut ExecCtx<R, E>,
306            &'c mut [Node<R, E>],
307            ExprId,
308            bool,
309        ) -> Result<Box<dyn Apply<R, E>>>
310        + Send
311        + Sync
312        + 'static,
313>;
314
315/// Apply is a kind of node that represents a function application. It
316/// does not hold ownership of it's arguments, instead those are held
317/// by a CallSite node. This allows us to change the function called
318/// at runtime without recompiling the arguments.
319pub trait Apply<R: Rt, E: UserEvent>: Debug + Send + Sync + Any {
320    fn update(
321        &mut self,
322        ctx: &mut ExecCtx<R, E>,
323        from: &mut [Node<R, E>],
324        event: &mut Event<E>,
325    ) -> Option<Value>;
326
327    /// delete any internally generated nodes, only needed for
328    /// builtins that dynamically generate code at runtime
329    fn delete(&mut self, _ctx: &mut ExecCtx<R, E>) {
330        ()
331    }
332
333    /// apply custom typechecking to the lambda, only needed for
334    /// builtins that take lambdas as arguments
335    fn typecheck(
336        &mut self,
337        _ctx: &mut ExecCtx<R, E>,
338        _from: &mut [Node<R, E>],
339    ) -> Result<()> {
340        Ok(())
341    }
342
343    /// return the lambdas type, builtins do not need to implement
344    /// this, it is implemented by the BuiltIn wrapper
345    fn typ(&self) -> Arc<FnType> {
346        static EMPTY: LazyLock<Arc<FnType>> = LazyLock::new(|| {
347            Arc::new(FnType {
348                args: Arc::from_iter([]),
349                constraints: Arc::new(RwLock::new(LPooled::take())),
350                rtype: Type::Bottom,
351                throws: Type::Bottom,
352                vargs: None,
353                explicit_throws: false,
354            })
355        });
356        Arc::clone(&*EMPTY)
357    }
358
359    /// Populate the Refs structure with all the ids bound and refed by this
360    /// node. It is only necessary for builtins to implement this if they create
361    /// nodes, such as call sites.
362    fn refs<'a>(&self, _refs: &mut Refs) {}
363
364    /// put the node to sleep, used in conditions like select for branches that
365    /// are not selected. Any cached values should be cleared on sleep.
366    fn sleep(&mut self, _ctx: &mut ExecCtx<R, E>);
367}
368
369/// Update represents a regular graph node, as opposed to a function
370/// application represented by Apply. Regular graph nodes are used for
371/// every built in node except for builtin functions.
372pub trait Update<R: Rt, E: UserEvent>: Debug + Send + Sync + Any + 'static {
373    /// update the node with the specified event and return any output
374    /// it might generate
375    fn update(&mut self, ctx: &mut ExecCtx<R, E>, event: &mut Event<E>) -> Option<Value>;
376
377    /// delete the node and it's children from the specified context
378    fn delete(&mut self, ctx: &mut ExecCtx<R, E>);
379
380    /// type check the node and it's children
381    fn typecheck(&mut self, ctx: &mut ExecCtx<R, E>) -> Result<()>;
382
383    /// return the node type
384    fn typ(&self) -> &Type;
385
386    /// Populate the Refs structure with all the bind ids either refed or bound
387    /// by the node and it's children
388    fn refs(&self, refs: &mut Refs);
389
390    /// return the original expression used to compile this node
391    fn spec(&self) -> &Expr;
392
393    /// put the node to sleep, called on unselected branches
394    fn sleep(&mut self, ctx: &mut ExecCtx<R, E>);
395}
396
397pub type BuiltInInitFn<R, E> = for<'a, 'b, 'c> fn(
398    &'a mut ExecCtx<R, E>,
399    &'a FnType,
400    &'b Scope,
401    &'c [Node<R, E>],
402    ExprId,
403) -> Result<Box<dyn Apply<R, E>>>;
404
405/// Trait implemented by graphix built-in functions implemented in rust. This
406/// trait isn't meant to be implemented manually, use derive(BuiltIn) from the
407/// graphix-derive crate
408pub trait BuiltIn<R: Rt, E: UserEvent> {
409    const NAME: &str;
410    const TYP: LazyLock<FnType>;
411
412    fn init<'a, 'b, 'c>(
413        ctx: &'a mut ExecCtx<R, E>,
414        typ: &'a FnType,
415        scope: &'b Scope,
416        from: &'c [Node<R, E>],
417        top_id: ExprId,
418    ) -> Result<Box<dyn Apply<R, E>>>;
419}
420
421pub trait Abortable {
422    fn abort(&self);
423}
424
425impl Abortable for task::AbortHandle {
426    fn abort(&self) {
427        task::AbortHandle::abort(self)
428    }
429}
430
431pub trait Rt: Debug + Any {
432    type AbortHandle: Abortable;
433
434    fn clear(&mut self);
435
436    /// Subscribe to the specified netidx path
437    ///
438    /// When the subscription updates you are expected to deliver
439    /// Netidx events to the expression specified by ref_by.
440    fn subscribe(&mut self, flags: UpdatesFlags, path: Path, ref_by: ExprId) -> Dval;
441
442    /// Called when a subscription is no longer needed
443    fn unsubscribe(&mut self, path: Path, dv: Dval, ref_by: ExprId);
444
445    /// List the netidx path, return Value::Null if the path did not
446    /// change. When the path did update you should send the output
447    /// back as a properly formatted struct with two fields, rows and
448    /// columns both containing string arrays.
449    fn list(&mut self, id: BindId, path: Path);
450
451    /// List the table at path, return Value::Null if the path did not
452    /// change
453    fn list_table(&mut self, id: BindId, path: Path);
454
455    /// list or table will no longer be called on this BindId, and
456    /// related resources can be cleaned up.
457    fn stop_list(&mut self, id: BindId);
458
459    /// Publish the specified value, returning it's Id, which must be
460    /// used to update the value and unpublish it. If the path is
461    /// already published, return an error.
462    fn publish(&mut self, path: Path, value: Value, ref_by: ExprId) -> Result<Val>;
463
464    /// Update the specified value
465    fn update(&mut self, id: &Val, value: Value);
466
467    /// Stop publishing the specified id
468    fn unpublish(&mut self, id: Val, ref_by: ExprId);
469
470    /// This will be called by the compiler whenever a bound variable
471    /// is referenced. The ref_by is the toplevel expression that
472    /// contains the variable reference. When a variable event
473    /// happens, you should update all the toplevel expressions that
474    /// ref that variable.
475    ///
476    /// ref_var will also be called when a bound lambda expression is
477    /// referenced, in that case the ref_by id will be the toplevel
478    /// expression containing the call site.
479    fn ref_var(&mut self, id: BindId, ref_by: ExprId);
480    fn unref_var(&mut self, id: BindId, ref_by: ExprId);
481
482    /// Called by the ExecCtx when set_var is called on it.
483    ///
484    /// All expressions that ref the id should be updated when this happens. The
485    /// runtime must deliver all set_vars in a single event except that set_vars
486    /// for the same variable in the same cycle must be queued and deferred to
487    /// the next cycle.
488    ///
489    /// The runtime MUST NOT change event while a cycle is in
490    /// progress. set_var must be queued until the cycle ends and then
491    /// presented as a new batch.
492    fn set_var(&mut self, id: BindId, value: Value);
493
494    /// Notify the RT that a top level variable has been set internally
495    ///
496    /// This is called when the compiler has determined that it's safe to set a
497    /// variable without waiting a cycle. When the updated variable is a
498    /// toplevel node this method is called to notify the runtime that needs to
499    /// update any dependent toplevel nodes.
500    fn notify_set(&mut self, id: BindId);
501
502    /// This must return results from the same path in the call order.
503    ///
504    /// when the rpc returns you are expected to deliver a Variable
505    /// event with the specified id to the expression specified by
506    /// ref_by.
507    fn call_rpc(&mut self, name: Path, args: Vec<(ArcStr, Value)>, id: BindId);
508
509    /// Publish an rpc at the specified path with the specified
510    /// procedure level doc and arg spec.
511    ///
512    /// When the RPC is called the rpc table in event will be
513    /// populated under the specified bind id.
514    ///
515    /// If the procedure is already published an error will be
516    /// returned
517    fn publish_rpc(
518        &mut self,
519        name: Path,
520        doc: Value,
521        spec: Vec<ArgSpec>,
522        id: BindId,
523    ) -> Result<()>;
524
525    /// unpublish the rpc identified by the bind id.
526    fn unpublish_rpc(&mut self, name: Path);
527
528    /// arrange to have a Timer event delivered after timeout. When
529    /// the timer expires you are expected to deliver a Variable event
530    /// for the id, containing the current time.
531    fn set_timer(&mut self, id: BindId, timeout: Duration);
532
533    /// Spawn a task
534    ///
535    /// When the task completes it's output must be delivered as a
536    /// custom event using the returned `BindId`
537    ///
538    /// Calling `abort` must guarantee that if it is called before the
539    /// task completes then no update will be delivered.
540    fn spawn<F: Future<Output = (BindId, Box<dyn CustomBuiltinType>)> + Send + 'static>(
541        &mut self,
542        f: F,
543    ) -> Self::AbortHandle;
544
545    /// Spawn a task
546    ///
547    /// When the task completes it's output must be delivered as a
548    /// variable event using the returned `BindId`
549    ///
550    /// Calling `abort` must guarantee that if it is called before the
551    /// task completes then no update will be delivered.
552    fn spawn_var<F: Future<Output = (BindId, Value)> + Send + 'static>(
553        &mut self,
554        f: F,
555    ) -> Self::AbortHandle;
556
557    /// Ask the runtime to watch a channel
558    ///
559    /// When event batches arrive via the channel the runtime must
560    /// deliver the events as custom updates.
561    fn watch(
562        &mut self,
563        s: mpsc::Receiver<GPooled<Vec<(BindId, Box<dyn CustomBuiltinType>)>>>,
564    );
565
566    /// Ask the runtime to watch a channel
567    ///
568    /// When event batches arrive via the channel the runtime must
569    /// deliver the events variable updates.
570    fn watch_var(&mut self, s: mpsc::Receiver<GPooled<Vec<(BindId, Value)>>>);
571}
572
573#[derive(Default)]
574pub struct LibState(FxHashMap<TypeId, Box<dyn Any + Send + Sync>>);
575
576impl LibState {
577    /// Look up and return the context global library state of type
578    /// `T`.
579    ///
580    /// If none is registered in this context for `T` then create one
581    /// using `T::default`
582    pub fn get_or_default<T>(&mut self) -> &mut T
583    where
584        T: Default + Any + Send + Sync,
585    {
586        self.0
587            .entry(TypeId::of::<T>())
588            .or_insert_with(|| Box::new(T::default()) as Box<dyn Any + Send + Sync>)
589            .downcast_mut::<T>()
590            .unwrap()
591    }
592
593    /// Look up and return the context global library state of type
594    /// `T`.
595    ///
596    /// If none is registered in this context for `T` then create one
597    /// using the provided function.
598    pub fn get_or_else<T, F>(&mut self, f: F) -> &mut T
599    where
600        T: Any + Send + Sync,
601        F: FnOnce() -> T,
602    {
603        self.0
604            .entry(TypeId::of::<T>())
605            .or_insert_with(|| Box::new(f()) as Box<dyn Any + Send + Sync>)
606            .downcast_mut::<T>()
607            .unwrap()
608    }
609
610    pub fn entry<'a, T>(
611        &'a mut self,
612    ) -> hash_map::Entry<'a, TypeId, Box<dyn Any + Send + Sync>>
613    where
614        T: Any + Send + Sync,
615    {
616        self.0.entry(TypeId::of::<T>())
617    }
618
619    /// return true if `T` is present
620    pub fn contains<T>(&self) -> bool
621    where
622        T: Any + Send + Sync,
623    {
624        self.0.contains_key(&TypeId::of::<T>())
625    }
626
627    /// Look up and return a reference to the context global library
628    /// state of type `T`.
629    ///
630    /// If none is registered in this context for `T` return `None`
631    pub fn get<T>(&mut self) -> Option<&T>
632    where
633        T: Any + Send + Sync,
634    {
635        self.0.get(&TypeId::of::<T>()).map(|t| t.downcast_ref::<T>().unwrap())
636    }
637
638    /// Look up and return a mutable reference to the context global
639    /// library state of type `T`.
640    ///
641    /// If none is registered return `None`
642    pub fn get_mut<T>(&mut self) -> Option<&mut T>
643    where
644        T: Any + Send + Sync,
645    {
646        self.0.get_mut(&TypeId::of::<T>()).map(|t| t.downcast_mut::<T>().unwrap())
647    }
648
649    /// Set the context global library state of type `T`
650    ///
651    /// Any existing state will be returned
652    pub fn set<T>(&mut self, t: T) -> Option<Box<T>>
653    where
654        T: Any + Send + Sync,
655    {
656        self.0
657            .insert(TypeId::of::<T>(), Box::new(t) as Box<dyn Any + Send + Sync>)
658            .map(|t| t.downcast::<T>().unwrap())
659    }
660
661    /// Remove and refurn the context global state library state of type `T`
662    pub fn remove<T>(&mut self) -> Option<Box<T>>
663    where
664        T: Any + Send + Sync,
665    {
666        self.0.remove(&TypeId::of::<T>()).map(|t| t.downcast::<T>().unwrap())
667    }
668}
669
670/// A registry of abstract type UUIDs used by graphix and graphix libraries,
671/// along with a string tag describing what the type is. We must do this because
672/// you can't register different type ids with the same uuid in netidx's
673/// abstract type system, and because abstract types often need to be
674/// parameterized by the Rt and UserEvent they will have different a different
675/// type id for each monomorphization, and thus they must have a different uuid.
676///
677/// The tag is necessary because non parameterized functions often end up with
678/// an abstract netidx type and want to know generally what it is, for example
679/// printing functions.
680#[derive(Default)]
681pub struct AbstractTypeRegistry {
682    by_tid: FxHashMap<TypeId, Uuid>,
683    by_uuid: FxHashMap<Uuid, &'static str>,
684}
685
686impl AbstractTypeRegistry {
687    fn with<V, F: FnMut(&mut AbstractTypeRegistry) -> V>(mut f: F) -> V {
688        static REG: LazyLock<Mutex<AbstractTypeRegistry>> =
689            LazyLock::new(|| Mutex::new(AbstractTypeRegistry::default()));
690        let mut g = REG.lock();
691        f(&mut *g)
692    }
693
694    /// Get the UUID of abstract type T
695    pub(crate) fn uuid<T: Any>(tag: &'static str) -> Uuid {
696        Self::with(|rg| {
697            *rg.by_tid.entry(TypeId::of::<T>()).or_insert_with(|| {
698                let id = Uuid::new_v4();
699                rg.by_uuid.insert(id, tag);
700                id
701            })
702        })
703    }
704
705    /// return the tag of this abstract type, or None if it isn't registered
706    pub fn tag(a: &Abstract) -> Option<&'static str> {
707        Self::with(|rg| rg.by_uuid.get(&a.id()).map(|r| *r))
708    }
709
710    /// return true if the abstract type has tag
711    pub fn is_a(a: &Abstract, tag: &str) -> bool {
712        match Self::tag(a) {
713            Some(t) => t == tag,
714            None => false,
715        }
716    }
717}
718
719pub struct ExecCtx<R: Rt, E: UserEvent> {
720    // used to wrap lambdas into an abstract netidx value type
721    lambdawrap: AbstractWrapper<LambdaDef<R, E>>,
722    // all registered built-in functions
723    builtins: FxHashMap<&'static str, (FnType, BuiltInInitFn<R, E>)>,
724    // whether calling built-in functions is allowed in this context, used for
725    // sandboxing
726    builtins_allowed: bool,
727    // hash consed variant tags
728    tags: FxHashSet<ArcStr>,
729    /// context global library state for built-in functions
730    pub libstate: LibState,
731    /// the language environment, typdefs, binds, lambdas, etc
732    pub env: Env,
733    /// the last value of every bound variable
734    pub cached: FxHashMap<BindId, Value>,
735    /// the runtime
736    pub rt: R,
737}
738
739impl<R: Rt, E: UserEvent> ExecCtx<R, E> {
740    pub fn clear(&mut self) {
741        self.env.clear();
742        self.rt.clear();
743    }
744
745    /// Build a new execution context.
746    ///
747    /// This is a very low level interface that you can use to build a
748    /// custom runtime with deep integration to your code. It is very
749    /// difficult to use, and if you don't implement everything
750    /// correctly the semantics of the language can be wrong.
751    ///
752    /// Most likely you want to use the `rt` module instead.
753    pub fn new(user: R) -> Result<Self> {
754        let id = AbstractTypeRegistry::uuid::<LambdaDef<R, E>>("lambda");
755        Ok(Self {
756            lambdawrap: Abstract::register(id)?,
757            env: Env::default(),
758            builtins: FxHashMap::default(),
759            builtins_allowed: true,
760            libstate: LibState::default(),
761            tags: FxHashSet::default(),
762            cached: HashMap::default(),
763            rt: user,
764        })
765    }
766
767    pub fn register_builtin<T: BuiltIn<R, E>>(&mut self) -> Result<()> {
768        match self.builtins.entry(T::NAME) {
769            Entry::Vacant(e) => {
770                e.insert((T::TYP.clone(), T::init));
771            }
772            Entry::Occupied(_) => bail!("builtin {} is already registered", T::NAME),
773        }
774        Ok(())
775    }
776
777    /// Built in functions should call this when variables are set
778    /// unless they are sure the variable does not need to be
779    /// cached. This will also call the user ctx set_var.
780    pub fn set_var(&mut self, id: BindId, v: Value) {
781        self.cached.insert(id, v.clone());
782        self.rt.set_var(id, v)
783    }
784
785    fn tag(&mut self, s: &ArcStr) -> ArcStr {
786        match self.tags.get(s) {
787            Some(s) => s.clone(),
788            None => {
789                self.tags.insert(s.clone());
790                s.clone()
791            }
792        }
793    }
794
795    /// Restore the lexical environment to the snapshot `env` for the duration
796    /// of `f` restoring it to it's original value afterwords. `by_id` and
797    /// `lambdas` defined by the closure will be retained.
798    pub fn with_restored<T, F: FnOnce(&mut Self) -> T>(&mut self, env: Env, f: F) -> T {
799        let snap = self.env.restore_lexical_env(env);
800        let orig = mem::replace(&mut self.env, snap);
801        let r = f(self);
802        self.env = self.env.restore_lexical_env(orig);
803        r
804    }
805
806    /// Restore the lexical environment to the snapshot `env` for the duration
807    /// of `f` restoring it to it's original value afterwords. `by_id` and
808    /// `lambdas` defined by the closure will be retained. `env` will be mutated
809    /// instead of requiring a clone, this allows maintaining continuity in two
810    /// different envs across multiple invocations
811    pub fn with_restored_mut<T, F: FnOnce(&mut Self) -> T>(
812        &mut self,
813        env: &mut Env,
814        f: F,
815    ) -> T {
816        let snap = self.env.restore_lexical_env_mut(env);
817        let orig = mem::replace(&mut self.env, snap);
818        let r = f(self);
819        *env = self.env.clone();
820        self.env = self.env.restore_lexical_env(orig);
821        r
822    }
823}
824
825#[derive(Debug, Clone)]
826pub struct Scope {
827    pub lexical: ModPath,
828    pub dynamic: ModPath,
829}
830
831impl Scope {
832    pub fn append<S: AsRef<str> + ?Sized>(&self, s: &S) -> Self {
833        Self {
834            lexical: ModPath(self.lexical.append(s)),
835            dynamic: ModPath(self.dynamic.append(s)),
836        }
837    }
838
839    pub fn root() -> Self {
840        Self { lexical: ModPath::root(), dynamic: ModPath::root() }
841    }
842}
843
844/// compile the expression into a node graph in the specified context
845/// and scope, return the root node or an error if compilation failed.
846pub fn compile<R: Rt, E: UserEvent>(
847    ctx: &mut ExecCtx<R, E>,
848    flags: BitFlags<CFlag>,
849    scope: &Scope,
850    spec: Expr,
851) -> Result<Node<R, E>> {
852    let top_id = spec.id;
853    let env = ctx.env.clone();
854    let st = Instant::now();
855    let mut node = match compiler::compile(ctx, flags, spec, scope, top_id) {
856        Ok(n) => n,
857        Err(e) => {
858            ctx.env = env;
859            return Err(e);
860        }
861    };
862    info!("compile time {:?}", st.elapsed());
863    let st = Instant::now();
864    if let Err(e) = node.typecheck(ctx) {
865        ctx.env = env;
866        return Err(e);
867    }
868    info!("typecheck time {:?}", st.elapsed());
869    Ok(node)
870}