grass/src/atrule/mixin.rs

179 lines
5.6 KiB
Rust
Raw Normal View History

use std::iter::Peekable;
use std::vec::IntoIter;
2020-03-22 15:08:13 -04:00
use super::eat_stmts;
2020-01-25 11:00:29 -05:00
use crate::args::{eat_call_args, eat_func_args, CallArgs, FuncArgs};
2020-03-01 17:06:55 -05:00
use crate::atrule::AtRule;
2020-03-29 13:28:17 -04:00
use crate::error::SassResult;
use crate::scope::Scope;
use crate::selector::Selector;
2020-03-29 13:28:17 -04:00
use crate::utils::{
devour_whitespace, devour_whitespace_or_comment, eat_ident, read_until_closing_curly_brace,
};
use crate::{eat_expr, Expr, RuleSet, Stmt, Token};
2020-01-12 20:15:40 -05:00
#[derive(Debug, Clone)]
pub(crate) struct Mixin {
2020-01-12 20:15:40 -05:00
scope: Scope,
args: FuncArgs,
body: Peekable<IntoIter<Token>>,
2020-03-22 15:08:13 -04:00
content: Vec<Stmt>,
2020-01-12 20:15:40 -05:00
}
impl Mixin {
2020-03-22 15:08:13 -04:00
pub fn new(scope: Scope, args: FuncArgs, body: Vec<Token>, content: Vec<Stmt>) -> Self {
2020-01-17 21:20:56 -05:00
let body = body.into_iter().peekable();
2020-03-22 15:08:13 -04:00
Mixin {
scope,
args,
body,
content,
}
2020-01-12 20:15:40 -05:00
}
pub fn decl_from_tokens<I: Iterator<Item = Token>>(
2020-01-18 19:54:47 -05:00
toks: &mut Peekable<I>,
scope: &Scope,
2020-03-01 12:03:14 -05:00
super_selector: &Selector,
2020-02-17 09:22:41 -05:00
) -> SassResult<(String, Mixin)> {
2020-01-18 19:54:47 -05:00
devour_whitespace(toks);
2020-03-29 13:28:17 -04:00
let name = eat_ident(toks, scope, super_selector)?;
2020-01-18 19:54:47 -05:00
devour_whitespace(toks);
let args = match toks.next() {
2020-03-29 13:28:17 -04:00
Some(Token { kind: '(', .. }) => eat_func_args(toks, scope, super_selector)?,
Some(Token { kind: '{', .. }) => FuncArgs::new(),
2020-02-17 10:02:44 -05:00
_ => return Err("expected \"{\".".into()),
2020-01-18 19:54:47 -05:00
};
2020-02-02 18:01:09 -05:00
devour_whitespace(toks);
2020-03-29 13:28:17 -04:00
let mut body = read_until_closing_curly_brace(toks);
body.push(toks.next().unwrap());
2020-01-18 19:54:47 -05:00
2020-03-22 15:08:13 -04:00
Ok((name, Mixin::new(scope.clone(), args, body, Vec::new())))
}
pub fn content(mut self, content: Vec<Stmt>) -> Mixin {
self.content = content;
self
2020-01-18 19:54:47 -05:00
}
pub fn args(mut self, args: &mut CallArgs) -> SassResult<Mixin> {
2020-01-19 10:58:38 -05:00
for (idx, arg) in self.args.0.iter().enumerate() {
let val = match args.remove_positional(idx) {
Some(v) => v,
None => match args.remove_named(arg.name.clone()) {
Some(v) => v,
None => match &arg.default {
Some(v) => v.clone(),
None => return Err(format!("Missing argument ${}.", &arg.name).into()),
},
2020-01-19 10:58:38 -05:00
},
};
self.scope.insert_var(&arg.name, val)?;
2020-01-17 14:44:55 -05:00
}
Ok(self)
2020-01-17 14:44:55 -05:00
}
2020-02-16 10:54:25 -05:00
pub fn call(mut self, super_selector: &Selector) -> SassResult<Vec<Stmt>> {
2020-01-18 21:15:09 -05:00
self.eval(super_selector)
2020-01-18 09:03:38 -05:00
}
2020-02-16 10:54:25 -05:00
fn eval(&mut self, super_selector: &Selector) -> SassResult<Vec<Stmt>> {
let mut stmts = Vec::new();
while let Some(expr) = eat_expr(&mut self.body, &mut self.scope, super_selector)? {
match expr {
2020-03-01 17:06:55 -05:00
Expr::AtRule(a) => match a {
2020-03-22 15:08:13 -04:00
AtRule::Content => stmts.extend(self.content.clone()),
2020-03-10 21:23:47 -04:00
_ => stmts.push(Stmt::AtRule(a)),
2020-03-01 17:06:55 -05:00
},
2020-02-29 16:13:57 -05:00
Expr::Style(s) => stmts.push(Stmt::Style(s)),
Expr::Styles(s) => stmts.extend(s.into_iter().map(Box::new).map(Stmt::Style)),
Expr::Include(s) => stmts.extend(s),
2020-02-28 18:32:11 -05:00
Expr::FunctionDecl(..) => {
return Err("Mixins may not contain function declarations.".into())
2020-01-26 15:26:45 -05:00
}
2020-02-28 18:32:11 -05:00
Expr::MixinDecl(..) => {
return Err("Mixins may not contain mixin declarations.".into())
}
Expr::Debug(..) | Expr::Warn(..) => todo!(),
2020-01-18 21:15:09 -05:00
Expr::Selector(selector) => {
let rules = self.eval(&super_selector.zip(&selector))?;
stmts.push(Stmt::RuleSet(RuleSet {
super_selector: super_selector.clone(),
2020-01-18 21:15:09 -05:00
selector,
rules,
}));
2020-01-12 20:15:40 -05:00
}
Expr::VariableDecl(name, val) => {
self.scope.insert_var(&name, *val)?;
2020-01-14 19:34:13 -05:00
}
Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s)),
2020-01-12 20:15:40 -05:00
}
}
Ok(stmts)
2020-01-12 20:15:40 -05:00
}
}
pub(crate) fn eat_include<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
scope: &Scope,
super_selector: &Selector,
2020-02-16 10:54:25 -05:00
) -> SassResult<Vec<Stmt>> {
2020-03-29 13:28:17 -04:00
devour_whitespace_or_comment(toks)?;
let name = eat_ident(toks, scope, super_selector)?;
2020-03-29 13:28:17 -04:00
devour_whitespace_or_comment(toks)?;
2020-03-22 15:08:13 -04:00
let mut has_include = false;
let mut args = if let Some(tok) = toks.next() {
match tok.kind {
2020-03-29 13:28:17 -04:00
';' => CallArgs::new(),
'(' => {
2020-03-01 12:03:14 -05:00
let tmp = eat_call_args(toks, scope, super_selector)?;
2020-03-29 13:28:17 -04:00
devour_whitespace_or_comment(toks)?;
if let Some(tok) = toks.next() {
2020-03-22 15:08:13 -04:00
match tok.kind {
2020-03-29 13:28:17 -04:00
';' => {}
'{' => has_include = true,
2020-03-22 15:08:13 -04:00
_ => todo!(),
}
}
tmp
2020-01-26 15:26:45 -05:00
}
2020-03-29 13:28:17 -04:00
'{' => {
2020-03-22 15:08:13 -04:00
has_include = true;
CallArgs::new()
}
2020-02-23 07:32:40 -05:00
_ => return Err("expected \"{\".".into()),
}
} else {
2020-03-29 13:28:17 -04:00
return Err("unexpected EOF".into());
};
devour_whitespace(toks);
2020-03-22 15:08:13 -04:00
let content = if let Some(tok) = toks.peek() {
2020-03-29 13:28:17 -04:00
if tok.kind == '{' {
2020-03-22 15:08:13 -04:00
toks.next();
eat_stmts(toks, &mut scope.clone(), super_selector)?
} else if has_include {
eat_stmts(toks, &mut scope.clone(), super_selector)?
} else {
Vec::new()
}
} else {
Vec::new()
};
2020-02-17 09:22:41 -05:00
let mixin = scope.get_mixin(&name)?.clone();
2020-03-22 15:08:13 -04:00
let rules = mixin
.args(&mut args)?
.content(content)
.call(super_selector)?;
Ok(rules)
}