refactor how scopes are calculated
This commit is contained in:
parent
3a5526ab26
commit
47902c077c
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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 {
|
||||||
|
@ -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)),
|
||||||
},
|
},
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
223
src/scope.rs
223
src/scope.rs
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
);
|
||||||
|
@ -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"
|
||||||
|
);
|
||||||
|
@ -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"
|
||||||
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user