diff --git a/src/atrule/for_rule.rs b/src/atrule/for_rule.rs index 0a11b2b..7e1a01b 100644 --- a/src/atrule/for_rule.rs +++ b/src/atrule/for_rule.rs @@ -1,3 +1,5 @@ +use std::iter::Iterator; + use codemap::{Span, Spanned}; use peekmore::{PeekMore, PeekMoreIterator}; @@ -15,7 +17,44 @@ use crate::utils::{ devour_whitespace, eat_ident, read_until_closing_curly_brace, read_until_open_curly_brace, }; use crate::value::{Number, Value}; -use crate::Token; +use crate::{Stmt, Token}; + +#[derive(Debug, Clone)] +pub(crate) struct For { + pub var: Spanned, + // TODO: optimization: this could be a generic or &dyn Iterator maybe? + pub iter: Vec, + pub body: Vec, +} + +impl For { + pub fn ruleset_eval( + self, + scope: &mut Scope, + super_selector: &Selector, + ) -> SassResult>> { + let mut stmts = Vec::new(); + for i in self.iter { + scope.insert_var( + &self.var.node, + Spanned { + node: Value::Dimension(Number::from(i), Unit::None), + span: self.var.span, + }, + )?; + stmts.extend(eat_stmts( + &mut self.body.clone().into_iter().peekmore(), + scope, + super_selector, + )?); + } + Ok(stmts) + } + + pub fn iter(&self) -> std::slice::Iter<'_, usize> { + self.iter.iter() + } +} pub(crate) fn parse_for>( toks: &mut PeekMoreIterator, @@ -23,7 +62,6 @@ pub(crate) fn parse_for>( super_selector: &Selector, span: Span, ) -> SassResult { - let mut stmts = Vec::new(); devour_whitespace(toks); let var = match toks.next().ok_or(("expected \"$\".", span))?.kind { '$' => eat_ident(toks, scope, super_selector)?, @@ -137,29 +175,11 @@ pub(crate) fn parse_for>( devour_whitespace(toks); - let (mut x, mut y); - let iter: &mut dyn std::iter::Iterator = if from < to { - x = from..(to + through); - &mut x + let iter = if from < to { + (from..(to + through)).collect() } else { - y = ((to - through)..(from + 1)).skip(1).rev(); - &mut y + ((to - through)..(from + 1)).skip(1).rev().collect() }; - for i in iter { - scope.insert_var( - &var, - Spanned { - node: Value::Dimension(Number::from(i), Unit::None), - span: var.span, - }, - )?; - stmts.extend(eat_stmts( - &mut body.clone().into_iter().peekmore(), - scope, - super_selector, - )?); - } - - Ok(AtRule::For(stmts)) + Ok(AtRule::For(For { iter, body, var })) } diff --git a/src/atrule/function.rs b/src/atrule/function.rs index b56032f..7fb0998 100644 --- a/src/atrule/function.rs +++ b/src/atrule/function.rs @@ -9,8 +9,9 @@ use crate::atrule::AtRule; use crate::error::SassResult; use crate::scope::Scope; use crate::selector::Selector; +use crate::unit::Unit; use crate::utils::{devour_whitespace, eat_ident, read_until_closing_curly_brace}; -use crate::value::Value; +use crate::value::{Number, Value}; use crate::{Stmt, Token}; #[derive(Debug, Clone)] @@ -118,32 +119,50 @@ impl Function { ) -> SassResult { self.args(args, scope, super_selector)?; let stmts = self.eval_body(super_selector)?; - self.call(super_selector, stmts) + self.call(super_selector, stmts)? + .ok_or(("Function finished without @return.", self.pos).into()) } - pub fn call(&self, super_selector: &Selector, stmts: Vec>) -> SassResult { + pub fn call( + &mut self, + super_selector: &Selector, + stmts: Vec>, + ) -> SassResult> { for stmt in stmts { match stmt.node { Stmt::AtRule(AtRule::Return(toks)) => { - return Ok(Value::from_tokens( - &mut toks.into_iter().peekmore(), - &self.scope, - super_selector, - )? - .node) + return Ok(Some( + Value::from_vec(toks, &self.scope, super_selector)?.node, + )); } - Stmt::AtRule(AtRule::For(..)) => todo!("@for in function"), - Stmt::AtRule(AtRule::If(i)) => { - if let Ok(v) = self.call( + Stmt::AtRule(AtRule::For(f)) => { + for i in f.iter().cloned() { + self.scope.insert_var( + &f.var.node, + Spanned { + node: Value::Dimension(Number::from(i), Unit::None), + span: f.var.span, + }, + )?; + } + let for_stmts = eat_stmts( + &mut f.body.clone().into_iter().peekmore(), + &mut self.scope, super_selector, - i.eval(&mut self.scope.clone(), super_selector)?, - ) { - return Ok(v); + )?; + if let Some(v) = self.call(super_selector, for_stmts)? { + return Ok(Some(v)); + } + } + Stmt::AtRule(AtRule::If(i)) => { + let if_stmts = i.eval(&mut self.scope, super_selector)?; + if let Some(v) = self.call(super_selector, if_stmts)? { + return Ok(Some(v)); } } _ => return Err(("This at-rule is not allowed here.", stmt.span).into()), } } - Err(("Function finished without @return.", self.pos).into()) + Ok(None) } } diff --git a/src/atrule/mixin.rs b/src/atrule/mixin.rs index 346e652..d44580e 100644 --- a/src/atrule/mixin.rs +++ b/src/atrule/mixin.rs @@ -123,9 +123,10 @@ impl Mixin { let span = expr.span; match expr.node { Expr::AtRule(a) => match a { - AtRule::Include(s) | AtRule::While(s) | AtRule::Each(s) | AtRule::For(s) => { - stmts.extend(s) + AtRule::For(f) => { + stmts.extend(f.ruleset_eval(&mut self.scope, super_selector)?) } + AtRule::Include(s) | AtRule::While(s) | AtRule::Each(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(..) => { @@ -193,7 +194,7 @@ pub(crate) fn eat_include>( match tok.kind { ';' => CallArgs::new(name.span), '(' => { - let tmp = eat_call_args(toks, scope, super_selector)?; + let tmp = eat_call_args(toks)?; devour_whitespace_or_comment(toks)?; if let Some(tok) = toks.next() { match tok.kind { diff --git a/src/atrule/mod.rs b/src/atrule/mod.rs index 26d1732..59d615d 100644 --- a/src/atrule/mod.rs +++ b/src/atrule/mod.rs @@ -13,6 +13,7 @@ use crate::utils::{ use crate::value::Value; use crate::{RuleSet, Stmt, Token}; +use for_rule::For; pub(crate) use function::Function; pub(crate) use if_rule::If; pub(crate) use kind::AtRuleKind; @@ -38,7 +39,7 @@ pub(crate) enum AtRule { Charset, Content, Unknown(UnknownAtRule), - For(Vec>), + For(For), Each(Vec>), While(Vec>), Include(Vec>), diff --git a/src/lib.rs b/src/lib.rs index 15b032a..4cd9680 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -378,10 +378,10 @@ impl<'a> StyleSheetParser<'a> { ("This at-rule is not allowed here.", rule.span).into() ) } + AtRule::For(f) => rules.extend(f.ruleset_eval(&mut Scope::new(), &Selector::new())?), AtRule::Include(s) | AtRule::While(s) - | AtRule::Each(s) - | AtRule::For(s) => rules.extend(s), + | AtRule::Each(s) => rules.extend(s), AtRule::Content => return Err( ("@content is only allowed within mixin declarations.", rule.span ).into()), @@ -425,9 +425,8 @@ impl<'a> StyleSheetParser<'a> { span, }), Expr::AtRule(a) => match a { - AtRule::Include(s) | AtRule::While(s) | AtRule::Each(s) | AtRule::For(s) => { - stmts.extend(s) - } + AtRule::For(f) => stmts.extend(f.ruleset_eval(scope, super_selector)?), + AtRule::Include(s) | AtRule::While(s) | AtRule::Each(s) => stmts.extend(s), AtRule::If(i) => stmts.extend(i.eval(scope, super_selector)?), AtRule::Content => { return Err(( diff --git a/tests/for.rs b/tests/for.rs index f2ad251..3201dfa 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -43,3 +43,13 @@ test!( "a {\n $a: red;\n @for $i from 1 to 3 {\n $a: blue;\n }\n color: $a;\n}\n", "a {\n color: blue;\n}\n" ); +test!( + simple_return_in_function, + "@function foo() {\n @for $i from 1 to 2 {\n @return $i;\n }\n}\na {\n color: foo();\n}\n", + "a {\n color: 1;\n}\n" +); +test!( + return_gated_by_if_in_function, + "@function foo() {\n @for $i from 1 through 2 {\n @if $i==2 {\n @return $i;\n }\n }\n}\na {\n color: foo();\n}\n", + "a {\n color: 2;\n}\n" +);