diff --git a/src/builtin/color.rs b/src/builtin/color.rs index 0270295..aa9b81c 100644 --- a/src/builtin/color.rs +++ b/src/builtin/color.rs @@ -10,7 +10,7 @@ use crate::units::Unit; use crate::value::Value; pub(crate) fn register(f: &mut BTreeMap) { - decl!(f "rgb", |args| { + decl!(f "rgb", |args, _| { let channels = args.get("channels").unwrap_or(&Value::Null); if channels.is_null() { let red: u16 = arg!(args, 0, "red").clone().try_into().unwrap(); @@ -21,19 +21,19 @@ pub(crate) fn register(f: &mut BTreeMap) { todo!("channels variable in `rgb`") } }); - decl!(f "red", |args| { + decl!(f "red", |args, _| { match arg!(args, 0, "red") { Value::Color(c) => Some(Value::Dimension(BigRational::from_integer(BigInt::from(c.red())), Unit::None)), _ => todo!("non-color given to builtin function `red()`") } }); - decl!(f "green", |args| { + decl!(f "green", |args, _| { match arg!(args, 0, "green") { Value::Color(c) => Some(Value::Dimension(BigRational::from_integer(BigInt::from(c.green())), Unit::None)), _ => todo!("non-color given to builtin function `green()`") } }); - decl!(f "blue", |args| { + decl!(f "blue", |args, _| { match arg!(args, 0, "blue") { Value::Color(c) => Some(Value::Dimension(BigRational::from_integer(BigInt::from(c.blue())), Unit::None)), _ => todo!("non-color given to builtin function `blue()`") diff --git a/src/builtin/meta.rs b/src/builtin/meta.rs index afc8d31..053abd8 100644 --- a/src/builtin/meta.rs +++ b/src/builtin/meta.rs @@ -6,7 +6,7 @@ use crate::units::Unit; use crate::value::Value; pub(crate) fn register(f: &mut BTreeMap) { - decl!(f "if", |args| { + decl!(f "if", |args, _| { let cond: &Value = arg!(args, 0, "condition"); let if_true = arg!(args, 1, "if-true").clone(); let if_false = arg!(args, 2, "if-false").clone(); @@ -16,7 +16,7 @@ pub(crate) fn register(f: &mut BTreeMap) { Some(if_false) } }); - decl!(f "feature-exists", |args| { + decl!(f "feature-exists", |args, _| { let feature: &Value = arg!(args, 0, "feature"); match feature.clone().unquote().to_string().as_str() { // A local variable will shadow a global variable unless @@ -37,7 +37,7 @@ pub(crate) fn register(f: &mut BTreeMap) { _ => Some(Value::False), } }); - decl!(f "unit", |args| { + decl!(f "unit", |args, _| { let number = arg!(args, 0, "number"); let unit = match number { Value::Dimension(_, u) => u.to_string(), @@ -45,11 +45,11 @@ pub(crate) fn register(f: &mut BTreeMap) { }; Some(Value::Ident(unit, QuoteKind::Double)) }); - decl!(f "type-of", |args| { + decl!(f "type-of", |args, _| { let value = arg!(args, 0, "value"); Some(Value::Ident(value.kind().to_owned(), QuoteKind::None)) }); - decl!(f "unitless", |args| { + decl!(f "unitless", |args, _| { let number = arg!(args, 0, "number"); match number { Value::Dimension(_, Unit::None) => Some(Value::True), @@ -57,8 +57,20 @@ pub(crate) fn register(f: &mut BTreeMap) { _ => Some(Value::True) } }); - decl!(f "inspect", |args| { + decl!(f "inspect", |args, _| { let value = arg!(args, 0, "value"); Some(Value::Ident(value.to_string(), QuoteKind::None)) }); + decl!(f "variable-exists", |args, scope| { + let value = arg!(args, 0, "name"); + Some(Value::bool(scope.var_exists(&value.to_string()))) + }); + decl!(f "mixin-exists", |args, scope| { + let value = arg!(args, 0, "name"); + Some(Value::bool(scope.mixin_exists(&value.to_string()))) + }); + decl!(f "function-exists", |args, scope| { + let value = arg!(args, 0, "name"); + Some(Value::bool(scope.fn_exists(&value.to_string()))) + }); } diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index fc31040..f9da88e 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -2,6 +2,7 @@ use lazy_static::lazy_static; use std::collections::BTreeMap; use crate::args::CallArgs; +use crate::common::Scope; use crate::value::Value; #[macro_use] @@ -15,7 +16,7 @@ mod meta; mod selector; mod string; -pub(crate) type Builtin = Box Option + Send + Sync>; +pub(crate) type Builtin = Box Option + Send + Sync>; lazy_static! { pub(crate) static ref GLOBAL_FUNCTIONS: BTreeMap = { diff --git a/src/value/parse.rs b/src/value/parse.rs index eed8b23..603b967 100644 --- a/src/value/parse.rs +++ b/src/value/parse.rs @@ -200,7 +200,7 @@ impl Value { let func = match scope.get_fn(&s) { Ok(f) => f, Err(_) => match GLOBAL_FUNCTIONS.get(&s) { - Some(f) => return f(&args), + Some(f) => return f(&args, scope), None => todo!("called undefined function"), }, }; diff --git a/tests/meta.rs b/tests/meta.rs index 518393e..d5ac76e 100644 --- a/tests/meta.rs +++ b/tests/meta.rs @@ -229,6 +229,51 @@ test!( "a {\n color: inspect(null)\n}\n", "a {\n color: null;\n}\n" ); +test!( + variable_does_exist, + "$a: red; a {\n color: variable-exists(a)\n}\n", + "a {\n color: true;\n}\n" +); +test!( + variable_does_not_exist, + "a {\n color: variable-exists(a)\n}\n", + "a {\n color: false;\n}\n" +); +test!( + variable_exists_named, + "$a: red; a {\n color: variable-exists($name: a)\n}\n", + "a {\n color: true;\n}\n" +); +test!( + mixin_does_exist, + "@mixin a{} a {\n color: mixin-exists(a)\n}\n", + "a {\n color: true;\n}\n" +); +test!( + mixin_does_not_exist, + "a {\n color: mixin-exists(a)\n}\n", + "a {\n color: false;\n}\n" +); +test!( + mixin_exists_named, + "@mixin a{} a {\n color: mixin-exists($name: a)\n}\n", + "a {\n color: true;\n}\n" +); +test!( + function_does_exist, + "@function a(){} a {\n color: function-exists(a)\n}\n", + "a {\n color: true;\n}\n" +); +test!( + function_does_not_exist, + "a {\n color: function-exists(a)\n}\n", + "a {\n color: false;\n}\n" +); +test!( + function_exists_named, + "@function a(){} a {\n color: function-exists($name: a)\n}\n", + "a {\n color: true;\n}\n" +); // test!( // inspect_empty_list, // "a {\n color: inspect(())\n}\n",