From bb0b352af276ff23ea8dfd180f4b17302dd8984c Mon Sep 17 00:00:00 2001 From: Connor Skees Date: Thu, 6 Aug 2020 22:05:50 -0400 Subject: [PATCH] move module parsing to separate file --- src/parse/mod.rs | 242 +----------------------------------------- src/parse/module.rs | 251 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+), 239 deletions(-) create mode 100644 src/parse/module.rs diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 79db038..6b5ece3 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, fs, path::Path, vec::IntoIter}; +use std::{convert::TryFrom, path::Path, vec::IntoIter}; use codemap::{CodeMap, Span, Spanned}; use peekmore::{PeekMore, PeekMoreIterator}; @@ -10,13 +10,8 @@ use crate::{ mixin::Content, AtRuleKind, SupportsRule, UnknownAtRule, }, - builtin::modules::{ - declare_module_color, declare_module_list, declare_module_map, declare_module_math, - declare_module_meta, declare_module_selector, declare_module_string, Module, ModuleConfig, - Modules, - }, + builtin::modules::{ModuleConfig, Modules}, error::SassResult, - lexer::Lexer, scope::{Scope, Scopes}, selector::{ ComplexSelectorComponent, ExtendRule, ExtendedSelector, Extender, Selector, SelectorParser, @@ -42,6 +37,7 @@ mod import; mod keyframes; mod media; mod mixin; +mod module; mod style; mod throw_away; mod value; @@ -135,238 +131,6 @@ impl<'a> Parser<'a> { } } - fn parse_module_alias(&mut self) -> SassResult> { - if let Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) = self.toks.peek() { - let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?; - ident.node.make_ascii_lowercase(); - if ident.node != "as" { - return Err(("expected \";\".", ident.span).into()); - } - - self.whitespace_or_comment(); - - if let Some(Token { kind: '*', .. }) = self.toks.peek() { - self.toks.next(); - return Ok(Some('*'.to_string())); - } else { - let name = self.parse_identifier_no_interpolation(false)?; - - return Ok(Some(name.node)); - } - } - - Ok(None) - } - - fn parse_module_config(&mut self) -> SassResult { - let mut config = ModuleConfig::default(); - - if let Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. }) = self.toks.peek() { - let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?; - ident.node.make_ascii_lowercase(); - if ident.node != "with" { - return Err(("expected \";\".", ident.span).into()); - } - - self.whitespace_or_comment(); - - self.span_before = ident.span; - - self.expect_char('(')?; - - loop { - self.whitespace_or_comment(); - self.expect_char('$')?; - - let name = self.parse_identifier_no_interpolation(false)?; - - self.whitespace_or_comment(); - self.expect_char(':')?; - self.whitespace_or_comment(); - - let value = self.parse_value(false, &|toks| match toks.peek() { - Some(Token { kind: ',', .. }) | Some(Token { kind: ')', .. }) => true, - _ => false, - })?; - - config.insert(name.map_node(|n| n.into()), value)?; - - match self.toks.next() { - Some(Token { kind: ',', .. }) => { - continue; - } - Some(Token { kind: ')', .. }) => { - break; - } - Some(..) | None => { - return Err(("expected \")\".", self.span_before).into()); - } - } - } - } - - Ok(config) - } - - pub fn load_module( - &mut self, - name: &str, - config: &mut ModuleConfig, - ) -> SassResult<(Module, Vec)> { - Ok(match name { - "sass:color" => (declare_module_color(), Vec::new()), - "sass:list" => (declare_module_list(), Vec::new()), - "sass:map" => (declare_module_map(), Vec::new()), - "sass:math" => (declare_module_math(), Vec::new()), - "sass:meta" => (declare_module_meta(), Vec::new()), - "sass:selector" => (declare_module_selector(), Vec::new()), - "sass:string" => (declare_module_string(), Vec::new()), - _ => { - if let Some(import) = self.find_import(name.as_ref()) { - let mut global_scope = Scope::new(); - - let file = self - .map - .add_file(name.to_owned(), String::from_utf8(fs::read(&import)?)?); - - let stmts = Parser { - toks: &mut Lexer::new(&file) - .collect::>() - .into_iter() - .peekmore(), - map: self.map, - path: &import, - scopes: self.scopes, - global_scope: &mut global_scope, - super_selectors: self.super_selectors, - span_before: file.span.subspan(0, 0), - content: self.content, - flags: self.flags, - at_root: self.at_root, - at_root_has_selector: self.at_root_has_selector, - extender: self.extender, - content_scopes: self.content_scopes, - options: self.options, - modules: self.modules, - module_config: config, - } - .parse()?; - - if !config.is_empty() { - return Err(( - "This variable was not declared with !default in the @used module.", - self.span_before, - ) - .into()); - } - - (Module::new_from_scope(global_scope), stmts) - } else { - return Err(("Can't find stylesheet to import.", self.span_before).into()); - } - } - }) - } - - /// Returns any multiline comments that may have been found - /// while loading modules - #[allow(clippy::eval_order_dependence)] - fn load_modules(&mut self) -> SassResult> { - let mut comments = Vec::new(); - - loop { - self.whitespace(); - match self.toks.peek() { - Some(Token { kind: '@', .. }) => { - self.toks.advance_cursor(); - - if let Some(Token { kind, .. }) = self.toks.peek() { - if !matches!(kind, 'a'..='z' | 'A'..='Z' | '\\') { - break; - } - } - - match AtRuleKind::try_from(&peek_ident_no_interpolation( - self.toks, - false, - self.span_before, - )?)? { - AtRuleKind::Use => { - self.toks.truncate_iterator_to_cursor(); - } - _ => { - break; - } - } - - self.whitespace_or_comment(); - - let quote = match self.toks.next() { - Some(Token { kind: q @ '"', .. }) | Some(Token { kind: q @ '\'', .. }) => q, - Some(..) | None => todo!(), - }; - - let Spanned { node: module, span } = self.parse_quoted_string(quote)?; - let module_name = module.unquote().to_css_string(span)?; - - self.whitespace_or_comment(); - - let module_alias = self.parse_module_alias()?; - - self.whitespace_or_comment(); - - let mut config = self.parse_module_config()?; - - self.whitespace_or_comment(); - self.expect_char(';')?; - - let (module, mut stmts) = - self.load_module(module_name.as_ref(), &mut config)?; - - comments.append(&mut stmts); - - // if the config isn't empty here, that means - // variables were passed to a builtin module - if !config.is_empty() { - return Err(("Built-in modules can't be configured.", span).into()); - } - - 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.into(), module, span)?; - } - Some(Token { kind: '/', .. }) => { - self.toks.next(); - match self.parse_comment()?.node { - Comment::Silent => continue, - Comment::Loud(s) => comments.push(Stmt::Comment(s)), - } - } - Some(..) | None => break, - } - } - - self.toks.reset_cursor(); - - Ok(comments) - } - fn parse_stmt(&mut self) -> SassResult> { let mut stmts = Vec::new(); while let Some(Token { kind, pos }) = self.toks.peek() { diff --git a/src/parse/module.rs b/src/parse/module.rs new file mode 100644 index 0000000..0e97b0f --- /dev/null +++ b/src/parse/module.rs @@ -0,0 +1,251 @@ +use std::{convert::TryFrom, fs}; + +use codemap::Spanned; +use peekmore::PeekMore; + +use crate::{ + atrule::AtRuleKind, + builtin::modules::{ + declare_module_color, declare_module_list, declare_module_map, declare_module_math, + declare_module_meta, declare_module_selector, declare_module_string, Module, ModuleConfig, + }, + error::SassResult, + lexer::Lexer, + parse::{common::Comment, Parser, Stmt}, + scope::Scope, + utils::peek_ident_no_interpolation, + Token, +}; + +impl<'a> Parser<'a> { + fn parse_module_alias(&mut self) -> SassResult> { + if let Some(Token { kind: 'a', .. }) | Some(Token { kind: 'A', .. }) = self.toks.peek() { + let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?; + ident.node.make_ascii_lowercase(); + if ident.node != "as" { + return Err(("expected \";\".", ident.span).into()); + } + + self.whitespace_or_comment(); + + if let Some(Token { kind: '*', .. }) = self.toks.peek() { + self.toks.next(); + return Ok(Some('*'.to_string())); + } else { + let name = self.parse_identifier_no_interpolation(false)?; + + return Ok(Some(name.node)); + } + } + + Ok(None) + } + + fn parse_module_config(&mut self) -> SassResult { + let mut config = ModuleConfig::default(); + + if let Some(Token { kind: 'w', .. }) | Some(Token { kind: 'W', .. }) = self.toks.peek() { + let mut ident = peek_ident_no_interpolation(self.toks, false, self.span_before)?; + ident.node.make_ascii_lowercase(); + if ident.node != "with" { + return Err(("expected \";\".", ident.span).into()); + } + + self.whitespace_or_comment(); + + self.span_before = ident.span; + + self.expect_char('(')?; + + loop { + self.whitespace_or_comment(); + self.expect_char('$')?; + + let name = self.parse_identifier_no_interpolation(false)?; + + self.whitespace_or_comment(); + self.expect_char(':')?; + self.whitespace_or_comment(); + + let value = self.parse_value(false, &|toks| match toks.peek() { + Some(Token { kind: ',', .. }) | Some(Token { kind: ')', .. }) => true, + _ => false, + })?; + + config.insert(name.map_node(|n| n.into()), value)?; + + match self.toks.next() { + Some(Token { kind: ',', .. }) => { + continue; + } + Some(Token { kind: ')', .. }) => { + break; + } + Some(..) | None => { + return Err(("expected \")\".", self.span_before).into()); + } + } + } + } + + Ok(config) + } + + pub fn load_module( + &mut self, + name: &str, + config: &mut ModuleConfig, + ) -> SassResult<(Module, Vec)> { + Ok(match name { + "sass:color" => (declare_module_color(), Vec::new()), + "sass:list" => (declare_module_list(), Vec::new()), + "sass:map" => (declare_module_map(), Vec::new()), + "sass:math" => (declare_module_math(), Vec::new()), + "sass:meta" => (declare_module_meta(), Vec::new()), + "sass:selector" => (declare_module_selector(), Vec::new()), + "sass:string" => (declare_module_string(), Vec::new()), + _ => { + if let Some(import) = self.find_import(name.as_ref()) { + let mut global_scope = Scope::new(); + + let file = self + .map + .add_file(name.to_owned(), String::from_utf8(fs::read(&import)?)?); + + let stmts = Parser { + toks: &mut Lexer::new(&file) + .collect::>() + .into_iter() + .peekmore(), + map: self.map, + path: &import, + scopes: self.scopes, + global_scope: &mut global_scope, + super_selectors: self.super_selectors, + span_before: file.span.subspan(0, 0), + content: self.content, + flags: self.flags, + at_root: self.at_root, + at_root_has_selector: self.at_root_has_selector, + extender: self.extender, + content_scopes: self.content_scopes, + options: self.options, + modules: self.modules, + module_config: config, + } + .parse()?; + + if !config.is_empty() { + return Err(( + "This variable was not declared with !default in the @used module.", + self.span_before, + ) + .into()); + } + + (Module::new_from_scope(global_scope), stmts) + } else { + return Err(("Can't find stylesheet to import.", self.span_before).into()); + } + } + }) + } + + /// Returns any multiline comments that may have been found + /// while loading modules + pub(super) fn load_modules(&mut self) -> SassResult> { + let mut comments = Vec::new(); + + loop { + self.whitespace(); + match self.toks.peek() { + Some(Token { kind: '@', .. }) => { + self.toks.advance_cursor(); + + if let Some(Token { kind, .. }) = self.toks.peek() { + if !matches!(kind, 'a'..='z' | 'A'..='Z' | '\\') { + break; + } + } + + match AtRuleKind::try_from(&peek_ident_no_interpolation( + self.toks, + false, + self.span_before, + )?)? { + AtRuleKind::Use => { + self.toks.truncate_iterator_to_cursor(); + } + _ => { + break; + } + } + + self.whitespace_or_comment(); + + let quote = match self.toks.next() { + Some(Token { kind: q @ '"', .. }) | Some(Token { kind: q @ '\'', .. }) => q, + Some(..) | None => todo!(), + }; + + let Spanned { node: module, span } = self.parse_quoted_string(quote)?; + let module_name = module.unquote().to_css_string(span)?; + + self.whitespace_or_comment(); + + let module_alias = self.parse_module_alias()?; + + self.whitespace_or_comment(); + + let mut config = self.parse_module_config()?; + + self.whitespace_or_comment(); + self.expect_char(';')?; + + let (module, mut stmts) = + self.load_module(module_name.as_ref(), &mut config)?; + + comments.append(&mut stmts); + + // if the config isn't empty here, that means + // variables were passed to a builtin module + if !config.is_empty() { + return Err(("Built-in modules can't be configured.", span).into()); + } + + 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.into(), module, span)?; + } + Some(Token { kind: '/', .. }) => { + self.toks.next(); + match self.parse_comment()?.node { + Comment::Silent => continue, + Comment::Loud(s) => comments.push(Stmt::Comment(s)), + } + } + Some(..) | None => break, + } + } + + self.toks.reset_cursor(); + + Ok(comments) + } +}