refactor parsing and eval of @ each

This commit is contained in:
ConnorSkees 2020-04-24 22:57:39 -04:00
parent 092cbe75bb
commit f60089f4f9
12 changed files with 261 additions and 180 deletions

129
src/atrule/each_rule.rs Normal file
View File

@ -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<Spanned<String>>,
iter: Vec<Value>,
body: Vec<Token>,
}
impl Each {
pub fn ruleset_eval(
self,
scope: &mut Scope,
super_selector: &Selector,
) -> SassResult<Vec<Spanned<Stmt>>> {
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<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>,
scope: &mut Scope,
super_selector: &Selector,
mut span: Span,
) -> SassResult<AtRule> {
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 }))
}

View File

@ -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)
}

View File

@ -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()),
}
}

View File

@ -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)
}
}

View File

@ -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(..) => {

View File

@ -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<String>),
Debug(Spanned<String>),
Mixin(String, Box<Mixin>),
Function(String, Box<Function>),
Return(Vec<Token>),
@ -42,7 +43,7 @@ pub(crate) enum AtRule {
Content,
Unknown(UnknownAtRule),
For(For),
Each(Vec<Spanned<Stmt>>),
Each(Each),
While(While),
Include(Vec<Spanned<Stmt>>),
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)?),

View File

@ -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<I: Iterator<Item = Token>>(
}
Ok(stmts)
}
pub(crate) fn ruleset_eval<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>,
scope: &mut Scope,
super_selector: &Selector,
at_root: bool,
stmts: &mut Vec<Spanned<Stmt>>,
) -> 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(())
}

View File

@ -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)

View File

@ -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),
}
}

View File

@ -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<Spanned<Stmt>>, Scope)> {
pub(crate) fn import(
ctx: &Path,
path: &Path,
map: &mut CodeMap,
) -> SassResult<(Vec<Spanned<Stmt>>, 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<Spanned<Stmt>>,
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);
}

View File

@ -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<P: AsRef<Path> + Into<String> + Clone>(
p: &P,
map: &mut CodeMap,
) -> SassResult<(Vec<Spanned<Stmt>>, 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<Lexer<'a>>,
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((

View File

@ -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<String> {
pub fn pretty_print(self, map: &CodeMap) -> SassResult<String> {
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<u8>, 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<u8>,
map: &CodeMap,
nesting: usize,
) -> SassResult<()> {
let mut has_written = false;
let padding = vec![' '; nesting * 2].iter().collect::<String>();
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()?)?;
}