From 1b79127cc532678abd5571335e987cfc4eddf5aa Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sat, 29 Feb 2020 11:45:36 -0500 Subject: [PATCH] Initial implementation of @for --- src/atrule.rs | 103 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/common.rs | 16 +++++++- src/lib.rs | 2 + 3 files changed, 116 insertions(+), 5 deletions(-) diff --git a/src/atrule.rs b/src/atrule.rs index d75e6a9..64a4ce8 100644 --- a/src/atrule.rs +++ b/src/atrule.rs @@ -1,12 +1,17 @@ +use std::cmp::Ordering; use std::fmt::{self, Display}; use std::iter::Peekable; -use crate::common::{Pos, Scope, Symbol}; +use num_traits::cast::ToPrimitive; + +use crate::common::{Keyword, Pos, Scope, Symbol}; use crate::error::SassResult; use crate::function::Function; use crate::mixin::Mixin; use crate::selector::Selector; -use crate::utils::{devour_whitespace, parse_interpolation}; +use crate::units::Unit; +use crate::utils::{devour_whitespace, devour_whitespace_or_comment, parse_interpolation}; +use crate::value::{Number, Value}; use crate::{eat_expr, Expr, Stmt}; use crate::{RuleSet, Token, TokenKind}; @@ -20,6 +25,7 @@ pub(crate) enum AtRule { Return(Vec), Charset, Unknown(UnknownAtRule), + For(Vec), } #[derive(Debug, Clone)] @@ -102,7 +108,98 @@ impl AtRule { AtRuleKind::Extend => todo!("@extend not yet implemented"), AtRuleKind::If => todo!("@if not yet implemented"), AtRuleKind::Else => todo!("@else not yet implemented"), - AtRuleKind::For => todo!("@for not yet implemented"), + AtRuleKind::For => { + let mut stmts = Vec::new(); + devour_whitespace_or_comment(toks); + let var = if let Some(tok) = toks.next() { + match tok.kind { + TokenKind::Variable(s) => s, + _ => return Err("expected \"$\".".into()), + } + } else { + return Err("expected \"$\".".into()); + }; + devour_whitespace_or_comment(toks); + if let Some(tok) = toks.next() { + match tok.kind { + TokenKind::Keyword(Keyword::From) => {} + _ => return Err("Expected \"from\".".into()), + } + } else { + return Err("Expected \"from\".".into()); + }; + devour_whitespace_or_comment(toks); + let mut from_toks = Vec::new(); + let mut through = 0; + while let Some(tok) = toks.next() { + match tok.kind { + TokenKind::Keyword(Keyword::Through) => { + through = 1; + break; + } + TokenKind::Keyword(Keyword::To) => break, + _ => from_toks.push(tok), + } + } + let from = match Value::from_tokens(&mut from_toks.into_iter().peekable(), scope)? { + Value::Dimension(n, _) => match n.to_integer().to_usize() { + Some(v) => v, + None => todo!(), + }, + v => return Err(format!("{} is not a number.", v).into()), + }; + devour_whitespace_or_comment(toks); + let mut to_toks = Vec::new(); + while let Some(tok) = toks.next() { + match tok.kind { + TokenKind::Symbol(Symbol::OpenCurlyBrace) => break, + _ => to_toks.push(tok), + } + } + let to = match Value::from_tokens(&mut to_toks.into_iter().peekable(), scope)? { + Value::Dimension(n, _) => match n.to_integer().to_usize() { + Some(v) => v, + None => todo!(), + }, + v => return Err(format!("{} is not a number.", v).into()), + }; + let mut body = Vec::new(); + let mut n = 1; + for tok in toks { + match tok.kind { + TokenKind::Symbol(Symbol::OpenCurlyBrace) => n += 1, + TokenKind::Symbol(Symbol::CloseCurlyBrace) => n -= 1, + TokenKind::Interpolation => n += 1, + _ => {} + } + body.push(tok); + if n == 0 { + break; + } + } + + let mut scope = scope.clone(); + if from < to { + for i in from..(to + through) { + scope.insert_var(&var, Value::Dimension(Number::from(i), Unit::None)); + stmts.extend(eat_unknown_atrule_body( + &mut body.clone().into_iter().peekable(), + &scope, + super_selector, + )?); + } + } else if from > to { + for i in ((to - through)..(from + 1)).skip(1).rev() { + scope.insert_var(&var, Value::Dimension(Number::from(i), Unit::None)); + stmts.extend(eat_unknown_atrule_body( + &mut body.clone().into_iter().peekable(), + &scope, + super_selector, + )?); + } + } + AtRule::For(stmts) + } AtRuleKind::While => todo!("@while not yet implemented"), AtRuleKind::Keyframes => todo!("@keyframes not yet implemented"), AtRuleKind::Unknown(name) => { diff --git a/src/common.rs b/src/common.rs index 1c9e41f..4239711 100644 --- a/src/common.rs +++ b/src/common.rs @@ -220,6 +220,9 @@ pub enum Keyword { False, Null, Default, + From, + To, + Through, // Infinity, // NaN, // Auto, @@ -240,6 +243,10 @@ impl Display for Keyword { Self::False => write!(f, "false"), Self::Null => write!(f, "null"), Self::Default => write!(f, "!default"), + // todo!(maintain casing for keywords) + Self::From => write!(f, "from"), + Self::To => write!(f, "to"), + Self::Through => write!(f, "through"), // Self::Infinity => write!(f, "Infinity"), // Self::NaN => write!(f, "NaN"), // Self::Auto => write!(f, "auto"), @@ -262,6 +269,9 @@ impl Into<&'static str> for Keyword { Self::False => "false", Self::Null => "null", Self::Default => "!default", + Self::From => "from", + Self::To => "to", + Self::Through => "through", // Self::Infinity => "Infinity", // Self::NaN => "NaN", // Self::Auto => "auto", @@ -280,13 +290,15 @@ impl TryFrom<&str> for Keyword { type Error = &'static str; fn try_from(kw: &str) -> Result { - // todo: case insensitive? - match kw { + match kw.to_ascii_lowercase().as_str() { "important" => Ok(Self::Important), "true" => Ok(Self::True), "false" => Ok(Self::False), "null" => Ok(Self::Null), "default" => Ok(Self::Default), + "from" => Ok(Self::From), + "to" => Ok(Self::To), + "through" => Ok(Self::Through), // "infinity" => Ok(Self::Infinity), // "nan" => Ok(Self::NaN), // "auto" => Ok(Self::Auto), diff --git a/src/lib.rs b/src/lib.rs index 5cc582e..78cd870 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -474,6 +474,7 @@ impl<'a> StyleSheetParser<'a> { AtRule::Return(_) => { return Err("This at-rule is not allowed here.".into()) } + AtRule::For(s) => rules.extend(s), u @ AtRule::Unknown(..) => rules.push(Stmt::AtRule(u)), } } @@ -652,6 +653,7 @@ pub(crate) fn eat_expr>( AtRule::Warn(a, b) => Ok(Some(Expr::Warn(a, b))), AtRule::Error(pos, err) => Err(SassError::new(err, pos)), AtRule::Return(_) => todo!("@return in unexpected location!"), + f @ AtRule::For(..) => Ok(Some(Expr::AtRule(f))), u @ AtRule::Unknown(..) => Ok(Some(Expr::AtRule(u))), }; }