use std::collections::BTreeMap; use codemap::Spanned; use crate::{ atrule::mixin::Mixin, builtin::{modules::Module, GLOBAL_FUNCTIONS}, common::Identifier, error::SassResult, value::{SassFunction, Value}, }; #[derive(Debug, Default)] pub(crate) struct Scope { pub vars: BTreeMap, pub mixins: BTreeMap, pub functions: BTreeMap, } impl Scope { // `BTreeMap::new` is not yet const #[allow(clippy::missing_const_for_fn)] #[must_use] pub fn new() -> Self { Self { vars: BTreeMap::new(), mixins: BTreeMap::new(), functions: BTreeMap::new(), } } fn get_var(&self, name: Spanned) -> SassResult<&Value> { match self.vars.get(&name.node) { Some(v) => Ok(v), None => Err(("Undefined variable.", name.span).into()), } } pub fn insert_var(&mut self, s: Identifier, v: Value) -> Option { self.vars.insert(s, v) } pub fn var_exists(&self, name: Identifier) -> bool { self.vars.contains_key(&name) } fn get_mixin(&self, name: Spanned) -> SassResult { match self.mixins.get(&name.node) { Some(v) => Ok(v.clone()), None => Err(("Undefined mixin.", name.span).into()), } } pub fn insert_mixin>(&mut self, s: T, v: Mixin) -> Option { self.mixins.insert(s.into(), v) } pub fn mixin_exists(&self, name: Identifier) -> bool { self.mixins.contains_key(&name) } fn get_fn(&self, name: Identifier) -> Option { self.functions.get(&name).cloned() } pub fn insert_fn(&mut self, s: Identifier, v: SassFunction) -> Option { self.functions.insert(s, v) } fn fn_exists(&self, name: Identifier) -> bool { if self.functions.is_empty() { return false; } self.functions.contains_key(&name) } fn merge(&mut self, other: Scope) { self.vars.extend(other.vars); self.mixins.extend(other.mixins); self.functions.extend(other.functions); } pub fn merge_module(&mut self, other: Module) { self.merge(other.0); } } #[derive(Debug, Default)] pub(crate) struct Scopes(Vec); impl Scopes { pub const fn new() -> Self { Self(Vec::new()) } pub fn len(&self) -> usize { self.0.len() } pub fn split_off(mut self, len: usize) -> (Scopes, Scopes) { let split = self.0.split_off(len); (self, Scopes(split)) } pub fn enter_new_scope(&mut self) { self.0.push(Scope::new()); } pub fn enter_scope(&mut self, scope: Scope) { self.0.push(scope); } pub fn exit_scope(&mut self) { self.0.pop(); } pub fn merge(&mut self, mut other: Self) { self.0.append(&mut other.0); } } /// Variables impl Scopes { pub fn insert_var(&mut self, s: Identifier, v: Value) -> Option { for scope in self.0.iter_mut().rev() { if scope.var_exists(s) { return scope.insert_var(s, v); } } if let Some(scope) = self.0.last_mut() { scope.insert_var(s, v) } else { let mut scope = Scope::new(); scope.insert_var(s, v); self.0.push(scope); None } } /// Always insert this variable into the innermost scope /// /// Used, for example, for variables from `@each` and `@for` pub fn insert_var_last(&mut self, s: Identifier, v: Value) -> Option { if let Some(scope) = self.0.last_mut() { scope.insert_var(s, v) } else { let mut scope = Scope::new(); scope.insert_var(s, v); self.0.push(scope); None } } pub fn insert_default_var(&mut self, s: Identifier, v: Value) -> Option { if let Some(scope) = self.0.last_mut() { if scope.var_exists(s) { None } else { scope.insert_var(s, v) } } else { panic!() } } pub fn get_var<'a>( &'a self, name: Spanned, global_scope: &'a Scope, ) -> SassResult<&Value> { for scope in self.0.iter().rev() { if scope.var_exists(name.node) { return scope.get_var(name); } } global_scope.get_var(name) } pub fn var_exists(&self, name: Identifier, global_scope: &Scope) -> bool { for scope in &self.0 { if scope.var_exists(name) { return true; } } global_scope.var_exists(name) } } /// Mixins impl Scopes { pub fn insert_mixin(&mut self, s: Identifier, v: Mixin) -> Option { if let Some(scope) = self.0.last_mut() { scope.insert_mixin(s, v) } else { let mut scope = Scope::new(); scope.insert_mixin(s, v); self.0.push(scope); None } } pub fn get_mixin<'a>( &'a self, name: Spanned, global_scope: &'a Scope, ) -> SassResult { for scope in self.0.iter().rev() { if scope.mixin_exists(name.node) { return scope.get_mixin(name); } } global_scope.get_mixin(name) } pub fn mixin_exists(&self, name: Identifier, global_scope: &Scope) -> bool { for scope in &self.0 { if scope.mixin_exists(name) { return true; } } global_scope.mixin_exists(name) } } /// Functions impl Scopes { pub fn insert_fn(&mut self, s: Identifier, v: SassFunction) -> Option { if let Some(scope) = self.0.last_mut() { scope.insert_fn(s, v) } else { let mut scope = Scope::new(); scope.insert_fn(s, v); self.0.push(scope); None } } pub fn get_fn<'a>(&'a self, name: Identifier, global_scope: &'a Scope) -> Option { for scope in self.0.iter().rev() { if scope.fn_exists(name) { return scope.get_fn(name); } } global_scope.get_fn(name) } pub fn fn_exists(&self, name: Identifier, global_scope: &Scope) -> bool { for scope in &self.0 { if scope.fn_exists(name) { return true; } } global_scope.fn_exists(name) || GLOBAL_FUNCTIONS.contains_key(name.as_str()) } }