refactor @if/@else if/@else
This commit is contained in:
parent
6d2d9bcb4a
commit
f15ab57573
@ -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
111
src/atrule/if_rule.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
26
src/lib.rs
26
src/lib.rs
@ -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())
|
||||
}
|
||||
|
54
src/utils.rs
54
src/utils.rs
@ -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, "e.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,
|
||||
|
@ -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 \\"),
|
||||
}
|
||||
|
98
tests/if.rs
98
tests/if.rs
@ -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"
|
||||
// );
|
||||
|
Loading…
x
Reference in New Issue
Block a user