improve handling of @ while scoping
This commit is contained in:
parent
ca318d47df
commit
9bb7c05d19
@ -46,6 +46,7 @@ impl For {
|
||||
&mut self.body.clone().into_iter().peekmore(),
|
||||
scope,
|
||||
super_selector,
|
||||
false,
|
||||
)?);
|
||||
}
|
||||
Ok(stmts)
|
||||
|
@ -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));
|
||||
|
@ -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),
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -286,6 +286,7 @@ impl AtRule {
|
||||
&mut body.clone().into_iter().peekmore(),
|
||||
scope,
|
||||
super_selector,
|
||||
false,
|
||||
)?);
|
||||
}
|
||||
Spanned {
|
||||
|
@ -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)),
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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 {
|
||||
|
30
src/lib.rs
30
src/lib.rs
@ -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;
|
||||
|
11
src/scope.rs
11
src/scope.rs
@ -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> {
|
||||
|
@ -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"
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user