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 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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)),
|
||||
},
|
||||
|
@ -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(())
|
||||
}
|
||||
|
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> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
);
|
||||
|
@ -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"
|
||||
);
|
||||
|
@ -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"
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user