diff --git a/src/builtin/functions/math.rs b/src/builtin/functions/math.rs index 7e6a0d7..ec17da2 100644 --- a/src/builtin/functions/math.rs +++ b/src/builtin/functions/math.rs @@ -2,32 +2,13 @@ use crate::{builtin::builtin_imports::*, evaluate::div}; pub(crate) fn percentage(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - let num = match args.get_err(0, "number")? { - Value::Dimension(SassNumber { - num: n, - unit: Unit::None, - as_slash: _, - }) => n * Number::from(100), - v @ Value::Dimension(SassNumber { .. }) => { - return Err(( - format!( - "$number: Expected {} to have no units.", - v.inspect(args.span())? - ), - args.span(), - ) - .into()) - } - v => { - return Err(( - format!("$number: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }; + let num = args + .get_err(0, "number")? + .assert_number_with_name("number", args.span)?; + num.assert_no_units("number", args.span)?; + Ok(Value::Dimension(SassNumber { - num, + num: Number(num.num.0 * 100.0), unit: Unit::Percent, as_slash: None, })) @@ -107,54 +88,26 @@ pub(crate) fn floor(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResu pub(crate) fn abs(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(1)?; - match args.get_err(0, "number")? { - Value::Dimension(SassNumber { - num: n, - unit: u, - as_slash: _, - }) => Ok(Value::Dimension(SassNumber { - num: (n.abs()), - unit: u, - as_slash: None, - })), - v => Err(( - format!("$number: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()), - } + let mut num = args + .get_err(0, "number")? + .assert_number_with_name("number", args.span())?; + + num.num = num.num.abs(); + + Ok(Value::Dimension(num)) } pub(crate) fn comparable(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult { args.max_args(2)?; - let unit1 = match args.get_err(0, "number1")? { - Value::Dimension(SassNumber { - num: _, - unit: u, - as_slash: _, - }) => u, - v => { - return Err(( - format!("$number1: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }; - let unit2 = match args.get_err(1, "number2")? { - Value::Dimension(SassNumber { - num: _, - unit: u, - as_slash: _, - }) => u, - v => { - return Err(( - format!("$number2: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }; + let unit1 = args + .get_err(0, "number1")? + .assert_number_with_name("number1", args.span())? + .unit; + + let unit2 = args + .get_err(1, "number2")? + .assert_number_with_name("number2", args.span())? + .unit; Ok(Value::bool(unit1.comparable(&unit2))) } diff --git a/src/builtin/functions/meta.rs b/src/builtin/functions/meta.rs index 4917e30..ec42481 100644 --- a/src/builtin/functions/meta.rs +++ b/src/builtin/functions/meta.rs @@ -295,7 +295,7 @@ pub(crate) fn get_function(mut args: ArgumentResult, visitor: &mut Visitor) -> S }; match func { - Some(func) => Ok(Value::FunctionRef(func)), + Some(func) => Ok(Value::FunctionRef(Box::new(func))), None => Err((format!("Function not found: {}", name), args.span()).into()), } } @@ -318,7 +318,7 @@ pub(crate) fn call(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResul args.remove_positional(0).unwrap(); - visitor.run_function_callable_with_maybe_evaled(func, MaybeEvaledArguments::Evaled(args), span) + visitor.run_function_callable_with_maybe_evaled(*func, MaybeEvaledArguments::Evaled(args), span) } #[allow(clippy::needless_pass_by_value)] diff --git a/src/builtin/functions/string.rs b/src/builtin/functions/string.rs index 09baead..f0daa45 100644 --- a/src/builtin/functions/string.rs +++ b/src/builtin/functions/string.rs @@ -100,7 +100,7 @@ pub(crate) fn str_slice(mut args: ArgumentResult, visitor: &mut Visitor) -> Sass } else if start > 0 { (start as usize).min(str_len + 1) } else { - (start + str_len as i32 + 1).max(1) as usize + (start + str_len as i64 + 1).max(1) as usize }; let end = args @@ -120,7 +120,7 @@ pub(crate) fn str_slice(mut args: ArgumentResult, visitor: &mut Visitor) -> Sass let mut end = end.num().assert_int(span)?; if end < 0 { - end += str_len as i32 + 1; + end += str_len as i64 + 1; } let end = (end.max(0) as usize).min(str_len + 1); @@ -232,7 +232,7 @@ pub(crate) fn str_insert(mut args: ArgumentResult, visitor: &mut Visitor) -> Sas } else if index_int == 0 { insert(0, s1, &substr) } else { - let idx = (len as i32 + index_int + 1).max(0) as usize; + let idx = (len as i64 + index_int + 1).max(0) as usize; insert(idx, s1, &substr) }; diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index 2a20063..fee0852 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -343,7 +343,7 @@ impl Module { .map(|(key, value)| { ( Value::String(key.to_string(), QuoteKind::Quoted).span(span), - Value::FunctionRef(value), + Value::FunctionRef(Box::new(value)), ) }) .collect::>(), diff --git a/src/evaluate/env.rs b/src/evaluate/env.rs index babfebc..ed4f044 100644 --- a/src/evaluate/env.rs +++ b/src/evaluate/env.rs @@ -134,10 +134,10 @@ impl Environment { Ok(v) => Ok(v), Err(e) => { if let Some(v) = self.get_variable_from_global_modules(name.node) { - return Ok(v); + Ok(v) + } else { + Err(e) } - - Err(e) } } } diff --git a/src/evaluate/visitor.rs b/src/evaluate/visitor.rs index eed1043..ef78df8 100644 --- a/src/evaluate/visitor.rs +++ b/src/evaluate/visitor.rs @@ -64,6 +64,16 @@ impl UserDefinedCallable for AstFunctionDecl { } } +impl UserDefinedCallable for Arc { + fn name(&self) -> Identifier { + self.name.node + } + + fn arguments(&self) -> &ArgumentDeclaration { + &self.arguments + } +} + impl UserDefinedCallable for AstMixin { fn name(&self) -> Identifier { self.name @@ -1112,7 +1122,7 @@ impl<'a> Visitor<'a> { // todo: independency let func = SassFunction::UserDefined(UserDefinedFunction { - function: Box::new(fn_decl), + function: Arc::new(fn_decl), name, env: self.env.new_closure(), }); @@ -1701,6 +1711,14 @@ impl<'a> Visitor<'a> { let direction = if from > to { -1 } else { 1 }; + if to == i64::MAX || to == i64::MIN { + return Err(( + "@for loop upper bound exceeds valid integer representation (i64::MAX)", + to_span, + ) + .into()); + } + if !for_stmt.is_exclusive { to += direction; } @@ -2200,23 +2218,17 @@ impl<'a> Visitor<'a> { Ok(self.without_slash(val)) } SassFunction::UserDefined(UserDefinedFunction { function, env, .. }) => self - .run_user_defined_callable( - arguments, - *function, - &env, - span, - |function, visitor| { - for stmt in function.children { - let result = visitor.visit_stmt(stmt)?; + .run_user_defined_callable(arguments, function, &env, span, |function, visitor| { + for stmt in function.children.clone() { + let result = visitor.visit_stmt(stmt)?; - if let Some(val) = result { - return Ok(val); - } + if let Some(val) = result { + return Ok(val); } + } - Err(("Function finished without @return.", span).into()) - }, - ), + Err(("Function finished without @return.", span).into()) + }), SassFunction::Plain { name } => { let arguments = match arguments { MaybeEvaledArguments::Invocation(args) => args, diff --git a/src/value/mod.rs b/src/value/mod.rs index 26b0263..9a4ac01 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -40,8 +40,7 @@ pub(crate) enum Value { Map(SassMap), ArgList(ArgList), /// Returned by `get-function()` - // todo: benchmark boxing this (function refs are infrequent) - FunctionRef(SassFunction), + FunctionRef(Box), Calculation(SassCalculation), } diff --git a/src/value/number.rs b/src/value/number.rs index 1784a75..349a142 100644 --- a/src/value/number.rs +++ b/src/value/number.rs @@ -47,7 +47,7 @@ pub(crate) fn fuzzy_equals(a: f64, b: f64) -> bool { (a - b).abs() <= epsilon() && (a * inverse_epsilon()).round() == (b * inverse_epsilon()).round() } -pub(crate) fn fuzzy_as_int(num: f64) -> Option { +pub(crate) fn fuzzy_as_int(num: f64) -> Option { if !num.is_finite() { return None; } @@ -55,7 +55,7 @@ pub(crate) fn fuzzy_as_int(num: f64) -> Option { let rounded = num.round(); if fuzzy_equals(num, rounded) { - Some(rounded as i32) + Some(rounded as i64) } else { None } @@ -110,14 +110,14 @@ impl Number { self.0.is_sign_negative() && !self.is_zero() } - pub fn assert_int(self, span: Span) -> SassResult { + pub fn assert_int(self, span: Span) -> SassResult { match fuzzy_as_int(self.0) { Some(i) => Ok(i), None => Err((format!("{} is not an int.", self.0), span).into()), } } - pub fn assert_int_with_name(self, name: &'static str, span: Span) -> SassResult { + pub fn assert_int_with_name(self, name: &'static str, span: Span) -> SassResult { match fuzzy_as_int(self.0) { Some(i) => Ok(i), None => Err(( diff --git a/src/value/sass_function.rs b/src/value/sass_function.rs index 8da6673..e419f6b 100644 --- a/src/value/sass_function.rs +++ b/src/value/sass_function.rs @@ -3,24 +3,15 @@ //! Sass functions can be either user-defined or builtin. //! //! User-defined functions are those that have been implemented in Sass -//! using the @function rule. See the documentation of `crate::atrule::Function` +//! using the @function rule. See the documentation of [`crate::atrule::Function`] //! for more information. //! //! Builtin functions are those that have been implemented in rust and are //! in the global scope. -use std::fmt; +use std::{fmt, sync::Arc}; -// use codemap::Spanned; - -use crate::{ - // error::SassResult, - ast::AstFunctionDecl, - // value::Value, - builtin::Builtin, - common::Identifier, - evaluate::Environment, -}; +use crate::{ast::AstFunctionDecl, builtin::Builtin, common::Identifier, evaluate::Environment}; /// A Sass function /// @@ -39,7 +30,7 @@ pub(crate) enum SassFunction { #[derive(Debug, Clone)] pub(crate) struct UserDefinedFunction { - pub function: Box, + pub function: Arc, pub name: Identifier, pub env: Environment, }