diff --git a/Cargo.toml b/Cargo.toml index 12d9664..edda69d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ beef = "0.4.4" # long to compile, and you cannot make dev-dependencies optional criterion = { version = "0.3.2", optional = true } indexmap = "1.4.0" +lasso = "0.2.2" [features] default = ["commandline", "random"] diff --git a/src/common.rs b/src/common.rs index c52a593..3a4a6fe 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,5 +1,7 @@ use std::fmt::{self, Display, Write}; +use crate::interner::InternedString; + #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Op { Equal, @@ -111,27 +113,35 @@ impl ListSeparator { /// This struct protects that invariant by normalizing all /// underscores into hypens. #[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] -pub(crate) struct Identifier(String); +pub(crate) struct Identifier(InternedString); impl From for Identifier { fn from(s: String) -> Identifier { - if s.contains('_') { - Identifier(s.replace('_', "-")) + Identifier(InternedString::get_or_intern(if s.contains('_') { + s.replace('_', "-") } else { - Identifier(s) - } + s + })) } } impl From<&String> for Identifier { fn from(s: &String) -> Identifier { - Identifier(s.replace('_', "-")) + if s.contains('_') { + Identifier(InternedString::get_or_intern(s.replace('_', "-"))) + } else { + Identifier(InternedString::get_or_intern(s)) + } } } impl From<&str> for Identifier { fn from(s: &str) -> Identifier { - Identifier(s.replace('_', "-")) + if s.contains('_') { + Identifier(InternedString::get_or_intern(s.replace('_', "-"))) + } else { + Identifier(InternedString::get_or_intern(s)) + } } } @@ -141,15 +151,9 @@ impl Display for Identifier { } } -impl Default for Identifier { - fn default() -> Self { - Self(String::new()) - } -} - impl Identifier { pub fn as_str(&self) -> &str { - &self.0 + self.0.resolve_ref() } } diff --git a/src/interner.rs b/src/interner.rs new file mode 100644 index 0000000..673c4c6 --- /dev/null +++ b/src/interner.rs @@ -0,0 +1,37 @@ +use lasso::{Rodeo, Spur}; + +use std::cell::RefCell; +use std::fmt::{self, Display}; + +thread_local!(static STRINGS: RefCell> = RefCell::new(Rodeo::default())); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub(crate) struct InternedString(Spur); + +impl InternedString { + pub fn get_or_intern>(s: T) -> Self { + Self(STRINGS.with(|interner| interner.borrow_mut().get_or_intern(s))) + } + + #[allow(dead_code)] + pub fn resolve(self) -> String { + STRINGS.with(|interner| interner.borrow().resolve(&self.0).to_string()) + } + + #[allow(dead_code)] + pub fn is_empty(self) -> bool { + self.resolve_ref() == "" + } + + pub fn resolve_ref<'a>(self) -> &'a str { + unsafe { + STRINGS.with(|interner| &(*(interner.as_ptr()).as_ref().unwrap().resolve(&self.0))) + } + } +} + +impl Display for InternedString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + STRINGS.with(|interner| write!(f, "{}", interner.borrow().resolve(&self.0))) + } +} diff --git a/src/lib.rs b/src/lib.rs index 428878d..5c0b7af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,6 +116,7 @@ mod selector; mod style; mod token; mod unit; +mod interner; mod utils; mod value; diff --git a/src/parse/args.rs b/src/parse/args.rs index b358580..0e86c19 100644 --- a/src/parse/args.rs +++ b/src/parse/args.rs @@ -351,18 +351,14 @@ impl<'a> Parser<'a> { Ok(vals) } - pub(super) fn eval_args( - &mut self, - mut fn_args: FuncArgs, - mut args: CallArgs, - ) -> SassResult { + pub(super) fn eval_args(&mut self, fn_args: FuncArgs, mut args: CallArgs) -> SassResult { let mut scope = Scope::new(); if fn_args.0.is_empty() { args.max_args(0)?; return Ok(scope); } self.scopes.enter_new_scope(); - for (idx, arg) in fn_args.0.iter_mut().enumerate() { + for (idx, mut arg) in fn_args.0.into_iter().enumerate() { if arg.is_variadic { let span = args.span(); let arg_list = Value::ArgList(self.variadic_args(args)?); @@ -387,7 +383,7 @@ impl<'a> Parser<'a> { }, }?; self.scopes.insert_var(arg.name.clone(), val.clone()); - scope.insert_var(mem::take(&mut arg.name), val); + scope.insert_var(arg.name, val); } self.scopes.exit_scope(); Ok(scope) diff --git a/src/parse/common.rs b/src/parse/common.rs index be766e3..fa724b8 100644 --- a/src/parse/common.rs +++ b/src/parse/common.rs @@ -2,7 +2,7 @@ use std::ops::{BitAnd, BitOr}; use codemap::Spanned; -use crate::value::Value; +use crate::{interner::InternedString, value::Value}; #[derive(Debug, Clone)] pub(crate) struct NeverEmptyVec { @@ -41,7 +41,7 @@ impl NeverEmptyVec { #[derive(Debug)] pub(super) enum SelectorOrStyle { Selector(String), - Style(String, Option>>), + Style(InternedString, Option>>), } #[derive(Debug, Copy, Clone)] diff --git a/src/parse/style.rs b/src/parse/style.rs index eec0180..90a5dd6 100644 --- a/src/parse/style.rs +++ b/src/parse/style.rs @@ -2,6 +2,7 @@ use codemap::Spanned; use crate::{ error::SassResult, + interner::InternedString, style::Style, utils::{is_name, is_name_start}, value::Value, @@ -126,7 +127,10 @@ impl<'a> Parser<'a> { let len = toks.len(); if let Ok(val) = self.parse_value_from_vec(toks) { self.toks.take(len).for_each(drop); - return Ok(SelectorOrStyle::Style(property, Some(Box::new(val)))); + return Ok(SelectorOrStyle::Style( + InternedString::get_or_intern(property), + Some(Box::new(val)), + )); } } @@ -136,7 +140,7 @@ impl<'a> Parser<'a> { property.push(':'); return Ok(SelectorOrStyle::Selector(property)); } - _ => SelectorOrStyle::Style(property, None), + _ => SelectorOrStyle::Style(InternedString::get_or_intern(property), None), }); } } else { @@ -172,7 +176,10 @@ impl<'a> Parser<'a> { self.parse_value() } - pub(super) fn parse_style_group(&mut self, super_property: String) -> SassResult> { + pub(super) fn parse_style_group( + &mut self, + super_property: InternedString, + ) -> SassResult> { let mut styles = Vec::new(); self.whitespace(); while let Some(tok) = self.toks.peek().cloned() { @@ -181,7 +188,9 @@ impl<'a> Parser<'a> { self.toks.next(); self.whitespace(); loop { - let property = self.parse_property(super_property.clone())?; + let property = InternedString::get_or_intern( + self.parse_property(super_property.resolve())?, + ); if let Some(tok) = self.toks.peek() { if tok.kind == '{' { styles.append(&mut self.parse_style_group(property)?); diff --git a/src/style.rs b/src/style.rs index 3c261d4..03f9662 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,11 +1,11 @@ use codemap::Spanned; -use crate::{error::SassResult, value::Value}; +use crate::{error::SassResult, interner::InternedString, value::Value}; /// A style: `color: red` #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct Style { - pub property: String, + pub property: InternedString, pub value: Box>, }