mixins and functions can use their module's scope

This commit is contained in:
Connor Skees 2021-07-31 11:42:20 -04:00
parent c86b5f82e9
commit 1f672c4c49
8 changed files with 146 additions and 41 deletions

View File

@ -283,7 +283,7 @@ pub(crate) fn call(mut args: CallArgs, parser: &mut Parser) -> SassResult<Value>
.into()) .into())
} }
}; };
func.call(args.decrement(), parser) func.call(args.decrement(), None, parser)
} }
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::needless_pass_by_value)]

View File

@ -14,8 +14,16 @@ use crate::{
use super::{common::ContextFlags, 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 RESERVED_IDENTIFIERS: [&str; 7] = const RESERVED_IDENTIFIERS: [&str; 8] = [
["calc", "element", "expression", "url", "and", "or", "not"]; "calc",
"element",
"expression",
"url",
"and",
"or",
"not",
"clamp",
];
impl<'a, 'b> Parser<'a, 'b> { impl<'a, 'b> Parser<'a, 'b> {
pub(super) fn parse_function(&mut self) -> SassResult<()> { pub(super) fn parse_function(&mut self) -> SassResult<()> {
@ -56,16 +64,15 @@ impl<'a, 'b> Parser<'a, 'b> {
let name_as_ident = Identifier::from(name); let name_as_ident = Identifier::from(name);
let sass_function = SassFunction::UserDefined {
function: Box::new(function),
name: name_as_ident,
};
if self.at_root { if self.at_root {
self.global_scope.insert_fn( self.global_scope.insert_fn(name_as_ident, sass_function);
name_as_ident,
SassFunction::UserDefined(Box::new(function), name_as_ident),
);
} else { } else {
self.scopes.insert_fn( self.scopes.insert_fn(name_as_ident, sass_function);
name_as_ident,
SassFunction::UserDefined(Box::new(function), name_as_ident),
);
} }
Ok(()) Ok(())
} }
@ -78,7 +85,12 @@ impl<'a, 'b> Parser<'a, 'b> {
Ok(Box::new(v.node)) Ok(Box::new(v.node))
} }
pub fn eval_function(&mut self, function: Function, args: CallArgs) -> SassResult<Value> { pub fn eval_function(
&mut self,
function: Function,
args: CallArgs,
module: Option<Spanned<Identifier>>,
) -> SassResult<Value> {
let Function { let Function {
body, body,
args: fn_args, args: fn_args,
@ -97,6 +109,16 @@ impl<'a, 'b> Parser<'a, 'b> {
self.scopes.enter_scope(scope); self.scopes.enter_scope(scope);
}; };
if let Some(module) = module {
let module = self.modules.get(module.node, module.span)?;
if declared_at_root {
new_scope.enter_scope(module.scope.clone());
} else {
self.scopes.enter_scope(module.scope.clone());
}
}
let mut return_value = Parser { let mut return_value = Parser {
toks: &mut Lexer::new(body), toks: &mut Lexer::new(body),
map: self.map, map: self.map,
@ -125,6 +147,10 @@ impl<'a, 'b> Parser<'a, 'b> {
self.scopes.exit_scope(); self.scopes.exit_scope();
} }
if module.is_some() {
self.scopes.exit_scope();
}
debug_assert!( debug_assert!(
return_value.len() <= 1, return_value.len() <= 1,
"we expect there to be only one return value" "we expect there to be only one return value"

View File

@ -73,15 +73,18 @@ impl<'a, 'b> Parser<'a, 'b> {
self.whitespace_or_comment(); self.whitespace_or_comment();
let name = self.parse_identifier()?.map_node(Into::into); let name = self.parse_identifier()?.map_node(Into::into);
let mixin = if self.consume_char_if_exists('.') { let (mixin, module) = if self.consume_char_if_exists('.') {
let module = name; let module = name;
let name = self.parse_identifier()?.map_node(Into::into); let name = self.parse_identifier()?.map_node(Into::into);
self.modules (
.get(module.node, module.span)? self.modules
.get_mixin(name)? .get(module.node, module.span)?
.get_mixin(name)?,
Some(module),
)
} else { } else {
self.scopes.get_mixin(name, self.global_scope)? (self.scopes.get_mixin(name, self.global_scope)?, None)
}; };
self.whitespace_or_comment(); self.whitespace_or_comment();
@ -152,6 +155,11 @@ impl<'a, 'b> Parser<'a, 'b> {
self.scopes.enter_scope(scope); self.scopes.enter_scope(scope);
if let Some(module) = module {
let module = self.modules.get(module.node, module.span)?;
self.scopes.enter_scope(module.scope.clone());
}
self.content.push(Content { self.content.push(Content {
content, content,
content_args, content_args,
@ -180,6 +188,11 @@ impl<'a, 'b> Parser<'a, 'b> {
.parse_stmt()?; .parse_stmt()?;
self.content.pop(); self.content.pop();
if module.is_some() {
self.scopes.exit_scope();
}
self.scopes.exit_scope(); self.scopes.exit_scope();
if declared_at_root { if declared_at_root {

View File

@ -7,7 +7,7 @@ use num_traits::Zero;
use crate::{ use crate::{
args::CallArgs, args::CallArgs,
common::{Op, QuoteKind}, common::{Identifier, Op, QuoteKind},
error::SassResult, error::SassResult,
unit::Unit, unit::Unit,
value::{SassFunction, Value}, value::{SassFunction, Value},
@ -19,7 +19,7 @@ use super::super::Parser;
pub(crate) enum HigherIntermediateValue { pub(crate) enum HigherIntermediateValue {
Literal(Value), Literal(Value),
/// A function that hasn't yet been evaluated /// A function that hasn't yet been evaluated
Function(SassFunction, CallArgs), Function(SassFunction, CallArgs, Option<Spanned<Identifier>>),
BinaryOp(Box<Self>, Op, Box<Self>), BinaryOp(Box<Self>, Op, Box<Self>),
UnaryOp(Op, Box<Self>), UnaryOp(Op, Box<Self>),
} }
@ -31,8 +31,13 @@ impl HigherIntermediateValue {
} }
impl<'a, 'b> Parser<'a, 'b> { impl<'a, 'b> Parser<'a, 'b> {
fn call_function(&mut self, function: SassFunction, args: CallArgs) -> SassResult<Value> { fn call_function(
function.call(args, self) &mut self,
function: SassFunction,
args: CallArgs,
module: Option<Spanned<Identifier>>,
) -> SassResult<Value> {
function.call(args, module, self)
} }
} }
@ -54,8 +59,8 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> {
HigherIntermediateValue::Literal(v) => Ok(v), HigherIntermediateValue::Literal(v) => Ok(v),
HigherIntermediateValue::BinaryOp(v1, op, v2) => self.bin_op(*v1, op, *v2, in_parens), HigherIntermediateValue::BinaryOp(v1, op, v2) => self.bin_op(*v1, op, *v2, in_parens),
HigherIntermediateValue::UnaryOp(op, val) => self.unary_op(op, *val, in_parens), HigherIntermediateValue::UnaryOp(op, val) => self.unary_op(op, *val, in_parens),
HigherIntermediateValue::Function(function, args) => { HigherIntermediateValue::Function(function, args, module) => {
self.parser.call_function(function, args) self.parser.call_function(function, args, module)
} }
} }
} }
@ -216,8 +221,8 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> {
HigherIntermediateValue::UnaryOp(op, val) => { HigherIntermediateValue::UnaryOp(op, val) => {
HigherIntermediateValue::Literal(self.unary_op(op, *val, in_parens)?) HigherIntermediateValue::Literal(self.unary_op(op, *val, in_parens)?)
} }
HigherIntermediateValue::Function(function, args) => { HigherIntermediateValue::Function(function, args, module) => {
HigherIntermediateValue::Literal(self.parser.call_function(function, args)?) HigherIntermediateValue::Literal(self.parser.call_function(function, args, module)?)
} }
val => val, val => val,
}) })
@ -301,7 +306,6 @@ impl<'a, 'b: 'a, 'c> ValueVisitor<'a, 'b, 'c> {
.into()); .into());
} }
if unit == unit2 { if unit == unit2 {
// dbg!(&num, &num2, num.clone() + num2.clone(), unit.clone(), unit2.clone());
Value::Dimension(Some(num + num2), unit, true) Value::Dimension(Some(num + num2), unit, true)
} else if unit == Unit::None { } else if unit == Unit::None {
Value::Dimension(Some(num + num2), unit2, true) Value::Dimension(Some(num + num2), unit2, true)

View File

@ -198,8 +198,7 @@ impl<'a, 'b> Parser<'a, 'b> {
#[allow(clippy::eval_order_dependence)] #[allow(clippy::eval_order_dependence)]
fn parse_module_item( fn parse_module_item(
&mut self, &mut self,
module: &str, mut module: Spanned<Identifier>,
mut module_span: Span,
) -> SassResult<Spanned<IntermediateValue>> { ) -> SassResult<Spanned<IntermediateValue>> {
Ok( Ok(
IntermediateValue::Value(if self.consume_char_if_exists('$') { IntermediateValue::Value(if self.consume_char_if_exists('$') {
@ -207,9 +206,9 @@ impl<'a, 'b> Parser<'a, 'b> {
.parse_identifier_no_interpolation(false)? .parse_identifier_no_interpolation(false)?
.map_node(|i| i.into()); .map_node(|i| i.into());
module_span = module_span.merge(var.span); module.span = module.span.merge(var.span);
let value = self.modules.get(module.into(), module_span)?.get_var(var)?; let value = self.modules.get(module.node, module.span)?.get_var(var)?;
HigherIntermediateValue::Literal(value.clone()) HigherIntermediateValue::Literal(value.clone())
} else { } else {
let fn_name = self let fn_name = self
@ -218,7 +217,7 @@ impl<'a, 'b> Parser<'a, 'b> {
let function = self let function = self
.modules .modules
.get(module.into(), module_span)? .get(module.node, module.span)?
.get_fn(fn_name)? .get_fn(fn_name)?
.ok_or(("Undefined function.", fn_name.span))?; .ok_or(("Undefined function.", fn_name.span))?;
@ -226,9 +225,9 @@ impl<'a, 'b> Parser<'a, 'b> {
let call_args = self.parse_call_args()?; let call_args = self.parse_call_args()?;
HigherIntermediateValue::Function(function, call_args) HigherIntermediateValue::Function(function, call_args, Some(module))
}) })
.span(module_span), .span(module.span),
) )
} }
@ -260,6 +259,7 @@ impl<'a, 'b> Parser<'a, 'b> {
return Ok(IntermediateValue::Value(HigherIntermediateValue::Function( return Ok(IntermediateValue::Value(HigherIntermediateValue::Function(
SassFunction::Builtin(f.clone(), as_ident), SassFunction::Builtin(f.clone(), as_ident),
self.parse_call_args()?, self.parse_call_args()?,
None,
)) ))
.span(self.span_before)); .span(self.span_before));
} }
@ -297,7 +297,7 @@ impl<'a, 'b> Parser<'a, 'b> {
let call_args = self.parse_call_args()?; let call_args = self.parse_call_args()?;
Ok( Ok(
IntermediateValue::Value(HigherIntermediateValue::Function(func, call_args)) IntermediateValue::Value(HigherIntermediateValue::Function(func, call_args, None))
.span(self.span_before), .span(self.span_before),
) )
} }
@ -336,7 +336,10 @@ impl<'a, 'b> Parser<'a, 'b> {
Some(Token { kind: '.', .. }) => { Some(Token { kind: '.', .. }) => {
if !predicate(self) { if !predicate(self) {
self.toks.next(); self.toks.next();
return self.parse_module_item(&s, span); return self.parse_module_item(Spanned {
node: s.into(),
span,
});
} }
} }
_ => {} _ => {}

View File

@ -13,7 +13,7 @@ use crate::{
/// A singular scope /// A singular scope
/// ///
/// Contains variables, functions, and mixins /// Contains variables, functions, and mixins
#[derive(Debug, Default)] #[derive(Debug, Default, Clone)]
pub(crate) struct Scope { pub(crate) struct Scope {
pub vars: BTreeMap<Identifier, Value>, pub vars: BTreeMap<Identifier, Value>,
pub mixins: BTreeMap<Identifier, Mixin>, pub mixins: BTreeMap<Identifier, Mixin>,

View File

@ -11,6 +11,8 @@
use std::fmt; use std::fmt;
use codemap::Spanned;
use crate::{ use crate::{
args::CallArgs, atrule::Function, builtin::Builtin, common::Identifier, error::SassResult, args::CallArgs, atrule::Function, builtin::Builtin, common::Identifier, error::SassResult,
parse::Parser, value::Value, parse::Parser, value::Value,
@ -25,7 +27,10 @@ use crate::{
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub(crate) enum SassFunction { pub(crate) enum SassFunction {
Builtin(Builtin, Identifier), Builtin(Builtin, Identifier),
UserDefined(Box<Function>, Identifier), UserDefined {
function: Box<Function>,
name: Identifier,
},
} }
impl SassFunction { impl SassFunction {
@ -34,7 +39,7 @@ impl SassFunction {
/// Used mainly in debugging and `inspect()` /// Used mainly in debugging and `inspect()`
pub fn name(&self) -> &Identifier { pub fn name(&self) -> &Identifier {
match self { match self {
Self::Builtin(_, name) | Self::UserDefined(_, name) => name, Self::Builtin(_, name) | Self::UserDefined { name, .. } => name,
} }
} }
@ -44,14 +49,19 @@ impl SassFunction {
fn kind(&self) -> &'static str { fn kind(&self) -> &'static str {
match &self { match &self {
Self::Builtin(..) => "Builtin", Self::Builtin(..) => "Builtin",
Self::UserDefined(..) => "UserDefined", Self::UserDefined { .. } => "UserDefined",
} }
} }
pub fn call(self, args: CallArgs, parser: &mut Parser) -> SassResult<Value> { pub fn call(
self,
args: CallArgs,
module: Option<Spanned<Identifier>>,
parser: &mut Parser,
) -> SassResult<Value> {
match self { match self {
Self::Builtin(f, ..) => f.0(args, parser), Self::Builtin(f, ..) => f.0(args, parser),
Self::UserDefined(f, ..) => parser.eval_function(*f, args), Self::UserDefined { function, .. } => parser.eval_function(*function, args, module),
} }
} }
} }

View File

@ -369,6 +369,55 @@ fn use_modules_imported_by_other_modules_does_not_cause_conflict() {
); );
} }
#[test]
fn use_mixin_can_use_scope_from_own_module() {
let input = r#"
@use "use_mixin_can_use_scope_from_own_module__a" as a;
@include a.foo();
"#;
tempfile!(
"use_mixin_can_use_scope_from_own_module__a.scss",
"$a: red;
@mixin foo() {
a {
color: $a;
}
}"
);
assert_eq!(
"a {\n color: red;\n}\n",
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
);
}
#[test]
fn use_function_can_use_scope_from_own_module() {
let input = r#"
@use "use_function_can_use_scope_from_own_module__a" as a;
a {
color: a.foo();
}
"#;
tempfile!(
"use_function_can_use_scope_from_own_module__a.scss",
"$a: red;
@function foo() {
@return $a;
}"
);
assert_eq!(
"a {\n color: red;\n}\n",
&grass::from_string(input.to_string(), &grass::Options::default()).expect(input)
);
}
#[test] #[test]
fn use_variable_redeclaration_builtin() { fn use_variable_redeclaration_builtin() {
let input = "@use \"sass:math\";\nmath.$e: red;"; let input = "@use \"sass:math\";\nmath.$e: red;";