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