move module parsing to separate file
This commit is contained in:
parent
cacf605af8
commit
bb0b352af2
242
src/parse/mod.rs
242
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<Option<String>> {
|
||||
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<ModuleConfig> {
|
||||
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<Stmt>)> {
|
||||
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::<Vec<Token>>()
|
||||
.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<Vec<Stmt>> {
|
||||
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<Vec<Stmt>> {
|
||||
let mut stmts = Vec::new();
|
||||
while let Some(Token { kind, pos }) = self.toks.peek() {
|
||||
|
251
src/parse/module.rs
Normal file
251
src/parse/module.rs
Normal file
@ -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<Option<String>> {
|
||||
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<ModuleConfig> {
|
||||
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<Stmt>)> {
|
||||
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::<Vec<Token>>()
|
||||
.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<Vec<Stmt>> {
|
||||
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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user