refactor how scopes are calculated

This commit is contained in:
Connor Skees 2020-07-08 14:51:04 -04:00
parent 3a5526ab26
commit 47902c077c
16 changed files with 371 additions and 173 deletions

View File

@ -2,13 +2,13 @@ use std::hash::{Hash, Hasher};
use codemap::Span; use codemap::Span;
use crate::{args::FuncArgs, scope::Scope, Token}; use crate::{args::FuncArgs, Token};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct Function { pub(crate) struct Function {
pub scope: Scope,
pub args: FuncArgs, pub args: FuncArgs,
pub body: Vec<Token>, pub body: Vec<Token>,
pub declared_at_root: bool,
pos: Span, pos: Span,
} }
@ -27,12 +27,12 @@ impl PartialEq for Function {
impl Eq for Function {} impl Eq for Function {}
impl Function { impl Function {
pub fn new(scope: Scope, args: FuncArgs, body: Vec<Token>, pos: Span) -> Self { pub fn new(args: FuncArgs, body: Vec<Token>, declared_at_root: bool, pos: Span) -> Self {
Function { Function {
scope,
args, args,
body, body,
pos, pos,
declared_at_root,
} }
} }
} }

View File

@ -1,25 +1,25 @@
use crate::{args::FuncArgs, scope::Scope, Token}; use crate::{args::FuncArgs, scope::Scopes, Token};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct Mixin { pub(crate) struct Mixin {
pub scope: Scope,
pub args: FuncArgs, pub args: FuncArgs,
pub body: Vec<Token>, pub body: Vec<Token>,
pub accepts_content_block: bool, pub accepts_content_block: bool,
pub declared_at_root: bool,
} }
impl Mixin { impl Mixin {
pub fn new( pub fn new(
scope: Scope,
args: FuncArgs, args: FuncArgs,
body: Vec<Token>, body: Vec<Token>,
accepts_content_block: bool, accepts_content_block: bool,
declared_at_root: bool,
) -> Self { ) -> Self {
Mixin { Mixin {
scope,
args, args,
body, body,
accepts_content_block, accepts_content_block,
declared_at_root,
} }
} }
} }
@ -28,15 +28,15 @@ impl Mixin {
pub(crate) struct Content { pub(crate) struct Content {
pub content: Option<Vec<Token>>, pub content: Option<Vec<Token>>,
pub content_args: Option<FuncArgs>, pub content_args: Option<FuncArgs>,
pub scope: Scope, pub scopes: Scopes,
} }
impl Content { impl Content {
pub fn new() -> Self { pub const fn new() -> Self {
Self { Self {
content: None, content: None,
content_args: None, content_args: None,
scope: Scope::new(), scopes: Scopes::new(),
} }
} }
} }

View File

@ -96,10 +96,7 @@ fn variable_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Va
args.max_args(1)?; args.max_args(1)?;
match parser.arg(&mut args, 0, "name")? { match parser.arg(&mut args, 0, "name")? {
Value::String(s, _) => Ok(Value::bool( Value::String(s, _) => Ok(Value::bool(
parser parser.scopes.var_exists(&s.into(), parser.global_scope),
.scopes
.last()
.var_exists(&s.into(), parser.global_scope),
)), )),
v => Err(( v => Err((
format!("$name: {} is not a string.", v.inspect(args.span())?), format!("$name: {} is not a string.", v.inspect(args.span())?),
@ -112,9 +109,7 @@ fn variable_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Va
fn global_variable_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> { fn global_variable_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value> {
args.max_args(1)?; args.max_args(1)?;
match parser.arg(&mut args, 0, "name")? { match parser.arg(&mut args, 0, "name")? {
Value::String(s, _) => Ok(Value::bool( Value::String(s, _) => Ok(Value::bool(parser.global_scope.var_exists(&s.into()))),
parser.global_scope.var_exists_no_global(&s.into()),
)),
v => Err(( v => Err((
format!("$name: {} is not a string.", v.inspect(args.span())?), format!("$name: {} is not a string.", v.inspect(args.span())?),
args.span(), args.span(),
@ -127,7 +122,7 @@ fn mixin_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value
args.max_args(2)?; args.max_args(2)?;
match parser.arg(&mut args, 0, "name")? { match parser.arg(&mut args, 0, "name")? {
Value::String(s, _) => Ok(Value::bool( Value::String(s, _) => Ok(Value::bool(
parser.scopes.last().mixin_exists(s, parser.global_scope), parser.scopes.mixin_exists(&s.into(), parser.global_scope),
)), )),
v => Err(( v => Err((
format!("$name: {} is not a string.", v.inspect(args.span())?), format!("$name: {} is not a string.", v.inspect(args.span())?),
@ -141,7 +136,7 @@ fn function_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Va
args.max_args(2)?; args.max_args(2)?;
match parser.arg(&mut args, 0, "name")? { match parser.arg(&mut args, 0, "name")? {
Value::String(s, _) => Ok(Value::bool( Value::String(s, _) => Ok(Value::bool(
parser.scopes.last().fn_exists(s, parser.global_scope), parser.scopes.fn_exists(&s.into(), parser.global_scope),
)), )),
v => Err(( v => Err((
format!("$name: {} is not a string.", v.inspect(args.span())?), format!("$name: {} is not a string.", v.inspect(args.span())?),
@ -186,7 +181,7 @@ fn get_function(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value
.into()); .into());
} }
let func = match parser.scopes.last().get_fn( let func = match parser.scopes.get_fn(
Spanned { Spanned {
node: &name, node: &name,
span: args.span(), span: args.span(),

View File

@ -98,7 +98,7 @@ use crate::{
common::{ContextFlags, NeverEmptyVec}, common::{ContextFlags, NeverEmptyVec},
Parser, Parser,
}, },
scope::Scope, scope::{Scope, Scopes},
selector::{Extender, Selector}, selector::{Extender, Selector},
}; };
@ -148,7 +148,7 @@ pub fn from_path(p: &str) -> Result<String> {
.peekmore(), .peekmore(),
map: &mut map, map: &mut map,
path: p.as_ref(), path: p.as_ref(),
scopes: &mut NeverEmptyVec::new(Scope::new()), scopes: &mut Scopes::new(),
global_scope: &mut Scope::new(), global_scope: &mut Scope::new(),
super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)), super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)),
span_before: empty_span, span_before: empty_span,
@ -190,7 +190,7 @@ pub fn from_string(p: String) -> Result<String> {
.peekmore(), .peekmore(),
map: &mut map, map: &mut map,
path: Path::new(""), path: Path::new(""),
scopes: &mut NeverEmptyVec::new(Scope::new()), scopes: &mut Scopes::new(),
global_scope: &mut Scope::new(), global_scope: &mut Scope::new(),
super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)), super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)),
span_before: empty_span, span_before: empty_span,
@ -223,7 +223,7 @@ pub fn from_string(p: String) -> std::result::Result<String, JsValue> {
.peekmore(), .peekmore(),
map: &mut map, map: &mut map,
path: Path::new(""), path: Path::new(""),
scopes: &mut NeverEmptyVec::new(Scope::new()), scopes: &mut Scopes::new(),
global_scope: &mut Scope::new(), global_scope: &mut Scope::new(),
super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)), super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)),
span_before: empty_span, span_before: empty_span,

View File

@ -355,13 +355,12 @@ impl<'a> Parser<'a> {
&mut self, &mut self,
mut fn_args: FuncArgs, mut fn_args: FuncArgs,
mut args: CallArgs, mut args: CallArgs,
scope: &mut Scope, ) -> SassResult<Scope> {
) -> SassResult<()> { let mut scope = Scope::new();
self.scopes.push(self.scopes.last().clone()); self.scopes.enter_new_scope();
for (idx, arg) in fn_args.0.iter_mut().enumerate() { for (idx, arg) in fn_args.0.iter_mut().enumerate() {
if arg.is_variadic { if arg.is_variadic {
let span = args.span(); let span = args.span();
// todo: does this get the most recent scope?
let arg_list = Value::ArgList(self.variadic_args(args)?); let arg_list = Value::ArgList(self.variadic_args(args)?);
scope.insert_var( scope.insert_var(
arg.name.clone(), arg.name.clone(),
@ -383,12 +382,10 @@ impl<'a> Parser<'a> {
} }
}, },
}?; }?;
self.scopes self.scopes.insert_var(arg.name.clone(), val.clone());
.last_mut()
.insert_var(arg.name.clone(), val.clone());
scope.insert_var(mem::take(&mut arg.name), val); scope.insert_var(mem::take(&mut arg.name), val);
} }
self.scopes.pop(); self.scopes.exit_scope();
Ok(()) Ok(scope)
} }
} }

View File

@ -1,7 +1,4 @@
use std::{ use std::ops::{BitAnd, BitOr};
ops::{BitAnd, BitOr},
slice::IterMut,
};
use codemap::Spanned; use codemap::Spanned;
@ -25,18 +22,6 @@ impl<T> NeverEmptyVec<T> {
self.rest.last().unwrap_or(&self.first) self.rest.last().unwrap_or(&self.first)
} }
pub fn last_mut(&mut self) -> &mut T {
self.rest.last_mut().unwrap_or(&mut self.first)
}
pub fn first(&mut self) -> &T {
&self.first
}
pub fn first_mut(&mut self) -> &mut T {
&mut self.first
}
pub fn push(&mut self, value: T) { pub fn push(&mut self, value: T) {
self.rest.push(value) self.rest.push(value)
} }
@ -45,14 +30,6 @@ impl<T> NeverEmptyVec<T> {
self.rest.pop() self.rest.pop()
} }
pub fn iter_mut(&mut self) -> IterMut<'_, T> {
self.rest.iter_mut()
}
pub fn len(&self) -> usize {
self.rest.len()
}
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.rest.is_empty() self.rest.is_empty()
} }

View File

@ -288,10 +288,10 @@ impl<'a> Parser<'a> {
let mut stmts = Vec::new(); let mut stmts = Vec::new();
self.scopes.push(self.scopes.last().clone()); self.scopes.enter_new_scope();
for i in iter { for i in iter {
self.scopes.last_mut().insert_var( self.scopes.insert_var(
var.node.clone(), var.node.clone(),
Spanned { Spanned {
node: Value::Dimension(Number::from(i), Unit::None), node: Value::Dimension(Number::from(i), Unit::None),
@ -338,7 +338,7 @@ impl<'a> Parser<'a> {
} }
} }
self.scopes.pop(); self.scopes.exit_scope();
Ok(stmts) Ok(stmts)
} }
@ -364,7 +364,7 @@ impl<'a> Parser<'a> {
let mut stmts = Vec::new(); let mut stmts = Vec::new();
let mut val = self.parse_value_from_vec(cond.clone())?; let mut val = self.parse_value_from_vec(cond.clone())?;
self.scopes.push(self.scopes.last().clone()); self.scopes.enter_new_scope();
while val.node.is_true() { while val.node.is_true() {
if self.flags.in_function() { if self.flags.in_function() {
let these_stmts = Parser { let these_stmts = Parser {
@ -406,7 +406,7 @@ impl<'a> Parser<'a> {
} }
val = self.parse_value_from_vec(cond.clone())?; val = self.parse_value_from_vec(cond.clone())?;
} }
self.scopes.pop(); self.scopes.exit_scope();
Ok(stmts) Ok(stmts)
} }
@ -459,7 +459,7 @@ impl<'a> Parser<'a> {
for row in iter { for row in iter {
if vars.len() == 1 { if vars.len() == 1 {
self.scopes.last_mut().insert_var( self.scopes.insert_var(
vars[0].node.clone(), vars[0].node.clone(),
Spanned { Spanned {
node: row, node: row,
@ -472,7 +472,7 @@ impl<'a> Parser<'a> {
.into_iter() .into_iter()
.chain(std::iter::once(Value::Null).cycle()), .chain(std::iter::once(Value::Null).cycle()),
) { ) {
self.scopes.last_mut().insert_var( self.scopes.insert_var(
var.node.clone(), var.node.clone(),
Spanned { Spanned {
node: val, node: val,

View File

@ -6,15 +6,16 @@ use crate::{
atrule::Function, atrule::Function,
common::unvendor, common::unvendor,
error::SassResult, error::SassResult,
scope::Scopes,
utils::{read_until_closing_curly_brace, read_until_semicolon_or_closing_curly_brace}, utils::{read_until_closing_curly_brace, read_until_semicolon_or_closing_curly_brace},
value::Value, value::Value,
Token, Token,
}; };
use super::{common::ContextFlags, NeverEmptyVec, Parser, Stmt}; use super::{common::ContextFlags, Parser, Stmt};
/// Names that functions are not allowed to have /// Names that functions are not allowed to have
const FORBIDDEN_IDENTIFIERS: [&str; 7] = const RESERVED_IDENTIFIERS: [&str; 7] =
["calc", "element", "expression", "url", "and", "or", "not"]; ["calc", "element", "expression", "url", "and", "or", "not"];
impl<'a> Parser<'a> { impl<'a> Parser<'a> {
@ -30,7 +31,7 @@ impl<'a> Parser<'a> {
return Err(("Functions may not be declared in control directives.", span).into()); return Err(("Functions may not be declared in control directives.", span).into());
} }
if FORBIDDEN_IDENTIFIERS.contains(&unvendor(&name)) { if RESERVED_IDENTIFIERS.contains(&unvendor(&name)) {
return Err(("Invalid function name.", span).into()); return Err(("Invalid function name.", span).into());
} }
@ -50,12 +51,12 @@ impl<'a> Parser<'a> {
}); });
self.whitespace(); self.whitespace();
let function = Function::new(self.scopes.last().clone(), args, body, span); let function = Function::new(args, body, self.at_root, span);
if self.at_root { if self.at_root {
self.global_scope.insert_fn(name, function); self.global_scope.insert_fn(name, function);
} else { } else {
self.scopes.last_mut().insert_fn(name, function); self.scopes.insert_fn(name.into(), function);
} }
Ok(()) Ok(())
} }
@ -71,19 +72,32 @@ impl<'a> Parser<'a> {
pub fn eval_function(&mut self, function: Function, args: CallArgs) -> SassResult<Value> { pub fn eval_function(&mut self, function: Function, args: CallArgs) -> SassResult<Value> {
let Function { let Function {
mut scope,
body, body,
args: fn_args, args: fn_args,
declared_at_root,
.. ..
} = function; } = function;
self.eval_args(fn_args, args, &mut scope)?; let scope = self.eval_args(fn_args, args)?;
let mut new_scope = Scopes::new();
let mut entered_scope = false;
if declared_at_root {
new_scope.enter_scope(scope);
} else {
entered_scope = true;
self.scopes.enter_scope(scope);
};
let mut return_value = Parser { let mut return_value = Parser {
toks: &mut body.into_iter().peekmore(), toks: &mut body.into_iter().peekmore(),
map: self.map, map: self.map,
path: self.path, path: self.path,
scopes: &mut NeverEmptyVec::new(scope), scopes: if declared_at_root {
&mut new_scope
} else {
self.scopes
},
global_scope: self.global_scope, global_scope: self.global_scope,
super_selectors: self.super_selectors, super_selectors: self.super_selectors,
span_before: self.span_before, span_before: self.span_before,
@ -95,6 +109,10 @@ impl<'a> Parser<'a> {
} }
.parse()?; .parse()?;
if entered_scope {
self.scopes.exit_scope();
}
debug_assert!(return_value.len() <= 1); debug_assert!(return_value.len() <= 1);
match return_value match return_value
.pop() .pop()

View File

@ -6,11 +6,12 @@ use crate::{
args::{CallArgs, FuncArgs}, args::{CallArgs, FuncArgs},
atrule::{Content, Mixin}, atrule::{Content, Mixin},
error::SassResult, error::SassResult,
scope::Scopes,
utils::read_until_closing_curly_brace, utils::read_until_closing_curly_brace,
Token, Token,
}; };
use super::{common::ContextFlags, NeverEmptyVec, Parser, Stmt}; use super::{common::ContextFlags, Parser, Stmt};
impl<'a> Parser<'a> { impl<'a> Parser<'a> {
pub(super) fn parse_mixin(&mut self) -> SassResult<()> { pub(super) fn parse_mixin(&mut self) -> SassResult<()> {
@ -39,19 +40,19 @@ impl<'a> Parser<'a> {
// this is blocked on figuring out just how to check for this. presumably we could have a check // this is blocked on figuring out just how to check for this. presumably we could have a check
// not when parsing initially, but rather when `@include`ing to see if an `@content` was found. // not when parsing initially, but rather when `@include`ing to see if an `@content` was found.
let mixin = Mixin::new(self.scopes.last().clone(), args, body, false); let mixin = Mixin::new(args, body, false, self.at_root);
if self.at_root { if self.at_root {
self.global_scope.insert_mixin(name, mixin); self.global_scope.insert_mixin(name, mixin);
} else { } else {
self.scopes.last_mut().insert_mixin(name, mixin); self.scopes.insert_mixin(name.into(), mixin);
} }
Ok(()) Ok(())
} }
pub(super) fn parse_include(&mut self) -> SassResult<Vec<Stmt>> { pub(super) fn parse_include(&mut self) -> SassResult<Vec<Stmt>> {
self.whitespace_or_comment(); self.whitespace_or_comment();
let name = self.parse_identifier()?; let name = self.parse_identifier()?.map_node(Into::into);
self.whitespace_or_comment(); self.whitespace_or_comment();
@ -106,24 +107,44 @@ impl<'a> Parser<'a> {
} }
let Mixin { let Mixin {
mut scope,
body, body,
args: fn_args, args: fn_args,
declared_at_root,
.. ..
} = self.scopes.last().get_mixin(name, self.global_scope)?; } = self.scopes.get_mixin(
self.eval_args(fn_args, args, &mut scope)?; {
let Spanned { ref node, span } = name;
Spanned { node, span }
},
self.global_scope,
)?;
let scope = self.eval_args(fn_args, args)?;
let mut new_scope = Scopes::new();
let mut entered_scope = false;
if declared_at_root {
new_scope.enter_scope(scope);
} else {
entered_scope = true;
self.scopes.enter_scope(scope);
};
self.content.push(Content { self.content.push(Content {
content, content,
content_args, content_args,
scope: self.scopes.last().clone(), scopes: self.scopes.clone(),
}); });
let body = Parser { let body = Parser {
toks: &mut body.into_iter().peekmore(), toks: &mut body.into_iter().peekmore(),
map: self.map, map: self.map,
path: self.path, path: self.path,
scopes: &mut NeverEmptyVec::new(scope), scopes: if declared_at_root {
&mut new_scope
} else {
self.scopes
},
global_scope: self.global_scope, global_scope: self.global_scope,
super_selectors: self.super_selectors, super_selectors: self.super_selectors,
span_before: self.span_before, span_before: self.span_before,
@ -136,6 +157,9 @@ impl<'a> Parser<'a> {
.parse()?; .parse()?;
self.content.pop(); self.content.pop();
if entered_scope {
self.scopes.exit_scope();
}
Ok(body) Ok(body)
} }
@ -147,7 +171,7 @@ impl<'a> Parser<'a> {
.last() .last()
.cloned() .cloned()
.unwrap_or_else(Content::new) .unwrap_or_else(Content::new)
.scope; .scopes;
if let Some(Token { kind: '(', .. }) = self.toks.peek() { if let Some(Token { kind: '(', .. }) = self.toks.peek() {
self.toks.next(); self.toks.next();
let args = self.parse_call_args()?; let args = self.parse_call_args()?;
@ -156,7 +180,7 @@ impl<'a> Parser<'a> {
{ {
args.max_args(content_args.len())?; args.max_args(content_args.len())?;
self.eval_args(content_args, args, &mut scope)?; scope.merge(self.eval_args(content_args, args)?);
} else { } else {
args.max_args(0)?; args.max_args(0)?;
} }
@ -168,7 +192,7 @@ impl<'a> Parser<'a> {
toks: &mut body.into_iter().peekmore(), toks: &mut body.into_iter().peekmore(),
map: self.map, map: self.map,
path: self.path, path: self.path,
scopes: &mut NeverEmptyVec::new(scope), scopes: &mut scope,
global_scope: self.global_scope, global_scope: self.global_scope,
super_selectors: self.super_selectors, super_selectors: self.super_selectors,
span_before: self.span_before, span_before: self.span_before,
@ -183,6 +207,7 @@ impl<'a> Parser<'a> {
Vec::new() Vec::new()
}; };
self.content.push(content.clone()); self.content.push(content.clone());
self.scopes.exit_scope();
stmts stmts
} else { } else {
Vec::new() Vec::new()

View File

@ -10,7 +10,7 @@ use crate::{
AtRuleKind, Content, SupportsRule, UnknownAtRule, AtRuleKind, Content, SupportsRule, UnknownAtRule,
}, },
error::SassResult, error::SassResult,
scope::Scope, scope::{Scope, Scopes},
selector::{ selector::{
ComplexSelectorComponent, ExtendRule, ExtendedSelector, Extender, Selector, SelectorParser, ComplexSelectorComponent, ExtendRule, ExtendedSelector, Extender, Selector, SelectorParser,
}, },
@ -69,7 +69,7 @@ pub(crate) struct Parser<'a> {
pub map: &'a mut CodeMap, pub map: &'a mut CodeMap,
pub path: &'a Path, pub path: &'a Path,
pub global_scope: &'a mut Scope, pub global_scope: &'a mut Scope,
pub scopes: &'a mut NeverEmptyVec<Scope>, pub scopes: &'a mut Scopes,
pub super_selectors: &'a mut NeverEmptyVec<Selector>, pub super_selectors: &'a mut NeverEmptyVec<Selector>,
pub span_before: Span, pub span_before: Span,
pub content: &'a mut Vec<Content>, pub content: &'a mut Vec<Content>,
@ -237,10 +237,10 @@ impl<'a> Parser<'a> {
} }
SelectorOrStyle::Selector(init) => { SelectorOrStyle::Selector(init) => {
let selector = self.parse_keyframes_selector(init)?; let selector = self.parse_keyframes_selector(init)?;
self.scopes.push(self.scopes.last().clone()); self.scopes.enter_new_scope();
let body = self.parse_stmt()?; let body = self.parse_stmt()?;
self.scopes.pop(); self.scopes.exit_scope();
stmts.push(Stmt::KeyframesRuleSet(Box::new(KeyframesRuleSet { stmts.push(Stmt::KeyframesRuleSet(Box::new(KeyframesRuleSet {
selector, selector,
body, body,
@ -271,13 +271,13 @@ impl<'a> Parser<'a> {
self.super_selectors.last(), self.super_selectors.last(),
!at_root || self.at_root_has_selector, !at_root || self.at_root_has_selector,
)?; )?;
self.scopes.push(self.scopes.last().clone()); self.scopes.enter_new_scope();
self.super_selectors.push(selector.clone()); self.super_selectors.push(selector.clone());
let extended_selector = self.extender.add_selector(selector.0, None); let extended_selector = self.extender.add_selector(selector.0, None);
let body = self.parse_stmt()?; let body = self.parse_stmt()?;
self.scopes.pop(); self.scopes.exit_scope();
self.super_selectors.pop(); self.super_selectors.pop();
self.at_root = self.super_selectors.is_empty(); self.at_root = self.super_selectors.is_empty();
stmts.push(Stmt::RuleSet { stmts.push(Stmt::RuleSet {

View File

@ -255,7 +255,7 @@ impl<'a> Parser<'a> {
} }
let as_ident = Identifier::from(&s); let as_ident = Identifier::from(&s);
let func = match self.scopes.last().get_fn( let func = match self.scopes.get_fn(
Spanned { Spanned {
node: &as_ident, node: &as_ident,
span, span,
@ -527,7 +527,13 @@ impl<'a> Parser<'a> {
Err(e) => return Some(Err(e)), Err(e) => return Some(Err(e)),
}; };
IntermediateValue::Value(HigherIntermediateValue::Literal( IntermediateValue::Value(HigherIntermediateValue::Literal(
match self.scopes.last().get_var(&val, self.global_scope) { match self.scopes.get_var(
{
let Spanned { ref node, span } = val;
Spanned { node, span }
},
self.global_scope,
) {
Ok(v) => v.clone(), Ok(v) => v.clone(),
Err(e) => return Some(Err(e)), Err(e) => return Some(Err(e)),
}, },

View File

@ -47,46 +47,28 @@ impl<'a> Parser<'a> {
if value.default { if value.default {
if self.at_root && !self.flags.in_control_flow() { if self.at_root && !self.flags.in_control_flow() {
if !self.global_scope.var_exists_no_global(&ident) { if !self.global_scope.var_exists(&ident) {
self.global_scope.insert_var(ident, value.value); self.global_scope.insert_var(ident, value.value);
} }
} else { } else {
if value.global && !self.global_scope.var_exists_no_global(&ident) { if value.global && !self.global_scope.var_exists(&ident) {
self.global_scope self.global_scope
.insert_var(ident.clone(), value.value.clone()); .insert_var(ident.clone(), value.value.clone());
} }
if !self.scopes.last().var_exists_no_global(&ident) { self.scopes.insert_default_var(ident, value.value);
self.scopes.last_mut().insert_var(ident, value.value);
}
} }
} else if self.at_root { } else if self.at_root {
if self.flags.in_control_flow() { if self.flags.in_control_flow() {
if self.global_scope.var_exists_no_global(&ident) { if self.global_scope.var_exists(&ident) {
self.global_scope.insert_var(ident, value.value); self.global_scope.insert_var(ident, value.value);
} else { } else {
self.scopes.last_mut().insert_var(ident, value.value); self.scopes.insert_var(ident, value.value);
} }
} else { } else {
self.global_scope.insert_var(ident, value.value); self.global_scope.insert_var(ident, value.value);
} }
} else { } else {
let len = self.scopes.len(); self.scopes.insert_var(ident, value.value);
for (_, scope) in self
.scopes
.iter_mut()
.enumerate()
.filter(|(i, _)| *i != len)
{
if scope.var_exists_no_global(&ident) {
scope.insert_var(ident.clone(), value.value.clone());
}
}
if self.scopes.first().var_exists_no_global(&ident) {
self.scopes
.first_mut()
.insert_var(ident.clone(), value.value.clone());
}
self.scopes.last_mut().insert_var(ident, value.value);
} }
Ok(()) Ok(())
} }

View File

@ -27,95 +27,212 @@ impl Scope {
} }
} }
fn get_var_no_global(&self, name: &Spanned<Identifier>) -> SassResult<&Value> { fn get_var(&self, name: Spanned<&Identifier>) -> SassResult<&Value> {
match self.vars.get(&name.node) { match self.vars.get(name.node) {
Some(v) => Ok(&v.node), Some(v) => Ok(&v.node),
None => Err(("Undefined variable.", name.span).into()), None => Err(("Undefined variable.", name.span).into()),
} }
} }
pub fn get_var<'a>(
&'a self,
name: &Spanned<Identifier>,
global_scope: &'a Scope,
) -> SassResult<&Value> {
match self.vars.get(&name.node) {
Some(v) => Ok(&v.node),
None => global_scope.get_var_no_global(name),
}
}
pub fn insert_var(&mut self, s: Identifier, v: Spanned<Value>) -> Option<Spanned<Value>> { pub fn insert_var(&mut self, s: Identifier, v: Spanned<Value>) -> Option<Spanned<Value>> {
self.vars.insert(s, v) self.vars.insert(s, v)
} }
pub fn var_exists_no_global(&self, name: &Identifier) -> bool { pub fn var_exists(&self, name: &Identifier) -> bool {
self.vars.contains_key(name) self.vars.contains_key(name)
} }
pub fn var_exists<'a, T: Into<&'a Identifier>>(&self, v: T, global_scope: &Scope) -> bool { fn get_mixin(&self, name: Spanned<&Identifier>) -> SassResult<Mixin> {
let name = v.into(); match self.mixins.get(name.node) {
self.vars.contains_key(name) || global_scope.var_exists_no_global(name)
}
fn get_mixin_no_global(&self, name: &Spanned<Identifier>) -> SassResult<Mixin> {
match self.mixins.get(&name.node) {
Some(v) => Ok(v.clone()), Some(v) => Ok(v.clone()),
None => Err(("Undefined mixin.", name.span).into()), None => Err(("Undefined mixin.", name.span).into()),
} }
} }
pub fn get_mixin<T: Into<Identifier>>(
&self,
name: Spanned<T>,
global_scope: &Scope,
) -> SassResult<Mixin> {
let name = name.map_node(Into::into);
match self.mixins.get(&name.node) {
Some(v) => Ok(v.clone()),
None => global_scope.get_mixin_no_global(&name),
}
}
pub fn insert_mixin<T: Into<Identifier>>(&mut self, s: T, v: Mixin) -> Option<Mixin> { pub fn insert_mixin<T: Into<Identifier>>(&mut self, s: T, v: Mixin) -> Option<Mixin> {
self.mixins.insert(s.into(), v) self.mixins.insert(s.into(), v)
} }
fn mixin_exists_no_global(&self, name: &Identifier) -> bool { fn mixin_exists(&self, name: &Identifier) -> bool {
self.mixins.contains_key(name) self.mixins.contains_key(name)
} }
pub fn mixin_exists<T: Into<Identifier>>(&self, v: T, global_scope: &Scope) -> bool { fn get_fn(&self, name: Spanned<&Identifier>) -> SassResult<Function> {
let name = v.into();
self.mixins.contains_key(&name) || global_scope.mixin_exists_no_global(&name)
}
fn get_fn_no_global(&self, name: Spanned<&Identifier>) -> SassResult<Function> {
match self.functions.get(name.node) { match self.functions.get(name.node) {
Some(v) => Ok(v.clone()), Some(v) => Ok(v.clone()),
None => Err(("Undefined function.", name.span).into()), None => Err(("Undefined function.", name.span).into()),
} }
} }
pub fn get_fn(&self, name: Spanned<&Identifier>, global_scope: &Scope) -> SassResult<Function> {
match self.functions.get(name.node) {
Some(v) => Ok(v.clone()),
None => global_scope.get_fn_no_global(name),
}
}
pub fn insert_fn<T: Into<Identifier>>(&mut self, s: T, v: Function) -> Option<Function> { pub fn insert_fn<T: Into<Identifier>>(&mut self, s: T, v: Function) -> Option<Function> {
self.functions.insert(s.into(), v) self.functions.insert(s.into(), v)
} }
fn fn_exists_no_global(&self, name: &Identifier) -> bool { fn fn_exists(&self, name: &Identifier) -> bool {
self.functions.contains_key(name) self.functions.contains_key(name)
} }
pub fn fn_exists<T: Into<Identifier>>(&self, v: T, global_scope: &Scope) -> bool { fn merge(&mut self, other: Scope) {
let name = v.into(); self.vars.extend(other.vars);
self.functions.contains_key(&name) self.mixins.extend(other.mixins);
|| global_scope.fn_exists_no_global(&name) self.functions.extend(other.functions);
|| GLOBAL_FUNCTIONS.contains_key(name.as_str()) }
}
#[derive(Debug, Clone, Default)]
pub(crate) struct Scopes(Vec<Scope>);
impl Scopes {
pub const fn new() -> Self {
Self(Vec::new())
}
pub fn enter_new_scope(&mut self) {
self.0.push(Scope::new());
}
pub fn enter_scope(&mut self, scope: Scope) {
self.0.push(scope);
}
pub fn exit_scope(&mut self) {
self.0.pop();
}
pub fn merge(&mut self, other: Scope) {
if let Some(scope) = self.0.last_mut() {
scope.merge(other)
} else {
panic!()
}
}
}
/// Variables
impl Scopes {
pub fn insert_var(&mut self, s: Identifier, v: Spanned<Value>) -> Option<Spanned<Value>> {
for scope in self.0.iter_mut().rev() {
if scope.var_exists(&s) {
return scope.insert_var(s, v);
}
}
if let Some(scope) = self.0.last_mut() {
scope.insert_var(s, v)
} else {
let mut scope = Scope::new();
scope.insert_var(s, v);
self.0.push(scope);
None
}
}
pub fn insert_default_var(
&mut self,
s: Identifier,
v: Spanned<Value>,
) -> Option<Spanned<Value>> {
if let Some(scope) = self.0.last_mut() {
if scope.var_exists(&s) {
None
} else {
scope.insert_var(s, v)
}
} else {
panic!()
}
}
pub fn get_var<'a>(
&'a self,
name: Spanned<&Identifier>,
global_scope: &'a Scope,
) -> SassResult<&Value> {
for scope in self.0.iter().rev() {
if let Ok(v) = scope.get_var(name) {
return Ok(v);
}
}
global_scope.get_var(name)
}
pub fn var_exists(&self, name: &Identifier, global_scope: &Scope) -> bool {
for scope in &self.0 {
if scope.var_exists(name) {
return true;
}
}
global_scope.var_exists(name)
}
}
/// Mixins
impl Scopes {
pub fn insert_mixin(&mut self, s: Identifier, v: Mixin) -> Option<Mixin> {
if let Some(scope) = self.0.last_mut() {
scope.insert_mixin(s, v)
} else {
let mut scope = Scope::new();
scope.insert_mixin(s, v);
self.0.push(scope);
None
}
}
pub fn get_mixin<'a>(
&'a self,
name: Spanned<&Identifier>,
global_scope: &'a Scope,
) -> SassResult<Mixin> {
for scope in self.0.iter().rev() {
if let Ok(v) = scope.get_mixin(name) {
return Ok(v);
}
}
global_scope.get_mixin(name)
}
pub fn mixin_exists(&self, name: &Identifier, global_scope: &Scope) -> bool {
for scope in &self.0 {
if scope.mixin_exists(name) {
return true;
}
}
global_scope.mixin_exists(name)
}
}
/// Functions
impl Scopes {
pub fn insert_fn(&mut self, s: Identifier, v: Function) -> Option<Function> {
if let Some(scope) = self.0.last_mut() {
scope.insert_fn(s, v)
} else {
let mut scope = Scope::new();
scope.insert_fn(s, v);
self.0.push(scope);
None
}
}
pub fn get_fn<'a>(
&'a self,
name: Spanned<&Identifier>,
global_scope: &'a Scope,
) -> SassResult<Function> {
for scope in self.0.iter().rev() {
if let Ok(v) = scope.get_fn(name) {
return Ok(v);
}
}
global_scope.get_fn(name)
}
pub fn fn_exists(&self, name: &Identifier, global_scope: &Scope) -> bool {
for scope in &self.0 {
if scope.fn_exists(name) {
return true;
}
}
global_scope.fn_exists(name) || GLOBAL_FUNCTIONS.contains_key(name.as_str())
} }
} }

View File

@ -144,7 +144,6 @@ test!(
"a {\n color: red;\n}\n" "a {\n color: red;\n}\n"
); );
test!( test!(
#[ignore = "haven't yet figured out how scoping works"]
function_ignores_the_scope_with_which_it_was_defined, function_ignores_the_scope_with_which_it_was_defined,
"a { "a {
$a: red; $a: red;
@ -156,3 +155,43 @@ test!(
}", }",
"a {\n color: green;\n}\n" "a {\n color: green;\n}\n"
); );
test!(
function_defined_and_called_at_toplevel_can_recognize_inner_variables,
"@function foo($level) {
$level: abs($level);
@return $level;
}
@mixin bar($a) {
a {
color: $a;
}
}
@include bar(foo(-9));",
"a {\n color: 9;\n}\n"
);
test!(
redeclaration_in_inner_scope,
"@function foo() {
@return foo;
}
a {
color: foo();
@function foo() {
@return bar;
}
a {
@function foo() {
@return baz;
}
}
color: foo();
}",
"a {\n color: foo;\n color: bar;\n}\n"
);

View File

@ -365,8 +365,7 @@ error!(
"Error: Missing argument $a." "Error: Missing argument $a."
); );
test!( test!(
#[ignore = "haven't yet figured out how scoping works"] inner_mixin_can_modify_scope,
mixin_ignores_the_scope_with_which_it_was_defined,
"a { "a {
$a: red; $a: red;
@mixin foo { @mixin foo {
@ -377,3 +376,26 @@ test!(
}", }",
"a {\n color: green;\n}\n" "a {\n color: green;\n}\n"
); );
test!(
redeclaration_in_inner_scope,
"@mixin foo {
color: foo;
}
a {
@include foo();
@mixin foo {
color: bar;
}
a {
@mixin foo {
color: baz;
}
}
@include foo();
}",
"a {\n color: foo;\n color: bar;\n}\n"
);

View File

@ -61,3 +61,23 @@ test!(
", ",
"a {\n color: orange;\n}\na b {\n color: orange;\n}\na b c {\n color: orange;\n}\na b c d {\n color: orange;\n}\n" "a {\n color: orange;\n}\na b {\n color: orange;\n}\na b c {\n color: orange;\n}\na b c d {\n color: orange;\n}\n"
); );
test!(
local_variable_exists_in_inner_fn_mixin_scope,
"a {
$x: foo;
a {
@function exists-fn-inner($name) {
@return variable-exists($name);
}
@mixin exists-mixin-inner($name) {
color: variable-exists($name);
}
color: exists-fn-inner(x);
@include exists-mixin-inner(x);
}
}",
"a a {\n color: true;\n color: true;\n}\n"
);