diff --git a/src/function.rs b/src/function.rs index e69de29..a9077e7 100644 --- a/src/function.rs +++ b/src/function.rs @@ -0,0 +1,70 @@ +use std::iter::Peekable; +use std::vec::IntoIter; + +use crate::common::{Pos, Scope, Symbol}; +use crate::args::{eat_func_args, FuncArgs}; +use crate::utils::devour_whitespace; +use crate::{Token, TokenKind}; + +#[derive(Debug, Clone)] +pub(crate) struct Function { + scope: Scope, + args: FuncArgs, + body: Peekable>, +} + + +impl Function { + pub fn new(scope: Scope, args: FuncArgs, body: Vec) -> Self { + let body = body.into_iter().peekable(); + Function { scope, args, body } + } + + pub fn decl_from_tokens>( + toks: &mut Peekable, + scope: &Scope, + ) -> Result<(String, Function), (Pos, String)> { + let Token { pos, kind } = toks + .next() + .expect("this must exist because we have already peeked"); + devour_whitespace(toks); + let name = match kind { + TokenKind::Ident(s) => s, + _ => { + return Err(( + pos, + String::from("expected identifier after function declaration"), + )) + } + }; + devour_whitespace(toks); + let args = match toks.next() { + Some(Token { + kind: TokenKind::Symbol(Symbol::OpenParen), + .. + }) => eat_func_args(toks), + _ => return Err((pos, String::from("expected `(` after function declaration"))), + }; + + let mut nesting = 1; + let mut body = Vec::new(); + + while nesting > 0 { + if let Some(tok) = toks.next() { + match &tok.kind { + TokenKind::Symbol(Symbol::OpenCurlyBrace) + // interpolation token eats the opening brace but not the closing + | TokenKind::Interpolation => nesting += 1, + TokenKind::Symbol(Symbol::CloseCurlyBrace) => nesting -= 1, + _ => {} + } + body.push(tok) + } else { + return Err((pos, String::from("unexpected EOF"))); + } + } + + Ok((name, Function::new(scope.clone(), args, body))) + } + +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 1490e10..a755a30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,7 @@ use crate::common::{Keyword, Op, Pos, Scope, Symbol, Whitespace}; use crate::css::Css; use crate::error::SassError; use crate::format::PrettyPrinter; +use crate::function::Function; use crate::imports::import; use crate::lexer::Lexer; use crate::mixin::{eat_include, Mixin}; @@ -201,6 +202,7 @@ enum Expr { VariableDecl(String, Vec), /// A mixin declaration `@mixin foo {}` MixinDecl(String, Mixin), + FunctionDecl(String, Function), /// An include statement `@include foo;` Include(Vec), /// A multiline comment: `/* foobar */` @@ -398,11 +400,6 @@ impl<'a> StyleSheetParser<'a> { rules.extend(new_rules); self.global_scope.merge(new_scope); } - TokenKind::AtRule(AtRuleKind::Mixin) => { - let (name, mixin) = - Mixin::decl_from_tokens(&mut self.lexer, &self.global_scope).unwrap(); - self.global_scope.mixins.insert(name, mixin); - } TokenKind::AtRule(_) => { if let Some(Token { kind: TokenKind::AtRule(ref rule), @@ -411,10 +408,10 @@ impl<'a> StyleSheetParser<'a> { { match eat_at_rule(rule, pos, &mut self.lexer, &self.global_scope) { AtRule::Mixin(name, mixin) => {self.global_scope.mixins.insert(name, mixin);}, + AtRule::Function(name, func) => {self.global_scope.functions.insert(name, func);}, AtRule::Error(pos, message) => self.error(pos, &message), AtRule::Warn(pos, message) => self.warn(pos, &message), AtRule::Debug(pos, message) => self.debug(pos, &message), - _ => todo!(), } } } @@ -437,6 +434,9 @@ impl<'a> StyleSheetParser<'a> { Expr::MixinDecl(name, mixin) => { scope.mixins.insert(name, mixin); } + Expr::FunctionDecl(name, func) => { + scope.functions.insert(name, func); + } Expr::Selector(s) => { self.scope += 1; let rules = self.eat_rules(&super_selector.zip(&s), scope); @@ -512,7 +512,7 @@ fn eat_at_rule>( Ok(m) => m, Err(e) => return AtRule::Error(e.0, e.1), }; - Ok(Expr::MixinDecl(name, mixin)) + AtRule::Function(name, mixin) } AtRuleKind::Use => todo!("@use not yet implemented"), AtRuleKind::Annotation => todo!("@annotation not yet implemented"), @@ -525,7 +525,6 @@ fn eat_at_rule>( AtRuleKind::For => todo!("@for not yet implemented"), AtRuleKind::While => todo!("@while not yet implemented"), AtRuleKind::Media => todo!("@media not yet implemented"), - AtRuleKind::Function => todo!("@function not yet implemented"), AtRuleKind::Keyframes => todo!("@keyframes not yet implemented"), _ => todo!("encountered unimplemented at rule"), } @@ -620,10 +619,10 @@ pub(crate) fn eat_expr>( { return match eat_at_rule(rule, pos, toks, scope) { AtRule::Mixin(name, mixin) => Ok(Some(Expr::MixinDecl(name, mixin))), + AtRule::Function(name, func) => Ok(Some(Expr::FunctionDecl(name, func))), AtRule::Debug(a, b) => Ok(Some(Expr::Debug(a, b))), AtRule::Warn(a, b) => Ok(Some(Expr::Warn(a, b))), AtRule::Error(a, b) => Err((a, b)), - _ => todo!(), } } } diff --git a/src/mixin.rs b/src/mixin.rs index 7051896..af3f562 100644 --- a/src/mixin.rs +++ b/src/mixin.rs @@ -94,7 +94,7 @@ impl Mixin { while let Some(expr) = eat_expr(&mut self.body, &self.scope, super_selector)? { match expr { Expr::Style(s) => stmts.push(Stmt::Style(s)), - Expr::Include(..) | Expr::MixinDecl(..) | Expr::Debug(..) | Expr::Warn(..) => { + Expr::Include(..) | Expr::MixinDecl(..) | Expr::FunctionDecl(..) | Expr::Debug(..) | Expr::Warn(..) => { todo!() } Expr::Selector(selector) => {