graphix_compiler/
env.rs

1use crate::{
2    expr::{Arg, ModPath, Sandbox},
3    typ::{FnType, TVar, Type},
4    BindId, InitFn, LambdaId, Rt, Scope, UserEvent,
5};
6use anyhow::{anyhow, bail, Result};
7use arcstr::ArcStr;
8use compact_str::CompactString;
9use fxhash::{FxHashMap, FxHashSet};
10use immutable_chunkmap::{map::MapS as Map, set::SetS as Set};
11use netidx::path::Path;
12use std::{cell::RefCell, fmt, iter, ops::Bound, sync::Weak};
13use triomphe::Arc;
14
15pub struct LambdaDef<R: Rt, E: UserEvent> {
16    pub id: LambdaId,
17    pub env: Env<R, E>,
18    pub scope: Scope,
19    pub argspec: Arc<[Arg]>,
20    pub typ: Arc<FnType>,
21    pub init: InitFn<R, E>,
22}
23
24impl<R: Rt, E: UserEvent> fmt::Debug for LambdaDef<R, E> {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        write!(f, "LambdaDef({:?})", self.id)
27    }
28}
29
30pub struct Bind {
31    pub id: BindId,
32    pub export: bool,
33    pub typ: Type,
34    pub doc: Option<ArcStr>,
35    pub scope: ModPath,
36    pub name: CompactString,
37}
38
39impl fmt::Debug for Bind {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        write!(f, "Bind {{ id: {:?}, export: {} }}", self.id, self.export,)
42    }
43}
44
45impl Clone for Bind {
46    fn clone(&self) -> Self {
47        Self {
48            id: self.id,
49            scope: self.scope.clone(),
50            name: self.name.clone(),
51            doc: self.doc.clone(),
52            export: self.export,
53            typ: self.typ.clone(),
54        }
55    }
56}
57
58#[derive(Debug, Clone)]
59pub struct TypeDef {
60    pub params: Arc<[(TVar, Option<Type>)]>,
61    pub typ: Type,
62}
63
64#[derive(Debug)]
65pub struct Env<R: Rt, E: UserEvent> {
66    pub by_id: Map<BindId, Bind>,
67    pub lambdas: Map<LambdaId, Weak<LambdaDef<R, E>>>,
68    pub byref_chain: Map<BindId, BindId>,
69    pub binds: Map<ModPath, Map<CompactString, BindId>>,
70    pub used: Map<ModPath, Arc<Vec<ModPath>>>,
71    pub modules: Set<ModPath>,
72    pub typedefs: Map<ModPath, Map<CompactString, TypeDef>>,
73    pub catch: Map<ModPath, BindId>,
74}
75
76impl<R: Rt, E: UserEvent> Clone for Env<R, E> {
77    fn clone(&self) -> Self {
78        Self {
79            by_id: self.by_id.clone(),
80            binds: self.binds.clone(),
81            byref_chain: self.byref_chain.clone(),
82            used: self.used.clone(),
83            modules: self.modules.clone(),
84            typedefs: self.typedefs.clone(),
85            lambdas: self.lambdas.clone(),
86            catch: self.catch.clone(),
87        }
88    }
89}
90
91impl<R: Rt, E: UserEvent> Env<R, E> {
92    pub(super) fn new() -> Self {
93        Self {
94            by_id: Map::new(),
95            binds: Map::new(),
96            byref_chain: Map::new(),
97            used: Map::new(),
98            modules: Set::new(),
99            typedefs: Map::new(),
100            lambdas: Map::new(),
101            catch: Map::new(),
102        }
103    }
104
105    pub(super) fn clear(&mut self) {
106        let Self { by_id, binds, byref_chain, used, modules, typedefs, lambdas, catch } =
107            self;
108        *by_id = Map::new();
109        *binds = Map::new();
110        *byref_chain = Map::new();
111        *used = Map::new();
112        *modules = Set::new();
113        *typedefs = Map::new();
114        *lambdas = Map::new();
115        *catch = Map::new();
116    }
117
118    // restore the lexical environment to the state it was in at the
119    // snapshot `other`, but leave the bind and type environment
120    // alone.
121    pub(super) fn restore_lexical_env(&self, other: Self) -> Self {
122        Self {
123            binds: other.binds,
124            used: other.used,
125            modules: other.modules,
126            typedefs: other.typedefs,
127            by_id: self.by_id.clone(),
128            lambdas: self.lambdas.clone(),
129            catch: self.catch.clone(),
130            byref_chain: self.byref_chain.clone(),
131        }
132    }
133
134    pub fn apply_sandbox(&self, spec: &Sandbox) -> Result<Self> {
135        fn get_bind_name(n: &ModPath) -> Result<(&str, &str)> {
136            let dir = Path::dirname(&**n).ok_or_else(|| anyhow!("unknown module {n}"))?;
137            let k = Path::basename(&**n).ok_or_else(|| anyhow!("unknown module {n}"))?;
138            Ok((dir, k))
139        }
140        match spec {
141            Sandbox::Unrestricted => Ok(self.clone()),
142            Sandbox::Blacklist(bl) => {
143                let mut t = self.clone();
144                for n in bl.iter() {
145                    if t.modules.remove_cow(n) {
146                        t.binds.remove_cow(n);
147                        t.typedefs.remove_cow(n);
148                    } else {
149                        let (dir, k) = get_bind_name(n)?;
150                        let vals = t.binds.get_mut_cow(dir).ok_or_else(|| {
151                            anyhow!("no value {k} in module {dir} and no module {n}")
152                        })?;
153                        if let None = vals.remove_cow(&CompactString::from(k)) {
154                            bail!("no value {k} in module {dir} and no module {n}")
155                        }
156                    }
157                }
158                Ok(t)
159            }
160            Sandbox::Whitelist(wl) => {
161                let mut t = self.clone();
162                let mut modules = FxHashSet::default();
163                let mut names: FxHashMap<_, FxHashSet<_>> = FxHashMap::default();
164                for w in wl.iter() {
165                    if t.modules.contains(w) {
166                        modules.insert(w.clone());
167                    } else {
168                        let (dir, n) = get_bind_name(w)?;
169                        let dir = ModPath(Path::from(ArcStr::from(dir)));
170                        let n = CompactString::from(n);
171                        t.binds.get(&dir).and_then(|v| v.get(&n)).ok_or_else(|| {
172                            anyhow!("no value {n} in module {dir} and no module {w}")
173                        })?;
174                        names.entry(dir).or_default().insert(n);
175                    }
176                }
177                t.typedefs = t.typedefs.update_many(
178                    t.typedefs.into_iter().map(|(k, v)| (k.clone(), v.clone())),
179                    |k, v, _| {
180                        if modules.contains(&k) || names.contains_key(&k) {
181                            Some((k, v))
182                        } else {
183                            None
184                        }
185                    },
186                );
187                t.modules =
188                    t.modules.update_many(t.modules.into_iter().cloned(), |k, _| {
189                        if modules.contains(&k) || names.contains_key(&k) {
190                            Some(k)
191                        } else {
192                            None
193                        }
194                    });
195                t.binds = t.binds.update_many(
196                    t.binds.into_iter().map(|(k, v)| (k.clone(), v.clone())),
197                    |k, v, _| {
198                        if modules.contains(&k) {
199                            Some((k, v))
200                        } else if let Some(names) = names.get(&k) {
201                            let v = v.update_many(
202                                v.into_iter().map(|(k, v)| (k.clone(), v.clone())),
203                                |kn, vn, _| {
204                                    if names.contains(&kn) {
205                                        Some((kn, vn))
206                                    } else {
207                                        None
208                                    }
209                                },
210                            );
211                            Some((k, v))
212                        } else {
213                            None
214                        }
215                    },
216                );
217                Ok(t)
218            }
219        }
220    }
221
222    pub fn find_visible<T, F: FnMut(&str, &str) -> Option<T>>(
223        &self,
224        scope: &ModPath,
225        name: &ModPath,
226        mut f: F,
227    ) -> Option<T> {
228        let mut buf = CompactString::from("");
229        let name_scope = Path::dirname(&**name);
230        let name = Path::basename(&**name).unwrap_or("");
231        for scope in Path::dirnames(&**scope).rev() {
232            let used = self.used.get(scope);
233            let used = iter::once(scope)
234                .chain(used.iter().flat_map(|s| s.iter().map(|p| &***p)));
235            for scope in used {
236                let scope = name_scope
237                    .map(|ns| {
238                        buf.clear();
239                        buf.push_str(scope);
240                        if let Some(Path::SEP) = buf.chars().next_back() {
241                            buf.pop();
242                        }
243                        buf.push_str(ns);
244                        buf.as_str()
245                    })
246                    .unwrap_or(scope);
247                if let Some(res) = f(scope, name) {
248                    return Some(res);
249                }
250            }
251        }
252        None
253    }
254
255    pub fn lookup_bind(
256        &self,
257        scope: &ModPath,
258        name: &ModPath,
259    ) -> Option<(&ModPath, &Bind)> {
260        self.find_visible(scope, name, |scope, name| {
261            self.binds.get_full(scope).and_then(|(scope, vars)| {
262                vars.get(name)
263                    .and_then(|bid| self.by_id.get(bid).map(|bind| (scope, bind)))
264            })
265        })
266    }
267
268    pub fn lookup_typedef(&self, scope: &ModPath, name: &ModPath) -> Option<&TypeDef> {
269        self.find_visible(scope, name, |scope, name| {
270            self.typedefs.get(scope).and_then(|m| m.get(name))
271        })
272    }
273
274    /// lookup the bind id of the nearest catch handler in this scope
275    pub fn lookup_catch(&self, scope: &ModPath) -> Result<BindId> {
276        match Path::dirnames(&scope.0).rev().find_map(|scope| self.catch.get(scope)) {
277            Some(id) => Ok(*id),
278            None => bail!("there is no catch visible in {scope}"),
279        }
280    }
281
282    /// lookup binds in scope that match the specified partial
283    /// name. This is intended to be used for IDEs and interactive
284    /// shells, and is not used by the compiler.
285    pub fn lookup_matching(
286        &self,
287        scope: &ModPath,
288        part: &ModPath,
289    ) -> Vec<(CompactString, BindId)> {
290        let mut res = vec![];
291        self.find_visible(scope, part, |scope, part| {
292            if let Some(vars) = self.binds.get(scope) {
293                let r = vars.range::<str, _>((Bound::Included(part), Bound::Unbounded));
294                for (name, bind) in r {
295                    if name.starts_with(part) {
296                        res.push((name.clone(), *bind));
297                    }
298                }
299            }
300            None::<()>
301        });
302        res
303    }
304
305    /// lookup modules in scope that match the specified partial
306    /// name. This is intended to be used for IDEs and interactive
307    /// shells, and is not used by the compiler.
308    pub fn lookup_matching_modules(
309        &self,
310        scope: &ModPath,
311        part: &ModPath,
312    ) -> Vec<ModPath> {
313        let mut res = vec![];
314        self.find_visible(scope, part, |scope, part| {
315            let p = ModPath(Path::from(ArcStr::from(scope)).append(part));
316            for m in self.modules.range((Bound::Included(p.clone()), Bound::Unbounded)) {
317                if m.0.starts_with(&*p.0) {
318                    if let Some(m) = m.strip_prefix(scope) {
319                        if !m.trim().is_empty() {
320                            res.push(ModPath(Path::from(ArcStr::from(m))));
321                        }
322                    }
323                }
324            }
325            None::<()>
326        });
327        res
328    }
329
330    pub fn canonical_modpath(&self, scope: &ModPath, name: &ModPath) -> Option<ModPath> {
331        self.find_visible(scope, name, |scope, name| {
332            let p = ModPath(Path::from(ArcStr::from(scope)).append(name));
333            if self.modules.contains(&p) {
334                Some(p)
335            } else {
336                None
337            }
338        })
339    }
340
341    pub fn deftype(
342        &mut self,
343        scope: &ModPath,
344        name: &str,
345        params: Arc<[(TVar, Option<Type>)]>,
346        typ: Type,
347    ) -> Result<()> {
348        let defs = self.typedefs.get_or_default_cow(scope.clone());
349        if defs.get(name).is_some() {
350            bail!("{name} is already defined in scope {scope}")
351        } else {
352            thread_local! {
353                static KNOWN: RefCell<FxHashMap<ArcStr, TVar>> = RefCell::new(FxHashMap::default());
354                static DECLARED: RefCell<FxHashSet<ArcStr>> = RefCell::new(FxHashSet::default());
355            }
356            KNOWN.with_borrow_mut(|known| {
357                known.clear();
358                for (tv, tc) in params.iter() {
359                    Type::TVar(tv.clone()).alias_tvars(known);
360                    if let Some(tc) = tc {
361                        tc.alias_tvars(known);
362                    }
363                }
364                typ.alias_tvars(known);
365            });
366            DECLARED.with_borrow_mut(|declared| {
367                declared.clear();
368                for (tv, _) in params.iter() {
369                    if !declared.insert(tv.name.clone()) {
370                        bail!("duplicate type variable {tv} in definition of {name}");
371                    }
372                }
373                typ.check_tvars_declared(declared)?;
374                for (_, t) in params.iter() {
375                    if let Some(t) = t {
376                        t.check_tvars_declared(declared)?;
377                    }
378                }
379                Ok::<_, anyhow::Error>(())
380            })?;
381            KNOWN.with_borrow(|known| {
382                DECLARED.with_borrow(|declared| {
383                    for dec in declared {
384                        if !known.contains_key(dec) {
385                            bail!("unused type parameter {dec} in definition of {name}")
386                        }
387                    }
388                    Ok(())
389                })
390            })?;
391            defs.insert_cow(name.into(), TypeDef { params, typ });
392            Ok(())
393        }
394    }
395
396    pub fn undeftype(&mut self, scope: &ModPath, name: &str) {
397        if let Some(defs) = self.typedefs.get_mut_cow(scope) {
398            defs.remove_cow(&CompactString::from(name));
399            if defs.len() == 0 {
400                self.typedefs.remove_cow(scope);
401            }
402        }
403    }
404
405    /// create a new binding. If an existing bind exists in the same
406    /// scope shadow it.
407    pub fn bind_variable(&mut self, scope: &ModPath, name: &str, typ: Type) -> &mut Bind {
408        let binds = self.binds.get_or_default_cow(scope.clone());
409        let mut existing = true;
410        let id = binds.get_or_insert_cow(CompactString::from(name), || {
411            existing = false;
412            BindId::new()
413        });
414        if existing {
415            *id = BindId::new();
416        }
417        self.by_id.get_or_insert_cow(*id, || Bind {
418            export: true,
419            id: *id,
420            scope: scope.clone(),
421            doc: None,
422            name: CompactString::from(name),
423            typ,
424        })
425    }
426
427    /// make the specified name an alias for `id`
428    pub fn alias_variable(&mut self, scope: &ModPath, name: &str, id: BindId) {
429        let binds = self.binds.get_or_default_cow(scope.clone());
430        binds.insert_cow(CompactString::from(name), id);
431    }
432
433    pub fn unbind_variable(&mut self, id: BindId) {
434        if let Some(b) = self.by_id.remove_cow(&id) {
435            if let Some(binds) = self.binds.get_mut_cow(&b.scope) {
436                binds.remove_cow(&b.name);
437                if binds.len() == 0 {
438                    self.binds.remove_cow(&b.scope);
439                }
440            }
441        }
442    }
443}