From 9bb7c05d19f1c57e08e64331a5d000019cf29efb Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Thu, 23 Apr 2020 18:14:42 -0400 Subject: [PATCH] improve handling of @ while scoping --- src/atrule/for_rule.rs | 1 + src/atrule/function.rs | 7 ++- src/atrule/if_rule.rs | 7 ++- src/atrule/mixin.rs | 6 +-- src/atrule/mod.rs | 1 + src/atrule/parse.rs | 9 +++- src/atrule/unknown.rs | 2 +- src/atrule/while_rule.rs | 24 ++++++++-- src/lib.rs | 30 +++++++------ src/scope.rs | 11 +++-- tests/while.rs | 94 ++++++++++++++++++++++++++++++++++++++++ 11 files changed, 162 insertions(+), 30 deletions(-) diff --git a/src/atrule/for_rule.rs b/src/atrule/for_rule.rs index 7e1a01b..4eda27d 100644 --- a/src/atrule/for_rule.rs +++ b/src/atrule/for_rule.rs @@ -46,6 +46,7 @@ impl For { &mut self.body.clone().into_iter().peekmore(), scope, super_selector, + false, )?); } Ok(stmts) diff --git a/src/atrule/function.rs b/src/atrule/function.rs index f7c7f6e..7aaad71 100644 --- a/src/atrule/function.rs +++ b/src/atrule/function.rs @@ -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)); diff --git a/src/atrule/if_rule.rs b/src/atrule/if_rule.rs index 555e8bf..4bd814c 100644 --- a/src/atrule/if_rule.rs +++ b/src/atrule/if_rule.rs @@ -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), diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index 7523c3f..13fe8cd 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -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>( 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() } diff --git a/src/atrule/mod.rs b/src/atrule/mod.rs index b29092d..8ec86ba 100644 --- a/src/atrule/mod.rs +++ b/src/atrule/mod.rs @@ -286,6 +286,7 @@ impl AtRule { &mut body.clone().into_iter().peekmore(), scope, super_selector, + false, )?); } Spanned { diff --git a/src/atrule/parse.rs b/src/atrule/parse.rs index 254c376..de97519 100644 --- a/src/atrule/parse.rs +++ b/src/atrule/parse.rs @@ -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>( toks: &mut PeekMoreIterator, scope: &mut Scope, super_selector: &Selector, + at_root: bool, ) -> SassResult>> { let mut stmts = Vec::new(); while let Some(expr) = eat_expr(toks, scope, super_selector)? { @@ -26,7 +27,7 @@ pub(crate) fn eat_stmts>( ), 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>( .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)), diff --git a/src/atrule/unknown.rs b/src/atrule/unknown.rs index 1d1964d..42e0b7e 100644 --- a/src/atrule/unknown.rs +++ b/src/atrule/unknown.rs @@ -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(); diff --git a/src/atrule/while_rule.rs b/src/atrule/while_rule.rs index 8a1b95a..9e64e0d 100644 --- a/src/atrule/while_rule.rs +++ b/src/atrule/while_rule.rs @@ -24,16 +24,32 @@ impl While { self, scope: &mut Scope, super_selector: &Selector, + at_root: bool, ) -> SassResult>> { 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>( 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 { diff --git a/src/lib.rs b/src/lib.rs index b99e50d..d38d28c 100644 --- a/src/lib.rs +++ b/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>( 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; diff --git a/src/scope.rs b/src/scope.rs index 4afd06f..54a6751 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -17,7 +17,7 @@ pub(crate) fn get_global_var(s: Spanned) -> SassResult> { } 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) -> SassResult>> { @@ -32,7 +32,12 @@ pub(crate) fn get_global_fn(s: Spanned) -> SassResult { } 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 { @@ -47,7 +52,7 @@ pub(crate) fn get_global_mixin(s: Spanned) -> SassResult { } 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 { diff --git a/tests/while.rs b/tests/while.rs index c73f30e..4725a72 100644 --- a/tests/while.rs +++ b/tests/while.rs @@ -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" +);