implement string interning

This commit is contained in:
Connor Skees 2020-07-08 21:31:21 -04:00
parent 5551a8f8a8
commit 2dfda192bc
8 changed files with 77 additions and 29 deletions

View File

@ -64,6 +64,7 @@ beef = "0.4.4"
# long to compile, and you cannot make dev-dependencies optional # long to compile, and you cannot make dev-dependencies optional
criterion = { version = "0.3.2", optional = true } criterion = { version = "0.3.2", optional = true }
indexmap = "1.4.0" indexmap = "1.4.0"
lasso = "0.2.2"
[features] [features]
default = ["commandline", "random"] default = ["commandline", "random"]

View File

@ -1,5 +1,7 @@
use std::fmt::{self, Display, Write}; use std::fmt::{self, Display, Write};
use crate::interner::InternedString;
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Op { pub enum Op {
Equal, Equal,
@ -111,27 +113,35 @@ impl ListSeparator {
/// This struct protects that invariant by normalizing all /// This struct protects that invariant by normalizing all
/// underscores into hypens. /// underscores into hypens.
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub(crate) struct Identifier(String); pub(crate) struct Identifier(InternedString);
impl From<String> for Identifier { impl From<String> for Identifier {
fn from(s: String) -> Identifier { fn from(s: String) -> Identifier {
if s.contains('_') { Identifier(InternedString::get_or_intern(if s.contains('_') {
Identifier(s.replace('_', "-")) s.replace('_', "-")
} else { } else {
Identifier(s) s
} }))
} }
} }
impl From<&String> for Identifier { impl From<&String> for Identifier {
fn from(s: &String) -> 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 { impl From<&str> for Identifier {
fn from(s: &str) -> 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 { impl Identifier {
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
&self.0 self.0.resolve_ref()
} }
} }

37
src/interner.rs Normal file
View File

@ -0,0 +1,37 @@
use lasso::{Rodeo, Spur};
use std::cell::RefCell;
use std::fmt::{self, Display};
thread_local!(static STRINGS: RefCell<Rodeo<Spur>> = 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<T: AsRef<str>>(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)))
}
}

View File

@ -116,6 +116,7 @@ mod selector;
mod style; mod style;
mod token; mod token;
mod unit; mod unit;
mod interner;
mod utils; mod utils;
mod value; mod value;

View File

@ -351,18 +351,14 @@ impl<'a> Parser<'a> {
Ok(vals) Ok(vals)
} }
pub(super) fn eval_args( pub(super) fn eval_args(&mut self, fn_args: FuncArgs, mut args: CallArgs) -> SassResult<Scope> {
&mut self,
mut fn_args: FuncArgs,
mut args: CallArgs,
) -> SassResult<Scope> {
let mut scope = Scope::new(); let mut scope = Scope::new();
if fn_args.0.is_empty() { if fn_args.0.is_empty() {
args.max_args(0)?; args.max_args(0)?;
return Ok(scope); return Ok(scope);
} }
self.scopes.enter_new_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 { if arg.is_variadic {
let span = args.span(); let span = args.span();
let arg_list = Value::ArgList(self.variadic_args(args)?); 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()); 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(); self.scopes.exit_scope();
Ok(scope) Ok(scope)

View File

@ -2,7 +2,7 @@ use std::ops::{BitAnd, BitOr};
use codemap::Spanned; use codemap::Spanned;
use crate::value::Value; use crate::{interner::InternedString, value::Value};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct NeverEmptyVec<T> { pub(crate) struct NeverEmptyVec<T> {
@ -41,7 +41,7 @@ impl<T> NeverEmptyVec<T> {
#[derive(Debug)] #[derive(Debug)]
pub(super) enum SelectorOrStyle { pub(super) enum SelectorOrStyle {
Selector(String), Selector(String),
Style(String, Option<Box<Spanned<Value>>>), Style(InternedString, Option<Box<Spanned<Value>>>),
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]

View File

@ -2,6 +2,7 @@ use codemap::Spanned;
use crate::{ use crate::{
error::SassResult, error::SassResult,
interner::InternedString,
style::Style, style::Style,
utils::{is_name, is_name_start}, utils::{is_name, is_name_start},
value::Value, value::Value,
@ -126,7 +127,10 @@ impl<'a> Parser<'a> {
let len = toks.len(); let len = toks.len();
if let Ok(val) = self.parse_value_from_vec(toks) { if let Ok(val) = self.parse_value_from_vec(toks) {
self.toks.take(len).for_each(drop); 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(':'); property.push(':');
return Ok(SelectorOrStyle::Selector(property)); return Ok(SelectorOrStyle::Selector(property));
} }
_ => SelectorOrStyle::Style(property, None), _ => SelectorOrStyle::Style(InternedString::get_or_intern(property), None),
}); });
} }
} else { } else {
@ -172,7 +176,10 @@ impl<'a> Parser<'a> {
self.parse_value() self.parse_value()
} }
pub(super) fn parse_style_group(&mut self, super_property: String) -> SassResult<Vec<Style>> { pub(super) fn parse_style_group(
&mut self,
super_property: InternedString,
) -> SassResult<Vec<Style>> {
let mut styles = Vec::new(); let mut styles = Vec::new();
self.whitespace(); self.whitespace();
while let Some(tok) = self.toks.peek().cloned() { while let Some(tok) = self.toks.peek().cloned() {
@ -181,7 +188,9 @@ impl<'a> Parser<'a> {
self.toks.next(); self.toks.next();
self.whitespace(); self.whitespace();
loop { 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 let Some(tok) = self.toks.peek() {
if tok.kind == '{' { if tok.kind == '{' {
styles.append(&mut self.parse_style_group(property)?); styles.append(&mut self.parse_style_group(property)?);

View File

@ -1,11 +1,11 @@
use codemap::Spanned; use codemap::Spanned;
use crate::{error::SassResult, value::Value}; use crate::{error::SassResult, interner::InternedString, value::Value};
/// 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: Box<Spanned<Value>>, pub value: Box<Spanned<Value>>,
} }