refactor @if/@else if/@else

This commit is contained in:
ConnorSkees 2020-03-24 22:13:38 -04:00
parent 6d2d9bcb4a
commit f15ab57573
7 changed files with 253 additions and 110 deletions

View File

@ -69,8 +69,12 @@ impl Function {
Ok(self)
}
pub fn call(&self, super_selector: &Selector) -> SassResult<Value> {
for stmt in &self.body {
pub fn body(&self) -> Vec<Stmt> {
self.body.clone()
}
pub fn call(&self, super_selector: &Selector, stmts: Vec<Stmt>) -> SassResult<Value> {
for stmt in stmts {
match stmt {
Stmt::AtRule(AtRule::Return(toks)) => {
return Value::from_tokens(
@ -80,9 +84,18 @@ impl Function {
)
}
Stmt::AtRule(AtRule::For(..)) => todo!("@for in function"),
Stmt::AtRule(AtRule::If(i)) => {
match self.call(
super_selector,
i.eval(&mut self.scope.clone(), super_selector)?,
) {
Ok(v) => return Ok(v),
Err(..) => {}
}
}
_ => return Err("This at-rule is not allowed here.".into()),
}
}
todo!()
Err("Function finished without @return.".into())
}
}

111
src/atrule/if_rule.rs Normal file
View File

@ -0,0 +1,111 @@
use std::iter::Peekable;
use super::{eat_stmts, AtRule, AtRuleKind};
use crate::common::Symbol;
use crate::error::SassResult;
use crate::scope::Scope;
use crate::selector::Selector;
use crate::utils::{
devour_whitespace_or_comment, read_until_closing_curly_brace, read_until_open_curly_brace,
};
use crate::value::Value;
use crate::{Stmt, Token, TokenKind};
#[derive(Debug, Clone)]
pub(crate) struct If {
pub branches: Vec<Branch>,
pub else_: Vec<Token>,
}
#[derive(Debug, Clone)]
pub(crate) struct Branch {
pub cond: Vec<Token>,
pub toks: Vec<Token>,
}
impl Branch {
pub fn new(cond: Vec<Token>, toks: Vec<Token>) -> Branch {
Branch { cond, toks }
}
}
impl If {
pub fn from_tokens<I: Iterator<Item = Token>>(toks: &mut Peekable<I>) -> SassResult<If> {
let mut branches = Vec::new();
let init_cond = read_until_open_curly_brace(toks);
toks.next();
devour_whitespace_or_comment(toks);
let mut init_toks = read_until_closing_curly_brace(toks);
init_toks.push(toks.next().unwrap());
devour_whitespace_or_comment(toks);
branches.push(Branch::new(init_cond, init_toks));
let mut else_ = Vec::new();
loop {
if let Some(tok) = toks.peek() {
if tok.kind == TokenKind::AtRule(AtRuleKind::Else) {
toks.next();
devour_whitespace_or_comment(toks);
if let Some(tok) = toks.next() {
devour_whitespace_or_comment(toks);
if tok.kind.to_string().to_ascii_lowercase() == "if" {
let cond = read_until_open_curly_brace(toks);
toks.next();
devour_whitespace_or_comment(toks);
let mut toks_ = read_until_closing_curly_brace(toks);
toks_.push(toks.next().unwrap());
devour_whitespace_or_comment(toks);
branches.push(Branch::new(cond, toks_))
} else if tok.is_symbol(Symbol::OpenCurlyBrace) {
else_ = read_until_closing_curly_brace(toks);
toks.next();
break;
} else {
return Err("expected \"{\".".into());
}
}
} else {
break;
}
} else {
break;
}
}
devour_whitespace_or_comment(toks);
Ok(If { branches, else_ })
}
pub fn eval(self, scope: &mut Scope, super_selector: &Selector) -> SassResult<Vec<Stmt>> {
let mut stmts = Vec::new();
let mut toks = Vec::new();
let mut found_true = false;
for branch in self.branches {
if Value::from_tokens(
&mut branch.cond.into_iter().peekable(),
scope,
super_selector,
)?
.is_true()?
{
toks = branch.toks;
found_true = true;
break;
}
}
if !found_true {
toks = self.else_;
}
for stmt in eat_stmts(&mut toks.into_iter().peekable(), scope, super_selector)? {
match stmt {
Stmt::AtRule(AtRule::If(i)) => stmts.extend(i.eval(scope, super_selector)?),
Stmt::RuleSet(r) if r.selector.is_empty() => stmts.extend(r.rules),
v => stmts.push(v),
}
}
Ok(stmts)
}
}

View File

@ -11,13 +11,15 @@ use crate::unit::Unit;
use crate::utils::{devour_whitespace, devour_whitespace_or_comment};
use crate::value::{Number, Value};
use crate::{Stmt, Token, TokenKind};
pub(crate) use function::Function;
pub(crate) use mixin::{eat_include, Mixin};
pub(crate) use function::Function;
pub(crate) use if_rule::If;
pub(crate) use mixin::{eat_include, Mixin};
use parse::eat_stmts;
use unknown::UnknownAtRule;
mod function;
mod if_rule;
mod mixin;
mod parse;
mod unknown;
@ -34,7 +36,7 @@ pub(crate) enum AtRule {
Content,
Unknown(UnknownAtRule),
For(Vec<Stmt>),
If(Vec<Token>, Vec<Stmt>, Vec<Stmt>),
If(If),
}
impl AtRule {
@ -107,46 +109,7 @@ impl AtRule {
}
AtRuleKind::Each => todo!("@each not yet implemented"),
AtRuleKind::Extend => todo!("@extend not yet implemented"),
AtRuleKind::If => {
let mut cond = Vec::new();
let mut n = 0;
while let Some(tok) = toks.peek() {
match tok.kind {
TokenKind::Symbol(Symbol::OpenCurlyBrace) => n += 1,
TokenKind::Symbol(Symbol::CloseCurlyBrace) => n -= 1,
TokenKind::Interpolation => n += 1,
_ => {}
}
if n == 1 {
break;
}
cond.push(toks.next().unwrap());
}
toks.next();
devour_whitespace_or_comment(toks);
let mut yes = Vec::new();
yes.extend(eat_stmts(toks, scope, super_selector)?);
devour_whitespace_or_comment(toks);
let mut no = Vec::new();
if let Some(tok) = toks.peek() {
if tok.kind == TokenKind::AtRule(AtRuleKind::Else) {
toks.next();
devour_whitespace_or_comment(toks);
if let Some(tok) = toks.next() {
if !tok.is_symbol(Symbol::OpenCurlyBrace) {
return Err("expected \"{\".".into());
}
}
devour_whitespace_or_comment(toks);
no.extend(eat_stmts(toks, scope, super_selector)?);
}
}
devour_whitespace_or_comment(toks);
AtRule::If(cond, yes, no)
}
AtRuleKind::If => AtRule::If(If::from_tokens(toks)?),
AtRuleKind::Else => todo!("@else not yet implemented"),
AtRuleKind::For => {
let mut stmts = Vec::new();

View File

@ -413,16 +413,8 @@ impl<'a> StyleSheetParser<'a> {
}
AtRule::For(s) => rules.extend(s),
AtRule::Content => return Err("@content is only allowed within mixin declarations.".into()),
AtRule::If(cond, yes, no) => {
if Value::from_tokens(
&mut cond.into_iter().peekable(),
&GLOBAL_SCOPE.with(|s| s.borrow().clone()),
&Selector::new(),
)?.is_true()? {
rules.extend(yes);
} else {
rules.extend(no);
}
AtRule::If(i) => {
rules.extend(i.eval(&mut Scope::new(), &Selector::new())?);
}
u @ AtRule::Unknown(..) => rules.push(Stmt::AtRule(u)),
}
@ -450,19 +442,7 @@ impl<'a> StyleSheetParser<'a> {
Expr::Style(s) => stmts.push(Stmt::Style(s)),
Expr::AtRule(a) => match a {
AtRule::For(s) => stmts.extend(s),
AtRule::If(cond, yes, no) => {
if Value::from_tokens(
&mut cond.into_iter().peekable(),
&GLOBAL_SCOPE.with(|s| s.borrow().clone()),
&Selector::new(),
)?
.is_true()?
{
stmts.extend(yes);
} else {
stmts.extend(no);
}
}
AtRule::If(i) => stmts.extend(i.eval(scope, super_selector)?),
AtRule::Content => {
return Err("@content is only allowed within mixin declarations.".into())
}

View File

@ -96,6 +96,60 @@ impl VariableDecl {
}
}
// Eat tokens until an open curly brace
//
// Does not consume the open curly brace
pub(crate) fn read_until_open_curly_brace<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
) -> Vec<Token> {
let mut val = Vec::new();
let mut n = 0;
while let Some(tok) = toks.peek() {
match tok.kind {
TokenKind::Symbol(Symbol::OpenCurlyBrace) => n += 1,
TokenKind::Symbol(Symbol::CloseCurlyBrace) => n -= 1,
TokenKind::Interpolation => n += 1,
_ => {}
}
if n == 1 {
break;
}
val.push(toks.next().unwrap());
}
val
}
pub(crate) fn read_until_closing_curly_brace<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
) -> Vec<Token> {
let mut t = Vec::new();
let mut nesting = 0;
while let Some(tok) = toks.peek() {
match tok.kind {
TokenKind::Symbol(Symbol::DoubleQuote) | TokenKind::Symbol(Symbol::SingleQuote) => {
let quote = toks.next().unwrap();
t.push(quote.clone());
t.extend(read_until_closing_quote(toks, &quote.kind));
}
TokenKind::Interpolation | TokenKind::Symbol(Symbol::OpenCurlyBrace) => {
nesting += 1;
t.push(toks.next().unwrap());
}
TokenKind::Symbol(Symbol::CloseCurlyBrace) => {
if nesting == 0 {
break;
} else {
nesting -= 1;
t.push(toks.next().unwrap());
}
}
_ => t.push(toks.next().unwrap()),
}
}
devour_whitespace(toks);
t
}
pub(crate) fn read_until_closing_quote<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
q: &TokenKind,

View File

@ -283,7 +283,7 @@ impl Value {
Ok(func
.clone()
.args(&mut eat_call_args(toks, scope, super_selector)?)?
.call(super_selector)?)
.call(super_selector, func.body())?)
}
_ => {
if let Ok(c) = crate::color::ColorName::try_from(s.as_ref()) {
@ -353,7 +353,9 @@ impl Value {
format!("\\{}{}", s, flatten_ident(toks, scope, super_selector)?),
QuoteKind::None,
)),
TokenKind::Whitespace(w) => Ok(Value::Ident(format!("\\{}", w), QuoteKind::None)),
TokenKind::Whitespace(w) => {
Ok(Value::Ident(format!("\\{}", w), QuoteKind::None))
}
TokenKind::Ident(s) => Ok(Value::Ident(s, QuoteKind::None)),
_ => todo!("value after \\"),
}

View File

@ -8,42 +8,62 @@ test!(
"@if true {\n a {\n color: foo;\n}\n}\n",
"a {\n color: foo;\n}\n"
);
test!(
if_inner_true,
"a {\n @if true {\n color: foo;\n}\n}\n",
"a {\n color: foo;\n}\n"
);
test!(
if_toplevel_false,
"@if false {\n a {\n color: foo;\n}\n}\n",
""
);
test!(
if_inner_false,
"a {\n @if false {\n color: foo;\n}\n}\n",
""
);
test!(
if_else_toplevel_true,
"@if true {\n a {\n color: foo;\n}\n} @else {\n b {\n color: bar\n}\n}\n",
"a {\n color: foo;\n}\n"
);
test!(
if_else_inner_true,
"a {\n @if true {\n color: foo;\n} @else {\n color: bar\n}\n}\n",
"a {\n color: foo;\n}\n"
);
test!(
if_else_toplevel_false,
"@if false {\n a {\n color: foo;\n}\n} @else {\n a {\n color: bar\n}\n}\n",
"a {\n color: bar;\n}\n"
);
test!(
if_else_inner_false,
"a {\n @if false {\n color: foo;\n} @else {\n color: bar\n}\n}\n",
"a {\n color: bar;\n}\n"
);
error!(
no_brace_after_else,
"@if false {} @else -}", "Error: expected \"{\"."
);
// test!(
// if_inner_true,
// "a {\n @if true {\n color: foo;\n}\n}\n",
// "a {\n color: foo;\n}\n"
// );
// test!(
// if_toplevel_false,
// "@if false {\n a {\n color: foo;\n}\n}\n",
// ""
// );
// test!(
// if_inner_false,
// "a {\n @if false {\n color: foo;\n}\n}\n",
// ""
// );
// test!(
// if_else_toplevel_true,
// "@if true {\n a {\n color: foo;\n}\n} @else {\n b {\n color: bar;\n}\n}\n",
// "a {\n color: foo;\n}\n"
// );
// test!(
// if_else_inner_true,
// "a {\n @if true {\n color: foo;\n} @else {\n color: bar;\n}\n}\n",
// "a {\n color: foo;\n}\n"
// );
// test!(
// if_else_toplevel_false,
// "@if false {\n a {\n color: foo;\n}\n} @else {\n a {\n color: bar;\n}\n}\n",
// "a {\n color: bar;\n}\n"
// );
// test!(
// if_else_inner_false,
// "a {\n @if false {\n color: foo;\n} @else {\n color: bar;\n}\n}\n",
// "a {\n color: bar;\n}\n"
// );
// error!(
// no_brace_after_else,
// "@if false {} @else -}", "Error: expected \"{\"."
// );
// test!(
// if_else_if_no_else,
// "a {\n @if false {\n color: red;\n} @else if true {\n color: blue;\n}\n}\n",
// "a {\n color: blue;\n}\n"
// );
// test!(
// if_false_else_if_false_else,
// "a {\n @if false {\n color: red;\n} @else if false {\n color: blue;\n} @else {\n color: green;\n}\n}\n",
// "a {\n color: green;\n}\n"
// );
// test!(
// if_false_else_if_true_else,
// "a {\n @if false {\n color: red;\n} @else if true {\n color: blue;\n} @else {\n color: green;\n}\n}\n",
// "a {\n color: blue;\n}\n"
// );
// test!(
// if_inner_style_missing_semicolon,
// "a {\n @if true {\n color: red\n }\n}\n",
// "a {\n color: red;\n}\n"
// );