use std::fmt; use peekmore::PeekMore; use crate::{ atrule::keyframes::{Keyframes, KeyframesSelector}, error::SassResult, parse::Stmt, utils::eat_whole_number, Token, }; use super::Parser; impl fmt::Display for KeyframesSelector { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { KeyframesSelector::To => f.write_str("to"), KeyframesSelector::From => f.write_str("from"), KeyframesSelector::Percent(p) => write!(f, "{}%", p), } } } struct KeyframesSelectorParser<'a, 'b> { parser: &'a mut Parser<'b>, } impl<'a, 'b> KeyframesSelectorParser<'a, 'b> { pub fn new(parser: &'a mut Parser<'b>) -> Self { Self { parser } } fn parse_keyframes_selector(&mut self) -> SassResult> { let mut selectors = Vec::new(); self.parser.whitespace_or_comment(); while let Some(tok) = self.parser.toks.peek().cloned() { match tok.kind { 't' | 'T' => { let mut ident = self.parser.parse_identifier()?; ident.node.make_ascii_lowercase(); if ident.node == "to" { selectors.push(KeyframesSelector::To) } else { return Err(("Expected \"to\" or \"from\".", tok.pos).into()); } } 'f' | 'F' => { let mut ident = self.parser.parse_identifier()?; ident.node.make_ascii_lowercase(); if ident.node == "from" { selectors.push(KeyframesSelector::From) } else { return Err(("Expected \"to\" or \"from\".", tok.pos).into()); } } '0'..='9' => { let mut num = String::new(); eat_whole_number(self.parser.toks, &mut num); if !matches!(self.parser.toks.next(), Some(Token { kind: '%', .. })) { return Err(("expected \"%\".", tok.pos).into()); } selectors.push(KeyframesSelector::Percent(num.into_boxed_str())); } '{' => break, '\\' => todo!("escaped chars in @keyframes selector"), _ => return Err(("Expected \"to\" or \"from\".", tok.pos).into()), } self.parser.whitespace_or_comment(); if let Some(Token { kind: ',', .. }) = self.parser.toks.peek() { self.parser.toks.next(); self.parser.whitespace_or_comment(); } else { break; } } Ok(selectors) } } impl<'a> Parser<'a> { fn parse_keyframes_name(&mut self) -> SassResult { let mut name = String::new(); let mut found_open_brace = false; self.whitespace_or_comment(); while let Some(tok) = self.toks.next() { match tok.kind { '#' => { if let Some(Token { kind: '{', .. }) = self.toks.peek() { self.toks.next(); name.push_str(&self.parse_interpolation_as_string()?); } else { name.push('#'); } } ' ' | '\n' | '\t' => { self.whitespace(); name.push(' '); } '{' => { found_open_brace = true; break; } _ => name.push(tok.kind), } } if !found_open_brace { return Err(("expected \"{\".", self.span_before).into()); } // todo: we can avoid the reallocation by trimming before emitting (in `output.rs`) Ok(name.trim().to_string()) } pub(super) fn parse_keyframes_selector( &mut self, mut string: String, ) -> SassResult> { let mut span = if let Some(tok) = self.toks.peek() { tok.pos() } else { return Err(("expected \"{\".", self.span_before).into()); }; self.span_before = span; let mut found_curly = false; while let Some(tok) = self.toks.next() { span = span.merge(tok.pos()); match tok.kind { '#' => { if let Some(Token { kind: '{', .. }) = self.toks.peek().cloned() { self.toks.next(); string.push_str(&self.parse_interpolation()?.to_css_string(span)?); } else { string.push('#'); } } ',' => { while let Some(c) = string.pop() { if c == ' ' || c == ',' { continue; } string.push(c); string.push(','); break; } } '/' => { if self.toks.peek().is_none() { return Err(("Expected selector.", tok.pos()).into()); } self.parse_comment()?; self.whitespace(); string.push(' '); } '{' => { found_curly = true; break; } c => string.push(c), } } if !found_curly { return Err(("expected \"{\".", span).into()); } let sel_toks: Vec = string.chars().map(|x| Token::new(span, x)).collect(); let mut iter = sel_toks.into_iter().peekmore(); let selector = KeyframesSelectorParser::new(&mut Parser { toks: &mut iter, map: self.map, path: self.path, scopes: self.scopes, global_scope: self.global_scope, super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, in_mixin: self.in_mixin, in_function: self.in_function, in_control_flow: self.in_control_flow, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, in_keyframes: self.in_keyframes, }) .parse_keyframes_selector()?; Ok(selector) } pub(super) fn parse_keyframes(&mut self) -> SassResult { let name = self.parse_keyframes_name()?; self.whitespace(); let body = Parser { toks: self.toks, map: self.map, path: self.path, scopes: self.scopes, global_scope: self.global_scope, super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, in_mixin: self.in_mixin, in_function: self.in_function, in_control_flow: self.in_control_flow, at_root: false, in_keyframes: true, at_root_has_selector: self.at_root_has_selector, extender: self.extender, } .parse_stmt()?; Ok(Stmt::Keyframes(Box::new(Keyframes { name, body }))) } }