diff --git a/src/atrule/each_rule.rs b/src/atrule/each_rule.rs new file mode 100644 index 0000000..e0a1fd4 --- /dev/null +++ b/src/atrule/each_rule.rs @@ -0,0 +1,129 @@ +use codemap::{Span, Spanned}; + +use peekmore::{PeekMore, PeekMoreIterator}; + +use crate::common::{Brackets, ListSeparator}; +use crate::error::SassResult; +use crate::scope::Scope; +use crate::selector::Selector; +use crate::utils::{ + devour_whitespace, eat_ident, read_until_closing_curly_brace, read_until_open_curly_brace, +}; +use crate::value::Value; +use crate::{Stmt, Token}; + +use super::{ruleset_eval, AtRule}; + +#[derive(Debug, Clone)] +pub(crate) struct Each { + vars: Vec>, + iter: Vec, + body: Vec, +} + +impl Each { + pub fn ruleset_eval( + self, + scope: &mut Scope, + super_selector: &Selector, + ) -> SassResult>> { + let mut stmts = Vec::new(); + for row in self.iter { + let this_iterator = match row { + Value::List(v, ..) => v, + Value::Map(m) => m + .into_iter() + .map(|(k, v)| Value::List(vec![k, v], ListSeparator::Space, Brackets::None)) + .collect(), + v => vec![v], + }; + + if self.vars.len() == 1 { + scope.insert_var( + &self.vars[0], + Spanned { + node: Value::List(this_iterator, ListSeparator::Space, Brackets::None), + span: self.vars[0].span, + }, + )?; + } else { + for (var, val) in self.vars.clone().into_iter().zip( + this_iterator + .into_iter() + .chain(std::iter::once(Value::Null).cycle()), + ) { + scope.insert_var( + &var.node, + Spanned { + node: val, + span: var.span, + }, + )?; + } + } + + ruleset_eval( + &mut self.body.clone().into_iter().peekmore(), + scope, + super_selector, + false, + &mut stmts, + )?; + } + Ok(stmts) + } +} + +pub(crate) fn parse_each>( + toks: &mut PeekMoreIterator, + scope: &mut Scope, + super_selector: &Selector, + mut span: Span, +) -> SassResult { + devour_whitespace(toks); + let mut vars = Vec::new(); + loop { + let next = toks.next().ok_or(("expected \"$\".", span))?; + span = next.pos(); + match next.kind { + '$' => vars.push(eat_ident(toks, scope, super_selector)?), + _ => return Err(("expected \"$\".", next.pos()).into()), + } + devour_whitespace(toks); + if toks + .peek() + .ok_or(("expected \"$\".", vars[vars.len() - 1].span))? + .kind + == ',' + { + toks.next(); + devour_whitespace(toks); + } else { + break; + } + } + if toks.peek().is_none() { + todo!() + } + let i = eat_ident(toks, scope, super_selector)?; + if i.node.to_ascii_lowercase() != "in" { + return Err(("Expected \"in\".", i.span).into()); + } + devour_whitespace(toks); + let iter_val = Value::from_vec(read_until_open_curly_brace(toks), scope, super_selector)?; + let iter = match iter_val.node.eval(iter_val.span)?.node { + Value::List(v, ..) => v, + Value::Map(m) => m + .into_iter() + .map(|(k, v)| Value::List(vec![k, v], ListSeparator::Space, Brackets::None)) + .collect(), + v => vec![v], + }; + toks.next(); + devour_whitespace(toks); + let mut body = read_until_closing_curly_brace(toks); + body.push(toks.next().unwrap()); + devour_whitespace(toks); + + Ok(AtRule::Each(Each { vars, iter, body })) +} diff --git a/src/atrule/for_rule.rs b/src/atrule/for_rule.rs index 032b8b4..f9a69aa 100644 --- a/src/atrule/for_rule.rs +++ b/src/atrule/for_rule.rs @@ -6,7 +6,7 @@ use peekmore::{PeekMore, PeekMoreIterator}; use num_traits::cast::ToPrimitive; -use super::parse::eat_stmts; +use super::parse::ruleset_eval; use super::AtRule; use crate::error::SassResult; @@ -42,27 +42,13 @@ impl For { span: self.var.span, }, )?; - for stmt in eat_stmts( + ruleset_eval( &mut self.body.clone().into_iter().peekmore(), scope, super_selector, false, - )? { - match stmt.node { - Stmt::AtRule(AtRule::For(f)) => { - stmts.extend(f.ruleset_eval(scope, super_selector)?) - } - Stmt::AtRule(AtRule::While(w)) => { - // TODO: should at_root be false? scoping - stmts.extend(w.ruleset_eval(scope, super_selector, false)?) - } - Stmt::AtRule(AtRule::Include(s)) | Stmt::AtRule(AtRule::Each(s)) => { - stmts.extend(s) - } - Stmt::AtRule(AtRule::If(i)) => stmts.extend(i.eval(scope, super_selector)?), - _ => stmts.push(stmt), - } - } + &mut stmts, + )?; } Ok(stmts) } diff --git a/src/atrule/function.rs b/src/atrule/function.rs index 7aaad71..21543ba 100644 --- a/src/atrule/function.rs +++ b/src/atrule/function.rs @@ -178,6 +178,7 @@ impl Function { val = Value::from_vec(w.cond.clone(), scope, super_selector)?; } } + Stmt::AtRule(AtRule::Each(..)) => todo!("@each in @function"), _ => return Err(("This at-rule is not allowed here.", stmt.span).into()), } } diff --git a/src/atrule/if_rule.rs b/src/atrule/if_rule.rs index c63ea12..92445f6 100644 --- a/src/atrule/if_rule.rs +++ b/src/atrule/if_rule.rs @@ -2,7 +2,7 @@ use codemap::Spanned; use peekmore::{PeekMore, PeekMoreIterator}; -use super::{eat_stmts, AtRule}; +use super::ruleset_eval; use crate::error::SassResult; use crate::scope::Scope; @@ -120,18 +120,13 @@ impl If { if !found_true { toks = self.else_; } - for stmt in eat_stmts( + ruleset_eval( &mut toks.into_iter().peekmore(), scope, super_selector, false, - )? { - match stmt.node { - Stmt::AtRule(AtRule::If(i)) => stmts.extend(i.eval(scope, super_selector)?), - Stmt::RuleSet(r) if r.selector.is_empty() => stmts.extend(r.rules), - _ => stmts.push(stmt), - } - } + &mut stmts, + )?; Ok(stmts) } } diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index 13fe8cd..8c5bf0e 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -126,10 +126,13 @@ impl Mixin { AtRule::For(f) => { stmts.extend(f.ruleset_eval(&mut self.scope, super_selector)?) } + AtRule::Each(e) => { + stmts.extend(e.ruleset_eval(&mut self.scope, super_selector)?) + } AtRule::While(w) => { stmts.extend(w.ruleset_eval(&mut self.scope, super_selector, false)?) } - AtRule::Include(s) | AtRule::Each(s) => stmts.extend(s), + AtRule::Include(s) => stmts.extend(s), AtRule::If(i) => stmts.extend(i.eval(&mut self.scope.clone(), super_selector)?), AtRule::Content => stmts.extend(self.content.clone()), AtRule::Return(..) => { diff --git a/src/atrule/mod.rs b/src/atrule/mod.rs index 8ec86ba..e7a8547 100644 --- a/src/atrule/mod.rs +++ b/src/atrule/mod.rs @@ -2,26 +2,27 @@ use codemap::{Span, Spanned}; use peekmore::{PeekMore, PeekMoreIterator}; -use crate::common::{Brackets, ListSeparator}; use crate::error::SassResult; use crate::scope::Scope; use crate::selector::Selector; use crate::utils::{ - devour_whitespace, eat_ident, read_until_closing_curly_brace, read_until_open_curly_brace, + devour_whitespace, read_until_closing_curly_brace, read_until_open_curly_brace, read_until_semicolon_or_closing_curly_brace, }; use crate::value::Value; use crate::{RuleSet, Stmt, Token}; +use each_rule::{parse_each, Each}; use for_rule::For; pub(crate) use function::Function; pub(crate) use if_rule::If; pub(crate) use kind::AtRuleKind; pub(crate) use mixin::{eat_include, Mixin}; -use parse::{eat_stmts, eat_stmts_at_root}; +use parse::{eat_stmts, eat_stmts_at_root, ruleset_eval}; use unknown::UnknownAtRule; use while_rule::{parse_while, While}; +mod each_rule; mod for_rule; mod function; mod if_rule; @@ -33,8 +34,8 @@ mod while_rule; #[derive(Debug, Clone)] pub(crate) enum AtRule { - Warn(String), - Debug(String), + Warn(Spanned), + Debug(Spanned), Mixin(String, Box), Function(String, Box), Return(Vec), @@ -42,7 +43,7 @@ pub(crate) enum AtRule { Content, Unknown(UnknownAtRule), For(For), - Each(Vec>), + Each(Each), While(While), Include(Vec>), If(If), @@ -86,7 +87,10 @@ impl AtRule { } devour_whitespace(toks); Spanned { - node: AtRule::Warn(message.to_css_string(span)?), + node: AtRule::Warn(Spanned { + node: message.to_css_string(span)?, + span, + }), span, } } @@ -105,7 +109,10 @@ impl AtRule { } devour_whitespace(toks); Spanned { - node: AtRule::Debug(message.inspect(span)?), + node: AtRule::Debug(Spanned { + node: message.inspect(span)?, + span, + }), span, } } @@ -199,101 +206,10 @@ impl AtRule { span: kind_span, } } - AtRuleKind::Each => { - let mut stmts = Vec::new(); - devour_whitespace(toks); - let mut vars = Vec::new(); - let mut span = kind_span; - loop { - let next = toks.next().ok_or(("expected \"$\".", span))?; - span = next.pos(); - match next.kind { - '$' => vars.push(eat_ident(toks, scope, super_selector)?), - _ => return Err(("expected \"$\".", next.pos()).into()), - } - devour_whitespace(toks); - if toks - .peek() - .ok_or(("expected \"$\".", vars[vars.len() - 1].span))? - .kind - == ',' - { - toks.next(); - devour_whitespace(toks); - } else { - break; - } - } - if toks.peek().is_none() { - todo!() - } - let i = eat_ident(toks, scope, super_selector)?; - if i.node.to_ascii_lowercase() != "in" { - return Err(("Expected \"in\".", i.span).into()); - } - devour_whitespace(toks); - let iter_val = - Value::from_vec(read_until_open_curly_brace(toks), scope, super_selector)?; - let iterator = match iter_val.node.eval(iter_val.span)?.node { - Value::List(v, ..) => v, - Value::Map(m) => m - .into_iter() - .map(|(k, v)| Value::List(vec![k, v], ListSeparator::Space, Brackets::None)) - .collect(), - v => vec![v], - }; - toks.next(); - devour_whitespace(toks); - let mut body = read_until_closing_curly_brace(toks); - body.push(toks.next().unwrap()); - devour_whitespace(toks); - - for row in iterator { - let this_iterator = match row { - Value::List(v, ..) => v, - Value::Map(m) => m - .into_iter() - .map(|(k, v)| { - Value::List(vec![k, v], ListSeparator::Space, Brackets::None) - }) - .collect(), - v => vec![v], - }; - - if vars.len() == 1 { - scope.insert_var( - &vars[0], - Spanned { - node: Value::List( - this_iterator, - ListSeparator::Space, - Brackets::None, - ), - span: vars[0].span, - }, - )?; - } else { - for (var, val) in vars.clone().into_iter().zip( - this_iterator - .into_iter() - .chain(std::iter::once(Value::Null).cycle()), - ) { - scope.insert_var(&var, Spanned { node: val, span })?; - } - } - - stmts.extend(eat_stmts( - &mut body.clone().into_iter().peekmore(), - scope, - super_selector, - false, - )?); - } - Spanned { - node: AtRule::Each(stmts), - span: kind_span, - } - } + AtRuleKind::Each => Spanned { + node: parse_each(toks, scope, super_selector, kind_span)?, + span: kind_span, + }, AtRuleKind::Extend => todo!("@extend not yet implemented"), AtRuleKind::If => Spanned { node: AtRule::If(If::from_tokens(toks)?), diff --git a/src/atrule/parse.rs b/src/atrule/parse.rs index de97519..c51db80 100644 --- a/src/atrule/parse.rs +++ b/src/atrule/parse.rs @@ -2,6 +2,8 @@ use codemap::Spanned; use peekmore::PeekMoreIterator; +use super::AtRule; + use crate::error::SassResult; use crate::scope::{global_var_exists, insert_global_var, Scope}; use crate::selector::Selector; @@ -100,3 +102,26 @@ pub(crate) fn eat_stmts_at_root>( } Ok(stmts) } + +pub(crate) fn ruleset_eval>( + toks: &mut PeekMoreIterator, + scope: &mut Scope, + super_selector: &Selector, + at_root: bool, + stmts: &mut Vec>, +) -> SassResult<()> { + for stmt in eat_stmts(toks, scope, super_selector, at_root)? { + match stmt.node { + Stmt::AtRule(AtRule::For(f)) => stmts.extend(f.ruleset_eval(scope, super_selector)?), + Stmt::AtRule(AtRule::Each(e)) => stmts.extend(e.ruleset_eval(scope, super_selector)?), + Stmt::AtRule(AtRule::While(w)) => { + // TODO: should at_root be false? scoping + stmts.extend(w.ruleset_eval(scope, super_selector, at_root)?) + } + Stmt::AtRule(AtRule::Include(s)) => stmts.extend(s), + Stmt::AtRule(AtRule::If(i)) => stmts.extend(i.eval(scope, super_selector)?), + _ => stmts.push(stmt), + } + } + Ok(()) +} diff --git a/src/atrule/while_rule.rs b/src/atrule/while_rule.rs index 9e64e0d..104bfc0 100644 --- a/src/atrule/while_rule.rs +++ b/src/atrule/while_rule.rs @@ -2,7 +2,7 @@ use codemap::{Span, Spanned}; use peekmore::{PeekMore, PeekMoreIterator}; -use super::{eat_stmts, AtRule}; +use super::{ruleset_eval, AtRule}; use crate::error::SassResult; use crate::scope::Scope; @@ -30,26 +30,13 @@ impl While { let mut val = Value::from_vec(self.cond.clone(), scope, super_selector)?; let scope = &mut scope.clone(); while val.node.is_true(val.span)? { - for stmt in eat_stmts( + ruleset_eval( &mut self.body.clone().into_iter().peekmore(), scope, super_selector, at_root, - )? { - match stmt.node { - Stmt::AtRule(AtRule::For(f)) => { - stmts.extend(f.ruleset_eval(scope, super_selector)?) - } - Stmt::AtRule(AtRule::While(w)) => { - stmts.extend(w.ruleset_eval(scope, super_selector, at_root)?) - } - Stmt::AtRule(AtRule::Include(s)) | Stmt::AtRule(AtRule::Each(s)) => { - stmts.extend(s) - } - Stmt::AtRule(AtRule::If(i)) => stmts.extend(i.eval(scope, super_selector)?), - _ => stmts.push(stmt), - } - } + &mut stmts, + )?; val = Value::from_vec(self.cond.clone(), scope, super_selector)?; } Ok(stmts) diff --git a/src/error.rs b/src/error.rs index 4b65970..840c753 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,7 +16,7 @@ impl SassError { pub(crate) fn raw(self) -> (String, Span) { match self.kind { SassErrorKind::Raw(string, span) => (string, span), - _ => todo!(), + e => todo!("unable to get raw of {:?}", e), } } diff --git a/src/imports.rs b/src/imports.rs index c103639..2b87429 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -1,13 +1,17 @@ use std::ffi::OsStr; use std::path::Path; -use codemap::Spanned; +use codemap::{CodeMap, Spanned}; use crate::error::SassResult; use crate::scope::Scope; use crate::{Stmt, StyleSheet}; -pub(crate) fn import(ctx: &Path, path: &Path) -> SassResult<(Vec>, Scope)> { +pub(crate) fn import( + ctx: &Path, + path: &Path, + map: &mut CodeMap, +) -> SassResult<(Vec>, Scope)> { let mut rules = Vec::new(); let mut scope = Scope::new(); if path.is_absolute() { @@ -39,7 +43,7 @@ pub(crate) fn import(ctx: &Path, path: &Path) -> SassResult<(Vec>, for name in &paths { if name.is_file() { let (rules2, scope2) = - StyleSheet::export_from_path(&name.to_str().expect("path should be UTF-8"))?; + StyleSheet::export_from_path(&name.to_str().expect("path should be UTF-8"), map)?; rules.extend(rules2); scope.extend(scope2); } diff --git a/src/lib.rs b/src/lib.rs index cbfc4ae..1e4ab4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -237,7 +237,7 @@ impl StyleSheet { StyleSheetParser { lexer: Lexer::new(&file).peekmore(), nesting: 0, - map: &map, + map: &mut map, path: Path::new(""), } .parse_toplevel() @@ -245,7 +245,7 @@ impl StyleSheet { .0, )) .map_err(|e| raw_to_parse_error(&map, e))? - .pretty_print() + .pretty_print(&map) .map_err(|e| raw_to_parse_error(&map, e)) } @@ -268,7 +268,7 @@ impl StyleSheet { StyleSheetParser { lexer: Lexer::new(&file).peekmore(), nesting: 0, - map: &map, + map: &mut map, path: p.as_ref(), } .parse_toplevel() @@ -276,19 +276,19 @@ impl StyleSheet { .0, )) .map_err(|e| raw_to_parse_error(&map, e))? - .pretty_print() + .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 mut map = CodeMap::new(); let file = map.add_file(p.clone().into(), String::from_utf8(fs::read(p)?)?); Ok(StyleSheetParser { lexer: Lexer::new(&file).peekmore(), nesting: 0, - map: &map, + map, path: p.as_ref(), } .parse_toplevel()?) @@ -302,7 +302,7 @@ impl StyleSheet { struct StyleSheetParser<'a> { lexer: PeekMoreIterator>, nesting: u32, - map: &'a CodeMap, + map: &'a mut CodeMap, path: &'a Path, } @@ -387,7 +387,7 @@ impl<'a> StyleSheetParser<'a> { devour_whitespace(&mut self.lexer); - let (new_rules, new_scope) = import(self.path, file_name.as_ref())?; + 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); @@ -412,8 +412,10 @@ impl<'a> StyleSheetParser<'a> { } AtRule::For(f) => rules.extend(f.ruleset_eval(&mut Scope::new(), &Selector::new())?), AtRule::While(w) => rules.extend(w.ruleset_eval(&mut Scope::new(), &Selector::new(), true)?), - AtRule::Include(s) - | AtRule::Each(s) => rules.extend(s), + AtRule::Each(e) => { + rules.extend(e.ruleset_eval(&mut Scope::new(), &Selector::new())?) + } + AtRule::Include(s) => rules.extend(s), AtRule::Content => return Err( ("@content is only allowed within mixin declarations.", rule.span ).into()), @@ -461,7 +463,8 @@ impl<'a> StyleSheetParser<'a> { AtRule::While(w) => { stmts.extend(w.ruleset_eval(scope, super_selector, false)?) } - AtRule::Include(s) | AtRule::Each(s) => stmts.extend(s), + AtRule::Each(e) => stmts.extend(e.ruleset_eval(scope, super_selector)?), + AtRule::Include(s) => stmts.extend(s), AtRule::If(i) => stmts.extend(i.eval(scope, super_selector)?), AtRule::Content => { return Err(( diff --git a/src/output.rs b/src/output.rs index f4f25be..a86528a 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,6 +1,8 @@ //! # Convert from SCSS AST to CSS use std::io::Write; +use codemap::{CodeMap, Span}; + use crate::atrule::AtRule; use crate::error::SassResult; use crate::{RuleSet, Selector, Stmt, Style, StyleSheet}; @@ -123,9 +125,9 @@ impl Css { Ok(self) } - pub fn pretty_print(self) -> SassResult { + pub fn pretty_print(self, map: &CodeMap) -> SassResult { let mut string = Vec::new(); - self._inner_pretty_print(&mut string, 0)?; + self._inner_pretty_print(&mut string, map, 0)?; if string.iter().any(|s| !s.is_ascii()) { return Ok(format!( "@charset \"UTF-8\";\n{}", @@ -135,7 +137,33 @@ impl Css { Ok(String::from_utf8(string)?) } - fn _inner_pretty_print(self, buf: &mut Vec, nesting: usize) -> SassResult<()> { + fn debug(map: &CodeMap, span: Span, message: &str) { + let loc = map.look_up_span(span); + eprintln!( + "{}:{} Debug: {}", + loc.file.name(), + loc.begin.line + 1, + message + ); + } + + fn warn(map: &CodeMap, span: Span, message: &str) { + let loc = map.look_up_span(span); + eprintln!( + "Warning: {}\n {} {}:{} root stylesheet", + message, + loc.file.name(), + loc.begin.line + 1, + loc.begin.column + 1 + ); + } + + fn _inner_pretty_print( + self, + buf: &mut Vec, + map: &CodeMap, + nesting: usize, + ) -> SassResult<()> { let mut has_written = false; let padding = vec![' '; nesting * 2].iter().collect::(); for block in self.blocks { @@ -155,22 +183,26 @@ impl Css { has_written = true; writeln!(buf, "{}/*{}*/", padding, s)?; } - Toplevel::AtRule(r) => match r { - AtRule::Unknown(u) => { - if u.body.is_empty() { - continue; + Toplevel::AtRule(r) => { + match r { + AtRule::Unknown(u) => { + if u.body.is_empty() { + continue; + } + if u.params.is_empty() { + writeln!(buf, "{}@{} {{", padding, u.name)?; + } else { + writeln!(buf, "{}@{} {} {{", padding, u.name, u.params)?; + } + Css::from_stylesheet(StyleSheet::from_stmts(u.body))? + ._inner_pretty_print(buf, map, nesting + 1)?; + writeln!(buf, "{}}}", padding)?; } - if u.params.is_empty() { - writeln!(buf, "{}@{} {{", padding, u.name)?; - } else { - writeln!(buf, "{}@{} {} {{", padding, u.name, u.params)?; - } - Css::from_stylesheet(StyleSheet::from_stmts(u.body))? - ._inner_pretty_print(buf, nesting + 1)?; - writeln!(buf, "{}}}", padding)?; + AtRule::Debug(e) => Self::debug(map, e.span, &e.node), + AtRule::Warn(e) => Self::warn(map, e.span, &e.node), + _ => todo!("at-rule other than unknown at toplevel: {:?}", r), } - _ => todo!("at-rule other than unknown at toplevel: {:?}", r), - }, + } Toplevel::Style(s) => { writeln!(buf, "{}{}", padding, s.to_string()?)?; }