diff options
Diffstat (limited to 'src/model/scope.rs')
| -rw-r--r-- | src/model/scope.rs | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/src/model/scope.rs b/src/model/scope.rs new file mode 100644 index 00000000..1ab7032c --- /dev/null +++ b/src/model/scope.rs @@ -0,0 +1,161 @@ +use std::collections::BTreeMap; +use std::fmt::{self, Debug, Formatter}; +use std::hash::Hash; + +use super::{Args, Func, Node, Value, Vm}; +use crate::diag::{SourceResult, StrResult}; +use crate::util::EcoString; + +/// A stack of scopes. +#[derive(Debug, Default, Clone)] +pub struct Scopes<'a> { + /// The active scope. + pub top: Scope, + /// The stack of lower scopes. + pub scopes: Vec<Scope>, + /// The base scope. + pub base: Option<&'a Scope>, +} + +impl<'a> Scopes<'a> { + /// Create a new, empty hierarchy of scopes. + pub fn new(base: Option<&'a Scope>) -> Self { + Self { top: Scope::new(), scopes: vec![], base } + } + + /// Enter a new scope. + pub fn enter(&mut self) { + self.scopes.push(std::mem::take(&mut self.top)); + } + + /// Exit the topmost scope. + /// + /// This panics if no scope was entered. + pub fn exit(&mut self) { + self.top = self.scopes.pop().expect("no pushed scope"); + } + + /// Try to access a variable immutably. + pub fn get(&self, var: &str) -> StrResult<&Value> { + Ok(std::iter::once(&self.top) + .chain(self.scopes.iter().rev()) + .chain(self.base.into_iter()) + .find_map(|scope| scope.get(var)) + .ok_or("unknown variable")?) + } + + /// Try to access a variable mutably. + pub fn get_mut(&mut self, var: &str) -> StrResult<&mut Value> { + std::iter::once(&mut self.top) + .chain(&mut self.scopes.iter_mut().rev()) + .find_map(|scope| scope.get_mut(var)) + .ok_or_else(|| { + if self.base.map_or(false, |base| base.get(var).is_some()) { + "cannot mutate a constant" + } else { + "unknown variable" + } + })? + } +} + +/// A map from binding names to values. +#[derive(Default, Clone, Hash)] +pub struct Scope(BTreeMap<EcoString, Slot>); + +impl Scope { + /// Create a new empty scope. + pub fn new() -> Self { + Self::default() + } + + /// Bind a value to a name. + pub fn define(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) { + self.0.insert(name.into(), Slot::new(value.into(), Kind::Normal)); + } + + /// Define a function through a native rust function. + pub fn def_fn( + &mut self, + name: &'static str, + func: fn(&mut Vm, &mut Args) -> SourceResult<Value>, + ) { + self.define(name, Func::from_fn(name, func)); + } + + /// Define a function through a native rust node. + pub fn def_node<T: Node>(&mut self, name: &'static str) { + self.define(name, Func::from_node::<T>(name)); + } + + /// Define a captured, immutable binding. + pub fn define_captured( + &mut self, + var: impl Into<EcoString>, + value: impl Into<Value>, + ) { + self.0.insert(var.into(), Slot::new(value.into(), Kind::Captured)); + } + + /// Try to access a variable immutably. + pub fn get(&self, var: &str) -> Option<&Value> { + self.0.get(var).map(Slot::read) + } + + /// Try to access a variable mutably. + pub fn get_mut(&mut self, var: &str) -> Option<StrResult<&mut Value>> { + self.0.get_mut(var).map(Slot::write) + } + + /// Iterate over all definitions. + pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> { + self.0.iter().map(|(k, v)| (k.as_str(), v.read())) + } +} + +impl Debug for Scope { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str("Scope ")?; + f.debug_map() + .entries(self.0.iter().map(|(k, v)| (k, v.read()))) + .finish() + } +} + +/// A slot where a value is stored. +#[derive(Clone, Hash)] +struct Slot { + /// The stored value. + value: Value, + /// The kind of slot, determines how the value can be accessed. + kind: Kind, +} + +/// The different kinds of slots. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +enum Kind { + /// A normal, mutable binding. + Normal, + /// A captured copy of another variable. + Captured, +} + +impl Slot { + /// Create a new slot. + fn new(value: Value, kind: Kind) -> Self { + Self { value, kind } + } + + /// Read the value. + fn read(&self) -> &Value { + &self.value + } + + /// Try to write to the value. + fn write(&mut self) -> StrResult<&mut Value> { + match self.kind { + Kind::Normal => Ok(&mut self.value), + Kind::Captured => Err("cannot mutate a captured variable")?, + } + } +} |
