From d6a1d64dcb6ec522f527f83ce993ba7fc99eeb28 Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Mon, 27 Jul 2020 18:55:38 -0400 Subject: [PATCH] implement `@use ... as *;` syntax --- src/builtin/functions/meta.rs | 2 +- src/builtin/modules/mod.rs | 6 +-- src/parse/args.rs | 14 ++----- src/parse/control_flow.rs | 21 ++-------- src/parse/function.rs | 16 ++++++-- src/parse/mod.rs | 76 +++++++++++++++++++---------------- src/parse/value/parse.rs | 3 +- src/parse/variable.rs | 6 +-- src/scope.rs | 40 +++++++++--------- tests/use.rs | 8 ++++ 10 files changed, 98 insertions(+), 94 deletions(-) diff --git a/src/builtin/functions/meta.rs b/src/builtin/functions/meta.rs index 2190701..a5e0087 100644 --- a/src/builtin/functions/meta.rs +++ b/src/builtin/functions/meta.rs @@ -181,7 +181,7 @@ pub(crate) fn get_function(mut args: CallArgs, parser: &mut Parser<'_>) -> SassR }, parser.global_scope, ) { - Some(f) => SassFunction::UserDefined(Box::new(f), name), + Some(f) => f, None => match GLOBAL_FUNCTIONS.get(name.as_str()) { Some(f) => SassFunction::Builtin(f.clone(), name), None => return Err((format!("Function not found: {}", name), args.span()).into()), diff --git a/src/builtin/modules/mod.rs b/src/builtin/modules/mod.rs index 0e3a3ac..102772e 100644 --- a/src/builtin/modules/mod.rs +++ b/src/builtin/modules/mod.rs @@ -24,9 +24,9 @@ mod string; #[derive(Debug, Default)] pub(crate) struct Module { - vars: BTreeMap, - mixins: BTreeMap, - functions: BTreeMap, + pub vars: BTreeMap, + pub mixins: BTreeMap, + pub functions: BTreeMap, } impl Module { diff --git a/src/parse/args.rs b/src/parse/args.rs index 053e160..b569a16 100644 --- a/src/parse/args.rs +++ b/src/parse/args.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, mem}; -use codemap::{Span, Spanned}; +use codemap::Span; use crate::{ args::{CallArg, CallArgs, FuncArg, FuncArgs}, @@ -283,15 +283,8 @@ impl<'a> Parser<'a> { self.scopes.enter_new_scope(); for (idx, mut arg) in fn_args.0.into_iter().enumerate() { if arg.is_variadic { - let span = args.span(); let arg_list = Value::ArgList(args.get_variadic()?); - scope.insert_var( - arg.name, - Spanned { - node: arg_list, - span, - }, - ); + scope.insert_var(arg.name, arg_list); break; } let val = match args.get(idx, arg.name) { @@ -304,7 +297,8 @@ impl<'a> Parser<'a> { ) } }, - }?; + }? + .node; self.scopes.insert_var(arg.name, val.clone()); scope.insert_var(arg.name, val); } diff --git a/src/parse/control_flow.rs b/src/parse/control_flow.rs index 913ad2a..0293664 100644 --- a/src/parse/control_flow.rs +++ b/src/parse/control_flow.rs @@ -304,10 +304,7 @@ impl<'a> Parser<'a> { for i in iter { self.scopes.insert_var_last( var.node, - Spanned { - node: Value::Dimension(Some(Number::from(i)), Unit::None, true), - span: var.span, - }, + Value::Dimension(Some(Number::from(i)), Unit::None, true), ); if self.flags.in_function() { let these_stmts = Parser { @@ -487,26 +484,14 @@ impl<'a> Parser<'a> { for row in iter { if vars.len() == 1 { - self.scopes.insert_var_last( - vars[0].node, - Spanned { - node: row, - span: vars[0].span, - }, - ); + self.scopes.insert_var_last(vars[0].node, row); } else { for (var, val) in vars.iter().zip( row.as_list() .into_iter() .chain(std::iter::once(Value::Null).cycle()), ) { - self.scopes.insert_var_last( - var.node, - Spanned { - node: val, - span: var.span, - }, - ); + self.scopes.insert_var_last(var.node, val); } } diff --git a/src/parse/function.rs b/src/parse/function.rs index 67544e5..a644332 100644 --- a/src/parse/function.rs +++ b/src/parse/function.rs @@ -4,11 +4,11 @@ use peekmore::PeekMore; use crate::{ args::CallArgs, atrule::Function, - common::unvendor, + common::{unvendor, Identifier}, error::SassResult, scope::Scopes, utils::{read_until_closing_curly_brace, read_until_semicolon_or_closing_curly_brace}, - value::Value, + value::{SassFunction, Value}, Token, }; @@ -53,10 +53,18 @@ impl<'a> Parser<'a> { let function = Function::new(args, body, self.at_root, span); + let name_as_ident = Identifier::from(name); + if self.at_root { - self.global_scope.insert_fn(name, function); + self.global_scope.insert_fn( + name_as_ident, + SassFunction::UserDefined(Box::new(function), name_as_ident), + ); } else { - self.scopes.insert_fn(name.into(), function); + self.scopes.insert_fn( + name_as_ident, + SassFunction::UserDefined(Box::new(function), name_as_ident), + ); } Ok(()) } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 7e77032..b6a9456 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -150,11 +150,11 @@ impl<'a> Parser<'a> { }; let Spanned { node: module, span } = self.parse_quoted_string(quote)?; - let module = module.unquote().to_css_string(span)?; + let module_name = module.unquote().to_css_string(span)?; self.whitespace_or_comment(); - let mut module_name: Option = None; + let mut module_alias: Option = None; match self.toks.peek() { Some(Token { kind: ';', .. }) => { @@ -170,12 +170,21 @@ impl<'a> Parser<'a> { self.whitespace_or_comment(); - let name = self.parse_identifier_no_interpolation(false)?; + let name_span; - module_name = Some(name.node); + if let Some(Token { kind: '*', pos }) = self.toks.peek() { + name_span = *pos; + self.toks.next(); + module_alias = Some('*'.to_string()); + } else { + let name = self.parse_identifier_no_interpolation(false)?; + + module_alias = Some(name.node); + name_span = name.span; + } if !matches!(self.toks.next(), Some(Token { kind: ';', .. })) { - return Err(("expected \";\".", name.span).into()); + return Err(("expected \";\".", name_span).into()); } } Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. }) => { @@ -184,37 +193,36 @@ impl<'a> Parser<'a> { Some(..) | None => return Err(("expected \";\".", span).into()), } - match module.as_ref() { - "sass:color" => self.modules.insert( - module_name.unwrap_or_else(|| "color".to_owned()), - declare_module_color(), - ), - "sass:list" => self.modules.insert( - module_name.unwrap_or_else(|| "list".to_owned()), - declare_module_list(), - ), - "sass:map" => self.modules.insert( - module_name.unwrap_or_else(|| "map".to_owned()), - declare_module_map(), - ), - "sass:math" => self.modules.insert( - module_name.unwrap_or_else(|| "math".to_owned()), - declare_module_math(), - ), - "sass:meta" => self.modules.insert( - module_name.unwrap_or_else(|| "meta".to_owned()), - declare_module_meta(), - ), - "sass:selector" => self.modules.insert( - module_name.unwrap_or_else(|| "selector".to_owned()), - declare_module_selector(), - ), - "sass:string" => self.modules.insert( - module_name.unwrap_or_else(|| "string".to_owned()), - declare_module_string(), - ), + let module = match module_name.as_ref() { + "sass:color" => declare_module_color(), + "sass:list" => declare_module_list(), + "sass:map" => declare_module_map(), + "sass:math" => declare_module_math(), + "sass:meta" => declare_module_meta(), + "sass:selector" => declare_module_selector(), + "sass:string" => declare_module_string(), _ => todo!("@use not yet implemented"), }; + + let module_name = match module_alias.as_deref() { + Some("*") => { + self.global_scope.merge_module(module); + continue; + } + Some(..) => module_alias.unwrap(), + None => match module_name.as_ref() { + "sass:color" => "color".to_owned(), + "sass:list" => "list".to_owned(), + "sass:map" => "map".to_owned(), + "sass:math" => "math".to_owned(), + "sass:meta" => "meta".to_owned(), + "sass:selector" => "selector".to_owned(), + "sass:string" => "string".to_owned(), + _ => module_name.into_owned(), + }, + }; + + self.modules.insert(module_name, module); } Some(Token { kind: '/', .. }) => { self.toks.next(); diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 795261c..6fd9324 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -355,8 +355,7 @@ impl<'a> Parser<'a> { let call_args = self.parse_call_args()?; return Ok(IntermediateValue::Value(HigherIntermediateValue::Function( - SassFunction::UserDefined(Box::new(func), as_ident), - call_args, + func, call_args, )) .span(span)); } diff --git a/src/parse/variable.rs b/src/parse/variable.rs index fe25971..ad08142 100644 --- a/src/parse/variable.rs +++ b/src/parse/variable.rs @@ -41,11 +41,11 @@ impl<'a> Parser<'a> { if default { if self.at_root && !self.flags.in_control_flow() { if !self.global_scope.var_exists(ident) { - let value = self.parse_value_from_vec(val_toks, true)?; + let value = self.parse_value_from_vec(val_toks, true)?.node; self.global_scope.insert_var(ident, value); } } else { - let value = self.parse_value_from_vec(val_toks, true)?; + let value = self.parse_value_from_vec(val_toks, true)?.node; if global && !self.global_scope.var_exists(ident) { self.global_scope.insert_var(ident, value.clone()); } @@ -55,7 +55,7 @@ impl<'a> Parser<'a> { return Ok(()); } - let value = self.parse_value_from_vec(val_toks, true)?; + let value = self.parse_value_from_vec(val_toks, true)?.node; if global { self.global_scope.insert_var(ident, value.clone()); diff --git a/src/scope.rs b/src/scope.rs index 484b1aa..9463e54 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -3,18 +3,18 @@ use std::collections::BTreeMap; use codemap::Spanned; use crate::{ - atrule::{Function, Mixin}, - builtin::GLOBAL_FUNCTIONS, + atrule::Mixin, + builtin::{modules::Module, GLOBAL_FUNCTIONS}, common::Identifier, error::SassResult, - value::Value, + value::{SassFunction, Value}, }; #[derive(Debug, Default)] pub(crate) struct Scope { - vars: BTreeMap>, + vars: BTreeMap, mixins: BTreeMap, - functions: BTreeMap, + functions: BTreeMap, } impl Scope { @@ -31,12 +31,12 @@ impl Scope { fn get_var(&self, name: Spanned) -> SassResult<&Value> { match self.vars.get(&name.node) { - Some(v) => Ok(&v.node), + Some(v) => Ok(v), None => Err(("Undefined variable.", name.span).into()), } } - pub fn insert_var(&mut self, s: Identifier, v: Spanned) -> Option> { + pub fn insert_var(&mut self, s: Identifier, v: Value) -> Option { self.vars.insert(s, v) } @@ -59,12 +59,12 @@ impl Scope { self.mixins.contains_key(&name) } - fn get_fn(&self, name: Identifier) -> Option { + fn get_fn(&self, name: Identifier) -> Option { self.functions.get(&name).cloned() } - pub fn insert_fn>(&mut self, s: T, v: Function) -> Option { - self.functions.insert(s.into(), v) + pub fn insert_fn(&mut self, s: Identifier, v: SassFunction) -> Option { + self.functions.insert(s, v) } fn fn_exists(&self, name: Identifier) -> bool { @@ -79,6 +79,12 @@ impl Scope { self.mixins.extend(other.mixins); self.functions.extend(other.functions); } + + pub fn merge_module(&mut self, other: Module) { + self.vars.extend(other.vars); + self.mixins.extend(other.mixins); + self.functions.extend(other.functions); + } } #[derive(Debug, Default)] @@ -112,7 +118,7 @@ impl Scopes { /// Variables impl Scopes { - pub fn insert_var(&mut self, s: Identifier, v: Spanned) -> Option> { + pub fn insert_var(&mut self, s: Identifier, v: Value) -> Option { for scope in self.0.iter_mut().rev() { if scope.var_exists(s) { return scope.insert_var(s, v); @@ -131,7 +137,7 @@ impl Scopes { /// Always insert this variable into the innermost scope /// /// Used, for example, for variables from `@each` and `@for` - pub fn insert_var_last(&mut self, s: Identifier, v: Spanned) -> Option> { + pub fn insert_var_last(&mut self, s: Identifier, v: Value) -> Option { if let Some(scope) = self.0.last_mut() { scope.insert_var(s, v) } else { @@ -142,11 +148,7 @@ impl Scopes { } } - pub fn insert_default_var( - &mut self, - s: Identifier, - v: Spanned, - ) -> Option> { + pub fn insert_default_var(&mut self, s: Identifier, v: Value) -> Option { if let Some(scope) = self.0.last_mut() { if scope.var_exists(s) { None @@ -219,7 +221,7 @@ impl Scopes { /// Functions impl Scopes { - pub fn insert_fn(&mut self, s: Identifier, v: Function) -> Option { + pub fn insert_fn(&mut self, s: Identifier, v: SassFunction) -> Option { if let Some(scope) = self.0.last_mut() { scope.insert_fn(s, v) } else { @@ -234,7 +236,7 @@ impl Scopes { &'a self, name: Spanned, global_scope: &'a Scope, - ) -> Option { + ) -> Option { for scope in self.0.iter().rev() { if scope.fn_exists(name.node) { return scope.get_fn(name.node); diff --git a/tests/use.rs b/tests/use.rs index b40a5d3..4a20f9e 100644 --- a/tests/use.rs +++ b/tests/use.rs @@ -50,3 +50,11 @@ test!( }", "a {\n color: 1;\n}\n" ); +test!( + use_as_universal, + "@use \"sass:math\" as *; + a { + color: cos(2); + }", + "a {\n color: -0.4161468365;\n}\n" +);