implement aliased sass:math functions

This commit is contained in:
Connor Skees 2020-07-26 00:05:13 -04:00
parent c0631c75a0
commit 060641b86d
14 changed files with 285 additions and 102 deletions

View File

@ -13,7 +13,7 @@ use crate::{
value::{Number, Value}, value::{Number, Value},
}; };
fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { pub(crate) fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
let num = match args.get_err(0, "number")? { let num = match args.get_err(0, "number")? {
Value::Dimension(n, Unit::None, _) => n * Number::from(100), Value::Dimension(n, Unit::None, _) => n * Number::from(100),
@ -38,7 +38,7 @@ fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
Ok(Value::Dimension(num, Unit::Percent, true)) Ok(Value::Dimension(num, Unit::Percent, true))
} }
fn round(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { pub(crate) fn round(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "number")? { match args.get_err(0, "number")? {
Value::Dimension(n, u, _) => Ok(Value::Dimension(n.round(), u, true)), Value::Dimension(n, u, _) => Ok(Value::Dimension(n.round(), u, true)),
@ -50,7 +50,7 @@ fn round(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
} }
} }
fn ceil(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { pub(crate) fn ceil(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "number")? { match args.get_err(0, "number")? {
Value::Dimension(n, u, _) => Ok(Value::Dimension(n.ceil(), u, true)), Value::Dimension(n, u, _) => Ok(Value::Dimension(n.ceil(), u, true)),
@ -62,7 +62,7 @@ fn ceil(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
} }
} }
fn floor(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { pub(crate) fn floor(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "number")? { match args.get_err(0, "number")? {
Value::Dimension(n, u, _) => Ok(Value::Dimension(n.floor(), u, true)), Value::Dimension(n, u, _) => Ok(Value::Dimension(n.floor(), u, true)),
@ -74,7 +74,7 @@ fn floor(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
} }
} }
fn abs(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { pub(crate) fn abs(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match args.get_err(0, "number")? { match args.get_err(0, "number")? {
Value::Dimension(n, u, _) => Ok(Value::Dimension(n.abs(), u, true)), Value::Dimension(n, u, _) => Ok(Value::Dimension(n.abs(), u, true)),
@ -86,7 +86,7 @@ fn abs(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
} }
} }
fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { pub(crate) fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(2)?; args.max_args(2)?;
let unit1 = match args.get_err(0, "number1")? { let unit1 = match args.get_err(0, "number1")? {
Value::Dimension(_, u, _) => u, Value::Dimension(_, u, _) => u,
@ -114,7 +114,7 @@ fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value>
// TODO: write tests for this // TODO: write tests for this
#[cfg(feature = "random")] #[cfg(feature = "random")]
fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { pub(crate) fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
let limit = match args.default_arg(0, "limit", Value::Null)? { let limit = match args.default_arg(0, "limit", Value::Null)? {
Value::Dimension(n, ..) => n, Value::Dimension(n, ..) => n,
@ -170,7 +170,7 @@ fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
)) ))
} }
fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { pub(crate) fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.min_args(1)?; args.min_args(1)?;
let span = args.span(); let span = args.span();
let mut nums = args let mut nums = args
@ -208,7 +208,7 @@ fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
Ok(Value::Dimension(min.0, min.1, true)) Ok(Value::Dimension(min.0, min.1, true))
} }
fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { pub(crate) fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.min_args(1)?; args.min_args(1)?;
let span = args.span(); let span = args.span();
let mut nums = args let mut nums = args

View File

@ -50,7 +50,7 @@ fn feature_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Val
} }
} }
fn unit(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { pub(crate) fn unit(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
let unit = match args.get_err(0, "number")? { let unit = match args.get_err(0, "number")? {
Value::Dimension(_, u, _) => u.to_string(), Value::Dimension(_, u, _) => u.to_string(),
@ -71,7 +71,7 @@ fn type_of(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
Ok(Value::String(value.kind().to_owned(), QuoteKind::None)) Ok(Value::String(value.kind().to_owned(), QuoteKind::None))
} }
fn unitless(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { pub(crate) fn unitless(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
#[allow(clippy::match_same_arms)] #[allow(clippy::match_same_arms)]
Ok(match args.get_err(0, "number")? { Ok(match args.get_err(0, "number")? {

View File

@ -13,13 +13,13 @@ use crate::{args::CallArgs, error::SassResult, parse::Parser, value::Value};
#[macro_use] #[macro_use]
mod macros; mod macros;
mod color; pub mod color;
mod list; pub mod list;
mod map; pub mod map;
mod math; pub mod math;
mod meta; pub mod meta;
mod selector; pub mod selector;
mod string; pub mod string;
pub(crate) type GlobalFunctionMap = HashMap<&'static str, Builtin>; pub(crate) type GlobalFunctionMap = HashMap<&'static str, Builtin>;

View File

@ -1,5 +1,4 @@
mod functions; mod functions;
mod modules; pub(crate) mod modules;
pub(crate) use functions::{Builtin, GLOBAL_FUNCTIONS}; pub(crate) use functions::{math, meta, Builtin, GLOBAL_FUNCTIONS};
pub(crate) use modules::*;

View File

@ -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) {} pub(crate) fn declare(_f: &mut Module) {}

View File

@ -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) {} pub(crate) fn declare(_f: &mut Module) {}

View File

@ -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) {} pub(crate) fn declare(_f: &mut Module) {}

View File

@ -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<Value> {
args.max_args(3)?;
todo!()
}
fn hypot(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
todo!()
}
fn log(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(2)?;
todo!()
}
fn pow(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(2)?;
todo!()
}
fn sqrt(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?;
todo!()
}
fn cos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?;
todo!()
}
fn sin(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?;
todo!()
}
fn tan(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?;
todo!()
}
fn acos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?;
todo!()
}
fn asin(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?;
todo!()
}
fn atan(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?;
todo!()
}
fn atan2(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
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);
}

View File

@ -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) {} pub(crate) fn declare(_f: &mut Module) {}

View File

@ -1,8 +1,16 @@
#![allow(unused_imports, unused_variables, dead_code, unused_mut)]
use std::collections::BTreeMap; use std::collections::BTreeMap;
use codemap::Spanned;
use crate::{ use crate::{
args::CallArgs,
atrule::Mixin, atrule::Mixin,
builtin::Builtin,
common::Identifier, common::Identifier,
error::SassResult,
parse::Parser,
value::{SassFunction, Value}, value::{SassFunction, Value},
}; };
@ -21,6 +29,29 @@ pub(crate) struct Module {
functions: BTreeMap<Identifier, SassFunction>, functions: BTreeMap<Identifier, SassFunction>,
} }
impl Module {
pub fn get_var(&self, name: Spanned<Identifier>) -> 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<SassFunction> {
self.functions.get(&name).cloned()
}
pub fn insert_builtin(
&mut self,
name: &'static str,
function: fn(CallArgs, &mut Parser<'_>) -> SassResult<Value>,
) {
let ident = name.into();
self.functions
.insert(ident, SassFunction::Builtin(Builtin::new(function), ident));
}
}
pub(crate) fn declare_module_color() -> Module { pub(crate) fn declare_module_color() -> Module {
let mut module = Module::default(); let mut module = Module::default();
color::declare(&mut module); color::declare(&mut module);

View File

@ -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) {} pub(crate) fn declare(_f: &mut Module) {}

View File

@ -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) {} pub(crate) fn declare(_f: &mut Module) {}

View File

@ -9,7 +9,7 @@ use crate::{
media::MediaRule, media::MediaRule,
AtRuleKind, Content, SupportsRule, UnknownAtRule, AtRuleKind, Content, SupportsRule, UnknownAtRule,
}, },
builtin::*, builtin::modules::*,
error::SassResult, error::SassResult,
scope::{Scope, Scopes}, scope::{Scope, Scopes},
selector::{ selector::{

View File

@ -207,6 +207,53 @@ impl<'a> Parser<'a> {
.parse_value(in_paren) .parse_value(in_paren)
} }
fn parse_module_item(
&mut self,
module: &str,
mut module_span: Span,
) -> SassResult<Spanned<IntermediateValue>> {
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<Spanned<IntermediateValue>> {
// }
fn parse_ident_value(&mut self) -> SassResult<Spanned<IntermediateValue>> { fn parse_ident_value(&mut self) -> SassResult<Spanned<IntermediateValue>> {
let Spanned { node: mut s, span } = self.parse_identifier()?; 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() { match self.toks.peek() {
self.toks.next(); Some(Token { kind: '(', .. }) => {
self.toks.next();
if lower == "min" { if lower == "min" {
match self.try_parse_min_max("min", true)? { match self.try_parse_min_max("min", true)? {
Some(val) => { Some(val) => {
self.toks.truncate_iterator_to_cursor(); self.toks.truncate_iterator_to_cursor();
self.toks.next(); self.toks.next();
return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal( return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal(
Value::String(val, QuoteKind::None), Value::String(val, QuoteKind::None),
)) ))
.span(span)); .span(span));
} }
None => { None => {
self.toks.reset_cursor(); self.toks.reset_cursor();
} }
} }
} else if lower == "max" { } else if lower == "max" {
match self.try_parse_min_max("max", true)? { match self.try_parse_min_max("max", true)? {
Some(val) => { Some(val) => {
self.toks.truncate_iterator_to_cursor(); self.toks.truncate_iterator_to_cursor();
self.toks.next(); self.toks.next();
return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal( return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal(
Value::String(val, QuoteKind::None), Value::String(val, QuoteKind::None),
)) ))
.span(span)); .span(span));
} }
None => { None => {
self.toks.reset_cursor(); 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()?),
} }
return Ok(IntermediateValue::Value(HigherIntermediateValue::Literal(
Value::String(s, QuoteKind::None),
))
.span(span));
} }
} }
};
let call_args = self.parse_call_args()?; let as_ident = Identifier::from(&s);
return Ok(IntermediateValue::Value(HigherIntermediateValue::Function( let func = match self.scopes.get_fn(
SassFunction::UserDefined(Box::new(func), as_ident), Spanned {
call_args, node: as_ident,
)) span,
.span(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 // check for named colors