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 ahash::{AHashMap, AHashSet};
24use anyhow::{bail, Result};
25use arcstr::ArcStr;
26use enumflags2::{bitflags, BitFlags};
27use expr::Expr;
28use futures::channel::mpsc;
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 nohash::{IntMap, IntSet};
39use parking_lot::{Mutex, RwLock};
40use poolshark::{
41    global::{GPooled, Pool},
42    local::LPooled,
43};
44use std::{
45    any::{Any, TypeId},
46    cell::Cell,
47    collections::hash_map::{self, Entry},
48    fmt::Debug,
49    mem,
50    sync::{
51        self,
52        atomic::{AtomicBool, Ordering},
53        LazyLock,
54    },
55    time::Duration,
56};
57use tokio::{task, time::Instant};
58use triomphe::Arc;
59use uuid::Uuid;
60
61#[derive(Debug, Clone, Copy)]
62#[bitflags]
63#[repr(u64)]
64pub enum CFlag {
65    WarnUnhandled,
66    WarnUnused,
67    WarningsAreErrors,
68}
69
70#[allow(dead_code)]
71static TRACE: AtomicBool = AtomicBool::new(false);
72
73#[allow(dead_code)]
74pub fn set_trace(b: bool) {
75    TRACE.store(b, Ordering::Relaxed)
76}
77
78#[allow(dead_code)]
79pub fn with_trace<F: FnOnce() -> Result<R>, R>(
80    enable: bool,
81    spec: &Expr,
82    f: F,
83) -> Result<R> {
84    let prev = trace();
85    set_trace(enable);
86    if !prev && enable {
87        eprintln!("trace enabled at {}, spec: {}", spec.pos, spec);
88    } else if prev && !enable {
89        eprintln!("trace disabled at {}, spec: {}", spec.pos, spec);
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 prev && !enable {
99        eprintln!("trace reenabled")
100    }
101    set_trace(prev);
102    r
103}
104
105#[allow(dead_code)]
106pub fn 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: IntMap<BindId, Value>,
248    pub netidx: IntMap<SubId, subscriber::Event>,
249    pub writes: IntMap<Id, WriteRequest>,
250    pub rpc_calls: IntMap<BindId, RpcCall>,
251    pub custom: IntMap<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: IntMap::default(),
260            netidx: IntMap::default(),
261            writes: IntMap::default(),
262            rpc_calls: IntMap::default(),
263            custom: IntMap::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<IntSet<BindId>>,
283    bound: LPooled<IntSet<BindId>>,
284}
285
286pub use combine::stream::position::SourcePosition;
287
288/// A textual occurrence of a name at a specific source position that
289/// the compiler resolved to a particular `BindId`. Populated as a side
290/// effect of compilation so IDE tooling can answer
291/// `textDocument/references` and `textDocument/definition` without
292/// re-implementing name resolution.
293///
294/// `def_pos` and `def_ori` mirror the bind's declaration site at
295/// resolution time. They're captured here because some bindings
296/// (notably lambda parameters) are unbound from the env when the
297/// callsite that created them is dropped — but their declaration
298/// site is still meaningful to the user.
299#[derive(Debug, Clone)]
300pub struct ReferenceSite {
301    pub pos: SourcePosition,
302    pub ori: Arc<expr::Origin>,
303    pub name: expr::ModPath,
304    pub bind_id: BindId,
305    pub def_pos: SourcePosition,
306    pub def_ori: Arc<expr::Origin>,
307}
308
309/// A textual occurrence of a module reference (either `use foo;` or
310/// `mod foo;`). For the `mod foo;` case `def_ori` points at the file
311/// the module's body was loaded from — that's the natural target for
312/// go-to-definition on a module name.
313#[derive(Debug, Clone)]
314pub struct ModuleRefSite {
315    pub pos: SourcePosition,
316    pub ori: Arc<expr::Origin>,
317    /// Module name as the user wrote it (might be relative).
318    pub name: expr::ModPath,
319    /// Absolute module path the compiler resolved this reference to.
320    pub canonical: expr::ModPath,
321    /// Origin of the module's body (the file it was loaded from)
322    /// when this site is itself a declaration that pulled the
323    /// module in. `None` for plain `use` sites.
324    pub def_ori: Option<Arc<expr::Origin>>,
325}
326
327/// One entry in the per-compile scope map: the compiler descended
328/// into an `Expr` at this `(pos, ori)` while in this `scope`. IDE
329/// tooling answers `cursor → scope` by finding the entry with the
330/// greatest `pos` ≤ the cursor in the same file.
331#[derive(Debug, Clone)]
332pub struct ScopeMapEntry {
333    pub pos: SourcePosition,
334    pub ori: Arc<expr::Origin>,
335    pub scope: Scope,
336}
337
338/// A textual occurrence of a type reference (e.g. `Foo` in `let x: Foo`).
339/// Captured by the compiler when a `Type::Ref` carrying parse-time
340/// position info gets dereferenced. `def_pos`/`def_ori` point at the
341/// `type Foo = …` declaration site so go-to-def on a type name lands
342/// on the typedef.
343#[derive(Debug, Clone)]
344pub struct TypeRefSite {
345    pub pos: SourcePosition,
346    pub ori: Arc<expr::Origin>,
347    /// The name as written in source (e.g. `Result`, `array::Foo`).
348    pub name: expr::ModPath,
349    /// Canonical scope of the typedef the reference resolved to.
350    pub canonical_scope: expr::ModPath,
351    pub def_pos: SourcePosition,
352    pub def_ori: Arc<expr::Origin>,
353}
354
355/// Maps a `val foo: T` declaration in a `.gxi` interface to its
356/// `let foo = …` implementation site in the paired `.gx`. Populated by
357/// `check_sig` whenever it matches a sig proxy bind to its impl bind.
358/// Used by IDE tooling to (a) goto-def from a sig val site to the impl,
359/// and (b) union find-references results across both `BindId`s.
360/// Only populated when `env.lsp_mode` is set.
361#[derive(Debug, Clone)]
362pub struct SigImplLink {
363    pub scope: expr::ModPath,
364    pub name: compact_str::CompactString,
365    pub sig_id: BindId,
366    pub impl_id: BindId,
367}
368
369/// Per-module snapshot of the *internal* env (the impl's view, where
370/// implementation bindings shadow sig proxies). The CheckResult's
371/// top-level `env` is the *external* view across the project; this
372/// per-module entry lets IDE queries on names inside a module reach
373/// the impl bind metadata. Only populated when `env.lsp_mode` is set.
374#[derive(Debug, Clone)]
375pub struct ModuleInternalView {
376    pub scope: expr::ModPath,
377    pub env: env::Env,
378}
379
380/// Pools backing the IDE side-channel collections. `GPooled` so the
381/// buffers can return to the same pool after crossing the
382/// runtime-task → LSP-thread boundary as part of `CheckResult`. Sized
383/// generously since the LSP recompiles on every keystroke and these
384/// can grow into the tens of thousands of entries on large modules.
385pub static REFERENCE_SITE_POOL: LazyLock<Pool<Vec<ReferenceSite>>> =
386    LazyLock::new(|| Pool::new(64, 65536));
387pub static MODULE_REF_SITE_POOL: LazyLock<Pool<Vec<ModuleRefSite>>> =
388    LazyLock::new(|| Pool::new(64, 65536));
389pub static SCOPE_MAP_ENTRY_POOL: LazyLock<Pool<Vec<ScopeMapEntry>>> =
390    LazyLock::new(|| Pool::new(64, 65536));
391// `TYPE_REF_SITE_POOL`, `SIG_LINK_POOL`, and `MODULE_INTERNAL_VIEW_POOL`
392// back the per-check `Lsp` sinks; they live in `env` next to the
393// `Lsp` struct that consumes them.
394
395impl Refs {
396    pub fn clear(&mut self) {
397        self.refed.clear();
398        self.bound.clear();
399    }
400
401    pub fn with_external_refs(&self, mut f: impl FnMut(BindId)) {
402        for id in &*self.refed {
403            if !self.bound.contains(id) {
404                f(*id);
405            }
406        }
407    }
408}
409
410pub type Node<R, E> = Box<dyn Update<R, E>>;
411
412/// Phase indicator for Apply::typecheck
413#[derive(Debug)]
414pub enum TypecheckPhase<'a> {
415    /// During Lambda::typecheck — faux args, building FnType
416    Lambda,
417    /// During deferred check or bind — resolved FnType available
418    CallSite(&'a FnType),
419}
420
421pub type InitFn<R, E> = sync::Arc<
422    dyn for<'a, 'b, 'c, 'd> Fn(
423            &'a Scope,
424            &'b mut ExecCtx<R, E>,
425            &'c mut [Node<R, E>],
426            Option<&'d FnType>,
427            ExprId,
428        ) -> Result<Box<dyn Apply<R, E>>>
429        + Send
430        + Sync
431        + 'static,
432>;
433
434/// Apply is a kind of node that represents a function application. It
435/// does not hold ownership of it's arguments, instead those are held
436/// by a CallSite node. This allows us to change the function called
437/// at runtime without recompiling the arguments.
438pub trait Apply<R: Rt, E: UserEvent>: Debug + Send + Sync + Any {
439    fn update(
440        &mut self,
441        ctx: &mut ExecCtx<R, E>,
442        from: &mut [Node<R, E>],
443        event: &mut Event<E>,
444    ) -> Option<Value>;
445
446    /// delete any internally generated nodes, only needed for
447    /// builtins that dynamically generate code at runtime
448    fn delete(&mut self, _ctx: &mut ExecCtx<R, E>) {
449        ()
450    }
451
452    /// apply custom typechecking. Phase indicates context:
453    /// - Lambda: during lambda body checking (faux args). Return NeedsCallSite
454    ///   to opt in to deferred call-site type checking.
455    /// - CallSite: during deferred check or bind (resolved FnType available)
456    fn typecheck(
457        &mut self,
458        _ctx: &mut ExecCtx<R, E>,
459        _from: &mut [Node<R, E>],
460        _phase: TypecheckPhase<'_>,
461    ) -> Result<()> {
462        Ok(())
463    }
464
465    /// return the lambdas type, builtins do not need to implement
466    /// this, it is implemented by the BuiltIn wrapper
467    fn typ(&self) -> Arc<FnType> {
468        static EMPTY: LazyLock<Arc<FnType>> = LazyLock::new(|| {
469            Arc::new(FnType {
470                args: Arc::from_iter([]),
471                constraints: Arc::new(RwLock::new(LPooled::take())),
472                rtype: Type::Bottom,
473                throws: Type::Bottom,
474                vargs: None,
475                explicit_throws: false,
476                ..Default::default()
477            })
478        });
479        Arc::clone(&*EMPTY)
480    }
481
482    /// Populate the Refs structure with all the ids bound and refed by this
483    /// node. It is only necessary for builtins to implement this if they create
484    /// nodes, such as call sites.
485    fn refs<'a>(&self, _refs: &mut Refs) {}
486
487    /// put the node to sleep, used in conditions like select for branches that
488    /// are not selected. Any cached values should be cleared on sleep.
489    fn sleep(&mut self, _ctx: &mut ExecCtx<R, E>);
490}
491
492/// Update represents a regular graph node, as opposed to a function
493/// application represented by Apply. Regular graph nodes are used for
494/// every built in node except for builtin functions.
495pub trait Update<R: Rt, E: UserEvent>: Debug + Send + Sync + Any + 'static {
496    /// update the node with the specified event and return any output
497    /// it might generate
498    fn update(&mut self, ctx: &mut ExecCtx<R, E>, event: &mut Event<E>) -> Option<Value>;
499
500    /// delete the node and it's children from the specified context
501    fn delete(&mut self, ctx: &mut ExecCtx<R, E>);
502
503    /// type check the node and it's children
504    fn typecheck(&mut self, ctx: &mut ExecCtx<R, E>) -> Result<()>;
505
506    /// return the node type
507    fn typ(&self) -> &Type;
508
509    /// Populate the Refs structure with all the bind ids either refed or bound
510    /// by the node and it's children
511    fn refs(&self, refs: &mut Refs);
512
513    /// return the original expression used to compile this node
514    fn spec(&self) -> &Expr;
515
516    /// put the node to sleep, called on unselected branches
517    fn sleep(&mut self, ctx: &mut ExecCtx<R, E>);
518}
519
520pub type BuiltInInitFn<R, E> = for<'a, 'b, 'c, 'd> fn(
521    &'a mut ExecCtx<R, E>,
522    &'a FnType,
523    Option<&'d FnType>,
524    &'b Scope,
525    &'c [Node<R, E>],
526    ExprId,
527) -> Result<Box<dyn Apply<R, E>>>;
528
529/// Trait implemented by graphix built-in functions implemented in rust. This
530/// trait isn't meant to be implemented manually, use derive(BuiltIn) from the
531/// graphix-derive crate
532pub trait BuiltIn<R: Rt, E: UserEvent> {
533    const NAME: &str;
534    const NEEDS_CALLSITE: bool;
535
536    fn init<'a, 'b, 'c, 'd>(
537        ctx: &'a mut ExecCtx<R, E>,
538        typ: &'a FnType,
539        resolved_type: Option<&'d FnType>,
540        scope: &'b Scope,
541        from: &'c [Node<R, E>],
542        top_id: ExprId,
543    ) -> Result<Box<dyn Apply<R, E>>>;
544}
545
546pub trait Abortable {
547    fn abort(&self);
548}
549
550impl Abortable for task::AbortHandle {
551    fn abort(&self) {
552        task::AbortHandle::abort(self)
553    }
554}
555
556pub trait Rt: Debug + Any {
557    type AbortHandle: Abortable;
558
559    fn clear(&mut self);
560
561    /// Subscribe to the specified netidx path
562    ///
563    /// When the subscription updates you are expected to deliver
564    /// Netidx events to the expression specified by ref_by.
565    fn subscribe(&mut self, flags: UpdatesFlags, path: Path, ref_by: ExprId) -> Dval;
566
567    /// Called when a subscription is no longer needed
568    fn unsubscribe(&mut self, path: Path, dv: Dval, ref_by: ExprId);
569
570    /// List the netidx path, return Value::Null if the path did not
571    /// change. When the path did update you should send the output
572    /// back as a properly formatted struct with two fields, rows and
573    /// columns both containing string arrays.
574    fn list(&mut self, id: BindId, path: Path);
575
576    /// List the table at path, return Value::Null if the path did not
577    /// change
578    fn list_table(&mut self, id: BindId, path: Path);
579
580    /// list or table will no longer be called on this BindId, and
581    /// related resources can be cleaned up.
582    fn stop_list(&mut self, id: BindId);
583
584    /// Publish the specified value, returning it's Id, which must be
585    /// used to update the value and unpublish it. If the path is
586    /// already published, return an error.
587    fn publish(&mut self, path: Path, value: Value, ref_by: ExprId) -> Result<Val>;
588
589    /// Update the specified value
590    fn update(&mut self, id: &Val, value: Value);
591
592    /// Stop publishing the specified id
593    fn unpublish(&mut self, id: Val, ref_by: ExprId);
594
595    /// This will be called by the compiler whenever a bound variable
596    /// is referenced. The ref_by is the toplevel expression that
597    /// contains the variable reference. When a variable event
598    /// happens, you should update all the toplevel expressions that
599    /// ref that variable.
600    ///
601    /// ref_var will also be called when a bound lambda expression is
602    /// referenced, in that case the ref_by id will be the toplevel
603    /// expression containing the call site.
604    fn ref_var(&mut self, id: BindId, ref_by: ExprId);
605    fn unref_var(&mut self, id: BindId, ref_by: ExprId);
606
607    /// Called by the ExecCtx when set_var is called on it.
608    ///
609    /// All expressions that ref the id should be updated when this happens. The
610    /// runtime must deliver all set_vars in a single event except that set_vars
611    /// for the same variable in the same cycle must be queued and deferred to
612    /// the next cycle.
613    ///
614    /// The runtime MUST NOT change event while a cycle is in
615    /// progress. set_var must be queued until the cycle ends and then
616    /// presented as a new batch.
617    fn set_var(&mut self, id: BindId, value: Value);
618
619    /// Notify the RT that a top level variable has been set internally
620    ///
621    /// This is called when the compiler has determined that it's safe to set a
622    /// variable without waiting a cycle. When the updated variable is a
623    /// toplevel node this method is called to notify the runtime that needs to
624    /// update any dependent toplevel nodes.
625    fn notify_set(&mut self, id: BindId);
626
627    /// This must return results from the same path in the call order.
628    ///
629    /// when the rpc returns you are expected to deliver a Variable
630    /// event with the specified id to the expression specified by
631    /// ref_by.
632    fn call_rpc(&mut self, name: Path, args: Vec<(ArcStr, Value)>, id: BindId);
633
634    /// Publish an rpc at the specified path with the specified
635    /// procedure level doc and arg spec.
636    ///
637    /// When the RPC is called the rpc table in event will be
638    /// populated under the specified bind id.
639    ///
640    /// If the procedure is already published an error will be
641    /// returned
642    fn publish_rpc(
643        &mut self,
644        name: Path,
645        doc: Value,
646        spec: Vec<ArgSpec>,
647        id: BindId,
648    ) -> Result<()>;
649
650    /// unpublish the rpc identified by the bind id.
651    fn unpublish_rpc(&mut self, name: Path);
652
653    /// arrange to have a Timer event delivered after timeout. When
654    /// the timer expires you are expected to deliver a Variable event
655    /// for the id, containing the current time.
656    fn set_timer(&mut self, id: BindId, timeout: Duration);
657
658    /// Spawn a task
659    ///
660    /// When the task completes it's output must be delivered as a
661    /// custom event using the returned `BindId`
662    ///
663    /// Calling `abort` must guarantee that if it is called before the
664    /// task completes then no update will be delivered.
665    fn spawn<F: Future<Output = (BindId, Box<dyn CustomBuiltinType>)> + Send + 'static>(
666        &mut self,
667        f: F,
668    ) -> Self::AbortHandle;
669
670    /// Spawn a task
671    ///
672    /// When the task completes it's output must be delivered as a
673    /// variable event using the returned `BindId`
674    ///
675    /// Calling `abort` must guarantee that if it is called before the
676    /// task completes then no update will be delivered.
677    fn spawn_var<F: Future<Output = (BindId, Value)> + Send + 'static>(
678        &mut self,
679        f: F,
680    ) -> Self::AbortHandle;
681
682    /// Ask the runtime to watch a channel
683    ///
684    /// When event batches arrive via the channel the runtime must
685    /// deliver the events as custom updates.
686    fn watch(
687        &mut self,
688        s: mpsc::Receiver<GPooled<Vec<(BindId, Box<dyn CustomBuiltinType>)>>>,
689    );
690
691    /// Ask the runtime to watch a channel
692    ///
693    /// When event batches arrive via the channel the runtime must
694    /// deliver the events variable updates.
695    fn watch_var(&mut self, s: mpsc::Receiver<GPooled<Vec<(BindId, Value)>>>);
696}
697
698#[derive(Default)]
699pub struct LibState(AHashMap<TypeId, Box<dyn Any + Send + Sync>>);
700
701impl LibState {
702    /// Look up and return the context global library state of type
703    /// `T`.
704    ///
705    /// If none is registered in this context for `T` then create one
706    /// using `T::default`
707    pub fn get_or_default<T>(&mut self) -> &mut T
708    where
709        T: Default + Any + Send + Sync,
710    {
711        self.0
712            .entry(TypeId::of::<T>())
713            .or_insert_with(|| Box::new(T::default()) as Box<dyn Any + Send + Sync>)
714            .downcast_mut::<T>()
715            .unwrap()
716    }
717
718    /// Look up and return the context global library state of type
719    /// `T`.
720    ///
721    /// If none is registered in this context for `T` then create one
722    /// using the provided function.
723    pub fn get_or_else<T, F>(&mut self, f: F) -> &mut T
724    where
725        T: Any + Send + Sync,
726        F: FnOnce() -> T,
727    {
728        self.0
729            .entry(TypeId::of::<T>())
730            .or_insert_with(|| Box::new(f()) as Box<dyn Any + Send + Sync>)
731            .downcast_mut::<T>()
732            .unwrap()
733    }
734
735    pub fn entry<'a, T>(
736        &'a mut self,
737    ) -> hash_map::Entry<'a, TypeId, Box<dyn Any + Send + Sync>>
738    where
739        T: Any + Send + Sync,
740    {
741        self.0.entry(TypeId::of::<T>())
742    }
743
744    /// return true if `T` is present
745    pub fn contains<T>(&self) -> bool
746    where
747        T: Any + Send + Sync,
748    {
749        self.0.contains_key(&TypeId::of::<T>())
750    }
751
752    /// Look up and return a reference to the context global library
753    /// state of type `T`.
754    ///
755    /// If none is registered in this context for `T` return `None`
756    pub fn get<T>(&mut self) -> Option<&T>
757    where
758        T: Any + Send + Sync,
759    {
760        self.0.get(&TypeId::of::<T>()).map(|t| t.downcast_ref::<T>().unwrap())
761    }
762
763    /// Look up and return a mutable reference to the context global
764    /// library state of type `T`.
765    ///
766    /// If none is registered return `None`
767    pub fn get_mut<T>(&mut self) -> Option<&mut T>
768    where
769        T: Any + Send + Sync,
770    {
771        self.0.get_mut(&TypeId::of::<T>()).map(|t| t.downcast_mut::<T>().unwrap())
772    }
773
774    /// Set the context global library state of type `T`
775    ///
776    /// Any existing state will be returned
777    pub fn set<T>(&mut self, t: T) -> Option<Box<T>>
778    where
779        T: Any + Send + Sync,
780    {
781        self.0
782            .insert(TypeId::of::<T>(), Box::new(t) as Box<dyn Any + Send + Sync>)
783            .map(|t| t.downcast::<T>().unwrap())
784    }
785
786    /// Remove and refurn the context global state library state of type `T`
787    pub fn remove<T>(&mut self) -> Option<Box<T>>
788    where
789        T: Any + Send + Sync,
790    {
791        self.0.remove(&TypeId::of::<T>()).map(|t| t.downcast::<T>().unwrap())
792    }
793}
794
795/// A registry of abstract type UUIDs used by graphix and graphix libraries,
796/// along with a string tag describing what the type is. We must do this because
797/// you can't register different type ids with the same uuid in netidx's
798/// abstract type system, and because abstract types often need to be
799/// parameterized by the Rt and UserEvent they will have different a different
800/// type id for each monomorphization, and thus they must have a different uuid.
801///
802/// The tag is necessary because non parameterized functions often end up with
803/// an abstract netidx type and want to know generally what it is, for example
804/// printing functions.
805#[derive(Default)]
806pub struct AbstractTypeRegistry {
807    by_tid: AHashMap<TypeId, Uuid>,
808    by_uuid: AHashMap<Uuid, &'static str>,
809}
810
811impl AbstractTypeRegistry {
812    fn with<V, F: FnMut(&mut AbstractTypeRegistry) -> V>(mut f: F) -> V {
813        static REG: LazyLock<Mutex<AbstractTypeRegistry>> =
814            LazyLock::new(|| Mutex::new(AbstractTypeRegistry::default()));
815        let mut g = REG.lock();
816        f(&mut *g)
817    }
818
819    /// Get the UUID of abstract type T
820    pub(crate) fn uuid<T: Any>(tag: &'static str) -> Uuid {
821        Self::with(|rg| {
822            *rg.by_tid.entry(TypeId::of::<T>()).or_insert_with(|| {
823                let id = Uuid::new_v4();
824                rg.by_uuid.insert(id, tag);
825                id
826            })
827        })
828    }
829
830    /// return the tag of this abstract type, or None if it isn't registered
831    pub fn tag(a: &Abstract) -> Option<&'static str> {
832        Self::with(|rg| rg.by_uuid.get(&a.id()).map(|r| *r))
833    }
834
835    /// return true if the abstract type has tag
836    pub fn is_a(a: &Abstract, tag: &str) -> bool {
837        match Self::tag(a) {
838            Some(t) => t == tag,
839            None => false,
840        }
841    }
842}
843
844pub struct ExecCtx<R: Rt, E: UserEvent> {
845    // used to wrap lambdas into an abstract netidx value type
846    lambdawrap: AbstractWrapper<LambdaDef<R, E>>,
847    // all registered built-in functions
848    builtins: AHashMap<&'static str, (BuiltInInitFn<R, E>, bool)>,
849    // whether calling built-in functions is allowed in this context, used for
850    // sandboxing
851    builtins_allowed: bool,
852    // hash consed variant tags
853    tags: AHashSet<ArcStr>,
854    /// context global library state for built-in functions
855    pub libstate: LibState,
856    /// the language environment, typdefs, binds, lambdas, etc
857    pub env: Env,
858    /// the last value of every bound variable
859    pub cached: IntMap<BindId, Value>,
860    /// the runtime
861    pub rt: R,
862    /// LambdaDefs indexed by LambdaId, for deferred type checking
863    pub lambda_defs: IntMap<LambdaId, Value>,
864    /// deferred type check closures, evaluated after all primary type checking
865    pub deferred_checks:
866        Vec<Box<dyn FnOnce(&mut ExecCtx<R, E>) -> Result<()> + Send + Sync>>,
867    /// Reference sites accumulated during compilation. Each is a
868    /// textual occurrence of a name and the `BindId` it resolved to.
869    /// At compile boundaries, swap with a fresh `REFERENCE_SITE_POOL`
870    /// container via `mem::replace` (not `mem::take` — `Default`
871    /// routes to the unsized thread-local registry, not our named
872    /// pool). Only populated when `env.lsp_mode` is set.
873    pub references: GPooled<Vec<ReferenceSite>>,
874    /// Module reference sites — `use foo;` and `mod foo;` mentions.
875    /// Same scoping rules as `references`.
876    pub module_references: GPooled<Vec<ModuleRefSite>>,
877    /// Per-compile scope map. `compile()` pushes one entry every
878    /// time it's invoked, recording the scope it was called with.
879    /// IDE tooling reads this to answer `cursor → scope` queries.
880    pub scope_map: GPooled<Vec<ScopeMapEntry>>,
881}
882
883impl<R: Rt, E: UserEvent> ExecCtx<R, E> {
884    pub fn clear(&mut self) {
885        self.env.clear();
886        self.rt.clear();
887    }
888
889    /// Build a new execution context.
890    ///
891    /// This is a very low level interface that you can use to build a
892    /// custom runtime with deep integration to your code. It is very
893    /// difficult to use, and if you don't implement everything
894    /// correctly the semantics of the language can be wrong.
895    ///
896    /// Most likely you want to use the `rt` module instead.
897    pub fn new(user: R) -> Result<Self> {
898        let id = AbstractTypeRegistry::uuid::<LambdaDef<R, E>>("lambda");
899        Ok(Self {
900            lambdawrap: Abstract::register(id)?,
901            env: Env::default(),
902            builtins: AHashMap::default(),
903            builtins_allowed: true,
904            libstate: LibState::default(),
905            tags: AHashSet::default(),
906            cached: IntMap::default(),
907            rt: user,
908            lambda_defs: IntMap::default(),
909            deferred_checks: Vec::new(),
910            references: REFERENCE_SITE_POOL.take(),
911            module_references: MODULE_REF_SITE_POOL.take(),
912            scope_map: SCOPE_MAP_ENTRY_POOL.take(),
913        })
914    }
915
916    pub fn register_builtin<T: BuiltIn<R, E>>(&mut self) -> Result<()> {
917        match self.builtins.entry(T::NAME) {
918            Entry::Vacant(e) => {
919                e.insert((T::init, T::NEEDS_CALLSITE));
920            }
921            Entry::Occupied(_) => bail!("builtin {} is already registered", T::NAME),
922        }
923        Ok(())
924    }
925
926    /// Wrap a `LambdaDef` into a `Value` that can be returned from a builtin
927    /// as a first-class function value. The runtime handles the resulting
928    /// Value as a callable lambda — call sites resolve against the LambdaDef's
929    /// `init` to construct an Apply impl. Also registers the LambdaDef in
930    /// `lambda_defs` so deferred typechecking can find it.
931    pub fn wrap_lambda(&mut self, def: LambdaDef<R, E>) -> Value {
932        let id = def.id;
933        let v = self.lambdawrap.wrap(def);
934        self.lambda_defs.insert(id, v.clone());
935        v
936    }
937
938    /// Built in functions should call this when variables are set
939    /// unless they are sure the variable does not need to be
940    /// cached. This will also call the user ctx set_var.
941    pub fn set_var(&mut self, id: BindId, v: Value) {
942        self.cached.insert(id, v.clone());
943        self.rt.set_var(id, v)
944    }
945
946    fn tag(&mut self, s: &ArcStr) -> ArcStr {
947        match self.tags.get(s) {
948            Some(s) => s.clone(),
949            None => {
950                self.tags.insert(s.clone());
951                s.clone()
952            }
953        }
954    }
955
956    /// Restore the lexical environment to the snapshot `env` for the duration
957    /// of `f` restoring it to it's original value afterwords. `by_id` and
958    /// `lambdas` defined by the closure will be retained.
959    pub fn with_restored<T, F: FnOnce(&mut Self) -> T>(&mut self, env: Env, f: F) -> T {
960        let snap = self.env.restore_lexical_env(env);
961        let orig = mem::replace(&mut self.env, snap);
962        let r = f(self);
963        self.env = self.env.restore_lexical_env(orig);
964        r
965    }
966
967    /// Restore the lexical environment to the snapshot `env` for the duration
968    /// of `f` restoring it to it's original value afterwords. `by_id` and
969    /// `lambdas` defined by the closure will be retained. `env` will be mutated
970    /// instead of requiring a clone, this allows maintaining continuity in two
971    /// different envs across multiple invocations
972    pub fn with_restored_mut<T, F: FnOnce(&mut Self) -> T>(
973        &mut self,
974        env: &mut Env,
975        f: F,
976    ) -> T {
977        let snap = self.env.restore_lexical_env_mut(env);
978        let orig = mem::replace(&mut self.env, snap);
979        let r = f(self);
980        *env = self.env.clone();
981        self.env = self.env.restore_lexical_env(orig);
982        r
983    }
984}
985
986#[derive(Debug, Clone)]
987pub struct Scope {
988    pub lexical: ModPath,
989    pub dynamic: ModPath,
990}
991
992impl Scope {
993    pub fn append<S: AsRef<str> + ?Sized>(&self, s: &S) -> Self {
994        Self {
995            lexical: ModPath(self.lexical.append(s)),
996            dynamic: ModPath(self.dynamic.append(s)),
997        }
998    }
999
1000    pub fn root() -> Self {
1001        Self { lexical: ModPath::root(), dynamic: ModPath::root() }
1002    }
1003}
1004
1005/// compile the expression into a node graph in the specified context
1006/// and scope, return the root node or an error if compilation failed.
1007pub fn compile<R: Rt, E: UserEvent>(
1008    ctx: &mut ExecCtx<R, E>,
1009    flags: BitFlags<CFlag>,
1010    scope: &Scope,
1011    spec: Expr,
1012) -> Result<Node<R, E>> {
1013    let top_id = spec.id;
1014    let env = ctx.env.clone();
1015    let st = Instant::now();
1016    let mut node = match compiler::compile(ctx, flags, spec, scope, top_id) {
1017        Ok(n) => n,
1018        Err(e) => {
1019            ctx.env = env;
1020            return Err(e);
1021        }
1022    };
1023    info!("compile time {:?}", st.elapsed());
1024    let st = Instant::now();
1025    if let Err(e) = node.typecheck(ctx) {
1026        ctx.env = env;
1027        return Err(e);
1028    }
1029    // run deferred builtin type checks after all primary type checking completes
1030    while let Some(check) = ctx.deferred_checks.pop() {
1031        if let Err(e) = check(ctx) {
1032            ctx.env = env;
1033            return Err(e);
1034        }
1035    }
1036    info!("typecheck time {:?}", st.elapsed());
1037    Ok(node)
1038}