box function references

This commit is contained in:
Connor Skees 2022-12-27 15:16:07 -05:00
parent add1698180
commit e25a9f7b12
9 changed files with 67 additions and 112 deletions

View File

@ -2,32 +2,13 @@ use crate::{builtin::builtin_imports::*, evaluate::div};
pub(crate) fn percentage(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn percentage(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
let num = match args.get_err(0, "number")? { let num = args
Value::Dimension(SassNumber { .get_err(0, "number")?
num: n, .assert_number_with_name("number", args.span)?;
unit: Unit::None, num.assert_no_units("number", args.span)?;
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())
}
};
Ok(Value::Dimension(SassNumber { Ok(Value::Dimension(SassNumber {
num, num: Number(num.num.0 * 100.0),
unit: Unit::Percent, unit: Unit::Percent,
as_slash: None, 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<Value> { pub(crate) fn abs(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "number")? { let mut num = args
Value::Dimension(SassNumber { .get_err(0, "number")?
num: n, .assert_number_with_name("number", args.span())?;
unit: u,
as_slash: _, num.num = num.num.abs();
}) => Ok(Value::Dimension(SassNumber {
num: (n.abs()), Ok(Value::Dimension(num))
unit: u,
as_slash: None,
})),
v => Err((
format!("$number: {} is not a number.", v.inspect(args.span())?),
args.span(),
)
.into()),
}
} }
pub(crate) fn comparable(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> { pub(crate) fn comparable(mut args: ArgumentResult, visitor: &mut Visitor) -> SassResult<Value> {
args.max_args(2)?; args.max_args(2)?;
let unit1 = match args.get_err(0, "number1")? { let unit1 = args
Value::Dimension(SassNumber { .get_err(0, "number1")?
num: _, .assert_number_with_name("number1", args.span())?
unit: u, .unit;
as_slash: _,
}) => u, let unit2 = args
v => { .get_err(1, "number2")?
return Err(( .assert_number_with_name("number2", args.span())?
format!("$number1: {} is not a number.", v.inspect(args.span())?), .unit;
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())
}
};
Ok(Value::bool(unit1.comparable(&unit2))) Ok(Value::bool(unit1.comparable(&unit2)))
} }

View File

@ -295,7 +295,7 @@ pub(crate) fn get_function(mut args: ArgumentResult, visitor: &mut Visitor) -> S
}; };
match func { 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()), 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(); 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)] #[allow(clippy::needless_pass_by_value)]

View File

@ -100,7 +100,7 @@ pub(crate) fn str_slice(mut args: ArgumentResult, visitor: &mut Visitor) -> Sass
} else if start > 0 { } else if start > 0 {
(start as usize).min(str_len + 1) (start as usize).min(str_len + 1)
} else { } else {
(start + str_len as i32 + 1).max(1) as usize (start + str_len as i64 + 1).max(1) as usize
}; };
let end = args 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)?; let mut end = end.num().assert_int(span)?;
if end < 0 { 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); 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 { } else if index_int == 0 {
insert(0, s1, &substr) insert(0, s1, &substr)
} else { } 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) insert(idx, s1, &substr)
}; };

View File

@ -343,7 +343,7 @@ impl Module {
.map(|(key, value)| { .map(|(key, value)| {
( (
Value::String(key.to_string(), QuoteKind::Quoted).span(span), Value::String(key.to_string(), QuoteKind::Quoted).span(span),
Value::FunctionRef(value), Value::FunctionRef(Box::new(value)),
) )
}) })
.collect::<Vec<_>>(), .collect::<Vec<_>>(),

View File

@ -134,10 +134,10 @@ impl Environment {
Ok(v) => Ok(v), Ok(v) => Ok(v),
Err(e) => { Err(e) => {
if let Some(v) = self.get_variable_from_global_modules(name.node) { if let Some(v) = self.get_variable_from_global_modules(name.node) {
return Ok(v); Ok(v)
} else {
Err(e)
} }
Err(e)
} }
} }
} }

View File

@ -64,6 +64,16 @@ impl UserDefinedCallable for AstFunctionDecl {
} }
} }
impl UserDefinedCallable for Arc<AstFunctionDecl> {
fn name(&self) -> Identifier {
self.name.node
}
fn arguments(&self) -> &ArgumentDeclaration {
&self.arguments
}
}
impl UserDefinedCallable for AstMixin { impl UserDefinedCallable for AstMixin {
fn name(&self) -> Identifier { fn name(&self) -> Identifier {
self.name self.name
@ -1112,7 +1122,7 @@ impl<'a> Visitor<'a> {
// todo: independency // todo: independency
let func = SassFunction::UserDefined(UserDefinedFunction { let func = SassFunction::UserDefined(UserDefinedFunction {
function: Box::new(fn_decl), function: Arc::new(fn_decl),
name, name,
env: self.env.new_closure(), env: self.env.new_closure(),
}); });
@ -1701,6 +1711,14 @@ impl<'a> Visitor<'a> {
let direction = if from > to { -1 } else { 1 }; 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 { if !for_stmt.is_exclusive {
to += direction; to += direction;
} }
@ -2200,23 +2218,17 @@ impl<'a> Visitor<'a> {
Ok(self.without_slash(val)) Ok(self.without_slash(val))
} }
SassFunction::UserDefined(UserDefinedFunction { function, env, .. }) => self SassFunction::UserDefined(UserDefinedFunction { function, env, .. }) => self
.run_user_defined_callable( .run_user_defined_callable(arguments, function, &env, span, |function, visitor| {
arguments, for stmt in function.children.clone() {
*function, let result = visitor.visit_stmt(stmt)?;
&env,
span,
|function, visitor| {
for stmt in function.children {
let result = visitor.visit_stmt(stmt)?;
if let Some(val) = result { if let Some(val) = result {
return Ok(val); return Ok(val);
}
} }
}
Err(("Function finished without @return.", span).into()) Err(("Function finished without @return.", span).into())
}, }),
),
SassFunction::Plain { name } => { SassFunction::Plain { name } => {
let arguments = match arguments { let arguments = match arguments {
MaybeEvaledArguments::Invocation(args) => args, MaybeEvaledArguments::Invocation(args) => args,

View File

@ -40,8 +40,7 @@ pub(crate) enum Value {
Map(SassMap), Map(SassMap),
ArgList(ArgList), ArgList(ArgList),
/// Returned by `get-function()` /// Returned by `get-function()`
// todo: benchmark boxing this (function refs are infrequent) FunctionRef(Box<SassFunction>),
FunctionRef(SassFunction),
Calculation(SassCalculation), Calculation(SassCalculation),
} }

View File

@ -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() (a - b).abs() <= epsilon() && (a * inverse_epsilon()).round() == (b * inverse_epsilon()).round()
} }
pub(crate) fn fuzzy_as_int(num: f64) -> Option<i32> { pub(crate) fn fuzzy_as_int(num: f64) -> Option<i64> {
if !num.is_finite() { if !num.is_finite() {
return None; return None;
} }
@ -55,7 +55,7 @@ pub(crate) fn fuzzy_as_int(num: f64) -> Option<i32> {
let rounded = num.round(); let rounded = num.round();
if fuzzy_equals(num, rounded) { if fuzzy_equals(num, rounded) {
Some(rounded as i32) Some(rounded as i64)
} else { } else {
None None
} }
@ -110,14 +110,14 @@ impl Number {
self.0.is_sign_negative() && !self.is_zero() self.0.is_sign_negative() && !self.is_zero()
} }
pub fn assert_int(self, span: Span) -> SassResult<i32> { pub fn assert_int(self, span: Span) -> SassResult<i64> {
match fuzzy_as_int(self.0) { match fuzzy_as_int(self.0) {
Some(i) => Ok(i), Some(i) => Ok(i),
None => Err((format!("{} is not an int.", self.0), span).into()), None => Err((format!("{} is not an int.", self.0), span).into()),
} }
} }
pub fn assert_int_with_name(self, name: &'static str, span: Span) -> SassResult<i32> { pub fn assert_int_with_name(self, name: &'static str, span: Span) -> SassResult<i64> {
match fuzzy_as_int(self.0) { match fuzzy_as_int(self.0) {
Some(i) => Ok(i), Some(i) => Ok(i),
None => Err(( None => Err((

View File

@ -3,24 +3,15 @@
//! Sass functions can be either user-defined or builtin. //! Sass functions can be either user-defined or builtin.
//! //!
//! User-defined functions are those that have been implemented in Sass //! 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. //! for more information.
//! //!
//! Builtin functions are those that have been implemented in rust and are //! Builtin functions are those that have been implemented in rust and are
//! in the global scope. //! in the global scope.
use std::fmt; use std::{fmt, sync::Arc};
// use codemap::Spanned; use crate::{ast::AstFunctionDecl, builtin::Builtin, common::Identifier, evaluate::Environment};
use crate::{
// error::SassResult,
ast::AstFunctionDecl,
// value::Value,
builtin::Builtin,
common::Identifier,
evaluate::Environment,
};
/// A Sass function /// A Sass function
/// ///
@ -39,7 +30,7 @@ pub(crate) enum SassFunction {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct UserDefinedFunction { pub(crate) struct UserDefinedFunction {
pub function: Box<AstFunctionDecl>, pub function: Arc<AstFunctionDecl>,
pub name: Identifier, pub name: Identifier,
pub env: Environment, pub env: Environment,
} }