Initial implementation of @for

This commit is contained in:
ConnorSkees 2020-02-29 11:45:36 -05:00
parent e2fcfeec47
commit 1b79127cc5
3 changed files with 116 additions and 5 deletions

View File

@ -1,12 +1,17 @@
use std::cmp::Ordering;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::iter::Peekable; 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::error::SassResult;
use crate::function::Function; use crate::function::Function;
use crate::mixin::Mixin; use crate::mixin::Mixin;
use crate::selector::Selector; 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::{eat_expr, Expr, Stmt};
use crate::{RuleSet, Token, TokenKind}; use crate::{RuleSet, Token, TokenKind};
@ -20,6 +25,7 @@ pub(crate) enum AtRule {
Return(Vec<Token>), Return(Vec<Token>),
Charset, Charset,
Unknown(UnknownAtRule), Unknown(UnknownAtRule),
For(Vec<Stmt>),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -102,7 +108,98 @@ impl AtRule {
AtRuleKind::Extend => todo!("@extend not yet implemented"), AtRuleKind::Extend => todo!("@extend not yet implemented"),
AtRuleKind::If => todo!("@if not yet implemented"), AtRuleKind::If => todo!("@if not yet implemented"),
AtRuleKind::Else => todo!("@else 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::While => todo!("@while not yet implemented"),
AtRuleKind::Keyframes => todo!("@keyframes not yet implemented"), AtRuleKind::Keyframes => todo!("@keyframes not yet implemented"),
AtRuleKind::Unknown(name) => { AtRuleKind::Unknown(name) => {

View File

@ -220,6 +220,9 @@ pub enum Keyword {
False, False,
Null, Null,
Default, Default,
From,
To,
Through,
// Infinity, // Infinity,
// NaN, // NaN,
// Auto, // Auto,
@ -240,6 +243,10 @@ impl Display for Keyword {
Self::False => write!(f, "false"), Self::False => write!(f, "false"),
Self::Null => write!(f, "null"), Self::Null => write!(f, "null"),
Self::Default => write!(f, "!default"), 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::Infinity => write!(f, "Infinity"),
// Self::NaN => write!(f, "NaN"), // Self::NaN => write!(f, "NaN"),
// Self::Auto => write!(f, "auto"), // Self::Auto => write!(f, "auto"),
@ -262,6 +269,9 @@ impl Into<&'static str> for Keyword {
Self::False => "false", Self::False => "false",
Self::Null => "null", Self::Null => "null",
Self::Default => "!default", Self::Default => "!default",
Self::From => "from",
Self::To => "to",
Self::Through => "through",
// Self::Infinity => "Infinity", // Self::Infinity => "Infinity",
// Self::NaN => "NaN", // Self::NaN => "NaN",
// Self::Auto => "auto", // Self::Auto => "auto",
@ -280,13 +290,15 @@ impl TryFrom<&str> for Keyword {
type Error = &'static str; type Error = &'static str;
fn try_from(kw: &str) -> Result<Self, Self::Error> { fn try_from(kw: &str) -> Result<Self, Self::Error> {
// todo: case insensitive? match kw.to_ascii_lowercase().as_str() {
match kw {
"important" => Ok(Self::Important), "important" => Ok(Self::Important),
"true" => Ok(Self::True), "true" => Ok(Self::True),
"false" => Ok(Self::False), "false" => Ok(Self::False),
"null" => Ok(Self::Null), "null" => Ok(Self::Null),
"default" => Ok(Self::Default), "default" => Ok(Self::Default),
"from" => Ok(Self::From),
"to" => Ok(Self::To),
"through" => Ok(Self::Through),
// "infinity" => Ok(Self::Infinity), // "infinity" => Ok(Self::Infinity),
// "nan" => Ok(Self::NaN), // "nan" => Ok(Self::NaN),
// "auto" => Ok(Self::Auto), // "auto" => Ok(Self::Auto),

View File

@ -474,6 +474,7 @@ impl<'a> StyleSheetParser<'a> {
AtRule::Return(_) => { AtRule::Return(_) => {
return Err("This at-rule is not allowed here.".into()) return Err("This at-rule is not allowed here.".into())
} }
AtRule::For(s) => rules.extend(s),
u @ AtRule::Unknown(..) => rules.push(Stmt::AtRule(u)), u @ AtRule::Unknown(..) => rules.push(Stmt::AtRule(u)),
} }
} }
@ -652,6 +653,7 @@ pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
AtRule::Warn(a, b) => Ok(Some(Expr::Warn(a, b))), AtRule::Warn(a, b) => Ok(Some(Expr::Warn(a, b))),
AtRule::Error(pos, err) => Err(SassError::new(err, pos)), AtRule::Error(pos, err) => Err(SassError::new(err, pos)),
AtRule::Return(_) => todo!("@return in unexpected location!"), AtRule::Return(_) => todo!("@return in unexpected location!"),
f @ AtRule::For(..) => Ok(Some(Expr::AtRule(f))),
u @ AtRule::Unknown(..) => Ok(Some(Expr::AtRule(u))), u @ AtRule::Unknown(..) => Ok(Some(Expr::AtRule(u))),
}; };
} }