156 lines
5.1 KiB
Rust
156 lines
5.1 KiB
Rust
|
use std::mem;
|
||
|
|
||
|
use codemap::Spanned;
|
||
|
|
||
|
use crate::{
|
||
|
args::{CallArgs, FuncArgs},
|
||
|
atrule::Mixin,
|
||
|
error::SassResult,
|
||
|
scope::Scope,
|
||
|
utils::read_until_closing_curly_brace,
|
||
|
value::Value,
|
||
|
Token,
|
||
|
};
|
||
|
|
||
|
use super::{Parser, Stmt};
|
||
|
|
||
|
impl<'a> Parser<'a> {
|
||
|
pub(super) fn parse_mixin(&mut self) -> SassResult<()> {
|
||
|
self.whitespace();
|
||
|
let Spanned { node: name, span } = self.parse_identifier()?;
|
||
|
self.whitespace();
|
||
|
let args = match self.toks.next() {
|
||
|
Some(Token { kind: '(', .. }) => self.parse_func_args()?,
|
||
|
Some(Token { kind: '{', .. }) => FuncArgs::new(),
|
||
|
Some(t) => return Err(("expected \"{\".", t.pos()).into()),
|
||
|
None => return Err(("expected \"{\".", span).into()),
|
||
|
};
|
||
|
|
||
|
self.whitespace();
|
||
|
|
||
|
let mut body = read_until_closing_curly_brace(self.toks)?;
|
||
|
body.push(self.toks.next().unwrap());
|
||
|
|
||
|
// todo: `@include` can only give content when `@content` is present within the body
|
||
|
// if `@content` is *not* present and `@include` attempts to give a body, we throw an error
|
||
|
// `Error: Mixin doesn't accept a content block.`
|
||
|
//
|
||
|
// this is blocked on figuring out just how to check for this. presumably we could have a check
|
||
|
// not when parsing initially, but rather when `@include`ing to see if an `@content` was found.
|
||
|
|
||
|
let mixin = Mixin::new(Scope::new(), args, body, false);
|
||
|
|
||
|
if self.at_root {
|
||
|
self.global_scope.insert_mixin(name, mixin);
|
||
|
} else {
|
||
|
self.scopes.last_mut().insert_mixin(name, mixin);
|
||
|
}
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
pub(super) fn parse_include(&mut self) -> SassResult<Vec<Stmt>> {
|
||
|
self.whitespace_or_comment();
|
||
|
let name = self.parse_identifier()?;
|
||
|
|
||
|
self.whitespace_or_comment();
|
||
|
|
||
|
let mut has_content = false;
|
||
|
|
||
|
let args = match self.toks.next() {
|
||
|
Some(Token { kind: ';', .. }) => CallArgs::new(name.span),
|
||
|
Some(Token { kind: '(', .. }) => {
|
||
|
let tmp = self.parse_call_args()?;
|
||
|
self.whitespace_or_comment();
|
||
|
if let Some(tok) = self.toks.peek() {
|
||
|
match tok.kind {
|
||
|
';' => {
|
||
|
self.toks.next();
|
||
|
}
|
||
|
'{' => {
|
||
|
self.toks.next();
|
||
|
has_content = true
|
||
|
}
|
||
|
_ => {}
|
||
|
}
|
||
|
}
|
||
|
tmp
|
||
|
}
|
||
|
Some(Token { kind: '{', .. }) => {
|
||
|
has_content = true;
|
||
|
CallArgs::new(name.span)
|
||
|
}
|
||
|
Some(Token { pos, .. }) => return Err(("expected \"{\".", pos).into()),
|
||
|
None => return Err(("expected \"{\".", name.span).into()),
|
||
|
};
|
||
|
|
||
|
self.whitespace();
|
||
|
|
||
|
let content = if has_content {
|
||
|
Some(self.parse_content()?)
|
||
|
} else {
|
||
|
None
|
||
|
};
|
||
|
|
||
|
let mut mixin = self.scopes.last().get_mixin(name, self.global_scope)?;
|
||
|
self.eval_mixin_args(&mut mixin, args)?;
|
||
|
|
||
|
self.scopes.push(mixin.scope);
|
||
|
|
||
|
let body = Parser {
|
||
|
toks: &mut mixin.body,
|
||
|
map: self.map,
|
||
|
path: self.path,
|
||
|
scopes: self.scopes,
|
||
|
global_scope: self.global_scope,
|
||
|
super_selectors: self.super_selectors,
|
||
|
span_before: self.span_before,
|
||
|
in_mixin: true,
|
||
|
in_function: self.in_function,
|
||
|
in_control_flow: self.in_control_flow,
|
||
|
content,
|
||
|
at_root: false,
|
||
|
at_root_has_selector: self.at_root_has_selector,
|
||
|
}
|
||
|
.parse()?;
|
||
|
|
||
|
Ok(body)
|
||
|
}
|
||
|
|
||
|
pub(super) fn parse_content(&mut self) -> SassResult<Vec<Stmt>> {
|
||
|
self.parse_stmt()
|
||
|
}
|
||
|
|
||
|
fn eval_mixin_args(&mut self, mixin: &mut Mixin, mut args: CallArgs) -> SassResult<()> {
|
||
|
let mut scope = self.scopes.last().clone();
|
||
|
for (idx, arg) in mixin.args.0.iter_mut().enumerate() {
|
||
|
if arg.is_variadic {
|
||
|
let span = args.span();
|
||
|
// todo: does this get the most recent scope?
|
||
|
let arg_list = Value::ArgList(self.variadic_args(args)?);
|
||
|
mixin.scope.insert_var(
|
||
|
arg.name.clone(),
|
||
|
Spanned {
|
||
|
node: arg_list,
|
||
|
span,
|
||
|
},
|
||
|
)?;
|
||
|
break;
|
||
|
}
|
||
|
let val = match args.get(idx, arg.name.clone()) {
|
||
|
Some(v) => self.parse_value_from_vec(v)?,
|
||
|
None => match arg.default.as_mut() {
|
||
|
Some(v) => self.parse_value_from_vec(mem::take(v))?,
|
||
|
None => {
|
||
|
return Err(
|
||
|
(format!("Missing argument ${}.", &arg.name), args.span()).into()
|
||
|
)
|
||
|
}
|
||
|
},
|
||
|
};
|
||
|
scope.insert_var(arg.name.clone(), val.clone())?;
|
||
|
mixin.scope.insert_var(mem::take(&mut arg.name), val)?;
|
||
|
}
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|