refactor stylesheet into separate file
This commit is contained in:
parent
769b7628d8
commit
8711e61398
393
src/lib.rs
393
src/lib.rs
@ -80,33 +80,26 @@ grass input.scss
|
||||
clippy::redundant_pub_crate,
|
||||
)]
|
||||
#![cfg_attr(feature = "nightly", feature(track_caller))]
|
||||
use std::fs;
|
||||
use std::iter::Iterator;
|
||||
use std::path::Path;
|
||||
|
||||
use codemap::{CodeMap, Span, Spanned};
|
||||
use std::iter::Iterator;
|
||||
|
||||
use codemap::{Span, Spanned};
|
||||
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::atrule::{eat_include, AtRule, AtRuleKind, Function, Mixin};
|
||||
use crate::atrule::{AtRule, AtRuleKind, Function, Mixin};
|
||||
pub use crate::error::{SassError, SassResult};
|
||||
use crate::imports::import;
|
||||
use crate::lexer::Lexer;
|
||||
use crate::output::Css;
|
||||
use crate::scope::{
|
||||
global_var_exists, insert_global_fn, insert_global_mixin, insert_global_var, Scope,
|
||||
GLOBAL_SCOPE,
|
||||
};
|
||||
use crate::scope::{insert_global_var, Scope};
|
||||
use crate::selector::Selector;
|
||||
use crate::style::Style;
|
||||
pub use crate::stylesheet::StyleSheet;
|
||||
pub(crate) use crate::token::Token;
|
||||
use crate::utils::{
|
||||
devour_whitespace, eat_comment, eat_ident, eat_ident_no_interpolation, eat_variable_value,
|
||||
parse_quoted_string, read_until_closing_curly_brace, read_until_closing_paren,
|
||||
read_until_newline, VariableDecl,
|
||||
read_until_closing_curly_brace, read_until_closing_paren, read_until_newline, VariableDecl,
|
||||
};
|
||||
use crate::value::Value;
|
||||
|
||||
@ -122,16 +115,12 @@ mod output;
|
||||
mod scope;
|
||||
mod selector;
|
||||
mod style;
|
||||
mod stylesheet;
|
||||
mod token;
|
||||
mod unit;
|
||||
mod utils;
|
||||
mod value;
|
||||
|
||||
/// Represents a parsed SASS stylesheet with nesting
|
||||
#[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StyleSheet(Vec<Spanned<Stmt>>);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum Stmt {
|
||||
/// A [`Style`](/grass/style/struct.Style)
|
||||
@ -188,348 +177,6 @@ enum Expr {
|
||||
AtRule(AtRule),
|
||||
}
|
||||
|
||||
fn raw_to_parse_error(map: &CodeMap, err: SassError) -> SassError {
|
||||
let (message, span) = err.raw();
|
||||
SassError::from_loc(message, map.look_up_span(span))
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
#[wasm_bindgen]
|
||||
impl StyleSheet {
|
||||
pub fn new(input: String) -> Result<String, JsValue> {
|
||||
let mut map = CodeMap::new();
|
||||
let file = map.add_file("stdin".into(), input);
|
||||
Ok(Css::from_stylesheet(StyleSheet(
|
||||
StyleSheetParser {
|
||||
lexer: Lexer::new(&file).peekmore(),
|
||||
nesting: 0,
|
||||
map: &map,
|
||||
path: Path::new(""),
|
||||
}
|
||||
.parse_toplevel()
|
||||
.map_err(|e| raw_to_parse_error(&map, e).to_string())?
|
||||
.0,
|
||||
))
|
||||
.map_err(|e| raw_to_parse_error(&map, e).to_string())?
|
||||
.pretty_print()
|
||||
.map_err(|e| raw_to_parse_error(&map, e).to_string())?)
|
||||
}
|
||||
}
|
||||
|
||||
impl StyleSheet {
|
||||
/// Write CSS to `buf`, constructed from a string
|
||||
///
|
||||
/// ```
|
||||
/// use grass::{SassResult, StyleSheet};
|
||||
///
|
||||
/// fn main() -> SassResult<()> {
|
||||
/// let sass = StyleSheet::new("a { b { color: red; } }".to_string())?;
|
||||
/// assert_eq!(sass, "a b {\n color: red;\n}\n");
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
pub fn new(input: String) -> SassResult<String> {
|
||||
let mut map = CodeMap::new();
|
||||
let file = map.add_file("stdin".into(), input);
|
||||
Css::from_stylesheet(StyleSheet(
|
||||
StyleSheetParser {
|
||||
lexer: Lexer::new(&file).peekmore(),
|
||||
nesting: 0,
|
||||
map: &mut map,
|
||||
path: Path::new(""),
|
||||
}
|
||||
.parse_toplevel()
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?
|
||||
.0,
|
||||
))
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?
|
||||
.pretty_print(&map)
|
||||
.map_err(|e| raw_to_parse_error(&map, e))
|
||||
}
|
||||
|
||||
/// Write CSS to `buf`, constructed from a path
|
||||
///
|
||||
/// ```
|
||||
/// use grass::{SassResult, StyleSheet};
|
||||
///
|
||||
/// fn main() -> SassResult<()> {
|
||||
/// let sass = StyleSheet::from_path("input.scss")?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
pub fn from_path(p: &str) -> SassResult<String> {
|
||||
let mut map = CodeMap::new();
|
||||
let file = map.add_file(p.clone().into(), String::from_utf8(fs::read(p.clone())?)?);
|
||||
Css::from_stylesheet(StyleSheet(
|
||||
StyleSheetParser {
|
||||
lexer: Lexer::new(&file).peekmore(),
|
||||
nesting: 0,
|
||||
map: &mut map,
|
||||
path: p.as_ref(),
|
||||
}
|
||||
.parse_toplevel()
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?
|
||||
.0,
|
||||
))
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?
|
||||
.pretty_print(&map)
|
||||
.map_err(|e| raw_to_parse_error(&map, e))
|
||||
}
|
||||
|
||||
pub(crate) fn export_from_path<P: AsRef<Path> + Into<String> + Clone>(
|
||||
p: &P,
|
||||
map: &mut CodeMap,
|
||||
) -> SassResult<(Vec<Spanned<Stmt>>, Scope)> {
|
||||
let file = map.add_file(p.clone().into(), String::from_utf8(fs::read(p)?)?);
|
||||
Ok(StyleSheetParser {
|
||||
lexer: Lexer::new(&file).peekmore(),
|
||||
nesting: 0,
|
||||
map,
|
||||
path: p.as_ref(),
|
||||
}
|
||||
.parse_toplevel()?)
|
||||
}
|
||||
|
||||
pub(crate) fn from_stmts(s: Vec<Spanned<Stmt>>) -> StyleSheet {
|
||||
StyleSheet(s)
|
||||
}
|
||||
}
|
||||
|
||||
struct StyleSheetParser<'a> {
|
||||
lexer: PeekMoreIterator<Lexer<'a>>,
|
||||
nesting: u32,
|
||||
map: &'a mut CodeMap,
|
||||
path: &'a Path,
|
||||
}
|
||||
|
||||
impl<'a> StyleSheetParser<'a> {
|
||||
fn parse_toplevel(mut self) -> SassResult<(Vec<Spanned<Stmt>>, Scope)> {
|
||||
let mut rules: Vec<Spanned<Stmt>> = Vec::new();
|
||||
while let Some(Token { kind, .. }) = self.lexer.peek() {
|
||||
match kind {
|
||||
'a'..='z' | 'A'..='Z' | '_' | '-' | '0'..='9'
|
||||
| '[' | '#' | ':' | '*' | '%' | '.' | '>' => rules
|
||||
.extend(self.eat_rules(&Selector::new(), &mut Scope::new())?),
|
||||
&'\t' | &'\n' | ' ' => {
|
||||
self.lexer.next();
|
||||
continue;
|
||||
}
|
||||
'$' => {
|
||||
self.lexer.next();
|
||||
let name = eat_ident(&mut self.lexer, &Scope::new(), &Selector::new())?;
|
||||
devour_whitespace(&mut self.lexer);
|
||||
let Token { kind, pos } = self
|
||||
.lexer
|
||||
.next()
|
||||
.unwrap();
|
||||
if kind != ':' {
|
||||
return Err(("expected \":\".", pos).into());
|
||||
}
|
||||
let VariableDecl { val, default, .. } =
|
||||
eat_variable_value(&mut self.lexer, &Scope::new(), &Selector::new())?;
|
||||
if !(default && global_var_exists(&name)) {
|
||||
insert_global_var(&name.node, val)?;
|
||||
}
|
||||
}
|
||||
'/' => {
|
||||
self.lexer.next();
|
||||
if '*' == self.lexer.peek().unwrap().kind {
|
||||
self.lexer.next();
|
||||
let comment = eat_comment(&mut self.lexer, &Scope::new(), &Selector::new())?;
|
||||
rules.push(comment.map_node(Stmt::MultilineComment));
|
||||
} else if '/' == self.lexer.peek().unwrap().kind {
|
||||
read_until_newline(&mut self.lexer);
|
||||
devour_whitespace(&mut self.lexer);
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
'@' => {
|
||||
self.lexer.next();
|
||||
let Spanned { node: at_rule_kind, span } = eat_ident(&mut self.lexer, &Scope::new(), &Selector::new())?;
|
||||
if at_rule_kind.is_empty() {
|
||||
return Err(("Expected identifier.", span).into());
|
||||
}
|
||||
match AtRuleKind::from(at_rule_kind.as_str()) {
|
||||
AtRuleKind::Include => rules.extend(eat_include(
|
||||
&mut self.lexer,
|
||||
&Scope::new(),
|
||||
&Selector::new(),
|
||||
None,
|
||||
)?),
|
||||
AtRuleKind::Import => {
|
||||
devour_whitespace(&mut self.lexer);
|
||||
let mut file_name = String::new();
|
||||
let next = match self.lexer.next() {
|
||||
Some(v) => v,
|
||||
None => todo!("expected input after @import")
|
||||
};
|
||||
match next.kind {
|
||||
q @ '"' | q @ '\'' => {
|
||||
file_name.push_str(
|
||||
&parse_quoted_string(
|
||||
&mut self.lexer,
|
||||
&Scope::new(),
|
||||
q,
|
||||
&Selector::new())?
|
||||
.node.unquote().to_css_string(span)?);
|
||||
}
|
||||
_ => return Err(("Expected string.", next.pos()).into()),
|
||||
}
|
||||
if let Some(t) = self.lexer.peek() {
|
||||
if t.kind == ';' {
|
||||
self.lexer.next();
|
||||
}
|
||||
}
|
||||
|
||||
devour_whitespace(&mut self.lexer);
|
||||
|
||||
let (new_rules, new_scope) = import(self.path, file_name.as_ref(), &mut self.map)?;
|
||||
rules.extend(new_rules);
|
||||
GLOBAL_SCOPE.with(|s| {
|
||||
s.borrow_mut().extend(new_scope);
|
||||
});
|
||||
}
|
||||
v => {
|
||||
let rule = AtRule::from_tokens(&v, span, &mut self.lexer, &mut Scope::new(), &Selector::new(), None)?;
|
||||
match rule.node {
|
||||
AtRule::Mixin(name, mixin) => {
|
||||
insert_global_mixin(&name, *mixin);
|
||||
}
|
||||
AtRule::Function(name, func) => {
|
||||
insert_global_fn(&name, *func);
|
||||
}
|
||||
AtRule::Charset => continue,
|
||||
AtRule::Warn(message) => self.warn(rule.span, &message),
|
||||
AtRule::Debug(message) => self.debug(rule.span, &message),
|
||||
AtRule::Return(_) => {
|
||||
return Err(
|
||||
("This at-rule is not allowed here.", rule.span).into()
|
||||
)
|
||||
}
|
||||
AtRule::For(f) => rules.extend(f.ruleset_eval(&mut Scope::new(), &Selector::new(), None)?),
|
||||
AtRule::While(w) => rules.extend(w.ruleset_eval(&mut Scope::new(), &Selector::new(), true, None)?),
|
||||
AtRule::Each(e) => {
|
||||
rules.extend(e.ruleset_eval(&mut Scope::new(), &Selector::new(), None)?)
|
||||
}
|
||||
AtRule::Include(s) => rules.extend(s),
|
||||
AtRule::Content => return Err(
|
||||
("@content is only allowed within mixin declarations.", rule.span
|
||||
).into()),
|
||||
AtRule::If(i) => {
|
||||
rules.extend(i.eval(&mut Scope::new(), &Selector::new(), None)?);
|
||||
}
|
||||
AtRule::AtRoot(root_rules) => rules.extend(root_rules),
|
||||
AtRule::Unknown(..) => rules.push(rule.map_node(Stmt::AtRule)),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'&' => {
|
||||
return Err(
|
||||
("Base-level rules cannot contain the parent-selector-referencing character '&'.", self.lexer.next().unwrap().pos()).into(),
|
||||
)
|
||||
}
|
||||
c if c.is_control() => {
|
||||
return Err(("expected selector.", self.lexer.next().unwrap().pos()).into());
|
||||
}
|
||||
_ => todo!("unexpected toplevel token: {:?}", kind),
|
||||
};
|
||||
}
|
||||
Ok((rules, GLOBAL_SCOPE.with(|s| s.borrow().clone())))
|
||||
}
|
||||
|
||||
fn eat_rules(
|
||||
&mut self,
|
||||
super_selector: &Selector,
|
||||
scope: &mut Scope,
|
||||
) -> SassResult<Vec<Spanned<Stmt>>> {
|
||||
let mut stmts = Vec::new();
|
||||
while let Some(expr) = eat_expr(&mut self.lexer, scope, super_selector, None)? {
|
||||
let span = expr.span;
|
||||
match expr.node {
|
||||
Expr::Style(s) => stmts.push(Spanned {
|
||||
node: Stmt::Style(s),
|
||||
span,
|
||||
}),
|
||||
Expr::AtRule(a) => match a {
|
||||
AtRule::For(f) => stmts.extend(f.ruleset_eval(scope, super_selector, None)?),
|
||||
AtRule::While(w) => {
|
||||
stmts.extend(w.ruleset_eval(scope, super_selector, false, None)?)
|
||||
}
|
||||
AtRule::Each(e) => stmts.extend(e.ruleset_eval(scope, super_selector, None)?),
|
||||
AtRule::Include(s) => stmts.extend(s),
|
||||
AtRule::If(i) => stmts.extend(i.eval(scope, super_selector, None)?),
|
||||
AtRule::Content => {
|
||||
return Err((
|
||||
"@content is only allowed within mixin declarations.",
|
||||
expr.span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
AtRule::Return(..) => {
|
||||
return Err(("This at-rule is not allowed here.", expr.span).into())
|
||||
}
|
||||
AtRule::AtRoot(root_stmts) => stmts.extend(root_stmts),
|
||||
AtRule::Debug(ref message) => self.debug(expr.span, message),
|
||||
AtRule::Warn(ref message) => self.warn(expr.span, message),
|
||||
AtRule::Mixin(..) | AtRule::Function(..) => todo!(),
|
||||
AtRule::Charset => todo!(),
|
||||
r @ AtRule::Unknown(..) => stmts.push(Spanned {
|
||||
node: Stmt::AtRule(r),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Expr::Styles(s) => stmts.extend(
|
||||
s.into_iter()
|
||||
.map(Box::new)
|
||||
.map(Stmt::Style)
|
||||
.map(|style| Spanned { node: style, span }),
|
||||
),
|
||||
Expr::MixinDecl(name, mixin) => {
|
||||
scope.insert_mixin(&name, *mixin);
|
||||
}
|
||||
Expr::FunctionDecl(name, func) => {
|
||||
scope.insert_fn(&name, *func);
|
||||
}
|
||||
Expr::Selector(s) => {
|
||||
self.nesting += 1;
|
||||
let rules = self.eat_rules(&super_selector.zip(&s), scope)?;
|
||||
stmts.push(Spanned {
|
||||
node: Stmt::RuleSet(RuleSet {
|
||||
super_selector: super_selector.clone(),
|
||||
selector: s,
|
||||
rules,
|
||||
}),
|
||||
span,
|
||||
});
|
||||
self.nesting -= 1;
|
||||
if self.nesting == 0 {
|
||||
return Ok(stmts);
|
||||
}
|
||||
}
|
||||
Expr::VariableDecl(name, val) => {
|
||||
if self.nesting == 0 {
|
||||
scope.insert_var(&name, *val.clone())?;
|
||||
insert_global_var(&name, *val)?;
|
||||
} else {
|
||||
scope.insert_var(&name, *val)?;
|
||||
}
|
||||
}
|
||||
Expr::MultilineComment(s) => stmts.push(Spanned {
|
||||
node: Stmt::MultilineComment(s),
|
||||
span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
Ok(stmts)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
|
||||
toks: &mut PeekMoreIterator<I>,
|
||||
scope: &mut Scope,
|
||||
@ -736,27 +383,3 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Functions that print to stdout or stderr
|
||||
impl<'a> StyleSheetParser<'a> {
|
||||
fn debug(&self, span: Span, message: &str) {
|
||||
let loc = self.map.look_up_span(span);
|
||||
eprintln!(
|
||||
"{}:{} Debug: {}",
|
||||
loc.file.name(),
|
||||
loc.begin.line + 1,
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
fn warn(&self, span: Span, message: &str) {
|
||||
let loc = self.map.look_up_span(span);
|
||||
eprintln!(
|
||||
"Warning: {}\n {} {}:{} root stylesheet",
|
||||
message,
|
||||
loc.file.name(),
|
||||
loc.begin.line + 1,
|
||||
loc.begin.column + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
398
src/stylesheet.rs
Normal file
398
src/stylesheet.rs
Normal file
@ -0,0 +1,398 @@
|
||||
use std::fs;
|
||||
use std::iter::Iterator;
|
||||
use std::path::Path;
|
||||
|
||||
use codemap::{CodeMap, Span, Spanned};
|
||||
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::atrule::{eat_include, AtRule, AtRuleKind};
|
||||
use crate::error::{SassError, SassResult};
|
||||
use crate::imports::import;
|
||||
use crate::lexer::Lexer;
|
||||
use crate::output::Css;
|
||||
use crate::scope::{
|
||||
global_var_exists, insert_global_fn, insert_global_mixin, insert_global_var, Scope,
|
||||
GLOBAL_SCOPE,
|
||||
};
|
||||
use crate::selector::Selector;
|
||||
use crate::token::Token;
|
||||
use crate::utils::{
|
||||
devour_whitespace, eat_comment, eat_ident, eat_variable_value, parse_quoted_string,
|
||||
read_until_newline, VariableDecl,
|
||||
};
|
||||
use crate::{eat_expr, Expr, RuleSet, Stmt};
|
||||
|
||||
/// Represents a parsed SASS stylesheet with nesting
|
||||
#[cfg_attr(feature = "wasm", wasm_bindgen)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StyleSheet(pub(crate) Vec<Spanned<Stmt>>);
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
#[wasm_bindgen]
|
||||
impl StyleSheet {
|
||||
pub fn new(input: String) -> Result<String, JsValue> {
|
||||
let mut map = CodeMap::new();
|
||||
let file = map.add_file("stdin".into(), input);
|
||||
Ok(Css::from_stylesheet(StyleSheet(
|
||||
StyleSheetParser {
|
||||
lexer: Lexer::new(&file).peekmore(),
|
||||
nesting: 0,
|
||||
map: &map,
|
||||
path: Path::new(""),
|
||||
}
|
||||
.parse_toplevel()
|
||||
.map_err(|e| raw_to_parse_error(&map, e).to_string())?
|
||||
.0,
|
||||
))
|
||||
.map_err(|e| raw_to_parse_error(&map, e).to_string())?
|
||||
.pretty_print()
|
||||
.map_err(|e| raw_to_parse_error(&map, e).to_string())?)
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_to_parse_error(map: &CodeMap, err: SassError) -> SassError {
|
||||
let (message, span) = err.raw();
|
||||
SassError::from_loc(message, map.look_up_span(span))
|
||||
}
|
||||
|
||||
impl StyleSheet {
|
||||
/// Write CSS to `buf`, constructed from a string
|
||||
///
|
||||
/// ```
|
||||
/// use grass::{SassResult, StyleSheet};
|
||||
///
|
||||
/// fn main() -> SassResult<()> {
|
||||
/// let sass = StyleSheet::new("a { b { color: red; } }".to_string())?;
|
||||
/// assert_eq!(sass, "a b {\n color: red;\n}\n");
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
pub fn new(input: String) -> SassResult<String> {
|
||||
let mut map = CodeMap::new();
|
||||
let file = map.add_file("stdin".into(), input);
|
||||
Css::from_stylesheet(StyleSheet(
|
||||
StyleSheetParser {
|
||||
lexer: Lexer::new(&file).peekmore(),
|
||||
nesting: 0,
|
||||
map: &mut map,
|
||||
path: Path::new(""),
|
||||
}
|
||||
.parse_toplevel()
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?
|
||||
.0,
|
||||
))
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?
|
||||
.pretty_print(&map)
|
||||
.map_err(|e| raw_to_parse_error(&map, e))
|
||||
}
|
||||
|
||||
/// Write CSS to `buf`, constructed from a path
|
||||
///
|
||||
/// ```
|
||||
/// use grass::{SassResult, StyleSheet};
|
||||
///
|
||||
/// fn main() -> SassResult<()> {
|
||||
/// let sass = StyleSheet::from_path("input.scss")?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
pub fn from_path(p: &str) -> SassResult<String> {
|
||||
let mut map = CodeMap::new();
|
||||
let file = map.add_file(p.clone().into(), String::from_utf8(fs::read(p.clone())?)?);
|
||||
Css::from_stylesheet(StyleSheet(
|
||||
StyleSheetParser {
|
||||
lexer: Lexer::new(&file).peekmore(),
|
||||
nesting: 0,
|
||||
map: &mut map,
|
||||
path: p.as_ref(),
|
||||
}
|
||||
.parse_toplevel()
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?
|
||||
.0,
|
||||
))
|
||||
.map_err(|e| raw_to_parse_error(&map, e))?
|
||||
.pretty_print(&map)
|
||||
.map_err(|e| raw_to_parse_error(&map, e))
|
||||
}
|
||||
|
||||
pub(crate) fn export_from_path<P: AsRef<Path> + Into<String> + Clone>(
|
||||
p: &P,
|
||||
map: &mut CodeMap,
|
||||
) -> SassResult<(Vec<Spanned<Stmt>>, Scope)> {
|
||||
let file = map.add_file(p.clone().into(), String::from_utf8(fs::read(p)?)?);
|
||||
Ok(StyleSheetParser {
|
||||
lexer: Lexer::new(&file).peekmore(),
|
||||
nesting: 0,
|
||||
map,
|
||||
path: p.as_ref(),
|
||||
}
|
||||
.parse_toplevel()?)
|
||||
}
|
||||
|
||||
pub(crate) fn from_stmts(s: Vec<Spanned<Stmt>>) -> StyleSheet {
|
||||
StyleSheet(s)
|
||||
}
|
||||
}
|
||||
|
||||
struct StyleSheetParser<'a> {
|
||||
lexer: PeekMoreIterator<Lexer<'a>>,
|
||||
nesting: u32,
|
||||
map: &'a mut CodeMap,
|
||||
path: &'a Path,
|
||||
}
|
||||
|
||||
impl<'a> StyleSheetParser<'a> {
|
||||
fn parse_toplevel(mut self) -> SassResult<(Vec<Spanned<Stmt>>, Scope)> {
|
||||
let mut rules: Vec<Spanned<Stmt>> = Vec::new();
|
||||
while let Some(Token { kind, .. }) = self.lexer.peek() {
|
||||
match kind {
|
||||
'a'..='z' | 'A'..='Z' | '_' | '-' | '0'..='9'
|
||||
| '[' | '#' | ':' | '*' | '%' | '.' | '>' => rules
|
||||
.extend(self.eat_rules(&Selector::new(), &mut Scope::new())?),
|
||||
&'\t' | &'\n' | ' ' => {
|
||||
self.lexer.next();
|
||||
continue;
|
||||
}
|
||||
'$' => {
|
||||
self.lexer.next();
|
||||
let name = eat_ident(&mut self.lexer, &Scope::new(), &Selector::new())?;
|
||||
devour_whitespace(&mut self.lexer);
|
||||
let Token { kind, pos } = self
|
||||
.lexer
|
||||
.next()
|
||||
.unwrap();
|
||||
if kind != ':' {
|
||||
return Err(("expected \":\".", pos).into());
|
||||
}
|
||||
let VariableDecl { val, default, .. } =
|
||||
eat_variable_value(&mut self.lexer, &Scope::new(), &Selector::new())?;
|
||||
if !(default && global_var_exists(&name)) {
|
||||
insert_global_var(&name.node, val)?;
|
||||
}
|
||||
}
|
||||
'/' => {
|
||||
self.lexer.next();
|
||||
if '*' == self.lexer.peek().unwrap().kind {
|
||||
self.lexer.next();
|
||||
let comment = eat_comment(&mut self.lexer, &Scope::new(), &Selector::new())?;
|
||||
rules.push(comment.map_node(Stmt::MultilineComment));
|
||||
} else if '/' == self.lexer.peek().unwrap().kind {
|
||||
read_until_newline(&mut self.lexer);
|
||||
devour_whitespace(&mut self.lexer);
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
'@' => {
|
||||
self.lexer.next();
|
||||
let Spanned { node: at_rule_kind, span } = eat_ident(&mut self.lexer, &Scope::new(), &Selector::new())?;
|
||||
if at_rule_kind.is_empty() {
|
||||
return Err(("Expected identifier.", span).into());
|
||||
}
|
||||
match AtRuleKind::from(at_rule_kind.as_str()) {
|
||||
AtRuleKind::Include => rules.extend(eat_include(
|
||||
&mut self.lexer,
|
||||
&Scope::new(),
|
||||
&Selector::new(),
|
||||
None,
|
||||
)?),
|
||||
AtRuleKind::Import => {
|
||||
devour_whitespace(&mut self.lexer);
|
||||
let mut file_name = String::new();
|
||||
let next = match self.lexer.next() {
|
||||
Some(v) => v,
|
||||
None => todo!("expected input after @import")
|
||||
};
|
||||
match next.kind {
|
||||
q @ '"' | q @ '\'' => {
|
||||
file_name.push_str(
|
||||
&parse_quoted_string(
|
||||
&mut self.lexer,
|
||||
&Scope::new(),
|
||||
q,
|
||||
&Selector::new())?
|
||||
.node.unquote().to_css_string(span)?);
|
||||
}
|
||||
_ => return Err(("Expected string.", next.pos()).into()),
|
||||
}
|
||||
if let Some(t) = self.lexer.peek() {
|
||||
if t.kind == ';' {
|
||||
self.lexer.next();
|
||||
}
|
||||
}
|
||||
|
||||
devour_whitespace(&mut self.lexer);
|
||||
|
||||
let (new_rules, new_scope) = import(self.path, file_name.as_ref(), &mut self.map)?;
|
||||
rules.extend(new_rules);
|
||||
GLOBAL_SCOPE.with(|s| {
|
||||
s.borrow_mut().extend(new_scope);
|
||||
});
|
||||
}
|
||||
v => {
|
||||
let rule = AtRule::from_tokens(&v, span, &mut self.lexer, &mut Scope::new(), &Selector::new(), None)?;
|
||||
match rule.node {
|
||||
AtRule::Mixin(name, mixin) => {
|
||||
insert_global_mixin(&name, *mixin);
|
||||
}
|
||||
AtRule::Function(name, func) => {
|
||||
insert_global_fn(&name, *func);
|
||||
}
|
||||
AtRule::Charset => continue,
|
||||
AtRule::Warn(message) => self.warn(rule.span, &message),
|
||||
AtRule::Debug(message) => self.debug(rule.span, &message),
|
||||
AtRule::Return(_) => {
|
||||
return Err(
|
||||
("This at-rule is not allowed here.", rule.span).into()
|
||||
)
|
||||
}
|
||||
AtRule::For(f) => rules.extend(f.ruleset_eval(&mut Scope::new(), &Selector::new(), None)?),
|
||||
AtRule::While(w) => rules.extend(w.ruleset_eval(&mut Scope::new(), &Selector::new(), true, None)?),
|
||||
AtRule::Each(e) => {
|
||||
rules.extend(e.ruleset_eval(&mut Scope::new(), &Selector::new(), None)?)
|
||||
}
|
||||
AtRule::Include(s) => rules.extend(s),
|
||||
AtRule::Content => return Err(
|
||||
("@content is only allowed within mixin declarations.", rule.span
|
||||
).into()),
|
||||
AtRule::If(i) => {
|
||||
rules.extend(i.eval(&mut Scope::new(), &Selector::new(), None)?);
|
||||
}
|
||||
AtRule::AtRoot(root_rules) => rules.extend(root_rules),
|
||||
AtRule::Unknown(..) => rules.push(rule.map_node(Stmt::AtRule)),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'&' => {
|
||||
return Err(
|
||||
("Base-level rules cannot contain the parent-selector-referencing character '&'.", self.lexer.next().unwrap().pos()).into(),
|
||||
)
|
||||
}
|
||||
c if c.is_control() => {
|
||||
return Err(("expected selector.", self.lexer.next().unwrap().pos()).into());
|
||||
}
|
||||
_ => todo!("unexpected toplevel token: {:?}", kind),
|
||||
};
|
||||
}
|
||||
Ok((rules, GLOBAL_SCOPE.with(|s| s.borrow().clone())))
|
||||
}
|
||||
|
||||
fn eat_rules(
|
||||
&mut self,
|
||||
super_selector: &Selector,
|
||||
scope: &mut Scope,
|
||||
) -> SassResult<Vec<Spanned<Stmt>>> {
|
||||
let mut stmts = Vec::new();
|
||||
while let Some(expr) = eat_expr(&mut self.lexer, scope, super_selector, None)? {
|
||||
let span = expr.span;
|
||||
match expr.node {
|
||||
Expr::Style(s) => stmts.push(Spanned {
|
||||
node: Stmt::Style(s),
|
||||
span,
|
||||
}),
|
||||
Expr::AtRule(a) => match a {
|
||||
AtRule::For(f) => stmts.extend(f.ruleset_eval(scope, super_selector, None)?),
|
||||
AtRule::While(w) => {
|
||||
stmts.extend(w.ruleset_eval(scope, super_selector, false, None)?)
|
||||
}
|
||||
AtRule::Each(e) => stmts.extend(e.ruleset_eval(scope, super_selector, None)?),
|
||||
AtRule::Include(s) => stmts.extend(s),
|
||||
AtRule::If(i) => stmts.extend(i.eval(scope, super_selector, None)?),
|
||||
AtRule::Content => {
|
||||
return Err((
|
||||
"@content is only allowed within mixin declarations.",
|
||||
expr.span,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
AtRule::Return(..) => {
|
||||
return Err(("This at-rule is not allowed here.", expr.span).into())
|
||||
}
|
||||
AtRule::AtRoot(root_stmts) => stmts.extend(root_stmts),
|
||||
AtRule::Debug(ref message) => self.debug(expr.span, message),
|
||||
AtRule::Warn(ref message) => self.warn(expr.span, message),
|
||||
AtRule::Mixin(..) | AtRule::Function(..) => todo!(),
|
||||
AtRule::Charset => todo!(),
|
||||
r @ AtRule::Unknown(..) => stmts.push(Spanned {
|
||||
node: Stmt::AtRule(r),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Expr::Styles(s) => stmts.extend(
|
||||
s.into_iter()
|
||||
.map(Box::new)
|
||||
.map(Stmt::Style)
|
||||
.map(|style| Spanned { node: style, span }),
|
||||
),
|
||||
Expr::MixinDecl(name, mixin) => {
|
||||
scope.insert_mixin(&name, *mixin);
|
||||
}
|
||||
Expr::FunctionDecl(name, func) => {
|
||||
scope.insert_fn(&name, *func);
|
||||
}
|
||||
Expr::Selector(s) => {
|
||||
self.nesting += 1;
|
||||
let rules = self.eat_rules(&super_selector.zip(&s), scope)?;
|
||||
stmts.push(Spanned {
|
||||
node: Stmt::RuleSet(RuleSet {
|
||||
super_selector: super_selector.clone(),
|
||||
selector: s,
|
||||
rules,
|
||||
}),
|
||||
span,
|
||||
});
|
||||
self.nesting -= 1;
|
||||
if self.nesting == 0 {
|
||||
return Ok(stmts);
|
||||
}
|
||||
}
|
||||
Expr::VariableDecl(name, val) => {
|
||||
if self.nesting == 0 {
|
||||
scope.insert_var(&name, *val.clone())?;
|
||||
insert_global_var(&name, *val)?;
|
||||
} else {
|
||||
scope.insert_var(&name, *val)?;
|
||||
}
|
||||
}
|
||||
Expr::MultilineComment(s) => stmts.push(Spanned {
|
||||
node: Stmt::MultilineComment(s),
|
||||
span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
Ok(stmts)
|
||||
}
|
||||
}
|
||||
|
||||
/// Functions that print to stdout or stderr
|
||||
impl<'a> StyleSheetParser<'a> {
|
||||
fn debug(&self, span: Span, message: &str) {
|
||||
let loc = self.map.look_up_span(span);
|
||||
eprintln!(
|
||||
"{}:{} Debug: {}",
|
||||
loc.file.name(),
|
||||
loc.begin.line + 1,
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
fn warn(&self, span: Span, message: &str) {
|
||||
let loc = self.map.look_up_span(span);
|
||||
eprintln!(
|
||||
"Warning: {}\n {} {}:{} root stylesheet",
|
||||
message,
|
||||
loc.file.name(),
|
||||
loc.begin.line + 1,
|
||||
loc.begin.column + 1
|
||||
);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user