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
criterion = { version = "0.3.2", optional = true }
indexmap = "1.4.0"
lasso = "0.2.2"
[features]
default = ["commandline", "random"]

View File

@ -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<String> 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()
}
}

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 token;
mod unit;
mod interner;
mod utils;
mod value;

View File

@ -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<Scope> {
pub(super) fn eval_args(&mut self, fn_args: FuncArgs, mut args: CallArgs) -> SassResult<Scope> {
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)

View File

@ -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<T> {
@ -41,7 +41,7 @@ impl<T> NeverEmptyVec<T> {
#[derive(Debug)]
pub(super) enum SelectorOrStyle {
Selector(String),
Style(String, Option<Box<Spanned<Value>>>),
Style(InternedString, Option<Box<Spanned<Value>>>),
}
#[derive(Debug, Copy, Clone)]

View File

@ -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<Vec<Style>> {
pub(super) fn parse_style_group(
&mut self,
super_property: InternedString,
) -> SassResult<Vec<Style>> {
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)?);

View File

@ -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<Spanned<Value>>,
}