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 crate::{args::FuncArgs, scope::Scope, Token};
use crate::{args::FuncArgs, Token};
#[derive(Debug, Clone)]
pub(crate) struct Function {
pub scope: Scope,
pub args: FuncArgs,
pub body: Vec<Token>,
pub declared_at_root: bool,
pos: Span,
}
@ -27,12 +27,12 @@ impl PartialEq for Function {
impl Eq for 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 {
scope,
args,
body,
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)]
pub(crate) struct Mixin {
pub scope: Scope,
pub args: FuncArgs,
pub body: Vec<Token>,
pub accepts_content_block: bool,
pub declared_at_root: bool,
}
impl Mixin {
pub fn new(
scope: Scope,
args: FuncArgs,
body: Vec<Token>,
accepts_content_block: bool,
declared_at_root: bool,
) -> Self {
Mixin {
scope,
args,
body,
accepts_content_block,
declared_at_root,
}
}
}
@ -28,15 +28,15 @@ impl Mixin {
pub(crate) struct Content {
pub content: Option<Vec<Token>>,
pub content_args: Option<FuncArgs>,
pub scope: Scope,
pub scopes: Scopes,
}
impl Content {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
content: 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)?;
match parser.arg(&mut args, 0, "name")? {
Value::String(s, _) => Ok(Value::bool(
parser
.scopes
.last()
.var_exists(&s.into(), parser.global_scope),
parser.scopes.var_exists(&s.into(), parser.global_scope),
)),
v => Err((
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> {
args.max_args(1)?;
match parser.arg(&mut args, 0, "name")? {
Value::String(s, _) => Ok(Value::bool(
parser.global_scope.var_exists_no_global(&s.into()),
)),
Value::String(s, _) => Ok(Value::bool(parser.global_scope.var_exists(&s.into()))),
v => Err((
format!("$name: {} is not a string.", v.inspect(args.span())?),
args.span(),
@ -127,7 +122,7 @@ fn mixin_exists(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult<Value
args.max_args(2)?;
match parser.arg(&mut args, 0, "name")? {
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((
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)?;
match parser.arg(&mut args, 0, "name")? {
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((
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());
}
let func = match parser.scopes.last().get_fn(
let func = match parser.scopes.get_fn(
Spanned {
node: &name,
span: args.span(),

View File

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

View File

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

View File

@ -1,7 +1,4 @@
use std::{
ops::{BitAnd, BitOr},
slice::IterMut,
};
use std::ops::{BitAnd, BitOr};
use codemap::Spanned;
@ -25,18 +22,6 @@ impl<T> NeverEmptyVec<T> {
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) {
self.rest.push(value)
}
@ -45,14 +30,6 @@ impl<T> NeverEmptyVec<T> {
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 {
self.rest.is_empty()
}

View File

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

View File

@ -6,15 +6,16 @@ use crate::{
atrule::Function,
common::unvendor,
error::SassResult,
scope::Scopes,
utils::{read_until_closing_curly_brace, read_until_semicolon_or_closing_curly_brace},
value::Value,
Token,
};
use super::{common::ContextFlags, NeverEmptyVec, Parser, Stmt};
use super::{common::ContextFlags, Parser, Stmt};
/// 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"];
impl<'a> Parser<'a> {
@ -30,7 +31,7 @@ impl<'a> Parser<'a> {
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());
}
@ -50,12 +51,12 @@ impl<'a> Parser<'a> {
});
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 {
self.global_scope.insert_fn(name, function);
} else {
self.scopes.last_mut().insert_fn(name, function);
self.scopes.insert_fn(name.into(), function);
}
Ok(())
}
@ -71,19 +72,32 @@ impl<'a> Parser<'a> {
pub fn eval_function(&mut self, function: Function, args: CallArgs) -> SassResult<Value> {
let Function {
mut scope,
body,
args: fn_args,
declared_at_root,
..
} = 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 {
toks: &mut body.into_iter().peekmore(),
map: self.map,
path: self.path,
scopes: &mut NeverEmptyVec::new(scope),
scopes: if declared_at_root {
&mut new_scope
} else {
self.scopes
},
global_scope: self.global_scope,
super_selectors: self.super_selectors,
span_before: self.span_before,
@ -95,6 +109,10 @@ impl<'a> Parser<'a> {
}
.parse()?;
if entered_scope {
self.scopes.exit_scope();
}
debug_assert!(return_value.len() <= 1);
match return_value
.pop()

View File

@ -6,11 +6,12 @@ use crate::{
args::{CallArgs, FuncArgs},
atrule::{Content, Mixin},
error::SassResult,
scope::Scopes,
utils::read_until_closing_curly_brace,
Token,
};
use super::{common::ContextFlags, NeverEmptyVec, Parser, Stmt};
use super::{common::ContextFlags, Parser, Stmt};
impl<'a> Parser<'a> {
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
// 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 {
self.global_scope.insert_mixin(name, mixin);
} else {
self.scopes.last_mut().insert_mixin(name, mixin);
self.scopes.insert_mixin(name.into(), mixin);
}
Ok(())
}
pub(super) fn parse_include(&mut self) -> SassResult<Vec<Stmt>> {
self.whitespace_or_comment();
let name = self.parse_identifier()?;
let name = self.parse_identifier()?.map_node(Into::into);
self.whitespace_or_comment();
@ -106,24 +107,44 @@ impl<'a> Parser<'a> {
}
let Mixin {
mut scope,
body,
args: fn_args,
declared_at_root,
..
} = self.scopes.last().get_mixin(name, self.global_scope)?;
self.eval_args(fn_args, args, &mut scope)?;
} = self.scopes.get_mixin(
{
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 {
content,
content_args,
scope: self.scopes.last().clone(),
scopes: self.scopes.clone(),
});
let body = Parser {
toks: &mut body.into_iter().peekmore(),
map: self.map,
path: self.path,
scopes: &mut NeverEmptyVec::new(scope),
scopes: if declared_at_root {
&mut new_scope
} else {
self.scopes
},
global_scope: self.global_scope,
super_selectors: self.super_selectors,
span_before: self.span_before,
@ -136,6 +157,9 @@ impl<'a> Parser<'a> {
.parse()?;
self.content.pop();
if entered_scope {
self.scopes.exit_scope();
}
Ok(body)
}
@ -147,7 +171,7 @@ impl<'a> Parser<'a> {
.last()
.cloned()
.unwrap_or_else(Content::new)
.scope;
.scopes;
if let Some(Token { kind: '(', .. }) = self.toks.peek() {
self.toks.next();
let args = self.parse_call_args()?;
@ -156,7 +180,7 @@ impl<'a> Parser<'a> {
{
args.max_args(content_args.len())?;
self.eval_args(content_args, args, &mut scope)?;
scope.merge(self.eval_args(content_args, args)?);
} else {
args.max_args(0)?;
}
@ -168,7 +192,7 @@ impl<'a> Parser<'a> {
toks: &mut body.into_iter().peekmore(),
map: self.map,
path: self.path,
scopes: &mut NeverEmptyVec::new(scope),
scopes: &mut scope,
global_scope: self.global_scope,
super_selectors: self.super_selectors,
span_before: self.span_before,
@ -183,6 +207,7 @@ impl<'a> Parser<'a> {
Vec::new()
};
self.content.push(content.clone());
self.scopes.exit_scope();
stmts
} else {
Vec::new()

View File

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

View File

@ -255,7 +255,7 @@ impl<'a> Parser<'a> {
}
let as_ident = Identifier::from(&s);
let func = match self.scopes.last().get_fn(
let func = match self.scopes.get_fn(
Spanned {
node: &as_ident,
span,
@ -527,7 +527,13 @@ impl<'a> Parser<'a> {
Err(e) => return Some(Err(e)),
};
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(),
Err(e) => return Some(Err(e)),
},

View File

@ -47,46 +47,28 @@ impl<'a> Parser<'a> {
if value.default {
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);
}
} else {
if value.global && !self.global_scope.var_exists_no_global(&ident) {
if value.global && !self.global_scope.var_exists(&ident) {
self.global_scope
.insert_var(ident.clone(), value.value.clone());
}
if !self.scopes.last().var_exists_no_global(&ident) {
self.scopes.last_mut().insert_var(ident, value.value);
}
self.scopes.insert_default_var(ident, value.value);
}
} else if self.at_root {
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);
} else {
self.scopes.last_mut().insert_var(ident, value.value);
self.scopes.insert_var(ident, value.value);
}
} else {
self.global_scope.insert_var(ident, value.value);
}
} else {
let len = self.scopes.len();
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);
self.scopes.insert_var(ident, value.value);
}
Ok(())
}

View File

@ -27,95 +27,212 @@ impl Scope {
}
}
fn get_var_no_global(&self, name: &Spanned<Identifier>) -> SassResult<&Value> {
match self.vars.get(&name.node) {
fn get_var(&self, name: Spanned<&Identifier>) -> SassResult<&Value> {
match self.vars.get(name.node) {
Some(v) => Ok(&v.node),
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>> {
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)
}
pub fn var_exists<'a, T: Into<&'a Identifier>>(&self, v: T, global_scope: &Scope) -> bool {
let name = v.into();
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) {
fn get_mixin(&self, name: Spanned<&Identifier>) -> SassResult<Mixin> {
match self.mixins.get(name.node) {
Some(v) => Ok(v.clone()),
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> {
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)
}
pub fn mixin_exists<T: Into<Identifier>>(&self, v: T, global_scope: &Scope) -> bool {
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> {
fn get_fn(&self, name: Spanned<&Identifier>) -> SassResult<Function> {
match self.functions.get(name.node) {
Some(v) => Ok(v.clone()),
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> {
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)
}
pub fn fn_exists<T: Into<Identifier>>(&self, v: T, global_scope: &Scope) -> bool {
let name = v.into();
self.functions.contains_key(&name)
|| global_scope.fn_exists_no_global(&name)
|| GLOBAL_FUNCTIONS.contains_key(name.as_str())
fn merge(&mut self, other: Scope) {
self.vars.extend(other.vars);
self.mixins.extend(other.mixins);
self.functions.extend(other.functions);
}
}
#[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"
);
test!(
#[ignore = "haven't yet figured out how scoping works"]
function_ignores_the_scope_with_which_it_was_defined,
"a {
$a: red;
@ -156,3 +155,43 @@ test!(
}",
"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."
);
test!(
#[ignore = "haven't yet figured out how scoping works"]
mixin_ignores_the_scope_with_which_it_was_defined,
inner_mixin_can_modify_scope,
"a {
$a: red;
@mixin foo {
@ -377,3 +376,26 @@ test!(
}",
"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"
);
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"
);