diff --git a/src/lib.rs b/src/lib.rs index 8d877f8..32f4c41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,7 +80,7 @@ grass input.scss )] #![cfg_attr(feature = "nightly", feature(track_caller))] #![cfg_attr(feature = "profiling", inline(never))] -use std::{fs, path::Path}; +use std::{collections::HashMap, fs, path::Path}; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; @@ -292,6 +292,7 @@ pub fn from_path(p: &str, options: &Options) -> Result { extender: &mut Extender::new(empty_span), content_scopes: &mut Scopes::new(), options, + modules: &mut HashMap::new(), } .parse() .map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?; @@ -336,6 +337,7 @@ pub fn from_string(p: String, options: &Options) -> Result { extender: &mut Extender::new(empty_span), content_scopes: &mut Scopes::new(), options, + modules: &mut HashMap::new(), } .parse() .map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?; @@ -371,6 +373,7 @@ pub fn from_string(p: String) -> std::result::Result { extender: &mut Extender::new(empty_span), content_scopes: &mut Scopes::new(), options: &Options::default(), + modules: &mut HashMap::new(), } .parse() .map_err(|e| raw_to_parse_error(&map, *e, true).to_string())?; diff --git a/src/parse/control_flow.rs b/src/parse/control_flow.rs index 5ce5333..47d69c4 100644 --- a/src/parse/control_flow.rs +++ b/src/parse/control_flow.rs @@ -53,6 +53,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } .parse_stmt()?; } else { @@ -112,6 +113,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } .parse_stmt()?; } else { @@ -140,6 +142,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } .parse_stmt(); } @@ -320,8 +323,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?; + .parse_stmt()?; if !these_stmts.is_empty() { return Ok(these_stmts); } @@ -342,8 +346,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?, + .parse_stmt()?, ); } } @@ -392,8 +397,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?; + .parse_stmt()?; if !these_stmts.is_empty() { return Ok(these_stmts); } @@ -414,8 +420,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?, + .parse_stmt()?, ); } val = self.parse_value_from_vec(cond.clone(), true)?; @@ -517,8 +524,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?; + .parse_stmt()?; if !these_stmts.is_empty() { return Ok(these_stmts); } @@ -539,8 +547,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?, + .parse_stmt()?, ); } } diff --git a/src/parse/function.rs b/src/parse/function.rs index fb8b516..67544e5 100644 --- a/src/parse/function.rs +++ b/src/parse/function.rs @@ -108,8 +108,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?; + .parse_stmt()?; if entered_scope { self.scopes.exit_scope(); diff --git a/src/parse/import.rs b/src/parse/import.rs index debad53..eaa0fd0 100644 --- a/src/parse/import.rs +++ b/src/parse/import.rs @@ -102,6 +102,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } .parse(); } diff --git a/src/parse/keyframes.rs b/src/parse/keyframes.rs index b24ec30..8494a5e 100644 --- a/src/parse/keyframes.rs +++ b/src/parse/keyframes.rs @@ -166,6 +166,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, }) .parse_keyframes_selector()?; @@ -197,6 +198,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } .parse_stmt()?; diff --git a/src/parse/mixin.rs b/src/parse/mixin.rs index 1e3466c..f72a5b5 100644 --- a/src/parse/mixin.rs +++ b/src/parse/mixin.rs @@ -155,8 +155,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()?; + .parse_stmt()?; self.content.pop(); self.scopes.exit_scope(); @@ -207,8 +208,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.scopes, options: self.options, + modules: self.modules, } - .parse()? + .parse_stmt()? } else { Vec::new() }; diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 040f568..b2224d3 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, path::Path, vec::IntoIter}; +use std::{collections::HashMap, convert::TryFrom, path::Path, vec::IntoIter}; use codemap::{CodeMap, Span, Spanned}; use peekmore::{PeekMore, PeekMoreIterator}; @@ -86,11 +86,17 @@ pub(crate) struct Parser<'a> { pub extender: &'a mut Extender, pub options: &'a Options<'a>, + + pub modules: &'a mut HashMap, } impl<'a> Parser<'a> { pub fn parse(&mut self) -> SassResult> { let mut stmts = Vec::new(); + + self.whitespace(); + stmts.append(&mut self.load_modules()?); + while self.toks.peek().is_some() { stmts.append(&mut self.parse_stmt()?); if self.flags.in_function() && !stmts.is_empty() { @@ -101,6 +107,76 @@ impl<'a> Parser<'a> { Ok(stmts) } + /// Returns any multiline comments that may have been found + /// while loading modules + 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(); + + 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(..) => todo!(), + None => todo!(), + }; + + let Spanned { node: module, span } = self.parse_quoted_string(quote)?; + let module = module.unquote().to_css_string(span)?; + + if let Some(Token { kind: ';', .. }) = self.toks.peek() { + self.toks.next(); + } else { + todo!() + } + + match module.as_ref() { + "sass:color" => todo!("builtin module `sass:color` not yet implemented"), + "sass:list" => todo!("builtin module `sass:list` not yet implemented"), + "sass:map" => todo!("builtin module `sass:map` not yet implemented"), + "sass:math" => todo!("builtin module `sass:math` not yet implemented"), + "sass:meta" => todo!("builtin module `sass:meta` not yet implemented"), + "sass:selector" => { + todo!("builtin module `sass:selector` not yet implemented") + } + "sass:string" => todo!("builtin module `sass:string` not yet implemented"), + _ => todo!("@use not yet implemented"), + } + } + Some(Token { kind: '/', .. }) => { + self.toks.advance_cursor(); + 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() { @@ -196,7 +272,13 @@ impl<'a> Parser<'a> { AtRuleKind::Unknown(_) => { stmts.push(self.parse_unknown_at_rule(kind_string.node)?) } - AtRuleKind::Use => todo!("@use not yet implemented"), + AtRuleKind::Use => { + return Err(( + "@use rules must be written before any other rules.", + kind_string.span, + ) + .into()) + } AtRuleKind::Forward => todo!("@forward not yet implemented"), AtRuleKind::Extend => self.parse_extend()?, AtRuleKind::Supports => stmts.push(self.parse_supports()?), @@ -377,6 +459,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, }, allows_parent, true, @@ -668,8 +751,9 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } - .parse()? + .parse_stmt()? .into_iter() .filter_map(|s| match s { Stmt::Style(..) => { @@ -709,6 +793,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } .parse_selector(false, true, String::new())?; diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index f18976d..9732ca7 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -202,6 +202,7 @@ impl<'a> Parser<'a> { extender: self.extender, content_scopes: self.content_scopes, options: self.options, + modules: self.modules, } .parse_value(in_paren) } diff --git a/src/value/mod.rs b/src/value/mod.rs index 8d0bbad..5cf7cd4 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -477,6 +477,7 @@ impl Value { extender: parser.extender, content_scopes: parser.content_scopes, options: parser.options, + modules: parser.modules, } .parse_selector(allows_parent, true, String::new())? .0)