Initial implementation of !global (some issues remain)

This commit is contained in:
ConnorSkees 2020-03-17 20:13:53 -04:00
parent d560f13289
commit 061694bd63
17 changed files with 190 additions and 107 deletions

View File

@ -1,8 +1,9 @@
use std::collections::BTreeMap;
use std::iter::Peekable;
use crate::common::{Scope, Symbol};
use crate::common::Symbol;
use crate::error::SassResult;
use crate::scope::Scope;
use crate::selector::Selector;
use crate::utils::{devour_whitespace, devour_whitespace_or_comment};
use crate::value::Value;

View File

@ -3,10 +3,11 @@ use std::iter::Peekable;
use num_traits::cast::ToPrimitive;
use crate::common::{Keyword, Pos, Scope, Symbol};
use crate::common::{Keyword, Pos, Symbol};
use crate::error::SassResult;
use crate::function::Function;
use crate::mixin::Mixin;
use crate::scope::Scope;
use crate::selector::Selector;
use crate::units::Unit;
use crate::utils::{devour_whitespace, devour_whitespace_or_comment};

View File

@ -1,7 +1,7 @@
use std::iter::Peekable;
use crate::common::Scope;
use crate::error::SassResult;
use crate::scope::Scope;
use crate::selector::Selector;
use crate::{eat_expr, Expr, RuleSet, Stmt, Token};

View File

@ -1,8 +1,9 @@
use std::iter::Peekable;
use super::parse::eat_stmts;
use crate::common::{Scope, Symbol};
use crate::common::Symbol;
use crate::error::SassResult;
use crate::scope::Scope;
use crate::selector::Selector;
use crate::utils::{devour_whitespace, parse_interpolation};
use crate::{RuleSet, Stmt, Token, TokenKind};

View File

@ -2,8 +2,8 @@ use once_cell::sync::Lazy;
use std::collections::HashMap;
use crate::args::CallArgs;
use crate::common::Scope;
use crate::error::SassResult;
use crate::scope::Scope;
use crate::value::Value;
#[macro_use]

View File

@ -1,12 +1,6 @@
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::{self, Display};
use crate::error::SassResult;
use crate::function::Function;
use crate::mixin::Mixin;
use crate::value::Value;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Symbol {
/// .
@ -220,6 +214,7 @@ pub enum Keyword {
False,
Null,
Default,
Global,
From(String),
To(String),
Through(String),
@ -243,7 +238,7 @@ impl Display for Keyword {
Self::False => write!(f, "false"),
Self::Null => write!(f, "null"),
Self::Default => write!(f, "!default"),
// todo!(maintain casing for keywords)
Self::Global => write!(f, "!global"),
Self::From(s) => write!(f, "{}", s),
Self::To(s) => write!(f, "{}", s),
Self::Through(s) => write!(f, "{}", s),
@ -269,6 +264,7 @@ impl Into<&'static str> for Keyword {
Self::False => "false",
Self::Null => "null",
Self::Default => "!default",
Self::Global => "!global",
Self::From(_) => "from",
Self::To(_) => "to",
Self::Through(_) => "through",
@ -296,6 +292,7 @@ impl TryFrom<&str> for Keyword {
"false" => Ok(Self::False),
"null" => Ok(Self::Null),
"default" => Ok(Self::Default),
"global" => Ok(Self::Global),
"from" => Ok(Self::From(kw.to_owned())),
"to" => Ok(Self::To(kw.to_owned())),
"through" => Ok(Self::Through(kw.to_owned())),
@ -353,75 +350,6 @@ impl Display for Pos {
}
}
#[derive(Debug, Clone)]
pub(crate) struct Scope {
vars: HashMap<String, Value>,
mixins: HashMap<String, Mixin>,
functions: HashMap<String, Function>,
}
impl Scope {
#[must_use]
pub fn new() -> Self {
Self {
vars: HashMap::new(),
mixins: HashMap::new(),
functions: HashMap::new(),
}
}
pub fn get_var(&self, v: &str) -> SassResult<&Value> {
match self.vars.get(&v.replace('_', "-")) {
Some(v) => Ok(v),
None => Err("Undefined variable.".into()),
}
}
pub fn insert_var(&mut self, s: &str, v: Value) -> SassResult<Option<Value>> {
Ok(self.vars.insert(s.replace('_', "-"), v.eval()?))
}
pub fn var_exists(&self, v: &str) -> bool {
self.vars.contains_key(&v.replace('_', "-"))
}
pub fn get_mixin(&self, v: &str) -> SassResult<&Mixin> {
match self.mixins.get(&v.replace('_', "-")) {
Some(v) => Ok(v),
None => Err("Undefined mixin.".into()),
}
}
pub fn insert_mixin(&mut self, s: &str, v: Mixin) -> Option<Mixin> {
self.mixins.insert(s.replace('_', "-"), v)
}
pub fn mixin_exists(&self, v: &str) -> bool {
self.mixins.contains_key(&v.replace('_', "-"))
}
pub fn get_fn(&self, v: &str) -> SassResult<&Function> {
match self.functions.get(&v.replace('_', "-")) {
Some(v) => Ok(v),
None => Err("Undefined function.".into()),
}
}
pub fn insert_fn(&mut self, s: &str, v: Function) -> Option<Function> {
self.functions.insert(s.replace('_', "-"), v)
}
pub fn fn_exists(&self, v: &str) -> bool {
self.functions.contains_key(&v.replace('_', "-"))
}
pub fn extend(&mut self, other: Scope) {
self.vars.extend(other.vars);
self.mixins.extend(other.mixins);
self.functions.extend(other.functions);
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) enum QuoteKind {
Single,

View File

@ -2,8 +2,9 @@ use std::iter::Peekable;
use crate::args::{eat_func_args, CallArgs, FuncArgs};
use crate::atrule::AtRule;
use crate::common::{Scope, Symbol};
use crate::common::Symbol;
use crate::error::SassResult;
use crate::scope::Scope;
use crate::selector::Selector;
use crate::utils::devour_whitespace;
use crate::value::Value;

View File

@ -1,8 +1,8 @@
use std::ffi::OsStr;
use std::path::Path;
use crate::common::Scope;
use crate::error::SassResult;
use crate::scope::Scope;
use crate::{Stmt, StyleSheet};
pub(crate) fn import<P: AsRef<Path>>(path: P) -> SassResult<(Vec<Stmt>, Scope)> {

View File

@ -180,6 +180,11 @@ impl<'a> Lexer<'a> {
assert_char!(self, 'e' 'f' 'a' 'u' 'l' 't');
TokenKind::Keyword(Keyword::Default)
}
Some('g') | Some('G') => {
self.buf.next();
assert_char!(self, 'l' 'o' 'b' 'a' 'l');
TokenKind::Keyword(Keyword::Global)
}
Some('=') => {
self.buf.next();
TokenKind::Op(Op::NotEqual)

View File

@ -85,7 +85,7 @@ use std::iter::{Iterator, Peekable};
use std::path::Path;
use crate::atrule::{AtRule, AtRuleKind};
use crate::common::{Keyword, Op, Pos, Scope, Symbol, Whitespace};
use crate::common::{Keyword, Op, Pos, Symbol, Whitespace};
use crate::css::Css;
use crate::error::SassError;
pub use crate::error::SassResult;
@ -94,6 +94,7 @@ use crate::function::Function;
use crate::imports::import;
use crate::lexer::Lexer;
use crate::mixin::{eat_include, Mixin};
use crate::scope::{insert_global_var, Scope, GLOBAL_SCOPE};
use crate::selector::Selector;
use crate::style::Style;
use crate::utils::{devour_whitespace, eat_variable_value, IsComment, IsWhitespace, VariableDecl};
@ -111,12 +112,18 @@ mod function;
mod imports;
mod lexer;
mod mixin;
mod scope;
mod selector;
mod style;
mod units;
mod utils;
mod value;
pub(crate) fn error<E: Into<String>>(msg: E) -> ! {
eprintln!("Error: {}", msg.into());
std::process::exit(1);
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Token {
pos: Pos,
@ -391,7 +398,7 @@ impl<'a> StyleSheetParser<'a> {
| TokenKind::Symbol(Symbol::Mul)
| TokenKind::Symbol(Symbol::Percent)
| TokenKind::Symbol(Symbol::Period) => rules
.extend(self.eat_rules(&Selector::new(), &mut self.global_scope.clone())?),
.extend(self.eat_rules(&Selector::new(), &mut GLOBAL_SCOPE.with(|s| s.borrow().clone()))?),
TokenKind::Whitespace(_) => {
self.lexer.next();
continue;
@ -415,12 +422,17 @@ impl<'a> StyleSheetParser<'a> {
{
self.error(pos, "unexpected variable use at toplevel");
}
let VariableDecl { val, default } =
eat_variable_value(&mut self.lexer, &self.global_scope, &Selector::new())?;
if !default || self.global_scope.get_var(&name).is_err() {
self.global_scope.insert_var(&name, val)?;
let VariableDecl { val, default, .. } =
eat_variable_value(&mut self.lexer, &GLOBAL_SCOPE.with(|s| s.borrow().clone()), &Selector::new())?;
GLOBAL_SCOPE.with(|s| {
if !default || s.borrow().get_var(&name).is_err() {
match s.borrow_mut().insert_var(&name, val) {
Ok(..) => {},
Err(e) => error(e),
}
}
})
}
TokenKind::MultilineComment(_) => {
let comment = match self
.lexer
@ -435,7 +447,7 @@ impl<'a> StyleSheetParser<'a> {
}
TokenKind::AtRule(AtRuleKind::Include) => rules.extend(eat_include(
&mut self.lexer,
&self.global_scope,
&GLOBAL_SCOPE.with(|s| s.borrow().clone()),
&Selector::new(),
)?),
TokenKind::AtRule(AtRuleKind::Import) => {
@ -479,7 +491,9 @@ impl<'a> StyleSheetParser<'a> {
let (new_rules, new_scope) = import(file_name)?;
rules.extend(new_rules);
self.global_scope.extend(new_scope);
GLOBAL_SCOPE.with(|s| {
s.borrow_mut().extend(new_scope);
});
}
TokenKind::AtRule(_) => {
if let Some(Token {
@ -487,12 +501,16 @@ impl<'a> StyleSheetParser<'a> {
pos,
}) = self.lexer.next()
{
match AtRule::from_tokens(rule, pos, &mut self.lexer, &mut self.global_scope, &Selector::new())? {
match AtRule::from_tokens(rule, pos, &mut self.lexer, &mut GLOBAL_SCOPE.with(|s| s.borrow().clone()), &Selector::new())? {
AtRule::Mixin(name, mixin) => {
self.global_scope.insert_mixin(&name, *mixin);
GLOBAL_SCOPE.with(|s| {
s.borrow_mut().insert_mixin(&name, *mixin);
});
}
AtRule::Function(name, func) => {
self.global_scope.insert_fn(&name, *func);
GLOBAL_SCOPE.with(|s| {
s.borrow_mut().insert_fn(&name, *func);
});
}
AtRule::Charset => continue,
AtRule::Error(pos, message) => self.error(pos, &message),
@ -518,7 +536,7 @@ impl<'a> StyleSheetParser<'a> {
},
};
}
Ok((rules, self.global_scope))
Ok((rules, GLOBAL_SCOPE.with(|s| s.borrow().clone())))
}
fn eat_rules(&mut self, super_selector: &Selector, scope: &mut Scope) -> SassResult<Vec<Stmt>> {
@ -556,7 +574,7 @@ impl<'a> StyleSheetParser<'a> {
Expr::VariableDecl(name, val) => {
if self.scope == 0 {
scope.insert_var(&name, *val.clone())?;
self.global_scope.insert_var(&name, *val)?;
insert_global_var(&name, *val)?;
} else {
scope.insert_var(&name, *val)?;
}
@ -648,9 +666,14 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
{
toks.next();
devour_whitespace(toks);
let VariableDecl { val, default } =
eat_variable_value(toks, scope, super_selector)?;
if !default || scope.get_var(&name).is_err() {
let VariableDecl {
val,
default,
global,
} = eat_variable_value(toks, scope, super_selector)?;
if global {
insert_global_var(&name, val)?;
} else if !default || scope.get_var(&name).is_err() {
return Ok(Some(Expr::VariableDecl(name, Box::new(val))));
}
} else {

View File

@ -3,8 +3,9 @@ use std::vec::IntoIter;
use crate::args::{eat_call_args, eat_func_args, CallArgs, FuncArgs};
use crate::atrule::AtRule;
use crate::common::{Scope, Symbol};
use crate::common::Symbol;
use crate::error::{SassError, SassResult};
use crate::scope::Scope;
use crate::selector::Selector;
use crate::utils::devour_whitespace;
use crate::{eat_expr, Expr, RuleSet, Stmt, Token, TokenKind};

94
src/scope.rs Normal file
View File

@ -0,0 +1,94 @@
use std::cell::RefCell;
use std::collections::HashMap;
use crate::error::SassResult;
use crate::function::Function;
use crate::mixin::Mixin;
use crate::value::Value;
thread_local!(pub(crate) static GLOBAL_SCOPE: RefCell<Scope> = RefCell::new(Scope::new()));
pub(crate) fn get_global_var(s: &str) -> SassResult<Value> {
GLOBAL_SCOPE.with(|scope| match scope.borrow().vars().get(s) {
Some(v) => Ok(v.clone()),
None => Err("Undefined variable.".into()),
})
}
pub(crate) fn insert_global_var(s: &str, v: Value) -> SassResult<Option<Value>> {
GLOBAL_SCOPE.with(|scope| scope.borrow_mut().insert_var(s, v))
}
#[derive(Debug, Clone)]
pub(crate) struct Scope {
vars: HashMap<String, Value>,
mixins: HashMap<String, Mixin>,
functions: HashMap<String, Function>,
}
impl Scope {
#[must_use]
pub fn new() -> Self {
Self {
vars: HashMap::new(),
mixins: HashMap::new(),
functions: HashMap::new(),
}
}
pub fn vars(&self) -> &HashMap<String, Value> {
&self.vars
}
pub fn get_var(&self, v: &str) -> SassResult<Value> {
let v = &v.replace('_', "-");
match self.vars.get(v) {
Some(v) => Ok(v.clone()),
None => get_global_var(v),
}
}
pub fn insert_var(&mut self, s: &str, v: Value) -> SassResult<Option<Value>> {
Ok(self.vars.insert(s.replace('_', "-"), v.eval()?))
}
pub fn var_exists(&self, v: &str) -> bool {
self.vars.contains_key(&v.replace('_', "-"))
}
pub fn get_mixin(&self, v: &str) -> SassResult<&Mixin> {
match self.mixins.get(&v.replace('_', "-")) {
Some(v) => Ok(v),
None => Err("Undefined mixin.".into()),
}
}
pub fn insert_mixin(&mut self, s: &str, v: Mixin) -> Option<Mixin> {
self.mixins.insert(s.replace('_', "-"), v)
}
pub fn mixin_exists(&self, v: &str) -> bool {
self.mixins.contains_key(&v.replace('_', "-"))
}
pub fn get_fn(&self, v: &str) -> SassResult<&Function> {
match self.functions.get(&v.replace('_', "-")) {
Some(v) => Ok(v),
None => Err("Undefined function.".into()),
}
}
pub fn insert_fn(&mut self, s: &str, v: Function) -> Option<Function> {
self.functions.insert(s.replace('_', "-"), v)
}
pub fn fn_exists(&self, v: &str) -> bool {
self.functions.contains_key(&v.replace('_', "-"))
}
pub fn extend(&mut self, other: Scope) {
self.vars.extend(other.vars);
self.mixins.extend(other.mixins);
self.functions.extend(other.functions);
}
}

View File

@ -2,9 +2,10 @@ use std::fmt::{self, Display, Write};
use std::iter::Peekable;
use std::string::ToString;
use crate::common::{Scope, Symbol, Whitespace};
use crate::common::{Symbol, Whitespace};
use crate::error::SassResult;
use crate::lexer::Lexer;
use crate::scope::Scope;
use crate::utils::{
devour_whitespace, devour_whitespace_or_comment, flatten_ident, parse_interpolation,
parse_quoted_string, IsWhitespace,

View File

@ -1,8 +1,9 @@
use std::fmt::{self, Display};
use std::iter::Peekable;
use crate::common::{Pos, QuoteKind, Scope, Symbol};
use crate::common::{Pos, QuoteKind, Symbol};
use crate::error::SassResult;
use crate::scope::Scope;
use crate::selector::Selector;
use crate::utils::{devour_whitespace, parse_interpolation, parse_quoted_string};
use crate::value::Value;

View File

@ -83,11 +83,16 @@ pub(crate) fn parse_interpolation<I: Iterator<Item = Token>>(
pub(crate) struct VariableDecl {
pub val: Value,
pub default: bool,
pub global: bool,
}
impl VariableDecl {
pub const fn new(val: Value, default: bool) -> VariableDecl {
VariableDecl { val, default }
pub const fn new(val: Value, default: bool, global: bool) -> VariableDecl {
VariableDecl {
val,
default,
global,
}
}
}
@ -98,6 +103,7 @@ pub(crate) fn eat_variable_value<I: Iterator<Item = Token>>(
) -> SassResult<VariableDecl> {
devour_whitespace(toks);
let mut default = false;
let mut global = false;
let mut raw: Vec<Token> = Vec::new();
let mut nesting = 0;
while let Some(tok) = toks.peek() {
@ -110,6 +116,10 @@ pub(crate) fn eat_variable_value<I: Iterator<Item = Token>>(
toks.next();
default = true
}
TokenKind::Keyword(Keyword::Global) => {
toks.next();
global = true
}
TokenKind::Interpolation | TokenKind::Symbol(Symbol::OpenCurlyBrace) => {
nesting += 1;
raw.push(toks.next().unwrap());
@ -127,7 +137,7 @@ pub(crate) fn eat_variable_value<I: Iterator<Item = Token>>(
}
devour_whitespace(toks);
let val = Value::from_tokens(&mut raw.into_iter().peekable(), scope, super_selector).unwrap();
Ok(VariableDecl::new(val, default))
Ok(VariableDecl::new(val, default, global))
}
pub(crate) fn flatten_ident<I: Iterator<Item = Token>>(

View File

@ -8,8 +8,9 @@ use num_traits::pow;
use crate::args::eat_call_args;
use crate::builtin::GLOBAL_FUNCTIONS;
use crate::color::Color;
use crate::common::{Keyword, ListSeparator, Op, QuoteKind, Scope, Symbol};
use crate::common::{Keyword, ListSeparator, Op, QuoteKind, Symbol};
use crate::error::SassResult;
use crate::scope::Scope;
use crate::selector::Selector;
use crate::units::Unit;
use crate::utils::{

15
tests/scope.rs Normal file
View File

@ -0,0 +1,15 @@
#![cfg(test)]
#[macro_use]
mod macros;
test!(
scoping_var_decl_inner_ruleset,
"a {\n $color: red;\n b {\n $color: blue;\n }\n color: $color;\n}\n",
"a {\n color: blue;\n}\n"
);
test!(
basic_global,
"a {\n $color: red !global;\n}\n\nb {\n color: $color;\n}\n",
"b {\n color: red;\n}\n"
);