From 3a7a3f508a2b17843b58b3378502e826d0784f42 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sat, 25 Jul 2020 19:22:12 -0400 Subject: [PATCH 01/59] initial parsing work for modules --- src/lib.rs | 5 ++- src/parse/control_flow.rs | 21 ++++++--- src/parse/function.rs | 3 +- src/parse/import.rs | 1 + src/parse/keyframes.rs | 2 + src/parse/mixin.rs | 6 ++- src/parse/mod.rs | 91 +++++++++++++++++++++++++++++++++++++-- src/parse/value/parse.rs | 1 + src/value/mod.rs | 1 + 9 files changed, 118 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8d877f8..32f4c41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,7 +80,7 @@ grass input.scss )] #![cfg_attr(feature = "nightly", feature(track_caller))] #![cfg_attr(feature = "profiling", inline(never))] -use std::{fs, path::Path}; +use std::{collections::HashMap, fs, path::Path}; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; @@ -292,6 +292,7 @@ pub fn from_path(p: &str, options: &Options) -> Result { extender: &mut Extender::new(empty_span), content_scopes: &mut Scopes::new(), options, + modules: &mut HashMap::new(), } .parse() .map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?; @@ -336,6 +337,7 @@ pub fn from_string(p: String, options: &Options) -> Result { extender: &mut Extender::new(empty_span), content_scopes: &mut Scopes::new(), options, + modules: &mut HashMap::new(), } .parse() .map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?; @@ -371,6 +373,7 @@ pub fn from_string(p: String) -> std::result::Result { extender: &mut Extender::new(empty_span), content_scopes: &mut Scopes::new(), options: &Options::default(), + modules: &mut HashMap::new(), } .parse() .map_err(|e| raw_to_parse_error(&map, *e, true).to_string())?; diff --git a/src/parse/control_flow.rs b/src/parse/control_flow.rs index 5ce5333..47d69c4 100644 --- a/src/parse/control_flow.rs +++ b/src/parse/control_flow.rs @@ -53,6 +53,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } .parse_stmt()?; } else { @@ -112,6 +113,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } .parse_stmt()?; } else { @@ -140,6 +142,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } .parse_stmt(); } @@ -320,8 +323,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?; + .parse_stmt()?; if !these_stmts.is_empty() { return Ok(these_stmts); } @@ -342,8 +346,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?, + .parse_stmt()?, ); } } @@ -392,8 +397,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?; + .parse_stmt()?; if !these_stmts.is_empty() { return Ok(these_stmts); } @@ -414,8 +420,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?, + .parse_stmt()?, ); } val = self.parse_value_from_vec(cond.clone(), true)?; @@ -517,8 +524,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?; + .parse_stmt()?; if !these_stmts.is_empty() { return Ok(these_stmts); } @@ -539,8 +547,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?, + .parse_stmt()?, ); } } diff --git a/src/parse/function.rs b/src/parse/function.rs index fb8b516..67544e5 100644 --- a/src/parse/function.rs +++ b/src/parse/function.rs @@ -108,8 +108,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?; + .parse_stmt()?; if entered_scope { self.scopes.exit_scope(); diff --git a/src/parse/import.rs b/src/parse/import.rs index debad53..eaa0fd0 100644 --- a/src/parse/import.rs +++ b/src/parse/import.rs @@ -102,6 +102,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } .parse(); } diff --git a/src/parse/keyframes.rs b/src/parse/keyframes.rs index b24ec30..8494a5e 100644 --- a/src/parse/keyframes.rs +++ b/src/parse/keyframes.rs @@ -166,6 +166,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, }) .parse_keyframes_selector()?; @@ -197,6 +198,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } .parse_stmt()?; diff --git a/src/parse/mixin.rs b/src/parse/mixin.rs index 1e3466c..f72a5b5 100644 --- a/src/parse/mixin.rs +++ b/src/parse/mixin.rs @@ -155,8 +155,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?; + .parse_stmt()?; self.content.pop(); self.scopes.exit_scope(); @@ -207,8 +208,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.scopes, options: self.options, + modules: self.modules, } - .parse()? + .parse_stmt()? } else { Vec::new() }; diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 040f568..b2224d3 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, path::Path, vec::IntoIter}; +use std::{collections::HashMap, convert::TryFrom, path::Path, vec::IntoIter}; use codemap::{CodeMap, Span, Spanned}; use peekmore::{PeekMore, PeekMoreIterator}; @@ -86,11 +86,17 @@ pub(crate) struct Parser<'a> { pub extender: &'a mut Extender, pub options: &'a Options<'a>, + + pub modules: &'a mut HashMap, } impl<'a> Parser<'a> { pub fn parse(&mut self) -> SassResult> { let mut stmts = Vec::new(); + + self.whitespace(); + stmts.append(&mut self.load_modules()?); + while self.toks.peek().is_some() { stmts.append(&mut self.parse_stmt()?); if self.flags.in_function() && !stmts.is_empty() { @@ -101,6 +107,76 @@ impl<'a> Parser<'a> { Ok(stmts) } + /// Returns any multiline comments that may have been found + /// while loading modules + fn load_modules(&mut self) -> SassResult> { + let mut comments = Vec::new(); + + loop { + self.whitespace(); + match self.toks.peek() { + Some(Token { kind: '@', .. }) => { + self.toks.advance_cursor(); + + match AtRuleKind::try_from(&peek_ident_no_interpolation( + self.toks, + false, + self.span_before, + )?)? { + AtRuleKind::Use => { + self.toks.truncate_iterator_to_cursor(); + } + _ => { + break; + } + } + + self.whitespace_or_comment(); + + let quote = match self.toks.next() { + Some(Token { kind: q @ '"', .. }) | Some(Token { kind: q @ '\'', .. }) => q, + Some(..) => todo!(), + None => todo!(), + }; + + let Spanned { node: module, span } = self.parse_quoted_string(quote)?; + let module = module.unquote().to_css_string(span)?; + + if let Some(Token { kind: ';', .. }) = self.toks.peek() { + self.toks.next(); + } else { + todo!() + } + + match module.as_ref() { + "sass:color" => todo!("builtin module `sass:color` not yet implemented"), + "sass:list" => todo!("builtin module `sass:list` not yet implemented"), + "sass:map" => todo!("builtin module `sass:map` not yet implemented"), + "sass:math" => todo!("builtin module `sass:math` not yet implemented"), + "sass:meta" => todo!("builtin module `sass:meta` not yet implemented"), + "sass:selector" => { + todo!("builtin module `sass:selector` not yet implemented") + } + "sass:string" => todo!("builtin module `sass:string` not yet implemented"), + _ => todo!("@use not yet implemented"), + } + } + Some(Token { kind: '/', .. }) => { + self.toks.advance_cursor(); + match self.parse_comment()?.node { + Comment::Silent => continue, + Comment::Loud(s) => comments.push(Stmt::Comment(s)), + } + } + Some(..) | None => break, + } + } + + self.toks.reset_cursor(); + + Ok(comments) + } + fn parse_stmt(&mut self) -> SassResult> { let mut stmts = Vec::new(); while let Some(Token { kind, pos }) = self.toks.peek() { @@ -196,7 +272,13 @@ impl<'a> Parser<'a> { AtRuleKind::Unknown(_) => { stmts.push(self.parse_unknown_at_rule(kind_string.node)?) } - AtRuleKind::Use => todo!("@use not yet implemented"), + AtRuleKind::Use => { + return Err(( + "@use rules must be written before any other rules.", + kind_string.span, + ) + .into()) + } AtRuleKind::Forward => todo!("@forward not yet implemented"), AtRuleKind::Extend => self.parse_extend()?, AtRuleKind::Supports => stmts.push(self.parse_supports()?), @@ -377,6 +459,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, }, allows_parent, true, @@ -668,8 +751,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()? + .parse_stmt()? .into_iter() .filter_map(|s| match s { Stmt::Style(..) => { @@ -709,6 +793,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } .parse_selector(false, true, String::new())?; diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index f18976d..9732ca7 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -202,6 +202,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } .parse_value(in_paren) } diff --git a/src/value/mod.rs b/src/value/mod.rs index 8d0bbad..5cf7cd4 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -477,6 +477,7 @@ impl Value { extender: parser.extender, content_scopes: parser.content_scopes, options: parser.options, + modules: parser.modules, } .parse_selector(allows_parent, true, String::new())? .0) From 31cdc972321ddacd146790d85f35156fad684e25 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sat, 25 Jul 2020 19:23:37 -0400 Subject: [PATCH 02/59] create `@use` specific tests --- tests/use.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/use.rs diff --git a/tests/use.rs b/tests/use.rs new file mode 100644 index 0000000..e673c9f --- /dev/null +++ b/tests/use.rs @@ -0,0 +1,12 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +error!( + after_style, + "a {} + @use \"foo\"; + ", + "Error: @use rules must be written before any other rules." +); From 8c23ec046e2480ee82002f0ccc968e2ae6e727ad Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sat, 25 Jul 2020 20:05:46 -0400 Subject: [PATCH 03/59] allow loading of builtin modules --- src/builtin/{ => functions}/color/hsl.rs | 0 src/builtin/{ => functions}/color/mod.rs | 0 src/builtin/{ => functions}/color/opacity.rs | 0 src/builtin/{ => functions}/color/other.rs | 0 src/builtin/{ => functions}/color/rgb.rs | 0 src/builtin/{ => functions}/list.rs | 0 src/builtin/{ => functions}/macros.rs | 0 src/builtin/{ => functions}/map.rs | 0 src/builtin/{ => functions}/math.rs | 0 src/builtin/{ => functions}/meta.rs | 0 src/builtin/functions/mod.rs | 60 ++++++++++++++++++ src/builtin/{ => functions}/selector.rs | 0 src/builtin/{ => functions}/string.rs | 0 src/builtin/mod.rs | 63 ++----------------- src/builtin/modules/color.rs | 3 + src/builtin/modules/list.rs | 3 + src/builtin/modules/map.rs | 3 + src/builtin/modules/math.rs | 3 + src/builtin/modules/meta.rs | 3 + src/builtin/modules/mod.rs | 64 ++++++++++++++++++++ src/builtin/modules/selector.rs | 3 + src/builtin/modules/string.rs | 3 + src/parse/mod.rs | 33 ++++++---- 23 files changed, 171 insertions(+), 70 deletions(-) rename src/builtin/{ => functions}/color/hsl.rs (100%) rename src/builtin/{ => functions}/color/mod.rs (100%) rename src/builtin/{ => functions}/color/opacity.rs (100%) rename src/builtin/{ => functions}/color/other.rs (100%) rename src/builtin/{ => functions}/color/rgb.rs (100%) rename src/builtin/{ => functions}/list.rs (100%) rename src/builtin/{ => functions}/macros.rs (100%) rename src/builtin/{ => functions}/map.rs (100%) rename src/builtin/{ => functions}/math.rs (100%) rename src/builtin/{ => functions}/meta.rs (100%) create mode 100644 src/builtin/functions/mod.rs rename src/builtin/{ => functions}/selector.rs (100%) rename src/builtin/{ => functions}/string.rs (100%) create mode 100644 src/builtin/modules/color.rs create mode 100644 src/builtin/modules/list.rs create mode 100644 src/builtin/modules/map.rs create mode 100644 src/builtin/modules/math.rs create mode 100644 src/builtin/modules/meta.rs create mode 100644 src/builtin/modules/mod.rs create mode 100644 src/builtin/modules/selector.rs create mode 100644 src/builtin/modules/string.rs diff --git a/src/builtin/color/hsl.rs b/src/builtin/functions/color/hsl.rs similarity index 100% rename from src/builtin/color/hsl.rs rename to src/builtin/functions/color/hsl.rs diff --git a/src/builtin/color/mod.rs b/src/builtin/functions/color/mod.rs similarity index 100% rename from src/builtin/color/mod.rs rename to src/builtin/functions/color/mod.rs diff --git a/src/builtin/color/opacity.rs b/src/builtin/functions/color/opacity.rs similarity index 100% rename from src/builtin/color/opacity.rs rename to src/builtin/functions/color/opacity.rs diff --git a/src/builtin/color/other.rs b/src/builtin/functions/color/other.rs similarity index 100% rename from src/builtin/color/other.rs rename to src/builtin/functions/color/other.rs diff --git a/src/builtin/color/rgb.rs b/src/builtin/functions/color/rgb.rs similarity index 100% rename from src/builtin/color/rgb.rs rename to src/builtin/functions/color/rgb.rs diff --git a/src/builtin/list.rs b/src/builtin/functions/list.rs similarity index 100% rename from src/builtin/list.rs rename to src/builtin/functions/list.rs diff --git a/src/builtin/macros.rs b/src/builtin/functions/macros.rs similarity index 100% rename from src/builtin/macros.rs rename to src/builtin/functions/macros.rs diff --git a/src/builtin/map.rs b/src/builtin/functions/map.rs similarity index 100% rename from src/builtin/map.rs rename to src/builtin/functions/map.rs diff --git a/src/builtin/math.rs b/src/builtin/functions/math.rs similarity index 100% rename from src/builtin/math.rs rename to src/builtin/functions/math.rs diff --git a/src/builtin/meta.rs b/src/builtin/functions/meta.rs similarity index 100% rename from src/builtin/meta.rs rename to src/builtin/functions/meta.rs diff --git a/src/builtin/functions/mod.rs b/src/builtin/functions/mod.rs new file mode 100644 index 0000000..4ec4354 --- /dev/null +++ b/src/builtin/functions/mod.rs @@ -0,0 +1,60 @@ +// A reference to the parser is only necessary for some functions +#![allow(unused_variables)] + +use std::{ + collections::HashMap, + sync::atomic::{AtomicUsize, Ordering}, +}; + +use once_cell::sync::Lazy; + +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(crate) type GlobalFunctionMap = HashMap<&'static str, Builtin>; + +static FUNCTION_COUNT: AtomicUsize = AtomicUsize::new(0); + +// TODO: impl Fn +#[derive(Clone)] +pub(crate) struct Builtin( + pub fn(CallArgs, &mut Parser<'_>) -> SassResult, + usize, +); + +impl Builtin { + pub fn new(body: fn(CallArgs, &mut Parser<'_>) -> SassResult) -> Builtin { + let count = FUNCTION_COUNT.fetch_add(1, Ordering::Relaxed); + Self(body, count) + } +} + +impl PartialEq for Builtin { + fn eq(&self, other: &Self) -> bool { + self.1 == other.1 + } +} + +impl Eq for Builtin {} + +pub(crate) static GLOBAL_FUNCTIONS: Lazy = Lazy::new(|| { + let mut m = HashMap::new(); + color::declare(&mut m); + list::declare(&mut m); + map::declare(&mut m); + math::declare(&mut m); + meta::declare(&mut m); + selector::declare(&mut m); + string::declare(&mut m); + m +}); diff --git a/src/builtin/selector.rs b/src/builtin/functions/selector.rs similarity index 100% rename from src/builtin/selector.rs rename to src/builtin/functions/selector.rs diff --git a/src/builtin/string.rs b/src/builtin/functions/string.rs similarity index 100% rename from src/builtin/string.rs rename to src/builtin/functions/string.rs diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index 4ec4354..18bd1bf 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -1,60 +1,5 @@ -// A reference to the parser is only necessary for some functions -#![allow(unused_variables)] +mod functions; +mod modules; -use std::{ - collections::HashMap, - sync::atomic::{AtomicUsize, Ordering}, -}; - -use once_cell::sync::Lazy; - -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(crate) type GlobalFunctionMap = HashMap<&'static str, Builtin>; - -static FUNCTION_COUNT: AtomicUsize = AtomicUsize::new(0); - -// TODO: impl Fn -#[derive(Clone)] -pub(crate) struct Builtin( - pub fn(CallArgs, &mut Parser<'_>) -> SassResult, - usize, -); - -impl Builtin { - pub fn new(body: fn(CallArgs, &mut Parser<'_>) -> SassResult) -> Builtin { - let count = FUNCTION_COUNT.fetch_add(1, Ordering::Relaxed); - Self(body, count) - } -} - -impl PartialEq for Builtin { - fn eq(&self, other: &Self) -> bool { - self.1 == other.1 - } -} - -impl Eq for Builtin {} - -pub(crate) static GLOBAL_FUNCTIONS: Lazy = Lazy::new(|| { - let mut m = HashMap::new(); - color::declare(&mut m); - list::declare(&mut m); - map::declare(&mut m); - math::declare(&mut m); - meta::declare(&mut m); - selector::declare(&mut m); - string::declare(&mut m); - m -}); +pub(crate) use functions::{Builtin, GLOBAL_FUNCTIONS}; +pub(crate) use modules::*; diff --git a/src/builtin/modules/color.rs b/src/builtin/modules/color.rs new file mode 100644 index 0000000..c3c3a05 --- /dev/null +++ b/src/builtin/modules/color.rs @@ -0,0 +1,3 @@ +use crate::builtin::Module; + +pub(crate) fn declare(_f: &mut Module) {} diff --git a/src/builtin/modules/list.rs b/src/builtin/modules/list.rs new file mode 100644 index 0000000..c3c3a05 --- /dev/null +++ b/src/builtin/modules/list.rs @@ -0,0 +1,3 @@ +use crate::builtin::Module; + +pub(crate) fn declare(_f: &mut Module) {} diff --git a/src/builtin/modules/map.rs b/src/builtin/modules/map.rs new file mode 100644 index 0000000..c3c3a05 --- /dev/null +++ b/src/builtin/modules/map.rs @@ -0,0 +1,3 @@ +use crate::builtin::Module; + +pub(crate) fn declare(_f: &mut Module) {} diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs new file mode 100644 index 0000000..c3c3a05 --- /dev/null +++ b/src/builtin/modules/math.rs @@ -0,0 +1,3 @@ +use crate::builtin::Module; + +pub(crate) fn declare(_f: &mut Module) {} diff --git a/src/builtin/modules/meta.rs b/src/builtin/modules/meta.rs new file mode 100644 index 0000000..c3c3a05 --- /dev/null +++ b/src/builtin/modules/meta.rs @@ -0,0 +1,3 @@ +use crate::builtin::Module; + +pub(crate) fn declare(_f: &mut Module) {} diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs new file mode 100644 index 0000000..dc248ec --- /dev/null +++ b/src/builtin/modules/mod.rs @@ -0,0 +1,64 @@ +use std::collections::BTreeMap; + +use crate::{ + atrule::Mixin, + common::Identifier, + value::{SassFunction, Value}, +}; + +mod color; +mod list; +mod map; +mod math; +mod meta; +mod selector; +mod string; + +#[derive(Debug, Default)] +pub(crate) struct Module { + vars: BTreeMap, + mixins: BTreeMap, + functions: BTreeMap, +} + +pub(crate) fn declare_module_color() -> Module { + let mut module = Module::default(); + color::declare(&mut module); + module +} + +pub(crate) fn declare_module_list() -> Module { + let mut module = Module::default(); + list::declare(&mut module); + module +} + +pub(crate) fn declare_module_map() -> Module { + let mut module = Module::default(); + map::declare(&mut module); + module +} + +pub(crate) fn declare_module_math() -> Module { + let mut module = Module::default(); + math::declare(&mut module); + module +} + +pub(crate) fn declare_module_meta() -> Module { + let mut module = Module::default(); + meta::declare(&mut module); + module +} + +pub(crate) fn declare_module_selector() -> Module { + let mut module = Module::default(); + selector::declare(&mut module); + module +} + +pub(crate) fn declare_module_string() -> Module { + let mut module = Module::default(); + string::declare(&mut module); + module +} diff --git a/src/builtin/modules/selector.rs b/src/builtin/modules/selector.rs new file mode 100644 index 0000000..c3c3a05 --- /dev/null +++ b/src/builtin/modules/selector.rs @@ -0,0 +1,3 @@ +use crate::builtin::Module; + +pub(crate) fn declare(_f: &mut Module) {} diff --git a/src/builtin/modules/string.rs b/src/builtin/modules/string.rs new file mode 100644 index 0000000..c3c3a05 --- /dev/null +++ b/src/builtin/modules/string.rs @@ -0,0 +1,3 @@ +use crate::builtin::Module; + +pub(crate) fn declare(_f: &mut Module) {} diff --git a/src/parse/mod.rs b/src/parse/mod.rs index b2224d3..ed4ff09 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -9,6 +9,7 @@ use crate::{ media::MediaRule, AtRuleKind, Content, SupportsRule, UnknownAtRule, }, + builtin::*, error::SassResult, scope::{Scope, Scopes}, selector::{ @@ -87,7 +88,7 @@ pub(crate) struct Parser<'a> { pub options: &'a Options<'a>, - pub modules: &'a mut HashMap, + pub modules: &'a mut HashMap, } impl<'a> Parser<'a> { @@ -149,17 +150,27 @@ impl<'a> Parser<'a> { } match module.as_ref() { - "sass:color" => todo!("builtin module `sass:color` not yet implemented"), - "sass:list" => todo!("builtin module `sass:list` not yet implemented"), - "sass:map" => todo!("builtin module `sass:map` not yet implemented"), - "sass:math" => todo!("builtin module `sass:math` not yet implemented"), - "sass:meta" => todo!("builtin module `sass:meta` not yet implemented"), - "sass:selector" => { - todo!("builtin module `sass:selector` not yet implemented") - } - "sass:string" => todo!("builtin module `sass:string` not yet implemented"), + "sass:color" => self + .modules + .insert("color".to_owned(), declare_module_color()), + "sass:list" => self + .modules + .insert("list".to_owned(), declare_module_list()), + "sass:map" => self.modules.insert("map".to_owned(), declare_module_map()), + "sass:math" => self + .modules + .insert("math".to_owned(), declare_module_math()), + "sass:meta" => self + .modules + .insert("meta".to_owned(), declare_module_meta()), + "sass:selector" => self + .modules + .insert("selector".to_owned(), declare_module_selector()), + "sass:string" => self + .modules + .insert("string".to_owned(), declare_module_string()), _ => todo!("@use not yet implemented"), - } + }; } Some(Token { kind: '/', .. }) => { self.toks.advance_cursor(); From c0631c75a068516ed37005590c484f0b4e137f3f Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sat, 25 Jul 2020 20:30:01 -0400 Subject: [PATCH 04/59] resolve parsing error involving toplevel, multiline comments --- src/parse/mod.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/parse/mod.rs b/src/parse/mod.rs index ed4ff09..b0ea782 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -119,6 +119,12 @@ impl<'a> Parser<'a> { Some(Token { kind: '@', .. }) => { self.toks.advance_cursor(); + if let Some(Token { kind, .. }) = self.toks.peek() { + if !matches!(kind, 'a'..='z' | 'A'..='Z' | '\\') { + break; + } + } + match AtRuleKind::try_from(&peek_ident_no_interpolation( self.toks, false, @@ -173,7 +179,7 @@ impl<'a> Parser<'a> { }; } Some(Token { kind: '/', .. }) => { - self.toks.advance_cursor(); + self.toks.next(); match self.parse_comment()?.node { Comment::Silent => continue, Comment::Loud(s) => comments.push(Stmt::Comment(s)), From 060641b86d9876b666a69d10d9b7206242147044 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 00:05:13 -0400 Subject: [PATCH 05/59] implement aliased `sass:math` functions --- src/builtin/functions/math.rs | 18 +-- src/builtin/functions/meta.rs | 4 +- src/builtin/functions/mod.rs | 14 +-- src/builtin/mod.rs | 5 +- src/builtin/modules/color.rs | 4 +- src/builtin/modules/list.rs | 4 +- src/builtin/modules/map.rs | 4 +- src/builtin/modules/math.rs | 89 +++++++++++++- src/builtin/modules/meta.rs | 4 +- src/builtin/modules/mod.rs | 31 +++++ src/builtin/modules/selector.rs | 4 +- src/builtin/modules/string.rs | 4 +- src/parse/mod.rs | 2 +- src/parse/value/parse.rs | 200 ++++++++++++++++++++------------ 14 files changed, 285 insertions(+), 102 deletions(-) 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 From 15f657df6d069a717ac9f0464251c778ef67c750 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 00:10:55 -0400 Subject: [PATCH 06/59] implement builtin `sass:math` variables --- src/builtin/modules/math.rs | 12 +++++++++++- src/builtin/modules/mod.rs | 4 ++++ src/parse/value/parse.rs | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index e3d1421..36f8019 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -7,7 +7,8 @@ use crate::{ }, error::SassResult, parse::Parser, - value::Value, + unit::Unit, + value::{Number, Value}, }; #[cfg(feature = "random")] @@ -85,4 +86,13 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("percentage", percentage); #[cfg(feature = "random")] f.insert_builtin("random", random); + + f.insert_builtin_var( + "pi", + Value::Dimension(Number::from(std::f64::consts::PI), Unit::None, true), + ); + f.insert_builtin_var( + "e", + Value::Dimension(Number::from(std::f64::consts::E), Unit::None, true), + ); } diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index e527df6..45bd3ca 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -37,6 +37,10 @@ impl Module { } } + pub fn insert_builtin_var(&mut self, name: &'static str, value: Value) { + self.vars.insert(name.into(), value); + } + pub fn get_fn(&self, name: Identifier) -> Option { self.functions.get(&name).cloned() } diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 179e609..449a1d6 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -214,6 +214,7 @@ impl<'a> Parser<'a> { ) -> SassResult> { Ok(IntermediateValue::Value( if matches!(self.toks.peek(), Some(Token { kind: '$', .. })) { + self.toks.next(); let var = self .parse_identifier_no_interpolation(false)? .map_node(|i| i.into()); From d6126110e92efdac5fe611e7c6799da0dab76fc0 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 00:15:34 -0400 Subject: [PATCH 07/59] alias `sass:selector` builtin functions --- src/builtin/functions/selector.rs | 16 ++++++++-------- src/builtin/mod.rs | 2 +- src/builtin/modules/selector.rs | 21 +++++++++++++++++++-- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/builtin/functions/selector.rs b/src/builtin/functions/selector.rs index 4015796..5773a80 100644 --- a/src/builtin/functions/selector.rs +++ b/src/builtin/functions/selector.rs @@ -9,7 +9,7 @@ use crate::{ value::Value, }; -fn is_superselector(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn is_superselector(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let parent_selector = args .get_err(0, "super")? @@ -21,7 +21,7 @@ fn is_superselector(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn simple_selectors(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; // todo: Value::to_compound_selector let selector = args @@ -51,7 +51,7 @@ fn simple_selectors(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn selector_parse(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; Ok(args .get_err(0, "selector")? @@ -59,7 +59,7 @@ fn selector_parse(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn selector_nest(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { let span = args.span(); let selectors = args.get_variadic()?; if selectors.is_empty() { @@ -80,7 +80,7 @@ fn selector_nest(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { .into_value()) } -fn selector_append(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn selector_append(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { let span = args.span(); let selectors = args.get_variadic()?; if selectors.is_empty() { @@ -138,7 +138,7 @@ fn selector_append(args: CallArgs, parser: &mut Parser<'_>) -> SassResult .into_value()) } -fn selector_extend(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn selector_extend(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(3)?; let selector = args .get_err(0, "selector")? @@ -153,7 +153,7 @@ fn selector_extend(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn selector_replace(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(3)?; let selector = args .get_err(0, "selector")? @@ -167,7 +167,7 @@ fn selector_replace(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn selector_unify(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let selector1 = args .get_err(0, "selector1")? diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index 56375bd..e1b3d41 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -1,4 +1,4 @@ mod functions; pub(crate) mod modules; -pub(crate) use functions::{math, meta, Builtin, GLOBAL_FUNCTIONS}; +pub(crate) use functions::{math, meta, selector, Builtin, GLOBAL_FUNCTIONS}; diff --git a/src/builtin/modules/selector.rs b/src/builtin/modules/selector.rs index 47d58dc..32a7860 100644 --- a/src/builtin/modules/selector.rs +++ b/src/builtin/modules/selector.rs @@ -1,5 +1,22 @@ use crate::{ - args::CallArgs, builtin::modules::Module, error::SassResult, parse::Parser, value::Value, + args::CallArgs, + builtin::modules::Module, + builtin::selector::{ + is_superselector, selector_append, selector_extend, selector_nest, selector_parse, + selector_replace, selector_unify, simple_selectors, + }, + error::SassResult, + parse::Parser, + value::Value, }; -pub(crate) fn declare(_f: &mut Module) {} +pub(crate) fn declare(f: &mut Module) { + f.insert_builtin("is-superselector", is_superselector); + f.insert_builtin("append", selector_append); + f.insert_builtin("extend", selector_extend); + f.insert_builtin("nest", selector_nest); + f.insert_builtin("parse", selector_parse); + f.insert_builtin("replace", selector_replace); + f.insert_builtin("unify", selector_unify); + f.insert_builtin("simple-selectors", simple_selectors); +} From 21e096247e17d2603d13ffc21e592b1506cd5dfb Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 00:20:20 -0400 Subject: [PATCH 08/59] alias `sass:list` builtin functions --- src/builtin/functions/list.rs | 18 +++++++++--------- src/builtin/mod.rs | 2 +- src/builtin/modules/list.rs | 21 +++++++++++++++++++-- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/builtin/functions/list.rs b/src/builtin/functions/list.rs index 9dbb483..c0b8550 100644 --- a/src/builtin/functions/list.rs +++ b/src/builtin/functions/list.rs @@ -11,7 +11,7 @@ use crate::{ value::{Number, Value}, }; -fn length(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn length(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; Ok(Value::Dimension( Number::from(args.get_err(0, "list")?.as_list().len()), @@ -20,7 +20,7 @@ fn length(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { )) } -fn nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let mut list = args.get_err(0, "list")?.as_list(); let n = match args.get_err(1, "n")? { @@ -61,7 +61,7 @@ fn nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { })) } -fn list_separator(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn list_separator(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; Ok(Value::String( match args.get_err(0, "list")? { @@ -73,7 +73,7 @@ fn list_separator(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn set_nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(3)?; let (mut list, sep, brackets) = match args.get_err(0, "list")? { Value::List(v, sep, b) => (v, sep, b), @@ -120,7 +120,7 @@ fn set_nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(Value::List(list, sep, brackets)) } -fn append(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn append(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(3)?; let (mut list, sep, brackets) = match args.get_err(0, "list")? { Value::List(v, sep, b) => (v, sep, b), @@ -158,7 +158,7 @@ fn append(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(Value::List(list, sep, brackets)) } -fn join(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn join(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(4)?; let (mut list1, sep1, brackets) = match args.get_err(0, "list1")? { Value::List(v, sep, brackets) => (v, sep, brackets), @@ -225,7 +225,7 @@ fn join(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(Value::List(list1, sep, brackets)) } -fn is_bracketed(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn is_bracketed(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; Ok(Value::bool(match args.get_err(0, "list")? { Value::List(.., brackets) => match brackets { @@ -236,7 +236,7 @@ fn is_bracketed(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let list = args.get_err(0, "list")?.as_list(); let value = args.get_err(1, "value")?; @@ -247,7 +247,7 @@ fn index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(Value::Dimension(index, Unit::None, true)) } -fn zip(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn zip(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { let lists = args .get_variadic()? .into_iter() diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index e1b3d41..ccef78d 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -1,4 +1,4 @@ mod functions; pub(crate) mod modules; -pub(crate) use functions::{math, meta, selector, Builtin, GLOBAL_FUNCTIONS}; +pub(crate) use functions::{list, math, meta, selector, Builtin, GLOBAL_FUNCTIONS}; diff --git a/src/builtin/modules/list.rs b/src/builtin/modules/list.rs index 47d58dc..ddd6611 100644 --- a/src/builtin/modules/list.rs +++ b/src/builtin/modules/list.rs @@ -1,5 +1,22 @@ use crate::{ - args::CallArgs, builtin::modules::Module, error::SassResult, parse::Parser, value::Value, + args::CallArgs, + builtin::{ + list::{append, index, is_bracketed, join, length, list_separator, nth, set_nth, zip}, + modules::Module, + }, + error::SassResult, + parse::Parser, + value::Value, }; -pub(crate) fn declare(_f: &mut Module) {} +pub(crate) fn declare(f: &mut Module) { + f.insert_builtin("append", append); + f.insert_builtin("index", index); + f.insert_builtin("is-bracketed", is_bracketed); + f.insert_builtin("join", join); + f.insert_builtin("length", length); + f.insert_builtin("separator", list_separator); + f.insert_builtin("nth", nth); + f.insert_builtin("set-nth", set_nth); + f.insert_builtin("zip", zip); +} From ca4c5da73e22d743abe51fc82782c40974c550a7 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 00:22:44 -0400 Subject: [PATCH 09/59] alias `sass:map` builtin functions --- src/builtin/functions/map.rs | 12 ++++++------ src/builtin/mod.rs | 2 +- src/builtin/modules/map.rs | 18 ++++++++++++++++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/builtin/functions/map.rs b/src/builtin/functions/map.rs index 068d4af..5572b12 100644 --- a/src/builtin/functions/map.rs +++ b/src/builtin/functions/map.rs @@ -8,7 +8,7 @@ use crate::{ value::{SassMap, Value}, }; -fn map_get(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn map_get(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let key = args.get_err(1, "key")?; let map = match args.get_err(0, "map")? { @@ -26,7 +26,7 @@ fn map_get(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(map.get(&key)?.unwrap_or(Value::Null)) } -fn map_has_key(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn map_has_key(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let key = args.get_err(1, "key")?; let map = match args.get_err(0, "map")? { @@ -44,7 +44,7 @@ fn map_has_key(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult Ok(Value::bool(map.get(&key)?.is_some())) } -fn map_keys(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn map_keys(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let map = match args.get_err(0, "map")? { Value::Map(m) => m, @@ -65,7 +65,7 @@ fn map_keys(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { )) } -fn map_values(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn map_values(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let map = match args.get_err(0, "map")? { Value::Map(m) => m, @@ -86,7 +86,7 @@ fn map_values(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult )) } -fn map_merge(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn map_merge(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let mut map1 = match args.get_err(0, "map1")? { Value::Map(m) => m, @@ -116,7 +116,7 @@ fn map_merge(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(Value::Map(map1)) } -fn map_remove(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn map_remove(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { let mut map = match args.get_err(0, "map")? { Value::Map(m) => m, Value::List(v, ..) if v.is_empty() => SassMap::new(), diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index ccef78d..41efe0e 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -1,4 +1,4 @@ mod functions; pub(crate) mod modules; -pub(crate) use functions::{list, math, meta, selector, Builtin, GLOBAL_FUNCTIONS}; +pub(crate) use functions::{list, map, math, meta, selector, Builtin, GLOBAL_FUNCTIONS}; diff --git a/src/builtin/modules/map.rs b/src/builtin/modules/map.rs index 47d58dc..5a349f5 100644 --- a/src/builtin/modules/map.rs +++ b/src/builtin/modules/map.rs @@ -1,5 +1,19 @@ use crate::{ - args::CallArgs, builtin::modules::Module, error::SassResult, parse::Parser, value::Value, + args::CallArgs, + builtin::{ + map::{map_get, map_has_key, map_keys, map_merge, map_remove, map_values}, + modules::Module, + }, + error::SassResult, + parse::Parser, + value::Value, }; -pub(crate) fn declare(_f: &mut Module) {} +pub(crate) fn declare(f: &mut Module) { + f.insert_builtin("get", map_get); + f.insert_builtin("has-key", map_has_key); + f.insert_builtin("keys", map_keys); + f.insert_builtin("merge", map_merge); + f.insert_builtin("remove", map_remove); + f.insert_builtin("values", map_values); +} From b3d20a574a6f87e2c7efd96627bd14661d055153 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 00:25:58 -0400 Subject: [PATCH 10/59] alias `sass:string` builtin functions --- src/builtin/functions/string.rs | 18 +++++++++--------- src/builtin/mod.rs | 2 +- src/builtin/modules/string.rs | 27 +++++++++++++++++++++++++-- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/builtin/functions/string.rs b/src/builtin/functions/string.rs index 989d393..8e95059 100644 --- a/src/builtin/functions/string.rs +++ b/src/builtin/functions/string.rs @@ -15,7 +15,7 @@ use crate::{ value::{Number, Value}, }; -fn to_upper_case(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn to_upper_case(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "string")? { Value::String(mut i, q) => { @@ -30,7 +30,7 @@ fn to_upper_case(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn to_lower_case(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "string")? { Value::String(mut i, q) => { @@ -45,7 +45,7 @@ fn to_lower_case(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn str_length(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "string")? { Value::String(i, _) => Ok(Value::Dimension( @@ -61,7 +61,7 @@ fn str_length(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult } } -fn quote(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn quote(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "string")? { Value::String(i, _) => Ok(Value::String(i, QuoteKind::Quoted)), @@ -73,7 +73,7 @@ fn quote(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } -fn unquote(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn unquote(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "string")? { i @ Value::String(..) => Ok(i.unquote()), @@ -85,7 +85,7 @@ fn unquote(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } -fn str_slice(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn str_slice(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(3)?; let (string, quotes) = match args.get_err(0, "string")? { Value::String(s, q) => (s, q), @@ -178,7 +178,7 @@ fn str_slice(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } -fn str_index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn str_index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let s1 = match args.get_err(0, "string")? { Value::String(i, _) => i, @@ -208,7 +208,7 @@ fn str_index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { }) } -fn str_insert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn str_insert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(3)?; let (s1, quotes) = match args.get_err(0, "string")? { Value::String(i, q) => (i, q), @@ -305,7 +305,7 @@ fn str_insert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult #[cfg(feature = "random")] #[allow(clippy::needless_pass_by_value)] -fn unique_id(args: CallArgs, _: &mut Parser<'_>) -> SassResult { +pub(crate) fn unique_id(args: CallArgs, _: &mut Parser<'_>) -> SassResult { args.max_args(0)?; let mut rng = thread_rng(); let string = std::iter::repeat(()) diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index 41efe0e..e31fb8c 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -1,4 +1,4 @@ mod functions; pub(crate) mod modules; -pub(crate) use functions::{list, map, math, meta, selector, Builtin, GLOBAL_FUNCTIONS}; +pub(crate) use functions::{list, map, math, meta, selector, string, Builtin, GLOBAL_FUNCTIONS}; diff --git a/src/builtin/modules/string.rs b/src/builtin/modules/string.rs index 47d58dc..5e63c40 100644 --- a/src/builtin/modules/string.rs +++ b/src/builtin/modules/string.rs @@ -1,5 +1,28 @@ use crate::{ - args::CallArgs, builtin::modules::Module, error::SassResult, parse::Parser, value::Value, + args::CallArgs, + builtin::{ + modules::Module, + string::{ + quote, str_index, str_insert, str_length, str_slice, to_lower_case, to_upper_case, + unquote, + }, + }, + error::SassResult, + parse::Parser, + value::Value, }; -pub(crate) fn declare(_f: &mut Module) {} +#[cfg(feature = "random")] +use crate::builtin::string::unique_id; + +pub(crate) fn declare(f: &mut Module) { + f.insert_builtin("quote", quote); + f.insert_builtin("index", str_index); + f.insert_builtin("insert", str_insert); + f.insert_builtin("length", str_length); + f.insert_builtin("slice", str_slice); + f.insert_builtin("to-lower-case", to_lower_case); + f.insert_builtin("to-upper-case", to_upper_case); + #[cfg(feature = "random")] + f.insert_builtin("unique-id", unique_id); +} From 67fffe74987fc2771fe3c87df590fdb1a3b0f452 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 00:47:40 -0400 Subject: [PATCH 11/59] alias `sass:meta` builtin functions --- src/builtin/functions/meta.rs | 31 +++++++++++++++++--------- src/builtin/modules/meta.rs | 41 +++++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/src/builtin/functions/meta.rs b/src/builtin/functions/meta.rs index c4b9b4f..d6e3fd9 100644 --- a/src/builtin/functions/meta.rs +++ b/src/builtin/functions/meta.rs @@ -20,7 +20,7 @@ fn if_(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } -fn feature_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn feature_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "feature")? { #[allow(clippy::match_same_arms)] @@ -65,7 +65,7 @@ pub(crate) fn unit(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn type_of(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let value = args.get_err(0, "value")?; Ok(Value::String(value.kind().to_owned(), QuoteKind::None)) @@ -81,7 +81,7 @@ pub(crate) fn unitless(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResul }) } -fn inspect(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn inspect(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; Ok(Value::String( args.get_err(0, "value")?.inspect(args.span())?.into_owned(), @@ -89,7 +89,7 @@ fn inspect(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { )) } -fn variable_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn variable_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "name")? { Value::String(s, _) => Ok(Value::bool( @@ -103,7 +103,10 @@ fn variable_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn global_variable_exists( + mut args: CallArgs, + parser: &mut Parser<'_>, +) -> SassResult { args.max_args(1)?; match args.get_err(0, "name")? { Value::String(s, _) => Ok(Value::bool(parser.global_scope.var_exists(s.into()))), @@ -115,7 +118,8 @@ fn global_variable_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassRe } } -fn mixin_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +// todo: should check for module arg (as well as several others) +pub(crate) fn mixin_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; match args.get_err(0, "name")? { Value::String(s, _) => Ok(Value::bool( @@ -129,7 +133,7 @@ fn mixin_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn function_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; match args.get_err(0, "name")? { Value::String(s, _) => Ok(Value::bool( @@ -143,7 +147,7 @@ fn function_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn get_function(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(3)?; let name: Identifier = match args.get_err(0, "name")? { Value::String(s, _) => s.into(), @@ -193,7 +197,7 @@ fn get_function(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn call(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { let func = match args.get_err(0, "function")? { Value::FunctionRef(f) => f, v => { @@ -211,7 +215,7 @@ fn call(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } #[allow(clippy::needless_pass_by_value)] -fn content_exists(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn content_exists(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(0)?; if !parser.flags.in_mixin() { return Err(( @@ -225,6 +229,12 @@ fn content_exists(args: CallArgs, parser: &mut Parser<'_>) -> SassResult )) } +#[allow(dead_code, unused_mut, unused_variables)] +pub(crate) fn keywords(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(1)?; + todo!("builtin function `keywords` blocked on better handling of call args") +} + pub(crate) fn declare(f: &mut GlobalFunctionMap) { f.insert("if", Builtin::new(if_)); f.insert("feature-exists", Builtin::new(feature_exists)); @@ -242,4 +252,5 @@ pub(crate) fn declare(f: &mut GlobalFunctionMap) { f.insert("get-function", Builtin::new(get_function)); f.insert("call", Builtin::new(call)); f.insert("content-exists", Builtin::new(content_exists)); + f.insert("keywords", Builtin::new(keywords)); } diff --git a/src/builtin/modules/meta.rs b/src/builtin/modules/meta.rs index 47d58dc..be4a626 100644 --- a/src/builtin/modules/meta.rs +++ b/src/builtin/modules/meta.rs @@ -1,5 +1,42 @@ use crate::{ - args::CallArgs, builtin::modules::Module, error::SassResult, parse::Parser, value::Value, + args::CallArgs, + builtin::{ + meta::{ + call, content_exists, feature_exists, function_exists, get_function, + global_variable_exists, inspect, keywords, mixin_exists, type_of, variable_exists, + }, + modules::Module, + }, + error::SassResult, + parse::Parser, + value::Value, }; -pub(crate) fn declare(_f: &mut Module) {} +fn load_css(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(2)?; + todo!() +} + +fn module_functions(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(2)?; + todo!() +} + +fn module_variables(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(2)?; + todo!() +} + +pub(crate) fn declare(f: &mut Module) { + f.insert_builtin("call", call); + f.insert_builtin("content-exists", content_exists); + f.insert_builtin("feature-exists", feature_exists); + f.insert_builtin("function-exists", function_exists); + f.insert_builtin("get-function", get_function); + f.insert_builtin("global-variable-exists", global_variable_exists); + f.insert_builtin("inspect", inspect); + f.insert_builtin("keywords", keywords); + f.insert_builtin("mixin-exists", mixin_exists); + f.insert_builtin("type-of", type_of); + f.insert_builtin("variable-exists", variable_exists); +} From 3fae0a962115a2080b0e58b3618e5cd8f55ccb33 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 00:58:09 -0400 Subject: [PATCH 12/59] alias `sass:color` builtin functions --- src/builtin/functions/color/hsl.rs | 18 ++++++------- src/builtin/functions/color/mod.rs | 8 +++--- src/builtin/functions/color/opacity.rs | 6 +++-- src/builtin/functions/color/other.rs | 10 +++---- src/builtin/functions/color/rgb.rs | 12 ++++----- src/builtin/mod.rs | 4 ++- src/builtin/modules/color.rs | 37 ++++++++++++++++++++++++-- 7 files changed, 66 insertions(+), 29 deletions(-) diff --git a/src/builtin/functions/color/hsl.rs b/src/builtin/functions/color/hsl.rs index 64c07a6..72821bb 100644 --- a/src/builtin/functions/color/hsl.rs +++ b/src/builtin/functions/color/hsl.rs @@ -205,15 +205,15 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> } } -fn hsl(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn hsl(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { inner_hsl("hsl", args, parser) } -fn hsla(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn hsla(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { inner_hsl("hsla", args, parser) } -fn hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { Value::Color(c) => Ok(Value::Dimension(c.hue(), Unit::Deg, true)), @@ -225,7 +225,7 @@ fn hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } -fn saturation(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn saturation(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { Value::Color(c) => Ok(Value::Dimension(c.saturation(), Unit::Percent, true)), @@ -237,7 +237,7 @@ fn saturation(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult } } -fn lightness(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn lightness(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { Value::Color(c) => Ok(Value::Dimension(c.lightness(), Unit::Percent, true)), @@ -249,7 +249,7 @@ fn lightness(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } -fn adjust_hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn adjust_hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let color = match args.get_err(0, "color")? { Value::Color(c) => c, @@ -405,7 +405,7 @@ fn desaturate(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult Ok(Value::Color(Box::new(color.desaturate(amount)))) } -fn grayscale(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn grayscale(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let color = match args.get_err(0, "color")? { Value::Color(c) => c, @@ -426,7 +426,7 @@ fn grayscale(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(Value::Color(Box::new(color.desaturate(Number::one())))) } -fn complement(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn complement(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let color = match args.get_err(0, "color")? { Value::Color(c) => c, @@ -441,7 +441,7 @@ fn complement(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult Ok(Value::Color(Box::new(color.complement()))) } -fn invert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn invert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let weight = match args.default_arg( 1, diff --git a/src/builtin/functions/color/mod.rs b/src/builtin/functions/color/mod.rs index 42bb71d..55c6425 100644 --- a/src/builtin/functions/color/mod.rs +++ b/src/builtin/functions/color/mod.rs @@ -1,9 +1,9 @@ use super::{Builtin, GlobalFunctionMap}; -mod hsl; -mod opacity; -mod other; -mod rgb; +pub mod hsl; +pub mod opacity; +pub mod other; +pub mod rgb; pub(crate) fn declare(f: &mut GlobalFunctionMap) { hsl::declare(f); diff --git a/src/builtin/functions/color/opacity.rs b/src/builtin/functions/color/opacity.rs index f73b52b..553740e 100644 --- a/src/builtin/functions/color/opacity.rs +++ b/src/builtin/functions/color/opacity.rs @@ -5,7 +5,7 @@ use crate::{ value::Value, }; -fn alpha(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn alpha(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None, true)), @@ -17,7 +17,7 @@ fn alpha(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } -fn opacity(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn opacity(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None, true)), @@ -33,6 +33,7 @@ fn opacity(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } +// todo: unify `opacify` and `fade_in` fn opacify(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let color = match args.get_err(0, "color")? { @@ -83,6 +84,7 @@ fn fade_in(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(Value::Color(Box::new(color.fade_in(amount)))) } +// todo: unify with `fade_out` fn transparentize(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let color = match args.get_err(0, "color")? { diff --git a/src/builtin/functions/color/other.rs b/src/builtin/functions/color/other.rs index af1367d..dc4d8ae 100644 --- a/src/builtin/functions/color/other.rs +++ b/src/builtin/functions/color/other.rs @@ -46,7 +46,7 @@ macro_rules! opt_hsl { }; } -fn change_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn change_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { if args.positional_arg(1).is_some() { return Err(( "Only one positional argument is allowed. All other arguments must be passed by name.", @@ -113,7 +113,7 @@ fn change_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { +pub(crate) fn adjust_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { let color = match args.get_err(0, "color")? { Value::Color(c) => c, v => { @@ -175,8 +175,8 @@ fn adjust_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { - fn scale(val: Number, by: Number, max: Number) -> Number { +pub(crate) fn scale_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + pub(crate) fn scale(val: Number, by: Number, max: Number) -> Number { if by.is_zero() { return val; } @@ -288,7 +288,7 @@ fn scale_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult })) } -fn ie_hex_str(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn ie_hex_str(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let color = match args.get_err(0, "color")? { Value::Color(c) => c, diff --git a/src/builtin/functions/color/rgb.rs b/src/builtin/functions/color/rgb.rs index 3249f44..a744c14 100644 --- a/src/builtin/functions/color/rgb.rs +++ b/src/builtin/functions/color/rgb.rs @@ -336,15 +336,15 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> } } -fn rgb(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn rgb(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { inner_rgb("rgb", args, parser) } -fn rgba(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn rgba(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { inner_rgb("rgba", args, parser) } -fn red(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn red(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { Value::Color(c) => Ok(Value::Dimension(c.red(), Unit::None, true)), @@ -356,7 +356,7 @@ fn red(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } -fn green(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn green(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { Value::Color(c) => Ok(Value::Dimension(c.green(), Unit::None, true)), @@ -368,7 +368,7 @@ fn green(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } -fn blue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn blue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { Value::Color(c) => Ok(Value::Dimension(c.blue(), Unit::None, true)), @@ -380,7 +380,7 @@ fn blue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } } -fn mix(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +pub(crate) fn mix(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(3)?; let color1 = match args.get_err(0, "color1")? { Value::Color(c) => c, diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index e31fb8c..7dd9b7d 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -1,4 +1,6 @@ mod functions; pub(crate) mod modules; -pub(crate) use functions::{list, map, math, meta, selector, string, Builtin, GLOBAL_FUNCTIONS}; +pub(crate) use functions::{ + color, list, map, math, meta, selector, string, Builtin, GLOBAL_FUNCTIONS, +}; diff --git a/src/builtin/modules/color.rs b/src/builtin/modules/color.rs index 47d58dc..e27a985 100644 --- a/src/builtin/modules/color.rs +++ b/src/builtin/modules/color.rs @@ -1,5 +1,38 @@ use crate::{ - args::CallArgs, builtin::modules::Module, error::SassResult, parse::Parser, value::Value, + args::CallArgs, + builtin::{ + color::{ + hsl::{complement, grayscale, hue, invert, lightness, saturation}, + opacity::alpha, + other::{adjust_color, change_color, ie_hex_str, scale_color}, + rgb::{blue, green, mix, red}, + }, + modules::Module, + }, + error::SassResult, + parse::Parser, + value::Value, }; -pub(crate) fn declare(_f: &mut Module) {} +fn adjust_hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(2)?; + todo!() +} + +pub(crate) fn declare(f: &mut Module) { + f.insert_builtin("adjust", adjust_color); + f.insert_builtin("alpha", alpha); + f.insert_builtin("blue", blue); + f.insert_builtin("change", change_color); + f.insert_builtin("complement", complement); + f.insert_builtin("grayscale", grayscale); + f.insert_builtin("green", green); + f.insert_builtin("hue", hue); + f.insert_builtin("ie-hex-str", ie_hex_str); + f.insert_builtin("invert", invert); + f.insert_builtin("lightness", lightness); + f.insert_builtin("mix", mix); + f.insert_builtin("red", red); + f.insert_builtin("saturation", saturation); + f.insert_builtin("scale", scale_color); +} From eeb0b0a924d02d88d45523125d439b81962f71f4 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 13:12:35 -0400 Subject: [PATCH 13/59] implement builtin `sass:math` function `clamp` --- src/builtin/functions/meta.rs | 8 +--- src/builtin/modules/math.rs | 83 ++++++++++++++++++++++++++++++++++- src/parse/value/eval.rs | 50 ++------------------- src/value/mod.rs | 61 ++++++++++++++++++++++++- src/value/number/mod.rs | 26 ++++++++++- tests/math-module.rs | 40 +++++++++++++++++ tests/ordering.rs | 5 +++ 7 files changed, 216 insertions(+), 57 deletions(-) create mode 100644 tests/math-module.rs diff --git a/src/builtin/functions/meta.rs b/src/builtin/functions/meta.rs index d6e3fd9..7ad6e34 100644 --- a/src/builtin/functions/meta.rs +++ b/src/builtin/functions/meta.rs @@ -7,7 +7,6 @@ use crate::{ common::{Identifier, QuoteKind}, error::SassResult, parse::Parser, - unit::Unit, value::{SassFunction, Value}, }; @@ -73,12 +72,7 @@ pub(crate) fn type_of(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")? { - Value::Dimension(_, Unit::None, _) => Value::True, - Value::Dimension(..) => Value::False, - _ => Value::True, - }) + Ok(Value::bool(args.get_err(0, "number")?.unitless())) } pub(crate) fn inspect(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index 36f8019..533f6e9 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use crate::{ args::CallArgs, builtin::{ @@ -5,6 +7,7 @@ use crate::{ meta::{unit, unitless}, modules::Module, }, + common::Op, error::SassResult, parse::Parser, unit::Unit, @@ -16,7 +19,84 @@ use crate::builtin::math::random; fn clamp(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(3)?; - todo!() + let span = args.span(); + + let min = match args.get_err(0, "min")? { + v @ Value::Dimension(..) => v, + v => { + return Err(( + format!("$min: {} is not a number.", v.inspect(args.span())?), + span, + ) + .into()) + } + }; + + let number = match args.get_err(1, "number")? { + v @ Value::Dimension(..) => v, + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(span)?), + span, + ) + .into()) + } + }; + + let max = match args.get_err(2, "max")? { + v @ Value::Dimension(..) => v, + v => return Err((format!("$max: {} is not a number.", v.inspect(span)?), span).into()), + }; + + // ensure that `min` and `max` are compatible + min.cmp(&max, span, Op::LessThan)?; + + let min_unit = match min { + Value::Dimension(_, ref u, _) => u, + _ => unreachable!(), + }; + let number_unit = match number { + Value::Dimension(_, ref u, _) => u, + _ => unreachable!(), + }; + let max_unit = match max { + Value::Dimension(_, ref u, _) => u, + _ => unreachable!(), + }; + + if min_unit == &Unit::None && number_unit != &Unit::None { + return Err(( + format!( + "$min is unitless but $number has unit {}. Arguments must all have units or all be unitless.", + number_unit + ), span).into()); + } else if min_unit != &Unit::None && number_unit == &Unit::None { + return Err(( + format!( + "$min has unit {} but $number is unitless. Arguments must all have units or all be unitless.", + min_unit + ), span).into()); + } else if min_unit != &Unit::None && max_unit == &Unit::None { + return Err(( + format!( + "$min has unit {} but $max is unitless. Arguments must all have units or all be unitless.", + min_unit + ), span).into()); + } + + match min.cmp(&number, span, Op::LessThan)? { + Ordering::Greater => return Ok(min), + Ordering::Equal => return Ok(number), + Ordering::Less => {} + } + + match max.cmp(&number, span, Op::GreaterThan)? { + Ordering::Less => return Ok(max), + Ordering::Equal => return Ok(number), + Ordering::Greater => {} + } + + Ok(number) } fn hypot(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { @@ -84,6 +164,7 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("is-unitless", unitless); f.insert_builtin("unit", unit); f.insert_builtin("percentage", percentage); + f.insert_builtin("clamp", clamp); #[cfg(feature = "random")] f.insert_builtin("random", random); diff --git a/src/parse/value/eval.rs b/src/parse/value/eval.rs index 94c5101..736eed9 100644 --- a/src/parse/value/eval.rs +++ b/src/parse/value/eval.rs @@ -735,53 +735,9 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { HigherIntermediateValue::Literal(v) => v, v => panic!("{:?}", v), }; - let ordering = match left { - Value::Dimension(num, unit, _) => match &right { - Value::Dimension(num2, unit2, _) => { - if !unit.comparable(unit2) { - return Err(( - format!("Incompatible units {} and {}.", unit2, unit), - self.span, - ) - .into()); - } - if &unit == unit2 || unit == Unit::None || unit2 == &Unit::None { - num.cmp(num2) - } else { - num.cmp( - &(num2.clone() - * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] - [unit2.to_string().as_str()] - .clone()), - ) - } - } - v => { - return Err(( - format!( - "Undefined operation \"{} {} {}\".", - v.inspect(self.span)?, - op, - right.inspect(self.span)? - ), - self.span, - ) - .into()) - } - }, - _ => { - return Err(( - format!( - "Undefined operation \"{} {} {}\".", - left.inspect(self.span)?, - op, - right.inspect(self.span)? - ), - self.span, - ) - .into()) - } - }; + + let ordering = left.cmp(&right, self.span, op)?; + Ok(match op { Op::GreaterThan => match ordering { Ordering::Greater => Value::True, diff --git a/src/value/mod.rs b/src/value/mod.rs index 5cf7cd4..7766b3f 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -1,10 +1,12 @@ +use std::cmp::Ordering; + use peekmore::PeekMore; use codemap::{Span, Spanned}; use crate::{ color::Color, - common::{Brackets, ListSeparator, QuoteKind}, + common::{Brackets, ListSeparator, Op, QuoteKind}, error::SassResult, parse::Parser, selector::Selector, @@ -322,6 +324,63 @@ impl Value { } } + pub fn cmp(&self, other: &Self, span: Span, op: Op) -> SassResult { + Ok(match self { + Value::Dimension(num, unit, _) => match &other { + Value::Dimension(num2, unit2, _) => { + if !unit.comparable(unit2) { + return Err( + (format!("Incompatible units {} and {}.", unit2, unit), span).into(), + ); + } + if unit == unit2 || unit == &Unit::None || unit2 == &Unit::None { + num.cmp(num2) + } else { + num.cmp( + &(num2.clone() + * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] + [unit2.to_string().as_str()] + .clone()), + ) + } + } + v => { + return Err(( + format!( + "Undefined operation \"{} {} {}\".", + v.inspect(span)?, + op, + other.inspect(span)? + ), + span, + ) + .into()) + } + }, + _ => { + return Err(( + format!( + "Undefined operation \"{} {} {}\".", + self.inspect(span)?, + op, + other.inspect(span)? + ), + span, + ) + .into()) + } + }) + } + + pub fn unitless(&self) -> bool { + #[allow(clippy::match_same_arms)] + match self { + Value::Dimension(_, Unit::None, _) => true, + Value::Dimension(..) => false, + _ => true, + } + } + pub fn not_equals(&self, other: &Self) -> bool { match self { Value::String(s1, ..) => match other { diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index 00152b5..9cbb4ed 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -16,7 +16,7 @@ mod integer; const PRECISION: usize = 10; -#[derive(Clone, Eq, PartialEq, Ord)] +#[derive(Clone, Eq, PartialEq)] pub(crate) enum Number { Small(Rational64), Big(Box), @@ -321,6 +321,30 @@ impl PartialOrd for Number { } } +impl Ord for Number { + fn cmp(&self, other: &Self) -> Ordering { + match self { + Self::Small(val1) => match other { + Self::Small(val2) => val1.cmp(val2), + Self::Big(val2) => { + let tuple: (i64, i64) = (*val1).into(); + BigRational::new_raw(BigInt::from(tuple.0), BigInt::from(tuple.1)).cmp(val2) + } + }, + Self::Big(val1) => match other { + Self::Small(val2) => { + let tuple: (i64, i64) = (*val2).into(); + (**val1).cmp(&BigRational::new_raw( + BigInt::from(tuple.0), + BigInt::from(tuple.1), + )) + } + Self::Big(val2) => val1.cmp(val2), + }, + } + } +} + impl Add for Number { type Output = Self; diff --git a/tests/math-module.rs b/tests/math-module.rs new file mode 100644 index 0000000..50030df --- /dev/null +++ b/tests/math-module.rs @@ -0,0 +1,40 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +test!( + clamp_in_the_middle, + "@use 'sass:math';\na {\n color: math.clamp(0, 1, 2);\n}\n", + "a {\n color: 1;\n}\n" +); +test!( + clamp_first_is_bigger, + "@use 'sass:math';\na {\n color: math.clamp(2, 1, 0);\n}\n", + "a {\n color: 2;\n}\n" +); +test!( + clamp_all_same_unit, + "@use 'sass:math';\na {\n color: math.clamp(0px, 1px, 2px);\n}\n", + "a {\n color: 1px;\n}\n" +); +test!( + clamp_all_different_but_compatible_unit, + "@use 'sass:math';\na {\n color: math.clamp(0mm, 1cm, 2in);\n}\n", + "a {\n color: 1cm;\n}\n" +); +error!( + clamp_only_min_has_no_unit, + "@use 'sass:math';\na {\n color: math.clamp(0, 1cm, 2in);\n}\n", + "Error: $min is unitless but $number has unit cm. Arguments must all have units or all be unitless." +); +error!( + clamp_only_number_has_no_unit, + "@use 'sass:math';\na {\n color: math.clamp(0mm, 1, 2in);\n}\n", + "Error: $min has unit mm but $number is unitless. Arguments must all have units or all be unitless." +); +error!( + clamp_only_max_has_no_unit, + "@use 'sass:math';\na {\n color: math.clamp(0mm, 1cm, 2);\n}\n", + "Error: $min has unit mm but $max is unitless. Arguments must all have units or all be unitless." +); diff --git a/tests/ordering.rs b/tests/ordering.rs index ae00b88..5a2b644 100644 --- a/tests/ordering.rs +++ b/tests/ordering.rs @@ -63,3 +63,8 @@ test!( "a {\n color: 0 < 1;\n}\n", "a {\n color: true;\n}\n" ); +test!( + ord_the_same_as_partial_ord, + "a {\n color: 2in > 1cm;\n}\n", + "a {\n color: true;\n}\n" +); From 0916dcc5bd788315127cb32db2ee1893954ad45d Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 13:36:01 -0400 Subject: [PATCH 14/59] support the `@use ... as ...;` syntax --- src/parse/mod.rs | 81 ++++++++++++++++++++++++++++++++++-------------- tests/use.rs | 31 ++++++++++++++++++ 2 files changed, 89 insertions(+), 23 deletions(-) diff --git a/src/parse/mod.rs b/src/parse/mod.rs index a534f9e..d63cc2d 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -149,32 +149,67 @@ impl<'a> Parser<'a> { let Spanned { node: module, span } = self.parse_quoted_string(quote)?; let module = module.unquote().to_css_string(span)?; - if let Some(Token { kind: ';', .. }) = self.toks.peek() { - self.toks.next(); - } else { - todo!() + self.whitespace_or_comment(); + + let mut module_name: Option = None; + + match self.toks.peek() { + Some(Token { kind: ';', .. }) => { + self.toks.next(); + } + Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) => { + let mut ident = + peek_ident_no_interpolation(self.toks, false, self.span_before)?; + ident.node.make_ascii_lowercase(); + if ident.node != "as" { + return Err(("expected \";\".", ident.span).into()); + } + + self.whitespace_or_comment(); + + let name = self.parse_identifier_no_interpolation(false)?; + + module_name = Some(name.node); + + if !matches!(self.toks.next(), Some(Token { kind: ';', .. })) { + return Err(("expected \";\".", name.span).into()); + } + } + Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. }) => { + todo!("with") + } + Some(..) | None => return Err(("expected \";\".", span).into()), } match module.as_ref() { - "sass:color" => self - .modules - .insert("color".to_owned(), declare_module_color()), - "sass:list" => self - .modules - .insert("list".to_owned(), declare_module_list()), - "sass:map" => self.modules.insert("map".to_owned(), declare_module_map()), - "sass:math" => self - .modules - .insert("math".to_owned(), declare_module_math()), - "sass:meta" => self - .modules - .insert("meta".to_owned(), declare_module_meta()), - "sass:selector" => self - .modules - .insert("selector".to_owned(), declare_module_selector()), - "sass:string" => self - .modules - .insert("string".to_owned(), declare_module_string()), + "sass:color" => self.modules.insert( + module_name.unwrap_or("color".to_owned()), + declare_module_color(), + ), + "sass:list" => self.modules.insert( + module_name.unwrap_or("list".to_owned()), + declare_module_list(), + ), + "sass:map" => self.modules.insert( + module_name.unwrap_or("map".to_owned()), + declare_module_map(), + ), + "sass:math" => self.modules.insert( + module_name.unwrap_or("math".to_owned()), + declare_module_math(), + ), + "sass:meta" => self.modules.insert( + module_name.unwrap_or("meta".to_owned()), + declare_module_meta(), + ), + "sass:selector" => self.modules.insert( + module_name.unwrap_or("selector".to_owned()), + declare_module_selector(), + ), + "sass:string" => self.modules.insert( + module_name.unwrap_or("string".to_owned()), + declare_module_string(), + ), _ => todo!("@use not yet implemented"), }; } diff --git a/tests/use.rs b/tests/use.rs index e673c9f..7a53bff 100644 --- a/tests/use.rs +++ b/tests/use.rs @@ -10,3 +10,34 @@ error!( ", "Error: @use rules must be written before any other rules." ); +error!( + interpolation_in_as_identifier, + "@use \"sass:math\" as m#{a}th;", + "Error: expected \";\"." +); +error!( + use_as_quoted_string, + "@use \"sass:math\" as \"math\";", + "Error: Expected identifier." +); +error!( + use_as_missing_s, + "@use \"sass:math\" a math;", + "Error: expected \";\"." +); +test!( + use_as, + "@use \"sass:math\" as foo; + a { + color: foo.clamp(0, 1, 2); + }", + "a {\n color: 1;\n}\n" +); +test!( + use_as_uppercase, + "@use \"sass:math\" AS foo; + a { + color: foo.clamp(0, 1, 2); + }", + "a {\n color: 1;\n}\n" +); From ee57cda9c52686710f37a5f716a7fdd6c30cd705 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 13:44:30 -0400 Subject: [PATCH 15/59] give better error messages for undefined modules and functions --- src/parse/value/parse.rs | 6 +++--- tests/use.rs | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 449a1d6..48ed8f8 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -224,7 +224,7 @@ impl<'a> Parser<'a> { let value = self .modules .get(module) - .ok_or(("todo: module dne", module_span))? + .ok_or((format!("There is no module with the namespace \"{}\".", module), module_span))? .get_var(var)?; HigherIntermediateValue::Literal(value.clone()) } else { @@ -235,9 +235,9 @@ impl<'a> Parser<'a> { let function = self .modules .get(module) - .ok_or(("todo: module dne", module_span))? + .ok_or((format!("There is no module with the namespace \"{}\".", module), module_span))? .get_fn(fn_name.node) - .ok_or(("todo: fn dne", fn_name.span))?; + .ok_or(("Undefined function.", fn_name.span))?; if !matches!(self.toks.next(), Some(Token { kind: '(', .. })) { todo!() diff --git a/tests/use.rs b/tests/use.rs index 7a53bff..07072ba 100644 --- a/tests/use.rs +++ b/tests/use.rs @@ -25,6 +25,21 @@ error!( "@use \"sass:math\" a math;", "Error: expected \";\"." ); +error!( + unknown_module_get_variable, + "a { color: foo.$bar; }", + "Error: There is no module with the namespace \"foo\"." +); +error!( + unknown_module_get_function, + "a { color: foo.bar(); }", + "Error: There is no module with the namespace \"foo\"." +); +error!( + unknown_function, + "@use \"sass:math\";\na { color: math.bar(); }", + "Error: Undefined function." +); test!( use_as, "@use \"sass:math\" as foo; From dbfa69150586ef870f2f61702f15d7217ff429ad Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 13:49:13 -0400 Subject: [PATCH 16/59] resolve clippy lints --- src/builtin/functions/meta.rs | 3 ++- src/builtin/modules/mod.rs | 2 +- src/parse/mod.rs | 23 +++++++++++++---------- src/parse/value/parse.rs | 15 +++++++++------ tests/use.rs | 18 ++++++------------ 5 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/builtin/functions/meta.rs b/src/builtin/functions/meta.rs index 7ad6e34..2190701 100644 --- a/src/builtin/functions/meta.rs +++ b/src/builtin/functions/meta.rs @@ -223,9 +223,10 @@ pub(crate) fn content_exists(args: CallArgs, parser: &mut Parser<'_>) -> SassRes )) } -#[allow(dead_code, unused_mut, unused_variables)] +#[allow(unused_variables)] pub(crate) fn keywords(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; + drop(args); todo!("builtin function `keywords` blocked on better handling of call args") } diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index 45bd3ca..f32917f 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -32,7 +32,7 @@ pub(crate) struct Module { impl Module { pub fn get_var(&self, name: Spanned) -> SassResult<&Value> { match self.vars.get(&name.node) { - Some(v) => Ok(&v), + Some(v) => Ok(v), None => Err(("Undefined variable.", name.span).into()), } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index d63cc2d..7e77032 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -9,7 +9,10 @@ use crate::{ media::MediaRule, AtRuleKind, Content, SupportsRule, UnknownAtRule, }, - builtin::modules::*, + builtin::modules::{ + declare_module_color, declare_module_list, declare_module_map, declare_module_math, + declare_module_meta, declare_module_selector, declare_module_string, Module, + }, error::SassResult, scope::{Scope, Scopes}, selector::{ @@ -110,6 +113,7 @@ impl<'a> Parser<'a> { /// Returns any multiline comments that may have been found /// while loading modules + #[allow(clippy::eval_order_dependence)] fn load_modules(&mut self) -> SassResult> { let mut comments = Vec::new(); @@ -142,8 +146,7 @@ impl<'a> Parser<'a> { let quote = match self.toks.next() { Some(Token { kind: q @ '"', .. }) | Some(Token { kind: q @ '\'', .. }) => q, - Some(..) => todo!(), - None => todo!(), + Some(..) | None => todo!(), }; let Spanned { node: module, span } = self.parse_quoted_string(quote)?; @@ -183,31 +186,31 @@ impl<'a> Parser<'a> { match module.as_ref() { "sass:color" => self.modules.insert( - module_name.unwrap_or("color".to_owned()), + module_name.unwrap_or_else(|| "color".to_owned()), declare_module_color(), ), "sass:list" => self.modules.insert( - module_name.unwrap_or("list".to_owned()), + module_name.unwrap_or_else(|| "list".to_owned()), declare_module_list(), ), "sass:map" => self.modules.insert( - module_name.unwrap_or("map".to_owned()), + module_name.unwrap_or_else(|| "map".to_owned()), declare_module_map(), ), "sass:math" => self.modules.insert( - module_name.unwrap_or("math".to_owned()), + module_name.unwrap_or_else(|| "math".to_owned()), declare_module_math(), ), "sass:meta" => self.modules.insert( - module_name.unwrap_or("meta".to_owned()), + module_name.unwrap_or_else(|| "meta".to_owned()), declare_module_meta(), ), "sass:selector" => self.modules.insert( - module_name.unwrap_or("selector".to_owned()), + module_name.unwrap_or_else(|| "selector".to_owned()), declare_module_selector(), ), "sass:string" => self.modules.insert( - module_name.unwrap_or("string".to_owned()), + module_name.unwrap_or_else(|| "string".to_owned()), declare_module_string(), ), _ => todo!("@use not yet implemented"), diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 48ed8f8..be1ce79 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -207,6 +207,7 @@ impl<'a> Parser<'a> { .parse_value(in_paren) } + #[allow(clippy::eval_order_dependence)] fn parse_module_item( &mut self, module: &str, @@ -224,7 +225,10 @@ impl<'a> Parser<'a> { let value = self .modules .get(module) - .ok_or((format!("There is no module with the namespace \"{}\".", module), module_span))? + .ok_or(( + format!("There is no module with the namespace \"{}\".", module), + module_span, + ))? .get_var(var)?; HigherIntermediateValue::Literal(value.clone()) } else { @@ -235,7 +239,10 @@ impl<'a> Parser<'a> { let function = self .modules .get(module) - .ok_or((format!("There is no module with the namespace \"{}\".", module), module_span))? + .ok_or(( + format!("There is no module with the namespace \"{}\".", module), + module_span, + ))? .get_fn(fn_name.node) .ok_or(("Undefined function.", fn_name.span))?; @@ -251,10 +258,6 @@ impl<'a> Parser<'a> { .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()?; diff --git a/tests/use.rs b/tests/use.rs index 07072ba..b40a5d3 100644 --- a/tests/use.rs +++ b/tests/use.rs @@ -12,33 +12,27 @@ error!( ); error!( interpolation_in_as_identifier, - "@use \"sass:math\" as m#{a}th;", - "Error: expected \";\"." + "@use \"sass:math\" as m#{a}th;", "Error: expected \";\"." ); error!( use_as_quoted_string, - "@use \"sass:math\" as \"math\";", - "Error: Expected identifier." + "@use \"sass:math\" as \"math\";", "Error: Expected identifier." ); error!( use_as_missing_s, - "@use \"sass:math\" a math;", - "Error: expected \";\"." + "@use \"sass:math\" a math;", "Error: expected \";\"." ); error!( unknown_module_get_variable, - "a { color: foo.$bar; }", - "Error: There is no module with the namespace \"foo\"." + "a { color: foo.$bar; }", "Error: There is no module with the namespace \"foo\"." ); error!( unknown_module_get_function, - "a { color: foo.bar(); }", - "Error: There is no module with the namespace \"foo\"." + "a { color: foo.bar(); }", "Error: There is no module with the namespace \"foo\"." ); error!( unknown_function, - "@use \"sass:math\";\na { color: math.bar(); }", - "Error: Undefined function." + "@use \"sass:math\";\na { color: math.bar(); }", "Error: Undefined function." ); test!( use_as, From 53cf2816e0ec61c7f5c45544783725274d46a570 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 19:38:41 -0400 Subject: [PATCH 17/59] use `None` to represent `NaN` --- src/builtin/functions/color/hsl.rs | 58 ++++++++------ src/builtin/functions/color/opacity.rs | 20 +++-- src/builtin/functions/color/other.rs | 15 ++-- src/builtin/functions/color/rgb.rs | 59 ++++++++------ src/builtin/functions/list.rs | 10 ++- src/builtin/functions/math.rs | 44 ++++++----- src/builtin/functions/string.rs | 31 ++++---- src/builtin/modules/math.rs | 4 +- src/color/mod.rs | 4 +- src/parse/control_flow.rs | 8 +- src/parse/value/eval.rs | 105 +++++++++++++++---------- src/parse/value/parse.rs | 8 +- src/value/mod.rs | 24 +++--- tests/division.rs | 5 ++ tests/meta.rs | 5 ++ 15 files changed, 248 insertions(+), 152 deletions(-) diff --git a/src/builtin/functions/color/hsl.rs b/src/builtin/functions/color/hsl.rs index 72821bb..b97ef24 100644 --- a/src/builtin/functions/color/hsl.rs +++ b/src/builtin/functions/color/hsl.rs @@ -35,7 +35,8 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> } let lightness = match channels.pop() { - Some(Value::Dimension(n, ..)) => n / Number::from(100), + Some(Value::Dimension(Some(n), ..)) => n / Number::from(100), + Some(Value::Dimension(None, ..)) => todo!(), Some(v) => { return Err(( format!("$lightness: {} is not a number.", v.inspect(args.span())?), @@ -47,7 +48,8 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> }; let saturation = match channels.pop() { - Some(Value::Dimension(n, ..)) => n / Number::from(100), + Some(Value::Dimension(Some(n), ..)) => n / Number::from(100), + Some(Value::Dimension(None, ..)) => todo!(), Some(v) => { return Err(( format!("$saturation: {} is not a number.", v.inspect(args.span())?), @@ -59,7 +61,8 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> }; let hue = match channels.pop() { - Some(Value::Dimension(n, ..)) => n, + Some(Value::Dimension(Some(n), ..)) => n, + Some(Value::Dimension(None, ..)) => todo!(), Some(v) => { return Err(( format!("$hue: {} is not a number.", v.inspect(args.span())?), @@ -78,7 +81,8 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> )))) } else { let hue = match args.get_err(0, "hue")? { - Value::Dimension(n, ..) => n, + Value::Dimension(Some(n), ..) => n, + Value::Dimension(None, ..) => todo!(), v if v.is_special_function() => { let saturation = args.get_err(1, "saturation")?; let lightness = args.get_err(2, "lightness")?; @@ -105,7 +109,8 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> } }; let saturation = match args.get_err(1, "saturation")? { - Value::Dimension(n, ..) => n / Number::from(100), + Value::Dimension(Some(n), ..) => n / Number::from(100), + Value::Dimension(None, ..) => todo!(), v if v.is_special_function() => { let lightness = args.get_err(2, "lightness")?; let mut string = format!( @@ -134,7 +139,8 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> } }; let lightness = match args.get_err(2, "lightness")? { - Value::Dimension(n, ..) => n / Number::from(100), + Value::Dimension(Some(n), ..) => n / Number::from(100), + Value::Dimension(None, ..) => todo!(), v if v.is_special_function() => { let mut string = format!( "{}({}, {}, {}", @@ -164,10 +170,11 @@ fn inner_hsl(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> let alpha = match args.default_arg( 3, "alpha", - Value::Dimension(Number::one(), Unit::None, true), + Value::Dimension(Some(Number::one()), Unit::None, true), )? { - Value::Dimension(n, Unit::None, _) => n, - Value::Dimension(n, Unit::Percent, _) => n / Number::from(100), + Value::Dimension(Some(n), Unit::None, _) => n, + Value::Dimension(Some(n), Unit::Percent, _) => n / Number::from(100), + Value::Dimension(None, ..) => todo!(), v @ Value::Dimension(..) => { return Err(( format!( @@ -216,7 +223,7 @@ pub(crate) fn hsla(args: CallArgs, parser: &mut Parser<'_>) -> SassResult pub(crate) fn hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { - Value::Color(c) => Ok(Value::Dimension(c.hue(), Unit::Deg, true)), + Value::Color(c) => Ok(Value::Dimension(Some(c.hue()), Unit::Deg, true)), v => Err(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -228,7 +235,7 @@ pub(crate) fn hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { - Value::Color(c) => Ok(Value::Dimension(c.saturation(), Unit::Percent, true)), + Value::Color(c) => Ok(Value::Dimension(Some(c.saturation()), Unit::Percent, true)), v => Err(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -240,7 +247,7 @@ pub(crate) fn saturation(mut args: CallArgs, parser: &mut Parser<'_>) -> SassRes pub(crate) fn lightness(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { - Value::Color(c) => Ok(Value::Dimension(c.lightness(), Unit::Percent, true)), + Value::Color(c) => Ok(Value::Dimension(Some(c.lightness()), Unit::Percent, true)), v => Err(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -262,7 +269,8 @@ pub(crate) fn adjust_hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassRes } }; let degrees = match args.get_err(1, "degrees")? { - Value::Dimension(n, ..) => n, + Value::Dimension(Some(n), ..) => n, + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!( @@ -290,7 +298,8 @@ fn lighten(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } }; let amount = match args.get_err(1, "amount")? { - Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 100) / Number::from(100), + Value::Dimension(Some(n), u, _) => bound!(args, "amount", n, u, 0, 100) / Number::from(100), + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!( @@ -318,7 +327,8 @@ fn darken(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } }; let amount = match args.get_err(1, "amount")? { - Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 100) / Number::from(100), + Value::Dimension(Some(n), u, _) => bound!(args, "amount", n, u, 0, 100) / Number::from(100), + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!( @@ -346,7 +356,8 @@ fn saturate(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } let amount = match args.get_err(1, "amount")? { - Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 100) / Number::from(100), + Value::Dimension(Some(n), u, _) => bound!(args, "amount", n, u, 0, 100) / Number::from(100), + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!( @@ -360,7 +371,7 @@ fn saturate(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { }; let color = match args.get_err(0, "color")? { Value::Color(c) => c, - Value::Dimension(n, u, _) => { + Value::Dimension(Some(n), u, _) => { return Ok(Value::String( format!("saturate({}{})", n, u), QuoteKind::None, @@ -390,7 +401,8 @@ fn desaturate(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult } }; let amount = match args.get_err(1, "amount")? { - Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 100) / Number::from(100), + Value::Dimension(Some(n), u, _) => bound!(args, "amount", n, u, 0, 100) / Number::from(100), + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!( @@ -409,7 +421,7 @@ pub(crate) fn grayscale(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResu args.max_args(1)?; let color = match args.get_err(0, "color")? { Value::Color(c) => c, - Value::Dimension(n, u, _) => { + Value::Dimension(Some(n), u, _) => { return Ok(Value::String( format!("grayscale({}{})", n, u), QuoteKind::None, @@ -446,9 +458,10 @@ pub(crate) fn invert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult< let weight = match args.default_arg( 1, "weight", - Value::Dimension(Number::from(100), Unit::Percent, true), + Value::Dimension(Some(Number::from(100)), Unit::Percent, true), )? { - Value::Dimension(n, u, _) => bound!(args, "weight", n, u, 0, 100) / Number::from(100), + Value::Dimension(Some(n), u, _) => bound!(args, "weight", n, u, 0, 100) / Number::from(100), + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!( @@ -462,9 +475,10 @@ pub(crate) fn invert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult< }; match args.get_err(0, "color")? { Value::Color(c) => Ok(Value::Color(Box::new(c.invert(weight)))), - Value::Dimension(n, Unit::Percent, _) => { + Value::Dimension(Some(n), Unit::Percent, _) => { Ok(Value::String(format!("invert({}%)", n), QuoteKind::None)) } + Value::Dimension(None, ..) => todo!(), Value::Dimension(..) => Err(( "Only one argument may be passed to the plain-CSS invert() function.", args.span(), diff --git a/src/builtin/functions/color/opacity.rs b/src/builtin/functions/color/opacity.rs index 553740e..b6f82eb 100644 --- a/src/builtin/functions/color/opacity.rs +++ b/src/builtin/functions/color/opacity.rs @@ -8,7 +8,8 @@ use crate::{ pub(crate) fn alpha(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { - Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None, true)), + Value::Color(c) => Ok(Value::Dimension(Some(c.alpha()), Unit::None, true)), + Value::Dimension(None, ..) => todo!(), v => Err(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -20,11 +21,12 @@ pub(crate) fn alpha(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { - Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None, true)), - Value::Dimension(num, unit, _) => Ok(Value::String( + Value::Color(c) => Ok(Value::Dimension(Some(c.alpha()), Unit::None, true)), + Value::Dimension(Some(num), unit, _) => Ok(Value::String( format!("opacity({}{})", num, unit), QuoteKind::None, )), + Value::Dimension(None, ..) => todo!(), v => Err(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -47,7 +49,8 @@ fn opacify(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } }; let amount = match args.get_err(1, "amount")? { - Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 1), + Value::Dimension(Some(n), u, _) => bound!(args, "amount", n, u, 0, 1), + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!("$amount: {} is not a number.", v.inspect(args.span())?), @@ -72,7 +75,8 @@ fn fade_in(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } }; let amount = match args.get_err(1, "amount")? { - Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 1), + Value::Dimension(Some(n), u, _) => bound!(args, "amount", n, u, 0, 1), + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!("$amount: {} is not a number.", v.inspect(args.span())?), @@ -98,7 +102,8 @@ fn transparentize(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult bound!(args, "amount", n, u, 0, 1), + Value::Dimension(Some(n), u, _) => bound!(args, "amount", n, u, 0, 1), + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!("$amount: {} is not a number.", v.inspect(args.span())?), @@ -123,7 +128,8 @@ fn fade_out(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } }; let amount = match args.get_err(1, "amount")? { - Value::Dimension(n, u, _) => bound!(args, "amount", n, u, 0, 1), + Value::Dimension(Some(n), u, _) => bound!(args, "amount", n, u, 0, 1), + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!("$amount: {} is not a number.", v.inspect(args.span())?), diff --git a/src/builtin/functions/color/other.rs b/src/builtin/functions/color/other.rs index dc4d8ae..5b0b66e 100644 --- a/src/builtin/functions/color/other.rs +++ b/src/builtin/functions/color/other.rs @@ -15,7 +15,8 @@ use crate::{ macro_rules! opt_rgba { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => { let $name = match $args.default_named_arg($arg, Value::Null)? { - Value::Dimension(n, u, _) => Some(bound!($args, $arg, n, u, $low, $high)), + Value::Dimension(Some(n), u, _) => Some(bound!($args, $arg, n, u, $low, $high)), + Value::Dimension(None, ..) => todo!(), Value::Null => None, v => { return Err(( @@ -31,9 +32,10 @@ macro_rules! opt_rgba { macro_rules! opt_hsl { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => { let $name = match $args.default_named_arg($arg, Value::Null)? { - Value::Dimension(n, u, _) => { + Value::Dimension(Some(n), u, _) => { Some(bound!($args, $arg, n, u, $low, $high) / Number::from(100)) } + Value::Dimension(None, ..) => todo!(), Value::Null => None, v => { return Err(( @@ -81,7 +83,8 @@ pub(crate) fn change_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassR } let hue = match args.default_named_arg("hue", Value::Null)? { - Value::Dimension(n, ..) => Some(n), + Value::Dimension(Some(n), ..) => Some(n), + Value::Dimension(None, ..) => todo!(), Value::Null => None, v => { return Err(( @@ -140,7 +143,8 @@ pub(crate) fn adjust_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassR } let hue = match args.default_named_arg("hue", Value::Null)? { - Value::Dimension(n, ..) => Some(n), + Value::Dimension(Some(n), ..) => Some(n), + Value::Dimension(None, ..) => todo!(), Value::Null => None, v => { return Err(( @@ -198,9 +202,10 @@ pub(crate) fn scale_color(mut args: CallArgs, parser: &mut Parser<'_>) -> SassRe macro_rules! opt_scale_arg { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => { let $name = match $args.default_named_arg($arg, Value::Null)? { - Value::Dimension(n, Unit::Percent, _) => { + Value::Dimension(Some(n), Unit::Percent, _) => { Some(bound!($args, $arg, n, Unit::Percent, $low, $high) / Number::from(100)) } + Value::Dimension(None, ..) => todo!(), v @ Value::Dimension(..) => { return Err(( format!( diff --git a/src/builtin/functions/color/rgb.rs b/src/builtin/functions/color/rgb.rs index a744c14..9bf36ab 100644 --- a/src/builtin/functions/color/rgb.rs +++ b/src/builtin/functions/color/rgb.rs @@ -38,10 +38,11 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> } let blue = match channels.pop() { - Some(Value::Dimension(n, Unit::None, _)) => n, - Some(Value::Dimension(n, Unit::Percent, _)) => { + Some(Value::Dimension(Some(n), Unit::None, _)) => n, + Some(Value::Dimension(Some(n), Unit::Percent, _)) => { (n / Number::from(100)) * Number::from(255) } + Some(Value::Dimension(None, ..)) => todo!(), Some(v) if v.is_special_function() => { let green = channels.pop().unwrap(); let red = channels.pop().unwrap(); @@ -67,10 +68,11 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> }; let green = match channels.pop() { - Some(Value::Dimension(n, Unit::None, _)) => n, - Some(Value::Dimension(n, Unit::Percent, _)) => { + Some(Value::Dimension(Some(n), Unit::None, _)) => n, + Some(Value::Dimension(Some(n), Unit::Percent, _)) => { (n / Number::from(100)) * Number::from(255) } + Some(Value::Dimension(None, ..)) => todo!(), Some(v) if v.is_special_function() => { let string = match channels.pop() { Some(red) => format!( @@ -95,10 +97,11 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> }; let red = match channels.pop() { - Some(Value::Dimension(n, Unit::None, _)) => n, - Some(Value::Dimension(n, Unit::Percent, _)) => { + Some(Value::Dimension(Some(n), Unit::None, _)) => n, + Some(Value::Dimension(Some(n), Unit::Percent, _)) => { (n / Number::from(100)) * Number::from(255) } + Some(Value::Dimension(None, ..)) => todo!(), Some(v) if v.is_special_function() => { return Ok(Value::String( format!( @@ -148,8 +151,9 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> } }; let alpha = match args.get_err(1, "alpha")? { - Value::Dimension(n, Unit::None, _) => n, - Value::Dimension(n, Unit::Percent, _) => n / Number::from(100), + Value::Dimension(Some(n), Unit::None, _) => n, + Value::Dimension(Some(n), Unit::Percent, _) => n / Number::from(100), + Value::Dimension(None, ..) => todo!(), v @ Value::Dimension(..) => { return Err(( format!( @@ -184,8 +188,11 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> Ok(Value::Color(Box::new(color.with_alpha(alpha)))) } else { let red = match args.get_err(0, "red")? { - Value::Dimension(n, Unit::None, _) => n, - Value::Dimension(n, Unit::Percent, _) => (n / Number::from(100)) * Number::from(255), + Value::Dimension(Some(n), Unit::None, _) => n, + Value::Dimension(Some(n), Unit::Percent, _) => { + (n / Number::from(100)) * Number::from(255) + } + Value::Dimension(None, ..) => todo!(), v @ Value::Dimension(..) => { return Err(( format!( @@ -222,8 +229,11 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> } }; let green = match args.get_err(1, "green")? { - Value::Dimension(n, Unit::None, _) => n, - Value::Dimension(n, Unit::Percent, _) => (n / Number::from(100)) * Number::from(255), + Value::Dimension(Some(n), Unit::None, _) => n, + Value::Dimension(Some(n), Unit::Percent, _) => { + (n / Number::from(100)) * Number::from(255) + } + Value::Dimension(None, ..) => todo!(), v @ Value::Dimension(..) => { return Err(( format!( @@ -259,8 +269,11 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> } }; let blue = match args.get_err(2, "blue")? { - Value::Dimension(n, Unit::None, _) => n, - Value::Dimension(n, Unit::Percent, _) => (n / Number::from(100)) * Number::from(255), + Value::Dimension(Some(n), Unit::None, _) => n, + Value::Dimension(Some(n), Unit::Percent, _) => { + (n / Number::from(100)) * Number::from(255) + } + Value::Dimension(None, ..) => todo!(), v @ Value::Dimension(..) => { return Err(( format!( @@ -297,10 +310,11 @@ fn inner_rgb(name: &'static str, mut args: CallArgs, parser: &mut Parser<'_>) -> let alpha = match args.default_arg( 3, "alpha", - Value::Dimension(Number::one(), Unit::None, true), + Value::Dimension(Some(Number::one()), Unit::None, true), )? { - Value::Dimension(n, Unit::None, _) => n, - Value::Dimension(n, Unit::Percent, _) => n / Number::from(100), + Value::Dimension(Some(n), Unit::None, _) => n, + Value::Dimension(Some(n), Unit::Percent, _) => n / Number::from(100), + Value::Dimension(None, ..) => todo!(), v @ Value::Dimension(..) => { return Err(( format!( @@ -347,7 +361,7 @@ pub(crate) fn rgba(args: CallArgs, parser: &mut Parser<'_>) -> SassResult pub(crate) fn red(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { - Value::Color(c) => Ok(Value::Dimension(c.red(), Unit::None, true)), + Value::Color(c) => Ok(Value::Dimension(Some(c.red()), Unit::None, true)), v => Err(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -359,7 +373,7 @@ pub(crate) fn red(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { - Value::Color(c) => Ok(Value::Dimension(c.green(), Unit::None, true)), + Value::Color(c) => Ok(Value::Dimension(Some(c.green()), Unit::None, true)), v => Err(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -371,7 +385,7 @@ pub(crate) fn green(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { args.max_args(1)?; match args.get_err(0, "color")? { - Value::Color(c) => Ok(Value::Dimension(c.blue(), Unit::None, true)), + Value::Color(c) => Ok(Value::Dimension(Some(c.blue()), Unit::None, true)), v => Err(( format!("$color: {} is not a color.", v.inspect(args.span())?), args.span(), @@ -407,9 +421,10 @@ pub(crate) fn mix(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult bound!(args, "weight", n, u, 0, 100) / Number::from(100), + Value::Dimension(Some(n), u, _) => bound!(args, "weight", n, u, 0, 100) / Number::from(100), + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!( diff --git a/src/builtin/functions/list.rs b/src/builtin/functions/list.rs index c0b8550..56ac391 100644 --- a/src/builtin/functions/list.rs +++ b/src/builtin/functions/list.rs @@ -14,7 +14,7 @@ use crate::{ pub(crate) fn length(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; Ok(Value::Dimension( - Number::from(args.get_err(0, "list")?.as_list().len()), + Some(Number::from(args.get_err(0, "list")?.as_list().len())), Unit::None, true, )) @@ -24,7 +24,8 @@ pub(crate) fn nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult num, + Value::Dimension(Some(num), ..) => num, + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!("$n: {} is not a number.", v.inspect(args.span())?), @@ -81,7 +82,8 @@ pub(crate) fn set_nth(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult v => (vec![v], ListSeparator::Space, Brackets::None), }; let n = match args.get_err(1, "n")? { - Value::Dimension(num, ..) => num, + Value::Dimension(Some(num), ..) => num, + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!("$n: {} is not a number.", v.inspect(args.span())?), @@ -244,7 +246,7 @@ pub(crate) fn index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult Number::from(v + 1), None => return Ok(Value::Null), }; - Ok(Value::Dimension(index, Unit::None, true)) + Ok(Value::Dimension(Some(index), Unit::None, true)) } pub(crate) fn zip(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { diff --git a/src/builtin/functions/math.rs b/src/builtin/functions/math.rs index 42cce7d..470937b 100644 --- a/src/builtin/functions/math.rs +++ b/src/builtin/functions/math.rs @@ -16,7 +16,8 @@ use crate::{ 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), + Value::Dimension(Some(n), Unit::None, _) => n * Number::from(100), + Value::Dimension(None, ..) => todo!(), v @ Value::Dimension(..) => { return Err(( format!( @@ -35,13 +36,14 @@ pub(crate) fn percentage(mut args: CallArgs, parser: &mut Parser<'_>) -> SassRes .into()) } }; - Ok(Value::Dimension(num, Unit::Percent, true)) + Ok(Value::Dimension(Some(num), Unit::Percent, true)) } 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)), + Value::Dimension(Some(n), u, _) => Ok(Value::Dimension(Some(n.round()), u, true)), + Value::Dimension(None, ..) => todo!(), v => Err(( format!("$number: {} is not a number.", v.inspect(args.span())?), args.span(), @@ -53,7 +55,8 @@ pub(crate) fn round(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { args.max_args(1)?; match args.get_err(0, "number")? { - Value::Dimension(n, u, _) => Ok(Value::Dimension(n.ceil(), u, true)), + Value::Dimension(Some(n), u, _) => Ok(Value::Dimension(Some(n.ceil()), u, true)), + Value::Dimension(None, ..) => todo!(), v => Err(( format!("$number: {} is not a number.", v.inspect(args.span())?), args.span(), @@ -65,7 +68,8 @@ pub(crate) fn ceil(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { args.max_args(1)?; match args.get_err(0, "number")? { - Value::Dimension(n, u, _) => Ok(Value::Dimension(n.floor(), u, true)), + Value::Dimension(Some(n), u, _) => Ok(Value::Dimension(Some(n.floor()), u, true)), + Value::Dimension(None, ..) => todo!(), v => Err(( format!("$number: {} is not a number.", v.inspect(args.span())?), args.span(), @@ -77,7 +81,8 @@ pub(crate) fn floor(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { args.max_args(1)?; match args.get_err(0, "number")? { - Value::Dimension(n, u, _) => Ok(Value::Dimension(n.abs(), u, true)), + Value::Dimension(Some(n), u, _) => Ok(Value::Dimension(Some(n.abs()), u, true)), + Value::Dimension(None, ..) => todo!(), v => Err(( format!("$number: {} is not a number.", v.inspect(args.span())?), args.span(), @@ -117,11 +122,12 @@ pub(crate) fn comparable(mut args: CallArgs, parser: &mut Parser<'_>) -> SassRes 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, + Value::Dimension(Some(n), ..) => n, + Value::Dimension(None, ..) => todo!(), Value::Null => { let mut rng = rand::thread_rng(); return Ok(Value::Dimension( - Number::from(rng.gen_range(0.0, 1.0)), + Some(Number::from(rng.gen_range(0.0, 1.0))), Unit::None, true, )); @@ -136,7 +142,7 @@ pub(crate) fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult< }; if limit.is_one() { - return Ok(Value::Dimension(Number::one(), Unit::None, true)); + return Ok(Value::Dimension(Some(Number::one()), Unit::None, true)); } if limit.is_decimal() { @@ -164,7 +170,7 @@ pub(crate) fn random(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult< let mut rng = rand::thread_rng(); Ok(Value::Dimension( - Number::from(rng.gen_range(0, limit) + 1), + Some(Number::from(rng.gen_range(0, limit) + 1)), Unit::None, true, )) @@ -177,7 +183,8 @@ pub(crate) fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult .get_variadic()? .into_iter() .map(|val| match val.node { - Value::Dimension(number, unit, _) => Ok((number, unit)), + Value::Dimension(Some(number), unit, _) => Ok((number, unit)), + Value::Dimension(None, ..) => todo!(), v => Err((format!("{} is not a number.", v.inspect(span)?), span).into()), }) .collect::>>()? @@ -190,12 +197,12 @@ pub(crate) fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult if ValueVisitor::new(parser, span) .less_than( HigherIntermediateValue::Literal(Value::Dimension( - num.0.clone(), + Some(num.0.clone()), num.1.clone(), true, )), HigherIntermediateValue::Literal(Value::Dimension( - min.0.clone(), + Some(min.0.clone()), min.1.clone(), true, )), @@ -205,7 +212,7 @@ pub(crate) fn min(args: CallArgs, parser: &mut Parser<'_>) -> SassResult min = num; } } - Ok(Value::Dimension(min.0, min.1, true)) + Ok(Value::Dimension(Some(min.0), min.1, true)) } pub(crate) fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { @@ -215,7 +222,8 @@ pub(crate) fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult .get_variadic()? .into_iter() .map(|val| match val.node { - Value::Dimension(number, unit, _) => Ok((number, unit)), + Value::Dimension(Some(number), unit, _) => Ok((number, unit)), + Value::Dimension(None, ..) => todo!(), v => Err((format!("{} is not a number.", v.inspect(span)?), span).into()), }) .collect::>>()? @@ -228,12 +236,12 @@ pub(crate) fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult if ValueVisitor::new(parser, span) .greater_than( HigherIntermediateValue::Literal(Value::Dimension( - num.0.clone(), + Some(num.0.clone()), num.1.clone(), true, )), HigherIntermediateValue::Literal(Value::Dimension( - max.0.clone(), + Some(max.0.clone()), max.1.clone(), true, )), @@ -243,7 +251,7 @@ pub(crate) fn max(args: CallArgs, parser: &mut Parser<'_>) -> SassResult max = num; } } - Ok(Value::Dimension(max.0, max.1, true)) + Ok(Value::Dimension(Some(max.0), max.1, true)) } pub(crate) fn declare(f: &mut GlobalFunctionMap) { diff --git a/src/builtin/functions/string.rs b/src/builtin/functions/string.rs index 8e95059..edcbbd7 100644 --- a/src/builtin/functions/string.rs +++ b/src/builtin/functions/string.rs @@ -49,7 +49,7 @@ pub(crate) fn str_length(mut args: CallArgs, parser: &mut Parser<'_>) -> SassRes args.max_args(1)?; match args.get_err(0, "string")? { Value::String(i, _) => Ok(Value::Dimension( - Number::from(i.chars().count()), + Some(Number::from(i.chars().count())), Unit::None, true, )), @@ -99,17 +99,18 @@ pub(crate) fn str_slice(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResu }; let str_len = string.chars().count(); let start = match args.get_err(1, "start-at")? { - Value::Dimension(n, Unit::None, _) if n.is_decimal() => { + Value::Dimension(Some(n), Unit::None, _) if n.is_decimal() => { return Err((format!("{} is not an int.", n), args.span()).into()) } - Value::Dimension(n, Unit::None, _) if n.is_positive() => { + Value::Dimension(Some(n), Unit::None, _) if n.is_positive() => { n.to_integer().to_usize().unwrap_or(str_len + 1) } - Value::Dimension(n, Unit::None, _) if n.is_zero() => 1_usize, - Value::Dimension(n, Unit::None, _) if n < -Number::from(str_len) => 1_usize, - Value::Dimension(n, Unit::None, _) => (n.to_integer() + BigInt::from(str_len + 1)) + Value::Dimension(Some(n), Unit::None, _) if n.is_zero() => 1_usize, + Value::Dimension(Some(n), Unit::None, _) if n < -Number::from(str_len) => 1_usize, + Value::Dimension(Some(n), Unit::None, _) => (n.to_integer() + BigInt::from(str_len + 1)) .to_usize() .unwrap(), + Value::Dimension(None, ..) => todo!(), v @ Value::Dimension(..) => { return Err(( format!( @@ -129,17 +130,18 @@ pub(crate) fn str_slice(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResu } }; let mut end = match args.default_arg(2, "end-at", Value::Null)? { - Value::Dimension(n, Unit::None, _) if n.is_decimal() => { + Value::Dimension(Some(n), Unit::None, _) if n.is_decimal() => { return Err((format!("{} is not an int.", n), args.span()).into()) } - Value::Dimension(n, Unit::None, _) if n.is_positive() => { + Value::Dimension(Some(n), Unit::None, _) if n.is_positive() => { n.to_integer().to_usize().unwrap_or(str_len + 1) } - Value::Dimension(n, Unit::None, _) if n.is_zero() => 0_usize, - Value::Dimension(n, Unit::None, _) if n < -Number::from(str_len) => 0_usize, - Value::Dimension(n, Unit::None, _) => (n.to_integer() + BigInt::from(str_len + 1)) + Value::Dimension(Some(n), Unit::None, _) if n.is_zero() => 0_usize, + Value::Dimension(Some(n), Unit::None, _) if n < -Number::from(str_len) => 0_usize, + Value::Dimension(Some(n), Unit::None, _) => (n.to_integer() + BigInt::from(str_len + 1)) .to_usize() .unwrap_or(str_len + 1), + Value::Dimension(None, ..) => todo!(), v @ Value::Dimension(..) => { return Err(( format!( @@ -203,7 +205,7 @@ pub(crate) fn str_index(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResu }; Ok(match s1.find(&substr) { - Some(v) => Value::Dimension(Number::from(v + 1), Unit::None, true), + Some(v) => Value::Dimension(Some(Number::from(v + 1)), Unit::None, true), None => Value::Null, }) } @@ -233,10 +235,11 @@ pub(crate) fn str_insert(mut args: CallArgs, parser: &mut Parser<'_>) -> SassRes }; let index = match args.get_err(2, "index")? { - Value::Dimension(n, Unit::None, _) if n.is_decimal() => { + Value::Dimension(Some(n), Unit::None, _) if n.is_decimal() => { return Err((format!("$index: {} is not an int.", n), args.span()).into()) } - Value::Dimension(n, Unit::None, _) => n, + Value::Dimension(Some(n), Unit::None, _) => n, + Value::Dimension(None, ..) => todo!(), v @ Value::Dimension(..) => { return Err(( format!( diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index 533f6e9..fd55a8a 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -170,10 +170,10 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin_var( "pi", - Value::Dimension(Number::from(std::f64::consts::PI), Unit::None, true), + Value::Dimension(Some(Number::from(std::f64::consts::PI)), Unit::None, true), ); f.insert_builtin_var( "e", - Value::Dimension(Number::from(std::f64::consts::E), Unit::None, true), + Value::Dimension(Some(Number::from(std::f64::consts::E)), Unit::None, true), ); } diff --git a/src/color/mod.rs b/src/color/mod.rs index 8c3da1a..af8d261 100644 --- a/src/color/mod.rs +++ b/src/color/mod.rs @@ -264,7 +264,7 @@ impl Color { return h.saturation() * Number::from(100); } - let red = self.red() / Number::from(255); + let red: Number = self.red() / Number::from(255); let green = self.green() / Number::from(255); let blue = self.blue() / Number::from(255); @@ -291,7 +291,7 @@ impl Color { return h.luminance() * Number::from(100); } - let red = self.red() / Number::from(255); + let red: Number = self.red() / Number::from(255); let green = self.green() / Number::from(255); let blue = self.blue() / Number::from(255); let min = min(&red, min(&green, &blue)).clone(); diff --git a/src/parse/control_flow.rs b/src/parse/control_flow.rs index 47d69c4..913ad2a 100644 --- a/src/parse/control_flow.rs +++ b/src/parse/control_flow.rs @@ -245,10 +245,11 @@ impl<'a> Parser<'a> { self.whitespace(); let from_val = self.parse_value_from_vec(from_toks, true)?; let from = match from_val.node { - Value::Dimension(n, ..) => match n.to_integer().to_isize() { + Value::Dimension(Some(n), ..) => match n.to_integer().to_isize() { Some(v) => v, None => return Err((format!("{} is not a int.", n), from_val.span).into()), }, + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!("{} is not an integer.", v.inspect(from_val.span)?), @@ -260,10 +261,11 @@ impl<'a> Parser<'a> { let to_val = self.parse_value(true)?; let to = match to_val.node { - Value::Dimension(n, ..) => match n.to_integer().to_isize() { + Value::Dimension(Some(n), ..) => match n.to_integer().to_isize() { Some(v) => v, None => return Err((format!("{} is not a int.", n), to_val.span).into()), }, + Value::Dimension(None, ..) => todo!(), v => { return Err(( format!("{} is not an integer.", v.to_css_string(to_val.span)?), @@ -303,7 +305,7 @@ impl<'a> Parser<'a> { self.scopes.insert_var_last( var.node, Spanned { - node: Value::Dimension(Number::from(i), Unit::None, true), + node: Value::Dimension(Some(Number::from(i)), Unit::None, true), span: var.span, }, ); diff --git a/src/parse/value/eval.rs b/src/parse/value/eval.rs index 736eed9..20ccfbd 100644 --- a/src/parse/value/eval.rs +++ b/src/parse/value/eval.rs @@ -3,6 +3,7 @@ use std::cmp::Ordering; use codemap::{Span, Spanned}; +use num_traits::Zero; use crate::{ args::CallArgs, @@ -119,7 +120,11 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { fn unary_minus(&self, val: Value) -> SassResult { Ok(match val { - Value::Dimension(n, u, should_divide) => Value::Dimension(-n, u, should_divide), + Value::Dimension(Some(n), u, should_divide) => { + Value::Dimension(Some(-n), u, should_divide) + } + // todo: NaN test + Value::Dimension(None, u, should_divide) => Value::Dimension(None, u, should_divide), v => Value::String(format!("-{}", v.to_css_string(self.span)?), QuoteKind::None), }) } @@ -205,8 +210,10 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { QuoteKind::None, ), }, - Value::Dimension(num, unit, _) => match right { - Value::Dimension(num2, unit2, _) => { + v @ Value::Dimension(None, ..) => v, + Value::Dimension(Some(num), unit, _) => match right { + v @ Value::Dimension(None, ..) => v, + Value::Dimension(Some(num2), unit2, _) => { if !unit.comparable(&unit2) { return Err(( format!("Incompatible units {} and {}.", unit2, unit), @@ -215,17 +222,19 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { .into()); } if unit == unit2 { - Value::Dimension(num + num2, unit, true) + Value::Dimension(Some(num + num2), unit, true) } else if unit == Unit::None { - Value::Dimension(num + num2, unit2, true) + Value::Dimension(Some(num + num2), unit2, true) } else if unit2 == Unit::None { - Value::Dimension(num + num2, unit, true) + Value::Dimension(Some(num + num2), unit, true) } else { Value::Dimension( - num + num2 - * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] - [unit2.to_string().as_str()] - .clone(), + Some( + num + num2 + * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] + [unit2.to_string().as_str()] + .clone(), + ), unit, true, ) @@ -314,8 +323,10 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { format!("-{}", right.to_css_string(self.span)?), QuoteKind::None, ), - Value::Dimension(num, unit, _) => match right { - Value::Dimension(num2, unit2, _) => { + Value::Dimension(None, ..) => todo!(), + Value::Dimension(Some(num), unit, _) => match right { + Value::Dimension(None, ..) => todo!(), + Value::Dimension(Some(num2), unit2, _) => { if !unit.comparable(&unit2) { return Err(( format!("Incompatible units {} and {}.", unit2, unit), @@ -324,17 +335,19 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { .into()); } if unit == unit2 { - Value::Dimension(num - num2, unit, true) + Value::Dimension(Some(num - num2), unit, true) } else if unit == Unit::None { - Value::Dimension(num - num2, unit2, true) + Value::Dimension(Some(num - num2), unit2, true) } else if unit2 == Unit::None { - Value::Dimension(num - num2, unit, true) + Value::Dimension(Some(num - num2), unit, true) } else { Value::Dimension( - num - num2 - * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] - [unit2.to_string().as_str()] - .clone(), + Some( + num - num2 + * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] + [unit2.to_string().as_str()] + .clone(), + ), unit, true, ) @@ -434,14 +447,16 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { v => panic!("{:?}", v), }; Ok(match left { - Value::Dimension(num, unit, _) => match right { - Value::Dimension(num2, unit2, _) => { + Value::Dimension(None, ..) => todo!(), + Value::Dimension(Some(num), unit, _) => match right { + Value::Dimension(None, ..) => todo!(), + Value::Dimension(Some(num2), unit2, _) => { if unit == Unit::None { - Value::Dimension(num * num2, unit2, true) + Value::Dimension(Some(num * num2), unit2, true) } else if unit2 == Unit::None { - Value::Dimension(num * num2, unit, true) + Value::Dimension(Some(num * num2), unit, true) } else { - Value::Dimension(num * num2, unit * unit2, true) + Value::Dimension(Some(num * num2), unit * unit2, true) } } _ => { @@ -490,28 +505,36 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { format!("/{}", right.to_css_string(self.span)?), QuoteKind::None, ), - Value::Dimension(num, unit, should_divide1) => match right { - Value::Dimension(num2, unit2, should_divide2) => { + Value::Dimension(None, ..) => todo!(), + Value::Dimension(Some(num), unit, should_divide1) => match right { + Value::Dimension(None, ..) => todo!(), + Value::Dimension(Some(num2), unit2, should_divide2) => { if should_divide1 || should_divide2 || in_parens { + if num.is_zero() && num2.is_zero() { + return Ok(Value::Dimension(None, Unit::None, true)); + } + // `unit(1em / 1em)` => `""` if unit == unit2 { - Value::Dimension(num / num2, Unit::None, true) + Value::Dimension(Some(num / num2), Unit::None, true) // `unit(1 / 1em)` => `"em^-1"` } else if unit == Unit::None { - Value::Dimension(num / num2, Unit::None / unit2, true) + Value::Dimension(Some(num / num2), Unit::None / unit2, true) // `unit(1em / 1)` => `"em"` } else if unit2 == Unit::None { - Value::Dimension(num / num2, unit, true) + Value::Dimension(Some(num / num2), unit, true) // `unit(1in / 1px)` => `""` } else if unit.comparable(&unit2) { Value::Dimension( - num / (num2 - * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] - [unit2.to_string().as_str()] - .clone()), + Some( + num / (num2 + * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] + [unit2.to_string().as_str()] + .clone()), + ), Unit::None, true, ) @@ -630,28 +653,30 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { v => panic!("{:?}", v), }; Ok(match left { - Value::Dimension(n, u, _) => match right { - Value::Dimension(n2, u2, _) => { + Value::Dimension(None, ..) => todo!(), + Value::Dimension(Some(n), u, _) => match right { + Value::Dimension(None, ..) => todo!(), + Value::Dimension(Some(n2), u2, _) => { if !u.comparable(&u2) { return Err( (format!("Incompatible units {} and {}.", u2, u), self.span).into() ); } if u == u2 { - Value::Dimension(n % n2, u, true) + Value::Dimension(Some(n % n2), u, true) } else if u == Unit::None { - Value::Dimension(n % n2, u2, true) + Value::Dimension(Some(n % n2), u2, true) } else if u2 == Unit::None { - Value::Dimension(n % n2, u, true) + Value::Dimension(Some(n % n2), u, true) } else { - Value::Dimension(n, u, true) + Value::Dimension(Some(n), u, true) } } _ => { return Err(( format!( "Undefined operation \"{} % {}\".", - Value::Dimension(n, u, true).inspect(self.span)?, + Value::Dimension(Some(n), u, true).inspect(self.span)?, right.inspect(self.span)? ), self.span, diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index be1ce79..795261c 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -459,7 +459,7 @@ impl<'a> Parser<'a> { let n = Rational64::new_raw(parse_i64(&val.num), 1); return Some(Ok(IntermediateValue::Value( HigherIntermediateValue::Literal(Value::Dimension( - Number::new_small(n), + Some(Number::new_small(n)), unit, false, )), @@ -472,7 +472,7 @@ impl<'a> Parser<'a> { let n = Rational64::new(parse_i64(&val.num), pow(10, val.dec_len)); return Some(Ok(IntermediateValue::Value( HigherIntermediateValue::Literal(Value::Dimension( - Number::new_small(n), + Some(Number::new_small(n)), unit, false, )), @@ -485,7 +485,7 @@ impl<'a> Parser<'a> { if val.times_ten.is_empty() { return Some(Ok(IntermediateValue::Value( HigherIntermediateValue::Literal(Value::Dimension( - Number::new_big(n), + Some(Number::new_big(n)), unit, false, )), @@ -514,7 +514,7 @@ impl<'a> Parser<'a> { }; IntermediateValue::Value(HigherIntermediateValue::Literal(Value::Dimension( - Number::new_big(n * times_ten), + Some(Number::new_big(n * times_ten)), unit, false, ))) diff --git a/src/value/mod.rs b/src/value/mod.rs index 7766b3f..4e50ac0 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -31,7 +31,8 @@ pub(crate) enum Value { True, False, Null, - Dimension(Number, Unit, bool), + /// A `None` value for `Number` indicates a `NaN` value + Dimension(Option, Unit, bool), List(Vec, ListSeparator, Brackets), Color(Box), String(String, QuoteKind), @@ -48,8 +49,8 @@ impl PartialEq for Value { Value::String(s2, ..) => s1 == s2, _ => false, }, - Value::Dimension(n, unit, _) => match other { - Value::Dimension(n2, unit2, _) => { + Value::Dimension(Some(n), unit, _) => match other { + Value::Dimension(Some(n2), unit2, _) => { if !unit.comparable(unit2) { false } else if unit == unit2 { @@ -65,6 +66,7 @@ impl PartialEq for Value { } _ => false, }, + Value::Dimension(None, ..) => false, Value::List(list1, sep1, brackets1) => match other { Value::List(list2, sep2, brackets2) => { if sep1 != sep2 || brackets1 != brackets2 || list1.len() != list2.len() { @@ -200,12 +202,13 @@ impl Value { pub fn to_css_string(&self, span: Span) -> SassResult> { Ok(match self { Value::Important => Cow::const_str("!important"), - Value::Dimension(num, unit, _) => match unit { + Value::Dimension(Some(num), unit, _) => match unit { Unit::Mul(..) | Unit::Div(..) => { return Err((format!("{}{} isn't a valid CSS value.", num, unit), span).into()); } _ => Cow::owned(format!("{}{}", num, unit)), }, + Value::Dimension(None, ..) => Cow::const_str("NaN"), Value::Map(..) | Value::FunctionRef(..) => { return Err(( format!("{} isn't a valid CSS value.", self.inspect(span)?), @@ -326,8 +329,10 @@ impl Value { pub fn cmp(&self, other: &Self, span: Span, op: Op) -> SassResult { Ok(match self { - Value::Dimension(num, unit, _) => match &other { - Value::Dimension(num2, unit2, _) => { + Value::Dimension(None, ..) => todo!(), + Value::Dimension(Some(num), unit, _) => match &other { + Value::Dimension(None, ..) => todo!(), + Value::Dimension(Some(num2), unit2, _) => { if !unit.comparable(unit2) { return Err( (format!("Incompatible units {} and {}.", unit2, unit), span).into(), @@ -387,8 +392,8 @@ impl Value { Value::String(s2, ..) => s1 != s2, _ => true, }, - Value::Dimension(n, unit, _) => match other { - Value::Dimension(n2, unit2, _) => { + Value::Dimension(Some(n), unit, _) => match other { + Value::Dimension(Some(n2), unit2, _) => { if !unit.comparable(unit2) { true } else if unit == unit2 { @@ -464,7 +469,8 @@ impl Value { .collect::>>()? .join(", ") )), - Value::Dimension(num, unit, _) => Cow::owned(format!("{}{}", num, unit)), + Value::Dimension(Some(num), unit, _) => Cow::owned(format!("{}{}", num, unit)), + Value::Dimension(None, ..) => Cow::const_str("NaN"), Value::ArgList(args) if args.is_empty() => Cow::const_str("()"), Value::ArgList(args) if args.len() == 1 => Cow::owned(format!( "({},)", diff --git a/tests/division.rs b/tests/division.rs index 1c02dce..ad7bcfb 100644 --- a/tests/division.rs +++ b/tests/division.rs @@ -169,3 +169,8 @@ test!( "a {\n color: 1 + 3 / 4;\n}\n", "a {\n color: 1.75;\n}\n" ); +test!( + zero_div_zero_is_nan, + "a {\n color: (0 / 0);\n}\n", + "a {\n color: NaN;\n}\n" +); diff --git a/tests/meta.rs b/tests/meta.rs index 6690635..56f4d4b 100644 --- a/tests/meta.rs +++ b/tests/meta.rs @@ -195,6 +195,11 @@ test!( "a {\n color: type-of(- 2)\n}\n", "a {\n color: number;\n}\n" ); +test!( + type_of_nan, + "a {\n color: type-of((0 / 0))\n}\n", + "a {\n color: number;\n}\n" +); test!( type_of_arglist, "@mixin foo($a...) {color: type-of($a);}\na {@include foo(1, 2, 3, 4, 5);}", From 2265e7eb748b5a1aadb1c47859b2a3dd0bc1451e Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 21:22:10 -0400 Subject: [PATCH 18/59] implement builtin function `math.sqrt` --- src/builtin/modules/math.rs | 25 ++++++++++++++++++++++- src/lib.rs | 1 + src/value/number/mod.rs | 16 ++++++++++++++- tests/math-module.rs | 40 +++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index fd55a8a..643beaa 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -115,7 +115,29 @@ fn pow(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn sqrt(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; - todo!() + let number = args.get_err(0, "number")?; + + Ok(match number { + Value::Dimension(Some(n), Unit::None, ..) => Value::Dimension(n.sqrt(), Unit::None, true), + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to have no units.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }) } fn cos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { @@ -165,6 +187,7 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("unit", unit); f.insert_builtin("percentage", percentage); f.insert_builtin("clamp", clamp); + f.insert_builtin("sqrt", sqrt); #[cfg(feature = "random")] f.insert_builtin("random", random); diff --git a/src/lib.rs b/src/lib.rs index 32f4c41..dbea119 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,6 +50,7 @@ grass input.scss clippy::unknown_clippy_lints, clippy::replace_consts, clippy::single_match, + clippy::float_arithmetic, // temporarily allowed while under heavy development. // eventually these allows should be refactored away diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index 9cbb4ed..8e95bf9 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -8,7 +8,9 @@ use std::{ use num_bigint::BigInt; use num_rational::{BigRational, Rational64}; -use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Num, One, Signed, Zero}; +use num_traits::{ + CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Num, One, Signed, ToPrimitive, Zero, +}; use integer::Integer; @@ -106,6 +108,18 @@ impl Number { self } + + #[allow(clippy::cast_precision_loss)] + pub fn sqrt(self) -> Option { + Some(match self { + Number::Small(n) => Number::Big(Box::new(BigRational::from_float( + ((*n.numer() as f64) / (*n.denom() as f64)).sqrt(), + )?)), + Number::Big(n) => Number::Big(Box::new(BigRational::from_float( + ((n.numer().to_f64()?) / (n.denom().to_f64()?)).sqrt(), + )?)), + }) + } } impl Default for Number { diff --git a/tests/math-module.rs b/tests/math-module.rs index 50030df..f5beefe 100644 --- a/tests/math-module.rs +++ b/tests/math-module.rs @@ -38,3 +38,43 @@ error!( "@use 'sass:math';\na {\n color: math.clamp(0mm, 1cm, 2);\n}\n", "Error: $min has unit mm but $max is unitless. Arguments must all have units or all be unitless." ); +test!( + sqrt_zero, + "@use 'sass:math';\na {\n color: math.sqrt(0);\n}\n", + "a {\n color: 0;\n}\n" +); +test!( + sqrt_small_positive, + "@use 'sass:math';\na {\n color: math.sqrt(99);\n}\n", + "a {\n color: 9.9498743711;\n}\n" +); +test!( + sqrt_small_negative, + "@use 'sass:math';\na {\n color: math.sqrt(-99);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + sqrt_big_positive, + "@use 'sass:math';\na {\n color: math.sqrt(9999999999999999999999999999999999999999999999999);\n}\n", + "a {\n color: 3162277660168379038695424;\n}\n" +); +test!( + sqrt_big_negative, + "@use 'sass:math';\na {\n color: math.sqrt(-9999999999999999999999999999999999999999999999999);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + sqrt_irrational, + "@use 'sass:math';\na {\n color: math.sqrt(2);\n}\n", + "a {\n color: 1.4142135624;\n}\n" +); +test!( + sqrt_of_nan, + "@use 'sass:math';\na {\n color: math.sqrt((0 / 0));\n}\n", + "a {\n color: NaN;\n}\n" +); +error!( + sqrt_with_units, + "@use 'sass:math';\na {\n color: math.sqrt(1px);\n}\n", + "Error: $number: Expected 1px to have no units." +); From eee5eeb8266238566470cea433c3c0e12f06f73b Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 22:04:07 -0400 Subject: [PATCH 19/59] implement builtin function `math.cos` --- src/builtin/modules/math.rs | 28 +++++++++++++++++++++++++++- src/value/number/mod.rs | 28 +++++++++++++++++++++------- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index 643beaa..db97a78 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -142,7 +142,32 @@ fn sqrt(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn cos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; - todo!() + let number = args.get_err(0, "number")?; + + Ok(match number { + Value::Dimension(Some(n), Unit::None, ..) | Value::Dimension(Some(n), Unit::Rad, ..) => { + Value::Dimension(n.cos(), Unit::None, true) + } + Value::Dimension(Some(n), Unit::Deg, ..) => Value::Dimension(n.cos_deg(), Unit::None, true), + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to be an angle.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }) } fn sin(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { @@ -188,6 +213,7 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("percentage", percentage); f.insert_builtin("clamp", clamp); f.insert_builtin("sqrt", sqrt); + f.insert_builtin("cos", cos); #[cfg(feature = "random")] f.insert_builtin("random", random); diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index 8e95bf9..3c99f68 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -110,16 +110,30 @@ impl Number { } #[allow(clippy::cast_precision_loss)] - pub fn sqrt(self) -> Option { + fn as_float(self) -> Option { Some(match self { - Number::Small(n) => Number::Big(Box::new(BigRational::from_float( - ((*n.numer() as f64) / (*n.denom() as f64)).sqrt(), - )?)), - Number::Big(n) => Number::Big(Box::new(BigRational::from_float( - ((n.numer().to_f64()?) / (n.denom().to_f64()?)).sqrt(), - )?)), + Number::Small(n) => ((*n.numer() as f64) / (*n.denom() as f64)), + Number::Big(n) => ((n.numer().to_f64()?) / (n.denom().to_f64()?)), }) } + + pub fn cos_deg(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.to_radians().cos(), + )?))) + } + + pub fn sqrt(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.sqrt(), + )?))) + } + + pub fn cos(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.cos(), + )?))) + } } impl Default for Number { From e67b0dc440ffdfe7300f993d7d961fd893a8025b Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 22:11:19 -0400 Subject: [PATCH 20/59] implement builtin math functions `sin`, `tan`, `acos`, `asin`, and `atan` --- src/builtin/modules/math.rs | 147 ++++++++++++++++++++++++++++++++++-- src/value/number/mod.rs | 71 ++++++++++++++++- 2 files changed, 209 insertions(+), 9 deletions(-) diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index db97a78..1c6dce0 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -172,27 +172,159 @@ fn cos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn sin(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; - todo!() + let number = args.get_err(0, "number")?; + + Ok(match number { + Value::Dimension(Some(n), Unit::None, ..) | Value::Dimension(Some(n), Unit::Rad, ..) => { + Value::Dimension(n.sin(), Unit::None, true) + } + Value::Dimension(Some(n), Unit::Deg, ..) => Value::Dimension(n.sin_deg(), Unit::None, true), + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to be an angle.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }) } fn tan(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; - todo!() + let number = args.get_err(0, "number")?; + + Ok(match number { + Value::Dimension(Some(n), Unit::None, ..) | Value::Dimension(Some(n), Unit::Rad, ..) => { + Value::Dimension(n.tan(), Unit::None, true) + } + Value::Dimension(Some(n), Unit::Deg, ..) => Value::Dimension(n.tan_deg(), Unit::None, true), + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to be an angle.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }) } fn acos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; - todo!() + args.max_args(1)?; + let number = args.get_err(0, "number")?; + + Ok(match number { + Value::Dimension(Some(n), Unit::None, ..) | Value::Dimension(Some(n), Unit::Rad, ..) => { + Value::Dimension(n.acos(), Unit::None, true) + } + Value::Dimension(Some(n), Unit::Deg, ..) => { + Value::Dimension(n.acos_deg(), Unit::None, true) + } + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to be an angle.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }) } fn asin(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; - todo!() + let number = args.get_err(0, "number")?; + + Ok(match number { + Value::Dimension(Some(n), Unit::None, ..) | Value::Dimension(Some(n), Unit::Rad, ..) => { + Value::Dimension(n.asin(), Unit::None, true) + } + Value::Dimension(Some(n), Unit::Deg, ..) => { + Value::Dimension(n.asin_deg(), Unit::None, true) + } + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to be an angle.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }) } fn atan(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; - todo!() + let number = args.get_err(0, "number")?; + + Ok(match number { + Value::Dimension(Some(n), Unit::None, ..) | Value::Dimension(Some(n), Unit::Rad, ..) => { + Value::Dimension(n.atan(), Unit::None, true) + } + Value::Dimension(Some(n), Unit::Deg, ..) => { + Value::Dimension(n.atan_deg(), Unit::None, true) + } + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to be an angle.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }) } fn atan2(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { @@ -214,6 +346,11 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("clamp", clamp); f.insert_builtin("sqrt", sqrt); f.insert_builtin("cos", cos); + f.insert_builtin("sin", sin); + f.insert_builtin("tan", tan); + f.insert_builtin("acos", acos); + f.insert_builtin("asin", asin); + f.insert_builtin("atan", atan); #[cfg(feature = "random")] f.insert_builtin("random", random); diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index 3c99f68..8e9b9e7 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -117,21 +117,84 @@ impl Number { }) } + pub fn sqrt(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.sqrt(), + )?))) + } +} + +/// Trigonometry methods +impl Number { + pub fn cos(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.cos(), + )?))) + } + pub fn cos_deg(self) -> Option { Some(Number::Big(Box::new(BigRational::from_float( self.as_float()?.to_radians().cos(), )?))) } - pub fn sqrt(self) -> Option { + pub fn acos(self) -> Option { Some(Number::Big(Box::new(BigRational::from_float( - self.as_float()?.sqrt(), + self.as_float()?.acos(), )?))) } - pub fn cos(self) -> Option { + pub fn acos_deg(self) -> Option { Some(Number::Big(Box::new(BigRational::from_float( - self.as_float()?.cos(), + self.as_float()?.to_radians().acos(), + )?))) + } + + pub fn sin(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.sin(), + )?))) + } + + pub fn sin_deg(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.to_radians().sin(), + )?))) + } + + pub fn asin(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.asin(), + )?))) + } + + pub fn asin_deg(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.to_radians().asin(), + )?))) + } + + pub fn tan(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.tan(), + )?))) + } + + pub fn tan_deg(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.to_radians().tan(), + )?))) + } + + pub fn atan(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.atan(), + )?))) + } + + pub fn atan_deg(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.to_radians().atan(), )?))) } } From 78da4ad2fe90bbba6fd06bc26f74221666ba8962 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 22:15:28 -0400 Subject: [PATCH 21/59] use macro to simplify declaration of trig fns --- src/builtin/modules/math.rs | 221 +++++++----------------------------- src/value/number/mod.rs | 93 ++++----------- 2 files changed, 61 insertions(+), 253 deletions(-) diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index 1c6dce0..1e6ebe1 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -140,192 +140,49 @@ fn sqrt(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { }) } -fn cos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { - args.max_args(1)?; - let number = args.get_err(0, "number")?; +macro_rules! trig_fn { + ($name:ident, $name_deg:ident) => { + fn $name(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(1)?; + let number = args.get_err(0, "number")?; - Ok(match number { - Value::Dimension(Some(n), Unit::None, ..) | Value::Dimension(Some(n), Unit::Rad, ..) => { - Value::Dimension(n.cos(), Unit::None, true) + Ok(match number { + Value::Dimension(Some(n), Unit::None, ..) + | Value::Dimension(Some(n), Unit::Rad, ..) => { + Value::Dimension(n.$name(), Unit::None, true) + } + Value::Dimension(Some(n), Unit::Deg, ..) => { + Value::Dimension(n.$name_deg(), Unit::None, true) + } + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to be an angle.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }) } - Value::Dimension(Some(n), Unit::Deg, ..) => Value::Dimension(n.cos_deg(), Unit::None, true), - v @ Value::Dimension(Some(..), ..) => { - return Err(( - format!( - "$number: Expected {} to be an angle.", - v.inspect(args.span())? - ), - args.span(), - ) - .into()) - } - Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), - v => { - return Err(( - format!("$number: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }) + }; } -fn sin(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { - args.max_args(1)?; - let number = args.get_err(0, "number")?; - - Ok(match number { - Value::Dimension(Some(n), Unit::None, ..) | Value::Dimension(Some(n), Unit::Rad, ..) => { - Value::Dimension(n.sin(), Unit::None, true) - } - Value::Dimension(Some(n), Unit::Deg, ..) => Value::Dimension(n.sin_deg(), Unit::None, true), - v @ Value::Dimension(Some(..), ..) => { - return Err(( - format!( - "$number: Expected {} to be an angle.", - v.inspect(args.span())? - ), - args.span(), - ) - .into()) - } - Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), - v => { - return Err(( - format!("$number: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }) -} - -fn tan(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { - args.max_args(1)?; - let number = args.get_err(0, "number")?; - - Ok(match number { - Value::Dimension(Some(n), Unit::None, ..) | Value::Dimension(Some(n), Unit::Rad, ..) => { - Value::Dimension(n.tan(), Unit::None, true) - } - Value::Dimension(Some(n), Unit::Deg, ..) => Value::Dimension(n.tan_deg(), Unit::None, true), - v @ Value::Dimension(Some(..), ..) => { - return Err(( - format!( - "$number: Expected {} to be an angle.", - v.inspect(args.span())? - ), - args.span(), - ) - .into()) - } - Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), - v => { - return Err(( - format!("$number: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }) -} - -fn acos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { - args.max_args(1)?; - args.max_args(1)?; - let number = args.get_err(0, "number")?; - - Ok(match number { - Value::Dimension(Some(n), Unit::None, ..) | Value::Dimension(Some(n), Unit::Rad, ..) => { - Value::Dimension(n.acos(), Unit::None, true) - } - Value::Dimension(Some(n), Unit::Deg, ..) => { - Value::Dimension(n.acos_deg(), Unit::None, true) - } - v @ Value::Dimension(Some(..), ..) => { - return Err(( - format!( - "$number: Expected {} to be an angle.", - v.inspect(args.span())? - ), - args.span(), - ) - .into()) - } - Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), - v => { - return Err(( - format!("$number: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }) -} - -fn asin(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { - args.max_args(1)?; - let number = args.get_err(0, "number")?; - - Ok(match number { - Value::Dimension(Some(n), Unit::None, ..) | Value::Dimension(Some(n), Unit::Rad, ..) => { - Value::Dimension(n.asin(), Unit::None, true) - } - Value::Dimension(Some(n), Unit::Deg, ..) => { - Value::Dimension(n.asin_deg(), Unit::None, true) - } - v @ Value::Dimension(Some(..), ..) => { - return Err(( - format!( - "$number: Expected {} to be an angle.", - v.inspect(args.span())? - ), - args.span(), - ) - .into()) - } - Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), - v => { - return Err(( - format!("$number: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }) -} - -fn atan(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { - args.max_args(1)?; - let number = args.get_err(0, "number")?; - - Ok(match number { - Value::Dimension(Some(n), Unit::None, ..) | Value::Dimension(Some(n), Unit::Rad, ..) => { - Value::Dimension(n.atan(), Unit::None, true) - } - Value::Dimension(Some(n), Unit::Deg, ..) => { - Value::Dimension(n.atan_deg(), Unit::None, true) - } - v @ Value::Dimension(Some(..), ..) => { - return Err(( - format!( - "$number: Expected {} to be an angle.", - v.inspect(args.span())? - ), - args.span(), - ) - .into()) - } - Value::Dimension(None, ..) => Value::Dimension(None, Unit::None, true), - v => { - return Err(( - format!("$number: {} is not a number.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } - }) -} +trig_fn!(cos, cos_deg); +trig_fn!(sin, sin_deg); +trig_fn!(tan, tan_deg); +trig_fn!(acos, acos_deg); +trig_fn!(asin, asin_deg); +trig_fn!(atan, atan_deg); fn atan2(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index 8e9b9e7..7c3158b 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -124,79 +124,30 @@ impl Number { } } +macro_rules! trig_fn( + ($name:ident, $name_deg:ident) => { + pub fn $name(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.$name(), + )?))) + } + + pub fn $name_deg(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.to_radians().$name(), + )?))) + } + } +); + /// Trigonometry methods impl Number { - pub fn cos(self) -> Option { - Some(Number::Big(Box::new(BigRational::from_float( - self.as_float()?.cos(), - )?))) - } - - pub fn cos_deg(self) -> Option { - Some(Number::Big(Box::new(BigRational::from_float( - self.as_float()?.to_radians().cos(), - )?))) - } - - pub fn acos(self) -> Option { - Some(Number::Big(Box::new(BigRational::from_float( - self.as_float()?.acos(), - )?))) - } - - pub fn acos_deg(self) -> Option { - Some(Number::Big(Box::new(BigRational::from_float( - self.as_float()?.to_radians().acos(), - )?))) - } - - pub fn sin(self) -> Option { - Some(Number::Big(Box::new(BigRational::from_float( - self.as_float()?.sin(), - )?))) - } - - pub fn sin_deg(self) -> Option { - Some(Number::Big(Box::new(BigRational::from_float( - self.as_float()?.to_radians().sin(), - )?))) - } - - pub fn asin(self) -> Option { - Some(Number::Big(Box::new(BigRational::from_float( - self.as_float()?.asin(), - )?))) - } - - pub fn asin_deg(self) -> Option { - Some(Number::Big(Box::new(BigRational::from_float( - self.as_float()?.to_radians().asin(), - )?))) - } - - pub fn tan(self) -> Option { - Some(Number::Big(Box::new(BigRational::from_float( - self.as_float()?.tan(), - )?))) - } - - pub fn tan_deg(self) -> Option { - Some(Number::Big(Box::new(BigRational::from_float( - self.as_float()?.to_radians().tan(), - )?))) - } - - pub fn atan(self) -> Option { - Some(Number::Big(Box::new(BigRational::from_float( - self.as_float()?.atan(), - )?))) - } - - pub fn atan_deg(self) -> Option { - Some(Number::Big(Box::new(BigRational::from_float( - self.as_float()?.to_radians().atan(), - )?))) - } + trig_fn!(cos, cos_deg); + trig_fn!(sin, sin_deg); + trig_fn!(tan, tan_deg); + trig_fn!(acos, acos_deg); + trig_fn!(asin, asin_deg); + trig_fn!(atan, atan_deg); } impl Default for Number { From fbcee00bddc5ba79e4e694c7b8fe31917c235f67 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 23:45:00 -0400 Subject: [PATCH 22/59] allow NaN to take units and refactor `math.acos` --- src/builtin/modules/math.rs | 39 ++++++++++++++++++++++++++++++++++++- src/value/mod.rs | 21 +++++++++++++++----- src/value/number/mod.rs | 12 +++++++++++- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index 1e6ebe1..814f83b 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -1,5 +1,7 @@ use std::cmp::Ordering; +use num_traits::{One, Zero}; + use crate::{ args::CallArgs, builtin::{ @@ -180,10 +182,45 @@ macro_rules! trig_fn { trig_fn!(cos, cos_deg); trig_fn!(sin, sin_deg); trig_fn!(tan, tan_deg); -trig_fn!(acos, acos_deg); + trig_fn!(asin, asin_deg); trig_fn!(atan, atan_deg); +fn acos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(1)?; + let number = args.get_err(0, "number")?; + + Ok(match number { + Value::Dimension(Some(n), Unit::None, ..) => { + if n > Number::from(1) || n < Number::from(-1) { + return Ok(Value::Dimension(None, Unit::Deg, true)); + } else if n.is_one() { + return Ok(Value::Dimension(Some(Number::zero()), Unit::Deg, true)); + } + + Value::Dimension(n.acos(), Unit::Deg, true) + } + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to be unitless.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + Value::Dimension(None, ..) => Value::Dimension(None, Unit::Deg, true), + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }) +} + fn atan2(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; todo!() diff --git a/src/value/mod.rs b/src/value/mod.rs index 4e50ac0..f88c6c5 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -202,13 +202,24 @@ impl Value { pub fn to_css_string(&self, span: Span) -> SassResult> { Ok(match self { Value::Important => Cow::const_str("!important"), - Value::Dimension(Some(num), unit, _) => match unit { + Value::Dimension(num, unit, _) => match unit { Unit::Mul(..) | Unit::Div(..) => { - return Err((format!("{}{} isn't a valid CSS value.", num, unit), span).into()); + if let Some(num) = num { + return Err( + (format!("{}{} isn't a valid CSS value.", num, unit), span).into() + ); + } else { + return Err((format!("NaN{} isn't a valid CSS value.", unit), span).into()); + } + } + _ => { + if let Some(num) = num { + Cow::owned(format!("{}{}", num, unit)) + } else { + Cow::owned(format!("NaN{}", unit)) + } } - _ => Cow::owned(format!("{}{}", num, unit)), }, - Value::Dimension(None, ..) => Cow::const_str("NaN"), Value::Map(..) | Value::FunctionRef(..) => { return Err(( format!("{} isn't a valid CSS value.", self.inspect(span)?), @@ -470,7 +481,7 @@ impl Value { .join(", ") )), Value::Dimension(Some(num), unit, _) => Cow::owned(format!("{}{}", num, unit)), - Value::Dimension(None, ..) => Cow::const_str("NaN"), + Value::Dimension(None, unit, ..) => Cow::owned(format!("NaN{}", unit)), Value::ArgList(args) if args.is_empty() => Cow::const_str("()"), Value::ArgList(args) if args.len() == 1 => Cow::owned(format!( "({},)", diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index 7c3158b..18547cf 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -140,12 +140,22 @@ macro_rules! trig_fn( } ); +macro_rules! inverse_trig_fn( + ($name:ident) => { + pub fn $name(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.$name().to_degrees(), + )?))) + } + } +); + /// Trigonometry methods impl Number { trig_fn!(cos, cos_deg); trig_fn!(sin, sin_deg); trig_fn!(tan, tan_deg); - trig_fn!(acos, acos_deg); + inverse_trig_fn!(acos); trig_fn!(asin, asin_deg); trig_fn!(atan, atan_deg); } From a3e6607394bd883f23cced9e044d8f37408c4931 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 26 Jul 2020 23:45:29 -0400 Subject: [PATCH 23/59] tests for `cos`, `sin`, `tan`, and `acos` --- tests/math-module.rs | 171 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/tests/math-module.rs b/tests/math-module.rs index f5beefe..2bff4b4 100644 --- a/tests/math-module.rs +++ b/tests/math-module.rs @@ -78,3 +78,174 @@ error!( "@use 'sass:math';\na {\n color: math.sqrt(1px);\n}\n", "Error: $number: Expected 1px to have no units." ); +error!( + cos_non_angle, + "@use 'sass:math';\na {\n color: math.cos(1px);\n}\n", + "Error: $number: Expected 1px to be an angle." +); +test!( + cos_small_degree, + "@use 'sass:math';\na {\n color: math.cos(1deg);\n}\n", + "a {\n color: 0.9998476952;\n}\n" +); +test!( + cos_small_radian, + "@use 'sass:math';\na {\n color: math.cos(1rad);\n}\n", + "a {\n color: 0.5403023059;\n}\n" +); +test!( + cos_small_no_unit, + "@use 'sass:math';\na {\n color: math.cos(1);\n}\n", + "a {\n color: 0.5403023059;\n}\n" +); +test!( + cos_small_negative_degree, + "@use 'sass:math';\na {\n color: math.cos(-1deg);\n}\n", + "a {\n color: 0.9998476952;\n}\n" +); +test!( + cos_small_negative_radian, + "@use 'sass:math';\na {\n color: math.cos(-1rad);\n}\n", + "a {\n color: 0.5403023059;\n}\n" +); +test!( + cos_small_negative_no_unit, + "@use 'sass:math';\na {\n color: math.cos(-1);\n}\n", + "a {\n color: 0.5403023059;\n}\n" +); +test!( + cos_pi, + "@use 'sass:math';\na {\n color: math.cos(math.$pi);\n}\n", + "a {\n color: -1;\n}\n" +); +test!( + cos_two_pi, + "@use 'sass:math';\na {\n color: math.cos(2 * math.$pi);\n}\n", + "a {\n color: 1;\n}\n" +); +error!( + sin_non_angle, + "@use 'sass:math';\na {\n color: math.sin(1px);\n}\n", + "Error: $number: Expected 1px to be an angle." +); +test!( + sin_small_degree, + "@use 'sass:math';\na {\n color: math.sin(1deg);\n}\n", + "a {\n color: 0.0174524064;\n}\n" +); +test!( + sin_small_radian, + "@use 'sass:math';\na {\n color: math.sin(1rad);\n}\n", + "a {\n color: 0.8414709848;\n}\n" +); +test!( + sin_small_no_unit, + "@use 'sass:math';\na {\n color: math.sin(1);\n}\n", + "a {\n color: 0.8414709848;\n}\n" +); +test!( + sin_small_negative_degree, + "@use 'sass:math';\na {\n color: math.sin(-1deg);\n}\n", + "a {\n color: -0.0174524064;\n}\n" +); +test!( + sin_small_negative_radian, + "@use 'sass:math';\na {\n color: math.sin(-1rad);\n}\n", + "a {\n color: -0.8414709848;\n}\n" +); +test!( + sin_small_negative_no_unit, + "@use 'sass:math';\na {\n color: math.sin(-1);\n}\n", + "a {\n color: -0.8414709848;\n}\n" +); +test!( + sin_pi, + "@use 'sass:math';\na {\n color: math.sin(math.$pi);\n}\n", + "a {\n color: 0;\n}\n" +); +test!( + sin_two_pi, + "@use 'sass:math';\na {\n color: math.sin(2 * math.$pi);\n}\n", + "a {\n color: 0;\n}\n" +); +error!( + tan_non_angle, + "@use 'sass:math';\na {\n color: math.tan(1px);\n}\n", + "Error: $number: Expected 1px to be an angle." +); +test!( + tan_small_degree, + "@use 'sass:math';\na {\n color: math.tan(1deg);\n}\n", + "a {\n color: 0.0174550649;\n}\n" +); +test!( + tan_small_radian, + "@use 'sass:math';\na {\n color: math.tan(1rad);\n}\n", + "a {\n color: 1.5574077247;\n}\n" +); +test!( + tan_small_no_unit, + "@use 'sass:math';\na {\n color: math.tan(1);\n}\n", + "a {\n color: 1.5574077247;\n}\n" +); +test!( + tan_small_negative_degree, + "@use 'sass:math';\na {\n color: math.tan(-1deg);\n}\n", + "a {\n color: -0.0174550649;\n}\n" +); +test!( + tan_small_negative_radian, + "@use 'sass:math';\na {\n color: math.tan(-1rad);\n}\n", + "a {\n color: -1.5574077247;\n}\n" +); +test!( + tan_small_negative_no_unit, + "@use 'sass:math';\na {\n color: math.tan(-1);\n}\n", + "a {\n color: -1.5574077247;\n}\n" +); +test!( + tan_pi, + "@use 'sass:math';\na {\n color: math.tan(math.$pi);\n}\n", + "a {\n color: 0;\n}\n" +); +test!( + tan_two_pi, + "@use 'sass:math';\na {\n color: math.tan(2 * math.$pi);\n}\n", + "a {\n color: 0;\n}\n" +); + +test!( + acos_above_one, + "@use 'sass:math';\na {\n color: math.acos(2);\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +test!( + acos_below_negative_one, + "@use 'sass:math';\na {\n color: math.acos(-2);\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +test!( + acos_one, + "@use 'sass:math';\na {\n color: math.acos(1);\n}\n", + "a {\n color: 0deg;\n}\n" +); +test!( + acos_negative_one, + "@use 'sass:math';\na {\n color: math.acos(-1);\n}\n", + "a {\n color: 180deg;\n}\n" +); +test!( + acos_zero, + "@use 'sass:math';\na {\n color: math.acos(0);\n}\n", + "a {\n color: 90deg;\n}\n" +); +test!( + acos_point_five, + "@use 'sass:math';\na {\n color: math.acos(.5);\n}\n", + "a {\n color: 60deg;\n}\n" +); +test!( + acos_nan, + "@use 'sass:math';\na {\n color: math.acos((0 / 0));\n}\n", + "a {\n color: NaNdeg;\n}\n" +); From 313913734adaf48de1088f4ce6346b74a282a284 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Mon, 27 Jul 2020 00:07:29 -0400 Subject: [PATCH 24/59] tests for `math.asin` and `math.atan` --- src/builtin/modules/math.rs | 71 +++++++++++++++++++++++++++++++++++-- src/value/number/mod.rs | 5 +-- tests/math-module.rs | 71 ++++++++++++++++++++++++++++++++++++- 3 files changed, 141 insertions(+), 6 deletions(-) diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index 814f83b..fef1d91 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -183,9 +183,6 @@ trig_fn!(cos, cos_deg); trig_fn!(sin, sin_deg); trig_fn!(tan, tan_deg); -trig_fn!(asin, asin_deg); -trig_fn!(atan, atan_deg); - fn acos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let number = args.get_err(0, "number")?; @@ -221,6 +218,74 @@ fn acos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { }) } +fn asin(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(1)?; + let number = args.get_err(0, "number")?; + + Ok(match number { + Value::Dimension(Some(n), Unit::None, ..) => { + if n > Number::from(1) || n < Number::from(-1) { + return Ok(Value::Dimension(None, Unit::Deg, true)); + } else if n.is_zero() { + return Ok(Value::Dimension(Some(Number::zero()), Unit::Deg, true)); + } + + Value::Dimension(n.asin(), Unit::Deg, true) + } + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to be unitless.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + Value::Dimension(None, ..) => Value::Dimension(None, Unit::Deg, true), + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }) +} + +fn atan(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + args.max_args(1)?; + let number = args.get_err(0, "number")?; + + Ok(match number { + Value::Dimension(Some(n), Unit::None, ..) => { + if n.is_zero() { + return Ok(Value::Dimension(Some(Number::zero()), Unit::Deg, true)); + } + + Value::Dimension(n.atan(), Unit::Deg, true) + } + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to be unitless.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + Value::Dimension(None, ..) => Value::Dimension(None, Unit::Deg, true), + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }) +} + fn atan2(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; todo!() diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index 18547cf..e6b87c1 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -155,9 +155,10 @@ impl Number { trig_fn!(cos, cos_deg); trig_fn!(sin, sin_deg); trig_fn!(tan, tan_deg); + inverse_trig_fn!(acos); - trig_fn!(asin, asin_deg); - trig_fn!(atan, atan_deg); + inverse_trig_fn!(asin); + inverse_trig_fn!(atan); } impl Default for Number { diff --git a/tests/math-module.rs b/tests/math-module.rs index 2bff4b4..1b51562 100644 --- a/tests/math-module.rs +++ b/tests/math-module.rs @@ -213,7 +213,6 @@ test!( "@use 'sass:math';\na {\n color: math.tan(2 * math.$pi);\n}\n", "a {\n color: 0;\n}\n" ); - test!( acos_above_one, "@use 'sass:math';\na {\n color: math.acos(2);\n}\n", @@ -249,3 +248,73 @@ test!( "@use 'sass:math';\na {\n color: math.acos((0 / 0));\n}\n", "a {\n color: NaNdeg;\n}\n" ); +test!( + asin_above_one, + "@use 'sass:math';\na {\n color: math.asin(2);\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +test!( + asin_below_negative_one, + "@use 'sass:math';\na {\n color: math.asin(-2);\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +test!( + asin_one, + "@use 'sass:math';\na {\n color: math.asin(1);\n}\n", + "a {\n color: 90deg;\n}\n" +); +test!( + asin_negative_one, + "@use 'sass:math';\na {\n color: math.asin(-1);\n}\n", + "a {\n color: -90deg;\n}\n" +); +test!( + asin_zero, + "@use 'sass:math';\na {\n color: math.asin(0);\n}\n", + "a {\n color: 0deg;\n}\n" +); +test!( + asin_point_five, + "@use 'sass:math';\na {\n color: math.asin(.5);\n}\n", + "a {\n color: 30deg;\n}\n" +); +test!( + asin_nan, + "@use 'sass:math';\na {\n color: math.asin((0 / 0));\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +test!( + atan_above_one, + "@use 'sass:math';\na {\n color: math.atan(2);\n}\n", + "a {\n color: 63.4349488229deg;\n}\n" +); +test!( + atan_below_negative_one, + "@use 'sass:math';\na {\n color: math.atan(-2);\n}\n", + "a {\n color: -63.4349488229deg;\n}\n" +); +test!( + atan_one, + "@use 'sass:math';\na {\n color: math.atan(1);\n}\n", + "a {\n color: 45deg;\n}\n" +); +test!( + atan_negative_one, + "@use 'sass:math';\na {\n color: math.atan(-1);\n}\n", + "a {\n color: -45deg;\n}\n" +); +test!( + atan_zero, + "@use 'sass:math';\na {\n color: math.atan(0);\n}\n", + "a {\n color: 0deg;\n}\n" +); +test!( + atan_point_five, + "@use 'sass:math';\na {\n color: math.atan(.5);\n}\n", + "a {\n color: 26.5650511771deg;\n}\n" +); +test!( + atan_nan, + "@use 'sass:math';\na {\n color: math.atan((0 / 0));\n}\n", + "a {\n color: NaNdeg;\n}\n" +); From 458fcf0fd83d529dbf650b56c3dc76fe36b809c9 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Mon, 27 Jul 2020 01:52:34 -0400 Subject: [PATCH 25/59] implement builtin function `math.log` --- src/builtin/modules/math.rs | 67 ++++++++++++++++++++++++++++++++-- src/value/number/mod.rs | 6 ++++ tests/math-module.rs | 72 +++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 2 deletions(-) diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index fef1d91..61f4c3c 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use num_traits::{One, Zero}; +use num_traits::{One, Signed, Zero}; use crate::{ args::CallArgs, @@ -107,7 +107,69 @@ fn hypot(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn log(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; - todo!() + + let number = match args.get_err(0, "number")? { + Value::Dimension(Some(n), Unit::None, ..) => n, + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to be unitless.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + v @ Value::Dimension(None, ..) => return Ok(v), + v => { + return Err(( + format!("$number: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + let base = match args.default_arg(1, "base", Value::Null)? { + Value::Null => None, + Value::Dimension(Some(n), Unit::None, ..) => Some(n), + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$number: Expected {} to be unitless.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + v @ Value::Dimension(None, ..) => return Ok(v), + v => { + return Err(( + format!("$base: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + Ok(Value::Dimension( + if let Some(base) = base { + if base.is_zero() { + Some(Number::zero()) + } else { + (|| Some(number.ln()? / base.ln()?))() + } + } else if number.is_negative() { + None + } else if number.is_zero() { + todo!() + } else { + number.ln() + }, + Unit::None, + true, + )) } fn pow(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { @@ -310,6 +372,7 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("acos", acos); f.insert_builtin("asin", asin); f.insert_builtin("atan", atan); + f.insert_builtin("log", log); #[cfg(feature = "random")] f.insert_builtin("random", random); diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index e6b87c1..14eb52c 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -122,6 +122,12 @@ impl Number { self.as_float()?.sqrt(), )?))) } + + pub fn ln(self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.ln(), + )?))) + } } macro_rules! trig_fn( diff --git a/tests/math-module.rs b/tests/math-module.rs index 1b51562..9a32c9a 100644 --- a/tests/math-module.rs +++ b/tests/math-module.rs @@ -318,3 +318,75 @@ test!( "@use 'sass:math';\na {\n color: math.atan((0 / 0));\n}\n", "a {\n color: NaNdeg;\n}\n" ); +test!( + log_above_one, + "@use 'sass:math';\na {\n color: math.log(2);\n}\n", + "a {\n color: 0.6931471806;\n}\n" +); +test!( + log_below_negative_one, + "@use 'sass:math';\na {\n color: math.log(-2);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + log_one, + "@use 'sass:math';\na {\n color: math.log(1);\n}\n", + "a {\n color: 0;\n}\n" +); +test!( + log_negative_one, + "@use 'sass:math';\na {\n color: math.log(-1);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + #[ignore = "we do not support Infinity"] + log_zero, + "@use 'sass:math';\na {\n color: math.log(0);\n}\n", + "a {\n color: -Infinity;\n}\n" +); +test!( + log_point_five, + "@use 'sass:math';\na {\n color: math.log(.5);\n}\n", + "a {\n color: -0.6931471806;\n}\n" +); +test!( + log_nan, + "@use 'sass:math';\na {\n color: math.log((0 / 0));\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + log_base_nan, + "@use 'sass:math';\na {\n color: math.log(1, (0 / 0));\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + log_base_above_one, + "@use 'sass:math';\na {\n color: math.log(2, 2);\n}\n", + "a {\n color: 1;\n}\n" +); +test!( + log_base_below_negative_one, + "@use 'sass:math';\na {\n color: math.log(2, -2);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + #[ignore = "we do not support Infinity"] + log_base_one, + "@use 'sass:math';\na {\n color: math.log(2, 1);\n}\n", + "a {\n color: Infinity;\n}\n" +); +test!( + log_base_negative_one, + "@use 'sass:math';\na {\n color: math.log(2, -1);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + log_base_zero, + "@use 'sass:math';\na {\n color: math.log(2, 0);\n}\n", + "a {\n color: 0;\n}\n" +); +test!( + log_base_point_five, + "@use 'sass:math';\na {\n color: math.log(2, .5);\n}\n", + "a {\n color: -1;\n}\n" +); From 717cdd950169fe378b62385a1eec204b83546d7d Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Mon, 27 Jul 2020 02:03:56 -0400 Subject: [PATCH 26/59] adjust-hue is not included in the module system --- src/builtin/modules/color.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/builtin/modules/color.rs b/src/builtin/modules/color.rs index e27a985..0a10449 100644 --- a/src/builtin/modules/color.rs +++ b/src/builtin/modules/color.rs @@ -14,11 +14,6 @@ use crate::{ value::Value, }; -fn adjust_hue(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { - args.max_args(2)?; - todo!() -} - pub(crate) fn declare(f: &mut Module) { f.insert_builtin("adjust", adjust_color); f.insert_builtin("alpha", alpha); From f63b254367422273de217948bb9f0c32ae7dbe2b Mon Sep 17 00:00:00 2001 From: Connor Skees <39542938+connorskees@users.noreply.github.com> Date: Mon, 27 Jul 2020 15:30:57 -0400 Subject: [PATCH 27/59] refactor `math.acos` implementation Co-authored-by: Ivan Tham --- src/builtin/modules/math.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index 61f4c3c..a151c80 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -250,15 +250,17 @@ fn acos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { let number = args.get_err(0, "number")?; Ok(match number { - Value::Dimension(Some(n), Unit::None, ..) => { + Value::Dimension(Some(n), Unit::None, ..) => Value::Dimension( if n > Number::from(1) || n < Number::from(-1) { - return Ok(Value::Dimension(None, Unit::Deg, true)); + None } else if n.is_one() { - return Ok(Value::Dimension(Some(Number::zero()), Unit::Deg, true)); - } - - Value::Dimension(n.acos(), Unit::Deg, true) - } + Some(Number::zero()) + } else { + n.acos() + }, + Unit::Deg, + true, + ), v @ Value::Dimension(Some(..), ..) => { return Err(( format!( From 2b9cad5971c5239690d88af86efa211c4ab1a2d6 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Mon, 27 Jul 2020 17:58:29 -0400 Subject: [PATCH 28/59] implement builtin function `meta.module-functions` --- src/builtin/modules/meta.rs | 43 ++++++++++++++++++++++++++++--------- src/builtin/modules/mod.rs | 18 ++++++++++++++-- src/value/map.rs | 4 ++++ 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/builtin/modules/meta.rs b/src/builtin/modules/meta.rs index be4a626..fb14935 100644 --- a/src/builtin/modules/meta.rs +++ b/src/builtin/modules/meta.rs @@ -18,25 +18,48 @@ fn load_css(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } fn module_functions(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { - args.max_args(2)?; - todo!() + args.max_args(1)?; + + let module = match args.get_err(0, "module")? { + Value::String(s, ..) => s, + v => { + return Err(( + format!("$module: {} is not a string.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + Ok(Value::Map( + parser + .modules + .get(&module) + .ok_or(( + format!("There is no module with the namespace \"{}\".", module), + args.span(), + ))? + .functions(), + )) } fn module_variables(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { - args.max_args(2)?; + args.max_args(1)?; todo!() } pub(crate) fn declare(f: &mut Module) { - f.insert_builtin("call", call); - f.insert_builtin("content-exists", content_exists); f.insert_builtin("feature-exists", feature_exists); - f.insert_builtin("function-exists", function_exists); - f.insert_builtin("get-function", get_function); - f.insert_builtin("global-variable-exists", global_variable_exists); f.insert_builtin("inspect", inspect); - f.insert_builtin("keywords", keywords); - f.insert_builtin("mixin-exists", mixin_exists); f.insert_builtin("type-of", type_of); + f.insert_builtin("keywords", keywords); + f.insert_builtin("global-variable-exists", global_variable_exists); f.insert_builtin("variable-exists", variable_exists); + f.insert_builtin("function-exists", function_exists); + f.insert_builtin("mixin-exists", mixin_exists); + f.insert_builtin("content-exists", content_exists); + f.insert_builtin("module-variables", module_variables); + f.insert_builtin("module-functions", module_functions); + f.insert_builtin("get-function", get_function); + f.insert_builtin("call", call); } diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index f32917f..34bf783 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -8,10 +8,10 @@ use crate::{ args::CallArgs, atrule::Mixin, builtin::Builtin, - common::Identifier, + common::{Identifier, QuoteKind}, error::SassResult, parse::Parser, - value::{SassFunction, Value}, + value::{SassFunction, SassMap, Value}, }; mod color; @@ -54,6 +54,20 @@ impl Module { self.functions .insert(ident, SassFunction::Builtin(Builtin::new(function), ident)); } + + pub fn functions(&self) -> SassMap { + SassMap::new_with( + self.functions + .iter() + .map(|(key, value)| { + ( + Value::String(key.to_string(), QuoteKind::Quoted), + Value::FunctionRef(value.clone()), + ) + }) + .collect::>(), + ) + } } pub(crate) fn declare_module_color() -> Module { diff --git a/src/value/map.rs b/src/value/map.rs index e5071c6..fc2c3f9 100644 --- a/src/value/map.rs +++ b/src/value/map.rs @@ -34,6 +34,10 @@ impl SassMap { SassMap(Vec::new()) } + pub const fn new_with(elements: Vec<(Value, Value)>) -> SassMap { + SassMap(elements) + } + /// We take by value here (consuming the map) in order to /// save a clone of the value, since the only place this /// should be called is in a builtin function, which throws From 36d7b5d9204ec4674a6d591ac543bb3c89c8c043 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Mon, 27 Jul 2020 18:06:00 -0400 Subject: [PATCH 29/59] implement builtin function `meta.module-variables` --- src/builtin/modules/math.rs | 8 ++++---- src/builtin/modules/meta.rs | 23 ++++++++++++++++++++++- src/builtin/modules/mod.rs | 14 ++++++++++++++ tests/meta-module.rs | 15 +++++++++++++++ 4 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 tests/meta-module.rs diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index a151c80..9de39cc 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -378,12 +378,12 @@ pub(crate) fn declare(f: &mut Module) { #[cfg(feature = "random")] f.insert_builtin("random", random); - f.insert_builtin_var( - "pi", - Value::Dimension(Some(Number::from(std::f64::consts::PI)), Unit::None, true), - ); f.insert_builtin_var( "e", Value::Dimension(Some(Number::from(std::f64::consts::E)), Unit::None, true), ); + f.insert_builtin_var( + "pi", + Value::Dimension(Some(Number::from(std::f64::consts::PI)), Unit::None, true), + ); } diff --git a/src/builtin/modules/meta.rs b/src/builtin/modules/meta.rs index fb14935..de6153a 100644 --- a/src/builtin/modules/meta.rs +++ b/src/builtin/modules/meta.rs @@ -45,7 +45,28 @@ fn module_functions(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult { args.max_args(1)?; - todo!() + + let module = match args.get_err(0, "module")? { + Value::String(s, ..) => s, + v => { + return Err(( + format!("$module: {} is not a string.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + Ok(Value::Map( + parser + .modules + .get(&module) + .ok_or(( + format!("There is no module with the namespace \"{}\".", module), + args.span(), + ))? + .variables(), + )) } pub(crate) fn declare(f: &mut Module) { diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index 34bf783..0e3a3ac 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -68,6 +68,20 @@ impl Module { .collect::>(), ) } + + pub fn variables(&self) -> SassMap { + SassMap::new_with( + self.vars + .iter() + .map(|(key, value)| { + ( + Value::String(key.to_string(), QuoteKind::Quoted), + value.clone(), + ) + }) + .collect::>(), + ) + } } pub(crate) fn declare_module_color() -> Module { diff --git a/tests/meta-module.rs b/tests/meta-module.rs new file mode 100644 index 0000000..da5b626 --- /dev/null +++ b/tests/meta-module.rs @@ -0,0 +1,15 @@ +#![cfg(test)] + +#[macro_use] +mod macros; + +test!( + module_functions_builtin, + "@use 'sass:meta';\na {\n color: inspect(meta.module-functions(meta));\n}\n", + "a {\n color: (\"feature-exists\": get-function(\"feature-exists\"), \"inspect\": get-function(\"inspect\"), \"type-of\": get-function(\"type-of\"), \"keywords\": get-function(\"keywords\"), \"global-variable-exists\": get-function(\"global-variable-exists\"), \"variable-exists\": get-function(\"variable-exists\"), \"function-exists\": get-function(\"function-exists\"), \"mixin-exists\": get-function(\"mixin-exists\"), \"content-exists\": get-function(\"content-exists\"), \"module-variables\": get-function(\"module-variables\"), \"module-functions\": get-function(\"module-functions\"), \"get-function\": get-function(\"get-function\"), \"call\": get-function(\"call\"));\n}\n" +); +test!( + module_variables_builtin, + "@use 'sass:meta';\n@use 'sass:math';\na {\n color: inspect(meta.module-variables(math));\n}\n", + "a {\n color: (\"e\": 2.7182818285, \"pi\": 3.1415926536);\n}\n" +); From d6a1d64dcb6ec522f527f83ce993ba7fc99eeb28 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Mon, 27 Jul 2020 18:55:38 -0400 Subject: [PATCH 30/59] implement `@use ... as *;` syntax --- src/builtin/functions/meta.rs | 2 +- src/builtin/modules/mod.rs | 6 +-- src/parse/args.rs | 14 ++----- src/parse/control_flow.rs | 21 ++-------- src/parse/function.rs | 16 ++++++-- src/parse/mod.rs | 76 +++++++++++++++++++---------------- src/parse/value/parse.rs | 3 +- src/parse/variable.rs | 6 +-- src/scope.rs | 40 +++++++++--------- tests/use.rs | 8 ++++ 10 files changed, 98 insertions(+), 94 deletions(-) diff --git a/src/builtin/functions/meta.rs b/src/builtin/functions/meta.rs index 2190701..a5e0087 100644 --- a/src/builtin/functions/meta.rs +++ b/src/builtin/functions/meta.rs @@ -181,7 +181,7 @@ pub(crate) fn get_function(mut args: CallArgs, parser: &mut Parser<'_>) -> SassR }, parser.global_scope, ) { - Some(f) => SassFunction::UserDefined(Box::new(f), name), + Some(f) => f, None => match GLOBAL_FUNCTIONS.get(name.as_str()) { Some(f) => SassFunction::Builtin(f.clone(), name), None => return Err((format!("Function not found: {}", name), args.span()).into()), diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index 0e3a3ac..102772e 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -24,9 +24,9 @@ mod string; #[derive(Debug, Default)] pub(crate) struct Module { - vars: BTreeMap, - mixins: BTreeMap, - functions: BTreeMap, + pub vars: BTreeMap, + pub mixins: BTreeMap, + pub functions: BTreeMap, } impl Module { diff --git a/src/parse/args.rs b/src/parse/args.rs index 053e160..b569a16 100644 --- a/src/parse/args.rs +++ b/src/parse/args.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, mem}; -use codemap::{Span, Spanned}; +use codemap::Span; use crate::{ args::{CallArg, CallArgs, FuncArg, FuncArgs}, @@ -283,15 +283,8 @@ impl<'a> Parser<'a> { self.scopes.enter_new_scope(); for (idx, mut arg) in fn_args.0.into_iter().enumerate() { if arg.is_variadic { - let span = args.span(); let arg_list = Value::ArgList(args.get_variadic()?); - scope.insert_var( - arg.name, - Spanned { - node: arg_list, - span, - }, - ); + scope.insert_var(arg.name, arg_list); break; } let val = match args.get(idx, arg.name) { @@ -304,7 +297,8 @@ impl<'a> Parser<'a> { ) } }, - }?; + }? + .node; self.scopes.insert_var(arg.name, val.clone()); scope.insert_var(arg.name, val); } diff --git a/src/parse/control_flow.rs b/src/parse/control_flow.rs index 913ad2a..0293664 100644 --- a/src/parse/control_flow.rs +++ b/src/parse/control_flow.rs @@ -304,10 +304,7 @@ impl<'a> Parser<'a> { for i in iter { self.scopes.insert_var_last( var.node, - Spanned { - node: Value::Dimension(Some(Number::from(i)), Unit::None, true), - span: var.span, - }, + Value::Dimension(Some(Number::from(i)), Unit::None, true), ); if self.flags.in_function() { let these_stmts = Parser { @@ -487,26 +484,14 @@ impl<'a> Parser<'a> { for row in iter { if vars.len() == 1 { - self.scopes.insert_var_last( - vars[0].node, - Spanned { - node: row, - span: vars[0].span, - }, - ); + self.scopes.insert_var_last(vars[0].node, row); } else { for (var, val) in vars.iter().zip( row.as_list() .into_iter() .chain(std::iter::once(Value::Null).cycle()), ) { - self.scopes.insert_var_last( - var.node, - Spanned { - node: val, - span: var.span, - }, - ); + self.scopes.insert_var_last(var.node, val); } } diff --git a/src/parse/function.rs b/src/parse/function.rs index 67544e5..a644332 100644 --- a/src/parse/function.rs +++ b/src/parse/function.rs @@ -4,11 +4,11 @@ use peekmore::PeekMore; use crate::{ args::CallArgs, atrule::Function, - common::unvendor, + common::{unvendor, Identifier}, error::SassResult, scope::Scopes, utils::{read_until_closing_curly_brace, read_until_semicolon_or_closing_curly_brace}, - value::Value, + value::{SassFunction, Value}, Token, }; @@ -53,10 +53,18 @@ impl<'a> Parser<'a> { let function = Function::new(args, body, self.at_root, span); + let name_as_ident = Identifier::from(name); + if self.at_root { - self.global_scope.insert_fn(name, function); + self.global_scope.insert_fn( + name_as_ident, + SassFunction::UserDefined(Box::new(function), name_as_ident), + ); } else { - self.scopes.insert_fn(name.into(), function); + self.scopes.insert_fn( + name_as_ident, + SassFunction::UserDefined(Box::new(function), name_as_ident), + ); } Ok(()) } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 7e77032..b6a9456 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -150,11 +150,11 @@ impl<'a> Parser<'a> { }; let Spanned { node: module, span } = self.parse_quoted_string(quote)?; - let module = module.unquote().to_css_string(span)?; + let module_name = module.unquote().to_css_string(span)?; self.whitespace_or_comment(); - let mut module_name: Option = None; + let mut module_alias: Option = None; match self.toks.peek() { Some(Token { kind: ';', .. }) => { @@ -170,12 +170,21 @@ impl<'a> Parser<'a> { self.whitespace_or_comment(); - let name = self.parse_identifier_no_interpolation(false)?; + let name_span; - module_name = Some(name.node); + if let Some(Token { kind: '*', pos }) = self.toks.peek() { + name_span = *pos; + self.toks.next(); + module_alias = Some('*'.to_string()); + } else { + let name = self.parse_identifier_no_interpolation(false)?; + + module_alias = Some(name.node); + name_span = name.span; + } if !matches!(self.toks.next(), Some(Token { kind: ';', .. })) { - return Err(("expected \";\".", name.span).into()); + return Err(("expected \";\".", name_span).into()); } } Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. }) => { @@ -184,37 +193,36 @@ impl<'a> Parser<'a> { Some(..) | None => return Err(("expected \";\".", span).into()), } - match module.as_ref() { - "sass:color" => self.modules.insert( - module_name.unwrap_or_else(|| "color".to_owned()), - declare_module_color(), - ), - "sass:list" => self.modules.insert( - module_name.unwrap_or_else(|| "list".to_owned()), - declare_module_list(), - ), - "sass:map" => self.modules.insert( - module_name.unwrap_or_else(|| "map".to_owned()), - declare_module_map(), - ), - "sass:math" => self.modules.insert( - module_name.unwrap_or_else(|| "math".to_owned()), - declare_module_math(), - ), - "sass:meta" => self.modules.insert( - module_name.unwrap_or_else(|| "meta".to_owned()), - declare_module_meta(), - ), - "sass:selector" => self.modules.insert( - module_name.unwrap_or_else(|| "selector".to_owned()), - declare_module_selector(), - ), - "sass:string" => self.modules.insert( - module_name.unwrap_or_else(|| "string".to_owned()), - declare_module_string(), - ), + let module = match module_name.as_ref() { + "sass:color" => declare_module_color(), + "sass:list" => declare_module_list(), + "sass:map" => declare_module_map(), + "sass:math" => declare_module_math(), + "sass:meta" => declare_module_meta(), + "sass:selector" => declare_module_selector(), + "sass:string" => declare_module_string(), _ => todo!("@use not yet implemented"), }; + + let module_name = match module_alias.as_deref() { + Some("*") => { + self.global_scope.merge_module(module); + continue; + } + Some(..) => module_alias.unwrap(), + None => match module_name.as_ref() { + "sass:color" => "color".to_owned(), + "sass:list" => "list".to_owned(), + "sass:map" => "map".to_owned(), + "sass:math" => "math".to_owned(), + "sass:meta" => "meta".to_owned(), + "sass:selector" => "selector".to_owned(), + "sass:string" => "string".to_owned(), + _ => module_name.into_owned(), + }, + }; + + self.modules.insert(module_name, module); } Some(Token { kind: '/', .. }) => { self.toks.next(); diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 795261c..6fd9324 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -355,8 +355,7 @@ impl<'a> Parser<'a> { let call_args = self.parse_call_args()?; return Ok(IntermediateValue::Value(HigherIntermediateValue::Function( - SassFunction::UserDefined(Box::new(func), as_ident), - call_args, + func, call_args, )) .span(span)); } diff --git a/src/parse/variable.rs b/src/parse/variable.rs index fe25971..ad08142 100644 --- a/src/parse/variable.rs +++ b/src/parse/variable.rs @@ -41,11 +41,11 @@ impl<'a> Parser<'a> { if default { if self.at_root && !self.flags.in_control_flow() { if !self.global_scope.var_exists(ident) { - let value = self.parse_value_from_vec(val_toks, true)?; + let value = self.parse_value_from_vec(val_toks, true)?.node; self.global_scope.insert_var(ident, value); } } else { - let value = self.parse_value_from_vec(val_toks, true)?; + let value = self.parse_value_from_vec(val_toks, true)?.node; if global && !self.global_scope.var_exists(ident) { self.global_scope.insert_var(ident, value.clone()); } @@ -55,7 +55,7 @@ impl<'a> Parser<'a> { return Ok(()); } - let value = self.parse_value_from_vec(val_toks, true)?; + let value = self.parse_value_from_vec(val_toks, true)?.node; if global { self.global_scope.insert_var(ident, value.clone()); diff --git a/src/scope.rs b/src/scope.rs index 484b1aa..9463e54 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -3,18 +3,18 @@ use std::collections::BTreeMap; use codemap::Spanned; use crate::{ - atrule::{Function, Mixin}, - builtin::GLOBAL_FUNCTIONS, + atrule::Mixin, + builtin::{modules::Module, GLOBAL_FUNCTIONS}, common::Identifier, error::SassResult, - value::Value, + value::{SassFunction, Value}, }; #[derive(Debug, Default)] pub(crate) struct Scope { - vars: BTreeMap>, + vars: BTreeMap, mixins: BTreeMap, - functions: BTreeMap, + functions: BTreeMap, } impl Scope { @@ -31,12 +31,12 @@ impl Scope { fn get_var(&self, name: Spanned) -> SassResult<&Value> { match self.vars.get(&name.node) { - Some(v) => Ok(&v.node), + Some(v) => Ok(v), None => Err(("Undefined variable.", name.span).into()), } } - pub fn insert_var(&mut self, s: Identifier, v: Spanned) -> Option> { + pub fn insert_var(&mut self, s: Identifier, v: Value) -> Option { self.vars.insert(s, v) } @@ -59,12 +59,12 @@ impl Scope { self.mixins.contains_key(&name) } - fn get_fn(&self, name: Identifier) -> Option { + fn get_fn(&self, name: Identifier) -> Option { self.functions.get(&name).cloned() } - pub fn insert_fn>(&mut self, s: T, v: Function) -> Option { - self.functions.insert(s.into(), v) + pub fn insert_fn(&mut self, s: Identifier, v: SassFunction) -> Option { + self.functions.insert(s, v) } fn fn_exists(&self, name: Identifier) -> bool { @@ -79,6 +79,12 @@ impl Scope { self.mixins.extend(other.mixins); self.functions.extend(other.functions); } + + pub fn merge_module(&mut self, other: Module) { + self.vars.extend(other.vars); + self.mixins.extend(other.mixins); + self.functions.extend(other.functions); + } } #[derive(Debug, Default)] @@ -112,7 +118,7 @@ impl Scopes { /// Variables impl Scopes { - pub fn insert_var(&mut self, s: Identifier, v: Spanned) -> Option> { + pub fn insert_var(&mut self, s: Identifier, v: Value) -> Option { for scope in self.0.iter_mut().rev() { if scope.var_exists(s) { return scope.insert_var(s, v); @@ -131,7 +137,7 @@ impl Scopes { /// Always insert this variable into the innermost scope /// /// Used, for example, for variables from `@each` and `@for` - pub fn insert_var_last(&mut self, s: Identifier, v: Spanned) -> Option> { + pub fn insert_var_last(&mut self, s: Identifier, v: Value) -> Option { if let Some(scope) = self.0.last_mut() { scope.insert_var(s, v) } else { @@ -142,11 +148,7 @@ impl Scopes { } } - pub fn insert_default_var( - &mut self, - s: Identifier, - v: Spanned, - ) -> Option> { + pub fn insert_default_var(&mut self, s: Identifier, v: Value) -> Option { if let Some(scope) = self.0.last_mut() { if scope.var_exists(s) { None @@ -219,7 +221,7 @@ impl Scopes { /// Functions impl Scopes { - pub fn insert_fn(&mut self, s: Identifier, v: Function) -> Option { + pub fn insert_fn(&mut self, s: Identifier, v: SassFunction) -> Option { if let Some(scope) = self.0.last_mut() { scope.insert_fn(s, v) } else { @@ -234,7 +236,7 @@ impl Scopes { &'a self, name: Spanned, global_scope: &'a Scope, - ) -> Option { + ) -> Option { for scope in self.0.iter().rev() { if scope.fn_exists(name.node) { return scope.get_fn(name.node); diff --git a/tests/use.rs b/tests/use.rs index b40a5d3..4a20f9e 100644 --- a/tests/use.rs +++ b/tests/use.rs @@ -50,3 +50,11 @@ test!( }", "a {\n color: 1;\n}\n" ); +test!( + use_as_universal, + "@use \"sass:math\" as *; + a { + color: cos(2); + }", + "a {\n color: -0.4161468365;\n}\n" +); From 2fb19e161e7ee10126287116074908f349dce291 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Wed, 29 Jul 2020 08:58:21 -0400 Subject: [PATCH 31/59] resolve newly failing tests from merge --- src/parse/value/parse.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 3ceb474..fcbbdfd 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -297,7 +297,10 @@ impl<'a> Parser<'a> { .span(module_span)) } - fn parse_ident_value(&mut self) -> SassResult> { + fn parse_ident_value( + &mut self, + predicate: &dyn Fn(&mut PeekMoreIterator>) -> bool, + ) -> SassResult> { let Spanned { node: mut s, span } = self.parse_identifier()?; self.span_before = span; @@ -384,8 +387,10 @@ impl<'a> Parser<'a> { .span(span)); } Some(Token { kind: '.', .. }) => { - self.toks.next(); - return self.parse_module_item(&s, span); + if !predicate(self.toks) { + self.toks.next(); + return self.parse_module_item(&s, span); + } } _ => {} } @@ -524,7 +529,7 @@ impl<'a> Parser<'a> { || (!kind.is_ascii() && !kind.is_control()) || (kind == '-' && self.next_is_hypen()) => { - return Some(self.parse_ident_value()); + return Some(self.parse_ident_value(predicate)); } '0'..='9' | '.' => { let Spanned { @@ -652,7 +657,7 @@ impl<'a> Parser<'a> { if let Some(Token { kind: '{', pos }) = self.toks.peek_forward(1) { self.span_before = *pos; self.toks.reset_cursor(); - return Some(self.parse_ident_value()); + return Some(self.parse_ident_value(predicate)); } self.toks.reset_cursor(); self.toks.next(); From 8c1cde8a613acdfd9ed49e9f0ba0eb5ba8a46538 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Wed, 29 Jul 2020 18:13:37 -0400 Subject: [PATCH 32/59] implement builtin function `math.pow` --- src/builtin/modules/math.rs | 48 ++++++++++++++++++++++++++++++- src/value/number/mod.rs | 6 ++++ tests/math-module.rs | 56 +++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index 9de39cc..07d64af 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -174,7 +174,52 @@ fn log(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn pow(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; - todo!() + + let base = match args.get_err(0, "base")? { + Value::Dimension(Some(n), Unit::None, ..) => n, + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$base: Expected {} to have no units.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + Value::Dimension(None, ..) => return Ok(Value::Dimension(None, Unit::None, true)), + v => { + return Err(( + format!("$base: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + let exponent = match args.get_err(1, "exponent")? { + Value::Dimension(Some(n), Unit::None, ..) => n, + v @ Value::Dimension(Some(..), ..) => { + return Err(( + format!( + "$exponent: Expected {} to have no units.", + v.inspect(args.span())? + ), + args.span(), + ) + .into()) + } + Value::Dimension(None, ..) => return Ok(Value::Dimension(None, Unit::None, true)), + v => { + return Err(( + format!("$exponent: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + Ok(Value::Dimension(base.pow(exponent), Unit::None, true)) } fn sqrt(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { @@ -375,6 +420,7 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("asin", asin); f.insert_builtin("atan", atan); f.insert_builtin("log", log); + f.insert_builtin("pow", pow); #[cfg(feature = "random")] f.insert_builtin("random", random); diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index 14eb52c..a95a81a 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -128,6 +128,12 @@ impl Number { self.as_float()?.ln(), )?))) } + + pub fn pow(self, exponent: Self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.powf(exponent.as_float()?), + )?))) + } } macro_rules! trig_fn( diff --git a/tests/math-module.rs b/tests/math-module.rs index 9a32c9a..2915c28 100644 --- a/tests/math-module.rs +++ b/tests/math-module.rs @@ -390,3 +390,59 @@ test!( "@use 'sass:math';\na {\n color: math.log(2, .5);\n}\n", "a {\n color: -1;\n}\n" ); + +test!( + pow_exponent_and_base_one, + "@use 'sass:math';\na {\n color: math.pow(1, 1);\n}\n", + "a {\n color: 1;\n}\n" +); +test!( + pow_exponent_and_base_ten, + "@use 'sass:math';\na {\n color: math.pow(10, 10);\n}\n", + "a {\n color: 10000000000;\n}\n" +); +test!( + pow_base_negative_exponent_positive, + "@use 'sass:math';\na {\n color: math.pow(-2, 3);\n}\n", + "a {\n color: -8;\n}\n" +); +test!( + pow_base_positive_exponent_negative, + "@use 'sass:math';\na {\n color: math.pow(2, -3);\n}\n", + "a {\n color: 0.125;\n}\n" +); +test!( + pow_base_negative_exponent_negative, + "@use 'sass:math';\na {\n color: math.pow(-2, -3);\n}\n", + "a {\n color: -0.125;\n}\n" +); +test!( + pow_base_decimal, + "@use 'sass:math';\na {\n color: math.pow(2.4, 3);\n}\n", + "a {\n color: 13.824;\n}\n" +); +test!( + pow_exponent_decimal, + "@use 'sass:math';\na {\n color: math.pow(2, 3.5);\n}\n", + "a {\n color: 11.313708499;\n}\n" +); +test!( + pow_base_nan, + "@use 'sass:math';\na {\n color: math.pow((0 / 0), 3);\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + pow_exponent_nan, + "@use 'sass:math';\na {\n color: math.pow(2, (0 / 0));\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + pow_base_and_exponent_nan, + "@use 'sass:math';\na {\n color: math.pow((0 / 0), (0 / 0));\n}\n", + "a {\n color: NaN;\n}\n" +); +test!( + pow_exponent_zero, + "@use 'sass:math';\na {\n color: math.pow(2, 0);\n}\n", + "a {\n color: 1;\n}\n" +); From 80986efee94cad92c582a82ffa8d19bb56d34313 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Fri, 31 Jul 2020 04:39:06 +0800 Subject: [PATCH 33/59] refactor `is_ms_filter` * refactor to use matches or patterns * simplify text searching with iterator * match is_ms_filter on bytes level * add regex doc for is_ms_filter * use is_ascii_alphabetic * check equality rather than map_or = Co-authored-by: Connor Skees --- src/builtin/functions/color/opacity.rs | 31 ++++++-------------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/src/builtin/functions/color/opacity.rs b/src/builtin/functions/color/opacity.rs index 2314132..e61501b 100644 --- a/src/builtin/functions/color/opacity.rs +++ b/src/builtin/functions/color/opacity.rs @@ -5,35 +5,18 @@ use crate::{ value::Value, }; +/// Matches regex `^[a-zA-Z]+\s*=`. fn is_ms_filter(s: &str) -> bool { - let mut chars = s.chars(); + let mut bytes = s.bytes(); - if let Some(c) = chars.next() { - if !matches!(c, 'a'..='z' | 'A'..='Z') { - return false; - } - } else { + if !bytes.next().map_or(false, |c| c.is_ascii_alphabetic()) { return false; } - for c in &mut chars { - match c { - ' ' | '\t' | '\n' => break, - 'a'..='z' | 'A'..='Z' => continue, - '=' => return true, - _ => return false, - } - } - - for c in chars { - match c { - ' ' | '\t' | '\n' => continue, - '=' => return true, - _ => return false, - } - } - - false + bytes + .skip_while(|c| c.is_ascii_alphabetic()) + .find(|c| !matches!(c, b' ' | b'\t' | b'\n')) + == Some(b'=') } #[cfg(test)] From af9864ff85461ed54998b451fea28e02622d212a Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 30 Jul 2020 16:42:08 -0400 Subject: [PATCH 34/59] resolve clippy lint for `is_ms_filter` --- src/builtin/functions/color/opacity.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/builtin/functions/color/opacity.rs b/src/builtin/functions/color/opacity.rs index e61501b..2686a23 100644 --- a/src/builtin/functions/color/opacity.rs +++ b/src/builtin/functions/color/opacity.rs @@ -5,7 +5,7 @@ use crate::{ value::Value, }; -/// Matches regex `^[a-zA-Z]+\s*=`. +/// Check if `s` matches the regex `^[a-zA-Z]+\s*=` fn is_ms_filter(s: &str) -> bool { let mut bytes = s.bytes(); @@ -14,7 +14,7 @@ fn is_ms_filter(s: &str) -> bool { } bytes - .skip_while(|c| c.is_ascii_alphabetic()) + .skip_while(u8::is_ascii_alphabetic) .find(|c| !matches!(c, b' ' | b'\t' | b'\n')) == Some(b'=') } From a03ad51b71398608a4e71f2ebdcd2109c02c3f80 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 30 Jul 2020 17:21:32 -0400 Subject: [PATCH 35/59] allow `@use` of user-defined modules --- src/builtin/modules/mod.rs | 26 +++++---- src/parse/import.rs | 110 ++++++++++++++++++------------------- src/parse/mod.rs | 44 ++++++++++++++- src/scope.rs | 10 ++-- tests/imports.rs | 35 +----------- tests/macros.rs | 34 ++++++++++++ tests/use.rs | 38 +++++++++++++ 7 files changed, 189 insertions(+), 108 deletions(-) diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index 102772e..4887a0b 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -11,6 +11,7 @@ use crate::{ common::{Identifier, QuoteKind}, error::SassResult, parse::Parser, + scope::Scope, value::{SassFunction, SassMap, Value}, }; @@ -23,26 +24,22 @@ mod selector; mod string; #[derive(Debug, Default)] -pub(crate) struct Module { - pub vars: BTreeMap, - pub mixins: BTreeMap, - pub functions: BTreeMap, -} +pub(crate) struct Module(pub Scope); impl Module { pub fn get_var(&self, name: Spanned) -> SassResult<&Value> { - match self.vars.get(&name.node) { + match self.0.vars.get(&name.node) { Some(v) => Ok(v), None => Err(("Undefined variable.", name.span).into()), } } pub fn insert_builtin_var(&mut self, name: &'static str, value: Value) { - self.vars.insert(name.into(), value); + self.0.vars.insert(name.into(), value); } pub fn get_fn(&self, name: Identifier) -> Option { - self.functions.get(&name).cloned() + self.0.functions.get(&name).cloned() } pub fn insert_builtin( @@ -51,13 +48,15 @@ impl Module { function: fn(CallArgs, &mut Parser<'_>) -> SassResult, ) { let ident = name.into(); - self.functions + self.0 + .functions .insert(ident, SassFunction::Builtin(Builtin::new(function), ident)); } pub fn functions(&self) -> SassMap { SassMap::new_with( - self.functions + self.0 + .functions .iter() .map(|(key, value)| { ( @@ -71,7 +70,8 @@ impl Module { pub fn variables(&self) -> SassMap { SassMap::new_with( - self.vars + self.0 + .vars .iter() .map(|(key, value)| { ( @@ -82,6 +82,10 @@ impl Module { .collect::>(), ) } + + pub const fn new_from_scope(scope: Scope) -> Self { + Module(scope) + } } pub(crate) fn declare_module_color() -> Module { diff --git a/src/parse/import.rs b/src/parse/import.rs index 5863e3c..28eac59 100644 --- a/src/parse/import.rs +++ b/src/parse/import.rs @@ -13,60 +13,13 @@ use crate::{ use super::{Parser, Stmt}; -/// Searches the current directory of the file then searches in `load_paths` directories -/// if the import has not yet been found. -/// -/// -fn find_import(file_path: &PathBuf, name: &OsStr, load_paths: &[&Path]) -> Option { - let paths = [ - file_path.with_file_name(name).with_extension("scss"), - file_path - .with_file_name(format!("_{}", name.to_str().unwrap())) - .with_extension("scss"), - file_path.clone(), - file_path.join("index.scss"), - file_path.join("_index.scss"), - ]; - - for name in &paths { - if name.is_file() { - return Some(name.to_path_buf()); - } - } - - for path in load_paths { - let paths: Vec = if path.is_dir() { - vec![ - path.join(format!("{}.scss", name.to_str().unwrap())), - path.join(format!("_{}.scss", name.to_str().unwrap())), - path.join("index.scss"), - path.join("_index.scss"), - ] - } else { - vec![ - path.to_path_buf(), - path.with_file_name(name).with_extension("scss"), - path.with_file_name(format!("_{}", name.to_str().unwrap())) - .with_extension("scss"), - path.join("index.scss"), - path.join("_index.scss"), - ] - }; - - for name in paths { - if name.is_file() { - return Some(name); - } - } - } - - None -} - impl<'a> Parser<'a> { - fn parse_single_import(&mut self, file_name: &str, span: Span) -> SassResult> { - let path: &Path = file_name.as_ref(); - + /// Searches the current directory of the file then searches in `load_paths` directories + /// if the import has not yet been found. + /// + /// + /// + pub(super) fn find_import(&self, path: &Path) -> Option { let path_buf = if path.is_absolute() { // todo: test for absolute path imports path.into() @@ -79,7 +32,55 @@ impl<'a> Parser<'a> { let name = path_buf.file_name().unwrap_or_else(|| OsStr::new("..")); - if let Some(name) = find_import(&path_buf, name, &self.options.load_paths) { + let paths = [ + path_buf.with_file_name(name).with_extension("scss"), + path_buf + .with_file_name(format!("_{}", name.to_str().unwrap())) + .with_extension("scss"), + path_buf.clone(), + path_buf.join("index.scss"), + path_buf.join("_index.scss"), + ]; + + for name in &paths { + if name.is_file() { + return Some(name.to_path_buf()); + } + } + + for path in &self.options.load_paths { + let paths: Vec = if path.is_dir() { + vec![ + path.join(format!("{}.scss", name.to_str().unwrap())), + path.join(format!("_{}.scss", name.to_str().unwrap())), + path.join("index.scss"), + path.join("_index.scss"), + ] + } else { + vec![ + path.to_path_buf(), + path.with_file_name(name).with_extension("scss"), + path.with_file_name(format!("_{}", name.to_str().unwrap())) + .with_extension("scss"), + path.join("index.scss"), + path.join("_index.scss"), + ] + }; + + for name in paths { + if name.is_file() { + return Some(name); + } + } + } + + None + } + + fn parse_single_import(&mut self, file_name: &str, span: Span) -> SassResult> { + let path: &Path = file_name.as_ref(); + + if let Some(name) = self.find_import(path) { let file = self.map.add_file( name.to_string_lossy().into(), String::from_utf8(fs::read(&name)?)?, @@ -106,7 +107,6 @@ impl<'a> Parser<'a> { } .parse(); } - self.whitespace(); Err(("Can't find stylesheet to import.", span).into()) } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 80c3759..4f189ac 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, convert::TryFrom, path::Path, vec::IntoIter}; +use std::{collections::HashMap, convert::TryFrom, fs, path::Path, vec::IntoIter}; use codemap::{CodeMap, Span, Spanned}; use peekmore::{PeekMore, PeekMoreIterator}; @@ -14,6 +14,7 @@ use crate::{ declare_module_meta, declare_module_selector, declare_module_string, Module, }, error::SassResult, + lexer::Lexer, scope::{Scope, Scopes}, selector::{ ComplexSelectorComponent, ExtendRule, ExtendedSelector, Extender, Selector, SelectorParser, @@ -201,7 +202,46 @@ impl<'a> Parser<'a> { "sass:meta" => declare_module_meta(), "sass:selector" => declare_module_selector(), "sass:string" => declare_module_string(), - _ => todo!("@use not yet implemented"), + _ => { + if let Some(import) = self.find_import(module_name.as_ref().as_ref()) { + let mut global_scope = Scope::new(); + + let file = self.map.add_file( + module_name.clone().into_owned(), + String::from_utf8(fs::read(&import)?)?, + ); + + comments.append( + &mut Parser { + toks: &mut Lexer::new(&file) + .collect::>() + .into_iter() + .peekmore(), + map: self.map, + path: &import, + scopes: self.scopes, + global_scope: &mut global_scope, + super_selectors: self.super_selectors, + span_before: file.span.subspan(0, 0), + content: self.content, + flags: self.flags, + at_root: self.at_root, + at_root_has_selector: self.at_root_has_selector, + extender: self.extender, + content_scopes: self.content_scopes, + options: self.options, + modules: self.modules, + } + .parse()?, + ); + + Module::new_from_scope(global_scope) + } else { + return Err( + ("Error: Can't find stylesheet to import.", span).into() + ); + } + } }; let module_name = match module_alias.as_deref() { diff --git a/src/scope.rs b/src/scope.rs index 9463e54..b5f3db9 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -12,9 +12,9 @@ use crate::{ #[derive(Debug, Default)] pub(crate) struct Scope { - vars: BTreeMap, - mixins: BTreeMap, - functions: BTreeMap, + pub vars: BTreeMap, + pub mixins: BTreeMap, + pub functions: BTreeMap, } impl Scope { @@ -81,9 +81,7 @@ impl Scope { } pub fn merge_module(&mut self, other: Module) { - self.vars.extend(other.vars); - self.mixins.extend(other.mixins); - self.functions.extend(other.functions); + self.merge(other.0); } } diff --git a/tests/imports.rs b/tests/imports.rs index f076028..c0888d0 100644 --- a/tests/imports.rs +++ b/tests/imports.rs @@ -1,43 +1,10 @@ #![cfg(test)] + use std::io::Write; -use tempfile::Builder; #[macro_use] mod macros; -/// Create a temporary file with the given name -/// and contents. -/// -/// This must be a macro rather than a function -/// because the tempfile will be deleted when it -/// exits scope -macro_rules! tempfile { - ($name:literal, $content:literal) => { - let mut f = Builder::new() - .rand_bytes(0) - .prefix("") - .suffix($name) - .tempfile_in("") - .unwrap(); - write!(f, "{}", $content).unwrap(); - }; - ($name:literal, $content:literal, dir=$dir:literal) => { - let _d = Builder::new() - .rand_bytes(0) - .prefix("") - .suffix($dir) - .tempdir_in("") - .unwrap(); - let mut f = Builder::new() - .rand_bytes(0) - .prefix("") - .suffix($name) - .tempfile_in($dir) - .unwrap(); - write!(f, "{}", $content).unwrap(); - }; -} - #[test] fn imports_variable() { let input = "@import \"imports_variable\";\na {\n color: $a;\n}"; diff --git a/tests/macros.rs b/tests/macros.rs index c6c1c7c..da4aa99 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -51,3 +51,37 @@ macro_rules! error { } }; } + +/// Create a temporary file with the given name +/// and contents. +/// +/// This must be a macro rather than a function +/// because the tempfile will be deleted when it +/// exits scope +#[macro_export] +macro_rules! tempfile { + ($name:literal, $content:literal) => { + let mut f = tempfile::Builder::new() + .rand_bytes(0) + .prefix("") + .suffix($name) + .tempfile_in("") + .unwrap(); + write!(f, "{}", $content).unwrap(); + }; + ($name:literal, $content:literal, dir=$dir:literal) => { + let _d = tempfile::Builder::new() + .rand_bytes(0) + .prefix("") + .suffix($dir) + .tempdir_in("") + .unwrap(); + let mut f = tempfile::Builder::new() + .rand_bytes(0) + .prefix("") + .suffix($name) + .tempfile_in($dir) + .unwrap(); + write!(f, "{}", $content).unwrap(); + }; +} diff --git a/tests/use.rs b/tests/use.rs index 4a20f9e..5998c09 100644 --- a/tests/use.rs +++ b/tests/use.rs @@ -1,5 +1,7 @@ #![cfg(test)] +use std::io::Write; + #[macro_use] mod macros; @@ -58,3 +60,39 @@ test!( }", "a {\n color: -0.4161468365;\n}\n" ); + +#[test] +fn use_user_defined_same_directory() { + let input = "@use \"use_user_defined_same_directory\";\na {\n color: use_user_defined_same_directory.$a;\n}"; + tempfile!( + "use_user_defined_same_directory.scss", + "$a: red; a { color: $a; }" + ); + assert_eq!( + "a {\n color: red;\n}\n\na {\n color: red;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) + ); +} + +#[test] +fn use_user_defined_as() { + let input = "@use \"use_user_defined_as\" as module;\na {\n color: module.$a;\n}"; + tempfile!("use_user_defined_as.scss", "$a: red; a { color: $a; }"); + assert_eq!( + "a {\n color: red;\n}\n\na {\n color: red;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) + ); +} + +#[test] +fn use_user_defined_function() { + let input = "@use \"use_user_defined_function\" as module;\na {\n color: module.foo(red);\n}"; + tempfile!( + "use_user_defined_function.scss", + "@function foo($a) { @return $a; }" + ); + assert_eq!( + "a {\n color: red;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) + ); +} From a7325436ca56851d7e613f402166ad2879192b25 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 30 Jul 2020 17:46:56 -0400 Subject: [PATCH 36/59] respect `$module` argument to `get-function` --- src/builtin/functions/meta.rs | 31 +++++++++++++++---------------- src/builtin/modules/meta.rs | 18 ++---------------- src/builtin/modules/mod.rs | 25 ++++++++++++++++++++++++- src/lib.rs | 9 +++++---- src/parse/mod.rs | 8 ++++---- src/parse/value/parse.rs | 23 +++-------------------- src/scope.rs | 12 ++++-------- tests/get-function.rs | 5 +++++ 8 files changed, 62 insertions(+), 69 deletions(-) diff --git a/src/builtin/functions/meta.rs b/src/builtin/functions/meta.rs index a5e0087..858f829 100644 --- a/src/builtin/functions/meta.rs +++ b/src/builtin/functions/meta.rs @@ -1,7 +1,5 @@ use super::{Builtin, GlobalFunctionMap, GLOBAL_FUNCTIONS}; -use codemap::Spanned; - use crate::{ args::CallArgs, common::{Identifier, QuoteKind}, @@ -166,21 +164,22 @@ pub(crate) fn get_function(mut args: CallArgs, parser: &mut Parser<'_>) -> SassR } }; - if module.is_some() && css { - return Err(( - "$css and $module may not both be passed at once.", - args.span(), - ) - .into()); - } + let func = match if let Some(module_name) = module { + if css { + return Err(( + "$css and $module may not both be passed at once.", + args.span(), + ) + .into()); + } - let func = match parser.scopes.get_fn( - Spanned { - node: name, - span: args.span(), - }, - parser.global_scope, - ) { + parser + .modules + .get(module_name.into(), args.span())? + .get_fn(name) + } else { + parser.scopes.get_fn(name, parser.global_scope) + } { Some(f) => f, None => match GLOBAL_FUNCTIONS.get(name.as_str()) { Some(f) => SassFunction::Builtin(f.clone(), name), diff --git a/src/builtin/modules/meta.rs b/src/builtin/modules/meta.rs index de6153a..1e0062e 100644 --- a/src/builtin/modules/meta.rs +++ b/src/builtin/modules/meta.rs @@ -32,14 +32,7 @@ fn module_functions(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult) -> SassResult); + +impl Modules { + pub fn insert(&mut self, name: Identifier, module: Module) { + self.0.insert(name, module); + } + + pub fn get(&self, name: Identifier, span: Span) -> SassResult<&Module> { + match self.0.get(&name) { + Some(v) => Ok(v), + None => Err(( + format!( + "There is no module with the namespace \"{}\".", + name.as_str() + ), + span, + ) + .into()), + } + } +} + impl Module { pub fn get_var(&self, name: Spanned) -> SassResult<&Value> { match self.0.vars.get(&name.node) { diff --git a/src/lib.rs b/src/lib.rs index dbea119..4e8cc6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,7 @@ grass input.scss )] #![cfg_attr(feature = "nightly", feature(track_caller))] #![cfg_attr(feature = "profiling", inline(never))] -use std::{collections::HashMap, fs, path::Path}; +use std::{fs, path::Path}; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; @@ -95,6 +95,7 @@ use peekmore::PeekMore; pub use crate::error::{SassError as Error, SassResult as Result}; pub(crate) use crate::token::Token; use crate::{ + builtin::modules::Modules, lexer::Lexer, output::Css, parse::{ @@ -293,7 +294,7 @@ pub fn from_path(p: &str, options: &Options) -> Result { extender: &mut Extender::new(empty_span), content_scopes: &mut Scopes::new(), options, - modules: &mut HashMap::new(), + modules: &mut Modules::default(), } .parse() .map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?; @@ -338,7 +339,7 @@ pub fn from_string(p: String, options: &Options) -> Result { extender: &mut Extender::new(empty_span), content_scopes: &mut Scopes::new(), options, - modules: &mut HashMap::new(), + modules: &mut Modules::default(), } .parse() .map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?; @@ -374,7 +375,7 @@ pub fn from_string(p: String) -> std::result::Result { extender: &mut Extender::new(empty_span), content_scopes: &mut Scopes::new(), options: &Options::default(), - modules: &mut HashMap::new(), + modules: &mut Modules::default(), } .parse() .map_err(|e| raw_to_parse_error(&map, *e, true).to_string())?; diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 4f189ac..074f778 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, convert::TryFrom, fs, path::Path, vec::IntoIter}; +use std::{convert::TryFrom, fs, path::Path, vec::IntoIter}; use codemap::{CodeMap, Span, Spanned}; use peekmore::{PeekMore, PeekMoreIterator}; @@ -11,7 +11,7 @@ use crate::{ }, builtin::modules::{ declare_module_color, declare_module_list, declare_module_map, declare_module_math, - declare_module_meta, declare_module_selector, declare_module_string, Module, + declare_module_meta, declare_module_selector, declare_module_string, Module, Modules, }, error::SassResult, lexer::Lexer, @@ -92,7 +92,7 @@ pub(crate) struct Parser<'a> { pub options: &'a Options<'a>, - pub modules: &'a mut HashMap, + pub modules: &'a mut Modules, } impl<'a> Parser<'a> { @@ -262,7 +262,7 @@ impl<'a> Parser<'a> { }, }; - self.modules.insert(module_name, module); + self.modules.insert(module_name.into(), module); } Some(Token { kind: '/', .. }) => { self.toks.next(); diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index fcbbdfd..0e268e2 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -261,14 +261,7 @@ impl<'a> Parser<'a> { module_span = module_span.merge(var.span); - let value = self - .modules - .get(module) - .ok_or(( - format!("There is no module with the namespace \"{}\".", module), - module_span, - ))? - .get_var(var)?; + let value = self.modules.get(module.into(), module_span)?.get_var(var)?; HigherIntermediateValue::Literal(value.clone()) } else { let fn_name = self @@ -277,11 +270,7 @@ impl<'a> Parser<'a> { let function = self .modules - .get(module) - .ok_or(( - format!("There is no module with the namespace \"{}\".", module), - module_span, - ))? + .get(module.into(), module_span)? .get_fn(fn_name.node) .ok_or(("Undefined function.", fn_name.span))?; @@ -341,13 +330,7 @@ impl<'a> Parser<'a> { } let as_ident = Identifier::from(&s); - let func = match self.scopes.get_fn( - Spanned { - node: as_ident, - span, - }, - self.global_scope, - ) { + let func = match self.scopes.get_fn(as_ident, self.global_scope) { Some(f) => f, None => { if let Some(f) = GLOBAL_FUNCTIONS.get(as_ident.as_str()) { diff --git a/src/scope.rs b/src/scope.rs index b5f3db9..ddf3340 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -230,17 +230,13 @@ impl Scopes { } } - pub fn get_fn<'a>( - &'a self, - name: Spanned, - global_scope: &'a Scope, - ) -> Option { + pub fn get_fn<'a>(&'a self, name: Identifier, global_scope: &'a Scope) -> Option { for scope in self.0.iter().rev() { - if scope.fn_exists(name.node) { - return scope.get_fn(name.node); + if scope.fn_exists(name) { + return scope.get_fn(name); } } - global_scope.get_fn(name.node) + global_scope.get_fn(name) } pub fn fn_exists(&self, name: Identifier, global_scope: &Scope) -> bool { diff --git a/tests/get-function.rs b/tests/get-function.rs index 31583f6..2ec4e9f 100644 --- a/tests/get-function.rs +++ b/tests/get-function.rs @@ -121,3 +121,8 @@ test!( "a {\n color: call(call(get-function(get-function), darken), red, 10%);\n}\n", "a {\n color: #cc0000;\n}\n" ); +test!( + get_function_of_module, + "@use 'sass:math';\na {\n color: call(get-function(cos, $module: math), 2);\n}\n", + "a {\n color: -0.4161468365;\n}\n" +); From cfd2e00ebb9f0f76642df5fcccee25a74edd5f7f Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 30 Jul 2020 18:35:34 -0400 Subject: [PATCH 37/59] respect `$module` argument to `mixin-exists` --- src/builtin/functions/meta.rs | 42 ++++++++++++++++++++++++++--------- src/builtin/modules/mod.rs | 4 ++++ src/scope.rs | 2 +- tests/meta-module.rs | 12 ++++++++++ 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/builtin/functions/meta.rs b/src/builtin/functions/meta.rs index 858f829..917ee35 100644 --- a/src/builtin/functions/meta.rs +++ b/src/builtin/functions/meta.rs @@ -110,19 +110,39 @@ pub(crate) fn global_variable_exists( } } -// todo: should check for module arg (as well as several others) pub(crate) fn mixin_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; - match args.get_err(0, "name")? { - Value::String(s, _) => Ok(Value::bool( - parser.scopes.mixin_exists(s.into(), parser.global_scope), - )), - v => Err(( - format!("$name: {} is not a string.", v.inspect(args.span())?), - args.span(), - ) - .into()), - } + let name: Identifier = match args.get_err(0, "name")? { + Value::String(s, _) => s.into(), + v => { + return Err(( + format!("$name: {} is not a string.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + let module = match args.default_arg(1, "module", Value::Null)? { + Value::String(s, _) => Some(s), + Value::Null => None, + v => { + return Err(( + format!("$module: {} is not a string.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + Ok(Value::bool(if let Some(module_name) = module { + parser + .modules + .get(module_name.into(), args.span())? + .mixin_exists(name) + } else { + parser.scopes.mixin_exists(name, parser.global_scope) + })) } pub(crate) fn function_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index 30f153e..21e6d6c 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -65,6 +65,10 @@ impl Module { self.0.functions.get(&name).cloned() } + pub fn mixin_exists(&self, name: Identifier) -> bool { + self.0.mixin_exists(name) + } + pub fn insert_builtin( &mut self, name: &'static str, diff --git a/src/scope.rs b/src/scope.rs index ddf3340..7583151 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -55,7 +55,7 @@ impl Scope { self.mixins.insert(s.into(), v) } - fn mixin_exists(&self, name: Identifier) -> bool { + pub fn mixin_exists(&self, name: Identifier) -> bool { self.mixins.contains_key(&name) } diff --git a/tests/meta-module.rs b/tests/meta-module.rs index da5b626..5e3310b 100644 --- a/tests/meta-module.rs +++ b/tests/meta-module.rs @@ -1,5 +1,7 @@ #![cfg(test)] +use std::io::Write; + #[macro_use] mod macros; @@ -13,3 +15,13 @@ test!( "@use 'sass:meta';\n@use 'sass:math';\na {\n color: inspect(meta.module-variables(math));\n}\n", "a {\n color: (\"e\": 2.7182818285, \"pi\": 3.1415926536);\n}\n" ); + +#[test] +fn mixin_exists_module() { + let input = "@use \"mixin_exists_module\" as module;\na {\n color: mixin-exists(foo, $module: module);\n}"; + tempfile!("mixin_exists_module.scss", "@mixin foo {}"); + assert_eq!( + "a {\n color: true;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) + ); +} From a8046b0d831821b908006d54ad9d3962fde07d50 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 30 Jul 2020 18:52:16 -0400 Subject: [PATCH 38/59] respect `$module` argument to `global-variable-exists` --- src/builtin/functions/meta.rs | 42 +++++++++++++++++++++++++++-------- src/builtin/modules/mod.rs | 4 ++++ tests/meta-module.rs | 5 +++++ 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/builtin/functions/meta.rs b/src/builtin/functions/meta.rs index 917ee35..5ae44a2 100644 --- a/src/builtin/functions/meta.rs +++ b/src/builtin/functions/meta.rs @@ -99,15 +99,39 @@ pub(crate) fn global_variable_exists( mut args: CallArgs, parser: &mut Parser<'_>, ) -> SassResult { - args.max_args(1)?; - match args.get_err(0, "name")? { - Value::String(s, _) => Ok(Value::bool(parser.global_scope.var_exists(s.into()))), - v => Err(( - format!("$name: {} is not a string.", v.inspect(args.span())?), - args.span(), - ) - .into()), - } + args.max_args(2)?; + + let name: Identifier = match args.get_err(0, "name")? { + Value::String(s, _) => s.into(), + v => { + return Err(( + format!("$name: {} is not a string.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + let module = match args.default_arg(1, "module", Value::Null)? { + Value::String(s, _) => Some(s), + Value::Null => None, + v => { + return Err(( + format!("$module: {} is not a string.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + Ok(Value::bool(if let Some(module_name) = module { + parser + .modules + .get(module_name.into(), args.span())? + .var_exists(name) + } else { + parser.global_scope.var_exists(name) + })) } pub(crate) fn mixin_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index 21e6d6c..7ec2240 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -65,6 +65,10 @@ impl Module { self.0.functions.get(&name).cloned() } + pub fn var_exists(&self, name: Identifier) -> bool { + self.0.var_exists(name) + } + pub fn mixin_exists(&self, name: Identifier) -> bool { self.0.mixin_exists(name) } diff --git a/tests/meta-module.rs b/tests/meta-module.rs index 5e3310b..1b3eca6 100644 --- a/tests/meta-module.rs +++ b/tests/meta-module.rs @@ -15,6 +15,11 @@ test!( "@use 'sass:meta';\n@use 'sass:math';\na {\n color: inspect(meta.module-variables(math));\n}\n", "a {\n color: (\"e\": 2.7182818285, \"pi\": 3.1415926536);\n}\n" ); +test!( + global_var_exists_module, + "@use 'sass:math';\na {\n color: global-variable-exists(pi, $module: math);\n}\n", + "a {\n color: true;\n}\n" +); #[test] fn mixin_exists_module() { From 8bd14e0e8642738e02733b31b6587d4f2e1ed1b6 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Fri, 31 Jul 2020 14:05:00 -0400 Subject: [PATCH 39/59] implement builtin function `math.hypot` --- src/builtin/modules/math.rs | 70 ++++++++++++++++++++++++++++++++++++- src/value/number/mod.rs | 7 ++++ tests/math-module.rs | 49 ++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index 07d64af..d9f9b11 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -102,7 +102,74 @@ fn clamp(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { } fn hypot(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { - todo!() + args.min_args(1)?; + + let span = args.span(); + + let mut numbers = args.get_variadic()?.into_iter().map(|v| -> SassResult<_> { + match v.node { + Value::Dimension(n, u, ..) => Ok((n, u)), + v => Err((format!("{} is not a number.", v.inspect(span)?), span).into()), + } + }); + + let first: (Number, Unit) = match numbers.next().unwrap()? { + (Some(n), u) => (n.clone() * n, u), + (None, u) => return Ok(Value::Dimension(None, u, true)), + }; + + let rest = numbers + .enumerate() + .map(|(idx, val)| -> SassResult> { + let (number, unit) = val?; + if first.1 == Unit::None { + if unit == Unit::None { + Ok(number.map(|n| n.clone() * n)) + } else { + Err(( + format!( + "Argument 1 is unitless but argument {} has unit {}. \ + Arguments must all have units or all be unitless.", + idx + 2, + unit + ), + span, + ) + .into()) + } + } else if unit == Unit::None { + Err(( + format!( + "Argument 1 has unit {} but argument {} is unitless. \ + Arguments must all have units or all be unitless.", + first.1, + idx + 2, + ), + span, + ) + .into()) + } else if first.1.comparable(&unit) { + Ok(number + .map(|n| n.convert(&unit, &first.1)) + .map(|n| n.clone() * n)) + } else { + Err(( + format!("Incompatible units {} and {}.", first.1, unit), + span, + ) + .into()) + } + }) + .collect::>>>()?; + + let rest = match rest { + Some(v) => v, + None => return Ok(Value::Dimension(None, first.1, true)), + }; + + let sum = first.0 + rest.into_iter().fold(Number::zero(), |a, b| a + b); + + Ok(Value::Dimension(sum.sqrt(), first.1, true)) } fn log(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { @@ -421,6 +488,7 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("atan", atan); f.insert_builtin("log", log); f.insert_builtin("pow", pow); + f.insert_builtin("hypot", hypot); #[cfg(feature = "random")] f.insert_builtin("random", random); diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index a95a81a..cff80a5 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -12,6 +12,8 @@ use num_traits::{ CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Num, One, Signed, ToPrimitive, Zero, }; +use crate::unit::{Unit, UNIT_CONVERSION_TABLE}; + use integer::Integer; mod integer; @@ -134,6 +136,11 @@ impl Number { self.as_float()?.powf(exponent.as_float()?), )?))) } + + /// Invariants: `from.comparable(&to)` must be true + pub fn convert(self, from: &Unit, to: &Unit) -> Self { + self * UNIT_CONVERSION_TABLE[to.to_string().as_str()][from.to_string().as_str()].clone() + } } macro_rules! trig_fn( diff --git a/tests/math-module.rs b/tests/math-module.rs index 2915c28..f1addb4 100644 --- a/tests/math-module.rs +++ b/tests/math-module.rs @@ -446,3 +446,52 @@ test!( "@use 'sass:math';\na {\n color: math.pow(2, 0);\n}\n", "a {\n color: 1;\n}\n" ); +test!( + hypot_all_same_unit, + "@use 'sass:math';\na {\n color: math.hypot(1px, 2px, 3px, 4px, 5px);\n}\n", + "a {\n color: 7.4161984871px;\n}\n" +); +test!( + hypot_negative, + "@use 'sass:math';\na {\n color: math.hypot(1px, 2px, 3px, 4px, 5px, -20px);\n}\n", + "a {\n color: 21.3307290077px;\n}\n" +); +test!( + hypot_all_different_but_comparable_unit, + "@use 'sass:math';\na {\n color: math.hypot(1in, 2cm, 3mm, 4pt, 5pc);\n}\n", + "a {\n color: 1.5269191636in;\n}\n" +); +test!( + hypot_all_no_unit, + "@use 'sass:math';\na {\n color: math.hypot(1, 2, 3);\n}\n", + "a {\n color: 3.7416573868;\n}\n" +); +test!( + hypot_nan_has_comparable_unit, + "@use 'sass:math';\na {\n color: math.hypot(1deg, 2deg, math.acos(2));\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +error!( + hypot_no_args, + "@use 'sass:math';\na {\n color: math.hypot();\n}\n", + "Error: At least one argument must be passed." +); +error!( + hypot_first_has_no_unit_third_has_unit, + "@use 'sass:math';\na {\n color: math.hypot(1, 2, 3px);\n}\n", + "Error: Argument 1 is unitless but argument 3 has unit px. Arguments must all have units or all be unitless." +); +error!( + hypot_non_numeric_argument, + "@use 'sass:math';\na {\n color: math.hypot(1, red, 3);\n}\n", "Error: red is not a number." +); +error!( + hypot_units_not_comparable, + "@use 'sass:math';\na {\n color: math.hypot(1px, 2in, 3rem);\n}\n", + "Error: Incompatible units px and rem." +); +error!( + hypot_nan_has_no_unit_but_first_has_unit, + "@use 'sass:math';\na {\n color: math.hypot(1deg, 2deg, (0 / 0));\n}\n", + "Error: Argument 1 has unit deg but argument 3 is unitless. Arguments must all have units or all be unitless." +); From dbe7f2d50b13f4478d4d827b2b624e1c25e9b712 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sat, 1 Aug 2020 14:33:22 -0400 Subject: [PATCH 40/59] use `Unit` rather than string as key to unit conversion map --- src/parse/value/eval.rs | 31 +---- src/unit/conversion.rs | 249 ++++++++++++++++++++-------------------- src/value/mod.rs | 19 +-- src/value/number/mod.rs | 2 +- 4 files changed, 133 insertions(+), 168 deletions(-) diff --git a/src/parse/value/eval.rs b/src/parse/value/eval.rs index 20ccfbd..acb9ef3 100644 --- a/src/parse/value/eval.rs +++ b/src/parse/value/eval.rs @@ -9,7 +9,7 @@ use crate::{ args::CallArgs, common::{Op, QuoteKind}, error::SassResult, - unit::{Unit, UNIT_CONVERSION_TABLE}, + unit::Unit, value::{SassFunction, Value}, }; @@ -228,16 +228,7 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { } else if unit2 == Unit::None { Value::Dimension(Some(num + num2), unit, true) } else { - Value::Dimension( - Some( - num + num2 - * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] - [unit2.to_string().as_str()] - .clone(), - ), - unit, - true, - ) + Value::Dimension(Some(num + num2.convert(&unit2, &unit)), unit, true) } } Value::String(s, q) => Value::String(format!("{}{}{}", num, unit, s), q), @@ -341,16 +332,7 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { } else if unit2 == Unit::None { Value::Dimension(Some(num - num2), unit, true) } else { - Value::Dimension( - Some( - num - num2 - * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] - [unit2.to_string().as_str()] - .clone(), - ), - unit, - true, - ) + Value::Dimension(Some(num - num2.convert(&unit2, &unit)), unit, true) } } Value::List(..) @@ -529,12 +511,7 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { // `unit(1in / 1px)` => `""` } else if unit.comparable(&unit2) { Value::Dimension( - Some( - num / (num2 - * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] - [unit2.to_string().as_str()] - .clone()), - ), + Some(num / num2.convert(&unit2, &unit)), Unit::None, true, ) diff --git a/src/unit/conversion.rs b/src/unit/conversion.rs index b848d19..8862d14 100644 --- a/src/unit/conversion.rs +++ b/src/unit/conversion.rs @@ -7,152 +7,151 @@ use std::{collections::HashMap, f64::consts::PI}; use num_traits::One; use once_cell::sync::Lazy; -use crate::value::Number; +use crate::{unit::Unit, value::Number}; -pub(crate) static UNIT_CONVERSION_TABLE: Lazy< - HashMap<&'static str, HashMap<&'static str, Number>>, -> = Lazy::new(|| { - let mut from_in = HashMap::new(); - from_in.insert("in", Number::one()); - from_in.insert("cm", Number::one() / Number::from(2.54)); - from_in.insert("pc", Number::small_ratio(1, 6)); - from_in.insert("mm", Number::one() / Number::from(25.4)); - from_in.insert("q", Number::one() / Number::from(101.6)); - from_in.insert("pt", Number::small_ratio(1, 72)); - from_in.insert("px", Number::small_ratio(1, 96)); +pub(crate) static UNIT_CONVERSION_TABLE: Lazy>> = + Lazy::new(|| { + let mut from_in = HashMap::new(); + from_in.insert(Unit::In, Number::one()); + from_in.insert(Unit::Cm, Number::one() / Number::from(2.54)); + from_in.insert(Unit::Pc, Number::small_ratio(1, 6)); + from_in.insert(Unit::Mm, Number::one() / Number::from(25.4)); + from_in.insert(Unit::Q, Number::one() / Number::from(101.6)); + from_in.insert(Unit::Pt, Number::small_ratio(1, 72)); + from_in.insert(Unit::Px, Number::small_ratio(1, 96)); - let mut from_cm = HashMap::new(); - from_cm.insert("in", Number::from(2.54)); - from_cm.insert("cm", Number::one()); - from_cm.insert("pc", Number::from(2.54) / Number::from(6)); - from_cm.insert("mm", Number::small_ratio(1, 10)); - from_cm.insert("q", Number::small_ratio(1, 40)); - from_cm.insert("pt", Number::from(2.54) / Number::from(72)); - from_cm.insert("px", Number::from(2.54) / Number::from(96)); + let mut from_cm = HashMap::new(); + from_cm.insert(Unit::In, Number::from(2.54)); + from_cm.insert(Unit::Cm, Number::one()); + from_cm.insert(Unit::Pc, Number::from(2.54) / Number::from(6)); + from_cm.insert(Unit::Mm, Number::small_ratio(1, 10)); + from_cm.insert(Unit::Q, Number::small_ratio(1, 40)); + from_cm.insert(Unit::Pt, Number::from(2.54) / Number::from(72)); + from_cm.insert(Unit::Px, Number::from(2.54) / Number::from(96)); - let mut from_pc = HashMap::new(); - from_pc.insert("in", Number::from(6)); - from_pc.insert("cm", Number::from(6) / Number::from(2.54)); - from_pc.insert("pc", Number::one()); - from_pc.insert("mm", Number::from(6) / Number::from(25.4)); - from_pc.insert("q", Number::from(6) / Number::from(101.6)); - from_pc.insert("pt", Number::small_ratio(1, 12)); - from_pc.insert("px", Number::small_ratio(1, 16)); + let mut from_pc = HashMap::new(); + from_pc.insert(Unit::In, Number::from(6)); + from_pc.insert(Unit::Cm, Number::from(6) / Number::from(2.54)); + from_pc.insert(Unit::Pc, Number::one()); + from_pc.insert(Unit::Mm, Number::from(6) / Number::from(25.4)); + from_pc.insert(Unit::Q, Number::from(6) / Number::from(101.6)); + from_pc.insert(Unit::Pt, Number::small_ratio(1, 12)); + from_pc.insert(Unit::Px, Number::small_ratio(1, 16)); - let mut from_mm = HashMap::new(); - from_mm.insert("in", Number::from(25.4)); - from_mm.insert("cm", Number::from(10)); - from_mm.insert("pc", Number::from(25.4) / Number::from(6)); - from_mm.insert("mm", Number::one()); - from_mm.insert("q", Number::small_ratio(1, 4)); - from_mm.insert("pt", Number::from(25.4) / Number::from(72)); - from_mm.insert("px", Number::from(25.4) / Number::from(96)); + let mut from_mm = HashMap::new(); + from_mm.insert(Unit::In, Number::from(25.4)); + from_mm.insert(Unit::Cm, Number::from(10)); + from_mm.insert(Unit::Pc, Number::from(25.4) / Number::from(6)); + from_mm.insert(Unit::Mm, Number::one()); + from_mm.insert(Unit::Q, Number::small_ratio(1, 4)); + from_mm.insert(Unit::Pt, Number::from(25.4) / Number::from(72)); + from_mm.insert(Unit::Px, Number::from(25.4) / Number::from(96)); - let mut from_q = HashMap::new(); - from_q.insert("in", Number::from(101.6)); - from_q.insert("cm", Number::from(40)); - from_q.insert("pc", Number::from(101.6) / Number::from(6)); - from_q.insert("mm", Number::from(4)); - from_q.insert("q", Number::one()); - from_q.insert("pt", Number::from(101.6) / Number::from(72)); - from_q.insert("px", Number::from(101.6) / Number::from(96)); + let mut from_q = HashMap::new(); + from_q.insert(Unit::In, Number::from(101.6)); + from_q.insert(Unit::Cm, Number::from(40)); + from_q.insert(Unit::Pc, Number::from(101.6) / Number::from(6)); + from_q.insert(Unit::Mm, Number::from(4)); + from_q.insert(Unit::Q, Number::one()); + from_q.insert(Unit::Pt, Number::from(101.6) / Number::from(72)); + from_q.insert(Unit::Px, Number::from(101.6) / Number::from(96)); - let mut from_pt = HashMap::new(); - from_pt.insert("in", Number::from(72)); - from_pt.insert("cm", Number::from(72) / Number::from(2.54)); - from_pt.insert("pc", Number::from(12)); - from_pt.insert("mm", Number::from(72) / Number::from(25.4)); - from_pt.insert("q", Number::from(72) / Number::from(101.6)); - from_pt.insert("pt", Number::one()); - from_pt.insert("px", Number::small_ratio(3, 4)); + let mut from_pt = HashMap::new(); + from_pt.insert(Unit::In, Number::from(72)); + from_pt.insert(Unit::Cm, Number::from(72) / Number::from(2.54)); + from_pt.insert(Unit::Pc, Number::from(12)); + from_pt.insert(Unit::Mm, Number::from(72) / Number::from(25.4)); + from_pt.insert(Unit::Q, Number::from(72) / Number::from(101.6)); + from_pt.insert(Unit::Pt, Number::one()); + from_pt.insert(Unit::Px, Number::small_ratio(3, 4)); - let mut from_px = HashMap::new(); - from_px.insert("in", Number::from(96)); - from_px.insert("cm", Number::from(96) / Number::from(2.54)); - from_px.insert("pc", Number::from(16)); - from_px.insert("mm", Number::from(96) / Number::from(25.4)); - from_px.insert("q", Number::from(96) / Number::from(101.6)); - from_px.insert("pt", Number::small_ratio(4, 3)); - from_px.insert("px", Number::one()); + let mut from_px = HashMap::new(); + from_px.insert(Unit::In, Number::from(96)); + from_px.insert(Unit::Cm, Number::from(96) / Number::from(2.54)); + from_px.insert(Unit::Pc, Number::from(16)); + from_px.insert(Unit::Mm, Number::from(96) / Number::from(25.4)); + from_px.insert(Unit::Q, Number::from(96) / Number::from(101.6)); + from_px.insert(Unit::Pt, Number::small_ratio(4, 3)); + from_px.insert(Unit::Px, Number::one()); - let mut from_deg = HashMap::new(); - from_deg.insert("deg", Number::one()); - from_deg.insert("grad", Number::small_ratio(9, 10)); - from_deg.insert("rad", Number::from(180) / Number::from(PI)); - from_deg.insert("turn", Number::from(360)); + let mut from_deg = HashMap::new(); + from_deg.insert(Unit::Deg, Number::one()); + from_deg.insert(Unit::Grad, Number::small_ratio(9, 10)); + from_deg.insert(Unit::Rad, Number::from(180) / Number::from(PI)); + from_deg.insert(Unit::Turn, Number::from(360)); - let mut from_grad = HashMap::new(); - from_grad.insert("deg", Number::small_ratio(10, 9)); - from_grad.insert("grad", Number::one()); - from_grad.insert("rad", Number::from(200) / Number::from(PI)); - from_grad.insert("turn", Number::from(400)); + let mut from_grad = HashMap::new(); + from_grad.insert(Unit::Deg, Number::small_ratio(10, 9)); + from_grad.insert(Unit::Grad, Number::one()); + from_grad.insert(Unit::Rad, Number::from(200) / Number::from(PI)); + from_grad.insert(Unit::Turn, Number::from(400)); - let mut from_rad = HashMap::new(); - from_rad.insert("deg", Number::from(PI) / Number::from(180)); - from_rad.insert("grad", Number::from(PI) / Number::from(200)); - from_rad.insert("rad", Number::one()); - from_rad.insert("turn", Number::from(2.0 * PI)); + let mut from_rad = HashMap::new(); + from_rad.insert(Unit::Deg, Number::from(PI) / Number::from(180)); + from_rad.insert(Unit::Grad, Number::from(PI) / Number::from(200)); + from_rad.insert(Unit::Rad, Number::one()); + from_rad.insert(Unit::Turn, Number::from(2.0 * PI)); - let mut from_turn = HashMap::new(); - from_turn.insert("deg", Number::small_ratio(1, 360)); - from_turn.insert("grad", Number::small_ratio(1, 400)); - from_turn.insert("rad", Number::one() / Number::from(2.0 * PI)); - from_turn.insert("turn", Number::one()); + let mut from_turn = HashMap::new(); + from_turn.insert(Unit::Deg, Number::small_ratio(1, 360)); + from_turn.insert(Unit::Grad, Number::small_ratio(1, 400)); + from_turn.insert(Unit::Rad, Number::one() / Number::from(2.0 * PI)); + from_turn.insert(Unit::Turn, Number::one()); - let mut from_s = HashMap::new(); - from_s.insert("s", Number::one()); - from_s.insert("ms", Number::small_ratio(1, 1000)); + let mut from_s = HashMap::new(); + from_s.insert(Unit::S, Number::one()); + from_s.insert(Unit::Ms, Number::small_ratio(1, 1000)); - let mut from_ms = HashMap::new(); - from_ms.insert("s", Number::from(1000)); - from_ms.insert("ms", Number::one()); + let mut from_ms = HashMap::new(); + from_ms.insert(Unit::S, Number::from(1000)); + from_ms.insert(Unit::Ms, Number::one()); - let mut from_hz = HashMap::new(); - from_hz.insert("Hz", Number::one()); - from_hz.insert("kHz", Number::from(1000)); + let mut from_hz = HashMap::new(); + from_hz.insert(Unit::Hz, Number::one()); + from_hz.insert(Unit::Khz, Number::from(1000)); - let mut from_khz = HashMap::new(); - from_khz.insert("Hz", Number::small_ratio(1, 1000)); - from_khz.insert("kHz", Number::one()); + let mut from_khz = HashMap::new(); + from_khz.insert(Unit::Hz, Number::small_ratio(1, 1000)); + from_khz.insert(Unit::Khz, Number::one()); - let mut from_dpi = HashMap::new(); - from_dpi.insert("dpi", Number::one()); - from_dpi.insert("dpcm", Number::from(2.54)); - from_dpi.insert("dppx", Number::from(96)); + let mut from_dpi = HashMap::new(); + from_dpi.insert(Unit::Dpi, Number::one()); + from_dpi.insert(Unit::Dpcm, Number::from(2.54)); + from_dpi.insert(Unit::Dppx, Number::from(96)); - let mut from_dpcm = HashMap::new(); - from_dpcm.insert("dpi", Number::one() / Number::from(2.54)); - from_dpcm.insert("dpcm", Number::one()); - from_dpcm.insert("dppx", Number::from(96) / Number::from(2.54)); + let mut from_dpcm = HashMap::new(); + from_dpcm.insert(Unit::Dpi, Number::one() / Number::from(2.54)); + from_dpcm.insert(Unit::Dpcm, Number::one()); + from_dpcm.insert(Unit::Dppx, Number::from(96) / Number::from(2.54)); - let mut from_dppx = HashMap::new(); - from_dppx.insert("dpi", Number::small_ratio(1, 96)); - from_dppx.insert("dpcm", Number::from(2.54) / Number::from(96)); - from_dppx.insert("dppx", Number::one()); + let mut from_dppx = HashMap::new(); + from_dppx.insert(Unit::Dpi, Number::small_ratio(1, 96)); + from_dppx.insert(Unit::Dpcm, Number::from(2.54) / Number::from(96)); + from_dppx.insert(Unit::Dppx, Number::one()); - let mut m = HashMap::new(); - m.insert("in", from_in); - m.insert("cm", from_cm); - m.insert("pc", from_pc); - m.insert("mm", from_mm); - m.insert("q", from_q); - m.insert("pt", from_pt); - m.insert("px", from_px); + let mut m = HashMap::new(); + m.insert(Unit::In, from_in); + m.insert(Unit::Cm, from_cm); + m.insert(Unit::Pc, from_pc); + m.insert(Unit::Mm, from_mm); + m.insert(Unit::Q, from_q); + m.insert(Unit::Pt, from_pt); + m.insert(Unit::Px, from_px); - m.insert("deg", from_deg); - m.insert("grad", from_grad); - m.insert("rad", from_rad); - m.insert("turn", from_turn); + m.insert(Unit::Deg, from_deg); + m.insert(Unit::Grad, from_grad); + m.insert(Unit::Rad, from_rad); + m.insert(Unit::Turn, from_turn); - m.insert("s", from_s); - m.insert("ms", from_ms); + m.insert(Unit::S, from_s); + m.insert(Unit::Ms, from_ms); - m.insert("Hz", from_hz); - m.insert("kHz", from_khz); + m.insert(Unit::Hz, from_hz); + m.insert(Unit::Khz, from_khz); - m.insert("dpi", from_dpi); - m.insert("dpcm", from_dpcm); - m.insert("dppx", from_dppx); + m.insert(Unit::Dpi, from_dpi); + m.insert(Unit::Dpcm, from_dpcm); + m.insert(Unit::Dppx, from_dppx); - m -}); + m + }); diff --git a/src/value/mod.rs b/src/value/mod.rs index f88c6c5..d9b13b3 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -10,7 +10,7 @@ use crate::{ error::SassResult, parse::Parser, selector::Selector, - unit::{Unit, UNIT_CONVERSION_TABLE}, + unit::Unit, utils::hex_char_for, {Cow, Token}, }; @@ -58,10 +58,7 @@ impl PartialEq for Value { } else if unit == &Unit::None || unit2 == &Unit::None { false } else { - n == &(n2.clone() - * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] - [unit2.to_string().as_str()] - .clone()) + n == &n2.clone().convert(unit2, unit) } } _ => false, @@ -352,12 +349,7 @@ impl Value { if unit == unit2 || unit == &Unit::None || unit2 == &Unit::None { num.cmp(num2) } else { - num.cmp( - &(num2.clone() - * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] - [unit2.to_string().as_str()] - .clone()), - ) + num.cmp(&num2.clone().convert(unit2, unit)) } } v => { @@ -412,10 +404,7 @@ impl Value { } else if unit == &Unit::None || unit2 == &Unit::None { true } else { - n != &(n2.clone() - * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] - [unit2.to_string().as_str()] - .clone()) + n != &n2.clone().convert(unit2, unit) } } _ => true, diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index cff80a5..c8d552d 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -139,7 +139,7 @@ impl Number { /// Invariants: `from.comparable(&to)` must be true pub fn convert(self, from: &Unit, to: &Unit) -> Self { - self * UNIT_CONVERSION_TABLE[to.to_string().as_str()][from.to_string().as_str()].clone() + self * UNIT_CONVERSION_TABLE[to][from].clone() } } From ed85ad55a25d00defa2f9ea87f2d81cacd0c754e Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Sat, 1 Aug 2020 12:12:21 +0800 Subject: [PATCH 41/59] elide lifetime for Default Options impl --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8d877f8..61cd1e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -148,7 +148,7 @@ pub struct Options<'a> { quiet: bool, } -impl<'a> Default for Options<'a> { +impl Default for Options<'_> { #[inline] fn default() -> Self { Self { From 7fe3abc625b88b8405a8d737a70ea1b51d76ee79 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Sat, 1 Aug 2020 12:12:37 +0800 Subject: [PATCH 42/59] use phf for named rgba map hashing Improves many_named_colors benchmark by ~40%. many_named_colors time: [897.66 us 900.05 us 903.05 us] change: [-41.299% -40.114% -38.800%] (p = 0.00 < 0.05) Performance has improved. Found 6 outliers among 100 measurements (6.00%) 2 (2.00%) high mild 4 (4.00%) high severe --- Cargo.toml | 1 + benches/colors.rs | 15 +- src/color/name.rs | 450 +++++++++++++++++++++++++++++----------------- 3 files changed, 300 insertions(+), 166 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 41e81ad..b16a8e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ codemap = "0.1.3" peekmore = "0.5.2" wasm-bindgen = { version = "0.2.65", optional = true } beef = "0.4.4" +phf = { version = "0.8.0", features = ["macros"] } # criterion is not a dev-dependency because it makes tests take too # long to compile, and you cannot make dev-dependencies optional criterion = { version = "0.3.3", optional = true } diff --git a/benches/colors.rs b/benches/colors.rs index f4e63fc..d96a8c8 100644 --- a/benches/colors.rs +++ b/benches/colors.rs @@ -1,18 +1,23 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use grass::StyleSheet; pub fn many_hsla(c: &mut Criterion) { c.bench_function("many_hsla", |b| { - b.iter(|| StyleSheet::new(black_box(include_str!("many_hsla.scss").to_string()))) + b.iter(|| { + grass::from_string( + black_box(include_str!("many_hsla.scss").to_string()), + &Default::default(), + ) + }) }); } pub fn many_named_colors(c: &mut Criterion) { c.bench_function("many_named_colors", |b| { b.iter(|| { - StyleSheet::new(black_box( - include_str!("many_named_colors.scss").to_string(), - )) + grass::from_string( + black_box(include_str!("many_named_colors.scss").to_string()), + &Default::default(), + ) }) }); } diff --git a/src/color/name.rs b/src/color/name.rs index 2461c33..9a0bf00 100644 --- a/src/color/name.rs +++ b/src/color/name.rs @@ -1,27 +1,12 @@ //! A big dictionary of named colors and their //! corresponding RGBA values -use once_cell::sync::Lazy; -use std::collections::HashMap; - pub(crate) struct NamedColorMap { - name_to_rgba: HashMap<&'static str, [u8; 4]>, - rgba_to_name: HashMap<[u8; 4], &'static str>, + name_to_rgba: phf::Map<&'static str, [u8; 4]>, + rgba_to_name: phf::Map<[u8; 4], &'static str>, } impl NamedColorMap { - pub fn with_capacity(capacity: usize) -> Self { - Self { - name_to_rgba: HashMap::with_capacity(capacity), - rgba_to_name: HashMap::with_capacity(capacity), - } - } - - pub fn insert(&mut self, name: &'static str, rgba: [u8; 4]) { - self.name_to_rgba.insert(name, rgba); - self.rgba_to_name.insert(rgba, name); - } - pub fn get_by_name(&self, name: &str) -> Option<&[u8; 4]> { self.name_to_rgba.get(name) } @@ -31,147 +16,290 @@ impl NamedColorMap { } } -pub(crate) static NAMED_COLORS: Lazy = Lazy::new(|| { - let mut m = NamedColorMap::with_capacity(150); - m.insert("aliceblue", [0xF0, 0xF8, 0xFF, 0xFF]); - m.insert("antiquewhite", [0xFA, 0xEB, 0xD7, 0xFF]); - m.insert("aqua", [0x00, 0xFF, 0xFF, 0xFF]); - m.insert("aquamarine", [0x7F, 0xFF, 0xD4, 0xFF]); - m.insert("azure", [0xF0, 0xFF, 0xFF, 0xFF]); - m.insert("beige", [0xF5, 0xF5, 0xDC, 0xFF]); - m.insert("bisque", [0xFF, 0xE4, 0xC4, 0xFF]); - m.insert("black", [0x00, 0x00, 0x00, 0xFF]); - m.insert("blanchedalmond", [0xFF, 0xEB, 0xCD, 0xFF]); - m.insert("blue", [0x00, 0x00, 0xFF, 0xFF]); - m.insert("blueviolet", [0x8A, 0x2B, 0xE2, 0xFF]); - m.insert("brown", [0xA5, 0x2A, 0x2A, 0xFF]); - m.insert("burlywood", [0xDE, 0xB8, 0x87, 0xFF]); - m.insert("cadetblue", [0x5F, 0x9E, 0xA0, 0xFF]); - m.insert("chartreuse", [0x7F, 0xFF, 0x00, 0xFF]); - m.insert("chocolate", [0xD2, 0x69, 0x1E, 0xFF]); - m.insert("coral", [0xFF, 0x7F, 0x50, 0xFF]); - m.insert("cornflowerblue", [0x64, 0x95, 0xED, 0xFF]); - m.insert("cornsilk", [0xFF, 0xF8, 0xDC, 0xFF]); - m.insert("crimson", [0xDC, 0x14, 0x3C, 0xFF]); - m.insert("darkblue", [0x00, 0x00, 0x8B, 0xFF]); - m.insert("darkcyan", [0x00, 0x8B, 0x8B, 0xFF]); - m.insert("darkgoldenrod", [0xB8, 0x86, 0x0B, 0xFF]); - m.insert("darkgray", [0xA9, 0xA9, 0xA9, 0xFF]); - m.insert("darkgreen", [0x00, 0x64, 0x00, 0xFF]); - m.insert("darkkhaki", [0xBD, 0xB7, 0x6B, 0xFF]); - m.insert("darkmagenta", [0x8B, 0x00, 0x8B, 0xFF]); - m.insert("darkolivegreen", [0x55, 0x6B, 0x2F, 0xFF]); - m.insert("darkorange", [0xFF, 0x8C, 0x00, 0xFF]); - m.insert("darkorchid", [0x99, 0x32, 0xCC, 0xFF]); - m.insert("darkred", [0x8B, 0x00, 0x00, 0xFF]); - m.insert("darksalmon", [0xE9, 0x96, 0x7A, 0xFF]); - m.insert("darkseagreen", [0x8F, 0xBC, 0x8F, 0xFF]); - m.insert("darkslateblue", [0x48, 0x3D, 0x8B, 0xFF]); - m.insert("darkslategray", [0x2F, 0x4F, 0x4F, 0xFF]); - m.insert("darkturquoise", [0x00, 0xCE, 0xD1, 0xFF]); - m.insert("darkviolet", [0x94, 0x00, 0xD3, 0xFF]); - m.insert("deeppink", [0xFF, 0x14, 0x93, 0xFF]); - m.insert("deepskyblue", [0x00, 0xBF, 0xFF, 0xFF]); - m.insert("dimgray", [0x69, 0x69, 0x69, 0xFF]); - m.insert("dodgerblue", [0x1E, 0x90, 0xFF, 0xFF]); - m.insert("firebrick", [0xB2, 0x22, 0x22, 0xFF]); - m.insert("floralwhite", [0xFF, 0xFA, 0xF0, 0xFF]); - m.insert("forestgreen", [0x22, 0x8B, 0x22, 0xFF]); - m.insert("fuchsia", [0xFF, 0x00, 0xFF, 0xFF]); - m.insert("gainsboro", [0xDC, 0xDC, 0xDC, 0xFF]); - m.insert("ghostwhite", [0xF8, 0xF8, 0xFF, 0xFF]); - m.insert("gold", [0xFF, 0xD7, 0x00, 0xFF]); - m.insert("goldenrod", [0xDA, 0xA5, 0x20, 0xFF]); - m.insert("gray", [0x80, 0x80, 0x80, 0xFF]); - m.insert("green", [0x00, 0x80, 0x00, 0xFF]); - m.insert("greenyellow", [0xAD, 0xFF, 0x2F, 0xFF]); - m.insert("honeydew", [0xF0, 0xFF, 0xF0, 0xFF]); - m.insert("hotpink", [0xFF, 0x69, 0xB4, 0xFF]); - m.insert("indianred", [0xCD, 0x5C, 0x5C, 0xFF]); - m.insert("indigo", [0x4B, 0x00, 0x82, 0xFF]); - m.insert("ivory", [0xFF, 0xFF, 0xF0, 0xFF]); - m.insert("khaki", [0xF0, 0xE6, 0x8C, 0xFF]); - m.insert("lavender", [0xE6, 0xE6, 0xFA, 0xFF]); - m.insert("lavenderblush", [0xFF, 0xF0, 0xF5, 0xFF]); - m.insert("lawngreen", [0x7C, 0xFC, 0x00, 0xFF]); - m.insert("lemonchiffon", [0xFF, 0xFA, 0xCD, 0xFF]); - m.insert("lightblue", [0xAD, 0xD8, 0xE6, 0xFF]); - m.insert("lightcoral", [0xF0, 0x80, 0x80, 0xFF]); - m.insert("lightcyan", [0xE0, 0xFF, 0xFF, 0xFF]); - m.insert("lightgoldenrodyellow", [0xFA, 0xFA, 0xD2, 0xFF]); - m.insert("lightgray", [0xD3, 0xD3, 0xD3, 0xFF]); - m.insert("lightgreen", [0x90, 0xEE, 0x90, 0xFF]); - m.insert("lightpink", [0xFF, 0xB6, 0xC1, 0xFF]); - m.insert("lightsalmon", [0xFF, 0xA0, 0x7A, 0xFF]); - m.insert("lightseagreen", [0x20, 0xB2, 0xAA, 0xFF]); - m.insert("lightskyblue", [0x87, 0xCE, 0xFA, 0xFF]); - m.insert("lightslategray", [0x77, 0x88, 0x99, 0xFF]); - m.insert("lightsteelblue", [0xB0, 0xC4, 0xDE, 0xFF]); - m.insert("lightyellow", [0xFF, 0xFF, 0xE0, 0xFF]); - m.insert("lime", [0x00, 0xFF, 0x00, 0xFF]); - m.insert("limegreen", [0x32, 0xCD, 0x32, 0xFF]); - m.insert("linen", [0xFA, 0xF0, 0xE6, 0xFF]); - m.insert("maroon", [0x80, 0x00, 0x00, 0xFF]); - m.insert("mediumaquamarine", [0x66, 0xCD, 0xAA, 0xFF]); - m.insert("mediumblue", [0x00, 0x00, 0xCD, 0xFF]); - m.insert("mediumorchid", [0xBA, 0x55, 0xD3, 0xFF]); - m.insert("mediumpurple", [0x93, 0x70, 0xDB, 0xFF]); - m.insert("mediumseagreen", [0x3C, 0xB3, 0x71, 0xFF]); - m.insert("mediumslateblue", [0x7B, 0x68, 0xEE, 0xFF]); - m.insert("mediumspringgreen", [0x00, 0xFA, 0x9A, 0xFF]); - m.insert("mediumturquoise", [0x48, 0xD1, 0xCC, 0xFF]); - m.insert("mediumvioletred", [0xC7, 0x15, 0x85, 0xFF]); - m.insert("midnightblue", [0x19, 0x19, 0x70, 0xFF]); - m.insert("mintcream", [0xF5, 0xFF, 0xFA, 0xFF]); - m.insert("mistyrose", [0xFF, 0xE4, 0xE1, 0xFF]); - m.insert("moccasin", [0xFF, 0xE4, 0xB5, 0xFF]); - m.insert("navajowhite", [0xFF, 0xDE, 0xAD, 0xFF]); - m.insert("navy", [0x00, 0x00, 0x80, 0xFF]); - m.insert("oldlace", [0xFD, 0xF5, 0xE6, 0xFF]); - m.insert("olive", [0x80, 0x80, 0x00, 0xFF]); - m.insert("olivedrab", [0x6B, 0x8E, 0x23, 0xFF]); - m.insert("orange", [0xFF, 0xA5, 0x00, 0xFF]); - m.insert("orangered", [0xFF, 0x45, 0x00, 0xFF]); - m.insert("orchid", [0xDA, 0x70, 0xD6, 0xFF]); - m.insert("palegoldenrod", [0xEE, 0xE8, 0xAA, 0xFF]); - m.insert("palegreen", [0x98, 0xFB, 0x98, 0xFF]); - m.insert("paleturquoise", [0xAF, 0xEE, 0xEE, 0xFF]); - m.insert("palevioletred", [0xDB, 0x70, 0x93, 0xFF]); - m.insert("papayawhip", [0xFF, 0xEF, 0xD5, 0xFF]); - m.insert("peachpuff", [0xFF, 0xDA, 0xB9, 0xFF]); - m.insert("peru", [0xCD, 0x85, 0x3F, 0xFF]); - m.insert("pink", [0xFF, 0xC0, 0xCB, 0xFF]); - m.insert("plum", [0xDD, 0xA0, 0xDD, 0xFF]); - m.insert("powderblue", [0xB0, 0xE0, 0xE6, 0xFF]); - m.insert("purple", [0x80, 0x00, 0x80, 0xFF]); - m.insert("rebeccapurple", [0x66, 0x33, 0x99, 0xFF]); - m.insert("red", [0xFF, 0x00, 0x00, 0xFF]); - m.insert("rosybrown", [0xBC, 0x8F, 0x8F, 0xFF]); - m.insert("royalblue", [0x41, 0x69, 0xE1, 0xFF]); - m.insert("saddlebrown", [0x8B, 0x45, 0x13, 0xFF]); - m.insert("salmon", [0xFA, 0x80, 0x72, 0xFF]); - m.insert("sandybrown", [0xF4, 0xA4, 0x60, 0xFF]); - m.insert("seagreen", [0x2E, 0x8B, 0x57, 0xFF]); - m.insert("seashell", [0xFF, 0xF5, 0xEE, 0xFF]); - m.insert("sienna", [0xA0, 0x52, 0x2D, 0xFF]); - m.insert("silver", [0xC0, 0xC0, 0xC0, 0xFF]); - m.insert("skyblue", [0x87, 0xCE, 0xEB, 0xFF]); - m.insert("slateblue", [0x6A, 0x5A, 0xCD, 0xFF]); - m.insert("slategray", [0x70, 0x80, 0x90, 0xFF]); - m.insert("snow", [0xFF, 0xFA, 0xFA, 0xFF]); - m.insert("springgreen", [0x00, 0xFF, 0x7F, 0xFF]); - m.insert("steelblue", [0x46, 0x82, 0xB4, 0xFF]); - m.insert("tan", [0xD2, 0xB4, 0x8C, 0xFF]); - m.insert("teal", [0x00, 0x80, 0x80, 0xFF]); - m.insert("thistle", [0xD8, 0xBF, 0xD8, 0xFF]); - m.insert("tomato", [0xFF, 0x63, 0x47, 0xFF]); - m.insert("turquoise", [0x40, 0xE0, 0xD0, 0xFF]); - m.insert("violet", [0xEE, 0x82, 0xEE, 0xFF]); - m.insert("wheat", [0xF5, 0xDE, 0xB3, 0xFF]); - m.insert("white", [0xFF, 0xFF, 0xFF, 0xFF]); - m.insert("whitesmoke", [0xF5, 0xF5, 0xF5, 0xFF]); - m.insert("yellow", [0xFF, 0xFF, 0x00, 0xFF]); - m.insert("yellowgreen", [0x9A, 0xCD, 0x32, 0xFF]); - m.insert("transparent", [0x00, 0x00, 0x00, 0x00]); - m -}); +// Adding one color requires adding to both map due to limitation in phf crate. +pub(crate) static NAMED_COLORS: NamedColorMap = NamedColorMap { + name_to_rgba: phf::phf_map! { + "aliceblue" => [0xF0, 0xF8, 0xFF, 0xFF], + "antiquewhite" => [0xFA, 0xEB, 0xD7, 0xFF], + "aqua" => [0x00, 0xFF, 0xFF, 0xFF], + "aquamarine" => [0x7F, 0xFF, 0xD4, 0xFF], + "azure" => [0xF0, 0xFF, 0xFF, 0xFF], + "beige" => [0xF5, 0xF5, 0xDC, 0xFF], + "bisque" => [0xFF, 0xE4, 0xC4, 0xFF], + "black" => [0x00, 0x00, 0x00, 0xFF], + "blanchedalmond" => [0xFF, 0xEB, 0xCD, 0xFF], + "blue" => [0x00, 0x00, 0xFF, 0xFF], + "blueviolet" => [0x8A, 0x2B, 0xE2, 0xFF], + "brown" => [0xA5, 0x2A, 0x2A, 0xFF], + "burlywood" => [0xDE, 0xB8, 0x87, 0xFF], + "cadetblue" => [0x5F, 0x9E, 0xA0, 0xFF], + "chartreuse" => [0x7F, 0xFF, 0x00, 0xFF], + "chocolate" => [0xD2, 0x69, 0x1E, 0xFF], + "coral" => [0xFF, 0x7F, 0x50, 0xFF], + "cornflowerblue" => [0x64, 0x95, 0xED, 0xFF], + "cornsilk" => [0xFF, 0xF8, 0xDC, 0xFF], + "crimson" => [0xDC, 0x14, 0x3C, 0xFF], + "darkblue" => [0x00, 0x00, 0x8B, 0xFF], + "darkcyan" => [0x00, 0x8B, 0x8B, 0xFF], + "darkgoldenrod" => [0xB8, 0x86, 0x0B, 0xFF], + "darkgray" => [0xA9, 0xA9, 0xA9, 0xFF], + "darkgreen" => [0x00, 0x64, 0x00, 0xFF], + "darkkhaki" => [0xBD, 0xB7, 0x6B, 0xFF], + "darkmagenta" => [0x8B, 0x00, 0x8B, 0xFF], + "darkolivegreen" => [0x55, 0x6B, 0x2F, 0xFF], + "darkorange" => [0xFF, 0x8C, 0x00, 0xFF], + "darkorchid" => [0x99, 0x32, 0xCC, 0xFF], + "darkred" => [0x8B, 0x00, 0x00, 0xFF], + "darksalmon" => [0xE9, 0x96, 0x7A, 0xFF], + "darkseagreen" => [0x8F, 0xBC, 0x8F, 0xFF], + "darkslateblue" => [0x48, 0x3D, 0x8B, 0xFF], + "darkslategray" => [0x2F, 0x4F, 0x4F, 0xFF], + "darkturquoise" => [0x00, 0xCE, 0xD1, 0xFF], + "darkviolet" => [0x94, 0x00, 0xD3, 0xFF], + "deeppink" => [0xFF, 0x14, 0x93, 0xFF], + "deepskyblue" => [0x00, 0xBF, 0xFF, 0xFF], + "dimgray" => [0x69, 0x69, 0x69, 0xFF], + "dodgerblue" => [0x1E, 0x90, 0xFF, 0xFF], + "firebrick" => [0xB2, 0x22, 0x22, 0xFF], + "floralwhite" => [0xFF, 0xFA, 0xF0, 0xFF], + "forestgreen" => [0x22, 0x8B, 0x22, 0xFF], + "fuchsia" => [0xFF, 0x00, 0xFF, 0xFF], + "gainsboro" => [0xDC, 0xDC, 0xDC, 0xFF], + "ghostwhite" => [0xF8, 0xF8, 0xFF, 0xFF], + "gold" => [0xFF, 0xD7, 0x00, 0xFF], + "goldenrod" => [0xDA, 0xA5, 0x20, 0xFF], + "gray" => [0x80, 0x80, 0x80, 0xFF], + "green" => [0x00, 0x80, 0x00, 0xFF], + "greenyellow" => [0xAD, 0xFF, 0x2F, 0xFF], + "honeydew" => [0xF0, 0xFF, 0xF0, 0xFF], + "hotpink" => [0xFF, 0x69, 0xB4, 0xFF], + "indianred" => [0xCD, 0x5C, 0x5C, 0xFF], + "indigo" => [0x4B, 0x00, 0x82, 0xFF], + "ivory" => [0xFF, 0xFF, 0xF0, 0xFF], + "khaki" => [0xF0, 0xE6, 0x8C, 0xFF], + "lavender" => [0xE6, 0xE6, 0xFA, 0xFF], + "lavenderblush" => [0xFF, 0xF0, 0xF5, 0xFF], + "lawngreen" => [0x7C, 0xFC, 0x00, 0xFF], + "lemonchiffon" => [0xFF, 0xFA, 0xCD, 0xFF], + "lightblue" => [0xAD, 0xD8, 0xE6, 0xFF], + "lightcoral" => [0xF0, 0x80, 0x80, 0xFF], + "lightcyan" => [0xE0, 0xFF, 0xFF, 0xFF], + "lightgoldenrodyellow" => [0xFA, 0xFA, 0xD2, 0xFF], + "lightgray" => [0xD3, 0xD3, 0xD3, 0xFF], + "lightgreen" => [0x90, 0xEE, 0x90, 0xFF], + "lightpink" => [0xFF, 0xB6, 0xC1, 0xFF], + "lightsalmon" => [0xFF, 0xA0, 0x7A, 0xFF], + "lightseagreen" => [0x20, 0xB2, 0xAA, 0xFF], + "lightskyblue" => [0x87, 0xCE, 0xFA, 0xFF], + "lightslategray" => [0x77, 0x88, 0x99, 0xFF], + "lightsteelblue" => [0xB0, 0xC4, 0xDE, 0xFF], + "lightyellow" => [0xFF, 0xFF, 0xE0, 0xFF], + "lime" => [0x00, 0xFF, 0x00, 0xFF], + "limegreen" => [0x32, 0xCD, 0x32, 0xFF], + "linen" => [0xFA, 0xF0, 0xE6, 0xFF], + "maroon" => [0x80, 0x00, 0x00, 0xFF], + "mediumaquamarine" => [0x66, 0xCD, 0xAA, 0xFF], + "mediumblue" => [0x00, 0x00, 0xCD, 0xFF], + "mediumorchid" => [0xBA, 0x55, 0xD3, 0xFF], + "mediumpurple" => [0x93, 0x70, 0xDB, 0xFF], + "mediumseagreen" => [0x3C, 0xB3, 0x71, 0xFF], + "mediumslateblue" => [0x7B, 0x68, 0xEE, 0xFF], + "mediumspringgreen" => [0x00, 0xFA, 0x9A, 0xFF], + "mediumturquoise" => [0x48, 0xD1, 0xCC, 0xFF], + "mediumvioletred" => [0xC7, 0x15, 0x85, 0xFF], + "midnightblue" => [0x19, 0x19, 0x70, 0xFF], + "mintcream" => [0xF5, 0xFF, 0xFA, 0xFF], + "mistyrose" => [0xFF, 0xE4, 0xE1, 0xFF], + "moccasin" => [0xFF, 0xE4, 0xB5, 0xFF], + "navajowhite" => [0xFF, 0xDE, 0xAD, 0xFF], + "navy" => [0x00, 0x00, 0x80, 0xFF], + "oldlace" => [0xFD, 0xF5, 0xE6, 0xFF], + "olive" => [0x80, 0x80, 0x00, 0xFF], + "olivedrab" => [0x6B, 0x8E, 0x23, 0xFF], + "orange" => [0xFF, 0xA5, 0x00, 0xFF], + "orangered" => [0xFF, 0x45, 0x00, 0xFF], + "orchid" => [0xDA, 0x70, 0xD6, 0xFF], + "palegoldenrod" => [0xEE, 0xE8, 0xAA, 0xFF], + "palegreen" => [0x98, 0xFB, 0x98, 0xFF], + "paleturquoise" => [0xAF, 0xEE, 0xEE, 0xFF], + "palevioletred" => [0xDB, 0x70, 0x93, 0xFF], + "papayawhip" => [0xFF, 0xEF, 0xD5, 0xFF], + "peachpuff" => [0xFF, 0xDA, 0xB9, 0xFF], + "peru" => [0xCD, 0x85, 0x3F, 0xFF], + "pink" => [0xFF, 0xC0, 0xCB, 0xFF], + "plum" => [0xDD, 0xA0, 0xDD, 0xFF], + "powderblue" => [0xB0, 0xE0, 0xE6, 0xFF], + "purple" => [0x80, 0x00, 0x80, 0xFF], + "rebeccapurple" => [0x66, 0x33, 0x99, 0xFF], + "red" => [0xFF, 0x00, 0x00, 0xFF], + "rosybrown" => [0xBC, 0x8F, 0x8F, 0xFF], + "royalblue" => [0x41, 0x69, 0xE1, 0xFF], + "saddlebrown" => [0x8B, 0x45, 0x13, 0xFF], + "salmon" => [0xFA, 0x80, 0x72, 0xFF], + "sandybrown" => [0xF4, 0xA4, 0x60, 0xFF], + "seagreen" => [0x2E, 0x8B, 0x57, 0xFF], + "seashell" => [0xFF, 0xF5, 0xEE, 0xFF], + "sienna" => [0xA0, 0x52, 0x2D, 0xFF], + "silver" => [0xC0, 0xC0, 0xC0, 0xFF], + "skyblue" => [0x87, 0xCE, 0xEB, 0xFF], + "slateblue" => [0x6A, 0x5A, 0xCD, 0xFF], + "slategray" => [0x70, 0x80, 0x90, 0xFF], + "snow" => [0xFF, 0xFA, 0xFA, 0xFF], + "springgreen" => [0x00, 0xFF, 0x7F, 0xFF], + "steelblue" => [0x46, 0x82, 0xB4, 0xFF], + "tan" => [0xD2, 0xB4, 0x8C, 0xFF], + "teal" => [0x00, 0x80, 0x80, 0xFF], + "thistle" => [0xD8, 0xBF, 0xD8, 0xFF], + "tomato" => [0xFF, 0x63, 0x47, 0xFF], + "turquoise" => [0x40, 0xE0, 0xD0, 0xFF], + "violet" => [0xEE, 0x82, 0xEE, 0xFF], + "wheat" => [0xF5, 0xDE, 0xB3, 0xFF], + "white" => [0xFF, 0xFF, 0xFF, 0xFF], + "whitesmoke" => [0xF5, 0xF5, 0xF5, 0xFF], + "yellow" => [0xFF, 0xFF, 0x00, 0xFF], + "yellowgreen" => [0x9A, 0xCD, 0x32, 0xFF], + "transparent" => [0x00, 0x00, 0x00, 0x00], + }, + rgba_to_name: phf::phf_map! { + [0xF0, 0xF8, 0xFF, 0xFF] => "aliceblue", + [0xFA, 0xEB, 0xD7, 0xFF] => "antiquewhite", + [0x00, 0xFF, 0xFF, 0xFF] => "aqua", + [0x7F, 0xFF, 0xD4, 0xFF] => "aquamarine", + [0xF0, 0xFF, 0xFF, 0xFF] => "azure", + [0xF5, 0xF5, 0xDC, 0xFF] => "beige", + [0xFF, 0xE4, 0xC4, 0xFF] => "bisque", + [0x00, 0x00, 0x00, 0xFF] => "black", + [0xFF, 0xEB, 0xCD, 0xFF] => "blanchedalmond", + [0x00, 0x00, 0xFF, 0xFF] => "blue", + [0x8A, 0x2B, 0xE2, 0xFF] => "blueviolet", + [0xA5, 0x2A, 0x2A, 0xFF] => "brown", + [0xDE, 0xB8, 0x87, 0xFF] => "burlywood", + [0x5F, 0x9E, 0xA0, 0xFF] => "cadetblue", + [0x7F, 0xFF, 0x00, 0xFF] => "chartreuse", + [0xD2, 0x69, 0x1E, 0xFF] => "chocolate", + [0xFF, 0x7F, 0x50, 0xFF] => "coral", + [0x64, 0x95, 0xED, 0xFF] => "cornflowerblue", + [0xFF, 0xF8, 0xDC, 0xFF] => "cornsilk", + [0xDC, 0x14, 0x3C, 0xFF] => "crimson", + [0x00, 0x00, 0x8B, 0xFF] => "darkblue", + [0x00, 0x8B, 0x8B, 0xFF] => "darkcyan", + [0xB8, 0x86, 0x0B, 0xFF] => "darkgoldenrod", + [0xA9, 0xA9, 0xA9, 0xFF] => "darkgray", + [0x00, 0x64, 0x00, 0xFF] => "darkgreen", + [0xBD, 0xB7, 0x6B, 0xFF] => "darkkhaki", + [0x8B, 0x00, 0x8B, 0xFF] => "darkmagenta", + [0x55, 0x6B, 0x2F, 0xFF] => "darkolivegreen", + [0xFF, 0x8C, 0x00, 0xFF] => "darkorange", + [0x99, 0x32, 0xCC, 0xFF] => "darkorchid", + [0x8B, 0x00, 0x00, 0xFF] => "darkred", + [0xE9, 0x96, 0x7A, 0xFF] => "darksalmon", + [0x8F, 0xBC, 0x8F, 0xFF] => "darkseagreen", + [0x48, 0x3D, 0x8B, 0xFF] => "darkslateblue", + [0x2F, 0x4F, 0x4F, 0xFF] => "darkslategray", + [0x00, 0xCE, 0xD1, 0xFF] => "darkturquoise", + [0x94, 0x00, 0xD3, 0xFF] => "darkviolet", + [0xFF, 0x14, 0x93, 0xFF] => "deeppink", + [0x00, 0xBF, 0xFF, 0xFF] => "deepskyblue", + [0x69, 0x69, 0x69, 0xFF] => "dimgray", + [0x1E, 0x90, 0xFF, 0xFF] => "dodgerblue", + [0xB2, 0x22, 0x22, 0xFF] => "firebrick", + [0xFF, 0xFA, 0xF0, 0xFF] => "floralwhite", + [0x22, 0x8B, 0x22, 0xFF] => "forestgreen", + [0xFF, 0x00, 0xFF, 0xFF] => "fuchsia", + [0xDC, 0xDC, 0xDC, 0xFF] => "gainsboro", + [0xF8, 0xF8, 0xFF, 0xFF] => "ghostwhite", + [0xFF, 0xD7, 0x00, 0xFF] => "gold", + [0xDA, 0xA5, 0x20, 0xFF] => "goldenrod", + [0x80, 0x80, 0x80, 0xFF] => "gray", + [0x00, 0x80, 0x00, 0xFF] => "green", + [0xAD, 0xFF, 0x2F, 0xFF] => "greenyellow", + [0xF0, 0xFF, 0xF0, 0xFF] => "honeydew", + [0xFF, 0x69, 0xB4, 0xFF] => "hotpink", + [0xCD, 0x5C, 0x5C, 0xFF] => "indianred", + [0x4B, 0x00, 0x82, 0xFF] => "indigo", + [0xFF, 0xFF, 0xF0, 0xFF] => "ivory", + [0xF0, 0xE6, 0x8C, 0xFF] => "khaki", + [0xE6, 0xE6, 0xFA, 0xFF] => "lavender", + [0xFF, 0xF0, 0xF5, 0xFF] => "lavenderblush", + [0x7C, 0xFC, 0x00, 0xFF] => "lawngreen", + [0xFF, 0xFA, 0xCD, 0xFF] => "lemonchiffon", + [0xAD, 0xD8, 0xE6, 0xFF] => "lightblue", + [0xF0, 0x80, 0x80, 0xFF] => "lightcoral", + [0xE0, 0xFF, 0xFF, 0xFF] => "lightcyan", + [0xFA, 0xFA, 0xD2, 0xFF] => "lightgoldenrodyellow", + [0xD3, 0xD3, 0xD3, 0xFF] => "lightgray", + [0x90, 0xEE, 0x90, 0xFF] => "lightgreen", + [0xFF, 0xB6, 0xC1, 0xFF] => "lightpink", + [0xFF, 0xA0, 0x7A, 0xFF] => "lightsalmon", + [0x20, 0xB2, 0xAA, 0xFF] => "lightseagreen", + [0x87, 0xCE, 0xFA, 0xFF] => "lightskyblue", + [0x77, 0x88, 0x99, 0xFF] => "lightslategray", + [0xB0, 0xC4, 0xDE, 0xFF] => "lightsteelblue", + [0xFF, 0xFF, 0xE0, 0xFF] => "lightyellow", + [0x00, 0xFF, 0x00, 0xFF] => "lime", + [0x32, 0xCD, 0x32, 0xFF] => "limegreen", + [0xFA, 0xF0, 0xE6, 0xFF] => "linen", + [0x80, 0x00, 0x00, 0xFF] => "maroon", + [0x66, 0xCD, 0xAA, 0xFF] => "mediumaquamarine", + [0x00, 0x00, 0xCD, 0xFF] => "mediumblue", + [0xBA, 0x55, 0xD3, 0xFF] => "mediumorchid", + [0x93, 0x70, 0xDB, 0xFF] => "mediumpurple", + [0x3C, 0xB3, 0x71, 0xFF] => "mediumseagreen", + [0x7B, 0x68, 0xEE, 0xFF] => "mediumslateblue", + [0x00, 0xFA, 0x9A, 0xFF] => "mediumspringgreen", + [0x48, 0xD1, 0xCC, 0xFF] => "mediumturquoise", + [0xC7, 0x15, 0x85, 0xFF] => "mediumvioletred", + [0x19, 0x19, 0x70, 0xFF] => "midnightblue", + [0xF5, 0xFF, 0xFA, 0xFF] => "mintcream", + [0xFF, 0xE4, 0xE1, 0xFF] => "mistyrose", + [0xFF, 0xE4, 0xB5, 0xFF] => "moccasin", + [0xFF, 0xDE, 0xAD, 0xFF] => "navajowhite", + [0x00, 0x00, 0x80, 0xFF] => "navy", + [0xFD, 0xF5, 0xE6, 0xFF] => "oldlace", + [0x80, 0x80, 0x00, 0xFF] => "olive", + [0x6B, 0x8E, 0x23, 0xFF] => "olivedrab", + [0xFF, 0xA5, 0x00, 0xFF] => "orange", + [0xFF, 0x45, 0x00, 0xFF] => "orangered", + [0xDA, 0x70, 0xD6, 0xFF] => "orchid", + [0xEE, 0xE8, 0xAA, 0xFF] => "palegoldenrod", + [0x98, 0xFB, 0x98, 0xFF] => "palegreen", + [0xAF, 0xEE, 0xEE, 0xFF] => "paleturquoise", + [0xDB, 0x70, 0x93, 0xFF] => "palevioletred", + [0xFF, 0xEF, 0xD5, 0xFF] => "papayawhip", + [0xFF, 0xDA, 0xB9, 0xFF] => "peachpuff", + [0xCD, 0x85, 0x3F, 0xFF] => "peru", + [0xFF, 0xC0, 0xCB, 0xFF] => "pink", + [0xDD, 0xA0, 0xDD, 0xFF] => "plum", + [0xB0, 0xE0, 0xE6, 0xFF] => "powderblue", + [0x80, 0x00, 0x80, 0xFF] => "purple", + [0x66, 0x33, 0x99, 0xFF] => "rebeccapurple", + [0xFF, 0x00, 0x00, 0xFF] => "red", + [0xBC, 0x8F, 0x8F, 0xFF] => "rosybrown", + [0x41, 0x69, 0xE1, 0xFF] => "royalblue", + [0x8B, 0x45, 0x13, 0xFF] => "saddlebrown", + [0xFA, 0x80, 0x72, 0xFF] => "salmon", + [0xF4, 0xA4, 0x60, 0xFF] => "sandybrown", + [0x2E, 0x8B, 0x57, 0xFF] => "seagreen", + [0xFF, 0xF5, 0xEE, 0xFF] => "seashell", + [0xA0, 0x52, 0x2D, 0xFF] => "sienna", + [0xC0, 0xC0, 0xC0, 0xFF] => "silver", + [0x87, 0xCE, 0xEB, 0xFF] => "skyblue", + [0x6A, 0x5A, 0xCD, 0xFF] => "slateblue", + [0x70, 0x80, 0x90, 0xFF] => "slategray", + [0xFF, 0xFA, 0xFA, 0xFF] => "snow", + [0x00, 0xFF, 0x7F, 0xFF] => "springgreen", + [0x46, 0x82, 0xB4, 0xFF] => "steelblue", + [0xD2, 0xB4, 0x8C, 0xFF] => "tan", + [0x00, 0x80, 0x80, 0xFF] => "teal", + [0xD8, 0xBF, 0xD8, 0xFF] => "thistle", + [0xFF, 0x63, 0x47, 0xFF] => "tomato", + [0x40, 0xE0, 0xD0, 0xFF] => "turquoise", + [0xEE, 0x82, 0xEE, 0xFF] => "violet", + [0xF5, 0xDE, 0xB3, 0xFF] => "wheat", + [0xFF, 0xFF, 0xFF, 0xFF] => "white", + [0xF5, 0xF5, 0xF5, 0xFF] => "whitesmoke", + [0xFF, 0xFF, 0x00, 0xFF] => "yellow", + [0x9A, 0xCD, 0x32, 0xFF] => "yellowgreen", + [0x00, 0x00, 0x00, 0x00] => "transparent", + }, +}; From 00aeacbc7495e3d1a7aac2e979faeb6460eb112c Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Sat, 1 Aug 2020 16:51:07 +0800 Subject: [PATCH 43/59] refactor parser return --- src/parse/value/parse.rs | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index f1483cb..d365957 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -330,27 +330,24 @@ impl<'a> Parser<'a> { } // check for named colors - if let Some(c) = NAMED_COLORS.get_by_name(lower.as_str()) { - return Ok( - IntermediateValue::Value(HigherIntermediateValue::Literal(Value::Color(Box::new( - Color::new(c[0], c[1], c[2], c[3], s), - )))) - .span(span), - ); - } - - // check for keywords - Ok(match s.as_str() { - "true" => IntermediateValue::Value(HigherIntermediateValue::Literal(Value::True)), - "false" => IntermediateValue::Value(HigherIntermediateValue::Literal(Value::False)), - "null" => IntermediateValue::Value(HigherIntermediateValue::Literal(Value::Null)), - "not" => IntermediateValue::Op(Op::Not), - "and" => IntermediateValue::Op(Op::And), - "or" => IntermediateValue::Op(Op::Or), - _ => IntermediateValue::Value(HigherIntermediateValue::Literal(Value::String( - s, - QuoteKind::None, - ))), + Ok(if let Some(c) = NAMED_COLORS.get_by_name(lower.as_str()) { + IntermediateValue::Value(HigherIntermediateValue::Literal(Value::Color(Box::new( + Color::new(c[0], c[1], c[2], c[3], s), + )))) + } else { + // check for keywords + match s.as_str() { + "true" => IntermediateValue::Value(HigherIntermediateValue::Literal(Value::True)), + "false" => IntermediateValue::Value(HigherIntermediateValue::Literal(Value::False)), + "null" => IntermediateValue::Value(HigherIntermediateValue::Literal(Value::Null)), + "not" => IntermediateValue::Op(Op::Not), + "and" => IntermediateValue::Op(Op::And), + "or" => IntermediateValue::Op(Op::Or), + _ => IntermediateValue::Value(HigherIntermediateValue::Literal(Value::String( + s, + QuoteKind::None, + ))), + } } .span(span)) } From d68ffd651057240bc9bdb753ce4ebda2f478b3a5 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Sat, 1 Aug 2020 16:51:21 +0800 Subject: [PATCH 44/59] remove alpha from get_by_name --- src/color/mod.rs | 2 +- src/color/name.rs | 284 +++++++++++++++++++++++----------------------- 2 files changed, 143 insertions(+), 143 deletions(-) diff --git a/src/color/mod.rs b/src/color/mod.rs index 8c3da1a..e795807 100644 --- a/src/color/mod.rs +++ b/src/color/mod.rs @@ -536,7 +536,7 @@ fn repr(red: &Number, green: &Number, blue: &Number, alpha: &Number) -> String { if alpha < &Number::one() { format!("rgba({}, {}, {}, {})", red_u8, green_u8, blue_u8, alpha) - } else if let Some(c) = NAMED_COLORS.get_by_rgba([red_u8, green_u8, blue_u8, 0xFF]) { + } else if let Some(c) = NAMED_COLORS.get_by_rgba([red_u8, green_u8, blue_u8]) { (*c).to_string() } else { format!("#{:0>2x}{:0>2x}{:0>2x}", red_u8, green_u8, blue_u8) diff --git a/src/color/name.rs b/src/color/name.rs index 9a0bf00..a8e4730 100644 --- a/src/color/name.rs +++ b/src/color/name.rs @@ -3,7 +3,7 @@ pub(crate) struct NamedColorMap { name_to_rgba: phf::Map<&'static str, [u8; 4]>, - rgba_to_name: phf::Map<[u8; 4], &'static str>, + rgba_to_name: phf::Map<[u8; 3], &'static str>, } impl NamedColorMap { @@ -11,7 +11,8 @@ impl NamedColorMap { self.name_to_rgba.get(name) } - pub fn get_by_rgba(&self, rgba: [u8; 4]) -> Option<&&str> { + // transparent is not handled + pub fn get_by_rgba(&self, rgba: [u8; 3]) -> Option<&&str> { self.rgba_to_name.get(&rgba) } } @@ -161,145 +162,144 @@ pub(crate) static NAMED_COLORS: NamedColorMap = NamedColorMap { "transparent" => [0x00, 0x00, 0x00, 0x00], }, rgba_to_name: phf::phf_map! { - [0xF0, 0xF8, 0xFF, 0xFF] => "aliceblue", - [0xFA, 0xEB, 0xD7, 0xFF] => "antiquewhite", - [0x00, 0xFF, 0xFF, 0xFF] => "aqua", - [0x7F, 0xFF, 0xD4, 0xFF] => "aquamarine", - [0xF0, 0xFF, 0xFF, 0xFF] => "azure", - [0xF5, 0xF5, 0xDC, 0xFF] => "beige", - [0xFF, 0xE4, 0xC4, 0xFF] => "bisque", - [0x00, 0x00, 0x00, 0xFF] => "black", - [0xFF, 0xEB, 0xCD, 0xFF] => "blanchedalmond", - [0x00, 0x00, 0xFF, 0xFF] => "blue", - [0x8A, 0x2B, 0xE2, 0xFF] => "blueviolet", - [0xA5, 0x2A, 0x2A, 0xFF] => "brown", - [0xDE, 0xB8, 0x87, 0xFF] => "burlywood", - [0x5F, 0x9E, 0xA0, 0xFF] => "cadetblue", - [0x7F, 0xFF, 0x00, 0xFF] => "chartreuse", - [0xD2, 0x69, 0x1E, 0xFF] => "chocolate", - [0xFF, 0x7F, 0x50, 0xFF] => "coral", - [0x64, 0x95, 0xED, 0xFF] => "cornflowerblue", - [0xFF, 0xF8, 0xDC, 0xFF] => "cornsilk", - [0xDC, 0x14, 0x3C, 0xFF] => "crimson", - [0x00, 0x00, 0x8B, 0xFF] => "darkblue", - [0x00, 0x8B, 0x8B, 0xFF] => "darkcyan", - [0xB8, 0x86, 0x0B, 0xFF] => "darkgoldenrod", - [0xA9, 0xA9, 0xA9, 0xFF] => "darkgray", - [0x00, 0x64, 0x00, 0xFF] => "darkgreen", - [0xBD, 0xB7, 0x6B, 0xFF] => "darkkhaki", - [0x8B, 0x00, 0x8B, 0xFF] => "darkmagenta", - [0x55, 0x6B, 0x2F, 0xFF] => "darkolivegreen", - [0xFF, 0x8C, 0x00, 0xFF] => "darkorange", - [0x99, 0x32, 0xCC, 0xFF] => "darkorchid", - [0x8B, 0x00, 0x00, 0xFF] => "darkred", - [0xE9, 0x96, 0x7A, 0xFF] => "darksalmon", - [0x8F, 0xBC, 0x8F, 0xFF] => "darkseagreen", - [0x48, 0x3D, 0x8B, 0xFF] => "darkslateblue", - [0x2F, 0x4F, 0x4F, 0xFF] => "darkslategray", - [0x00, 0xCE, 0xD1, 0xFF] => "darkturquoise", - [0x94, 0x00, 0xD3, 0xFF] => "darkviolet", - [0xFF, 0x14, 0x93, 0xFF] => "deeppink", - [0x00, 0xBF, 0xFF, 0xFF] => "deepskyblue", - [0x69, 0x69, 0x69, 0xFF] => "dimgray", - [0x1E, 0x90, 0xFF, 0xFF] => "dodgerblue", - [0xB2, 0x22, 0x22, 0xFF] => "firebrick", - [0xFF, 0xFA, 0xF0, 0xFF] => "floralwhite", - [0x22, 0x8B, 0x22, 0xFF] => "forestgreen", - [0xFF, 0x00, 0xFF, 0xFF] => "fuchsia", - [0xDC, 0xDC, 0xDC, 0xFF] => "gainsboro", - [0xF8, 0xF8, 0xFF, 0xFF] => "ghostwhite", - [0xFF, 0xD7, 0x00, 0xFF] => "gold", - [0xDA, 0xA5, 0x20, 0xFF] => "goldenrod", - [0x80, 0x80, 0x80, 0xFF] => "gray", - [0x00, 0x80, 0x00, 0xFF] => "green", - [0xAD, 0xFF, 0x2F, 0xFF] => "greenyellow", - [0xF0, 0xFF, 0xF0, 0xFF] => "honeydew", - [0xFF, 0x69, 0xB4, 0xFF] => "hotpink", - [0xCD, 0x5C, 0x5C, 0xFF] => "indianred", - [0x4B, 0x00, 0x82, 0xFF] => "indigo", - [0xFF, 0xFF, 0xF0, 0xFF] => "ivory", - [0xF0, 0xE6, 0x8C, 0xFF] => "khaki", - [0xE6, 0xE6, 0xFA, 0xFF] => "lavender", - [0xFF, 0xF0, 0xF5, 0xFF] => "lavenderblush", - [0x7C, 0xFC, 0x00, 0xFF] => "lawngreen", - [0xFF, 0xFA, 0xCD, 0xFF] => "lemonchiffon", - [0xAD, 0xD8, 0xE6, 0xFF] => "lightblue", - [0xF0, 0x80, 0x80, 0xFF] => "lightcoral", - [0xE0, 0xFF, 0xFF, 0xFF] => "lightcyan", - [0xFA, 0xFA, 0xD2, 0xFF] => "lightgoldenrodyellow", - [0xD3, 0xD3, 0xD3, 0xFF] => "lightgray", - [0x90, 0xEE, 0x90, 0xFF] => "lightgreen", - [0xFF, 0xB6, 0xC1, 0xFF] => "lightpink", - [0xFF, 0xA0, 0x7A, 0xFF] => "lightsalmon", - [0x20, 0xB2, 0xAA, 0xFF] => "lightseagreen", - [0x87, 0xCE, 0xFA, 0xFF] => "lightskyblue", - [0x77, 0x88, 0x99, 0xFF] => "lightslategray", - [0xB0, 0xC4, 0xDE, 0xFF] => "lightsteelblue", - [0xFF, 0xFF, 0xE0, 0xFF] => "lightyellow", - [0x00, 0xFF, 0x00, 0xFF] => "lime", - [0x32, 0xCD, 0x32, 0xFF] => "limegreen", - [0xFA, 0xF0, 0xE6, 0xFF] => "linen", - [0x80, 0x00, 0x00, 0xFF] => "maroon", - [0x66, 0xCD, 0xAA, 0xFF] => "mediumaquamarine", - [0x00, 0x00, 0xCD, 0xFF] => "mediumblue", - [0xBA, 0x55, 0xD3, 0xFF] => "mediumorchid", - [0x93, 0x70, 0xDB, 0xFF] => "mediumpurple", - [0x3C, 0xB3, 0x71, 0xFF] => "mediumseagreen", - [0x7B, 0x68, 0xEE, 0xFF] => "mediumslateblue", - [0x00, 0xFA, 0x9A, 0xFF] => "mediumspringgreen", - [0x48, 0xD1, 0xCC, 0xFF] => "mediumturquoise", - [0xC7, 0x15, 0x85, 0xFF] => "mediumvioletred", - [0x19, 0x19, 0x70, 0xFF] => "midnightblue", - [0xF5, 0xFF, 0xFA, 0xFF] => "mintcream", - [0xFF, 0xE4, 0xE1, 0xFF] => "mistyrose", - [0xFF, 0xE4, 0xB5, 0xFF] => "moccasin", - [0xFF, 0xDE, 0xAD, 0xFF] => "navajowhite", - [0x00, 0x00, 0x80, 0xFF] => "navy", - [0xFD, 0xF5, 0xE6, 0xFF] => "oldlace", - [0x80, 0x80, 0x00, 0xFF] => "olive", - [0x6B, 0x8E, 0x23, 0xFF] => "olivedrab", - [0xFF, 0xA5, 0x00, 0xFF] => "orange", - [0xFF, 0x45, 0x00, 0xFF] => "orangered", - [0xDA, 0x70, 0xD6, 0xFF] => "orchid", - [0xEE, 0xE8, 0xAA, 0xFF] => "palegoldenrod", - [0x98, 0xFB, 0x98, 0xFF] => "palegreen", - [0xAF, 0xEE, 0xEE, 0xFF] => "paleturquoise", - [0xDB, 0x70, 0x93, 0xFF] => "palevioletred", - [0xFF, 0xEF, 0xD5, 0xFF] => "papayawhip", - [0xFF, 0xDA, 0xB9, 0xFF] => "peachpuff", - [0xCD, 0x85, 0x3F, 0xFF] => "peru", - [0xFF, 0xC0, 0xCB, 0xFF] => "pink", - [0xDD, 0xA0, 0xDD, 0xFF] => "plum", - [0xB0, 0xE0, 0xE6, 0xFF] => "powderblue", - [0x80, 0x00, 0x80, 0xFF] => "purple", - [0x66, 0x33, 0x99, 0xFF] => "rebeccapurple", - [0xFF, 0x00, 0x00, 0xFF] => "red", - [0xBC, 0x8F, 0x8F, 0xFF] => "rosybrown", - [0x41, 0x69, 0xE1, 0xFF] => "royalblue", - [0x8B, 0x45, 0x13, 0xFF] => "saddlebrown", - [0xFA, 0x80, 0x72, 0xFF] => "salmon", - [0xF4, 0xA4, 0x60, 0xFF] => "sandybrown", - [0x2E, 0x8B, 0x57, 0xFF] => "seagreen", - [0xFF, 0xF5, 0xEE, 0xFF] => "seashell", - [0xA0, 0x52, 0x2D, 0xFF] => "sienna", - [0xC0, 0xC0, 0xC0, 0xFF] => "silver", - [0x87, 0xCE, 0xEB, 0xFF] => "skyblue", - [0x6A, 0x5A, 0xCD, 0xFF] => "slateblue", - [0x70, 0x80, 0x90, 0xFF] => "slategray", - [0xFF, 0xFA, 0xFA, 0xFF] => "snow", - [0x00, 0xFF, 0x7F, 0xFF] => "springgreen", - [0x46, 0x82, 0xB4, 0xFF] => "steelblue", - [0xD2, 0xB4, 0x8C, 0xFF] => "tan", - [0x00, 0x80, 0x80, 0xFF] => "teal", - [0xD8, 0xBF, 0xD8, 0xFF] => "thistle", - [0xFF, 0x63, 0x47, 0xFF] => "tomato", - [0x40, 0xE0, 0xD0, 0xFF] => "turquoise", - [0xEE, 0x82, 0xEE, 0xFF] => "violet", - [0xF5, 0xDE, 0xB3, 0xFF] => "wheat", - [0xFF, 0xFF, 0xFF, 0xFF] => "white", - [0xF5, 0xF5, 0xF5, 0xFF] => "whitesmoke", - [0xFF, 0xFF, 0x00, 0xFF] => "yellow", - [0x9A, 0xCD, 0x32, 0xFF] => "yellowgreen", - [0x00, 0x00, 0x00, 0x00] => "transparent", + [0xF0, 0xF8, 0xFF] => "aliceblue", + [0xFA, 0xEB, 0xD7] => "antiquewhite", + [0x00, 0xFF, 0xFF] => "aqua", + [0x7F, 0xFF, 0xD4] => "aquamarine", + [0xF0, 0xFF, 0xFF] => "azure", + [0xF5, 0xF5, 0xDC] => "beige", + [0xFF, 0xE4, 0xC4] => "bisque", + [0x00, 0x00, 0x00] => "black", + [0xFF, 0xEB, 0xCD] => "blanchedalmond", + [0x00, 0x00, 0xFF] => "blue", + [0x8A, 0x2B, 0xE2] => "blueviolet", + [0xA5, 0x2A, 0x2A] => "brown", + [0xDE, 0xB8, 0x87] => "burlywood", + [0x5F, 0x9E, 0xA0] => "cadetblue", + [0x7F, 0xFF, 0x00] => "chartreuse", + [0xD2, 0x69, 0x1E] => "chocolate", + [0xFF, 0x7F, 0x50] => "coral", + [0x64, 0x95, 0xED] => "cornflowerblue", + [0xFF, 0xF8, 0xDC] => "cornsilk", + [0xDC, 0x14, 0x3C] => "crimson", + [0x00, 0x00, 0x8B] => "darkblue", + [0x00, 0x8B, 0x8B] => "darkcyan", + [0xB8, 0x86, 0x0B] => "darkgoldenrod", + [0xA9, 0xA9, 0xA9] => "darkgray", + [0x00, 0x64, 0x00] => "darkgreen", + [0xBD, 0xB7, 0x6B] => "darkkhaki", + [0x8B, 0x00, 0x8B] => "darkmagenta", + [0x55, 0x6B, 0x2F] => "darkolivegreen", + [0xFF, 0x8C, 0x00] => "darkorange", + [0x99, 0x32, 0xCC] => "darkorchid", + [0x8B, 0x00, 0x00] => "darkred", + [0xE9, 0x96, 0x7A] => "darksalmon", + [0x8F, 0xBC, 0x8F] => "darkseagreen", + [0x48, 0x3D, 0x8B] => "darkslateblue", + [0x2F, 0x4F, 0x4F] => "darkslategray", + [0x00, 0xCE, 0xD1] => "darkturquoise", + [0x94, 0x00, 0xD3] => "darkviolet", + [0xFF, 0x14, 0x93] => "deeppink", + [0x00, 0xBF, 0xFF] => "deepskyblue", + [0x69, 0x69, 0x69] => "dimgray", + [0x1E, 0x90, 0xFF] => "dodgerblue", + [0xB2, 0x22, 0x22] => "firebrick", + [0xFF, 0xFA, 0xF0] => "floralwhite", + [0x22, 0x8B, 0x22] => "forestgreen", + [0xFF, 0x00, 0xFF] => "fuchsia", + [0xDC, 0xDC, 0xDC] => "gainsboro", + [0xF8, 0xF8, 0xFF] => "ghostwhite", + [0xFF, 0xD7, 0x00] => "gold", + [0xDA, 0xA5, 0x20] => "goldenrod", + [0x80, 0x80, 0x80] => "gray", + [0x00, 0x80, 0x00] => "green", + [0xAD, 0xFF, 0x2F] => "greenyellow", + [0xF0, 0xFF, 0xF0] => "honeydew", + [0xFF, 0x69, 0xB4] => "hotpink", + [0xCD, 0x5C, 0x5C] => "indianred", + [0x4B, 0x00, 0x82] => "indigo", + [0xFF, 0xFF, 0xF0] => "ivory", + [0xF0, 0xE6, 0x8C] => "khaki", + [0xE6, 0xE6, 0xFA] => "lavender", + [0xFF, 0xF0, 0xF5] => "lavenderblush", + [0x7C, 0xFC, 0x00] => "lawngreen", + [0xFF, 0xFA, 0xCD] => "lemonchiffon", + [0xAD, 0xD8, 0xE6] => "lightblue", + [0xF0, 0x80, 0x80] => "lightcoral", + [0xE0, 0xFF, 0xFF] => "lightcyan", + [0xFA, 0xFA, 0xD2] => "lightgoldenrodyellow", + [0xD3, 0xD3, 0xD3] => "lightgray", + [0x90, 0xEE, 0x90] => "lightgreen", + [0xFF, 0xB6, 0xC1] => "lightpink", + [0xFF, 0xA0, 0x7A] => "lightsalmon", + [0x20, 0xB2, 0xAA] => "lightseagreen", + [0x87, 0xCE, 0xFA] => "lightskyblue", + [0x77, 0x88, 0x99] => "lightslategray", + [0xB0, 0xC4, 0xDE] => "lightsteelblue", + [0xFF, 0xFF, 0xE0] => "lightyellow", + [0x00, 0xFF, 0x00] => "lime", + [0x32, 0xCD, 0x32] => "limegreen", + [0xFA, 0xF0, 0xE6] => "linen", + [0x80, 0x00, 0x00] => "maroon", + [0x66, 0xCD, 0xAA] => "mediumaquamarine", + [0x00, 0x00, 0xCD] => "mediumblue", + [0xBA, 0x55, 0xD3] => "mediumorchid", + [0x93, 0x70, 0xDB] => "mediumpurple", + [0x3C, 0xB3, 0x71] => "mediumseagreen", + [0x7B, 0x68, 0xEE] => "mediumslateblue", + [0x00, 0xFA, 0x9A] => "mediumspringgreen", + [0x48, 0xD1, 0xCC] => "mediumturquoise", + [0xC7, 0x15, 0x85] => "mediumvioletred", + [0x19, 0x19, 0x70] => "midnightblue", + [0xF5, 0xFF, 0xFA] => "mintcream", + [0xFF, 0xE4, 0xE1] => "mistyrose", + [0xFF, 0xE4, 0xB5] => "moccasin", + [0xFF, 0xDE, 0xAD] => "navajowhite", + [0x00, 0x00, 0x80] => "navy", + [0xFD, 0xF5, 0xE6] => "oldlace", + [0x80, 0x80, 0x00] => "olive", + [0x6B, 0x8E, 0x23] => "olivedrab", + [0xFF, 0xA5, 0x00] => "orange", + [0xFF, 0x45, 0x00] => "orangered", + [0xDA, 0x70, 0xD6] => "orchid", + [0xEE, 0xE8, 0xAA] => "palegoldenrod", + [0x98, 0xFB, 0x98] => "palegreen", + [0xAF, 0xEE, 0xEE] => "paleturquoise", + [0xDB, 0x70, 0x93] => "palevioletred", + [0xFF, 0xEF, 0xD5] => "papayawhip", + [0xFF, 0xDA, 0xB9] => "peachpuff", + [0xCD, 0x85, 0x3F] => "peru", + [0xFF, 0xC0, 0xCB] => "pink", + [0xDD, 0xA0, 0xDD] => "plum", + [0xB0, 0xE0, 0xE6] => "powderblue", + [0x80, 0x00, 0x80] => "purple", + [0x66, 0x33, 0x99] => "rebeccapurple", + [0xFF, 0x00, 0x00] => "red", + [0xBC, 0x8F, 0x8F] => "rosybrown", + [0x41, 0x69, 0xE1] => "royalblue", + [0x8B, 0x45, 0x13] => "saddlebrown", + [0xFA, 0x80, 0x72] => "salmon", + [0xF4, 0xA4, 0x60] => "sandybrown", + [0x2E, 0x8B, 0x57] => "seagreen", + [0xFF, 0xF5, 0xEE] => "seashell", + [0xA0, 0x52, 0x2D] => "sienna", + [0xC0, 0xC0, 0xC0] => "silver", + [0x87, 0xCE, 0xEB] => "skyblue", + [0x6A, 0x5A, 0xCD] => "slateblue", + [0x70, 0x80, 0x90] => "slategray", + [0xFF, 0xFA, 0xFA] => "snow", + [0x00, 0xFF, 0x7F] => "springgreen", + [0x46, 0x82, 0xB4] => "steelblue", + [0xD2, 0xB4, 0x8C] => "tan", + [0x00, 0x80, 0x80] => "teal", + [0xD8, 0xBF, 0xD8] => "thistle", + [0xFF, 0x63, 0x47] => "tomato", + [0x40, 0xE0, 0xD0] => "turquoise", + [0xEE, 0x82, 0xEE] => "violet", + [0xF5, 0xDE, 0xB3] => "wheat", + [0xFF, 0xFF, 0xFF] => "white", + [0xF5, 0xF5, 0xF5] => "whitesmoke", + [0xFF, 0xFF, 0x00] => "yellow", + [0x9A, 0xCD, 0x32] => "yellowgreen", }, }; From a9e4d5cba5aeb2fad92feffde18ca64dfeb06749 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 2 Aug 2020 00:42:24 -0400 Subject: [PATCH 45/59] implement builtin function `math.atan2` --- src/builtin/modules/math.rs | 121 +++++++++++++++++++++++++++++++++++- src/value/number/mod.rs | 10 +++ tests/math-module.rs | 95 ++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+), 1 deletion(-) diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index d9f9b11..c49a52b 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -464,7 +464,125 @@ fn atan(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { fn atan2(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(2)?; - todo!() + let (y_num, y_unit) = match args.get_err(0, "y")? { + Value::Dimension(n, u, ..) => (n, u), + v => { + return Err(( + format!("$y: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + let (x_num, x_unit) = match args.get_err(1, "x")? { + Value::Dimension(n, u, ..) => (n, u), + v => { + return Err(( + format!("$x: {} is not a number.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + let (x_num, y_num) = if x_unit == Unit::None && y_unit == Unit::None { + let x = match x_num { + Some(n) => n, + None => return Ok(Value::Dimension(None, Unit::Deg, true)), + }; + + let y = match y_num { + Some(n) => n, + None => return Ok(Value::Dimension(None, Unit::Deg, true)), + }; + + (x, y) + } else if y_unit == Unit::None { + return Err(( + format!( + "$y is unitless but $x has unit {}. \ + Arguments must all have units or all be unitless.", + x_unit + ), + args.span(), + ) + .into()); + } else if x_unit == Unit::None { + return Err(( + format!( + "$y has unit {} but $x is unitless. \ + Arguments must all have units or all be unitless.", + y_unit + ), + args.span(), + ) + .into()); + } else if x_unit.comparable(&y_unit) { + let x = match x_num { + Some(n) => n, + None => return Ok(Value::Dimension(None, Unit::Deg, true)), + }; + + let y = match y_num { + Some(n) => n, + None => return Ok(Value::Dimension(None, Unit::Deg, true)), + }; + + (x, y.convert(&y_unit, &x_unit)) + } else { + return Err(( + format!("Incompatible units {} and {}.", y_unit, x_unit), + args.span(), + ) + .into()); + }; + + Ok( + match ( + NumberState::from_number(&x_num), + NumberState::from_number(&y_num), + ) { + (NumberState::Zero, NumberState::FiniteNegative) => { + Value::Dimension(Some(Number::from(-90)), Unit::Deg, true) + } + (NumberState::Zero, NumberState::Zero) | (NumberState::Finite, NumberState::Zero) => { + Value::Dimension(Some(Number::zero()), Unit::Deg, true) + } + (NumberState::Zero, NumberState::Finite) => { + Value::Dimension(Some(Number::from(90)), Unit::Deg, true) + } + (NumberState::Finite, NumberState::Finite) + | (NumberState::FiniteNegative, NumberState::Finite) + | (NumberState::Finite, NumberState::FiniteNegative) + | (NumberState::FiniteNegative, NumberState::FiniteNegative) => Value::Dimension( + y_num + .atan2(x_num) + .map(|n| (n * Number::from(180)) / Number::pi()), + Unit::Deg, + true, + ), + (NumberState::FiniteNegative, NumberState::Zero) => { + Value::Dimension(Some(Number::from(180)), Unit::Deg, true) + } + }, + ) +} + +enum NumberState { + Zero, + Finite, + FiniteNegative, +} + +impl NumberState { + fn from_number(num: &Number) -> Self { + match (num.is_zero(), num.is_positive()) { + (true, _) => NumberState::Zero, + (false, true) => NumberState::Finite, + (false, false) => NumberState::FiniteNegative, + } + } } pub(crate) fn declare(f: &mut Module) { @@ -489,6 +607,7 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("log", log); f.insert_builtin("pow", pow); f.insert_builtin("hypot", hypot); + f.insert_builtin("atan2", atan2); #[cfg(feature = "random")] f.insert_builtin("random", random); diff --git a/src/value/number/mod.rs b/src/value/number/mod.rs index c8d552d..522f915 100644 --- a/src/value/number/mod.rs +++ b/src/value/number/mod.rs @@ -137,6 +137,16 @@ impl Number { )?))) } + pub fn pi() -> Self { + Number::from(std::f64::consts::PI) + } + + pub fn atan2(self, other: Self) -> Option { + Some(Number::Big(Box::new(BigRational::from_float( + self.as_float()?.atan2(other.as_float()?), + )?))) + } + /// Invariants: `from.comparable(&to)` must be true pub fn convert(self, from: &Unit, to: &Unit) -> Self { self * UNIT_CONVERSION_TABLE[to][from].clone() diff --git a/tests/math-module.rs b/tests/math-module.rs index f1addb4..4f83a4d 100644 --- a/tests/math-module.rs +++ b/tests/math-module.rs @@ -495,3 +495,98 @@ error!( "@use 'sass:math';\na {\n color: math.hypot(1deg, 2deg, (0 / 0));\n}\n", "Error: Argument 1 has unit deg but argument 3 is unitless. Arguments must all have units or all be unitless." ); +test!( + atan2_both_positive, + "@use 'sass:math';\na {\n color: math.atan2(3, 4);\n}\n", + "a {\n color: 36.8698976458deg;\n}\n" +); +test!( + atan2_first_negative, + "@use 'sass:math';\na {\n color: math.atan2(-3, 4);\n}\n", + "a {\n color: -36.8698976458deg;\n}\n" +); +test!( + atan2_second_negative, + "@use 'sass:math';\na {\n color: math.atan2(3, -4);\n}\n", + "a {\n color: 143.1301023542deg;\n}\n" +); +test!( + atan2_both_negative, + "@use 'sass:math';\na {\n color: math.atan2(-3, -4);\n}\n", + "a {\n color: -143.1301023542deg;\n}\n" +); +test!( + atan2_first_positive_second_zero, + "@use 'sass:math';\na {\n color: math.atan2(3, 0);\n}\n", + "a {\n color: 90deg;\n}\n" +); +test!( + atan2_first_negative_second_zero, + "@use 'sass:math';\na {\n color: math.atan2(-3, 0);\n}\n", + "a {\n color: -90deg;\n}\n" +); +test!( + atan2_first_zero_second_positive, + "@use 'sass:math';\na {\n color: math.atan2(0, 4);\n}\n", + "a {\n color: 0deg;\n}\n" +); +test!( + atan2_first_zero_second_negative, + "@use 'sass:math';\na {\n color: math.atan2(0, -4);\n}\n", + "a {\n color: 180deg;\n}\n" +); +test!( + atan2_both_zero, + "@use 'sass:math';\na {\n color: math.atan2(0, 0);\n}\n", + "a {\n color: 0deg;\n}\n" +); +test!( + atan2_both_same_unit, + "@use 'sass:math';\na {\n color: math.atan2(3px, 4px);\n}\n", + "a {\n color: 36.8698976458deg;\n}\n" +); +test!( + atan2_both_different_but_comparable_unit, + "@use 'sass:math';\na {\n color: math.atan2(3px, 4in);\n}\n", + "a {\n color: 0.4476141709deg;\n}\n" +); +error!( + atan2_first_unitless_second_unit, + "@use 'sass:math';\na {\n color: math.atan2(3, 4rem);\n}\n", + "Error: $y is unitless but $x has unit rem. Arguments must all have units or all be unitless." +); +error!( + atan2_first_unit_second_unitless, + "@use 'sass:math';\na {\n color: math.atan2(3px, 4);\n}\n", + "Error: $y has unit px but $x is unitless. Arguments must all have units or all be unitless." +); +error!( + atan2_incompatible_units, + "@use 'sass:math';\na {\n color: math.atan2(3px, 4rem);\n}\n", + "Error: Incompatible units px and rem." +); +error!( + atan2_nan_incompatible_units, + "@use 'sass:math';\na {\n color: math.atan2(math.acos(2), 3);\n}\n", + "Error: $y has unit deg but $x is unitless. Arguments must all have units or all be unitless." +); +test!( + atan2_first_nan, + "@use 'sass:math';\na {\n color: math.atan2((0/0), 0);\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +test!( + atan2_second_nan, + "@use 'sass:math';\na {\n color: math.atan2(0, (0/0));\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +test!( + atan2_both_nan, + "@use 'sass:math';\na {\n color: math.atan2((0/0), (0/0));\n}\n", + "a {\n color: NaNdeg;\n}\n" +); +test!( + atan2_nan_with_same_units, + "@use 'sass:math';\na {\n color: math.atan2(math.acos(2), 3deg);\n}\n", + "a {\n color: NaNdeg;\n}\n" +); From 698339b8c7d4221e13e9ab9c4b33aabbbbe04d07 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Sun, 2 Aug 2020 04:20:08 -0400 Subject: [PATCH 46/59] initial implementation of private module members --- src/builtin/functions/meta.rs | 7 +++- src/builtin/modules/mod.rs | 26 ++++++++++++--- src/parse/value/parse.rs | 2 +- tests/imports.rs | 13 ++------ tests/macros.rs | 17 ++++++++++ tests/use.rs | 62 +++++++++++++++++++++++++++++++++++ 6 files changed, 110 insertions(+), 17 deletions(-) diff --git a/src/builtin/functions/meta.rs b/src/builtin/functions/meta.rs index 5ae44a2..1bdc1b9 100644 --- a/src/builtin/functions/meta.rs +++ b/src/builtin/functions/meta.rs @@ -1,5 +1,7 @@ use super::{Builtin, GlobalFunctionMap, GLOBAL_FUNCTIONS}; +use codemap::Spanned; + use crate::{ args::CallArgs, common::{Identifier, QuoteKind}, @@ -220,7 +222,10 @@ pub(crate) fn get_function(mut args: CallArgs, parser: &mut Parser<'_>) -> SassR parser .modules .get(module_name.into(), args.span())? - .get_fn(name) + .get_fn(Spanned { + node: name, + span: args.span(), + })? } else { parser.scopes.get_fn(name, parser.global_scope) } { diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index 7ec2240..aa47e54 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -51,6 +51,14 @@ impl Modules { impl Module { pub fn get_var(&self, name: Spanned) -> SassResult<&Value> { + if name.node.as_str().starts_with('-') { + return Err(( + "Private members can't be accessed from outside their modules.", + name.span, + ) + .into()); + } + match self.0.vars.get(&name.node) { Some(v) => Ok(v), None => Err(("Undefined variable.", name.span).into()), @@ -61,16 +69,24 @@ impl Module { self.0.vars.insert(name.into(), value); } - pub fn get_fn(&self, name: Identifier) -> Option { - self.0.functions.get(&name).cloned() + pub fn get_fn(&self, name: Spanned) -> SassResult> { + if name.node.as_str().starts_with('-') { + return Err(( + "Private members can't be accessed from outside their modules.", + name.span, + ) + .into()); + } + + Ok(self.0.functions.get(&name.node).cloned()) } pub fn var_exists(&self, name: Identifier) -> bool { - self.0.var_exists(name) + !name.as_str().starts_with('-') && self.0.var_exists(name) } pub fn mixin_exists(&self, name: Identifier) -> bool { - self.0.mixin_exists(name) + !name.as_str().starts_with('-') && self.0.mixin_exists(name) } pub fn insert_builtin( @@ -89,6 +105,7 @@ impl Module { self.0 .functions .iter() + .filter(|(key, _)| !key.as_str().starts_with('-')) .map(|(key, value)| { ( Value::String(key.to_string(), QuoteKind::Quoted), @@ -104,6 +121,7 @@ impl Module { self.0 .vars .iter() + .filter(|(key, _)| !key.as_str().starts_with('-')) .map(|(key, value)| { ( Value::String(key.to_string(), QuoteKind::Quoted), diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index b70f66d..d58d37a 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -271,7 +271,7 @@ impl<'a> Parser<'a> { let function = self .modules .get(module.into(), module_span)? - .get_fn(fn_name.node) + .get_fn(fn_name)? .ok_or(("Undefined function.", fn_name.span))?; if !matches!(self.toks.next(), Some(Token { kind: '(', .. })) { diff --git a/tests/imports.rs b/tests/imports.rs index c0888d0..9210585 100644 --- a/tests/imports.rs +++ b/tests/imports.rs @@ -26,17 +26,8 @@ fn import_no_semicolon() { fn import_no_quotes() { let input = "@import import_no_quotes"; tempfile!("import_no_quotes", "$a: red;"); - match grass::from_string(input.to_string(), &grass::Options::default()) { - Ok(..) => panic!("did not fail"), - Err(e) => assert_eq!( - "Error: Expected string.", - e.to_string() - .chars() - .take_while(|c| *c != '\n') - .collect::() - .as_str() - ), - } + + assert_err!("Error: Expected string.", input); } #[test] diff --git a/tests/macros.rs b/tests/macros.rs index da4aa99..9e4d3c2 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -85,3 +85,20 @@ macro_rules! tempfile { write!(f, "{}", $content).unwrap(); }; } + +#[macro_export] +macro_rules! assert_err { + ($err:literal, $input:expr) => { + match grass::from_string($input.to_string(), &grass::Options::default()) { + Ok(..) => panic!("did not fail"), + Err(e) => assert_eq!( + $err, + e.to_string() + .chars() + .take_while(|c| *c != '\n') + .collect::() + .as_str() + ), + } + }; +} diff --git a/tests/use.rs b/tests/use.rs index 5998c09..f5e1b3f 100644 --- a/tests/use.rs +++ b/tests/use.rs @@ -74,6 +74,68 @@ fn use_user_defined_same_directory() { ); } +#[test] +fn private_variable_begins_with_underscore() { + let input = "@use \"private_variable_begins_with_underscore\" as module;\na {\n color: module.$_foo;\n}"; + tempfile!( + "private_variable_begins_with_underscore.scss", + "$_foo: red; a { color: $_foo; }" + ); + + assert_err!( + "Error: Private members can't be accessed from outside their modules.", + input + ); +} + +#[test] +fn private_variable_begins_with_hyphen() { + let input = + "@use \"private_variable_begins_with_hyphen\" as module;\na {\n color: module.$-foo;\n}"; + tempfile!( + "private_variable_begins_with_hyphen.scss", + "$-foo: red; a { color: $-foo; }" + ); + + assert_err!( + "Error: Private members can't be accessed from outside their modules.", + input + ); +} + +#[test] +fn private_function() { + let input = "@use \"private_function\" as module;\na {\n color: module._foo(green);\n}"; + tempfile!( + "private_function.scss", + "@function _foo($a) { @return $a; } a { color: _foo(red); }" + ); + + assert_err!( + "Error: Private members can't be accessed from outside their modules.", + input + ); +} + +#[test] +fn global_variable_exists_private() { + let input = r#" + @use "global_variable_exists_private" as module; + a { + color: global-variable-exists($name: foo, $module: module); + color: global-variable-exists($name: _foo, $module: module); + }"#; + tempfile!( + "global_variable_exists_private.scss", + "$foo: red;\n$_foo: red;\n" + ); + + assert_eq!( + "a {\n color: true;\n color: false;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) + ); +} + #[test] fn use_user_defined_as() { let input = "@use \"use_user_defined_as\" as module;\na {\n color: module.$a;\n}"; From d029fd2001a1566ed2f061873c13ed1abcf2e4af Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 6 Aug 2020 03:46:58 -0400 Subject: [PATCH 47/59] implement module mixins and `meta.load-css` --- src/atrule/mixin.rs | 54 ++++++++++++++++++++++++++++++++++--- src/atrule/mod.rs | 3 +-- src/builtin/modules/meta.rs | 37 ++++++++++++++++++++++--- src/builtin/modules/mod.rs | 21 ++++++++++++++- src/parse/import.rs | 6 ++++- src/parse/mixin.rs | 26 +++++++++++++++--- src/parse/mod.rs | 3 ++- src/scope.rs | 2 +- tests/meta-module.rs | 33 +++++++++++++++++++++++ 9 files changed, 168 insertions(+), 17 deletions(-) diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index a85bcf8..b89d2f7 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -1,21 +1,67 @@ -use crate::{args::FuncArgs, Token}; +use std::fmt; + +use crate::{ + args::{CallArgs, FuncArgs}, + error::SassResult, + parse::{Parser, Stmt}, + Token, +}; + +pub(crate) type BuiltinMixin = fn(CallArgs, &mut Parser<'_>) -> SassResult>; + +#[derive(Clone)] +pub(crate) enum Mixin { + UserDefined(UserDefinedMixin), + Builtin(BuiltinMixin), +} + +impl fmt::Debug for Mixin { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UserDefined(u) => f + .debug_struct("UserDefinedMixin") + .field("args", &u.args) + .field("body", &u.body) + .field("accepts_content_block", &u.accepts_content_block) + .field("declared_at_root", &u.declared_at_root) + .finish(), + Self::Builtin(..) => f.debug_struct("BuiltinMixin").finish(), + } + } +} + +impl Mixin { + pub fn new_user_defined( + args: FuncArgs, + body: Vec, + accepts_content_block: bool, + declared_at_root: bool, + ) -> Self { + Mixin::UserDefined(UserDefinedMixin::new( + args, + body, + accepts_content_block, + declared_at_root, + )) + } +} #[derive(Debug, Clone)] -pub(crate) struct Mixin { +pub(crate) struct UserDefinedMixin { pub args: FuncArgs, pub body: Vec, pub accepts_content_block: bool, pub declared_at_root: bool, } -impl Mixin { +impl UserDefinedMixin { pub fn new( args: FuncArgs, body: Vec, accepts_content_block: bool, declared_at_root: bool, ) -> Self { - Mixin { + Self { args, body, accepts_content_block, diff --git a/src/atrule/mod.rs b/src/atrule/mod.rs index 7c0bda1..80304d0 100644 --- a/src/atrule/mod.rs +++ b/src/atrule/mod.rs @@ -1,6 +1,5 @@ pub(crate) use function::Function; pub(crate) use kind::AtRuleKind; -pub(crate) use mixin::{Content, Mixin}; pub(crate) use supports::SupportsRule; pub(crate) use unknown::UnknownAtRule; @@ -8,6 +7,6 @@ mod function; pub mod keyframes; mod kind; pub mod media; -mod mixin; +pub mod mixin; mod supports; mod unknown; diff --git a/src/builtin/modules/meta.rs b/src/builtin/modules/meta.rs index 1e0062e..bd1274e 100644 --- a/src/builtin/modules/meta.rs +++ b/src/builtin/modules/meta.rs @@ -8,13 +8,42 @@ use crate::{ modules::Module, }, error::SassResult, - parse::Parser, + parse::{Parser, Stmt}, value::Value, }; -fn load_css(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +fn load_css(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult> { args.max_args(2)?; - todo!() + + // todo: https://github.com/sass/dart-sass/issues/1054 + let url = match args.get_err(0, "module")? { + Value::String(s, ..) => s, + v => { + return Err(( + format!("$module: {} is not a string.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + let with = match args.default_arg(1, "with", Value::Null)? { + Value::Map(map) => Some(map), + Value::Null => None, + v => { + return Err(( + format!("$with: {} is not a map.", v.inspect(args.span())?), + args.span(), + ) + .into()) + } + }; + + if let Some(with) = with { + todo!("`$with` to `load-css` not yet implemented") + } else { + parser.parse_single_import(&url, args.span()) + } } fn module_functions(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { @@ -69,4 +98,6 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("module-functions", module_functions); f.insert_builtin("get-function", get_function); f.insert_builtin("call", call); + + f.insert_builtin_mixin("load-css", load_css); } diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index aa47e54..1f1bcf6 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -6,7 +6,7 @@ use codemap::{Span, Spanned}; use crate::{ args::CallArgs, - atrule::Mixin, + atrule::mixin::{BuiltinMixin, Mixin}, builtin::Builtin, common::{Identifier, QuoteKind}, error::SassResult, @@ -65,6 +65,25 @@ impl Module { } } + pub fn get_mixin(&self, name: Spanned) -> SassResult { + if name.node.as_str().starts_with('-') { + return Err(( + "Private members can't be accessed from outside their modules.", + name.span, + ) + .into()); + } + + match self.0.mixins.get(&name.node) { + Some(v) => Ok(v.clone()), + None => Err(("Undefined mixin.", name.span).into()), + } + } + + pub fn insert_builtin_mixin(&mut self, name: &'static str, mixin: BuiltinMixin) { + self.0.mixins.insert(name.into(), Mixin::Builtin(mixin)); + } + pub fn insert_builtin_var(&mut self, name: &'static str, value: Value) { self.0.vars.insert(name.into(), value); } diff --git a/src/parse/import.rs b/src/parse/import.rs index 204787a..ac252af 100644 --- a/src/parse/import.rs +++ b/src/parse/import.rs @@ -77,7 +77,11 @@ impl<'a> Parser<'a> { None } - fn parse_single_import(&mut self, file_name: &str, span: Span) -> SassResult> { + pub(crate) fn parse_single_import( + &mut self, + file_name: &str, + span: Span, + ) -> SassResult> { let path: &Path = file_name.as_ref(); if let Some(name) = self.find_import(path) { diff --git a/src/parse/mixin.rs b/src/parse/mixin.rs index 78841b6..e0c0d63 100644 --- a/src/parse/mixin.rs +++ b/src/parse/mixin.rs @@ -6,7 +6,7 @@ use peekmore::PeekMore; use crate::{ args::{CallArgs, FuncArgs}, - atrule::{Content, Mixin}, + atrule::mixin::{Content, Mixin, UserDefinedMixin}, error::SassResult, scope::Scopes, utils::read_until_closing_curly_brace, @@ -55,7 +55,7 @@ impl<'a> Parser<'a> { // this is blocked on figuring out just how to check for this. presumably we could have a check // not when parsing initially, but rather when `@include`ing to see if an `@content` was found. - let mixin = Mixin::new(args, body, false, self.at_root); + let mixin = Mixin::new_user_defined(args, body, false, self.at_root); if self.at_root { self.global_scope.insert_mixin(name, mixin); @@ -73,6 +73,19 @@ impl<'a> Parser<'a> { self.whitespace_or_comment(); let name = self.parse_identifier()?.map_node(Into::into); + let mixin = if let Some(Token { kind: '.', .. }) = self.toks.peek() { + self.toks.next(); + + let module = name; + let name = self.parse_identifier()?.map_node(Into::into); + + self.modules + .get(module.node, module.span)? + .get_mixin(name)? + } else { + self.scopes.get_mixin(name, self.global_scope)? + }; + self.whitespace_or_comment(); let args = if let Some(Token { kind: '(', .. }) = self.toks.peek() { @@ -125,12 +138,17 @@ impl<'a> Parser<'a> { self.toks.next(); } - let Mixin { + let UserDefinedMixin { body, args: fn_args, declared_at_root, .. - } = self.scopes.get_mixin(name, self.global_scope)?; + } = match mixin { + Mixin::UserDefined(u) => u, + Mixin::Builtin(b) => { + return b(args, self); + } + }; let scope = self.eval_args(fn_args, args)?; diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 3c4fbb1..1d5bab7 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -7,7 +7,8 @@ use crate::{ atrule::{ keyframes::{Keyframes, KeyframesRuleSet}, media::MediaRule, - AtRuleKind, Content, SupportsRule, UnknownAtRule, + mixin::Content, + AtRuleKind, SupportsRule, UnknownAtRule, }, builtin::modules::{ declare_module_color, declare_module_list, declare_module_map, declare_module_math, diff --git a/src/scope.rs b/src/scope.rs index 2cef3d5..82f27f0 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use codemap::Spanned; use crate::{ - atrule::Mixin, + atrule::mixin::Mixin, builtin::{modules::Module, GLOBAL_FUNCTIONS}, common::Identifier, error::SassResult, diff --git a/tests/meta-module.rs b/tests/meta-module.rs index 1b3eca6..e6ee9ba 100644 --- a/tests/meta-module.rs +++ b/tests/meta-module.rs @@ -30,3 +30,36 @@ fn mixin_exists_module() { &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) ); } + +#[test] +fn load_css_simple() { + let input = "@use \"sass:meta\";\na {\n @include meta.load-css(load_css_simple);\n}"; + tempfile!("load_css_simple.scss", "a { color: red; }"); + assert_eq!( + "a a {\n color: red;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) + ); +} + +#[test] +fn load_css_explicit_args() { + let input = "@use \"sass:meta\";\na {\n @include meta.load-css($module: load_css_explicit_args, $with: null);\n}"; + tempfile!("load_css_explicit_args.scss", "a { color: red; }"); + assert_eq!( + "a a {\n color: red;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) + ); +} + +#[test] +fn load_css_non_string_url() { + let input = "@use \"sass:meta\";\na {\n @include meta.load-css(2);\n}"; + tempfile!("load_css_non_string_url.scss", "a { color: red; }"); + assert_err!("Error: $module: 2 is not a string.", input); +} + +#[test] +fn load_css_non_map_with() { + let input = "@use \"sass:meta\";\na {\n @include meta.load-css(foo, 2);\n}"; + assert_err!("Error: $with: 2 is not a map.", input); +} From de9571b3fef2df044b7678071b6ed05255c4f366 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 6 Aug 2020 03:53:18 -0400 Subject: [PATCH 48/59] remove `allow`s from modules --- src/builtin/modules/color.rs | 20 +++++++------------- src/builtin/modules/list.rs | 12 +++--------- src/builtin/modules/map.rs | 12 +++--------- src/builtin/modules/math.rs | 20 ++++++++++---------- src/builtin/modules/meta.rs | 2 +- src/builtin/modules/mod.rs | 2 -- src/builtin/modules/selector.rs | 4 ---- src/builtin/modules/string.rs | 16 +++++----------- 8 files changed, 29 insertions(+), 59 deletions(-) diff --git a/src/builtin/modules/color.rs b/src/builtin/modules/color.rs index 0a10449..f2cd754 100644 --- a/src/builtin/modules/color.rs +++ b/src/builtin/modules/color.rs @@ -1,17 +1,11 @@ -use crate::{ - args::CallArgs, - builtin::{ - color::{ - hsl::{complement, grayscale, hue, invert, lightness, saturation}, - opacity::alpha, - other::{adjust_color, change_color, ie_hex_str, scale_color}, - rgb::{blue, green, mix, red}, - }, - modules::Module, +use crate::builtin::{ + color::{ + hsl::{complement, grayscale, hue, invert, lightness, saturation}, + opacity::alpha, + other::{adjust_color, change_color, ie_hex_str, scale_color}, + rgb::{blue, green, mix, red}, }, - error::SassResult, - parse::Parser, - value::Value, + modules::Module, }; pub(crate) fn declare(f: &mut Module) { diff --git a/src/builtin/modules/list.rs b/src/builtin/modules/list.rs index ddd6611..16c7a6c 100644 --- a/src/builtin/modules/list.rs +++ b/src/builtin/modules/list.rs @@ -1,12 +1,6 @@ -use crate::{ - args::CallArgs, - builtin::{ - list::{append, index, is_bracketed, join, length, list_separator, nth, set_nth, zip}, - modules::Module, - }, - error::SassResult, - parse::Parser, - value::Value, +use crate::builtin::{ + list::{append, index, is_bracketed, join, length, list_separator, nth, set_nth, zip}, + modules::Module, }; pub(crate) fn declare(f: &mut Module) { diff --git a/src/builtin/modules/map.rs b/src/builtin/modules/map.rs index 5a349f5..13446ad 100644 --- a/src/builtin/modules/map.rs +++ b/src/builtin/modules/map.rs @@ -1,12 +1,6 @@ -use crate::{ - args::CallArgs, - builtin::{ - map::{map_get, map_has_key, map_keys, map_merge, map_remove, map_values}, - modules::Module, - }, - error::SassResult, - parse::Parser, - value::Value, +use crate::builtin::{ + map::{map_get, map_has_key, map_keys, map_merge, map_remove, map_values}, + modules::Module, }; pub(crate) fn declare(f: &mut Module) { diff --git a/src/builtin/modules/math.rs b/src/builtin/modules/math.rs index c49a52b..ecc553b 100644 --- a/src/builtin/modules/math.rs +++ b/src/builtin/modules/math.rs @@ -19,7 +19,7 @@ use crate::{ #[cfg(feature = "random")] use crate::builtin::math::random; -fn clamp(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +fn clamp(mut args: CallArgs, _: &mut Parser<'_>) -> SassResult { args.max_args(3)?; let span = args.span(); @@ -101,7 +101,7 @@ fn clamp(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(number) } -fn hypot(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +fn hypot(args: CallArgs, _: &mut Parser<'_>) -> SassResult { args.min_args(1)?; let span = args.span(); @@ -172,7 +172,7 @@ fn hypot(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(Value::Dimension(sum.sqrt(), first.1, true)) } -fn log(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +fn log(mut args: CallArgs, _: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let number = match args.get_err(0, "number")? { @@ -239,7 +239,7 @@ fn log(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { )) } -fn pow(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +fn pow(mut args: CallArgs, _: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let base = match args.get_err(0, "base")? { @@ -289,7 +289,7 @@ fn pow(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { Ok(Value::Dimension(base.pow(exponent), Unit::None, true)) } -fn sqrt(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +fn sqrt(mut args: CallArgs, _: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let number = args.get_err(0, "number")?; @@ -318,7 +318,7 @@ fn sqrt(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { macro_rules! trig_fn { ($name:ident, $name_deg:ident) => { - fn $name(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { + fn $name(mut args: CallArgs, _: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let number = args.get_err(0, "number")?; @@ -357,7 +357,7 @@ trig_fn!(cos, cos_deg); trig_fn!(sin, sin_deg); trig_fn!(tan, tan_deg); -fn acos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +fn acos(mut args: CallArgs, _: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let number = args.get_err(0, "number")?; @@ -394,7 +394,7 @@ fn acos(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { }) } -fn asin(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +fn asin(mut args: CallArgs, _: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let number = args.get_err(0, "number")?; @@ -429,7 +429,7 @@ fn asin(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { }) } -fn atan(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +fn atan(mut args: CallArgs, _: &mut Parser<'_>) -> SassResult { args.max_args(1)?; let number = args.get_err(0, "number")?; @@ -462,7 +462,7 @@ fn atan(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { }) } -fn atan2(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { +fn atan2(mut args: CallArgs, _: &mut Parser<'_>) -> SassResult { args.max_args(2)?; let (y_num, y_unit) = match args.get_err(0, "y")? { Value::Dimension(n, u, ..) => (n, u), diff --git a/src/builtin/modules/meta.rs b/src/builtin/modules/meta.rs index bd1274e..e808be7 100644 --- a/src/builtin/modules/meta.rs +++ b/src/builtin/modules/meta.rs @@ -39,7 +39,7 @@ fn load_css(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult } }; - if let Some(with) = with { + if let Some(..) = with { todo!("`$with` to `load-css` not yet implemented") } else { parser.parse_single_import(&url, args.span()) diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index 1f1bcf6..555aeff 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -1,5 +1,3 @@ -#![allow(unused_imports, unused_variables, dead_code, unused_mut)] - use std::collections::BTreeMap; use codemap::{Span, Spanned}; diff --git a/src/builtin/modules/selector.rs b/src/builtin/modules/selector.rs index 32a7860..3181254 100644 --- a/src/builtin/modules/selector.rs +++ b/src/builtin/modules/selector.rs @@ -1,13 +1,9 @@ use crate::{ - args::CallArgs, builtin::modules::Module, builtin::selector::{ is_superselector, selector_append, selector_extend, selector_nest, selector_parse, selector_replace, selector_unify, simple_selectors, }, - 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 5e63c40..bd1b2cf 100644 --- a/src/builtin/modules/string.rs +++ b/src/builtin/modules/string.rs @@ -1,15 +1,8 @@ -use crate::{ - args::CallArgs, - builtin::{ - modules::Module, - string::{ - quote, str_index, str_insert, str_length, str_slice, to_lower_case, to_upper_case, - unquote, - }, +use crate::builtin::{ + modules::Module, + string::{ + quote, str_index, str_insert, str_length, str_slice, to_lower_case, to_upper_case, unquote, }, - error::SassResult, - parse::Parser, - value::Value, }; #[cfg(feature = "random")] @@ -25,4 +18,5 @@ pub(crate) fn declare(f: &mut Module) { f.insert_builtin("to-upper-case", to_upper_case); #[cfg(feature = "random")] f.insert_builtin("unique-id", unique_id); + f.insert_builtin("unquote", unquote); } From 94becb4dcb8cfd896efe8a7812e855445652bcd3 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 6 Aug 2020 04:00:45 -0400 Subject: [PATCH 49/59] implement idempotency with regard to module aliasing --- src/builtin/modules/mod.rs | 12 +++++++++++- src/parse/mod.rs | 2 +- tests/use.rs | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index 555aeff..708bbbe 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -28,8 +28,18 @@ pub(crate) struct Module(pub Scope); pub(crate) struct Modules(BTreeMap); impl Modules { - pub fn insert(&mut self, name: Identifier, module: Module) { + pub fn insert(&mut self, name: Identifier, module: Module, span: Span) -> SassResult<()> { + if self.0.contains_key(&name) { + return Err(( + format!("There's already a module with namespace \"{}\".", name), + span, + ) + .into()); + } + self.0.insert(name, module); + + Ok(()) } pub fn get(&self, name: Identifier, span: Span) -> SassResult<&Module> { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 1d5bab7..45b67ca 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -263,7 +263,7 @@ impl<'a> Parser<'a> { }, }; - self.modules.insert(module_name.into(), module); + self.modules.insert(module_name.into(), module, span)?; } Some(Token { kind: '/', .. }) => { self.toks.next(); diff --git a/tests/use.rs b/tests/use.rs index f5e1b3f..d30af39 100644 --- a/tests/use.rs +++ b/tests/use.rs @@ -158,3 +158,36 @@ fn use_user_defined_function() { &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) ); } + +#[test] +fn use_idempotent_no_alias() { + let input = "@use \"use_idempotent_no_alias\";\n@use \"use_idempotent_no_alias\";\n"; + tempfile!("use_idempotent_no_alias.scss", ""); + + assert_err!( + "Error: There's already a module with namespace \"use-idempotent-no-alias\".", + input + ); +} + +#[test] +fn use_idempotent_with_alias() { + let input = "@use \"use_idempotent_with_alias__a\" as foo;\n@use \"use_idempotent_with_alias__b\" as foo;\n"; + tempfile!("use_idempotent_with_alias__a.scss", ""); + tempfile!("use_idempotent_with_alias__b.scss", ""); + + assert_err!( + "Error: There's already a module with namespace \"foo\".", + input + ); +} + +#[test] +fn use_idempotent_builtin() { + let input = "@use \"sass:math\";\n@use \"sass:math\";\n"; + + assert_err!( + "Error: There's already a module with namespace \"math\".", + input + ); +} From 074d679cbd2db66c1bc1daf8eb94104435e0edf1 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 6 Aug 2020 21:00:34 -0400 Subject: [PATCH 50/59] support the `@use ... with (...)` syntax --- src/builtin/modules/mod.rs | 28 ++++++ src/lib.rs | 5 +- src/parse/control_flow.rs | 9 ++ src/parse/function.rs | 1 + src/parse/import.rs | 1 + src/parse/keyframes.rs | 2 + src/parse/media.rs | 10 --- src/parse/mixin.rs | 2 + src/parse/mod.rs | 169 +++++++++++++++++++++++++++---------- src/parse/value/parse.rs | 2 + src/parse/variable.rs | 15 +++- src/value/mod.rs | 1 + tests/use.rs | 62 ++++++++++++++ 13 files changed, 248 insertions(+), 59 deletions(-) diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index 708bbbe..1490adc 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -27,6 +27,34 @@ pub(crate) struct Module(pub Scope); #[derive(Debug, Default)] pub(crate) struct Modules(BTreeMap); +#[derive(Debug, Default)] +pub(crate) struct ModuleConfig(BTreeMap); + +impl ModuleConfig { + /// Removes and returns element with name + pub fn get(&mut self, name: Identifier) -> Option { + self.0.remove(&name) + } + + /// If this structure is not empty at the end of + /// an `@use`, we must throw an error + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn insert(&mut self, name: Spanned, value: Spanned) -> SassResult<()> { + if self.0.insert(name.node, value.node).is_some() { + Err(( + "The same variable may only be configured once.", + name.span.merge(value.span), + ) + .into()) + } else { + Ok(()) + } + } +} + impl Modules { pub fn insert(&mut self, name: Identifier, module: Module, span: Span) -> SassResult<()> { if self.0.contains_key(&name) { diff --git a/src/lib.rs b/src/lib.rs index 6f20adc..f691da6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,7 +95,7 @@ use peekmore::PeekMore; pub use crate::error::{SassError as Error, SassResult as Result}; pub(crate) use crate::token::Token; use crate::{ - builtin::modules::Modules, + builtin::modules::{ModuleConfig, Modules}, lexer::Lexer, output::Css, parse::{ @@ -295,6 +295,7 @@ pub fn from_path(p: &str, options: &Options) -> Result { content_scopes: &mut Scopes::new(), options, modules: &mut Modules::default(), + module_config: &mut ModuleConfig::default(), } .parse() .map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?; @@ -340,6 +341,7 @@ pub fn from_string(p: String, options: &Options) -> Result { content_scopes: &mut Scopes::new(), options, modules: &mut Modules::default(), + module_config: &mut ModuleConfig::default(), } .parse() .map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?; @@ -376,6 +378,7 @@ pub fn from_string(p: String) -> std::result::Result { content_scopes: &mut Scopes::new(), options: &Options::default(), modules: &mut Modules::default(), + module_config: &mut ModuleConfig::default(), } .parse() .map_err(|e| raw_to_parse_error(&map, *e, true).to_string())?; diff --git a/src/parse/control_flow.rs b/src/parse/control_flow.rs index babbd43..cb0816e 100644 --- a/src/parse/control_flow.rs +++ b/src/parse/control_flow.rs @@ -54,6 +54,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_stmt()?; } else { @@ -114,6 +115,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_stmt()?; } else { @@ -143,6 +145,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_stmt(); } @@ -323,6 +326,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_stmt()?; if !these_stmts.is_empty() { @@ -346,6 +350,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_stmt()?, ); @@ -397,6 +402,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_stmt()?; if !these_stmts.is_empty() { @@ -420,6 +426,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_stmt()?, ); @@ -512,6 +519,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_stmt()?; if !these_stmts.is_empty() { @@ -535,6 +543,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_stmt()?, ); diff --git a/src/parse/function.rs b/src/parse/function.rs index a5a8b19..1f1830a 100644 --- a/src/parse/function.rs +++ b/src/parse/function.rs @@ -121,6 +121,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_stmt()?; diff --git a/src/parse/import.rs b/src/parse/import.rs index ac252af..cd17e8c 100644 --- a/src/parse/import.rs +++ b/src/parse/import.rs @@ -108,6 +108,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse(); } diff --git a/src/parse/keyframes.rs b/src/parse/keyframes.rs index adf792c..4a5caf9 100644 --- a/src/parse/keyframes.rs +++ b/src/parse/keyframes.rs @@ -174,6 +174,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, }) .parse_keyframes_selector()?; @@ -210,6 +211,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_stmt()?; diff --git a/src/parse/media.rs b/src/parse/media.rs index 2a0c036..61f124a 100644 --- a/src/parse/media.rs +++ b/src/parse/media.rs @@ -25,16 +25,6 @@ impl<'a> Parser<'a> { Ok(false) } - pub fn expect_char(&mut self, c: char) -> SassResult<()> { - if let Some(Token { kind, .. }) = self.toks.peek() { - if *kind == c { - self.toks.next(); - return Ok(()); - } - } - Err((format!("expected \"{}\".", c), self.span_before).into()) - } - pub fn scan_char(&mut self, c: char) -> bool { if let Some(Token { kind, .. }) = self.toks.peek() { if *kind == c { diff --git a/src/parse/mixin.rs b/src/parse/mixin.rs index e0c0d63..6fcc1d6 100644 --- a/src/parse/mixin.rs +++ b/src/parse/mixin.rs @@ -183,6 +183,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_stmt()?; @@ -245,6 +246,7 @@ impl<'a> Parser<'a> { content_scopes: self.scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_stmt()? } else { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 45b67ca..7a29a34 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -12,7 +12,8 @@ use crate::{ }, builtin::modules::{ declare_module_color, declare_module_list, declare_module_map, declare_module_math, - declare_module_meta, declare_module_selector, declare_module_string, Module, Modules, + declare_module_meta, declare_module_selector, declare_module_string, Module, ModuleConfig, + Modules, }, error::SassResult, lexer::Lexer, @@ -94,6 +95,7 @@ pub(crate) struct Parser<'a> { pub options: &'a Options<'a>, pub modules: &'a mut Modules, + pub module_config: &'a mut ModuleConfig, } impl<'a> Parser<'a> { @@ -113,6 +115,99 @@ impl<'a> Parser<'a> { Ok(stmts) } + pub fn expect_char(&mut self, c: char) -> SassResult<()> { + match self.toks.peek() { + Some(Token { kind, pos }) if *kind == c => { + self.span_before = *pos; + self.toks.next(); + Ok(()) + } + Some(Token { pos, .. }) => Err((format!("expected \"{}\".", c), *pos).into()), + None => Err((format!("expected \"{}\".", c), self.span_before).into()), + } + } + + pub fn consume_char_if_exists(&mut self, c: char) { + if let Some(Token { kind, .. }) = self.toks.peek() { + if *kind == c { + self.toks.next(); + } + } + } + + fn parse_module_alias(&mut self) -> SassResult> { + if let Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) = self.toks.peek() { + let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?; + ident.node.make_ascii_lowercase(); + if ident.node != "as" { + return Err(("expected \";\".", ident.span).into()); + } + + self.whitespace_or_comment(); + + if let Some(Token { kind: '*', .. }) = self.toks.peek() { + self.toks.next(); + return Ok(Some('*'.to_string())); + } else { + let name = self.parse_identifier_no_interpolation(false)?; + + return Ok(Some(name.node)); + } + } + + Ok(None) + } + + fn parse_module_config(&mut self) -> SassResult { + let mut config = ModuleConfig::default(); + + if let Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. }) = self.toks.peek() { + let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?; + ident.node.make_ascii_lowercase(); + if ident.node != "with" { + return Err(("expected \";\".", ident.span).into()); + } + + self.whitespace_or_comment(); + + self.span_before = ident.span; + + self.expect_char('(')?; + + loop { + self.whitespace_or_comment(); + self.expect_char('$')?; + + let name = self.parse_identifier_no_interpolation(false)?; + + self.whitespace_or_comment(); + self.expect_char(':')?; + self.whitespace_or_comment(); + + let value = self.parse_value(false, &|toks| match toks.peek() { + Some(Token { kind: ',', .. }) | Some(Token { kind: ')', .. }) => true, + _ => false, + })?; + + config.insert(name.map_node(|n| n.into()), value)?; + + match self.toks.next() { + Some(Token { kind: ',', .. }) => { + continue; + } + Some(Token { kind: ')', .. }) => { + break; + } + Some(..) | None => { + return Err(("expected \")\".", self.span_before).into()); + } + } + } + } + + Ok(config) + } + /// Returns any multiline comments that may have been found /// while loading modules #[allow(clippy::eval_order_dependence)] @@ -156,44 +251,14 @@ impl<'a> Parser<'a> { self.whitespace_or_comment(); - let mut module_alias: Option = None; + let module_alias = self.parse_module_alias()?; - match self.toks.peek() { - Some(Token { kind: ';', .. }) => { - self.toks.next(); - } - Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) => { - let mut ident = - peek_ident_no_interpolation(self.toks, false, self.span_before)?; - ident.node.make_ascii_lowercase(); - if ident.node != "as" { - return Err(("expected \";\".", ident.span).into()); - } + self.whitespace_or_comment(); - self.whitespace_or_comment(); + let mut config = self.parse_module_config()?; - let name_span; - - if let Some(Token { kind: '*', pos }) = self.toks.peek() { - name_span = *pos; - self.toks.next(); - module_alias = Some('*'.to_string()); - } else { - let name = self.parse_identifier_no_interpolation(false)?; - - module_alias = Some(name.node); - name_span = name.span; - } - - if !matches!(self.toks.next(), Some(Token { kind: ';', .. })) { - return Err(("expected \";\".", name_span).into()); - } - } - Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. }) => { - todo!("with") - } - Some(..) | None => return Err(("expected \";\".", span).into()), - } + self.whitespace_or_comment(); + self.expect_char(';')?; let module = match module_name.as_ref() { "sass:color" => declare_module_color(), @@ -232,19 +297,28 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: &mut config, } .parse()?, ); + if !config.is_empty() { + return Err(("This variable was not declared with !default in the @used module.", span).into()); + } + Module::new_from_scope(global_scope) } else { - return Err( - ("Error: Can't find stylesheet to import.", span).into() - ); + return Err(("Can't find stylesheet to import.", span).into()); } } }; + // if the config isn't empty here, that means + // variables were passed to a builtin module + if !config.is_empty() { + return Err(("Built-in modules can't be configured.", span).into()); + } + let module_name = match module_alias.as_deref() { Some("*") => { self.global_scope.merge_module(module); @@ -599,6 +673,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, }, allows_parent, true, @@ -657,10 +732,11 @@ impl<'a> Parser<'a> { pub fn parse_interpolation(&mut self) -> SassResult> { let val = self.parse_value(true, &|_| false)?; - match self.toks.next() { - Some(Token { kind: '}', .. }) => {} - Some(..) | None => return Err(("expected \"}\".", val.span).into()), - } + + self.span_before = val.span; + + self.expect_char('}')?; + Ok(val.map_node(Value::unquote)) } @@ -899,6 +975,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_stmt()? .into_iter() @@ -944,14 +1021,14 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_selector(false, true, String::new())?; + // todo: this might be superfluous self.whitespace(); - if let Some(Token { kind: ';', .. }) = self.toks.peek() { - self.toks.next(); - } + self.consume_char_if_exists(';'); let extend_rule = ExtendRule::new(value.clone(), is_optional, self.span_before); diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 5a826ab..1962fba 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -199,6 +199,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_value(in_paren, predicate) } @@ -224,6 +225,7 @@ impl<'a> Parser<'a> { content_scopes: self.content_scopes, options: self.options, modules: self.modules, + module_config: self.module_config, } .parse_value(in_paren, &|_| false) } diff --git a/src/parse/variable.rs b/src/parse/variable.rs index ad08142..78df9b2 100644 --- a/src/parse/variable.rs +++ b/src/parse/variable.rs @@ -39,13 +39,24 @@ impl<'a> Parser<'a> { } = self.parse_variable_value()?; if default { + let config_val = self.module_config.get(ident); if self.at_root && !self.flags.in_control_flow() { if !self.global_scope.var_exists(ident) { - let value = self.parse_value_from_vec(val_toks, true)?.node; + let value = if let Some(config_val) = config_val { + config_val + } else { + self.parse_value_from_vec(val_toks, true)?.node + }; + self.global_scope.insert_var(ident, value); } } else { - let value = self.parse_value_from_vec(val_toks, true)?.node; + let value = if let Some(config_val) = config_val { + config_val + } else { + self.parse_value_from_vec(val_toks, true)?.node + }; + if global && !self.global_scope.var_exists(ident) { self.global_scope.insert_var(ident, value.clone()); } diff --git a/src/value/mod.rs b/src/value/mod.rs index d9b13b3..7e7291a 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -543,6 +543,7 @@ impl Value { content_scopes: parser.content_scopes, options: parser.options, modules: parser.modules, + module_config: parser.module_config, } .parse_selector(allows_parent, true, String::new())? .0) diff --git a/tests/use.rs b/tests/use.rs index d30af39..035a86d 100644 --- a/tests/use.rs +++ b/tests/use.rs @@ -191,3 +191,65 @@ fn use_idempotent_builtin() { input ); } + +#[test] +fn use_with_simple() { + let input = "@use \"use_with_simple\" with ($a: red);\na {\n color: use_with_simple.$a;\n}"; + tempfile!("use_with_simple.scss", "$a: green !default;"); + assert_eq!( + "a {\n color: red;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) + ); +} + +#[test] +fn use_as_with() { + let input = "@use \"use_as_with\" as module with ($a: red);\na {\n color: module.$a;\n}"; + tempfile!("use_as_with.scss", "$a: green !default;"); + assert_eq!( + "a {\n color: red;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) + ); +} + +#[test] +fn use_whitespace_and_comments() { + let input = "@use /**/ \"use_whitespace_and_comments\" /**/ as /**/ foo /**/ with /**/ ( /**/ $a /**/ : /**/ red /**/ ) /**/ ;"; + tempfile!( + "use_whitespace_and_comments.scss", + "$a: green !default; a { color: $a }" + ); + assert_eq!( + "a {\n color: red;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) + ); +} + +#[test] +fn use_with_builtin_module() { + let input = "@use \"sass:math\" with ($e: 2.7);"; + + assert_err!("Error: Built-in modules can't be configured.", input); +} + +#[test] +fn use_with_variable_never_used() { + let input = "@use \"use_with_variable_never_used\" with ($a: red);"; + tempfile!("use_with_variable_never_used.scss", ""); + + assert_err!( + "Error: This variable was not declared with !default in the @used module.", + input + ); +} + +#[test] +fn use_with_same_variable_multiple_times() { + let input = "@use \"use_with_same_variable_multiple_times\" as foo with ($a: b, $a: c);"; + tempfile!("use_with_same_variable_multiple_times.scss", ""); + + assert_err!( + "Error: The same variable may only be configured once.", + input + ); +} From 0254517095ce9970669375e00366cd380e2ec16d Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 6 Aug 2020 21:36:11 -0400 Subject: [PATCH 51/59] refactor errors using `Parser::expect_char` --- src/parse/args.rs | 45 +++++++++------------------ src/parse/control_flow.rs | 51 ++++++++++--------------------- src/parse/function.rs | 8 ++--- src/parse/keyframes.rs | 5 ++- src/parse/mixin.rs | 4 +-- src/parse/mod.rs | 4 +-- src/parse/value/parse.rs | 1 + src/parse/variable.rs | 6 ++-- src/selector/attribute.rs | 64 ++++++++++++++++++--------------------- src/selector/parse.rs | 14 ++------- 10 files changed, 73 insertions(+), 129 deletions(-) diff --git a/src/parse/args.rs b/src/parse/args.rs index d10ac2f..8bb3e9d 100644 --- a/src/parse/args.rs +++ b/src/parse/args.rs @@ -72,19 +72,12 @@ impl<'a> Parser<'a> { } } '.' => { - let next = self.toks.next().ok_or(("expected \".\".", span))?; - if next.kind != '.' { - return Err(("expected \".\".", next.pos()).into()); - } - let next = self.toks.next().ok_or(("expected \".\".", next.pos()))?; - if next.kind != '.' { - return Err(("expected \".\".", next.pos()).into()); - } + self.expect_char('.')?; + self.expect_char('.')?; + self.whitespace_or_comment(); - let next = self.toks.next().ok_or(("expected \")\".", next.pos()))?; - if next.kind != ')' { - return Err(("expected \")\".", next.pos()).into()); - } + + self.expect_char(')')?; is_variadic = true; @@ -119,6 +112,7 @@ impl<'a> Parser<'a> { } self.whitespace_or_comment(); // TODO: this should NOT eat the opening curly brace + // todo: self.expect_char('{')?; match self.toks.next() { Some(v) if v.kind == '{' => {} Some(..) | None => return Err(("expected \"{\".", close_paren_span).into()), @@ -225,11 +219,7 @@ impl<'a> Parser<'a> { return Err(("expected \")\".", pos).into()); } self.toks.next(); - if let Some(Token { kind: '.', .. }) = self.toks.peek() { - self.toks.next(); - } else { - return Err(("expected \".\".", pos).into()); - } + self.expect_char('.')?; } else { return Err(("expected \")\".", pos).into()); } @@ -323,23 +313,16 @@ impl<'a> Parser<'a> { self.whitespace_or_comment(); continue; } - Some(Token { kind: '.', pos }) => { - let pos = *pos; + Some(Token { kind: '.', .. }) => { self.toks.next(); - if let Some(Token { kind: '.', pos }) = self.toks.peek().cloned() { - if !name.is_empty() { - return Err(("expected \")\".", pos).into()); - } - self.toks.next(); - if let Some(Token { kind: '.', .. }) = self.toks.peek() { - self.toks.next(); - } else { - return Err(("expected \".\".", pos).into()); - } - } else { - return Err(("expected \")\".", pos).into()); + self.expect_char('.')?; + + if !name.is_empty() { + return Err(("expected \")\".", self.span_before).into()); } + + self.expect_char('.')?; } Some(Token { pos, .. }) => { return Err(("expected \")\".", *pos).into()); diff --git a/src/parse/control_flow.rs b/src/parse/control_flow.rs index cb0816e..100cb3a 100644 --- a/src/parse/control_flow.rs +++ b/src/parse/control_flow.rs @@ -24,14 +24,10 @@ impl<'a> Parser<'a> { let init_cond = self.parse_value(true, &|_| false)?.node; - // consume the open curly brace - let span_before = match self.toks.next() { - Some(Token { kind: '{', pos }) => pos, - Some(..) | None => return Err(("expected \"{\".", self.span_before).into()), - }; + self.expect_char('{')?; if self.toks.peek().is_none() { - return Err(("expected \"}\".", span_before).into()); + return Err(("expected \"}\".", self.span_before).into()); } self.whitespace_or_comment(); @@ -89,12 +85,7 @@ impl<'a> Parser<'a> { false } else { let v = self.parse_value(true, &|_| false)?.node.is_true(); - match self.toks.next() { - Some(Token { kind: '{', .. }) => {} - Some(..) | None => { - return Err(("expected \"{\".", self.span_before).into()) - } - } + self.expect_char('{')?; v }; if cond { @@ -164,17 +155,15 @@ impl<'a> Parser<'a> { } pub(super) fn parse_for(&mut self) -> SassResult> { + // todo: whitespace or comment self.whitespace(); - let next = self - .toks - .next() - .ok_or(("expected \"$\".", self.span_before))?; - let var: Spanned = match next.kind { - '$' => self - .parse_identifier_no_interpolation(false)? - .map_node(|i| i.into()), - _ => return Err(("expected \"$\".", self.span_before).into()), - }; + // todo: test for error here + self.expect_char('$')?; + + let var = self + .parse_identifier_no_interpolation(false)? + .map_node(|n| n.into()); + self.whitespace(); self.span_before = match self.toks.peek() { Some(tok) => tok.pos, @@ -278,11 +267,7 @@ impl<'a> Parser<'a> { } }; - // consume the open curly brace - match self.toks.next() { - Some(Token { kind: '{', pos }) => pos, - Some(..) | None => return Err(("expected \"{\".", to_val.span).into()), - }; + self.expect_char('{')?; let body = read_until_closing_curly_brace(self.toks)?; self.toks.next(); @@ -443,15 +428,11 @@ impl<'a> Parser<'a> { let mut vars: Vec> = Vec::new(); loop { - let next = self - .toks - .next() - .ok_or(("expected \"$\".", self.span_before))?; + self.expect_char('$')?; - match next.kind { - '$' => vars.push(self.parse_identifier()?.map_node(|i| i.into())), - _ => return Err(("expected \"$\".", next.pos()).into()), - } + vars.push(self.parse_identifier()?.map_node(|i| i.into())); + + // todo: whitespace or comment self.whitespace(); if self .toks diff --git a/src/parse/function.rs b/src/parse/function.rs index 1f1830a..5b02227 100644 --- a/src/parse/function.rs +++ b/src/parse/function.rs @@ -40,11 +40,9 @@ impl<'a> Parser<'a> { } self.whitespace_or_comment(); - let args = match self.toks.next() { - Some(Token { kind: '(', .. }) => self.parse_func_args()?, - Some(Token { pos, .. }) => return Err(("expected \"(\".", pos).into()), - None => return Err(("expected \"(\".", span).into()), - }; + self.expect_char('(')?; + + let args = self.parse_func_args()?; self.whitespace(); diff --git a/src/parse/keyframes.rs b/src/parse/keyframes.rs index 4a5caf9..b082d03 100644 --- a/src/parse/keyframes.rs +++ b/src/parse/keyframes.rs @@ -63,9 +63,8 @@ impl<'a, 'b> KeyframesSelectorParser<'a, 'b> { num.push_str(&eat_whole_number(self.parser.toks)); } - if !matches!(self.parser.toks.next(), Some(Token { kind: '%', .. })) { - return Err(("expected \"%\".", tok.pos).into()); - } + self.parser.expect_char('%')?; + selectors.push(KeyframesSelector::Percent(num.into_boxed_str())); } '{' => break, diff --git a/src/parse/mixin.rs b/src/parse/mixin.rs index 6fcc1d6..f0764b7 100644 --- a/src/parse/mixin.rs +++ b/src/parse/mixin.rs @@ -104,9 +104,7 @@ impl<'a> Parser<'a> { ident.node.make_ascii_lowercase(); if ident.node == "using" { self.whitespace_or_comment(); - if !matches!(self.toks.next(), Some(Token { kind: '(', .. })) { - return Err(("expected \"(\".", ident.span).into()); - } + self.expect_char('(')?; Some(self.parse_func_args()?) } else { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 7a29a34..418ad7b 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -903,9 +903,7 @@ impl<'a> Parser<'a> { self.whitespace(); - if !matches!(self.toks.next(), Some(Token { kind: '{', .. })) { - return Err(("expected \"{\".", self.span_before).into()); - } + self.expect_char('{')?; let raw_body = self.parse_stmt()?; diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 1962fba..0e59095 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -598,6 +598,7 @@ impl<'a> Parser<'a> { }; // todo: the above shouldn't eat the closing paren if let Some(last_tok) = inner.pop() { + // todo: we should remove this like we did for square braces if last_tok.kind != ')' { return Some(Err(("expected \")\".", span).into())); } diff --git a/src/parse/variable.rs b/src/parse/variable.rs index 78df9b2..6a7429c 100644 --- a/src/parse/variable.rs +++ b/src/parse/variable.rs @@ -29,9 +29,9 @@ impl<'a> Parser<'a> { assert!(matches!(self.toks.next(), Some(Token { kind: '$', .. }))); let ident: Identifier = self.parse_identifier_no_interpolation(false)?.node.into(); self.whitespace(); - if !matches!(self.toks.next(), Some(Token { kind: ':', .. })) { - return Err(("expected \":\".", self.span_before).into()); - } + + self.expect_char(':')?; + let VariableValue { val_toks, global, diff --git a/src/selector/attribute.rs b/src/selector/attribute.rs index 3fb7dc1..e1b8129 100644 --- a/src/selector/attribute.rs +++ b/src/selector/attribute.rs @@ -5,7 +5,9 @@ use std::{ use codemap::Span; -use crate::{common::QuoteKind, error::SassResult, parse::Parser, utils::is_ident, value::Value}; +use crate::{ + common::QuoteKind, error::SassResult, parse::Parser, utils::is_ident, value::Value, Token, +}; use super::{Namespace, QualifiedName}; @@ -41,13 +43,8 @@ impl Hash for Attribute { fn attribute_name(parser: &mut Parser<'_>, start: Span) -> SassResult { let next = parser.toks.peek().ok_or(("Expected identifier.", start))?; if next.kind == '*' { - let pos = next.pos; parser.toks.next(); - if parser.toks.peek().ok_or(("expected \"|\".", pos))?.kind != '|' { - return Err(("expected \"|\".", pos).into()); - } - - parser.span_before = parser.toks.next().unwrap().pos(); + parser.expect_char('|')?; let ident = parser.parse_identifier()?.node; return Ok(QualifiedName { @@ -89,19 +86,18 @@ fn attribute_name(parser: &mut Parser<'_>, start: Span) -> SassResult) -> SassResult { - let start = parser.span_before; - let op = match parser.toks.next().ok_or(("Expected \"]\".", start))?.kind { - '=' => return Ok(AttributeOp::Equals), - '~' => AttributeOp::Include, - '|' => AttributeOp::Dash, - '^' => AttributeOp::Prefix, - '$' => AttributeOp::Suffix, - '*' => AttributeOp::Contains, - _ => return Err(("Expected \"]\".", start).into()), + let op = match parser.toks.next() { + Some(Token { kind: '=', .. }) => return Ok(AttributeOp::Equals), + Some(Token { kind: '~', .. }) => AttributeOp::Include, + Some(Token { kind: '|', .. }) => AttributeOp::Dash, + Some(Token { kind: '^', .. }) => AttributeOp::Prefix, + Some(Token { kind: '$', .. }) => AttributeOp::Suffix, + Some(Token { kind: '*', .. }) => AttributeOp::Contains, + Some(..) | None => return Err(("Expected \"]\".", parser.span_before).into()), }; - if parser.toks.next().ok_or(("expected \"=\".", start))?.kind != '=' { - return Err(("expected \"=\".", start).into()); - } + + parser.expect_char('=')?; + Ok(op) } impl Attribute { @@ -145,25 +141,23 @@ impl Attribute { }; parser.whitespace(); - let peek = parser.toks.peek().ok_or(("expected more input.", start))?; - - let modifier = match peek.kind { - c if c.is_alphabetic() => Some(c), + let modifier = match parser.toks.peek().cloned() { + Some(Token { + kind: c @ 'a'..='z', + .. + }) + | Some(Token { + kind: c @ 'A'..='Z', + .. + }) => { + parser.toks.next(); + parser.whitespace(); + Some(c) + } _ => None, }; - let pos = peek.pos(); - - if modifier.is_some() { - parser.toks.next(); - parser.whitespace(); - } - - if parser.toks.peek().ok_or(("expected \"]\".", pos))?.kind != ']' { - return Err(("expected \"]\".", pos).into()); - } - - parser.toks.next(); + parser.expect_char(']')?; Ok(Attribute { op, diff --git a/src/selector/parse.rs b/src/selector/parse.rs index cc7c001..2522bce 100644 --- a/src/selector/parse.rs +++ b/src/selector/parse.rs @@ -317,14 +317,14 @@ impl<'a, 'b> SelectorParser<'a, 'b> { if SELECTOR_PSEUDO_ELEMENTS.contains(&unvendored) { selector = Some(Box::new(self.parse_selector_list()?)); self.parser.whitespace(); - self.expect_closing_paren()?; + self.parser.expect_char(')')?; } else { argument = Some(self.declaration_value()?.into_boxed_str()); } } else if SELECTOR_PSEUDO_CLASSES.contains(&unvendored) { selector = Some(Box::new(self.parse_selector_list()?)); self.parser.whitespace(); - self.expect_closing_paren()?; + self.parser.expect_char(')')?; } else if unvendored == "nth-child" || unvendored == "nth-last-child" { let mut this_arg = self.parse_a_n_plus_b()?; let found_whitespace = self.parser.whitespace(); @@ -339,7 +339,7 @@ impl<'a, 'b> SelectorParser<'a, 'b> { } _ => {} } - self.expect_closing_paren()?; + self.parser.expect_char(')')?; argument = Some(this_arg.into_boxed_str()); } else { argument = Some( @@ -541,14 +541,6 @@ impl<'a, 'b> SelectorParser<'a, 'b> { Err((format!("Expected \"{}\".", s), self.span).into()) } } - - fn expect_closing_paren(&mut self) -> SassResult<()> { - if let Some(Token { kind: ')', .. }) = self.parser.toks.next() { - Ok(()) - } else { - Err(("expected \")\".", self.span).into()) - } - } } /// Returns whether `c` can start a simple selector other than a type From cacf605af8b9b3661a7778f768fb100251504780 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 6 Aug 2020 21:58:53 -0400 Subject: [PATCH 52/59] respect `$with` argument to `load-css` --- src/builtin/modules/meta.rs | 50 ++++++++++++---- src/parse/mod.rs | 114 ++++++++++++++++++++---------------- 2 files changed, 100 insertions(+), 64 deletions(-) diff --git a/src/builtin/modules/meta.rs b/src/builtin/modules/meta.rs index e808be7..fb0b1ab 100644 --- a/src/builtin/modules/meta.rs +++ b/src/builtin/modules/meta.rs @@ -1,3 +1,5 @@ +use codemap::Spanned; + use crate::{ args::CallArgs, builtin::{ @@ -5,7 +7,7 @@ use crate::{ call, content_exists, feature_exists, function_exists, get_function, global_variable_exists, inspect, keywords, mixin_exists, type_of, variable_exists, }, - modules::Module, + modules::{Module, ModuleConfig}, }, error::SassResult, parse::{Parser, Stmt}, @@ -15,13 +17,15 @@ use crate::{ fn load_css(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult> { args.max_args(2)?; + let span = args.span(); + // todo: https://github.com/sass/dart-sass/issues/1054 let url = match args.get_err(0, "module")? { Value::String(s, ..) => s, v => { return Err(( - format!("$module: {} is not a string.", v.inspect(args.span())?), - args.span(), + format!("$module: {} is not a string.", v.inspect(span)?), + span, ) .into()) } @@ -30,19 +34,39 @@ fn load_css(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult let with = match args.default_arg(1, "with", Value::Null)? { Value::Map(map) => Some(map), Value::Null => None, - v => { - return Err(( - format!("$with: {} is not a map.", v.inspect(args.span())?), - args.span(), - ) - .into()) - } + v => return Err((format!("$with: {} is not a map.", v.inspect(span)?), span).into()), }; - if let Some(..) = with { - todo!("`$with` to `load-css` not yet implemented") + // todo: tests for `with` + if let Some(with) = with { + let mut config = ModuleConfig::default(); + + for (key, value) in with { + let key = match key { + Value::String(s, ..) => s, + v => { + return Err(( + format!("$with key: {} is not a string.", v.inspect(span)?), + span, + ) + .into()) + } + }; + + config.insert( + Spanned { + node: key.into(), + span, + }, + value.span(span), + )?; + } + + let (_, stmts) = parser.load_module(&url, &mut config)?; + + Ok(stmts) } else { - parser.parse_single_import(&url, args.span()) + parser.parse_single_import(&url, span) } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 418ad7b..79db038 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -208,6 +208,66 @@ impl<'a> Parser<'a> { Ok(config) } + pub fn load_module( + &mut self, + name: &str, + config: &mut ModuleConfig, + ) -> SassResult<(Module, Vec)> { + Ok(match name { + "sass:color" => (declare_module_color(), Vec::new()), + "sass:list" => (declare_module_list(), Vec::new()), + "sass:map" => (declare_module_map(), Vec::new()), + "sass:math" => (declare_module_math(), Vec::new()), + "sass:meta" => (declare_module_meta(), Vec::new()), + "sass:selector" => (declare_module_selector(), Vec::new()), + "sass:string" => (declare_module_string(), Vec::new()), + _ => { + if let Some(import) = self.find_import(name.as_ref()) { + let mut global_scope = Scope::new(); + + let file = self + .map + .add_file(name.to_owned(), String::from_utf8(fs::read(&import)?)?); + + let stmts = Parser { + toks: &mut Lexer::new(&file) + .collect::>() + .into_iter() + .peekmore(), + map: self.map, + path: &import, + scopes: self.scopes, + global_scope: &mut global_scope, + super_selectors: self.super_selectors, + span_before: file.span.subspan(0, 0), + content: self.content, + flags: self.flags, + at_root: self.at_root, + at_root_has_selector: self.at_root_has_selector, + extender: self.extender, + content_scopes: self.content_scopes, + options: self.options, + modules: self.modules, + module_config: config, + } + .parse()?; + + if !config.is_empty() { + return Err(( + "This variable was not declared with !default in the @used module.", + self.span_before, + ) + .into()); + } + + (Module::new_from_scope(global_scope), stmts) + } else { + return Err(("Can't find stylesheet to import.", self.span_before).into()); + } + } + }) + } + /// Returns any multiline comments that may have been found /// while loading modules #[allow(clippy::eval_order_dependence)] @@ -260,58 +320,10 @@ impl<'a> Parser<'a> { self.whitespace_or_comment(); self.expect_char(';')?; - let module = match module_name.as_ref() { - "sass:color" => declare_module_color(), - "sass:list" => declare_module_list(), - "sass:map" => declare_module_map(), - "sass:math" => declare_module_math(), - "sass:meta" => declare_module_meta(), - "sass:selector" => declare_module_selector(), - "sass:string" => declare_module_string(), - _ => { - if let Some(import) = self.find_import(module_name.as_ref().as_ref()) { - let mut global_scope = Scope::new(); + let (module, mut stmts) = + self.load_module(module_name.as_ref(), &mut config)?; - let file = self.map.add_file( - module_name.clone().into_owned(), - String::from_utf8(fs::read(&import)?)?, - ); - - comments.append( - &mut Parser { - toks: &mut Lexer::new(&file) - .collect::>() - .into_iter() - .peekmore(), - map: self.map, - path: &import, - scopes: self.scopes, - global_scope: &mut global_scope, - super_selectors: self.super_selectors, - span_before: file.span.subspan(0, 0), - content: self.content, - flags: self.flags, - at_root: self.at_root, - at_root_has_selector: self.at_root_has_selector, - extender: self.extender, - content_scopes: self.content_scopes, - options: self.options, - modules: self.modules, - module_config: &mut config, - } - .parse()?, - ); - - if !config.is_empty() { - return Err(("This variable was not declared with !default in the @used module.", span).into()); - } - - Module::new_from_scope(global_scope) - } else { - return Err(("Can't find stylesheet to import.", span).into()); - } - } - }; + comments.append(&mut stmts); // if the config isn't empty here, that means // variables were passed to a builtin module From bb0b352af276ff23ea8dfd180f4b17302dd8984c Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 6 Aug 2020 22:05:50 -0400 Subject: [PATCH 53/59] move module parsing to separate file --- src/parse/mod.rs | 242 +----------------------------------------- src/parse/module.rs | 251 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+), 239 deletions(-) create mode 100644 src/parse/module.rs diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 79db038..6b5ece3 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, fs, path::Path, vec::IntoIter}; +use std::{convert::TryFrom, path::Path, vec::IntoIter}; use codemap::{CodeMap, Span, Spanned}; use peekmore::{PeekMore, PeekMoreIterator}; @@ -10,13 +10,8 @@ use crate::{ mixin::Content, AtRuleKind, SupportsRule, UnknownAtRule, }, - builtin::modules::{ - declare_module_color, declare_module_list, declare_module_map, declare_module_math, - declare_module_meta, declare_module_selector, declare_module_string, Module, ModuleConfig, - Modules, - }, + builtin::modules::{ModuleConfig, Modules}, error::SassResult, - lexer::Lexer, scope::{Scope, Scopes}, selector::{ ComplexSelectorComponent, ExtendRule, ExtendedSelector, Extender, Selector, SelectorParser, @@ -42,6 +37,7 @@ mod import; mod keyframes; mod media; mod mixin; +mod module; mod style; mod throw_away; mod value; @@ -135,238 +131,6 @@ impl<'a> Parser<'a> { } } - fn parse_module_alias(&mut self) -> SassResult> { - if let Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) = self.toks.peek() { - let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?; - ident.node.make_ascii_lowercase(); - if ident.node != "as" { - return Err(("expected \";\".", ident.span).into()); - } - - self.whitespace_or_comment(); - - if let Some(Token { kind: '*', .. }) = self.toks.peek() { - self.toks.next(); - return Ok(Some('*'.to_string())); - } else { - let name = self.parse_identifier_no_interpolation(false)?; - - return Ok(Some(name.node)); - } - } - - Ok(None) - } - - fn parse_module_config(&mut self) -> SassResult { - let mut config = ModuleConfig::default(); - - if let Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. }) = self.toks.peek() { - let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?; - ident.node.make_ascii_lowercase(); - if ident.node != "with" { - return Err(("expected \";\".", ident.span).into()); - } - - self.whitespace_or_comment(); - - self.span_before = ident.span; - - self.expect_char('(')?; - - loop { - self.whitespace_or_comment(); - self.expect_char('$')?; - - let name = self.parse_identifier_no_interpolation(false)?; - - self.whitespace_or_comment(); - self.expect_char(':')?; - self.whitespace_or_comment(); - - let value = self.parse_value(false, &|toks| match toks.peek() { - Some(Token { kind: ',', .. }) | Some(Token { kind: ')', .. }) => true, - _ => false, - })?; - - config.insert(name.map_node(|n| n.into()), value)?; - - match self.toks.next() { - Some(Token { kind: ',', .. }) => { - continue; - } - Some(Token { kind: ')', .. }) => { - break; - } - Some(..) | None => { - return Err(("expected \")\".", self.span_before).into()); - } - } - } - } - - Ok(config) - } - - pub fn load_module( - &mut self, - name: &str, - config: &mut ModuleConfig, - ) -> SassResult<(Module, Vec)> { - Ok(match name { - "sass:color" => (declare_module_color(), Vec::new()), - "sass:list" => (declare_module_list(), Vec::new()), - "sass:map" => (declare_module_map(), Vec::new()), - "sass:math" => (declare_module_math(), Vec::new()), - "sass:meta" => (declare_module_meta(), Vec::new()), - "sass:selector" => (declare_module_selector(), Vec::new()), - "sass:string" => (declare_module_string(), Vec::new()), - _ => { - if let Some(import) = self.find_import(name.as_ref()) { - let mut global_scope = Scope::new(); - - let file = self - .map - .add_file(name.to_owned(), String::from_utf8(fs::read(&import)?)?); - - let stmts = Parser { - toks: &mut Lexer::new(&file) - .collect::>() - .into_iter() - .peekmore(), - map: self.map, - path: &import, - scopes: self.scopes, - global_scope: &mut global_scope, - super_selectors: self.super_selectors, - span_before: file.span.subspan(0, 0), - content: self.content, - flags: self.flags, - at_root: self.at_root, - at_root_has_selector: self.at_root_has_selector, - extender: self.extender, - content_scopes: self.content_scopes, - options: self.options, - modules: self.modules, - module_config: config, - } - .parse()?; - - if !config.is_empty() { - return Err(( - "This variable was not declared with !default in the @used module.", - self.span_before, - ) - .into()); - } - - (Module::new_from_scope(global_scope), stmts) - } else { - return Err(("Can't find stylesheet to import.", self.span_before).into()); - } - } - }) - } - - /// Returns any multiline comments that may have been found - /// while loading modules - #[allow(clippy::eval_order_dependence)] - fn load_modules(&mut self) -> SassResult> { - let mut comments = Vec::new(); - - loop { - self.whitespace(); - match self.toks.peek() { - Some(Token { kind: '@', .. }) => { - self.toks.advance_cursor(); - - if let Some(Token { kind, .. }) = self.toks.peek() { - if !matches!(kind, 'a'..='z' | 'A'..='Z' | '\\') { - break; - } - } - - match AtRuleKind::try_from(&peek_ident_no_interpolation( - self.toks, - false, - self.span_before, - )?)? { - AtRuleKind::Use => { - self.toks.truncate_iterator_to_cursor(); - } - _ => { - break; - } - } - - self.whitespace_or_comment(); - - let quote = match self.toks.next() { - Some(Token { kind: q @ '"', .. }) | Some(Token { kind: q @ '\'', .. }) => q, - Some(..) | None => todo!(), - }; - - let Spanned { node: module, span } = self.parse_quoted_string(quote)?; - let module_name = module.unquote().to_css_string(span)?; - - self.whitespace_or_comment(); - - let module_alias = self.parse_module_alias()?; - - self.whitespace_or_comment(); - - let mut config = self.parse_module_config()?; - - self.whitespace_or_comment(); - self.expect_char(';')?; - - let (module, mut stmts) = - self.load_module(module_name.as_ref(), &mut config)?; - - comments.append(&mut stmts); - - // if the config isn't empty here, that means - // variables were passed to a builtin module - if !config.is_empty() { - return Err(("Built-in modules can't be configured.", span).into()); - } - - let module_name = match module_alias.as_deref() { - Some("*") => { - self.global_scope.merge_module(module); - continue; - } - Some(..) => module_alias.unwrap(), - None => match module_name.as_ref() { - "sass:color" => "color".to_owned(), - "sass:list" => "list".to_owned(), - "sass:map" => "map".to_owned(), - "sass:math" => "math".to_owned(), - "sass:meta" => "meta".to_owned(), - "sass:selector" => "selector".to_owned(), - "sass:string" => "string".to_owned(), - _ => module_name.into_owned(), - }, - }; - - self.modules.insert(module_name.into(), module, span)?; - } - Some(Token { kind: '/', .. }) => { - self.toks.next(); - match self.parse_comment()?.node { - Comment::Silent => continue, - Comment::Loud(s) => comments.push(Stmt::Comment(s)), - } - } - Some(..) | None => break, - } - } - - self.toks.reset_cursor(); - - Ok(comments) - } - fn parse_stmt(&mut self) -> SassResult> { let mut stmts = Vec::new(); while let Some(Token { kind, pos }) = self.toks.peek() { diff --git a/src/parse/module.rs b/src/parse/module.rs new file mode 100644 index 0000000..0e97b0f --- /dev/null +++ b/src/parse/module.rs @@ -0,0 +1,251 @@ +use std::{convert::TryFrom, fs}; + +use codemap::Spanned; +use peekmore::PeekMore; + +use crate::{ + atrule::AtRuleKind, + builtin::modules::{ + declare_module_color, declare_module_list, declare_module_map, declare_module_math, + declare_module_meta, declare_module_selector, declare_module_string, Module, ModuleConfig, + }, + error::SassResult, + lexer::Lexer, + parse::{common::Comment, Parser, Stmt}, + scope::Scope, + utils::peek_ident_no_interpolation, + Token, +}; + +impl<'a> Parser<'a> { + fn parse_module_alias(&mut self) -> SassResult> { + if let Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) = self.toks.peek() { + let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?; + ident.node.make_ascii_lowercase(); + if ident.node != "as" { + return Err(("expected \";\".", ident.span).into()); + } + + self.whitespace_or_comment(); + + if let Some(Token { kind: '*', .. }) = self.toks.peek() { + self.toks.next(); + return Ok(Some('*'.to_string())); + } else { + let name = self.parse_identifier_no_interpolation(false)?; + + return Ok(Some(name.node)); + } + } + + Ok(None) + } + + fn parse_module_config(&mut self) -> SassResult { + let mut config = ModuleConfig::default(); + + if let Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. }) = self.toks.peek() { + let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?; + ident.node.make_ascii_lowercase(); + if ident.node != "with" { + return Err(("expected \";\".", ident.span).into()); + } + + self.whitespace_or_comment(); + + self.span_before = ident.span; + + self.expect_char('(')?; + + loop { + self.whitespace_or_comment(); + self.expect_char('$')?; + + let name = self.parse_identifier_no_interpolation(false)?; + + self.whitespace_or_comment(); + self.expect_char(':')?; + self.whitespace_or_comment(); + + let value = self.parse_value(false, &|toks| match toks.peek() { + Some(Token { kind: ',', .. }) | Some(Token { kind: ')', .. }) => true, + _ => false, + })?; + + config.insert(name.map_node(|n| n.into()), value)?; + + match self.toks.next() { + Some(Token { kind: ',', .. }) => { + continue; + } + Some(Token { kind: ')', .. }) => { + break; + } + Some(..) | None => { + return Err(("expected \")\".", self.span_before).into()); + } + } + } + } + + Ok(config) + } + + pub fn load_module( + &mut self, + name: &str, + config: &mut ModuleConfig, + ) -> SassResult<(Module, Vec)> { + Ok(match name { + "sass:color" => (declare_module_color(), Vec::new()), + "sass:list" => (declare_module_list(), Vec::new()), + "sass:map" => (declare_module_map(), Vec::new()), + "sass:math" => (declare_module_math(), Vec::new()), + "sass:meta" => (declare_module_meta(), Vec::new()), + "sass:selector" => (declare_module_selector(), Vec::new()), + "sass:string" => (declare_module_string(), Vec::new()), + _ => { + if let Some(import) = self.find_import(name.as_ref()) { + let mut global_scope = Scope::new(); + + let file = self + .map + .add_file(name.to_owned(), String::from_utf8(fs::read(&import)?)?); + + let stmts = Parser { + toks: &mut Lexer::new(&file) + .collect::>() + .into_iter() + .peekmore(), + map: self.map, + path: &import, + scopes: self.scopes, + global_scope: &mut global_scope, + super_selectors: self.super_selectors, + span_before: file.span.subspan(0, 0), + content: self.content, + flags: self.flags, + at_root: self.at_root, + at_root_has_selector: self.at_root_has_selector, + extender: self.extender, + content_scopes: self.content_scopes, + options: self.options, + modules: self.modules, + module_config: config, + } + .parse()?; + + if !config.is_empty() { + return Err(( + "This variable was not declared with !default in the @used module.", + self.span_before, + ) + .into()); + } + + (Module::new_from_scope(global_scope), stmts) + } else { + return Err(("Can't find stylesheet to import.", self.span_before).into()); + } + } + }) + } + + /// Returns any multiline comments that may have been found + /// while loading modules + pub(super) fn load_modules(&mut self) -> SassResult> { + let mut comments = Vec::new(); + + loop { + self.whitespace(); + match self.toks.peek() { + Some(Token { kind: '@', .. }) => { + self.toks.advance_cursor(); + + if let Some(Token { kind, .. }) = self.toks.peek() { + if !matches!(kind, 'a'..='z' | 'A'..='Z' | '\\') { + break; + } + } + + match AtRuleKind::try_from(&peek_ident_no_interpolation( + self.toks, + false, + self.span_before, + )?)? { + AtRuleKind::Use => { + self.toks.truncate_iterator_to_cursor(); + } + _ => { + break; + } + } + + self.whitespace_or_comment(); + + let quote = match self.toks.next() { + Some(Token { kind: q @ '"', .. }) | Some(Token { kind: q @ '\'', .. }) => q, + Some(..) | None => todo!(), + }; + + let Spanned { node: module, span } = self.parse_quoted_string(quote)?; + let module_name = module.unquote().to_css_string(span)?; + + self.whitespace_or_comment(); + + let module_alias = self.parse_module_alias()?; + + self.whitespace_or_comment(); + + let mut config = self.parse_module_config()?; + + self.whitespace_or_comment(); + self.expect_char(';')?; + + let (module, mut stmts) = + self.load_module(module_name.as_ref(), &mut config)?; + + comments.append(&mut stmts); + + // if the config isn't empty here, that means + // variables were passed to a builtin module + if !config.is_empty() { + return Err(("Built-in modules can't be configured.", span).into()); + } + + let module_name = match module_alias.as_deref() { + Some("*") => { + self.global_scope.merge_module(module); + continue; + } + Some(..) => module_alias.unwrap(), + None => match module_name.as_ref() { + "sass:color" => "color".to_owned(), + "sass:list" => "list".to_owned(), + "sass:map" => "map".to_owned(), + "sass:math" => "math".to_owned(), + "sass:meta" => "meta".to_owned(), + "sass:selector" => "selector".to_owned(), + "sass:string" => "string".to_owned(), + _ => module_name.into_owned(), + }, + }; + + self.modules.insert(module_name.into(), module, span)?; + } + Some(Token { kind: '/', .. }) => { + self.toks.next(); + match self.parse_comment()?.node { + Comment::Silent => continue, + Comment::Loud(s) => comments.push(Stmt::Comment(s)), + } + } + Some(..) | None => break, + } + } + + self.toks.reset_cursor(); + + Ok(comments) + } +} From 438abe52be8a3ae43ce6dfb3c9689a5eaab8ab29 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Fri, 7 Aug 2020 02:01:04 -0400 Subject: [PATCH 54/59] allow redeclaration of module variables --- src/builtin/modules/mod.rs | 88 +++++++++++++++++++++++++++++--------- src/parse/common.rs | 3 +- src/parse/mod.rs | 43 +++++++++++++++++++ src/parse/module.rs | 2 +- src/parse/style.rs | 79 +++++++++++++++++++++------------- src/parse/variable.rs | 10 ++--- src/scope.rs | 2 +- tests/use.rs | 63 +++++++++++++++++++++++++++ 8 files changed, 232 insertions(+), 58 deletions(-) diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index 1490adc..8a12a9d 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -22,7 +22,13 @@ mod selector; mod string; #[derive(Debug, Default)] -pub(crate) struct Module(pub Scope); +pub(crate) struct Module { + pub scope: Scope, + + /// Whether or not this module is builtin + /// e.g. `"sass:math"` + is_builtin: bool, +} #[derive(Debug, Default)] pub(crate) struct Modules(BTreeMap); @@ -83,9 +89,27 @@ impl Modules { .into()), } } + + pub fn get_mut(&mut self, name: Identifier, span: Span) -> SassResult<&mut Module> { + match self.0.get_mut(&name) { + Some(v) => Ok(v), + None => Err(( + format!( + "There is no module with the namespace \"{}\".", + name.as_str() + ), + span, + ) + .into()), + } + } } impl Module { + pub fn new_builtin() -> Self { + Module { scope: Scope::default(), is_builtin: true } + } + pub fn get_var(&self, name: Spanned) -> SassResult<&Value> { if name.node.as_str().starts_with('-') { return Err(( @@ -95,12 +119,36 @@ impl Module { .into()); } - match self.0.vars.get(&name.node) { + match self.scope.vars.get(&name.node) { Some(v) => Ok(v), None => Err(("Undefined variable.", name.span).into()), } } + pub fn update_var(&mut self, name: Spanned, value: Value) -> SassResult<()> { + if self.is_builtin { + return Err(( + "Cannot modify built-in variable.", + name.span, + ) + .into()); + } + + if name.node.as_str().starts_with('-') { + return Err(( + "Private members can't be accessed from outside their modules.", + name.span, + ) + .into()); + } + + if self.scope.insert_var(name.node, value).is_some() { + Ok(()) + } else { + Err(("Undefined variable.", name.span).into()) + } + } + pub fn get_mixin(&self, name: Spanned) -> SassResult { if name.node.as_str().starts_with('-') { return Err(( @@ -110,18 +158,18 @@ impl Module { .into()); } - match self.0.mixins.get(&name.node) { + match self.scope.mixins.get(&name.node) { Some(v) => Ok(v.clone()), None => Err(("Undefined mixin.", name.span).into()), } } pub fn insert_builtin_mixin(&mut self, name: &'static str, mixin: BuiltinMixin) { - self.0.mixins.insert(name.into(), Mixin::Builtin(mixin)); + self.scope.mixins.insert(name.into(), Mixin::Builtin(mixin)); } pub fn insert_builtin_var(&mut self, name: &'static str, value: Value) { - self.0.vars.insert(name.into(), value); + self.scope.vars.insert(name.into(), value); } pub fn get_fn(&self, name: Spanned) -> SassResult> { @@ -133,15 +181,15 @@ impl Module { .into()); } - Ok(self.0.functions.get(&name.node).cloned()) + Ok(self.scope.functions.get(&name.node).cloned()) } pub fn var_exists(&self, name: Identifier) -> bool { - !name.as_str().starts_with('-') && self.0.var_exists(name) + !name.as_str().starts_with('-') && self.scope.var_exists(name) } pub fn mixin_exists(&self, name: Identifier) -> bool { - !name.as_str().starts_with('-') && self.0.mixin_exists(name) + !name.as_str().starts_with('-') && self.scope.mixin_exists(name) } pub fn insert_builtin( @@ -150,14 +198,14 @@ impl Module { function: fn(CallArgs, &mut Parser<'_>) -> SassResult, ) { let ident = name.into(); - self.0 + self.scope .functions .insert(ident, SassFunction::Builtin(Builtin::new(function), ident)); } pub fn functions(&self) -> SassMap { SassMap::new_with( - self.0 + self.scope .functions .iter() .filter(|(key, _)| !key.as_str().starts_with('-')) @@ -173,7 +221,7 @@ impl Module { pub fn variables(&self) -> SassMap { SassMap::new_with( - self.0 + self.scope .vars .iter() .filter(|(key, _)| !key.as_str().starts_with('-')) @@ -187,49 +235,49 @@ impl Module { ) } - pub const fn new_from_scope(scope: Scope) -> Self { - Module(scope) + pub const fn new_from_scope(scope: Scope, is_builtin: bool) -> Self { + Module { scope, is_builtin } } } pub(crate) fn declare_module_color() -> Module { - let mut module = Module::default(); + let mut module = Module::new_builtin(); color::declare(&mut module); module } pub(crate) fn declare_module_list() -> Module { - let mut module = Module::default(); + let mut module = Module::new_builtin(); list::declare(&mut module); module } pub(crate) fn declare_module_map() -> Module { - let mut module = Module::default(); + let mut module = Module::new_builtin(); map::declare(&mut module); module } pub(crate) fn declare_module_math() -> Module { - let mut module = Module::default(); + let mut module = Module::new_builtin(); math::declare(&mut module); module } pub(crate) fn declare_module_meta() -> Module { - let mut module = Module::default(); + let mut module = Module::new_builtin(); meta::declare(&mut module); module } pub(crate) fn declare_module_selector() -> Module { - let mut module = Module::default(); + let mut module = Module::new_builtin(); selector::declare(&mut module); module } pub(crate) fn declare_module_string() -> Module { - let mut module = Module::default(); + let mut module = Module::new_builtin(); string::declare(&mut module); module } diff --git a/src/parse/common.rs b/src/parse/common.rs index fa724b8..d23514a 100644 --- a/src/parse/common.rs +++ b/src/parse/common.rs @@ -2,7 +2,7 @@ use std::ops::{BitAnd, BitOr}; use codemap::Spanned; -use crate::{interner::InternedString, value::Value}; +use crate::{common::Identifier, interner::InternedString, value::Value}; #[derive(Debug, Clone)] pub(crate) struct NeverEmptyVec { @@ -42,6 +42,7 @@ impl NeverEmptyVec { pub(super) enum SelectorOrStyle { Selector(String), Style(InternedString, Option>>), + ModuleVariableRedeclaration(Identifier), } #[derive(Debug, Copy, Clone)] diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 6b5ece3..6536748 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -11,6 +11,7 @@ use crate::{ AtRuleKind, SupportsRule, UnknownAtRule, }, builtin::modules::{ModuleConfig, Modules}, + common::Identifier, error::SassResult, scope::{Scope, Scopes}, selector::{ @@ -27,6 +28,7 @@ use crate::{ use common::{Comment, ContextFlags, NeverEmptyVec, SelectorOrStyle}; pub(crate) use value::{HigherIntermediateValue, ValueVisitor}; +use variable::VariableValue; mod args; pub mod common; @@ -131,6 +133,41 @@ impl<'a> Parser<'a> { } } + fn parse_module_variable_redeclaration(&mut self, module: Identifier) -> SassResult<()> { + let variable = self + .parse_identifier_no_interpolation(false)? + .map_node(|n| n.into()); + + self.whitespace_or_comment(); + self.expect_char(':')?; + + let VariableValue { + val_toks, + global, + default, + } = self.parse_variable_value()?; + + if global { + return Err(( + "!global isn't allowed for variables in other modules.", + variable.span, + ) + .into()); + } + + if default { + return Ok(()); + } + + let value = self.parse_value_from_vec(val_toks, true)?; + + self.modules + .get_mut(module, variable.span)? + .update_var(variable, value.node)?; + + Ok(()) + } + fn parse_stmt(&mut self) -> SassResult> { let mut stmts = Vec::new(); while let Some(Token { kind, pos }) = self.toks.peek() { @@ -294,6 +331,9 @@ impl<'a> Parser<'a> { } if self.flags.in_keyframes() { match self.is_selector_or_style()? { + SelectorOrStyle::ModuleVariableRedeclaration(module) => { + self.parse_module_variable_redeclaration(module)? + } SelectorOrStyle::Style(property, value) => { if let Some(value) = value { stmts.push(Stmt::Style(Style { property, value })); @@ -321,6 +361,9 @@ impl<'a> Parser<'a> { } match self.is_selector_or_style()? { + SelectorOrStyle::ModuleVariableRedeclaration(module) => { + self.parse_module_variable_redeclaration(module)? + } SelectorOrStyle::Style(property, value) => { if let Some(value) = value { stmts.push(Stmt::Style(Style { property, value })); diff --git a/src/parse/module.rs b/src/parse/module.rs index 0e97b0f..6032026 100644 --- a/src/parse/module.rs +++ b/src/parse/module.rs @@ -143,7 +143,7 @@ impl<'a> Parser<'a> { .into()); } - (Module::new_from_scope(global_scope), stmts) + (Module::new_from_scope(global_scope, false), stmts) } else { return Err(("Can't find stylesheet to import.", self.span_before).into()); } diff --git a/src/parse/style.rs b/src/parse/style.rs index 0c40a66..0e10bd1 100644 --- a/src/parse/style.rs +++ b/src/parse/style.rs @@ -111,43 +111,62 @@ impl<'a> Parser<'a> { let mut property = self.parse_identifier()?.node; let whitespace_after_property = self.whitespace(); - if let Some(Token { kind: ':', .. }) = self.toks.peek() { - self.toks.next(); - if let Some(Token { kind, .. }) = self.toks.peek() { - return Ok(match kind { - ':' => { - if whitespace_after_property { - property.push(' '); - } - property.push(':'); - SelectorOrStyle::Selector(property) - } - c if is_name(*c) => { - if let Some(toks) = self.parse_style_value_when_no_space_after_semicolon() { - let len = toks.len(); - if let Ok(val) = self.parse_value_from_vec(toks, false) { - self.toks.take(len).for_each(drop); - return Ok(SelectorOrStyle::Style( - InternedString::get_or_intern(property), - Some(Box::new(val)), - )); + match self.toks.peek() { + Some(Token { kind: ':', .. }) => { + self.toks.next(); + if let Some(Token { kind, .. }) = self.toks.peek() { + return Ok(match kind { + ':' => { + if whitespace_after_property { + property.push(' '); } + property.push(':'); + SelectorOrStyle::Selector(property) } + c if is_name(*c) => { + if let Some(toks) = + self.parse_style_value_when_no_space_after_semicolon() + { + let len = toks.len(); + if let Ok(val) = self.parse_value_from_vec(toks, false) { + self.toks.take(len).for_each(drop); + return Ok(SelectorOrStyle::Style( + InternedString::get_or_intern(property), + Some(Box::new(val)), + )); + } + } - if whitespace_after_property { - property.push(' '); + if whitespace_after_property { + property.push(' '); + } + property.push(':'); + return Ok(SelectorOrStyle::Selector(property)); } - property.push(':'); - return Ok(SelectorOrStyle::Selector(property)); + _ => SelectorOrStyle::Style(InternedString::get_or_intern(property), None), + }); + } + } + Some(Token { kind: '.', .. }) => { + if matches!(self.toks.peek_next(), Some(Token { kind: '$', .. })) { + self.toks.next(); + self.toks.next(); + return Ok(SelectorOrStyle::ModuleVariableRedeclaration( + property.into(), + )); + } else { + if whitespace_after_property { + property.push(' '); } - _ => SelectorOrStyle::Style(InternedString::get_or_intern(property), None), - }); + return Ok(SelectorOrStyle::Selector(property)); + } } - } else { - if whitespace_after_property { - property.push(' '); + _ => { + if whitespace_after_property { + property.push(' '); + } + return Ok(SelectorOrStyle::Selector(property)); } - return Ok(SelectorOrStyle::Selector(property)); } Err(("expected \"{\".", self.span_before).into()) } diff --git a/src/parse/variable.rs b/src/parse/variable.rs index 6a7429c..a5a77e4 100644 --- a/src/parse/variable.rs +++ b/src/parse/variable.rs @@ -8,10 +8,10 @@ use crate::{ use super::Parser; #[derive(Debug)] -struct VariableValue { - val_toks: Vec, - global: bool, - default: bool, +pub(crate) struct VariableValue { + pub val_toks: Vec, + pub global: bool, + pub default: bool, } impl VariableValue { @@ -88,7 +88,7 @@ impl<'a> Parser<'a> { Ok(()) } - fn parse_variable_value(&mut self) -> SassResult { + pub(super) fn parse_variable_value(&mut self) -> SassResult { let mut default = false; let mut global = false; diff --git a/src/scope.rs b/src/scope.rs index 82f27f0..dc968e2 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -81,7 +81,7 @@ impl Scope { } pub fn merge_module(&mut self, other: Module) { - self.merge(other.0); + self.merge(other.scope); } } diff --git a/tests/use.rs b/tests/use.rs index 035a86d..7c83b2e 100644 --- a/tests/use.rs +++ b/tests/use.rs @@ -253,3 +253,66 @@ fn use_with_same_variable_multiple_times() { input ); } + +#[test] +fn use_variable_redeclaration_var_dne() { + let input = "@use \"use_variable_redeclaration_var_dne\" as mod;\nmod.$a: red;"; + tempfile!("use_variable_redeclaration_var_dne.scss", ""); + + assert_err!("Error: Undefined variable.", input); +} + +#[test] +fn use_variable_redeclaration_global() { + let input = "@use \"use_variable_redeclaration_global\" as mod;\nmod.$a: red !global;"; + tempfile!("use_variable_redeclaration_global.scss", "$a: green;"); + + assert_err!( + "Error: !global isn't allowed for variables in other modules.", + input + ); +} + +#[test] +fn use_variable_redeclaration_simple() { + let input = + "@use \"use_variable_redeclaration_simple\" as mod;\nmod.$a: red; a { color: mod.$a; }"; + tempfile!("use_variable_redeclaration_simple.scss", "$a: green;"); + + assert_eq!( + "a {\n color: red;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) + ); +} + +#[test] +fn use_variable_redeclaration_default() { + let input = "@use \"use_variable_redeclaration_default\" as mod;\nmod.$a: 1 % red !default; a { color: mod.$a; }"; + tempfile!("use_variable_redeclaration_default.scss", "$a: green;"); + + assert_eq!( + "a {\n color: green;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) + ); +} + +#[test] +fn use_variable_redeclaration_private() { + let input = "@use \"use_variable_redeclaration_private\" as mod;\nmod.$-a: red;"; + tempfile!("use_variable_redeclaration_private.scss", "$a: green;"); + + assert_err!( + "Error: Private members can't be accessed from outside their modules.", + input + ); +} + +#[test] +fn use_variable_redeclaration_builtin() { + let input = "@use \"sass:math\";\nmath.$e: red;"; + + assert_err!( + "Error: Cannot modify built-in variable.", + input + ); +} From a7ccb4d6d36f1db2ee1e07fdb2d1414528041ae9 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Fri, 7 Aug 2020 02:03:43 -0400 Subject: [PATCH 55/59] move module variable parsing to module file --- src/parse/mod.rs | 36 ------------------------------------ src/parse/module.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 6536748..13c8a5e 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -11,7 +11,6 @@ use crate::{ AtRuleKind, SupportsRule, UnknownAtRule, }, builtin::modules::{ModuleConfig, Modules}, - common::Identifier, error::SassResult, scope::{Scope, Scopes}, selector::{ @@ -133,41 +132,6 @@ impl<'a> Parser<'a> { } } - fn parse_module_variable_redeclaration(&mut self, module: Identifier) -> SassResult<()> { - let variable = self - .parse_identifier_no_interpolation(false)? - .map_node(|n| n.into()); - - self.whitespace_or_comment(); - self.expect_char(':')?; - - let VariableValue { - val_toks, - global, - default, - } = self.parse_variable_value()?; - - if global { - return Err(( - "!global isn't allowed for variables in other modules.", - variable.span, - ) - .into()); - } - - if default { - return Ok(()); - } - - let value = self.parse_value_from_vec(val_toks, true)?; - - self.modules - .get_mut(module, variable.span)? - .update_var(variable, value.node)?; - - Ok(()) - } - fn parse_stmt(&mut self) -> SassResult> { let mut stmts = Vec::new(); while let Some(Token { kind, pos }) = self.toks.peek() { diff --git a/src/parse/module.rs b/src/parse/module.rs index 6032026..ab6924a 100644 --- a/src/parse/module.rs +++ b/src/parse/module.rs @@ -9,9 +9,10 @@ use crate::{ declare_module_color, declare_module_list, declare_module_map, declare_module_math, declare_module_meta, declare_module_selector, declare_module_string, Module, ModuleConfig, }, + common::Identifier, error::SassResult, lexer::Lexer, - parse::{common::Comment, Parser, Stmt}, + parse::{common::Comment, Parser, Stmt, VariableValue}, scope::Scope, utils::peek_ident_no_interpolation, Token, @@ -248,4 +249,42 @@ impl<'a> Parser<'a> { Ok(comments) } + + pub(super) fn parse_module_variable_redeclaration( + &mut self, + module: Identifier, + ) -> SassResult<()> { + let variable = self + .parse_identifier_no_interpolation(false)? + .map_node(|n| n.into()); + + self.whitespace_or_comment(); + self.expect_char(':')?; + + let VariableValue { + val_toks, + global, + default, + } = self.parse_variable_value()?; + + if global { + return Err(( + "!global isn't allowed for variables in other modules.", + variable.span, + ) + .into()); + } + + if default { + return Ok(()); + } + + let value = self.parse_value_from_vec(val_toks, true)?; + + self.modules + .get_mut(module, variable.span)? + .update_var(variable, value.node)?; + + Ok(()) + } } From 7a4a191d59d5b37f67e54cf6ab76cf6342337786 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Fri, 7 Aug 2020 02:10:51 -0400 Subject: [PATCH 56/59] allow variable declarations before and between `@use` --- src/parse/module.rs | 1 + tests/use.rs | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/parse/module.rs b/src/parse/module.rs index ab6924a..f6a056b 100644 --- a/src/parse/module.rs +++ b/src/parse/module.rs @@ -241,6 +241,7 @@ impl<'a> Parser<'a> { Comment::Loud(s) => comments.push(Stmt::Comment(s)), } } + Some(Token { kind: '$', .. }) => self.parse_variable_declaration()?, Some(..) | None => break, } } diff --git a/tests/use.rs b/tests/use.rs index 7c83b2e..0dca5c4 100644 --- a/tests/use.rs +++ b/tests/use.rs @@ -311,8 +311,23 @@ fn use_variable_redeclaration_private() { fn use_variable_redeclaration_builtin() { let input = "@use \"sass:math\";\nmath.$e: red;"; - assert_err!( - "Error: Cannot modify built-in variable.", - input + assert_err!("Error: Cannot modify built-in variable.", input); +} + +#[test] +fn use_variable_declaration_between_use() { + let input = r#" + $a: red; + $b: green; + @use "sass:math"; + $b: red; + @use "sass:meta"; + a { + color: $a $b; + }"#; + + assert_eq!( + "a {\n color: red red;\n}\n", + &grass::from_string(input.to_string(), &grass::Options::default()).expect(input) ); } From 399fe1d99ebf80630283d51cf73177f3e4510645 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Fri, 7 Aug 2020 02:12:13 -0400 Subject: [PATCH 57/59] rustfmt --- src/builtin/modules/mod.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index 8a12a9d..f9da0c6 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -107,7 +107,10 @@ impl Modules { impl Module { pub fn new_builtin() -> Self { - Module { scope: Scope::default(), is_builtin: true } + Module { + scope: Scope::default(), + is_builtin: true, + } } pub fn get_var(&self, name: Spanned) -> SassResult<&Value> { @@ -127,11 +130,7 @@ impl Module { pub fn update_var(&mut self, name: Spanned, value: Value) -> SassResult<()> { if self.is_builtin { - return Err(( - "Cannot modify built-in variable.", - name.span, - ) - .into()); + return Err(("Cannot modify built-in variable.", name.span).into()); } if name.node.as_str().starts_with('-') { From 10b333f66355885e90ff12ba814dcc624387923d Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Fri, 7 Aug 2020 02:24:17 -0400 Subject: [PATCH 58/59] update newly passing tests from module system --- CHANGELOG.md | 13 +++++++++++++ README.md | 7 +++++++ src/lib.rs | 4 ++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d861cea..36aa1d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# TBD + + - **implement `@use` and the module system** + - support the filter syntax for function arguments, e.g. `alpha(opacity=1)` + - disallow certain at-rules in functions, resolving several panics + - allow vendor-prefixed special CSS functions, e.g. `-webkit-calc(...)` + - allow decimal percent selectors inside `@keyframes` + - allow vendor-prefixed `@keyframes` + - resolve parsing bug for maps involving silent comments + - allow escaped `!` in selectors + - allow multiline comments in functions + - resolve several panics on malformed input when parsing bracketed lists + # 0.10.0 - bugfixes for `@media` query regressions diff --git a/README.md b/README.md index cf3ed26..fa25abf 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,13 @@ cargo b --release These numbers come from a default run of the Sass specification as shown above. +``` +2020-08-07 +PASSING: 3375 +FAILING: 1718 +TOTAL: 5093 +``` + ``` 2020-07-24 PASSING: 2935 diff --git a/src/lib.rs b/src/lib.rs index f691da6..4f29459 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,11 @@ /*! # grass An implementation of the Sass specification in pure rust. -Spec progress as of 2020-07-24: +Spec progress as of 2020-08-07: | Passing | Failing | Total | |---------|---------|-------| -| 2935 | 2158 | 5093 | +| 3375 | 1718 | 5093 | ## Use as library ``` From 882c84c2d8b5dab5a10400b557b85beffff5ed7e Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Fri, 7 Aug 2020 02:26:59 -0400 Subject: [PATCH 59/59] remove `@use` from remaining features --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa25abf..531cf72 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The large features remaining are ``` indented syntax css imports -@use, @forward, and the module system +@forward compressed output ```