improve handling of @ while scoping

This commit is contained in:
ConnorSkees 2020-04-23 18:14:42 -04:00
parent ca318d47df
commit 9bb7c05d19
11 changed files with 162 additions and 30 deletions

View File

@ -46,6 +46,7 @@ impl For {
&mut self.body.clone().into_iter().peekmore(),
scope,
super_selector,
false,
)?);
}
Ok(stmts)

View File

@ -108,6 +108,7 @@ impl Function {
&mut std::mem::take(&mut self.body).into_iter().peekmore(),
&mut self.scope,
super_selector,
false,
)
}
@ -148,6 +149,7 @@ impl Function {
&mut f.body.clone().into_iter().peekmore(),
&mut self.scope,
super_selector,
false,
)?;
if let Some(v) = self.call(super_selector, for_stmts)? {
return Ok(Some(v));
@ -161,13 +163,14 @@ impl Function {
}
}
Stmt::AtRule(AtRule::While(w)) => {
let mut val = Value::from_vec(w.cond.clone(), &mut self.scope, super_selector)?;
let scope = &mut self.scope.clone();
let mut val = Value::from_vec(w.cond.clone(), scope, super_selector)?;
while val.node.is_true(val.span)? {
let while_stmts = eat_stmts(
&mut w.body.clone().into_iter().peekmore(),
&mut self.scope,
scope,
super_selector,
false,
)?;
if let Some(v) = self.call(super_selector, while_stmts)? {
return Ok(Some(v));

View File

@ -118,7 +118,12 @@ impl If {
if !found_true {
toks = self.else_;
}
for stmt in eat_stmts(&mut toks.into_iter().peekmore(), scope, super_selector)? {
for stmt in eat_stmts(
&mut toks.into_iter().peekmore(),
scope,
super_selector,
false,
)? {
match stmt.node {
Stmt::AtRule(AtRule::If(i)) => stmts.extend(i.eval(scope, super_selector)?),
Stmt::RuleSet(r) if r.selector.is_empty() => stmts.extend(r.rules),

View File

@ -127,7 +127,7 @@ impl Mixin {
stmts.extend(f.ruleset_eval(&mut self.scope, super_selector)?)
}
AtRule::While(w) => {
stmts.extend(w.ruleset_eval(&mut self.scope, super_selector)?)
stmts.extend(w.ruleset_eval(&mut self.scope, super_selector, false)?)
}
AtRule::Include(s) | AtRule::Each(s) => stmts.extend(s),
AtRule::If(i) => stmts.extend(i.eval(&mut self.scope.clone(), super_selector)?),
@ -223,9 +223,9 @@ pub(crate) fn eat_include<I: Iterator<Item = Token>>(
let content = if let Some(tok) = toks.peek() {
if tok.kind == '{' {
toks.next();
eat_stmts(toks, &mut scope.clone(), super_selector)?
eat_stmts(toks, &mut scope.clone(), super_selector, false)?
} else if has_content {
eat_stmts(toks, &mut scope.clone(), super_selector)?
eat_stmts(toks, &mut scope.clone(), super_selector, false)?
} else {
Vec::new()
}

View File

@ -286,6 +286,7 @@ impl AtRule {
&mut body.clone().into_iter().peekmore(),
scope,
super_selector,
false,
)?);
}
Spanned {

View File

@ -3,7 +3,7 @@ use codemap::Spanned;
use peekmore::PeekMoreIterator;
use crate::error::SassResult;
use crate::scope::Scope;
use crate::scope::{global_var_exists, insert_global_var, Scope};
use crate::selector::Selector;
use crate::{eat_expr, Expr, RuleSet, Stmt, Token};
@ -11,6 +11,7 @@ pub(crate) fn eat_stmts<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>,
scope: &mut Scope,
super_selector: &Selector,
at_root: bool,
) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new();
while let Some(expr) = eat_expr(toks, scope, super_selector)? {
@ -26,7 +27,7 @@ pub(crate) fn eat_stmts<I: Iterator<Item = Token>>(
),
Expr::MixinDecl(..) | Expr::FunctionDecl(..) => todo!(),
Expr::Selector(selector) => {
let rules = eat_stmts(toks, scope, &super_selector.zip(&selector))?;
let rules = eat_stmts(toks, scope, &super_selector.zip(&selector), at_root)?;
stmts.push(
Stmt::RuleSet(RuleSet {
super_selector: super_selector.clone(),
@ -36,7 +37,11 @@ pub(crate) fn eat_stmts<I: Iterator<Item = Token>>(
.span(span),
);
}
//TODO: refactor handling of `Expr::VariableDecl`, as most is already handled in `eat_expr`
Expr::VariableDecl(name, val) => {
if at_root && global_var_exists(&name) {
insert_global_var(&name, *val.clone())?;
}
scope.insert_var(&name, *val)?;
}
Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s).span(span)),

View File

@ -49,7 +49,7 @@ impl UnknownAtRule {
params.push(tok.kind);
}
let raw_body = eat_stmts(toks, scope, super_selector)?;
let raw_body = eat_stmts(toks, scope, super_selector, false)?;
let mut rules = Vec::with_capacity(raw_body.len());
let mut body = Vec::new();

View File

@ -24,16 +24,32 @@ impl While {
self,
scope: &mut Scope,
super_selector: &Selector,
at_root: bool,
) -> SassResult<Vec<Spanned<Stmt>>> {
let mut stmts = Vec::new();
let mut val = Value::from_vec(self.cond.clone(), scope, super_selector)?;
let scope = &mut scope.clone();
while val.node.is_true(val.span)? {
stmts.extend(eat_stmts(
for stmt in eat_stmts(
&mut self.body.clone().into_iter().peekmore(),
scope,
super_selector,
)?);
at_root,
)? {
match stmt.node {
Stmt::AtRule(AtRule::For(f)) => {
stmts.extend(f.ruleset_eval(scope, super_selector)?)
}
Stmt::AtRule(AtRule::While(w)) => {
stmts.extend(w.ruleset_eval(scope, super_selector, at_root)?)
}
Stmt::AtRule(AtRule::Include(s)) | Stmt::AtRule(AtRule::Each(s)) => {
stmts.extend(s)
}
Stmt::AtRule(AtRule::If(i)) => stmts.extend(i.eval(scope, super_selector)?),
_ => stmts.push(stmt),
}
}
val = Value::from_vec(self.cond.clone(), scope, super_selector)?;
}
Ok(stmts)
@ -53,9 +69,9 @@ pub(crate) fn parse_while<I: Iterator<Item = Token>>(
toks.next();
let body = read_until_closing_curly_brace(toks);
let mut body = read_until_closing_curly_brace(toks);
toks.next();
body.push(toks.next().unwrap());
devour_whitespace(toks);
Ok(Spanned {

View File

@ -93,7 +93,10 @@ pub use crate::error::{SassError, SassResult};
use crate::imports::import;
use crate::lexer::Lexer;
use crate::output::Css;
use crate::scope::{insert_global_fn, insert_global_mixin, insert_global_var, Scope, GLOBAL_SCOPE};
use crate::scope::{
global_var_exists, insert_global_fn, insert_global_mixin, insert_global_var, Scope,
GLOBAL_SCOPE,
};
use crate::selector::Selector;
use crate::style::Style;
pub(crate) use crate::token::Token;
@ -294,16 +297,9 @@ impl<'a> StyleSheetParser<'a> {
}
let VariableDecl { val, default, .. } =
eat_variable_value(&mut self.lexer, &Scope::new(), &Selector::new())?;
GLOBAL_SCOPE.with(|s| {
if !default || s.borrow().get_var(name.clone()).is_err() {
match s.borrow_mut().insert_var(&name.node, val) {
Ok(..) => Ok(()),
Err(e) => Err(e),
}
} else {
Ok(())
}
})?
if (default && !global_var_exists(&name)) || !default {
insert_global_var(&name.node, val)?;
}
}
'/' => {
self.lexer.next();
@ -379,7 +375,7 @@ impl<'a> StyleSheetParser<'a> {
)
}
AtRule::For(f) => rules.extend(f.ruleset_eval(&mut Scope::new(), &Selector::new())?),
AtRule::While(w) => rules.extend(w.ruleset_eval(&mut Scope::new(), &Selector::new())?),
AtRule::While(w) => rules.extend(w.ruleset_eval(&mut Scope::new(), &Selector::new(), true)?),
AtRule::Include(s)
| AtRule::Each(s) => rules.extend(s),
AtRule::Content => return Err(
@ -426,7 +422,9 @@ impl<'a> StyleSheetParser<'a> {
}),
Expr::AtRule(a) => match a {
AtRule::For(f) => stmts.extend(f.ruleset_eval(scope, super_selector)?),
AtRule::While(w) => stmts.extend(w.ruleset_eval(scope, super_selector)?),
AtRule::While(w) => {
stmts.extend(w.ruleset_eval(scope, super_selector, false)?)
}
AtRule::Include(s) | AtRule::Each(s) => stmts.extend(s),
AtRule::If(i) => stmts.extend(i.eval(scope, super_selector)?),
AtRule::Content => {
@ -605,12 +603,16 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
if global {
insert_global_var(&name.node, val.clone())?;
}
if !default || scope.get_var(name.clone()).is_err() {
let var_exists = scope.var_exists(&name.node);
if (default && !var_exists) || !default {
return Ok(Some(Spanned {
node: Expr::VariableDecl(name.node, Box::new(val)),
span,
}));
}
if !values.is_empty() {
todo!()
}
} else {
values.push(tok);
let mut current_pos = 0;

View File

@ -17,7 +17,7 @@ pub(crate) fn get_global_var(s: Spanned<String>) -> SassResult<Spanned<Value>> {
}
pub(crate) fn global_var_exists(v: &str) -> bool {
GLOBAL_SCOPE.with(|scope| scope.borrow().vars().contains_key(v))
GLOBAL_SCOPE.with(|scope| scope.borrow().vars().contains_key(&v.replace('_', "-")))
}
pub(crate) fn insert_global_var(s: &str, v: Spanned<Value>) -> SassResult<Option<Spanned<Value>>> {
@ -32,7 +32,12 @@ pub(crate) fn get_global_fn(s: Spanned<String>) -> SassResult<Function> {
}
pub(crate) fn global_fn_exists(v: &str) -> bool {
GLOBAL_SCOPE.with(|scope| scope.borrow().functions().contains_key(v))
GLOBAL_SCOPE.with(|scope| {
scope
.borrow()
.functions()
.contains_key(&v.replace('_', "-"))
})
}
pub(crate) fn insert_global_fn(s: &str, v: Function) -> Option<Function> {
@ -47,7 +52,7 @@ pub(crate) fn get_global_mixin(s: Spanned<String>) -> SassResult<Mixin> {
}
pub(crate) fn global_mixin_exists(v: &str) -> bool {
GLOBAL_SCOPE.with(|scope| scope.borrow().mixins().contains_key(v))
GLOBAL_SCOPE.with(|scope| scope.borrow().mixins().contains_key(&v.replace('_', "-")))
}
pub(crate) fn insert_global_mixin(s: &str, v: Mixin) -> Option<Mixin> {

View File

@ -32,3 +32,97 @@ test!(
"@function bar() {\n @while (true) {\n @return true;\n }\n}\n\na {\n color: bar();\n}\n",
"a {\n color: true;\n}\n"
);
test!(
nested_while_at_root_scope,
"$continue_inner: true;\n$continue_outer: true;\n\n@while $continue_outer {\n @while $continue_inner {\n $continue_inner: false;\n }\n\n $continue_outer: false;\n}\n\nresult {\n continue_outer: $continue_outer;\n continue_inner: $continue_inner;\n}\n",
"result {\n continue_outer: false;\n continue_inner: false;\n}\n"
);
test!(
nested_while_not_at_root_scope,
"$continue_inner: true;\n$continue_outer: true;\n\nresult {\n @while $continue_outer {\n @while $continue_inner {\n $continue_inner: false;\n }\n\n $continue_outer: false;\n }\n\n continue_outer: $continue_outer;\n continue_inner: $continue_inner;\n}\n",
"result {\n continue_outer: true;\n continue_inner: true;\n}\n"
);
test!(
local_scope_at_root,
"$continue_inner: true;
$continue_outer: true;
@while $continue_outer {
$local_implicit: outer;
$local_explicit: outer !global;
$local_default: outer !default;
@while $continue_inner {
$local_implicit: inner;
$local_explicit: inner !global;
$local_default: inner !default;
$continue_inner: false;
}
$continue_outer: false;
}
result {
@if variable-exists(local_default) {
local_default: $local_default;
}
@if variable-exists(local_implicit) {
local_implicit: $local_implicit;
}
@if variable-exists(local_explicit) {
local_explicit: $local_explicit;
}
}",
"result {\n local_explicit: inner;\n}\n"
);
test!(
global_scope_at_root,
"$continue_inner: true;
$continue_outer: true;
$root_default: initial;
$root_implicit: initial;
$root_explicit: initial !global;
@while $continue_outer {
$root_implicit: outer;
$root_explicit: outer !global;
$root_default: outer !default;
@while $continue_inner {
$root_implicit: inner;
$root_explicit: inner !global;
$root_default: inner !default;
$continue_inner: false;
}
$continue_outer: false;
}
result {
root_default: $root_default;
root_implicit: $root_implicit;
root_explicit: $root_explicit;
}",
"result {\n root_default: initial;\n root_implicit: inner;\n root_explicit: inner;\n}\n"
);
test!(
if_inside_while,
"$continue_outer: true;
@while $continue_outer {
a {
color: red;
}
@if true {
$continue_outer: false;
}
a {
color: blue;
}
}",
"a {\n color: red;\n}\n\na {\n color: blue;\n}\n"
);