use crate::css::Value;
use crate::output::Format;
use crate::sass::{Function, Item, Mixin, Name};
use crate::selectors::Selectors;
use crate::Error;
use lazy_static::lazy_static;
use std::collections::BTreeMap;
use std::ops::Deref;
use std::sync::{Arc, Mutex};
#[derive(Clone)]
pub enum ScopeRef {
Builtin(&'static Scope),
Dynamic(Arc<Scope>),
}
impl ScopeRef {
pub fn new_global(format: Format) -> Self {
Self::dynamic(Scope::new_global(format))
}
pub fn sub(parent: ScopeRef) -> Self {
Self::dynamic(Scope::sub(parent))
}
pub fn sub_selectors(parent: ScopeRef, selectors: Selectors) -> Self {
Self::dynamic(Scope::sub_selectors(parent, selectors))
}
fn dynamic(scope: Scope) -> Self {
ScopeRef::Dynamic(Arc::new(scope))
}
pub fn is_same(a: &Self, b: &Self) -> bool {
match (a, b) {
(ScopeRef::Builtin(a), ScopeRef::Builtin(b)) => {
std::ptr::eq(a, b)
}
(ScopeRef::Dynamic(ref a), ScopeRef::Dynamic(ref b)) => {
Arc::ptr_eq(a, b)
}
_ => false,
}
}
pub fn eval_body(self, body: &[Item]) -> Result<Option<Value>, Error>
where
Self: Sized,
{
for b in body {
let result = match *b {
Item::IfStatement(ref cond, ref do_if, ref do_else) => {
if cond.evaluate(self.clone())?.is_true() {
self.clone().eval_body(do_if)?
} else {
self.clone().eval_body(do_else)?
}
}
Item::Each(ref names, ref values, ref body) => {
let s = self.clone();
for value in values.evaluate(s.clone())?.iter_items() {
s.define_multi(names, &value);
if let Some(r) = s.clone().eval_body(body)? {
return Ok(Some(r));
}
}
None
}
Item::For {
ref name,
ref from,
ref to,
inclusive,
ref body,
} => {
let range = crate::value::ValueRange::new(
from.evaluate(self.clone())?,
to.evaluate(self.clone())?,
inclusive,
)?;
let s = self.clone();
for value in range {
s.define(name.clone(), &value);
if let Some(r) = s.clone().eval_body(body)? {
return Ok(Some(r));
}
}
None
}
Item::VariableDeclaration {
ref name,
ref val,
default,
global,
} => {
let val = val.evaluate(self.clone())?;
self.set_variable(name.into(), val, default, global);
None
}
Item::Return(ref v) => Some(v.evaluate(self.clone())?),
Item::While(ref cond, ref body) => {
let scope = ScopeRef::sub(self.clone());
while cond.evaluate(scope.clone())?.is_true() {
if let Some(r) = scope.clone().eval_body(body)? {
return Ok(Some(r));
}
}
None
}
Item::Warn(ref value) => {
eprintln!(
"WARNING: {}",
value
.evaluate(self.clone())?
.format(self.get_format())
);
None
}
Item::Error(ref value) => {
return Err(Error::S(format!(
"Error: {}",
value
.evaluate(self.clone())?
.format(self.get_format()),
)));
}
Item::None => None,
Item::Comment(..) => None,
ref x => {
return Err(Error::S(format!(
"Not implemented in function: {:?}",
x
)))
}
};
if let Some(result) = result {
return Ok(Some(result));
}
}
Ok(None)
}
}
impl Deref for ScopeRef {
type Target = Scope;
fn deref(&self) -> &Scope {
match self {
ScopeRef::Builtin(m) => m,
ScopeRef::Dynamic(m) => m,
}
}
}
pub struct Scope {
parent: Option<ScopeRef>,
modules: Mutex<BTreeMap<Name, ScopeRef>>,
variables: Mutex<BTreeMap<Name, Value>>,
mixins: Mutex<BTreeMap<Name, Mixin>>,
functions: Mutex<BTreeMap<Name, Function>>,
selectors: Option<Selectors>,
format: Format,
}
impl<'a> Scope {
pub fn new_global(format: Format) -> Self {
Scope {
parent: None,
modules: Mutex::new(BTreeMap::new()),
variables: Mutex::new(BTreeMap::new()),
mixins: Mutex::new(BTreeMap::new()),
functions: Mutex::new(BTreeMap::new()),
selectors: None,
format,
}
}
pub fn builtin_module(name: &'static str) -> Self {
let s = Scope::new_global(Default::default());
s.set_variable(
Name::from_static("@scope_name@"),
name.into(),
false,
false,
);
s
}
pub(crate) fn get_name(&self) -> Name {
match self.get_or_none(&Name::from_static("@scope_name@")) {
Some(Value::Literal(s, _q)) => s.into(),
_ => Name::from_static(""),
}
}
pub fn sub(parent: ScopeRef) -> Self {
let format = parent.get_format();
Scope {
parent: Some(parent),
modules: Mutex::new(BTreeMap::new()),
variables: Mutex::new(BTreeMap::new()),
mixins: Mutex::new(BTreeMap::new()),
functions: Mutex::new(BTreeMap::new()),
selectors: None,
format,
}
}
pub fn sub_selectors(parent: ScopeRef, selectors: Selectors) -> Self {
let format = parent.get_format();
Scope {
parent: Some(parent),
modules: Mutex::new(BTreeMap::new()),
variables: Mutex::new(BTreeMap::new()),
mixins: Mutex::new(BTreeMap::new()),
functions: Mutex::new(BTreeMap::new()),
selectors: Some(selectors),
format,
}
}
pub fn define_module(&self, name: Name, module: ScopeRef) {
self.modules.lock().unwrap().insert(name, module);
}
pub fn get_module(&self, name: &Name) -> Option<ScopeRef> {
self.modules
.lock()
.unwrap()
.get(name)
.cloned()
.or_else(|| self.parent.as_ref().and_then(|p| p.get_module(name)))
}
pub fn get_format(&self) -> Format {
self.format
}
pub fn define(&self, name: Name, val: &Value) {
self.set_variable(name, val.clone(), false, false)
}
pub fn set_variable(
&self,
name: Name,
val: Value,
default: bool,
global: bool,
) {
if default
&& !matches!(self.get_or_none(&name), Some(Value::Null) | None)
{
return;
}
if global {
self.define_global(name, val);
} else {
self.variables.lock().unwrap().insert(name, val);
}
}
pub fn define_global(&self, name: Name, val: Value) {
if let Some(ref parent) = self.parent {
parent.define_global(name, val);
} else {
self.variables.lock().unwrap().insert(name, val);
}
}
pub fn define_multi(&self, names: &[Name], value: &Value) {
if names.len() == 1 {
self.define(names[0].clone(), &value);
} else {
let values = value.clone().iter_items();
if values.len() > names.len() {
panic!(
"Expected {} values, but got {}",
names.len(),
values.len(),
)
} else {
let mut values = values.iter();
for name in names {
self.define(
name.clone(),
&values.next().unwrap_or(&Value::Null),
)
}
}
}
}
pub fn get_or_none(&self, name: &Name) -> Option<Value> {
if let Some((modulename, name)) = name.split_module() {
if let Some(module) = self.get_module(&modulename) {
return module.get_or_none(&name);
}
}
self.variables
.lock()
.unwrap()
.get(name)
.cloned()
.or_else(|| {
self.parent.as_ref().and_then(|p| p.get_or_none(name))
})
}
pub fn get(&self, name: &str) -> Result<Value, Error> {
match self.get_or_none(&name.into()) {
Some(value) => Ok(value),
None => Err(Error::undefined_variable(name)),
}
}
pub fn store_local_values(
&self,
names: &[Name],
) -> Vec<(Name, Option<Value>)> {
let vars = self.variables.lock().unwrap();
names
.iter()
.map(|name| (name.clone(), vars.get(name).cloned()))
.collect()
}
pub fn restore_local_values(&self, data: Vec<(Name, Option<Value>)>) {
let mut vars = self.variables.lock().unwrap();
for (name, value) in data {
if let Some(value) = value {
vars.insert(name, value);
} else {
vars.remove(&name);
}
}
}
pub fn get_global_or_none(&self, name: &Name) -> Option<Value> {
if let Some(ref parent) = self.parent {
parent.get_global_or_none(name)
} else {
self.get_or_none(name)
}
}
pub fn get_mixin(&self, name: &Name) -> Option<Mixin> {
if let Some((modulename, name)) = name.split_module() {
self.get_module(&modulename)
.and_then(|m| m.get_mixin(&name))
} else {
self.mixins.lock().unwrap().get(name).cloned().or_else(|| {
self.parent.as_ref().and_then(|p| p.get_mixin(name))
})
}
}
pub fn define_mixin(&self, name: Name, mixin: Mixin) {
self.mixins.lock().unwrap().insert(name, mixin);
}
pub fn define_function(&self, name: Name, func: Function) {
self.functions.lock().unwrap().insert(name, func);
}
pub fn get_function(&self, name: &Name) -> Option<Function> {
if let Some((modulename, name)) = name.split_module() {
self.get_module(&modulename)
.and_then(|m| m.get_function(&name))
} else {
let f = self.functions.lock().unwrap().get(name).cloned();
if let Some(f) = f {
Some(f)
} else if let Some(ref parent) = self.parent {
parent.get_function(name)
} else {
Function::get_builtin(name).cloned()
}
}
}
pub fn get_selectors(&self) -> &Selectors {
lazy_static! {
static ref ROOT: Selectors = Selectors::root();
}
self.selectors.as_ref().unwrap_or_else(|| {
self.parent
.as_ref()
.map(|p| p.get_selectors())
.unwrap_or_else(|| &ROOT)
})
}
pub fn expose_star(&self, other: &Scope) {
for (name, function) in &*other.functions.lock().unwrap() {
self.define_function(name.clone(), function.clone());
}
for (name, value) in &*other.variables.lock().unwrap() {
self.define(name.clone(), value);
}
for (name, m) in &*other.mixins.lock().unwrap() {
self.define_mixin(name.clone(), m.clone());
}
}
}
#[cfg(test)]
pub mod test {
macro_rules! assert_expr {
($context:expr, $input:expr, $expected:expr) => {{
assert_eq!(
do_evaluate_or_error($context, $input)
.unwrap_or_else(|e| panic!("{}", e)),
$expected
)
}};
($input:expr, $expected:expr) => {{
assert_expr!(&[], $input, $expected)
}};
}
#[test]
fn variable_value() {
assert_expr!(&[("red", "#f02a42")], b"$red;", "#f02a42")
}
#[test]
fn undefined_variable() {
assert_eq!(
"Undefined variable: \"$x\"",
format!("{}", do_evaluate_or_error(&[], b"$x;").err().unwrap())
)
}
#[test]
fn partial_variable_value() {
assert_expr!(
&[("red", "#f02a42")],
b"solid 1px $red;",
"solid 1px #f02a42"
)
}
#[test]
fn simple_arithmetic() {
assert_expr!(b"3 + 3;", "6")
}
#[test]
fn simple_arithmetic_2() {
assert_expr!(b"2 + 3 * 4;", "14")
}
#[test]
fn simple_arithmetic_3() {
assert_expr!(&[("four", "4")], b"2 + 3 * $four;", "14")
}
#[test]
fn div_slash_1() {
assert_expr!(b"10px/8px;", "10px/8px")
}
#[test]
fn div_slash_2() {
assert_expr!(&[("width", "1000px")], b"$width/2;", "500px")
}
#[test]
fn div_slash_4() {
assert_expr!(b"(500px/2);", "250px")
}
#[test]
fn div_slash_5() {
assert_expr!(b"5px + 8px/2px;", "9px")
}
#[test]
fn div_slash_6() {
assert_expr!(b"(italic bold 10px/8px);", "italic bold 10px/8px")
}
#[test]
fn negative_in_arithmetic() {
assert_expr!(&[("m", "20")], b"1000px + $m * -2;", "960px")
}
#[test]
fn double_div_1() {
assert_expr!(b"15/3/5;", "15/3/5")
}
#[test]
fn double_div_2() {
assert_expr!(b"15 / 3 / 5;", "15/3/5")
}
#[test]
fn double_div_3() {
assert_expr!(b"(15 / 3 / 5);", "1")
}
#[test]
fn long_div_and_mul_sequence() {
assert_expr!(b"(3 / 2 / 2 / 2 * 32 / 2 / 2);", "3")
}
#[test]
fn double_div_4() {
assert_expr!(b"(15 / 3) / 5;", "1");
}
#[test]
fn double_div_5() {
assert_expr!(&[("five", "5")], b"15 / 3 / $five;", "1")
}
#[test]
fn sum_w_unit() {
assert_expr!(b"3px + 3px + 3px;", "9px")
}
#[test]
fn multi_multi() {
assert_expr!(
&[("stuff", "1 2 3")],
b"1 2 3, $stuff 4 5 (6, 7 8 9);",
"1 2 3, 1 2 3 4 5 6, 7 8 9"
)
}
#[test]
fn url_keeps_parens() {
assert_expr!(
b"black url(starfield.png) repeat;",
"black url(starfield.png) repeat"
)
}
#[test]
fn color_unchanged_1() {
assert_expr!(b"#AbC;", "#AbC")
}
#[test]
fn color_unchanged_2() {
assert_expr!(b"#AAbbCC;", "#AAbbCC")
}
#[test]
fn color_add_each_component() {
assert_expr!(b"#AbC + 1;", "#abbccd")
}
#[test]
fn color_add_each_component_overflow() {
assert_expr!(b"#00f + 1;", "#0101ff")
}
#[test]
fn color_add_components() {
assert_expr!(b"#AbC + #001;", "#aabbdd")
}
#[test]
fn color_add_components_overflow() {
assert_expr!(b"#1000ff + #001;", "#1000ff")
}
#[test]
fn color_add_components_to_named_overflow() {
assert_expr!(b"#0000ff + #001;", "blue")
}
#[test]
fn color_add_components_to_named() {
assert_expr!(b"#00f + #0f0 + #f00;", "white")
}
#[test]
fn color_simple_rgba() {
assert_expr!(b"rgba(1,2,3,.6);", "rgba(1, 2, 3, 0.6)")
}
#[test]
fn color_add_to_rgba() {
assert_expr!(b"rgba(0, 0, 0, 1) + #111;", "#111111")
}
#[test]
fn color_subtract() {
assert_expr!(b"#fff - 1;", "#fefefe")
}
#[test]
fn color_subtract_underflow() {
assert_expr!(b"#000 - 1;", "black")
}
#[test]
fn color_subtract_components() {
assert_expr!(b"#fff - #ff8;", "#000077")
}
#[test]
fn color_subtract_components_underflow() {
assert_expr!(b"#000001 - #001;", "black")
}
#[test]
fn color_division() {
assert_expr!(b"(#101010 / 7);", "#020202")
}
#[test]
fn color_add_rgb_1() {
assert_expr!(b"rgb(10,10,10) + #010001;", "#0b0a0b")
}
#[test]
fn color_add_rgb_2() {
assert_expr!(b"#010000 + rgb(255, 255, 255);", "white")
}
#[test]
fn color_named_args() {
assert_expr!(b"rgb($blue: 3, $red: 1, $green: 2);", "#010203")
}
#[test]
fn color_mixed_args() {
assert_expr!(b"rgb(1, $blue: 3, $green: 2);", "#010203")
}
#[test]
fn color_mixed_with_alpha_1() {
assert_expr!(
b"mix(rgba(255, 0, 0, 0.5), #00f);",
"rgba(64, 0, 191, 0.75)"
)
}
#[test]
fn color_mixed_with_alpha_2() {
assert_expr!(
b"mix(#00f, rgba(255, 0, 0, 0.5));",
"rgba(64, 0, 191, 0.75)"
)
}
#[test]
fn value_multiple_dashes() {
assert_expr!(b"foo-bar-baz 17%;", "foo-bar-baz 17%")
}
#[test]
fn color_arithemtic_by_name() {
assert_expr!(b"red + blue;", "fuchsia")
}
#[test]
fn function_if() {
assert_expr!(b"if(true, foo, bar);", "foo")
}
#[test]
fn function_if_false() {
assert_expr!(b"if(false, foo, bar);", "bar")
}
#[test]
fn function_if_named() {
assert_expr!(
b"if($if_true: hey, $if_false: ho, $condition: true);",
"hey"
)
}
#[test]
fn function_if_named_dash() {
assert_expr!(
b"if($if-true: hey, $if-false: ho, $condition: true);",
"hey"
)
}
#[test]
fn quoted_string() {
assert_expr!(b"\"foobar\";", "\"foobar\"")
}
#[test]
fn unquote_string() {
assert_expr!(b"unquote(\"foo bar\");", "foo bar")
}
#[test]
fn equal_true() {
assert_expr!(b"17 == 10 + 7;", "true")
}
#[test]
fn equal_false() {
assert_expr!(b"17 == 10 + 8;", "false")
}
#[test]
fn not_equal_true() {
assert_expr!(b"17 != 10 + 8;", "true")
}
#[test]
fn not_equal_false() {
assert_expr!(b"18 != 10 + 8;", "false")
}
#[test]
fn simple_boolean() {
assert_expr!(b"3 >= 2 and 1 < 10;", "true")
}
pub fn do_evaluate(
s: &[(&'static str, &str)],
expression: &[u8],
) -> String {
do_evaluate_or_error(s, expression).unwrap()
}
pub fn do_evaluate_or_error(
s: &[(&'static str, &str)],
expression: &[u8],
) -> Result<String, crate::Error> {
use super::{Scope, ScopeRef};
use crate::parser::value::value_expression;
use crate::parser::{code_span, ParseError};
use crate::sass::Name;
use nom::bytes::complete::tag;
use nom::sequence::terminated;
let f = Default::default();
let scope = Scope::new_global(f);
for &(name, val) in s {
let val = value_expression(code_span(val.as_bytes()));
scope.define(
Name::from_static(name),
&ParseError::check(val)?.evaluate(ScopeRef::new_global(f))?,
);
}
let expr =
terminated(value_expression, tag(";"))(code_span(expression));
Ok(ParseError::check(expr)?
.evaluate(ScopeRef::dynamic(scope))?
.format(f)
.to_string())
}
}