add string interning

this seems to improve performance by ~20% as well as significantly
decrease memory usage
This commit is contained in:
ConnorSkees 2020-05-21 22:45:31 -04:00
parent 3e2689a4cb
commit 9a6c8ce019
24 changed files with 1195 additions and 445 deletions

View File

@ -49,6 +49,7 @@ rand = { version = "0.7.3", optional = true }
codemap = "0.1.3" codemap = "0.1.3"
peekmore = "0.4.0" peekmore = "0.4.0"
wasm-bindgen = { version = "0.2.60", optional = true } wasm-bindgen = { version = "0.2.60", optional = true }
lasso = "0.2.2"
[features] [features]
default = ["commandline", "random"] default = ["commandline", "random"]

View File

@ -42,7 +42,7 @@ impl Each {
if self.vars.len() == 1 { if self.vars.len() == 1 {
if this_iterator.len() == 1 { if this_iterator.len() == 1 {
scope.insert_var( scope.insert_var(
&self.vars[0], &self.vars[0].node,
Spanned { Spanned {
node: this_iterator[0].clone(), node: this_iterator[0].clone(),
span: self.vars[0].span, span: self.vars[0].span,
@ -50,7 +50,7 @@ impl Each {
)?; )?;
} else { } else {
scope.insert_var( scope.insert_var(
&self.vars[0], &self.vars[0].node,
Spanned { Spanned {
node: Value::List(this_iterator, ListSeparator::Space, Brackets::None), node: Value::List(this_iterator, ListSeparator::Space, Brackets::None),
span: self.vars[0].span, span: self.vars[0].span,

View File

@ -6,6 +6,7 @@ use crate::args::CallArgs;
use crate::color::Color; use crate::color::Color;
use crate::common::QuoteKind; use crate::common::QuoteKind;
use crate::error::SassResult; use crate::error::SassResult;
use crate::interner::InternedString;
use crate::scope::Scope; use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
use crate::unit::Unit; use crate::unit::Unit;
@ -107,7 +108,10 @@ fn inner_hsl(
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(
InternedString::get_or_intern(string),
QuoteKind::None,
));
} }
v => { v => {
return Err(( return Err((
@ -136,7 +140,10 @@ fn inner_hsl(
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(
InternedString::get_or_intern(string),
QuoteKind::None,
));
} }
v => { v => {
return Err(( return Err((
@ -167,7 +174,10 @@ fn inner_hsl(
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(
InternedString::get_or_intern(string),
QuoteKind::None,
));
} }
v => { v => {
return Err(( return Err((
@ -201,14 +211,14 @@ fn inner_hsl(
} }
v if v.is_special_function() => { v if v.is_special_function() => {
return Ok(Value::Ident( return Ok(Value::Ident(
format!( InternedString::get_or_intern(format!(
"{}({}, {}, {}, {})", "{}({}, {}, {}, {})",
name, name,
hue, hue,
saturation, saturation,
lightness, lightness,
v.to_css_string(args.span())? v.to_css_string(args.span())?
), )),
QuoteKind::None, QuoteKind::None,
)); ));
} }
@ -358,10 +368,10 @@ fn saturate(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sas
args.max_args(2)?; args.max_args(2)?;
if args.len() == 1 { if args.len() == 1 {
return Ok(Value::Ident( return Ok(Value::Ident(
format!( InternedString::get_or_intern(format!(
"saturate({})", "saturate({})",
arg!(args, scope, super_selector, 0, "amount").to_css_string(args.span())? arg!(args, scope, super_selector, 0, "amount").to_css_string(args.span())?
), )),
QuoteKind::None, QuoteKind::None,
)); ));
} }
@ -383,7 +393,7 @@ fn saturate(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sas
Value::Color(c) => c, Value::Color(c) => c,
Value::Dimension(n, u) => { Value::Dimension(n, u) => {
return Ok(Value::Ident( return Ok(Value::Ident(
format!("saturate({}{})", n, u), InternedString::get_or_intern(format!("saturate({}{})", n, u)),
QuoteKind::None, QuoteKind::None,
)) ))
} }
@ -432,7 +442,7 @@ fn grayscale(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
Value::Color(c) => c, Value::Color(c) => c,
Value::Dimension(n, u) => { Value::Dimension(n, u) => {
return Ok(Value::Ident( return Ok(Value::Ident(
format!("grayscale({}{})", n, u), InternedString::get_or_intern(format!("grayscale({}{})", n, u)),
QuoteKind::None, QuoteKind::None,
)) ))
} }
@ -485,9 +495,10 @@ fn invert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassR
}; };
match arg!(args, scope, super_selector, 0, "color") { match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Color(Box::new(c.invert(weight)))), Value::Color(c) => Ok(Value::Color(Box::new(c.invert(weight)))),
Value::Dimension(n, Unit::Percent) => { Value::Dimension(n, Unit::Percent) => Ok(Value::Ident(
Ok(Value::Ident(format!("invert({}%)", n), QuoteKind::None)) InternedString::get_or_intern(format!("invert({}%)", n)),
} QuoteKind::None,
)),
Value::Dimension(..) => Err(( Value::Dimension(..) => Err((
"Only one argument may be passed to the plain-CSS invert() function.", "Only one argument may be passed to the plain-CSS invert() function.",
args.span(), args.span(),

View File

@ -3,6 +3,7 @@ use super::{Builtin, GlobalFunctionMap};
use crate::args::CallArgs; use crate::args::CallArgs;
use crate::common::QuoteKind; use crate::common::QuoteKind;
use crate::error::SassResult; use crate::error::SassResult;
use crate::interner::InternedString;
use crate::scope::Scope; use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
use crate::unit::Unit; use crate::unit::Unit;
@ -26,7 +27,7 @@ fn opacity(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sass
match arg!(args, scope, super_selector, 0, "color") { match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)), Value::Color(c) => Ok(Value::Dimension(c.alpha(), Unit::None)),
Value::Dimension(num, unit) => Ok(Value::Ident( Value::Dimension(num, unit) => Ok(Value::Ident(
format!("opacity({}{})", num, unit), InternedString::get_or_intern(format!("opacity({}{})", num, unit)),
QuoteKind::None, QuoteKind::None,
)), )),
v => Err(( v => Err((

View File

@ -6,6 +6,7 @@ use crate::args::CallArgs;
use crate::color::Color; use crate::color::Color;
use crate::common::QuoteKind; use crate::common::QuoteKind;
use crate::error::SassResult; use crate::error::SassResult;
use crate::interner::InternedString;
use crate::scope::Scope; use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
use crate::unit::Unit; use crate::unit::Unit;
@ -46,13 +47,13 @@ fn inner_rgb(
let green = channels.pop().unwrap(); let green = channels.pop().unwrap();
let red = channels.pop().unwrap(); let red = channels.pop().unwrap();
return Ok(Value::Ident( return Ok(Value::Ident(
format!( InternedString::get_or_intern(format!(
"{}({}, {}, {})", "{}({}, {}, {})",
name, name,
red.to_css_string(args.span())?, red.to_css_string(args.span())?,
green.to_css_string(args.span())?, green.to_css_string(args.span())?,
v.to_css_string(args.span())? v.to_css_string(args.span())?
), )),
QuoteKind::None, QuoteKind::None,
)); ));
} }
@ -80,7 +81,10 @@ fn inner_rgb(
), ),
None => format!("{}({} {})", name, v.to_css_string(args.span())?, blue), None => format!("{}({} {})", name, v.to_css_string(args.span())?, blue),
}; };
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(
InternedString::get_or_intern(string),
QuoteKind::None,
));
} }
Some(v) => { Some(v) => {
return Err(( return Err((
@ -97,13 +101,13 @@ fn inner_rgb(
Some(Value::Dimension(n, Unit::Percent)) => (n / Number::from(100)) * Number::from(255), Some(Value::Dimension(n, Unit::Percent)) => (n / Number::from(100)) * Number::from(255),
Some(v) if v.is_special_function() => { Some(v) if v.is_special_function() => {
return Ok(Value::Ident( return Ok(Value::Ident(
format!( InternedString::get_or_intern(format!(
"{}({}, {}, {})", "{}({}, {}, {})",
name, name,
v.to_css_string(args.span())?, v.to_css_string(args.span())?,
green, green,
blue blue
), )),
QuoteKind::None, QuoteKind::None,
)); ));
} }
@ -126,12 +130,12 @@ fn inner_rgb(
v if v.is_special_function() => { v if v.is_special_function() => {
let alpha = arg!(args, scope, super_selector, 1, "alpha"); let alpha = arg!(args, scope, super_selector, 1, "alpha");
return Ok(Value::Ident( return Ok(Value::Ident(
format!( InternedString::get_or_intern(format!(
"{}({}, {})", "{}({}, {})",
name, name,
v.to_css_string(args.span())?, v.to_css_string(args.span())?,
alpha.to_css_string(args.span())? alpha.to_css_string(args.span())?
), )),
QuoteKind::None, QuoteKind::None,
)); ));
} }
@ -158,14 +162,14 @@ fn inner_rgb(
} }
v if v.is_special_function() => { v if v.is_special_function() => {
return Ok(Value::Ident( return Ok(Value::Ident(
format!( InternedString::get_or_intern(format!(
"{}({}, {}, {}, {})", "{}({}, {}, {}, {})",
name, name,
color.red(), color.red(),
color.green(), color.green(),
color.blue(), color.blue(),
v.to_css_string(args.span())? v.to_css_string(args.span())?
), )),
QuoteKind::None, QuoteKind::None,
)); ));
} }
@ -210,7 +214,10 @@ fn inner_rgb(
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(
InternedString::get_or_intern(string),
QuoteKind::None,
));
} }
v => { v => {
return Err(( return Err((
@ -250,7 +257,10 @@ fn inner_rgb(
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(
InternedString::get_or_intern(string),
QuoteKind::None,
));
} }
v => { v => {
return Err(( return Err((
@ -289,7 +299,10 @@ fn inner_rgb(
); );
} }
string.push(')'); string.push(')');
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(
InternedString::get_or_intern(string),
QuoteKind::None,
));
} }
v => { v => {
return Err(( return Err((
@ -327,7 +340,10 @@ fn inner_rgb(
blue, blue,
v.to_css_string(args.span())? v.to_css_string(args.span())?
); );
return Ok(Value::Ident(string, QuoteKind::None)); return Ok(Value::Ident(
InternedString::get_or_intern(string),
QuoteKind::None,
));
} }
v => { v => {
return Err(( return Err((

View File

@ -5,6 +5,7 @@ use num_traits::{One, Signed, ToPrimitive, Zero};
use crate::args::CallArgs; use crate::args::CallArgs;
use crate::common::{Brackets, ListSeparator, QuoteKind}; use crate::common::{Brackets, ListSeparator, QuoteKind};
use crate::error::SassResult; use crate::error::SassResult;
use crate::interner::InternedString;
use crate::scope::Scope; use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
use crate::unit::Unit; use crate::unit::Unit;
@ -72,11 +73,10 @@ fn list_separator(
) -> SassResult<Value> { ) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
Ok(Value::Ident( Ok(Value::Ident(
match arg!(args, scope, super_selector, 0, "list") { InternedString::get_or_intern(match arg!(args, scope, super_selector, 0, "list") {
Value::List(_, sep, ..) => sep.name(), Value::List(_, sep, ..) => sep.name(),
_ => ListSeparator::Space.name(), _ => ListSeparator::Space.name(),
} }),
.to_owned(),
QuoteKind::None, QuoteKind::None,
)) ))
} }
@ -140,9 +140,9 @@ fn append(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassR
scope, scope,
super_selector, super_selector,
2, 2,
"separator" = Value::Ident("auto".to_owned(), QuoteKind::None) "separator" = Value::Ident(InternedString::get_or_intern("auto"), QuoteKind::None)
) { ) {
Value::Ident(s, ..) => match s.as_str() { Value::Ident(s, ..) => match s.resolve().as_str() {
"auto" => sep, "auto" => sep,
"comma" => ListSeparator::Comma, "comma" => ListSeparator::Comma,
"space" => ListSeparator::Space, "space" => ListSeparator::Space,
@ -188,9 +188,9 @@ fn join(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRes
scope, scope,
super_selector, super_selector,
2, 2,
"separator" = Value::Ident("auto".to_owned(), QuoteKind::None) "separator" = Value::Ident(InternedString::get_or_intern("auto"), QuoteKind::None)
) { ) {
Value::Ident(s, ..) => match s.as_str() { Value::Ident(s, ..) => match s.resolve().as_str() {
"auto" => { "auto" => {
if list1.is_empty() || (list1.len() == 1 && sep1 == ListSeparator::Space) { if list1.is_empty() || (list1.len() == 1 && sep1 == ListSeparator::Space) {
sep2 sep2
@ -225,9 +225,9 @@ fn join(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRes
scope, scope,
super_selector, super_selector,
3, 3,
"bracketed" = Value::Ident("auto".to_owned(), QuoteKind::None) "bracketed" = Value::Ident(InternedString::get_or_intern("auto"), QuoteKind::None)
) { ) {
Value::Ident(s, ..) => match s.as_str() { Value::Ident(s, ..) => match s.resolve().as_str() {
"auto" => brackets, "auto" => brackets,
_ => Brackets::Bracketed, _ => Brackets::Bracketed,
}, },

View File

@ -5,6 +5,7 @@ use codemap::Spanned;
use crate::args::CallArgs; use crate::args::CallArgs;
use crate::common::QuoteKind; use crate::common::QuoteKind;
use crate::error::SassResult; use crate::error::SassResult;
use crate::interner::InternedString;
use crate::scope::global_var_exists; use crate::scope::global_var_exists;
use crate::scope::Scope; use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
@ -27,7 +28,7 @@ fn feature_exists(
) -> SassResult<Value> { ) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "feature") { match arg!(args, scope, super_selector, 0, "feature") {
Value::Ident(s, _) => Ok(match s.as_str() { Value::Ident(s, _) => Ok(match s.resolve().as_str() {
// A local variable will shadow a global variable unless // A local variable will shadow a global variable unless
// `!global` is used. // `!global` is used.
"global-variable-shadowing" => Value::True, "global-variable-shadowing" => Value::True,
@ -71,14 +72,17 @@ fn unit(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassRes
.into()) .into())
} }
}; };
Ok(Value::Ident(unit, QuoteKind::Quoted)) Ok(Value::Ident(
InternedString::get_or_intern(unit),
QuoteKind::Quoted,
))
} }
fn type_of(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { fn type_of(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
let value = arg!(args, scope, super_selector, 0, "value"); let value = arg!(args, scope, super_selector, 0, "value");
Ok(Value::Ident( Ok(Value::Ident(
value.kind(args.span())?.to_owned(), InternedString::get_or_intern(value.kind(args.span())?),
QuoteKind::None, QuoteKind::None,
)) ))
} }
@ -95,9 +99,9 @@ fn unitless(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sas
fn inspect(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { fn inspect(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
Ok(Value::Ident( Ok(Value::Ident(
arg!(args, scope, super_selector, 0, "value") InternedString::get_or_intern(
.inspect(args.span())? arg!(args, scope, super_selector, 0, "value").inspect(args.span())?,
.into(), ),
QuoteKind::None, QuoteKind::None,
)) ))
} }
@ -109,7 +113,7 @@ fn variable_exists(
) -> SassResult<Value> { ) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "name") { match arg!(args, scope, super_selector, 0, "name") {
Value::Ident(s, _) => Ok(Value::bool(scope.var_exists(&s))), Value::Ident(s, _) => Ok(Value::bool(scope.var_exists(s))),
v => Err(( v => Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?), format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(), args.span(),
@ -125,7 +129,7 @@ fn global_variable_exists(
) -> SassResult<Value> { ) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "name") { match arg!(args, scope, super_selector, 0, "name") {
Value::Ident(s, _) => Ok(Value::bool(global_var_exists(&s))), Value::Ident(s, _) => Ok(Value::bool(global_var_exists(s))),
v => Err(( v => Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?), format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(), args.span(),
@ -137,7 +141,7 @@ fn global_variable_exists(
fn mixin_exists(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { fn mixin_exists(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(2)?; args.max_args(2)?;
match arg!(args, scope, super_selector, 0, "name") { match arg!(args, scope, super_selector, 0, "name") {
Value::Ident(s, _) => Ok(Value::bool(scope.mixin_exists(&s))), Value::Ident(s, _) => Ok(Value::bool(scope.mixin_exists(s))),
v => Err(( v => Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?), format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(), args.span(),
@ -153,9 +157,7 @@ fn function_exists(
) -> SassResult<Value> { ) -> SassResult<Value> {
args.max_args(2)?; args.max_args(2)?;
match arg!(args, scope, super_selector, 0, "name") { match arg!(args, scope, super_selector, 0, "name") {
Value::Ident(s, _) => Ok(Value::bool( Value::Ident(s, _) => Ok(Value::bool(scope.fn_exists(s))),
scope.fn_exists(&s) || GLOBAL_FUNCTIONS.contains_key(s.as_str()),
)),
v => Err(( v => Err((
format!("$name: {} is not a string.", v.to_css_string(args.span())?), format!("$name: {} is not a string.", v.to_css_string(args.span())?),
args.span(), args.span(),
@ -201,12 +203,12 @@ fn get_function(mut args: CallArgs, scope: &Scope, super_selector: &Selector) ->
} }
let func = match scope.get_fn(Spanned { let func = match scope.get_fn(Spanned {
node: name.clone(), node: name,
span: args.span(), span: args.span(),
}) { }) {
Ok(f) => SassFunction::UserDefined(Box::new(f), name), Ok(f) => SassFunction::UserDefined(Box::new(f), name.into()),
Err(..) => match GLOBAL_FUNCTIONS.get(name.as_str()) { Err(..) => match GLOBAL_FUNCTIONS.get(&name.resolve().as_str()) {
Some(f) => SassFunction::Builtin(f.clone(), name), Some(f) => SassFunction::Builtin(f.clone(), name.into()),
None => return Err((format!("Function not found: {}", name), args.span()).into()), None => return Err((format!("Function not found: {}", name), args.span()).into()),
}, },
}; };

View File

@ -9,6 +9,7 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng};
use crate::args::CallArgs; use crate::args::CallArgs;
use crate::common::QuoteKind; use crate::common::QuoteKind;
use crate::error::SassResult; use crate::error::SassResult;
use crate::interner::{keywords::EMPTY_STRING, InternedString};
use crate::scope::Scope; use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
use crate::unit::Unit; use crate::unit::Unit;
@ -21,9 +22,10 @@ fn to_upper_case(
) -> SassResult<Value> { ) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "string") { match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(mut i, q) => { Value::Ident(i, q) => {
let mut i = i.resolve();
i.make_ascii_uppercase(); i.make_ascii_uppercase();
Ok(Value::Ident(i, q)) Ok(Value::Ident(InternedString::get_or_intern(i), q))
} }
v => Err(( v => Err((
format!( format!(
@ -43,9 +45,10 @@ fn to_lower_case(
) -> SassResult<Value> { ) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "string") { match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(mut i, q) => { Value::Ident(i, q) => {
let mut i = i.resolve();
i.make_ascii_lowercase(); i.make_ascii_lowercase();
Ok(Value::Ident(i, q)) Ok(Value::Ident(InternedString::get_or_intern(i), q))
} }
v => Err(( v => Err((
format!( format!(
@ -62,7 +65,7 @@ fn str_length(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
args.max_args(1)?; args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "string") { match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(i, _) => Ok(Value::Dimension( Value::Ident(i, _) => Ok(Value::Dimension(
Number::from(i.chars().count()), Number::from(i.resolve().chars().count()),
Unit::None, Unit::None,
)), )),
v => Err(( v => Err((
@ -121,7 +124,7 @@ fn str_slice(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
.into()) .into())
} }
}; };
let str_len = string.chars().count(); let str_len = string.resolve().chars().count();
let start = match arg!(args, scope, super_selector, 1, "start-at") { let start = match arg!(args, scope, super_selector, 1, "start-at") {
Value::Dimension(n, Unit::None) if n.is_decimal() => { Value::Dimension(n, Unit::None) if n.is_decimal() => {
return Err((format!("{} is not an int.", n), args.span()).into()) return Err((format!("{} is not an int.", n), args.span()).into())
@ -195,14 +198,17 @@ fn str_slice(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
} }
if start > end || start > str_len { if start > end || start > str_len {
Ok(Value::Ident(String::new(), quotes)) Ok(Value::Ident(*EMPTY_STRING, quotes))
} else { } else {
Ok(Value::Ident( Ok(Value::Ident(
string InternedString::get_or_intern(
.chars() string
.skip(start - 1) .resolve()
.take(end - start + 1) .chars()
.collect(), .skip(start - 1)
.take(end - start + 1)
.collect::<String>(),
),
quotes, quotes,
)) ))
} }
@ -211,7 +217,7 @@ fn str_slice(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
fn str_index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { fn str_index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(2)?; args.max_args(2)?;
let s1 = match arg!(args, scope, super_selector, 0, "string") { let s1 = match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(i, _) => i, Value::Ident(i, _) => i.resolve(),
v => { v => {
return Err(( return Err((
format!( format!(
@ -225,7 +231,7 @@ fn str_index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
}; };
let substr = match arg!(args, scope, super_selector, 1, "substring") { let substr = match arg!(args, scope, super_selector, 1, "substring") {
Value::Ident(i, _) => i, Value::Ident(i, _) => i.resolve(),
v => { v => {
return Err(( return Err((
format!( format!(
@ -247,7 +253,7 @@ fn str_index(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> Sa
fn str_insert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> { fn str_insert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> SassResult<Value> {
args.max_args(3)?; args.max_args(3)?;
let (s1, quotes) = match arg!(args, scope, super_selector, 0, "string") { let (s1, quotes) = match arg!(args, scope, super_selector, 0, "string") {
Value::Ident(i, q) => (i, q), Value::Ident(i, q) => (i.resolve().to_string(), q),
v => { v => {
return Err(( return Err((
format!( format!(
@ -261,7 +267,7 @@ fn str_insert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
}; };
let substr = match arg!(args, scope, super_selector, 1, "insert") { let substr = match arg!(args, scope, super_selector, 1, "insert") {
Value::Ident(i, _) => i, Value::Ident(i, _) => i.resolve().to_string(),
v => { v => {
return Err(( return Err((
format!( format!(
@ -299,7 +305,7 @@ fn str_insert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
}; };
if s1.is_empty() { if s1.is_empty() {
return Ok(Value::Ident(substr, quotes)); return Ok(Value::Ident(InternedString::get_or_intern(substr), quotes));
} }
let len = s1.chars().count(); let len = s1.chars().count();
@ -342,18 +348,21 @@ fn str_insert(mut args: CallArgs, scope: &Scope, super_selector: &Selector) -> S
} }
}; };
Ok(Value::Ident(string, quotes)) Ok(Value::Ident(InternedString::get_or_intern(string), quotes))
} }
#[cfg(feature = "random")] #[cfg(feature = "random")]
fn unique_id(args: CallArgs, _: &Scope, _: &Selector) -> SassResult<Value> { fn unique_id(args: CallArgs, _: &Scope, _: &Selector) -> SassResult<Value> {
args.max_args(0)?; args.max_args(0)?;
let mut rng = thread_rng(); let mut rng = thread_rng();
let string = std::iter::repeat(()) let string: String = std::iter::repeat(())
.map(|()| rng.sample(Alphanumeric)) .map(|()| rng.sample(Alphanumeric))
.take(7) .take(7)
.collect(); .collect();
Ok(Value::Ident(string, QuoteKind::None)) Ok(Value::Ident(
InternedString::get_or_intern(string),
QuoteKind::None,
))
} }
pub(crate) fn declare(f: &mut GlobalFunctionMap) { pub(crate) fn declare(f: &mut GlobalFunctionMap) {

View File

@ -17,6 +17,7 @@
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use crate::interner::InternedString;
use crate::value::Number; use crate::value::Number;
pub(crate) use name::NAMED_COLORS; pub(crate) use name::NAMED_COLORS;
@ -468,14 +469,14 @@ impl Color {
/// Other color functions /// Other color functions
impl Color { impl Color {
pub fn to_ie_hex_str(&self) -> String { pub fn to_ie_hex_str(&self) -> InternedString {
format!( InternedString::get_or_intern(format!(
"#{:X}{:X}{:X}{:X}", "#{:X}{:X}{:X}{:X}",
(self.alpha() * Number::from(255)).round().to_integer(), (self.alpha() * Number::from(255)).round().to_integer(),
self.red().to_integer(), self.red().to_integer(),
self.green().to_integer(), self.green().to_integer(),
self.blue().to_integer() self.blue().to_integer()
) ))
} }
} }

View File

@ -4,9 +4,11 @@
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::collections::HashMap; use std::collections::HashMap;
use crate::interner::InternedString;
pub(crate) struct NamedColorMap { pub(crate) struct NamedColorMap {
name_to_rgba: HashMap<&'static str, [u8; 4]>, name_to_rgba: HashMap<InternedString, [u8; 4]>,
rgba_to_name: HashMap<[u8; 4], &'static str>, rgba_to_name: HashMap<[u8; 4], InternedString>,
} }
impl NamedColorMap { impl NamedColorMap {
@ -17,161 +19,581 @@ impl NamedColorMap {
} }
} }
pub fn insert(&mut self, name: &'static str, rgba: [u8; 4]) { fn insert(&mut self, name: InternedString, rgba: [u8; 4]) {
self.name_to_rgba.insert(name, rgba); self.name_to_rgba.insert(name, rgba);
self.rgba_to_name.insert(rgba, name); self.rgba_to_name.insert(rgba, name);
} }
pub fn get_by_name(&self, name: &str) -> Option<&[u8; 4]> { pub fn get_by_name(&self, name: InternedString) -> Option<&[u8; 4]> {
self.name_to_rgba.get(name) self.name_to_rgba.get(&name)
} }
pub fn get_by_rgba(&self, rgba: [u8; 4]) -> Option<&&str> { pub fn get_by_rgba(&self, rgba: [u8; 4]) -> Option<&InternedString> {
self.rgba_to_name.get(&rgba) self.rgba_to_name.get(&rgba)
} }
} }
pub(crate) static NAMED_COLORS: Lazy<NamedColorMap> = Lazy::new(|| { pub(crate) static NAMED_COLORS: Lazy<NamedColorMap> = Lazy::new(|| {
let mut m = NamedColorMap::with_capacity(150); let mut m = NamedColorMap::with_capacity(150);
m.insert("aliceblue", [0xF0, 0xF8, 0xFF, 0xFF]); m.insert(
m.insert("antiquewhite", [0xFA, 0xEB, 0xD7, 0xFF]); InternedString::get_or_intern("aliceblue"),
m.insert("aqua", [0x00, 0xFF, 0xFF, 0xFF]); [0xF0, 0xF8, 0xFF, 0xFF],
m.insert("aquamarine", [0x7F, 0xFF, 0xD4, 0xFF]); );
m.insert("azure", [0xF0, 0xFF, 0xFF, 0xFF]); m.insert(
m.insert("beige", [0xF5, 0xF5, 0xDC, 0xFF]); InternedString::get_or_intern("antiquewhite"),
m.insert("bisque", [0xFF, 0xE4, 0xC4, 0xFF]); [0xFA, 0xEB, 0xD7, 0xFF],
m.insert("black", [0x00, 0x00, 0x00, 0xFF]); );
m.insert("blanchedalmond", [0xFF, 0xEB, 0xCD, 0xFF]); m.insert(
m.insert("blue", [0x00, 0x00, 0xFF, 0xFF]); InternedString::get_or_intern("aqua"),
m.insert("blueviolet", [0x8A, 0x2B, 0xE2, 0xFF]); [0x00, 0xFF, 0xFF, 0xFF],
m.insert("brown", [0xA5, 0x2A, 0x2A, 0xFF]); );
m.insert("burlywood", [0xDE, 0xB8, 0x87, 0xFF]); m.insert(
m.insert("cadetblue", [0x5F, 0x9E, 0xA0, 0xFF]); InternedString::get_or_intern("aquamarine"),
m.insert("chartreuse", [0x7F, 0xFF, 0x00, 0xFF]); [0x7F, 0xFF, 0xD4, 0xFF],
m.insert("chocolate", [0xD2, 0x69, 0x1E, 0xFF]); );
m.insert("coral", [0xFF, 0x7F, 0x50, 0xFF]); m.insert(
m.insert("cornflowerblue", [0x64, 0x95, 0xED, 0xFF]); InternedString::get_or_intern("azure"),
m.insert("cornsilk", [0xFF, 0xF8, 0xDC, 0xFF]); [0xF0, 0xFF, 0xFF, 0xFF],
m.insert("crimson", [0xDC, 0x14, 0x3C, 0xFF]); );
m.insert("darkblue", [0x00, 0x00, 0x8B, 0xFF]); m.insert(
m.insert("darkcyan", [0x00, 0x8B, 0x8B, 0xFF]); InternedString::get_or_intern("beige"),
m.insert("darkgoldenrod", [0xB8, 0x86, 0x0B, 0xFF]); [0xF5, 0xF5, 0xDC, 0xFF],
m.insert("darkgray", [0xA9, 0xA9, 0xA9, 0xFF]); );
m.insert("darkgreen", [0x00, 0x64, 0x00, 0xFF]); m.insert(
m.insert("darkkhaki", [0xBD, 0xB7, 0x6B, 0xFF]); InternedString::get_or_intern("bisque"),
m.insert("darkmagenta", [0x8B, 0x00, 0x8B, 0xFF]); [0xFF, 0xE4, 0xC4, 0xFF],
m.insert("darkolivegreen", [0x55, 0x6B, 0x2F, 0xFF]); );
m.insert("darkorange", [0xFF, 0x8C, 0x00, 0xFF]); m.insert(
m.insert("darkorchid", [0x99, 0x32, 0xCC, 0xFF]); InternedString::get_or_intern("black"),
m.insert("darkred", [0x8B, 0x00, 0x00, 0xFF]); [0x00, 0x00, 0x00, 0xFF],
m.insert("darksalmon", [0xE9, 0x96, 0x7A, 0xFF]); );
m.insert("darkseagreen", [0x8F, 0xBC, 0x8F, 0xFF]); m.insert(
m.insert("darkslateblue", [0x48, 0x3D, 0x8B, 0xFF]); InternedString::get_or_intern("blanchedalmond"),
m.insert("darkslategray", [0x2F, 0x4F, 0x4F, 0xFF]); [0xFF, 0xEB, 0xCD, 0xFF],
m.insert("darkturquoise", [0x00, 0xCE, 0xD1, 0xFF]); );
m.insert("darkviolet", [0x94, 0x00, 0xD3, 0xFF]); m.insert(
m.insert("deeppink", [0xFF, 0x14, 0x93, 0xFF]); InternedString::get_or_intern("blue"),
m.insert("deepskyblue", [0x00, 0xBF, 0xFF, 0xFF]); [0x00, 0x00, 0xFF, 0xFF],
m.insert("dimgray", [0x69, 0x69, 0x69, 0xFF]); );
m.insert("dodgerblue", [0x1E, 0x90, 0xFF, 0xFF]); m.insert(
m.insert("firebrick", [0xB2, 0x22, 0x22, 0xFF]); InternedString::get_or_intern("blueviolet"),
m.insert("floralwhite", [0xFF, 0xFA, 0xF0, 0xFF]); [0x8A, 0x2B, 0xE2, 0xFF],
m.insert("forestgreen", [0x22, 0x8B, 0x22, 0xFF]); );
m.insert("fuchsia", [0xFF, 0x00, 0xFF, 0xFF]); m.insert(
m.insert("gainsboro", [0xDC, 0xDC, 0xDC, 0xFF]); InternedString::get_or_intern("brown"),
m.insert("ghostwhite", [0xF8, 0xF8, 0xFF, 0xFF]); [0xA5, 0x2A, 0x2A, 0xFF],
m.insert("gold", [0xFF, 0xD7, 0x00, 0xFF]); );
m.insert("goldenrod", [0xDA, 0xA5, 0x20, 0xFF]); m.insert(
m.insert("gray", [0x80, 0x80, 0x80, 0xFF]); InternedString::get_or_intern("burlywood"),
m.insert("green", [0x00, 0x80, 0x00, 0xFF]); [0xDE, 0xB8, 0x87, 0xFF],
m.insert("greenyellow", [0xAD, 0xFF, 0x2F, 0xFF]); );
m.insert("honeydew", [0xF0, 0xFF, 0xF0, 0xFF]); m.insert(
m.insert("hotpink", [0xFF, 0x69, 0xB4, 0xFF]); InternedString::get_or_intern("cadetblue"),
m.insert("indianred", [0xCD, 0x5C, 0x5C, 0xFF]); [0x5F, 0x9E, 0xA0, 0xFF],
m.insert("indigo", [0x4B, 0x00, 0x82, 0xFF]); );
m.insert("ivory", [0xFF, 0xFF, 0xF0, 0xFF]); m.insert(
m.insert("khaki", [0xF0, 0xE6, 0x8C, 0xFF]); InternedString::get_or_intern("chartreuse"),
m.insert("lavender", [0xE6, 0xE6, 0xFA, 0xFF]); [0x7F, 0xFF, 0x00, 0xFF],
m.insert("lavenderblush", [0xFF, 0xF0, 0xF5, 0xFF]); );
m.insert("lawngreen", [0x7C, 0xFC, 0x00, 0xFF]); m.insert(
m.insert("lemonchiffon", [0xFF, 0xFA, 0xCD, 0xFF]); InternedString::get_or_intern("chocolate"),
m.insert("lightblue", [0xAD, 0xD8, 0xE6, 0xFF]); [0xD2, 0x69, 0x1E, 0xFF],
m.insert("lightcoral", [0xF0, 0x80, 0x80, 0xFF]); );
m.insert("lightcyan", [0xE0, 0xFF, 0xFF, 0xFF]); m.insert(
m.insert("lightgoldenrodyellow", [0xFA, 0xFA, 0xD2, 0xFF]); InternedString::get_or_intern("coral"),
m.insert("lightgray", [0xD3, 0xD3, 0xD3, 0xFF]); [0xFF, 0x7F, 0x50, 0xFF],
m.insert("lightgreen", [0x90, 0xEE, 0x90, 0xFF]); );
m.insert("lightpink", [0xFF, 0xB6, 0xC1, 0xFF]); m.insert(
m.insert("lightsalmon", [0xFF, 0xA0, 0x7A, 0xFF]); InternedString::get_or_intern("cornflowerblue"),
m.insert("lightseagreen", [0x20, 0xB2, 0xAA, 0xFF]); [0x64, 0x95, 0xED, 0xFF],
m.insert("lightskyblue", [0x87, 0xCE, 0xFA, 0xFF]); );
m.insert("lightslategray", [0x77, 0x88, 0x99, 0xFF]); m.insert(
m.insert("lightsteelblue", [0xB0, 0xC4, 0xDE, 0xFF]); InternedString::get_or_intern("cornsilk"),
m.insert("lightyellow", [0xFF, 0xFF, 0xE0, 0xFF]); [0xFF, 0xF8, 0xDC, 0xFF],
m.insert("lime", [0x00, 0xFF, 0x00, 0xFF]); );
m.insert("limegreen", [0x32, 0xCD, 0x32, 0xFF]); m.insert(
m.insert("linen", [0xFA, 0xF0, 0xE6, 0xFF]); InternedString::get_or_intern("crimson"),
m.insert("maroon", [0x80, 0x00, 0x00, 0xFF]); [0xDC, 0x14, 0x3C, 0xFF],
m.insert("mediumaquamarine", [0x66, 0xCD, 0xAA, 0xFF]); );
m.insert("mediumblue", [0x00, 0x00, 0xCD, 0xFF]); m.insert(
m.insert("mediumorchid", [0xBA, 0x55, 0xD3, 0xFF]); InternedString::get_or_intern("darkblue"),
m.insert("mediumpurple", [0x93, 0x70, 0xDB, 0xFF]); [0x00, 0x00, 0x8B, 0xFF],
m.insert("mediumseagreen", [0x3C, 0xB3, 0x71, 0xFF]); );
m.insert("mediumslateblue", [0x7B, 0x68, 0xEE, 0xFF]); m.insert(
m.insert("mediumspringgreen", [0x00, 0xFA, 0x9A, 0xFF]); InternedString::get_or_intern("darkcyan"),
m.insert("mediumturquoise", [0x48, 0xD1, 0xCC, 0xFF]); [0x00, 0x8B, 0x8B, 0xFF],
m.insert("mediumvioletred", [0xC7, 0x15, 0x85, 0xFF]); );
m.insert("midnightblue", [0x19, 0x19, 0x70, 0xFF]); m.insert(
m.insert("mintcream", [0xF5, 0xFF, 0xFA, 0xFF]); InternedString::get_or_intern("darkgoldenrod"),
m.insert("mistyrose", [0xFF, 0xE4, 0xE1, 0xFF]); [0xB8, 0x86, 0x0B, 0xFF],
m.insert("moccasin", [0xFF, 0xE4, 0xB5, 0xFF]); );
m.insert("navajowhite", [0xFF, 0xDE, 0xAD, 0xFF]); m.insert(
m.insert("navy", [0x00, 0x00, 0x80, 0xFF]); InternedString::get_or_intern("darkgray"),
m.insert("oldlace", [0xFD, 0xF5, 0xE6, 0xFF]); [0xA9, 0xA9, 0xA9, 0xFF],
m.insert("olive", [0x80, 0x80, 0x00, 0xFF]); );
m.insert("olivedrab", [0x6B, 0x8E, 0x23, 0xFF]); m.insert(
m.insert("orange", [0xFF, 0xA5, 0x00, 0xFF]); InternedString::get_or_intern("darkgreen"),
m.insert("orangered", [0xFF, 0x45, 0x00, 0xFF]); [0x00, 0x64, 0x00, 0xFF],
m.insert("orchid", [0xDA, 0x70, 0xD6, 0xFF]); );
m.insert("palegoldenrod", [0xEE, 0xE8, 0xAA, 0xFF]); m.insert(
m.insert("palegreen", [0x98, 0xFB, 0x98, 0xFF]); InternedString::get_or_intern("darkkhaki"),
m.insert("paleturquoise", [0xAF, 0xEE, 0xEE, 0xFF]); [0xBD, 0xB7, 0x6B, 0xFF],
m.insert("palevioletred", [0xDB, 0x70, 0x93, 0xFF]); );
m.insert("papayawhip", [0xFF, 0xEF, 0xD5, 0xFF]); m.insert(
m.insert("peachpuff", [0xFF, 0xDA, 0xB9, 0xFF]); InternedString::get_or_intern("darkmagenta"),
m.insert("peru", [0xCD, 0x85, 0x3F, 0xFF]); [0x8B, 0x00, 0x8B, 0xFF],
m.insert("pink", [0xFF, 0xC0, 0xCB, 0xFF]); );
m.insert("plum", [0xDD, 0xA0, 0xDD, 0xFF]); m.insert(
m.insert("powderblue", [0xB0, 0xE0, 0xE6, 0xFF]); InternedString::get_or_intern("darkolivegreen"),
m.insert("purple", [0x80, 0x00, 0x80, 0xFF]); [0x55, 0x6B, 0x2F, 0xFF],
m.insert("rebeccapurple", [0x66, 0x33, 0x99, 0xFF]); );
m.insert("red", [0xFF, 0x00, 0x00, 0xFF]); m.insert(
m.insert("rosybrown", [0xBC, 0x8F, 0x8F, 0xFF]); InternedString::get_or_intern("darkorange"),
m.insert("royalblue", [0x41, 0x69, 0xE1, 0xFF]); [0xFF, 0x8C, 0x00, 0xFF],
m.insert("saddlebrown", [0x8B, 0x45, 0x13, 0xFF]); );
m.insert("salmon", [0xFA, 0x80, 0x72, 0xFF]); m.insert(
m.insert("sandybrown", [0xF4, 0xA4, 0x60, 0xFF]); InternedString::get_or_intern("darkorchid"),
m.insert("seagreen", [0x2E, 0x8B, 0x57, 0xFF]); [0x99, 0x32, 0xCC, 0xFF],
m.insert("seashell", [0xFF, 0xF5, 0xEE, 0xFF]); );
m.insert("sienna", [0xA0, 0x52, 0x2D, 0xFF]); m.insert(
m.insert("silver", [0xC0, 0xC0, 0xC0, 0xFF]); InternedString::get_or_intern("darkred"),
m.insert("skyblue", [0x87, 0xCE, 0xEB, 0xFF]); [0x8B, 0x00, 0x00, 0xFF],
m.insert("slateblue", [0x6A, 0x5A, 0xCD, 0xFF]); );
m.insert("slategray", [0x70, 0x80, 0x90, 0xFF]); m.insert(
m.insert("snow", [0xFF, 0xFA, 0xFA, 0xFF]); InternedString::get_or_intern("darksalmon"),
m.insert("springgreen", [0x00, 0xFF, 0x7F, 0xFF]); [0xE9, 0x96, 0x7A, 0xFF],
m.insert("steelblue", [0x46, 0x82, 0xB4, 0xFF]); );
m.insert("tan", [0xD2, 0xB4, 0x8C, 0xFF]); m.insert(
m.insert("teal", [0x00, 0x80, 0x80, 0xFF]); InternedString::get_or_intern("darkseagreen"),
m.insert("thistle", [0xD8, 0xBF, 0xD8, 0xFF]); [0x8F, 0xBC, 0x8F, 0xFF],
m.insert("tomato", [0xFF, 0x63, 0x47, 0xFF]); );
m.insert("turquoise", [0x40, 0xE0, 0xD0, 0xFF]); m.insert(
m.insert("violet", [0xEE, 0x82, 0xEE, 0xFF]); InternedString::get_or_intern("darkslateblue"),
m.insert("wheat", [0xF5, 0xDE, 0xB3, 0xFF]); [0x48, 0x3D, 0x8B, 0xFF],
m.insert("white", [0xFF, 0xFF, 0xFF, 0xFF]); );
m.insert("whitesmoke", [0xF5, 0xF5, 0xF5, 0xFF]); m.insert(
m.insert("yellow", [0xFF, 0xFF, 0x00, 0xFF]); InternedString::get_or_intern("darkslategray"),
m.insert("yellowgreen", [0x9A, 0xCD, 0x32, 0xFF]); [0x2F, 0x4F, 0x4F, 0xFF],
m.insert("transparent", [0x00, 0x00, 0x00, 0x00]); );
m.insert(
InternedString::get_or_intern("darkturquoise"),
[0x00, 0xCE, 0xD1, 0xFF],
);
m.insert(
InternedString::get_or_intern("darkviolet"),
[0x94, 0x00, 0xD3, 0xFF],
);
m.insert(
InternedString::get_or_intern("deeppink"),
[0xFF, 0x14, 0x93, 0xFF],
);
m.insert(
InternedString::get_or_intern("deepskyblue"),
[0x00, 0xBF, 0xFF, 0xFF],
);
m.insert(
InternedString::get_or_intern("dimgray"),
[0x69, 0x69, 0x69, 0xFF],
);
m.insert(
InternedString::get_or_intern("dodgerblue"),
[0x1E, 0x90, 0xFF, 0xFF],
);
m.insert(
InternedString::get_or_intern("firebrick"),
[0xB2, 0x22, 0x22, 0xFF],
);
m.insert(
InternedString::get_or_intern("floralwhite"),
[0xFF, 0xFA, 0xF0, 0xFF],
);
m.insert(
InternedString::get_or_intern("forestgreen"),
[0x22, 0x8B, 0x22, 0xFF],
);
m.insert(
InternedString::get_or_intern("fuchsia"),
[0xFF, 0x00, 0xFF, 0xFF],
);
m.insert(
InternedString::get_or_intern("gainsboro"),
[0xDC, 0xDC, 0xDC, 0xFF],
);
m.insert(
InternedString::get_or_intern("ghostwhite"),
[0xF8, 0xF8, 0xFF, 0xFF],
);
m.insert(
InternedString::get_or_intern("gold"),
[0xFF, 0xD7, 0x00, 0xFF],
);
m.insert(
InternedString::get_or_intern("goldenrod"),
[0xDA, 0xA5, 0x20, 0xFF],
);
m.insert(
InternedString::get_or_intern("gray"),
[0x80, 0x80, 0x80, 0xFF],
);
m.insert(
InternedString::get_or_intern("green"),
[0x00, 0x80, 0x00, 0xFF],
);
m.insert(
InternedString::get_or_intern("greenyellow"),
[0xAD, 0xFF, 0x2F, 0xFF],
);
m.insert(
InternedString::get_or_intern("honeydew"),
[0xF0, 0xFF, 0xF0, 0xFF],
);
m.insert(
InternedString::get_or_intern("hotpink"),
[0xFF, 0x69, 0xB4, 0xFF],
);
m.insert(
InternedString::get_or_intern("indianred"),
[0xCD, 0x5C, 0x5C, 0xFF],
);
m.insert(
InternedString::get_or_intern("indigo"),
[0x4B, 0x00, 0x82, 0xFF],
);
m.insert(
InternedString::get_or_intern("ivory"),
[0xFF, 0xFF, 0xF0, 0xFF],
);
m.insert(
InternedString::get_or_intern("khaki"),
[0xF0, 0xE6, 0x8C, 0xFF],
);
m.insert(
InternedString::get_or_intern("lavender"),
[0xE6, 0xE6, 0xFA, 0xFF],
);
m.insert(
InternedString::get_or_intern("lavenderblush"),
[0xFF, 0xF0, 0xF5, 0xFF],
);
m.insert(
InternedString::get_or_intern("lawngreen"),
[0x7C, 0xFC, 0x00, 0xFF],
);
m.insert(
InternedString::get_or_intern("lemonchiffon"),
[0xFF, 0xFA, 0xCD, 0xFF],
);
m.insert(
InternedString::get_or_intern("lightblue"),
[0xAD, 0xD8, 0xE6, 0xFF],
);
m.insert(
InternedString::get_or_intern("lightcoral"),
[0xF0, 0x80, 0x80, 0xFF],
);
m.insert(
InternedString::get_or_intern("lightcyan"),
[0xE0, 0xFF, 0xFF, 0xFF],
);
m.insert(
InternedString::get_or_intern("lightgoldenrodyellow"),
[0xFA, 0xFA, 0xD2, 0xFF],
);
m.insert(
InternedString::get_or_intern("lightgray"),
[0xD3, 0xD3, 0xD3, 0xFF],
);
m.insert(
InternedString::get_or_intern("lightgreen"),
[0x90, 0xEE, 0x90, 0xFF],
);
m.insert(
InternedString::get_or_intern("lightpink"),
[0xFF, 0xB6, 0xC1, 0xFF],
);
m.insert(
InternedString::get_or_intern("lightsalmon"),
[0xFF, 0xA0, 0x7A, 0xFF],
);
m.insert(
InternedString::get_or_intern("lightseagreen"),
[0x20, 0xB2, 0xAA, 0xFF],
);
m.insert(
InternedString::get_or_intern("lightskyblue"),
[0x87, 0xCE, 0xFA, 0xFF],
);
m.insert(
InternedString::get_or_intern("lightslategray"),
[0x77, 0x88, 0x99, 0xFF],
);
m.insert(
InternedString::get_or_intern("lightsteelblue"),
[0xB0, 0xC4, 0xDE, 0xFF],
);
m.insert(
InternedString::get_or_intern("lightyellow"),
[0xFF, 0xFF, 0xE0, 0xFF],
);
m.insert(
InternedString::get_or_intern("lime"),
[0x00, 0xFF, 0x00, 0xFF],
);
m.insert(
InternedString::get_or_intern("limegreen"),
[0x32, 0xCD, 0x32, 0xFF],
);
m.insert(
InternedString::get_or_intern("linen"),
[0xFA, 0xF0, 0xE6, 0xFF],
);
m.insert(
InternedString::get_or_intern("maroon"),
[0x80, 0x00, 0x00, 0xFF],
);
m.insert(
InternedString::get_or_intern("mediumaquamarine"),
[0x66, 0xCD, 0xAA, 0xFF],
);
m.insert(
InternedString::get_or_intern("mediumblue"),
[0x00, 0x00, 0xCD, 0xFF],
);
m.insert(
InternedString::get_or_intern("mediumorchid"),
[0xBA, 0x55, 0xD3, 0xFF],
);
m.insert(
InternedString::get_or_intern("mediumpurple"),
[0x93, 0x70, 0xDB, 0xFF],
);
m.insert(
InternedString::get_or_intern("mediumseagreen"),
[0x3C, 0xB3, 0x71, 0xFF],
);
m.insert(
InternedString::get_or_intern("mediumslateblue"),
[0x7B, 0x68, 0xEE, 0xFF],
);
m.insert(
InternedString::get_or_intern("mediumspringgreen"),
[0x00, 0xFA, 0x9A, 0xFF],
);
m.insert(
InternedString::get_or_intern("mediumturquoise"),
[0x48, 0xD1, 0xCC, 0xFF],
);
m.insert(
InternedString::get_or_intern("mediumvioletred"),
[0xC7, 0x15, 0x85, 0xFF],
);
m.insert(
InternedString::get_or_intern("midnightblue"),
[0x19, 0x19, 0x70, 0xFF],
);
m.insert(
InternedString::get_or_intern("mintcream"),
[0xF5, 0xFF, 0xFA, 0xFF],
);
m.insert(
InternedString::get_or_intern("mistyrose"),
[0xFF, 0xE4, 0xE1, 0xFF],
);
m.insert(
InternedString::get_or_intern("moccasin"),
[0xFF, 0xE4, 0xB5, 0xFF],
);
m.insert(
InternedString::get_or_intern("navajowhite"),
[0xFF, 0xDE, 0xAD, 0xFF],
);
m.insert(
InternedString::get_or_intern("navy"),
[0x00, 0x00, 0x80, 0xFF],
);
m.insert(
InternedString::get_or_intern("oldlace"),
[0xFD, 0xF5, 0xE6, 0xFF],
);
m.insert(
InternedString::get_or_intern("olive"),
[0x80, 0x80, 0x00, 0xFF],
);
m.insert(
InternedString::get_or_intern("olivedrab"),
[0x6B, 0x8E, 0x23, 0xFF],
);
m.insert(
InternedString::get_or_intern("orange"),
[0xFF, 0xA5, 0x00, 0xFF],
);
m.insert(
InternedString::get_or_intern("orangered"),
[0xFF, 0x45, 0x00, 0xFF],
);
m.insert(
InternedString::get_or_intern("orchid"),
[0xDA, 0x70, 0xD6, 0xFF],
);
m.insert(
InternedString::get_or_intern("palegoldenrod"),
[0xEE, 0xE8, 0xAA, 0xFF],
);
m.insert(
InternedString::get_or_intern("palegreen"),
[0x98, 0xFB, 0x98, 0xFF],
);
m.insert(
InternedString::get_or_intern("paleturquoise"),
[0xAF, 0xEE, 0xEE, 0xFF],
);
m.insert(
InternedString::get_or_intern("palevioletred"),
[0xDB, 0x70, 0x93, 0xFF],
);
m.insert(
InternedString::get_or_intern("papayawhip"),
[0xFF, 0xEF, 0xD5, 0xFF],
);
m.insert(
InternedString::get_or_intern("peachpuff"),
[0xFF, 0xDA, 0xB9, 0xFF],
);
m.insert(
InternedString::get_or_intern("peru"),
[0xCD, 0x85, 0x3F, 0xFF],
);
m.insert(
InternedString::get_or_intern("pink"),
[0xFF, 0xC0, 0xCB, 0xFF],
);
m.insert(
InternedString::get_or_intern("plum"),
[0xDD, 0xA0, 0xDD, 0xFF],
);
m.insert(
InternedString::get_or_intern("powderblue"),
[0xB0, 0xE0, 0xE6, 0xFF],
);
m.insert(
InternedString::get_or_intern("purple"),
[0x80, 0x00, 0x80, 0xFF],
);
m.insert(
InternedString::get_or_intern("rebeccapurple"),
[0x66, 0x33, 0x99, 0xFF],
);
m.insert(
InternedString::get_or_intern("red"),
[0xFF, 0x00, 0x00, 0xFF],
);
m.insert(
InternedString::get_or_intern("rosybrown"),
[0xBC, 0x8F, 0x8F, 0xFF],
);
m.insert(
InternedString::get_or_intern("royalblue"),
[0x41, 0x69, 0xE1, 0xFF],
);
m.insert(
InternedString::get_or_intern("saddlebrown"),
[0x8B, 0x45, 0x13, 0xFF],
);
m.insert(
InternedString::get_or_intern("salmon"),
[0xFA, 0x80, 0x72, 0xFF],
);
m.insert(
InternedString::get_or_intern("sandybrown"),
[0xF4, 0xA4, 0x60, 0xFF],
);
m.insert(
InternedString::get_or_intern("seagreen"),
[0x2E, 0x8B, 0x57, 0xFF],
);
m.insert(
InternedString::get_or_intern("seashell"),
[0xFF, 0xF5, 0xEE, 0xFF],
);
m.insert(
InternedString::get_or_intern("sienna"),
[0xA0, 0x52, 0x2D, 0xFF],
);
m.insert(
InternedString::get_or_intern("silver"),
[0xC0, 0xC0, 0xC0, 0xFF],
);
m.insert(
InternedString::get_or_intern("skyblue"),
[0x87, 0xCE, 0xEB, 0xFF],
);
m.insert(
InternedString::get_or_intern("slateblue"),
[0x6A, 0x5A, 0xCD, 0xFF],
);
m.insert(
InternedString::get_or_intern("slategray"),
[0x70, 0x80, 0x90, 0xFF],
);
m.insert(
InternedString::get_or_intern("snow"),
[0xFF, 0xFA, 0xFA, 0xFF],
);
m.insert(
InternedString::get_or_intern("springgreen"),
[0x00, 0xFF, 0x7F, 0xFF],
);
m.insert(
InternedString::get_or_intern("steelblue"),
[0x46, 0x82, 0xB4, 0xFF],
);
m.insert(
InternedString::get_or_intern("tan"),
[0xD2, 0xB4, 0x8C, 0xFF],
);
m.insert(
InternedString::get_or_intern("teal"),
[0x00, 0x80, 0x80, 0xFF],
);
m.insert(
InternedString::get_or_intern("thistle"),
[0xD8, 0xBF, 0xD8, 0xFF],
);
m.insert(
InternedString::get_or_intern("tomato"),
[0xFF, 0x63, 0x47, 0xFF],
);
m.insert(
InternedString::get_or_intern("turquoise"),
[0x40, 0xE0, 0xD0, 0xFF],
);
m.insert(
InternedString::get_or_intern("violet"),
[0xEE, 0x82, 0xEE, 0xFF],
);
m.insert(
InternedString::get_or_intern("wheat"),
[0xF5, 0xDE, 0xB3, 0xFF],
);
m.insert(
InternedString::get_or_intern("white"),
[0xFF, 0xFF, 0xFF, 0xFF],
);
m.insert(
InternedString::get_or_intern("whitesmoke"),
[0xF5, 0xF5, 0xF5, 0xFF],
);
m.insert(
InternedString::get_or_intern("yellow"),
[0xFF, 0xFF, 0x00, 0xFF],
);
m.insert(
InternedString::get_or_intern("yellowgreen"),
[0x9A, 0xCD, 0x32, 0xFF],
);
m.insert(
InternedString::get_or_intern("transparent"),
[0x00, 0x00, 0x00, 0x00],
);
m m
}); });

View File

@ -1,3 +1,4 @@
use crate::interner::InternedString;
use std::fmt::{self, Display, Write}; use std::fmt::{self, Display, Write};
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
@ -119,3 +120,38 @@ impl Display for QualifiedName {
f.write_str(&self.ident) f.write_str(&self.ident)
} }
} }
#[derive(Debug, Clone, Hash, Eq, PartialEq, Copy)]
pub(crate) struct Identifier(InternedString);
impl Into<Identifier> for InternedString {
fn into(self) -> Identifier {
Identifier(InternedString::get_or_intern(
self.resolve().replace('_', "-"),
))
}
}
impl From<String> for Identifier {
fn from(s: String) -> Identifier {
Identifier(InternedString::get_or_intern(s.replace('_', "-")))
}
}
impl Into<Identifier> for &String {
fn into(self) -> Identifier {
Identifier(InternedString::get_or_intern(self.replace('_', "-")))
}
}
impl Into<Identifier> for &str {
fn into(self) -> Identifier {
Identifier(InternedString::get_or_intern(self.replace('_', "-")))
}
}
impl Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}

55
src/interner.rs Normal file
View File

@ -0,0 +1,55 @@
use lasso::{Rodeo, Spur};
use std::cell::RefCell;
use std::fmt::{self, Display};
thread_local!(static STRINGS: RefCell<Rodeo<Spur>> = RefCell::new(Rodeo::default()));
use keywords::EMPTY_STRING;
pub(crate) mod keywords {
use super::InternedString;
use once_cell::sync::Lazy;
macro_rules! keyword {
($ident:ident, $val:literal) => {
pub(crate) static $ident: Lazy<InternedString> =
Lazy::new(|| InternedString::get_or_intern($val));
};
}
keyword!(EMPTY_STRING, "");
keyword!(TRUE, "true");
keyword!(FALSE, "false");
keyword!(AND, "and");
keyword!(OR, "or");
keyword!(NOT, "not");
keyword!(NULL, "null");
keyword!(CALC, "calc");
keyword!(URL, "url");
keyword!(PROGID, "progid");
keyword!(ELEMENT, "element");
keyword!(EXPRESSION, "expression");
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct InternedString(Spur);
impl InternedString {
pub fn get_or_intern<T: AsRef<str>>(s: T) -> Self {
Self(STRINGS.with(|interner| interner.borrow_mut().get_or_intern(s)))
}
pub fn resolve(&self) -> String {
STRINGS.with(|interner| interner.borrow().resolve(&self.0).to_string())
}
pub fn is_empty(self) -> bool {
self == *EMPTY_STRING
}
}
impl Display for InternedString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.resolve())
}
}

View File

@ -90,6 +90,7 @@ use peekmore::{PeekMore, PeekMoreIterator};
use crate::atrule::{AtRule, AtRuleKind, Function, Mixin}; use crate::atrule::{AtRule, AtRuleKind, Function, Mixin};
pub use crate::error::{SassError, SassResult}; pub use crate::error::{SassError, SassResult};
use crate::interner::keywords::EMPTY_STRING;
use crate::scope::{insert_global_var, Scope}; use crate::scope::{insert_global_var, Scope};
use crate::selector::Selector; use crate::selector::Selector;
use crate::style::Style; use crate::style::Style;
@ -109,6 +110,7 @@ mod color;
mod common; mod common;
mod error; mod error;
mod imports; mod imports;
mod interner;
mod lexer; mod lexer;
mod output; mod output;
mod scope; mod scope;
@ -198,7 +200,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
&mut values.into_iter().peekmore(), &mut values.into_iter().peekmore(),
scope, scope,
super_selector, super_selector,
String::new(), *EMPTY_STRING,
tok.pos, tok.pos,
)?; )?;
return Ok(Some(Spanned { return Ok(Some(Spanned {
@ -218,7 +220,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
devour_whitespace(toks); devour_whitespace(toks);
return Ok(Some(Spanned { return Ok(Some(Spanned {
node: Expr::Style(Box::new(Style { node: Expr::Style(Box::new(Style {
property: String::new(), property: *EMPTY_STRING,
value: Value::Null.span(span), value: Value::Null.span(span),
})), })),
span, span,
@ -228,7 +230,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
&mut v, &mut v,
scope, scope,
super_selector, super_selector,
String::new(), *EMPTY_STRING,
span_before, span_before,
)?; )?;
let value = Style::parse_value(&mut v, scope, super_selector)?; let value = Style::parse_value(&mut v, scope, super_selector)?;
@ -255,7 +257,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
&mut v, &mut v,
scope, scope,
super_selector, super_selector,
String::new(), *EMPTY_STRING,
tok.pos, tok.pos,
)?; )?;
let value = Style::parse_value(&mut v, scope, super_selector)?; let value = Style::parse_value(&mut v, scope, super_selector)?;

View File

@ -4,66 +4,70 @@ use std::collections::HashMap;
use codemap::Spanned; use codemap::Spanned;
use crate::atrule::{Function, Mixin}; use crate::atrule::{Function, Mixin};
use crate::common::Identifier;
use crate::error::SassResult; use crate::error::SassResult;
use crate::value::Value; use crate::value::Value;
thread_local!(pub(crate) static GLOBAL_SCOPE: RefCell<Scope> = RefCell::new(Scope::new())); thread_local!(pub(crate) static GLOBAL_SCOPE: RefCell<Scope> = RefCell::new(Scope::new()));
pub(crate) fn get_global_var(s: Spanned<String>) -> SassResult<Spanned<Value>> { pub(crate) fn get_global_var<T: Into<Identifier>>(s: Spanned<T>) -> SassResult<Spanned<Value>> {
GLOBAL_SCOPE.with(|scope| match scope.borrow().vars().get(&s.node) { GLOBAL_SCOPE.with(|scope| match scope.borrow().vars().get(&s.node.into()) {
Some(v) => Ok(v.clone()), Some(v) => Ok(v.clone()),
None => Err(("Undefined variable.", s.span).into()), None => Err(("Undefined variable.", s.span).into()),
}) })
} }
pub(crate) fn global_var_exists(v: &str) -> bool { /// Returns true if a variable exists in the *global* scope
GLOBAL_SCOPE.with(|scope| scope.borrow().vars().contains_key(&v.replace('_', "-"))) pub(crate) fn global_var_exists<T: Into<Identifier>>(v: T) -> bool {
GLOBAL_SCOPE.with(|scope| scope.borrow().vars().contains_key(&v.into()))
} }
pub(crate) fn insert_global_var(s: &str, v: Spanned<Value>) -> SassResult<Option<Spanned<Value>>> { pub(crate) fn insert_global_var<T: Into<Identifier>>(
GLOBAL_SCOPE.with(|scope| scope.borrow_mut().insert_var(s, v)) s: T,
v: Spanned<Value>,
) -> SassResult<Option<Spanned<Value>>> {
GLOBAL_SCOPE.with(|scope| scope.borrow_mut().insert_var(s.into(), v))
} }
pub(crate) fn get_global_fn(s: Spanned<String>) -> SassResult<Function> { pub(crate) fn get_global_fn<T: Into<Identifier>>(s: Spanned<T>) -> SassResult<Function> {
GLOBAL_SCOPE.with(|scope| match scope.borrow().functions().get(&s.node) { GLOBAL_SCOPE.with(
Some(v) => Ok(v.clone()), |scope| match scope.borrow().functions().get(&s.node.into()) {
None => Err(("Undefined function.", s.span).into()), Some(v) => Ok(v.clone()),
}) None => Err(("Undefined function.", s.span).into()),
},
)
} }
pub(crate) fn global_fn_exists(v: &str) -> bool { /// Returns true if a function exists in the *global* scope
GLOBAL_SCOPE.with(|scope| { pub(crate) fn global_fn_exists<T: Into<Identifier>>(v: T) -> bool {
scope GLOBAL_SCOPE.with(|scope| scope.borrow().functions().contains_key(&v.into()))
.borrow()
.functions()
.contains_key(&v.replace('_', "-"))
})
} }
pub(crate) fn insert_global_fn(s: &str, v: Function) -> Option<Function> { pub(crate) fn insert_global_fn<T: Into<Identifier>>(s: T, v: Function) -> Option<Function> {
GLOBAL_SCOPE.with(|scope| scope.borrow_mut().insert_fn(s, v)) GLOBAL_SCOPE.with(|scope| scope.borrow_mut().insert_fn(s.into(), v))
} }
pub(crate) fn get_global_mixin(s: Spanned<String>) -> SassResult<Mixin> { pub(crate) fn get_global_mixin<T: Into<Identifier>>(s: Spanned<T>) -> SassResult<Mixin> {
GLOBAL_SCOPE.with(|scope| match scope.borrow().mixins().get(&s.node) { GLOBAL_SCOPE.with(|scope| match scope.borrow().mixins().get(&s.node.into()) {
Some(v) => Ok(v.clone()), Some(v) => Ok(v.clone()),
None => Err(("Undefined mixin.", s.span).into()), None => Err(("Undefined mixin.", s.span).into()),
}) })
} }
pub(crate) fn global_mixin_exists(v: &str) -> bool { /// Returns true if a mixin exists in the *global* scope
GLOBAL_SCOPE.with(|scope| scope.borrow().mixins().contains_key(&v.replace('_', "-"))) pub(crate) fn global_mixin_exists<T: Into<Identifier>>(v: T) -> bool {
GLOBAL_SCOPE.with(|scope| scope.borrow().mixins().contains_key(&v.into()))
} }
pub(crate) fn insert_global_mixin(s: &str, v: Mixin) -> Option<Mixin> { pub(crate) fn insert_global_mixin<T: Into<Identifier>>(s: T, v: Mixin) -> Option<Mixin> {
GLOBAL_SCOPE.with(|scope| scope.borrow_mut().insert_mixin(s, v)) GLOBAL_SCOPE.with(|scope| scope.borrow_mut().insert_mixin(s.into(), v))
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct Scope { pub(crate) struct Scope {
vars: HashMap<String, Spanned<Value>>, vars: HashMap<Identifier, Spanned<Value>>,
mixins: HashMap<String, Mixin>, mixins: HashMap<Identifier, Mixin>,
functions: HashMap<String, Function>, functions: HashMap<Identifier, Function>,
} }
impl Scope { impl Scope {
@ -76,68 +80,72 @@ impl Scope {
} }
} }
pub const fn vars(&self) -> &HashMap<String, Spanned<Value>> { pub const fn vars(&self) -> &HashMap<Identifier, Spanned<Value>> {
&self.vars &self.vars
} }
pub const fn functions(&self) -> &HashMap<String, Function> { pub const fn functions(&self) -> &HashMap<Identifier, Function> {
&self.functions &self.functions
} }
pub const fn mixins(&self) -> &HashMap<String, Mixin> { pub const fn mixins(&self) -> &HashMap<Identifier, Mixin> {
&self.mixins &self.mixins
} }
pub fn get_var(&self, mut name: Spanned<String>) -> SassResult<Spanned<Value>> { pub fn get_var<T: Into<Identifier>>(&self, name: Spanned<T>) -> SassResult<Spanned<Value>> {
name.node = name.node.replace('_', "-"); let name = name.map_node(|n| n.into());
match self.vars.get(&name.node) { match self.vars.get(&name.node) {
Some(v) => Ok(v.clone()), Some(v) => Ok(v.clone()),
None => get_global_var(name), None => get_global_var(name),
} }
} }
pub fn insert_var(&mut self, s: &str, v: Spanned<Value>) -> SassResult<Option<Spanned<Value>>> { pub fn insert_var<T: Into<Identifier>>(
&mut self,
s: T,
v: Spanned<Value>,
) -> SassResult<Option<Spanned<Value>>> {
let Spanned { node, span } = v; let Spanned { node, span } = v;
Ok(self.vars.insert(s.replace('_', "-"), node.eval(span)?)) Ok(self.vars.insert(s.into(), node.eval(span)?))
} }
pub fn var_exists(&self, v: &str) -> bool { pub fn var_exists<T: Into<Identifier>>(&self, v: T) -> bool {
let name = &v.replace('_', "-"); let name = v.into();
self.vars.contains_key(name) || global_var_exists(name) self.vars.contains_key(&name) || global_var_exists(name)
} }
pub fn get_mixin(&self, mut name: Spanned<String>) -> SassResult<Mixin> { pub fn get_mixin<T: Into<Identifier>>(&self, name: Spanned<T>) -> SassResult<Mixin> {
name.node = name.node.replace('_', "-"); let name = name.map_node(|n| n.into());
match self.mixins.get(&name.node) { match self.mixins.get(&name.node) {
Some(v) => Ok(v.clone()), Some(v) => Ok(v.clone()),
None => get_global_mixin(name), None => get_global_mixin(name),
} }
} }
pub fn insert_mixin(&mut self, s: &str, v: Mixin) -> Option<Mixin> { pub fn insert_mixin<T: Into<Identifier>>(&mut self, s: T, v: Mixin) -> Option<Mixin> {
self.mixins.insert(s.replace('_', "-"), v) self.mixins.insert(s.into(), v)
} }
pub fn mixin_exists(&self, v: &str) -> bool { pub fn mixin_exists<T: Into<Identifier>>(&self, v: T) -> bool {
let name = &v.replace('_', "-"); let name = v.into();
self.mixins.contains_key(name) || global_mixin_exists(name) self.mixins.contains_key(&name) || global_mixin_exists(name)
} }
pub fn get_fn(&self, mut name: Spanned<String>) -> SassResult<Function> { pub fn get_fn<T: Into<Identifier>>(&self, name: Spanned<T>) -> SassResult<Function> {
name.node = name.node.replace('_', "-"); let name = name.map_node(|n| n.into());
match self.functions.get(&name.node) { match self.functions.get(&name.node) {
Some(v) => Ok(v.clone()), Some(v) => Ok(v.clone()),
None => get_global_fn(name), None => get_global_fn(name),
} }
} }
pub fn insert_fn(&mut self, s: &str, v: Function) -> Option<Function> { pub fn insert_fn<T: Into<Identifier>>(&mut self, s: T, v: Function) -> Option<Function> {
self.functions.insert(s.replace('_', "-"), v) self.functions.insert(s.into(), v)
} }
pub fn fn_exists(&self, v: &str) -> bool { pub fn fn_exists<T: Into<Identifier>>(&self, v: T) -> bool {
let name = &v.replace('_', "-"); let name = v.into();
self.functions.contains_key(name) || global_fn_exists(name) self.functions.contains_key(&name) || global_fn_exists(name)
} }
pub fn extend(&mut self, other: Scope) { pub fn extend(&mut self, other: Scope) {

View File

@ -7,6 +7,7 @@ use codemap::Span;
use super::{Selector, SelectorKind}; use super::{Selector, SelectorKind};
use crate::common::{QualifiedName, QuoteKind}; use crate::common::{QualifiedName, QuoteKind};
use crate::error::SassResult; use crate::error::SassResult;
use crate::interner::InternedString;
use crate::scope::Scope; use crate::scope::Scope;
use crate::utils::{devour_whitespace, eat_ident, is_ident, parse_quoted_string}; use crate::utils::{devour_whitespace, eat_ident, is_ident, parse_quoted_string};
use crate::value::Value; use crate::value::Value;
@ -124,7 +125,7 @@ impl Attribute {
q @ '\'' | q @ '"' => { q @ '\'' | q @ '"' => {
toks.next(); toks.next();
match parse_quoted_string(toks, scope, q, super_selector)?.node { match parse_quoted_string(toks, scope, q, super_selector)?.node {
Value::Ident(s, ..) => s, Value::Ident(s, ..) => s.resolve().to_string(),
_ => unreachable!(), _ => unreachable!(),
} }
} }
@ -180,9 +181,12 @@ impl Display for Attribute {
// or having special emitter for quoted strings? // or having special emitter for quoted strings?
// (also avoids the clone because we can consume/modify self) // (also avoids the clone because we can consume/modify self)
f.write_str( f.write_str(
&Value::Ident(self.value.clone(), QuoteKind::Quoted) &Value::Ident(
.to_css_string(self.span) InternedString::get_or_intern(self.value.clone()),
.unwrap(), QuoteKind::Quoted,
)
.to_css_string(self.span)
.unwrap(),
)?; )?;
// todo: this space is not emitted when `compressed` output // todo: this space is not emitted when `compressed` output
if self.modifier.is_some() { if self.modifier.is_some() {

View File

@ -4,6 +4,7 @@ use peekmore::{PeekMore, PeekMoreIterator};
use crate::common::{Brackets, ListSeparator, QuoteKind}; use crate::common::{Brackets, ListSeparator, QuoteKind};
use crate::error::SassResult; use crate::error::SassResult;
use crate::interner::InternedString;
use crate::scope::Scope; use crate::scope::Scope;
use crate::utils::{ use crate::utils::{
devour_whitespace, eat_comment, eat_ident_no_interpolation, parse_interpolation, devour_whitespace, eat_comment, eat_ident_no_interpolation, parse_interpolation,
@ -57,7 +58,12 @@ impl SelectorPart {
Value::List( Value::List(
kinds kinds
.iter() .iter()
.map(|s| Value::Ident(s.to_string(), QuoteKind::None)) .map(|s| {
Value::Ident(
InternedString::get_or_intern(s.to_string()),
QuoteKind::None,
)
})
.collect(), .collect(),
ListSeparator::Space, ListSeparator::Space,
Brackets::None, Brackets::None,

View File

@ -3,6 +3,7 @@ use peekmore::PeekMoreIterator;
use codemap::{Span, Spanned}; use codemap::{Span, Spanned};
use crate::error::SassResult; use crate::error::SassResult;
use crate::interner::InternedString;
use crate::scope::Scope; use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
use crate::utils::{devour_whitespace, devour_whitespace_or_comment, eat_ident}; use crate::utils::{devour_whitespace, devour_whitespace_or_comment, eat_ident};
@ -12,7 +13,7 @@ use crate::{Expr, Token};
/// A style: `color: red` /// A style: `color: red`
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Style { pub(crate) struct Style {
pub property: String, pub property: InternedString,
pub value: Spanned<Value>, pub value: Spanned<Value>,
} }
@ -21,9 +22,9 @@ impl Style {
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
super_property: String, super_property: InternedString,
span_before: Span, span_before: Span,
) -> SassResult<String> { ) -> SassResult<InternedString> {
StyleParser::new(scope, super_selector).parse_property(toks, super_property, span_before) StyleParser::new(scope, super_selector).parse_property(toks, super_property, span_before)
} }
@ -57,7 +58,7 @@ impl Style {
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
super_property: String, super_property: InternedString,
) -> SassResult<Expr> { ) -> SassResult<Expr> {
StyleParser::new(scope, super_selector).eat_style_group(toks, super_property, scope) StyleParser::new(scope, super_selector).eat_style_group(toks, super_property, scope)
} }
@ -88,7 +89,7 @@ impl<'a> StyleParser<'a> {
pub(crate) fn eat_style_group<I: Iterator<Item = Token>>( pub(crate) fn eat_style_group<I: Iterator<Item = Token>>(
&self, &self,
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
super_property: String, super_property: InternedString,
scope: &Scope, scope: &Scope,
) -> SassResult<Expr> { ) -> SassResult<Expr> {
let mut styles = Vec::new(); let mut styles = Vec::new();
@ -99,8 +100,7 @@ impl<'a> StyleParser<'a> {
let span_before = toks.next().unwrap().pos; let span_before = toks.next().unwrap().pos;
devour_whitespace(toks); devour_whitespace(toks);
loop { loop {
let property = let property = self.parse_property(toks, super_property, span_before)?;
self.parse_property(toks, super_property.clone(), span_before)?;
if let Some(tok) = toks.peek() { if let Some(tok) = toks.peek() {
if tok.kind == '{' { if tok.kind == '{' {
match self.eat_style_group(toks, property, scope)? { match self.eat_style_group(toks, property, scope)? {
@ -133,7 +133,7 @@ impl<'a> StyleParser<'a> {
} }
'{' => { '{' => {
styles.push(Style { styles.push(Style {
property: property.clone(), property: property,
value, value,
}); });
match self.eat_style_group(toks, property, scope)? { match self.eat_style_group(toks, property, scope)? {
@ -170,7 +170,7 @@ impl<'a> StyleParser<'a> {
} }
'{' => { '{' => {
let mut v = vec![Style { let mut v = vec![Style {
property: super_property.clone(), property: super_property,
value, value,
}]; }];
match self.eat_style_group(toks, super_property, scope)? { match self.eat_style_group(toks, super_property, scope)? {
@ -195,9 +195,9 @@ impl<'a> StyleParser<'a> {
pub(crate) fn parse_property<I: Iterator<Item = Token>>( pub(crate) fn parse_property<I: Iterator<Item = Token>>(
&self, &self,
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
mut super_property: String, super_property: InternedString,
span_before: Span, span_before: Span,
) -> SassResult<String> { ) -> SassResult<InternedString> {
devour_whitespace(toks); devour_whitespace(toks);
let property = eat_ident(toks, self.scope, self.super_selector, span_before)?; let property = eat_ident(toks, self.scope, self.super_selector, span_before)?;
devour_whitespace_or_comment(toks)?; devour_whitespace_or_comment(toks)?;
@ -209,12 +209,13 @@ impl<'a> StyleParser<'a> {
} }
if super_property.is_empty() { if super_property.is_empty() {
Ok(property.node) Ok(InternedString::get_or_intern(property.node))
} else { } else {
let mut super_property = super_property.resolve().to_string();
super_property.reserve(1 + property.node.len()); super_property.reserve(1 + property.node.len());
super_property.push('-'); super_property.push('-');
super_property.push_str(&property.node); super_property.push_str(&property.node);
Ok(super_property) Ok(InternedString::get_or_intern(super_property))
} }
} }
} }

View File

@ -10,6 +10,7 @@ use peekmore::{PeekMore, PeekMoreIterator};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use crate::atrule::{eat_include, AtRule, AtRuleKind}; use crate::atrule::{eat_include, AtRule, AtRuleKind};
use crate::common::Identifier;
use crate::error::{SassError, SassResult}; use crate::error::{SassError, SassResult};
use crate::imports::import; use crate::imports::import;
use crate::lexer::Lexer; use crate::lexer::Lexer;
@ -164,8 +165,9 @@ impl<'a> StyleSheetParser<'a> {
let mut rules: Vec<Spanned<Stmt>> = Vec::new(); let mut rules: Vec<Spanned<Stmt>> = Vec::new();
while let Some(Token { kind, .. }) = self.lexer.peek() { while let Some(Token { kind, .. }) = self.lexer.peek() {
match kind { match kind {
_ if is_selector_char(*kind) => rules _ if is_selector_char(*kind) => {
.extend(self.eat_rules(&Selector::new(), &mut Scope::new())?), rules.extend(self.eat_rules(&Selector::new(), &mut Scope::new())?)
}
'\t' | '\n' | ' ' => { '\t' | '\n' | ' ' => {
self.lexer.next(); self.lexer.next();
continue; continue;
@ -177,15 +179,18 @@ impl<'a> StyleSheetParser<'a> {
match self.lexer.peek() { match self.lexer.peek() {
Some(Token { kind: ':', .. }) => { Some(Token { kind: ':', .. }) => {
self.lexer.take(name.node.chars().count() + whitespace + 1) self.lexer
.take(name.node.chars().count() + whitespace + 1)
.for_each(drop); .for_each(drop);
devour_whitespace(self.lexer); devour_whitespace(self.lexer);
let VariableDecl { val, default, .. } = let name = name.map_node(|n| Identifier::from(n));
eat_variable_value(self.lexer, &Scope::new(), &Selector::new())?;
if !(default && global_var_exists(&name)) { let VariableDecl { val, default, .. } =
insert_global_var(&name.node, val)?; eat_variable_value(self.lexer, &Scope::new(), &Selector::new())?;
if !(default && global_var_exists(name.node)) {
insert_global_var(name.node, val)?;
} }
} }
Some(..) | None => return Err(("expected \":\".", name.span).into()), Some(..) | None => return Err(("expected \":\".", name.span).into()),
@ -202,17 +207,15 @@ impl<'a> StyleSheetParser<'a> {
let comment = eat_comment(self.lexer, &Scope::new(), &Selector::new())?; let comment = eat_comment(self.lexer, &Scope::new(), &Selector::new())?;
rules.push(comment.map_node(Stmt::MultilineComment)); rules.push(comment.map_node(Stmt::MultilineComment));
} }
_ => return Err(("expected selector.", pos).into()) _ => return Err(("expected selector.", pos).into()),
} }
} }
'@' => { '@' => {
let span_before = self.lexer.next().unwrap().pos(); let span_before = self.lexer.next().unwrap().pos();
let Spanned { node: at_rule_kind, span } = eat_ident( let Spanned {
self.lexer, node: at_rule_kind,
&Scope::new(), span,
&Selector::new(), } = eat_ident(self.lexer, &Scope::new(), &Selector::new(), span_before)?;
span_before
)?;
if at_rule_kind.is_empty() { if at_rule_kind.is_empty() {
return Err(("Expected identifier.", span).into()); return Err(("Expected identifier.", span).into());
} }
@ -222,14 +225,14 @@ impl<'a> StyleSheetParser<'a> {
&Scope::new(), &Scope::new(),
&Selector::new(), &Selector::new(),
None, None,
span span,
)?), )?),
AtRuleKind::Import => { AtRuleKind::Import => {
devour_whitespace(self.lexer); devour_whitespace(self.lexer);
let mut file_name = String::new(); let mut file_name = String::new();
let next = match self.lexer.next() { let next = match self.lexer.next() {
Some(v) => v, Some(v) => v,
None => todo!("expected input after @import") None => todo!("expected input after @import"),
}; };
match next.kind { match next.kind {
q @ '"' | q @ '\'' => { q @ '"' | q @ '\'' => {
@ -238,8 +241,12 @@ impl<'a> StyleSheetParser<'a> {
self.lexer, self.lexer,
&Scope::new(), &Scope::new(),
q, q,
&Selector::new())? &Selector::new(),
.node.unquote().to_css_string(span)?); )?
.node
.unquote()
.to_css_string(span)?,
);
} }
_ => return Err(("Expected string.", next.pos()).into()), _ => return Err(("Expected string.", next.pos()).into()),
} }
@ -251,14 +258,22 @@ impl<'a> StyleSheetParser<'a> {
devour_whitespace(self.lexer); devour_whitespace(self.lexer);
let (new_rules, new_scope) = import(self.path, file_name.as_ref(), &mut self.map)?; let (new_rules, new_scope) =
import(self.path, file_name.as_ref(), &mut self.map)?;
rules.extend(new_rules); rules.extend(new_rules);
GLOBAL_SCOPE.with(|s| { GLOBAL_SCOPE.with(|s| {
s.borrow_mut().extend(new_scope); s.borrow_mut().extend(new_scope);
}); });
} }
v => { v => {
let rule = AtRule::from_tokens(v, span, self.lexer, &mut Scope::new(), &Selector::new(), None)?; let rule = AtRule::from_tokens(
v,
span,
self.lexer,
&mut Scope::new(),
&Selector::new(),
None,
)?;
match rule.node { match rule.node {
AtRule::Mixin(name, mixin) => { AtRule::Mixin(name, mixin) => {
insert_global_mixin(&name, *mixin); insert_global_mixin(&name, *mixin);
@ -274,17 +289,36 @@ impl<'a> StyleSheetParser<'a> {
("This at-rule is not allowed here.", rule.span).into() ("This at-rule is not allowed here.", rule.span).into()
) )
} }
AtRule::For(f) => rules.extend(f.ruleset_eval(&mut Scope::new(), &Selector::new(), None)?), AtRule::For(f) => rules.extend(f.ruleset_eval(
AtRule::While(w) => rules.extend(w.ruleset_eval(&mut Scope::new(), &Selector::new(), true, None)?), &mut Scope::new(),
AtRule::Each(e) => { &Selector::new(),
rules.extend(e.ruleset_eval(&mut Scope::new(), &Selector::new(), None)?) None,
} )?),
AtRule::While(w) => rules.extend(w.ruleset_eval(
&mut Scope::new(),
&Selector::new(),
true,
None,
)?),
AtRule::Each(e) => rules.extend(e.ruleset_eval(
&mut Scope::new(),
&Selector::new(),
None,
)?),
AtRule::Include(s) => rules.extend(s), AtRule::Include(s) => rules.extend(s),
AtRule::Content => return Err( AtRule::Content => {
("@content is only allowed within mixin declarations.", rule.span return Err((
).into()), "@content is only allowed within mixin declarations.",
rule.span,
)
.into())
}
AtRule::If(i) => { AtRule::If(i) => {
rules.extend(i.eval(&mut Scope::new(), &Selector::new(), None)?); rules.extend(i.eval(
&mut Scope::new(),
&Selector::new(),
None,
)?);
} }
AtRule::AtRoot(root_rules) => rules.extend(root_rules), AtRule::AtRoot(root_rules) => rules.extend(root_rules),
AtRule::Unknown(..) => rules.push(rule.map_node(Stmt::AtRule)), AtRule::Unknown(..) => rules.push(rule.map_node(Stmt::AtRule)),
@ -292,11 +326,13 @@ impl<'a> StyleSheetParser<'a> {
} }
} }
} }
}, }
'&' => { '&' => {
return Err( return Err((
("Top-level selectors may not contain the parent selector \"&\".", self.lexer.next().unwrap().pos()).into(), "Top-level selectors may not contain the parent selector \"&\".",
self.lexer.next().unwrap().pos(),
) )
.into())
} }
c if c.is_control() => { c if c.is_control() => {
return Err(("expected selector.", self.lexer.next().unwrap().pos()).into()); return Err(("expected selector.", self.lexer.next().unwrap().pos()).into());

View File

@ -6,6 +6,7 @@ use peekmore::PeekMoreIterator;
use crate::common::QuoteKind; use crate::common::QuoteKind;
use crate::error::SassResult; use crate::error::SassResult;
use crate::interner::InternedString;
use crate::selector::Selector; use crate::selector::Selector;
use crate::value::Value; use crate::value::Value;
use crate::{Scope, Token}; use crate::{Scope, Token};
@ -213,7 +214,7 @@ pub(crate) fn eat_ident<I: Iterator<Item = Token>>(
toks.next(); toks.next();
text.push_str( text.push_str(
&match parse_interpolation(toks, scope, super_selector)?.node { &match parse_interpolation(toks, scope, super_selector)?.node {
Value::Ident(s, ..) => s, Value::Ident(s, ..) => s.resolve().to_string(),
v => v.to_css_string(span)?.into(), v => v.to_css_string(span)?.into(),
}, },
); );
@ -291,7 +292,7 @@ pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>(
toks.next(); toks.next();
let interpolation = parse_interpolation(toks, scope, super_selector)?; let interpolation = parse_interpolation(toks, scope, super_selector)?;
s.push_str(&match interpolation.node { s.push_str(&match interpolation.node {
Value::Ident(s, ..) => s, Value::Ident(s, ..) => s.resolve().to_string(),
v => v.to_css_string(interpolation.span)?.into(), v => v.to_css_string(interpolation.span)?.into(),
}); });
continue; continue;
@ -345,7 +346,7 @@ pub(crate) fn parse_quoted_string<I: Iterator<Item = Token>>(
} }
} }
Ok(Spanned { Ok(Spanned {
node: Value::Ident(s, QuoteKind::Quoted), node: Value::Ident(InternedString::get_or_intern(s), QuoteKind::Quoted),
span, span,
}) })
} }

View File

@ -113,7 +113,7 @@ pub(crate) fn try_eat_url<I: Iterator<Item = Token>>(
let (interpolation, count) = peek_interpolation(toks, scope, super_selector)?; let (interpolation, count) = peek_interpolation(toks, scope, super_selector)?;
peek_counter += count; peek_counter += count;
buf.push_str(&match interpolation.node { buf.push_str(&match interpolation.node {
Value::Ident(s, ..) => s, Value::Ident(s, ..) => s.resolve().to_string(),
v => v.to_css_string(interpolation.span)?.into(), v => v.to_css_string(interpolation.span)?.into(),
}); });
} else { } else {

View File

@ -6,6 +6,7 @@ use codemap::{Span, Spanned};
use crate::color::Color; use crate::color::Color;
use crate::common::{Brackets, ListSeparator, Op, QuoteKind}; use crate::common::{Brackets, ListSeparator, Op, QuoteKind};
use crate::error::SassResult; use crate::error::SassResult;
use crate::interner::InternedString;
use crate::unit::Unit; use crate::unit::Unit;
use crate::utils::hex_char_for; use crate::utils::hex_char_for;
@ -33,7 +34,7 @@ pub(crate) enum Value {
UnaryOp(Op, Box<Value>), UnaryOp(Op, Box<Value>),
BinaryOp(Box<Value>, Op, Box<Value>), BinaryOp(Box<Value>, Op, Box<Value>),
Paren(Box<Value>), Paren(Box<Value>),
Ident(String, QuoteKind), Ident(InternedString, QuoteKind),
Map(SassMap), Map(SassMap),
ArgList(Vec<Spanned<Value>>), ArgList(Vec<Spanned<Value>>),
/// Returned by `get-function()` /// Returned by `get-function()`
@ -167,6 +168,7 @@ impl Value {
} }
Self::Paren(val) => val.to_css_string(span)?, Self::Paren(val) => val.to_css_string(span)?,
Self::Ident(string, QuoteKind::None) => { Self::Ident(string, QuoteKind::None) => {
let string = string.resolve();
let mut after_newline = false; let mut after_newline = false;
let mut buf = String::with_capacity(string.len()); let mut buf = String::with_capacity(string.len());
for c in string.chars() { for c in string.chars() {
@ -189,8 +191,9 @@ impl Value {
Cow::Owned(buf) Cow::Owned(buf)
} }
Self::Ident(string, QuoteKind::Quoted) => { Self::Ident(string, QuoteKind::Quoted) => {
let string = string.resolve();
let mut buf = String::with_capacity(string.len()); let mut buf = String::with_capacity(string.len());
visit_quoted_string(&mut buf, false, string)?; visit_quoted_string(&mut buf, false, &string)?;
Cow::Owned(buf) Cow::Owned(buf)
} }
Self::True => Cow::Borrowed("true"), Self::True => Cow::Borrowed("true"),
@ -249,7 +252,7 @@ impl Value {
pub fn is_special_function(&self) -> bool { pub fn is_special_function(&self) -> bool {
match self { match self {
Self::Ident(s, QuoteKind::None) => is_special_function(s), Self::Ident(s, QuoteKind::None) => is_special_function(&s.resolve()),
_ => false, _ => false,
} }
} }

View File

@ -4,6 +4,7 @@ use codemap::{Span, Spanned};
use crate::common::{Op, QuoteKind}; use crate::common::{Op, QuoteKind};
use crate::error::SassResult; use crate::error::SassResult;
use crate::interner::InternedString;
use crate::unit::{Unit, UNIT_CONVERSION_TABLE}; use crate::unit::{Unit, UNIT_CONVERSION_TABLE};
use crate::value::Value; use crate::value::Value;
@ -111,7 +112,10 @@ impl Value {
pub fn unary_op_plus(self, span: Span) -> SassResult<Self> { pub fn unary_op_plus(self, span: Span) -> SassResult<Self> {
Ok(match self.eval(span)?.node { Ok(match self.eval(span)?.node {
v @ Value::Dimension(..) => v, v @ Value::Dimension(..) => v,
v => Value::Ident(format!("+{}", v.to_css_string(span)?), QuoteKind::None), v => Value::Ident(
InternedString::get_or_intern(format!("+{}", v.to_css_string(span)?)),
QuoteKind::None,
),
}) })
} }
@ -259,22 +263,28 @@ impl Value {
Self::Function(..) | Self::ArgList(..) | Self::Map(..) => todo!(), Self::Function(..) | Self::ArgList(..) | Self::Map(..) => todo!(),
Self::Important | Self::True | Self::False => match other { Self::Important | Self::True | Self::False => match other {
Self::Ident(s, QuoteKind::Quoted) => Value::Ident( Self::Ident(s, QuoteKind::Quoted) => Value::Ident(
format!("{}{}", self.to_css_string(span)?, s), InternedString::get_or_intern(format!("{}{}", self.to_css_string(span)?, s)),
QuoteKind::Quoted, QuoteKind::Quoted,
), ),
Self::Null => Value::Ident(self.to_css_string(span)?.into(), QuoteKind::None), Self::Null => Value::Ident(
InternedString::get_or_intern(self.to_css_string(span)?.into_owned()),
QuoteKind::None,
),
_ => Value::Ident( _ => Value::Ident(
format!( InternedString::get_or_intern(format!(
"{}{}", "{}{}",
self.to_css_string(span)?, self.to_css_string(span)?,
other.to_css_string(span)? other.to_css_string(span)?
), )),
QuoteKind::None, QuoteKind::None,
), ),
}, },
Self::Null => match other { Self::Null => match other {
Self::Null => Self::Null, Self::Null => Self::Null,
_ => Value::Ident(other.to_css_string(span)?.into(), QuoteKind::None), _ => Value::Ident(
InternedString::get_or_intern(other.to_css_string(span)?.into_owned()),
QuoteKind::None,
),
}, },
Self::Dimension(num, unit) => match other { Self::Dimension(num, unit) => match other {
Self::Dimension(num2, unit2) => { Self::Dimension(num2, unit2) => {
@ -299,14 +309,30 @@ impl Value {
) )
} }
} }
Self::Ident(s, q) => Value::Ident(format!("{}{}{}", num, unit, s), q), Self::Ident(s, q) => Value::Ident(
Self::Null => Value::Ident(format!("{}{}", num, unit), QuoteKind::None), InternedString::get_or_intern(format!("{}{}{}", num, unit, s)),
q,
),
Self::Null => Value::Ident(
InternedString::get_or_intern(format!("{}{}", num, unit)),
QuoteKind::None,
),
Self::List(..) => Value::Ident( Self::List(..) => Value::Ident(
format!("{}{}{}", num, unit, other.to_css_string(span)?), InternedString::get_or_intern(format!(
"{}{}{}",
num,
unit,
other.to_css_string(span)?
)),
QuoteKind::None, QuoteKind::None,
), ),
Self::True | Self::False => Self::Ident( Self::True | Self::False => Self::Ident(
format!("{}{}{}", num, unit, other.to_css_string(span)?), InternedString::get_or_intern(format!(
"{}{}{}",
num,
unit,
other.to_css_string(span)?
)),
QuoteKind::None, QuoteKind::None,
), ),
_ => { _ => {
@ -323,10 +349,15 @@ impl Value {
} }
}, },
Self::Color(c) => match other { Self::Color(c) => match other {
Self::Ident(s, q) => Value::Ident(format!("{}{}", c, s), q), Self::Ident(s, q) => {
Self::Null => Value::Ident(c.to_string(), QuoteKind::None), Value::Ident(InternedString::get_or_intern(format!("{}{}", c, s)), q)
}
Self::Null => Value::Ident(
InternedString::get_or_intern(c.to_string()),
QuoteKind::None,
),
Self::List(..) => Value::Ident( Self::List(..) => Value::Ident(
format!("{}{}", c, other.to_css_string(span)?), InternedString::get_or_intern(format!("{}{}", c, other.to_css_string(span)?)),
QuoteKind::None, QuoteKind::None,
), ),
_ => { _ => {
@ -363,18 +394,27 @@ impl Value {
} }
Self::UnaryOp(..) | Self::Paren(..) => self.eval(span)?.node.add(other, span)?, Self::UnaryOp(..) | Self::Paren(..) => self.eval(span)?.node.add(other, span)?,
Self::Ident(text, quotes) => match other { Self::Ident(text, quotes) => match other {
Self::Ident(text2, ..) => Self::Ident(text + &text2, quotes), Self::Ident(text2, ..) => Self::Ident(
_ => Value::Ident(text + &other.to_css_string(span)?, quotes), InternedString::get_or_intern(text.resolve() + &text2.resolve()),
quotes,
),
_ => Value::Ident(
InternedString::get_or_intern(text.resolve() + &other.to_css_string(span)?),
quotes,
),
}, },
Self::List(..) => match other { Self::List(..) => match other {
Self::Ident(s, q) => Value::Ident(format!("{}{}", self.to_css_string(span)?, s), q), Self::Ident(s, q) => Value::Ident(
InternedString::get_or_intern(format!("{}{}", self.to_css_string(span)?, s)),
q,
),
Self::Paren(..) => (self.add(other.eval(span)?.node, span))?, Self::Paren(..) => (self.add(other.eval(span)?.node, span))?,
_ => Value::Ident( _ => Value::Ident(
format!( InternedString::get_or_intern(format!(
"{}{}", "{}{}",
self.to_css_string(span)?, self.to_css_string(span)?,
other.to_css_string(span)? other.to_css_string(span)?
), )),
QuoteKind::None, QuoteKind::None,
), ),
}, },
@ -412,20 +452,34 @@ impl Value {
} }
} }
Self::List(..) => Value::Ident( Self::List(..) => Value::Ident(
format!("{}{}-{}", num, unit, other.to_css_string(span)?), InternedString::get_or_intern(format!(
"{}{}-{}",
num,
unit,
other.to_css_string(span)?
)),
QuoteKind::None, QuoteKind::None,
), ),
Self::Ident(..) => Value::Ident( Self::Ident(..) => Value::Ident(
format!("{}{}-{}", num, unit, other.to_css_string(span)?), InternedString::get_or_intern(format!(
"{}{}-{}",
num,
unit,
other.to_css_string(span)?
)),
QuoteKind::None, QuoteKind::None,
), ),
_ => todo!(), _ => todo!(),
}, },
Self::Color(c) => match other { Self::Color(c) => match other {
Self::Ident(s, q) => { Self::Ident(s, q) => Value::Ident(
Value::Ident(format!("{}-{}{}{}", c, q, s, q), QuoteKind::None) InternedString::get_or_intern(format!("{}-{}{}{}", c, q, s, q)),
} QuoteKind::None,
Self::Null => Value::Ident(format!("{}-", c), QuoteKind::None), ),
Self::Null => Value::Ident(
InternedString::get_or_intern(format!("{}-", c)),
QuoteKind::None,
),
Self::Dimension(..) | Self::Color(..) => { Self::Dimension(..) | Self::Color(..) => {
return Err(( return Err((
format!( format!(
@ -438,7 +492,7 @@ impl Value {
.into()) .into())
} }
_ => Value::Ident( _ => Value::Ident(
format!("{}-{}", c, other.to_css_string(span)?), InternedString::get_or_intern(format!("{}-{}", c, other.to_css_string(span)?)),
QuoteKind::None, QuoteKind::None,
), ),
}, },
@ -464,41 +518,54 @@ impl Value {
} }
Self::Paren(..) => self.eval(span)?.node.sub(other, span)?, Self::Paren(..) => self.eval(span)?.node.sub(other, span)?,
Self::Ident(..) => Self::Ident( Self::Ident(..) => Self::Ident(
format!( InternedString::get_or_intern(format!(
"{}-{}", "{}-{}",
self.to_css_string(span)?, self.to_css_string(span)?,
other.to_css_string(span)? other.to_css_string(span)?
), )),
QuoteKind::None, QuoteKind::None,
), ),
Self::List(..) => match other { Self::List(..) => match other {
Self::Ident(s, q) => Value::Ident( Self::Ident(s, q) => Value::Ident(
format!("{}-{}{}{}", self.to_css_string(span)?, q, s, q), InternedString::get_or_intern(format!(
"{}-{}{}{}",
self.to_css_string(span)?,
q,
s,
q
)),
QuoteKind::None, QuoteKind::None,
), ),
_ => Value::Ident( _ => Value::Ident(
format!( InternedString::get_or_intern(format!(
"{}-{}", "{}-{}",
self.to_css_string(span)?, self.to_css_string(span)?,
other.to_css_string(span)? other.to_css_string(span)?
), )),
QuoteKind::None, QuoteKind::None,
), ),
}, },
_ => match other { _ => match other {
Self::Ident(s, q) => Value::Ident( Self::Ident(s, q) => Value::Ident(
format!("{}-{}{}{}", self.to_css_string(span)?, q, s, q), InternedString::get_or_intern(format!(
"{}-{}{}{}",
self.to_css_string(span)?,
q,
s,
q
)),
QuoteKind::None,
),
Self::Null => Value::Ident(
InternedString::get_or_intern(format!("{}-", self.to_css_string(span)?)),
QuoteKind::None, QuoteKind::None,
), ),
Self::Null => {
Value::Ident(format!("{}-", self.to_css_string(span)?), QuoteKind::None)
}
_ => Value::Ident( _ => Value::Ident(
format!( InternedString::get_or_intern(format!(
"{}-{}", "{}-{}",
self.to_css_string(span)?, self.to_css_string(span)?,
other.to_css_string(span)? other.to_css_string(span)?
), )),
QuoteKind::None, QuoteKind::None,
), ),
}, },
@ -604,19 +671,24 @@ impl Value {
) )
} }
} }
Self::Ident(s, q) => { Self::Ident(s, q) => Value::Ident(
Value::Ident(format!("{}{}/{}{}{}", num, unit, q, s, q), QuoteKind::None) InternedString::get_or_intern(format!("{}{}/{}{}{}", num, unit, q, s, q)),
} QuoteKind::None,
),
Self::BinaryOp(..) | Self::Paren(..) => { Self::BinaryOp(..) | Self::Paren(..) => {
Self::Dimension(num, unit).div(other.eval(span)?.node, span)? Self::Dimension(num, unit).div(other.eval(span)?.node, span)?
} }
_ => todo!(), _ => todo!(),
}, },
Self::Color(c) => match other { Self::Color(c) => match other {
Self::Ident(s, q) => { Self::Ident(s, q) => Value::Ident(
Value::Ident(format!("{}/{}{}{}", c, q, s, q), QuoteKind::None) InternedString::get_or_intern(format!("{}/{}{}{}", c, q, s, q)),
} QuoteKind::None,
Self::Null => Value::Ident(format!("{}/", c), QuoteKind::None), ),
Self::Null => Value::Ident(
InternedString::get_or_intern(format!("{}/", c)),
QuoteKind::None,
),
Self::Dimension(..) | Self::Color(..) => { Self::Dimension(..) | Self::Color(..) => {
return Err(( return Err((
format!( format!(
@ -629,7 +701,7 @@ impl Value {
.into()) .into())
} }
_ => Value::Ident( _ => Value::Ident(
format!("{}/{}", c, other.to_css_string(span)?), InternedString::get_or_intern(format!("{}/{}", c, other.to_css_string(span)?)),
QuoteKind::None, QuoteKind::None,
), ),
}, },
@ -656,7 +728,7 @@ impl Value {
Self::Paren(..) => self.eval(span)?.node.div(other, span)?, Self::Paren(..) => self.eval(span)?.node.div(other, span)?,
Self::Ident(s1, q1) => match other { Self::Ident(s1, q1) => match other {
Self::Ident(s2, q2) => Value::Ident( Self::Ident(s2, q2) => Value::Ident(
format!("{}{}{}/{}{}{}", q1, s1, q1, q2, s2, q2), InternedString::get_or_intern(format!("{}{}{}/{}{}{}", q1, s1, q1, q2, s2, q2)),
QuoteKind::None, QuoteKind::None,
), ),
Self::Important Self::Important
@ -664,26 +736,42 @@ impl Value {
| Self::False | Self::False
| Self::Dimension(..) | Self::Dimension(..)
| Self::Color(..) => Value::Ident( | Self::Color(..) => Value::Ident(
format!("{}{}{}/{}", q1, s1, q1, other.to_css_string(span)?), InternedString::get_or_intern(format!(
"{}{}{}/{}",
q1,
s1,
q1,
other.to_css_string(span)?
)),
QuoteKind::None,
),
Self::Null => Value::Ident(
InternedString::get_or_intern(format!("{}{}{}/", q1, s1, q1)),
QuoteKind::None, QuoteKind::None,
), ),
Self::Null => Value::Ident(format!("{}{}{}/", q1, s1, q1), QuoteKind::None),
_ => todo!(), _ => todo!(),
}, },
_ => match other { _ => match other {
Self::Ident(s, q) => Value::Ident( Self::Ident(s, q) => Value::Ident(
format!("{}/{}{}{}", self.to_css_string(span)?, q, s, q), InternedString::get_or_intern(format!(
"{}/{}{}{}",
self.to_css_string(span)?,
q,
s,
q
)),
QuoteKind::None,
),
Self::Null => Value::Ident(
InternedString::get_or_intern(format!("{}/", self.to_css_string(span)?)),
QuoteKind::None, QuoteKind::None,
), ),
Self::Null => {
Value::Ident(format!("{}/", self.to_css_string(span)?), QuoteKind::None)
}
_ => Value::Ident( _ => Value::Ident(
format!( InternedString::get_or_intern(format!(
"{}/{}", "{}/{}",
self.to_css_string(span)?, self.to_css_string(span)?,
other.to_css_string(span)? other.to_css_string(span)?
), )),
QuoteKind::None, QuoteKind::None,
), ),
}, },
@ -736,7 +824,10 @@ impl Value {
pub fn neg(self, span: Span) -> SassResult<Self> { pub fn neg(self, span: Span) -> SassResult<Self> {
Ok(match self.eval(span)?.node { Ok(match self.eval(span)?.node {
Value::Dimension(n, u) => Value::Dimension(-n, u), Value::Dimension(n, u) => Value::Dimension(-n, u),
v => Value::Ident(format!("-{}", v.to_css_string(span)?), QuoteKind::None), v => Value::Ident(
InternedString::get_or_intern(format!("-{}", v.to_css_string(span)?)),
QuoteKind::None,
),
}) })
} }
} }

View File

@ -16,6 +16,7 @@ use crate::builtin::GLOBAL_FUNCTIONS;
use crate::color::{Color, NAMED_COLORS}; use crate::color::{Color, NAMED_COLORS};
use crate::common::{Brackets, ListSeparator, Op, QuoteKind}; use crate::common::{Brackets, ListSeparator, Op, QuoteKind};
use crate::error::SassResult; use crate::error::SassResult;
use crate::interner::{keywords, InternedString};
use crate::scope::Scope; use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
use crate::unit::Unit; use crate::unit::Unit;
@ -58,7 +59,10 @@ fn parse_hex<I: Iterator<Item = Token>>(
span = span.merge(i.span); span = span.merge(i.span);
} else { } else {
return Ok(Spanned { return Ok(Spanned {
node: Value::Ident(format!("#{}", i.node), QuoteKind::None), node: Value::Ident(
InternedString::get_or_intern(format!("#{}", i.node)),
QuoteKind::None,
),
span: i.span, span: i.span,
}); });
} }
@ -67,7 +71,13 @@ fn parse_hex<I: Iterator<Item = Token>>(
3 => { 3 => {
let v = match u16::from_str_radix(&s, 16) { let v = match u16::from_str_radix(&s, 16) {
Ok(a) => a, Ok(a) => a,
Err(_) => return Ok(Value::Ident(format!("#{}", s), QuoteKind::None).span(span)), Err(_) => {
return Ok(Value::Ident(
InternedString::get_or_intern(format!("#{}", s)),
QuoteKind::None,
)
.span(span))
}
}; };
let red = (((v & 0xf00) >> 8) * 0x11) as u8; let red = (((v & 0xf00) >> 8) * 0x11) as u8;
let green = (((v & 0x0f0) >> 4) * 0x11) as u8; let green = (((v & 0x0f0) >> 4) * 0x11) as u8;
@ -80,7 +90,13 @@ fn parse_hex<I: Iterator<Item = Token>>(
4 => { 4 => {
let v = match u16::from_str_radix(&s, 16) { let v = match u16::from_str_radix(&s, 16) {
Ok(a) => a, Ok(a) => a,
Err(_) => return Ok(Value::Ident(format!("#{}", s), QuoteKind::None).span(span)), Err(_) => {
return Ok(Value::Ident(
InternedString::get_or_intern(format!("#{}", s)),
QuoteKind::None,
)
.span(span))
}
}; };
let red = (((v & 0xf000) >> 12) * 0x11) as u8; let red = (((v & 0xf000) >> 12) * 0x11) as u8;
let green = (((v & 0x0f00) >> 8) * 0x11) as u8; let green = (((v & 0x0f00) >> 8) * 0x11) as u8;
@ -98,7 +114,13 @@ fn parse_hex<I: Iterator<Item = Token>>(
6 => { 6 => {
let v = match u32::from_str_radix(&s, 16) { let v = match u32::from_str_radix(&s, 16) {
Ok(a) => a, Ok(a) => a,
Err(_) => return Ok(Value::Ident(format!("#{}", s), QuoteKind::None).span(span)), Err(_) => {
return Ok(Value::Ident(
InternedString::get_or_intern(format!("#{}", s)),
QuoteKind::None,
)
.span(span))
}
}; };
let red = ((v & 0x00ff_0000) >> 16) as u8; let red = ((v & 0x00ff_0000) >> 16) as u8;
let green = ((v & 0x0000_ff00) >> 8) as u8; let green = ((v & 0x0000_ff00) >> 8) as u8;
@ -111,7 +133,13 @@ fn parse_hex<I: Iterator<Item = Token>>(
8 => { 8 => {
let v = match u32::from_str_radix(&s, 16) { let v = match u32::from_str_radix(&s, 16) {
Ok(a) => a, Ok(a) => a,
Err(_) => return Ok(Value::Ident(format!("#{}", s), QuoteKind::None).span(span)), Err(_) => {
return Ok(Value::Ident(
InternedString::get_or_intern(format!("#{}", s)),
QuoteKind::None,
)
.span(span))
}
}; };
let red = ((v & 0xff00_0000) >> 24) as u8; let red = ((v & 0xff00_0000) >> 24) as u8;
let green = ((v & 0x00ff_0000) >> 16) as u8; let green = ((v & 0x00ff_0000) >> 16) as u8;
@ -265,7 +293,10 @@ fn eat_op<I: Iterator<Item = Token>>(
devour_whitespace(iter); devour_whitespace(iter);
space_separated.push(Spanned { space_separated.push(Spanned {
node: Value::Ident( node: Value::Ident(
format!("/{}", right.node.to_css_string(right.span)?), InternedString::get_or_intern(format!(
"/{}",
right.node.to_css_string(right.span)?
)),
QuoteKind::None, QuoteKind::None,
), ),
span: op.span.merge(right.span), span: op.span.merge(right.span),
@ -309,7 +340,13 @@ fn eat_op<I: Iterator<Item = Token>>(
devour_whitespace(iter); devour_whitespace(iter);
// special case when the value is literally "and" or "or" // special case when the value is literally "and" or "or"
if iter.peek().is_none() { if iter.peek().is_none() {
space_separated.push(Value::Ident(op.to_string(), QuoteKind::None).span(op.span)); space_separated.push(
Value::Ident(
InternedString::get_or_intern(op.to_string()),
QuoteKind::None,
)
.span(op.span),
);
} else if let Some(left) = space_separated.pop() { } else if let Some(left) = space_separated.pop() {
devour_whitespace(iter); devour_whitespace(iter);
let right = single_value(iter, scope, super_selector, left.span)?; let right = single_value(iter, scope, super_selector, left.span)?;
@ -372,30 +409,23 @@ fn single_value<I: Iterator<Item = Token>>(
let val = single_value(iter, scope, super_selector, span)?; let val = single_value(iter, scope, super_selector, span)?;
Spanned { Spanned {
node: Value::Ident( node: Value::Ident(
format!("/{}", val.node.to_css_string(val.span)?), InternedString::get_or_intern(format!(
"/{}",
val.node.to_css_string(val.span)?
)),
QuoteKind::None, QuoteKind::None,
), ),
span: next.span.merge(val.span), span: next.span.merge(val.span),
} }
} }
Op::And => { Op::And => Spanned {
Spanned { node: Value::Ident(InternedString::get_or_intern("and"), QuoteKind::None),
node: Value::Ident( span: next.span,
"and".into(), },
QuoteKind::None, Op::Or => Spanned {
), node: Value::Ident(InternedString::get_or_intern("or"), QuoteKind::None),
span: next.span, span: next.span,
} },
}
Op::Or => {
Spanned {
node: Value::Ident(
"or".into(),
QuoteKind::None,
),
span: next.span,
}
}
_ => { _ => {
return Err(("Expected expression.", next.span).into()); return Err(("Expected expression.", next.span).into());
} }
@ -569,15 +599,17 @@ impl Value {
) -> SassResult<Spanned<IntermediateValue>> { ) -> SassResult<Spanned<IntermediateValue>> {
let Spanned { node: mut s, span } = eat_ident(toks, scope, super_selector, span_before)?; let Spanned { node: mut s, span } = eat_ident(toks, scope, super_selector, span_before)?;
let lower = s.to_ascii_lowercase(); let lower = InternedString::get_or_intern(s.to_ascii_lowercase());
if lower == "progid" && toks.peek().is_some() && toks.peek().unwrap().kind == ':' { if lower == *keywords::PROGID && toks.peek().is_some() && toks.peek().unwrap().kind == ':' {
s = lower; s = "progid:".to_string();
toks.next(); toks.next();
s.push(':');
s.push_str(&eat_progid(toks, scope, super_selector)?); s.push_str(&eat_progid(toks, scope, super_selector)?);
return Ok(Spanned { return Ok(Spanned {
node: IntermediateValue::Value(Value::Ident(s, QuoteKind::None)), node: IntermediateValue::Value(Value::Ident(
InternedString::get_or_intern(s),
QuoteKind::None,
)),
span, span,
}); });
} }
@ -599,26 +631,34 @@ impl Value {
.span(span)) .span(span))
} }
None => { None => {
match lower.as_str() { match lower {
"calc" | "element" | "expression" => { _ if lower == *keywords::CALC
s = lower; || lower == *keywords::ELEMENT
|| lower == *keywords::EXPRESSION =>
{
s = lower.resolve().to_string();
eat_calc_args(toks, scope, super_selector, &mut s)?; eat_calc_args(toks, scope, super_selector, &mut s)?;
} }
// "min" => {} // "min" => {}
// "max" => {} // "max" => {}
"url" => match try_eat_url(toks, scope, super_selector)? { _ if lower == *keywords::URL => {
Some(val) => s = val, match try_eat_url(toks, scope, super_selector)? {
None => s.push_str( Some(val) => s = val,
&eat_call_args(toks)?.to_css_string(scope, super_selector)?, None => s.push_str(
), &eat_call_args(toks)?
}, .to_css_string(scope, super_selector)?,
),
}
}
_ => s.push_str( _ => s.push_str(
&eat_call_args(toks)?.to_css_string(scope, super_selector)?, &eat_call_args(toks)?.to_css_string(scope, super_selector)?,
), ),
} }
return Ok( return Ok(IntermediateValue::Value(Value::Ident(
IntermediateValue::Value(Value::Ident(s, QuoteKind::None)).span(span) InternedString::get_or_intern(s),
); QuoteKind::None,
))
.span(span));
} }
}, },
}; };
@ -630,21 +670,24 @@ impl Value {
.span(span)); .span(span));
} }
if let Some(c) = NAMED_COLORS.get_by_name(&lower.as_str()) { if let Some(c) = NAMED_COLORS.get_by_name(lower) {
return Ok(IntermediateValue::Value(Value::Color(Box::new(Color::new( return Ok(IntermediateValue::Value(Value::Color(Box::new(Color::new(
c[0], c[1], c[2], c[3], s, c[0], c[1], c[2], c[3], s,
)))) ))))
.span(span)); .span(span));
} }
Ok(match lower.as_str() { Ok(match lower {
"true" => IntermediateValue::Value(Value::True), _ if lower == *keywords::TRUE => IntermediateValue::Value(Value::True),
"false" => IntermediateValue::Value(Value::False), _ if lower == *keywords::FALSE => IntermediateValue::Value(Value::False),
"null" => IntermediateValue::Value(Value::Null), _ if lower == *keywords::NULL => IntermediateValue::Value(Value::Null),
"not" => IntermediateValue::Op(Op::Not), _ if lower == *keywords::NOT => IntermediateValue::Op(Op::Not),
"and" => IntermediateValue::Op(Op::And), _ if lower == *keywords::AND => IntermediateValue::Op(Op::And),
"or" => IntermediateValue::Op(Op::Or), _ if lower == *keywords::OR => IntermediateValue::Op(Op::Or),
_ => IntermediateValue::Value(Value::Ident(s, QuoteKind::None)), _ => IntermediateValue::Value(Value::Ident(
InternedString::get_or_intern(s),
QuoteKind::None,
)),
} }
.span(span)) .span(span))
} }

View File

@ -14,6 +14,7 @@ use std::fmt;
use crate::args::CallArgs; use crate::args::CallArgs;
use crate::atrule::Function; use crate::atrule::Function;
use crate::builtin::Builtin; use crate::builtin::Builtin;
use crate::common::Identifier;
use crate::error::SassResult; use crate::error::SassResult;
use crate::scope::Scope; use crate::scope::Scope;
use crate::selector::Selector; use crate::selector::Selector;
@ -27,15 +28,15 @@ use crate::value::Value;
/// for use in the builtin function `inspect()` /// for use in the builtin function `inspect()`
#[derive(Clone)] #[derive(Clone)]
pub(crate) enum SassFunction { pub(crate) enum SassFunction {
Builtin(Builtin, String), Builtin(Builtin, Identifier),
UserDefined(Box<Function>, String), UserDefined(Box<Function>, Identifier),
} }
impl SassFunction { impl SassFunction {
/// Get the name of the function referenced /// Get the name of the function referenced
/// ///
/// Used mainly in debugging and `inspect()` /// Used mainly in debugging and `inspect()`
pub fn name(&self) -> &str { pub fn name(&self) -> &Identifier {
match self { match self {
Self::Builtin(_, name) => name, Self::Builtin(_, name) => name,
Self::UserDefined(_, name) => name, Self::UserDefined(_, name) => name,