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