use std::{
cell::RefCell,
collections::{BTreeMap, HashSet},
fmt,
sync::Arc,
};
use codemap::{Span, Spanned};
use crate::{
ast::{ArgumentResult, AstForwardRule, BuiltinMixin, Mixin},
builtin::Builtin,
common::Identifier,
error::SassResult,
evaluate::{Environment, Visitor},
selector::ExtensionStore,
utils::{BaseMapView, MapView, MergedMapView, PrefixedMapView, PublicMemberMapView},
value::{SassFunction, SassMap, Value},
};
use super::builtin_imports::QuoteKind;
mod color;
mod list;
mod map;
mod math;
mod meta;
mod selector;
mod string;
#[derive(Debug, Clone)]
pub(crate) struct ForwardedModule {
inner: Arc<RefCell<Module>>,
#[allow(dead_code)]
forward_rule: AstForwardRule,
}
impl ForwardedModule {
pub fn new(module: Arc<RefCell<Module>>, rule: AstForwardRule) -> Self {
let scope = (*module).borrow().scope();
let variables = Self::forwarded_map(
scope.variables,
rule.prefix.as_deref(),
rule.shown_variables.as_ref(),
rule.hidden_variables.as_ref(),
);
let functions = Self::forwarded_map(
scope.functions,
rule.prefix.as_deref(),
rule.shown_mixins_and_functions.as_ref(),
rule.hidden_mixins_and_functions.as_ref(),
);
let mixins = Self::forwarded_map(
scope.mixins,
rule.prefix.as_deref(),
rule.shown_mixins_and_functions.as_ref(),
rule.hidden_mixins_and_functions.as_ref(),
);
(*module).borrow_mut().set_scope(ModuleScope {
variables,
mixins,
functions,
});
ForwardedModule {
inner: module,
forward_rule: rule,
}
}
fn forwarded_map<T: Clone + fmt::Debug + 'static>(
mut map: Arc<dyn MapView<Value = T>>,
prefix: Option<&str>,
safelist: Option<&HashSet<Identifier>>,
blocklist: Option<&HashSet<Identifier>>,
) -> Arc<dyn MapView<Value = T>> {
debug_assert!(safelist.is_none() || blocklist.is_none());
if prefix.is_none() && safelist.is_none() && blocklist.is_none() {
return map;
}
if let Some(prefix) = prefix {
map = Arc::new(PrefixedMapView(map, prefix.to_owned()));
}
map
}
pub fn if_necessary(
module: Arc<RefCell<Module>>,
rule: AstForwardRule,
) -> Arc<RefCell<Module>> {
if rule.prefix.is_none()
&& rule.shown_mixins_and_functions.is_none()
&& rule.shown_variables.is_none()
&& rule
.hidden_mixins_and_functions
.as_ref()
.map_or(false, HashSet::is_empty)
&& rule
.hidden_variables
.as_ref()
.map_or(false, HashSet::is_empty)
{
module
} else {
Arc::new(RefCell::new(Module::Forwarded(ForwardedModule::new(
module, rule,
))))
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct ModuleScope {
pub variables: Arc<dyn MapView<Value = Value>>,
pub mixins: Arc<dyn MapView<Value = Mixin>>,
pub functions: Arc<dyn MapView<Value = SassFunction>>,
}
impl ModuleScope {
pub fn new() -> Self {
Self {
variables: Arc::new(BaseMapView(Arc::new(RefCell::new(BTreeMap::new())))),
mixins: Arc::new(BaseMapView(Arc::new(RefCell::new(BTreeMap::new())))),
functions: Arc::new(BaseMapView(Arc::new(RefCell::new(BTreeMap::new())))),
}
}
}
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub(crate) enum Module {
Environment {
scope: ModuleScope,
#[allow(dead_code)]
upstream: Vec<Module>,
#[allow(dead_code)]
extension_store: ExtensionStore,
#[allow(dead_code)]
env: Environment,
},
Builtin {
scope: ModuleScope,
},
Forwarded(ForwardedModule),
}
#[derive(Debug, Clone)]
pub(crate) struct Modules(BTreeMap<Identifier, Arc<RefCell<Module>>>);
impl Modules {
pub fn new() -> Self {
Self(BTreeMap::new())
}
pub fn insert(
&mut self,
name: Identifier,
module: Arc<RefCell<Module>>,
span: Span,
) -> SassResult<()> {
if self.0.contains_key(&name) {
return Err((
format!("There's already a module with namespace \"{}\".", name),
span,
)
.into());
}
self.0.insert(name, module);
Ok(())
}
pub fn get(&self, name: Identifier, span: Span) -> SassResult<Arc<RefCell<Module>>> {
match self.0.get(&name) {
Some(v) => Ok(Arc::clone(v)),
None => Err((
format!(
"There is no module with the namespace \"{}\".",
name.as_str()
),
span,
)
.into()),
}
}
pub fn get_mut(
&mut self,
name: Identifier,
span: Span,
) -> SassResult<&mut Arc<RefCell<Module>>> {
match self.0.get_mut(&name) {
Some(v) => Ok(v),
None => Err((
format!(
"There is no module with the namespace \"{}\".",
name.as_str()
),
span,
)
.into()),
}
}
}
fn member_map<V: fmt::Debug + Clone + 'static>(
local: Arc<dyn MapView<Value = V>>,
others: Vec<Arc<dyn MapView<Value = V>>>,
) -> Arc<dyn MapView<Value = V>> {
let local_map = PublicMemberMapView(local);
if others.is_empty() {
return Arc::new(local_map);
}
let mut all_maps: Vec<Arc<dyn MapView<Value = V>>> =
others.into_iter().filter(|map| !map.is_empty()).collect();
all_maps.push(Arc::new(local_map));
Arc::new(MergedMapView::new(all_maps))
}
impl Module {
pub fn new_env(env: Environment, extension_store: ExtensionStore) -> Self {
let variables = {
let variables = (*env.forwarded_modules).borrow();
let variables = variables
.iter()
.map(|module| Arc::clone(&(*module).borrow().scope().variables));
let this = Arc::new(BaseMapView(env.global_vars()));
member_map(this, variables.collect())
};
let mixins = {
let mixins = (*env.forwarded_modules).borrow();
let mixins = mixins
.iter()
.map(|module| Arc::clone(&(*module).borrow().scope().mixins));
let this = Arc::new(BaseMapView(env.global_mixins()));
member_map(this, mixins.collect())
};
let functions = {
let functions = (*env.forwarded_modules).borrow();
let functions = functions
.iter()
.map(|module| Arc::clone(&(*module).borrow().scope().functions));
let this = Arc::new(BaseMapView(env.global_functions()));
member_map(this, functions.collect())
};
let scope = ModuleScope {
variables,
mixins,
functions,
};
Module::Environment {
scope,
upstream: Vec::new(),
extension_store,
env,
}
}
pub fn new_builtin() -> Self {
Module::Builtin {
scope: ModuleScope::new(),
}
}
fn scope(&self) -> ModuleScope {
match self {
Self::Builtin { scope } | Self::Environment { scope, .. } => scope.clone(),
Self::Forwarded(forwarded) => (*forwarded.inner).borrow().scope(),
}
}
fn set_scope(&mut self, new_scope: ModuleScope) {
match self {
Self::Builtin { scope } | Self::Environment { scope, .. } => *scope = new_scope,
Self::Forwarded(forwarded) => (*forwarded.inner).borrow_mut().set_scope(new_scope),
}
}
pub fn get_var(&self, name: Spanned<Identifier>) -> SassResult<Value> {
let scope = self.scope();
match scope.variables.get(name.node) {
Some(v) => Ok(v),
None => Err(("Undefined variable.", name.span).into()),
}
}
pub fn get_var_no_err(&self, name: Identifier) -> Option<Value> {
let scope = self.scope();
scope.variables.get(name)
}
pub fn get_mixin_no_err(&self, name: Identifier) -> Option<Mixin> {
let scope = self.scope();
scope.mixins.get(name)
}
pub fn update_var(&mut self, name: Spanned<Identifier>, value: Value) -> SassResult<()> {
let scope = match self {
Self::Builtin { .. } => {
return Err(("Cannot modify built-in variable.", name.span).into())
}
Self::Environment { scope, .. } => scope.clone(),
Self::Forwarded(forwarded) => (*forwarded.inner).borrow_mut().scope(),
};
if scope.variables.insert(name.node, value).is_none() {
return Err(("Undefined variable.", name.span).into());
}
Ok(())
}
pub fn get_mixin(&self, name: Spanned<Identifier>) -> SassResult<Mixin> {
let scope = self.scope();
match scope.mixins.get(name.node) {
Some(v) => Ok(v),
None => Err(("Undefined mixin.", name.span).into()),
}
}
pub fn insert_builtin_mixin(&mut self, name: &'static str, mixin: BuiltinMixin) {
let scope = self.scope();
scope.mixins.insert(name.into(), Mixin::Builtin(mixin));
}
pub fn insert_builtin_var(&mut self, name: &'static str, value: Value) {
let ident = name.into();
let scope = self.scope();
scope.variables.insert(ident, value);
}
pub fn get_fn(&self, name: Identifier) -> Option<SassFunction> {
let scope = self.scope();
scope.functions.get(name)
}
pub fn var_exists(&self, name: Identifier) -> bool {
let scope = self.scope();
scope.variables.get(name).is_some()
}
pub fn mixin_exists(&self, name: Identifier) -> bool {
let scope = self.scope();
scope.mixins.get(name).is_some()
}
pub fn fn_exists(&self, name: Identifier) -> bool {
let scope = self.scope();
scope.functions.get(name).is_some()
}
pub fn insert_builtin(
&mut self,
name: &'static str,
function: fn(ArgumentResult, &mut Visitor) -> SassResult<Value>,
) {
let ident = name.into();
let scope = match self {
Self::Builtin { scope } => scope,
_ => unreachable!(),
};
scope
.functions
.insert(ident, SassFunction::Builtin(Builtin::new(function), ident));
}
pub fn functions(&self, span: Span) -> SassMap {
SassMap::new_with(
self.scope()
.functions
.iter()
.into_iter()
.filter(|(key, _)| !key.as_str().starts_with('-'))
.map(|(key, value)| {
(
Value::String(key.to_string(), QuoteKind::Quoted).span(span),
Value::FunctionRef(Box::new(value)),
)
})
.collect::<Vec<_>>(),
)
}
pub fn variables(&self, span: Span) -> SassMap {
SassMap::new_with(
self.scope()
.variables
.iter()
.into_iter()
.filter(|(key, _)| !key.as_str().starts_with('-'))
.map(|(key, value)| {
(
Value::String(key.to_string(), QuoteKind::Quoted).span(span),
value,
)
})
.collect::<Vec<_>>(),
)
}
}
pub(crate) fn declare_module_color() -> Module {
let mut module = Module::new_builtin();
color::declare(&mut module);
module
}
pub(crate) fn declare_module_list() -> Module {
let mut module = Module::new_builtin();
list::declare(&mut module);
module
}
pub(crate) fn declare_module_map() -> Module {
let mut module = Module::new_builtin();
map::declare(&mut module);
module
}
pub(crate) fn declare_module_math() -> Module {
let mut module = Module::new_builtin();
math::declare(&mut module);
module
}
pub(crate) fn declare_module_meta() -> Module {
let mut module = Module::new_builtin();
meta::declare(&mut module);
module
}
pub(crate) fn declare_module_selector() -> Module {
let mut module = Module::new_builtin();
selector::declare(&mut module);
module
}
pub(crate) fn declare_module_string() -> Module {
let mut module = Module::new_builtin();
string::declare(&mut module);
module
}