grass/src/parse/mixin.rs

203 lines
6.7 KiB
Rust
Raw Normal View History

2020-06-16 22:34:01 -04:00
use codemap::Spanned;
2020-06-30 06:53:17 -04:00
use peekmore::PeekMore;
2020-06-16 22:34:01 -04:00
use crate::{
args::{CallArgs, FuncArgs},
2020-07-02 10:31:32 -04:00
atrule::{Content, Mixin},
2020-06-16 22:34:01 -04:00
error::SassResult,
utils::read_until_closing_curly_brace,
Token,
};
2020-06-18 18:14:35 -04:00
use super::{NeverEmptyVec, Parser, Stmt};
2020-06-16 22:34:01 -04:00
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)?;
2020-06-18 03:09:24 -04:00
body.push(match self.toks.next() {
Some(tok) => tok,
None => return Err(("expected \"}\".", self.span_before).into()),
});
2020-06-16 22:34:01 -04:00
// 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.
2020-06-18 18:14:35 -04:00
let mixin = Mixin::new(self.scopes.last().clone(), args, body, false);
2020-06-16 22:34:01 -04:00
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();
2020-07-02 10:31:32 -04:00
let args = if let Some(Token { kind: '(', .. }) = self.toks.peek() {
self.toks.next();
self.parse_call_args()?
} else {
CallArgs::new(name.span)
};
2020-06-16 22:34:01 -04:00
2020-07-02 10:31:32 -04:00
self.whitespace_or_comment();
let content_args = if let Some(Token { kind: 'u', .. }) | Some(Token { kind: 'U', .. }) =
self.toks.peek()
{
let mut ident = self.parse_identifier_no_interpolation(false)?;
ident.node.make_ascii_lowercase();
if ident.node == "using" {
2020-06-16 22:34:01 -04:00
self.whitespace_or_comment();
2020-07-02 10:31:32 -04:00
if !matches!(self.toks.next(), Some(Token { kind: '(', .. })) {
return Err(("expected \"(\".", ident.span).into());
2020-06-16 22:34:01 -04:00
}
2020-07-02 10:31:32 -04:00
Some(self.parse_func_args()?)
} else {
return Err(("expected keyword \"using\".", ident.span).into());
2020-06-16 22:34:01 -04:00
}
2020-07-02 10:31:32 -04:00
} else {
None
2020-06-16 22:34:01 -04:00
};
2020-07-02 10:31:32 -04:00
self.whitespace_or_comment();
2020-06-16 22:34:01 -04:00
2020-07-02 10:31:32 -04:00
let content = if content_args.is_some()
|| matches!(self.toks.peek(), Some(Token { kind: '{', .. }))
{
if matches!(self.toks.peek(), Some(Token { kind: '{', .. })) {
self.toks.next();
}
let mut toks = read_until_closing_curly_brace(self.toks)?;
if let Some(tok) = self.toks.peek() {
toks.push(*tok);
self.toks.next();
}
Some(toks)
2020-06-16 22:34:01 -04:00
} else {
None
};
2020-07-02 10:31:32 -04:00
if let Some(Token { kind: ';', .. }) = self.toks.peek() {
self.toks.next();
}
let Mixin {
mut scope,
body,
args: fn_args,
..
} = self.scopes.last().get_mixin(name, self.global_scope)?;
self.eval_args(fn_args, args, &mut scope)?;
2020-06-16 22:34:01 -04:00
2020-07-02 15:22:15 -04:00
self.content.push(Content {
content,
content_args,
scope: self.scopes.last().clone(),
2020-07-02 15:22:15 -04:00
});
2020-06-16 22:34:01 -04:00
let body = Parser {
2020-07-02 10:31:32 -04:00
toks: &mut body.into_iter().peekmore(),
2020-06-16 22:34:01 -04:00
map: self.map,
path: self.path,
2020-07-02 10:31:32 -04:00
scopes: &mut NeverEmptyVec::new(scope),
2020-06-16 22:34:01 -04:00
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,
2020-07-02 15:22:15 -04:00
content: self.content,
2020-06-16 22:34:01 -04:00
at_root: false,
at_root_has_selector: self.at_root_has_selector,
2020-06-18 16:56:03 -04:00
extender: self.extender,
2020-06-16 22:34:01 -04:00
}
.parse()?;
2020-07-02 15:22:15 -04:00
self.content.pop();
2020-06-16 22:34:01 -04:00
Ok(body)
}
2020-07-02 10:31:32 -04:00
pub(super) fn parse_content_rule(&mut self) -> SassResult<Vec<Stmt>> {
if self.in_mixin {
let mut scope = self
.content
.last()
.cloned()
.unwrap_or_else(Content::new)
.scope;
2020-07-02 10:31:32 -04:00
if let Some(Token { kind: '(', .. }) = self.toks.peek() {
self.toks.next();
let args = self.parse_call_args()?;
2020-07-02 15:22:15 -04:00
if let Some(Some(content_args)) =
self.content.last().map(|v| v.content_args.clone())
{
2020-07-02 10:31:32 -04:00
args.max_args(content_args.len())?;
self.eval_args(content_args, args, &mut scope)?;
} else {
args.max_args(0)?;
}
}
2020-07-02 15:22:15 -04:00
Ok(if let Some(content) = &self.content.pop() {
let stmts = if let Some(body) = content.content.clone() {
Parser {
toks: &mut body.into_iter().peekmore(),
map: self.map,
path: self.path,
scopes: &mut NeverEmptyVec::new(scope),
global_scope: self.global_scope,
super_selectors: self.super_selectors,
span_before: self.span_before,
in_mixin: self.in_mixin,
in_function: self.in_function,
in_control_flow: self.in_control_flow,
content: self.content,
at_root: self.at_root,
at_root_has_selector: self.at_root_has_selector,
extender: self.extender,
}
.parse()?
} else {
Vec::new()
};
self.content.push(content.clone());
stmts
2020-07-02 10:31:32 -04:00
} else {
Vec::new()
})
} else {
Err((
"@content is only allowed within mixin declarations.",
self.span_before,
)
.into())
}
2020-06-16 22:34:01 -04:00
}
}