diff --git a/src/builtin/functions/math.rs b/src/builtin/functions/math.rs index 94104ec..42cce7d 100644 --- a/src/builtin/functions/math.rs +++ b/src/builtin/functions/math.rs @@ -13,7 +13,7 @@ use crate::{ value::{Number, Value}, }; -fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let num = match args.get_err(0, "number")? { Value::Dimension(n, Unit::None, _) => n * Number::from(100), @@ -38,7 +38,7 @@ fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult Ok(Value::Dimension(num, Unit::Percent, true)) } -fn round(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn round(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "number")? { Value::Dimension(n, u, _) => Ok(Value::Dimension(n.round(), u, true)), @@ -50,7 +50,7 @@ fn round(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } -fn ceil(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn ceil(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "number")? { Value::Dimension(n, u, _) => Ok(Value::Dimension(n.ceil(), u, true)), @@ -62,7 +62,7 @@ fn ceil(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } -fn floor(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn floor(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "number")? { Value::Dimension(n, u, _) => Ok(Value::Dimension(n.floor(), u, true)), @@ -74,7 +74,7 @@ fn floor(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } -fn abs(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn abs(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "number")? { Value::Dimension(n, u, _) => Ok(Value::Dimension(n.abs(), u, true)), @@ -86,7 +86,7 @@ fn abs(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } -fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let unit1 = match args.get_err(0, "number1")? { Value::Dimension(_, u, _) => u, @@ -114,7 +114,7 @@ fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult // TODO: write tests for this #[cfg(feature = "random")] -fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let limit = match args.default_arg(0, "limit", Value::Null)? { Value::Dimension(n, ..) => n, @@ -170,7 +170,7 @@ fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { )) } -fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.min_args(1)?; let span = args.span(); let mut nums = args @@ -208,7 +208,7 @@ fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(Value::Dimension(min.0, min.1, true)) } -fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.min_args(1)?; let span = args.span(); let mut nums = args diff --git a/src/builtin/functions/meta.rs b/src/builtin/functions/meta.rs index 9255de7..c4b9b4f 100644 --- a/src/builtin/functions/meta.rs +++ b/src/builtin/functions/meta.rs @@ -50,7 +50,7 @@ fn feature_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn unit(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let unit = match args.get_err(0, "number")? { Value::Dimension(_, u, _) => u.to_string(), @@ -71,7 +71,7 @@ fn type_of(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(Value::String(value.kind().to_owned(), QuoteKind::None)) } -fn unitless(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn unitless(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; #[allow(clippy::match_same_arms)] Ok(match args.get_err(0, "number")? { diff --git a/src/builtin/functions/mod.rs b/src/builtin/functions/mod.rs index 4ec4354..1c28974 100644 --- a/src/builtin/functions/mod.rs +++ b/src/builtin/functions/mod.rs @@ -13,13 +13,13 @@ use crate::{args::CallArgs, error::SassResult, parse::Parser, value::Value}; #[macro_use] mod macros; -mod color; -mod list; -mod map; -mod math; -mod meta; -mod selector; -mod string; +pub mod color; +pub mod list; +pub mod map; +pub mod math; +pub mod meta; +pub mod selector; +pub mod string; pub(crate) type GlobalFunctionMap = HashMap<&'static str, Builtin>; diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index 18bd1bf..56375bd 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -1,5 +1,4 @@ mod functions; -mod modules; +pub(crate) mod modules; -pub(crate) use functions::{Builtin, GLOBAL_FUNCTIONS}; -pub(crate) use modules::*; +pub(crate) use functions::{math, meta, Builtin, GLOBAL_FUNCTIONS}; diff --git a/src/builtin/modules/color.rs b/src/builtin/modules/color.rs index c3c3a05..47d58dc 100644 --- a/src/builtin/modules/color.rs +++ b/src/builtin/modules/color.rs @@ -1,3 +1,5 @@ -use crate::builtin::Module; +use crate::{ + args::CallArgs, builtin::modules::Module, error::SassResult, parse::Parser, value::Value, +}; pub(crate) fn declare(_f: &mut Module) {} diff --git a/src/builtin/modules/list.rs b/src/builtin/modules/list.rs index c3c3a05..47d58dc 100644 --- a/src/builtin/modules/list.rs +++ b/src/builtin/modules/list.rs @@ -1,3 +1,5 @@ -use crate::builtin::Module; +use crate::{ + args::CallArgs, builtin::modules::Module, error::SassResult, parse::Parser, value::Value, +}; pub(crate) fn declare(_f: &mut Module) {} diff --git a/src/builtin/modules/map.rs b/src/builtin/modules/map.rs index c3c3a05..47d58dc 100644 --- a/src/builtin/modules/map.rs +++ b/src/builtin/modules/map.rs @@ -1,3 +1,5 @@ -use crate::builtin::Module; +use crate::{ + args::CallArgs, builtin::modules::Module, error::SassResult, parse::Parser, value::Value, +}; pub(crate) fn declare(_f: &mut Module) {} diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index c3c3a05..e3d1421 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -1,3 +1,88 @@ -use crate::builtin::Module; +use crate::{ + args::CallArgs, + builtin::{ + math::{abs, ceil, comparable, floor, max, min, percentage, round}, + meta::{unit, unitless}, + modules::Module, + }, + error::SassResult, + parse::Parser, + value::Value, +}; -pub(crate) fn declare(_f: &mut Module) {} +#[cfg(feature = "random")] +use crate::builtin::math::random; + +fn clamp(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(3)?; + todo!() +} + +fn hypot(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + todo!() +} + +fn log(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(2)?; + todo!() +} + +fn pow(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(2)?; + todo!() +} + +fn sqrt(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(1)?; + todo!() +} + +fn cos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(1)?; + todo!() +} + +fn sin(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(1)?; + todo!() +} + +fn tan(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(1)?; + todo!() +} + +fn acos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(1)?; + todo!() +} + +fn asin(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(1)?; + todo!() +} + +fn atan(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(1)?; + todo!() +} + +fn atan2(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(2)?; + todo!() +} + +pub(crate) fn declare(f: &mut Module) { + f.insert_builtin("ceil", ceil); + f.insert_builtin("floor", floor); + f.insert_builtin("max", max); + f.insert_builtin("min", min); + f.insert_builtin("round", round); + f.insert_builtin("abs", abs); + f.insert_builtin("compatible", comparable); + f.insert_builtin("is-unitless", unitless); + f.insert_builtin("unit", unit); + f.insert_builtin("percentage", percentage); + #[cfg(feature = "random")] + f.insert_builtin("random", random); +} diff --git a/src/builtin/modules/meta.rs b/src/builtin/modules/meta.rs index c3c3a05..47d58dc 100644 --- a/src/builtin/modules/meta.rs +++ b/src/builtin/modules/meta.rs @@ -1,3 +1,5 @@ -use crate::builtin::Module; +use crate::{ + args::CallArgs, builtin::modules::Module, error::SassResult, parse::Parser, value::Value, +}; pub(crate) fn declare(_f: &mut Module) {} diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index dc248ec..e527df6 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -1,8 +1,16 @@ +#![allow(unused_imports, unused_variables, dead_code, unused_mut)] + use std::collections::BTreeMap; +use codemap::Spanned; + use crate::{ + args::CallArgs, atrule::Mixin, + builtin::Builtin, common::Identifier, + error::SassResult, + parse::Parser, value::{SassFunction, Value}, }; @@ -21,6 +29,29 @@ pub(crate) struct Module { functions: BTreeMap, } +impl Module { + pub 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 get_fn(&self, name: Identifier) -> Option { + self.functions.get(&name).cloned() + } + + pub fn insert_builtin( + &mut self, + name: &'static str, + function: fn(CallArgs, &mut Parser<'_>) -> SassResult, + ) { + let ident = name.into(); + self.functions + .insert(ident, SassFunction::Builtin(Builtin::new(function), ident)); + } +} + pub(crate) fn declare_module_color() -> Module { let mut module = Module::default(); color::declare(&mut module); diff --git a/src/builtin/modules/selector.rs b/src/builtin/modules/selector.rs index c3c3a05..47d58dc 100644 --- a/src/builtin/modules/selector.rs +++ b/src/builtin/modules/selector.rs @@ -1,3 +1,5 @@ -use crate::builtin::Module; +use crate::{ + args::CallArgs, builtin::modules::Module, error::SassResult, parse::Parser, value::Value, +}; pub(crate) fn declare(_f: &mut Module) {} diff --git a/src/builtin/modules/string.rs b/src/builtin/modules/string.rs index c3c3a05..47d58dc 100644 --- a/src/builtin/modules/string.rs +++ b/src/builtin/modules/string.rs @@ -1,3 +1,5 @@ -use crate::builtin::Module; +use crate::{ + args::CallArgs, builtin::modules::Module, error::SassResult, parse::Parser, value::Value, +}; pub(crate) fn declare(_f: &mut Module) {} diff --git a/src/parse/mod.rs b/src/parse/mod.rs index b0ea782..a534f9e 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -9,7 +9,7 @@ use crate::{ media::MediaRule, AtRuleKind, Content, SupportsRule, UnknownAtRule, }, - builtin::*, + builtin::modules::*, error::SassResult, scope::{Scope, Scopes}, selector::{ diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 9732ca7..179e609 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -207,6 +207,53 @@ impl<'a> Parser<'a> { .parse_value(in_paren) } + fn parse_module_item( + &mut self, + module: &str, + mut module_span: Span, + ) -> SassResult> { + Ok(IntermediateValue::Value( + if matches!(self.toks.peek(), Some(Token { kind: '$', .. })) { + let var = self + .parse_identifier_no_interpolation(false)? + .map_node(|i| i.into()); + + module_span = module_span.merge(var.span); + + let value = self + .modules + .get(module) + .ok_or(("todo: module dne", module_span))? + .get_var(var)?; + HigherIntermediateValue::Literal(value.clone()) + } else { + let fn_name = self + .parse_identifier_no_interpolation(false)? + .map_node(|i| i.into()); + + let function = self + .modules + .get(module) + .ok_or(("todo: module dne", module_span))? + .get_fn(fn_name.node) + .ok_or(("todo: fn dne", fn_name.span))?; + + if !matches!(self.toks.next(), Some(Token { kind: '(', .. })) { + todo!() + } + + let call_args = self.parse_call_args()?; + + HigherIntermediateValue::Function(function, call_args) + }, + ) + .span(module_span)) + } + + // fn parse_module_fn_call(&mut self, name: &str) -> SassResult> { + + // } + fn parse_ident_value(&mut self) -> SassResult> { let Spanned { node: mut s, span } = self.parse_identifier()?; @@ -228,83 +275,92 @@ impl<'a> Parser<'a> { }); } - if let Some(Token { kind: '(', .. }) = self.toks.peek() { - self.toks.next(); + match self.toks.peek() { + Some(Token { kind: '(', .. }) => { + self.toks.next(); - if lower == "min" { - match self.try_parse_min_max("min", true)? { - Some(val) => { - self.toks.truncate_iterator_to_cursor(); - self.toks.next(); - return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal( - Value::String(val, QuoteKind::None), - )) - .span(span)); - } - None => { - self.toks.reset_cursor(); - } - } - } else if lower == "max" { - match self.try_parse_min_max("max", true)? { - Some(val) => { - self.toks.truncate_iterator_to_cursor(); - self.toks.next(); - return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal( - Value::String(val, QuoteKind::None), - )) - .span(span)); - } - None => { - self.toks.reset_cursor(); - } - } - } - - let as_ident = Identifier::from(&s); - let func = match self.scopes.get_fn( - Spanned { - node: as_ident, - span, - }, - self.global_scope, - ) { - Some(f) => f, - None => { - if let Some(f) = GLOBAL_FUNCTIONS.get(as_ident.as_str()) { - return Ok(IntermediateValue::Value(HigherIntermediateValue::Function( - SassFunction::Builtin(f.clone(), as_ident), - self.parse_call_args()?, - )) - .span(span)); - } else { - // check for special cased CSS functions - match lower.as_str() { - "calc" | "element" | "expression" => { - s = lower; - self.parse_calc_args(&mut s)?; - } - "url" => match self.try_parse_url()? { - Some(val) => s = val, - None => s.push_str(&self.parse_call_args()?.to_css_string()?), - }, - _ => s.push_str(&self.parse_call_args()?.to_css_string()?), + if lower == "min" { + match self.try_parse_min_max("min", true)? { + Some(val) => { + self.toks.truncate_iterator_to_cursor(); + self.toks.next(); + return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal( + Value::String(val, QuoteKind::None), + )) + .span(span)); + } + None => { + self.toks.reset_cursor(); + } + } + } else if lower == "max" { + match self.try_parse_min_max("max", true)? { + Some(val) => { + self.toks.truncate_iterator_to_cursor(); + self.toks.next(); + return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal( + Value::String(val, QuoteKind::None), + )) + .span(span)); + } + None => { + self.toks.reset_cursor(); } - - return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal( - Value::String(s, QuoteKind::None), - )) - .span(span)); } } - }; - let call_args = self.parse_call_args()?; - return Ok(IntermediateValue::Value(HigherIntermediateValue::Function( - SassFunction::UserDefined(Box::new(func), as_ident), - call_args, - )) - .span(span)); + let as_ident = Identifier::from(&s); + let func = match self.scopes.get_fn( + Spanned { + node: as_ident, + span, + }, + self.global_scope, + ) { + Some(f) => f, + None => { + if let Some(f) = GLOBAL_FUNCTIONS.get(as_ident.as_str()) { + return Ok(IntermediateValue::Value( + HigherIntermediateValue::Function( + SassFunction::Builtin(f.clone(), as_ident), + self.parse_call_args()?, + ), + ) + .span(span)); + } else { + // check for special cased CSS functions + match lower.as_str() { + "calc" | "element" | "expression" => { + s = lower; + self.parse_calc_args(&mut s)?; + } + "url" => match self.try_parse_url()? { + Some(val) => s = val, + None => s.push_str(&self.parse_call_args()?.to_css_string()?), + }, + _ => s.push_str(&self.parse_call_args()?.to_css_string()?), + } + + return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal( + Value::String(s, QuoteKind::None), + )) + .span(span)); + } + } + }; + + let call_args = self.parse_call_args()?; + return Ok(IntermediateValue::Value(HigherIntermediateValue::Function( + SassFunction::UserDefined(Box::new(func), as_ident), + call_args, + )) + .span(span)); + } + Some(Token { kind: '.', .. }) => { + self.toks.next(); + return self.parse_module_item(&s, span); + } + _ => {} } // check for named colors