use std::collections::BTreeMap; use std::iter::Peekable; use crate::common::Pos; use crate::error::SassResult; use crate::scope::Scope; use crate::selector::Selector; use crate::utils::{ devour_whitespace, devour_whitespace_or_comment, eat_ident, read_until_closing_paren, read_until_closing_quote, read_until_closing_square_brace, }; use crate::value::Value; use crate::Token; #[derive(Debug, Clone, Eq, PartialEq)] pub(crate) struct FuncArgs(pub Vec); #[derive(Debug, Clone, Eq, PartialEq)] pub(crate) struct FuncArg { pub name: String, pub default: Option, } impl FuncArgs { pub const fn new() -> Self { FuncArgs(Vec::new()) } } #[derive(Debug, Clone, std::default::Default)] pub(crate) struct CallArgs(pub BTreeMap); impl CallArgs { pub fn new() -> Self { CallArgs(BTreeMap::new()) } pub fn get(&self, val: &str) -> Option<&Value> { self.0.get(val) } pub fn len(&self) -> usize { self.0.len() } pub fn is_empty(&self) -> bool { self.0.len() == 0 } pub fn remove(&mut self, s: &str) -> Option { self.0.remove(s) } } pub(crate) fn eat_func_args>( toks: &mut Peekable, scope: &Scope, super_selector: &Selector, ) -> SassResult { let mut args: Vec = Vec::new(); devour_whitespace(toks); while let Some(Token { kind, .. }) = toks.next() { let name = match kind { '$' => eat_ident(toks, scope, super_selector)?, ')' => break, _ => todo!(), }; let mut default: Vec = Vec::new(); devour_whitespace(toks); let kind = match toks.next() { Some(Token { kind, .. }) => kind, _ => todo!("unexpected eof"), }; match kind { ':' => { devour_whitespace(toks); while let Some(tok) = toks.peek() { match &tok.kind { ',' => { toks.next(); args.push(FuncArg { name: name.replace('_', "-"), default: Some(Value::from_tokens( &mut default.into_iter().peekable(), scope, super_selector, )?), }); break; } ')' => { args.push(FuncArg { name: name.replace('_', "-"), default: Some(Value::from_tokens( &mut default.into_iter().peekable(), scope, super_selector, )?), }); break; } _ => { let tok = toks.next().expect("we know this exists!"); default.push(tok) } } } } '.' => todo!("handle varargs"), ')' => { args.push(FuncArg { name: name.replace('_', "-"), default: if default.is_empty() { None } else { Some(Value::from_tokens( &mut default.into_iter().peekable(), scope, super_selector, )?) }, }); break; } ',' => args.push(FuncArg { name: name.replace('_', "-"), default: None, }), _ => {} } devour_whitespace(toks); } devour_whitespace(toks); if let Some(Token { kind: '{', .. }) = toks.next() { } else { todo!("expected `{{` after args") } Ok(FuncArgs(args)) } pub(crate) fn eat_call_args>( toks: &mut Peekable, scope: &Scope, super_selector: &Selector, ) -> SassResult { let mut args: BTreeMap = BTreeMap::new(); devour_whitespace_or_comment(toks)?; let mut name: String; let mut val: Vec = Vec::new(); loop { match toks.peek().unwrap().kind { '$' => { toks.next(); let v = eat_ident(toks, scope, super_selector)?; devour_whitespace_or_comment(toks)?; if toks.peek().unwrap().kind == ':' { toks.next(); name = v; } else { val.push(Token::new(Pos::new(), '$')); val.extend(v.chars().map(|x| Token::new(Pos::new(), x))); name = args.len().to_string(); } } ')' => { toks.next(); return Ok(CallArgs(args)); } _ => name = args.len().to_string(), } devour_whitespace_or_comment(toks)?; while let Some(tok) = toks.next() { match tok.kind { ')' => { args.insert( name.replace('_', "-"), Value::from_tokens(&mut val.into_iter().peekable(), scope, super_selector)?, ); return Ok(CallArgs(args)); } ',' => break, '[' => { val.push(tok); val.extend(read_until_closing_square_brace(toks)); } '(' => { val.push(tok); val.extend(read_until_closing_paren(toks)); } '"' | '\'' => { val.push(tok); val.extend(read_until_closing_quote(toks, tok.kind)); } _ => val.push(tok), } } args.insert( name.replace('_', "-"), Value::from_tokens( &mut val.clone().into_iter().peekable(), scope, super_selector, )?, ); val.clear(); devour_whitespace(toks); if toks.peek().is_none() { return Ok(CallArgs(args)); } } }