diff --git a/src/lib.rs b/src/lib.rs index 7f8eb13..26b2845 100644 --- a/src/lib.rs +++ b/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>); - #[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 { - 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 { - 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 { - 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 + Into + Clone>( - p: &P, - map: &mut CodeMap, - ) -> SassResult<(Vec>, 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>) -> StyleSheet { - StyleSheet(s) - } -} - -struct StyleSheetParser<'a> { - lexer: PeekMoreIterator>, - nesting: u32, - map: &'a mut CodeMap, - path: &'a Path, -} - -impl<'a> StyleSheetParser<'a> { - fn parse_toplevel(mut self) -> SassResult<(Vec>, Scope)> { - let mut rules: Vec> = 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>> { - 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>( toks: &mut PeekMoreIterator, scope: &mut Scope, @@ -736,27 +383,3 @@ pub(crate) fn eat_expr>( } 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 - ); - } -} diff --git a/src/stylesheet.rs b/src/stylesheet.rs new file mode 100644 index 0000000..704f888 --- /dev/null +++ b/src/stylesheet.rs @@ -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>); + +#[cfg(feature = "wasm")] +#[wasm_bindgen] +impl StyleSheet { + pub fn new(input: String) -> Result { + 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 { + 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 { + 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 + Into + Clone>( + p: &P, + map: &mut CodeMap, + ) -> SassResult<(Vec>, 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>) -> StyleSheet { + StyleSheet(s) + } +} + +struct StyleSheetParser<'a> { + lexer: PeekMoreIterator>, + nesting: u32, + map: &'a mut CodeMap, + path: &'a Path, +} + +impl<'a> StyleSheetParser<'a> { + fn parse_toplevel(mut self) -> SassResult<(Vec>, Scope)> { + let mut rules: Vec> = 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>> { + 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 + ); + } +}