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