From 107a7d996e0749d65cfbcab121165b068a117223 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Sun, 12 Jan 2020 17:44:49 -0500 Subject: [PATCH] Refactor how scope is handled and basic mixin parsing --- src/main.rs | 84 +++++++++++++++++++++++++++++++++++++++---------- src/selector.rs | 15 +++++---- src/style.rs | 15 +++++---- 3 files changed, 81 insertions(+), 33 deletions(-) diff --git a/src/main.rs b/src/main.rs index d6e80ee..aa16d86 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,6 +38,7 @@ use crate::css::Css; use crate::error::SassError; use crate::format::PrettyPrinter; use crate::lexer::Lexer; +use crate::mixin::{CallArgs, FuncArgs, Mixin}; use crate::selector::{Attribute, Selector}; use crate::style::Style; use crate::units::Unit; @@ -49,6 +50,7 @@ mod error; mod format; mod imports; mod lexer; +mod mixin; mod selector; mod style; mod units; @@ -115,12 +117,14 @@ pub enum Stmt { /// Represents a single rule set. Rule sets can contain other rule sets /// +/// ```scss /// a { /// color: blue; /// b { /// color: red; /// } /// } +/// ``` #[derive(Clone, Debug, Eq, PartialEq)] pub struct RuleSet { selector: Selector, @@ -148,7 +152,7 @@ enum Expr { impl StyleSheet { pub fn new(input: &str) -> SassResult { StyleSheetParser { - global_variables: HashMap::new(), + global_scope: Scope::new(), lexer: Lexer::new(input).peekable(), rules: Vec::new(), scope: 0, @@ -159,7 +163,7 @@ impl StyleSheet { pub fn from_path + Into>(p: P) -> SassResult { StyleSheetParser { - global_variables: HashMap::new(), + global_scope: Scope::new(), lexer: Lexer::new(&fs::read_to_string(p.as_ref())?).peekable(), rules: Vec::new(), scope: 0, @@ -190,13 +194,34 @@ impl StyleSheet { #[derive(Debug, Clone)] struct StyleSheetParser<'a> { - global_variables: HashMap>, + global_scope: Scope, lexer: Peekable>, rules: Vec, scope: u32, file: String, } +#[derive(Debug, Clone, std::default::Default)] +pub struct Scope { + pub vars: HashMap>, + pub mixins: HashMap, +} + +impl Scope { + #[must_use] + pub fn new() -> Self { + Self { + vars: HashMap::new(), + mixins: HashMap::new(), + } + } + + pub fn merge(&mut self, other: Scope) { + self.vars.extend(other.vars); + self.mixins.extend(other.mixins); + } +} + impl<'a> StyleSheetParser<'a> { fn parse_toplevel(mut self) -> SassResult { let mut rules = Vec::new(); @@ -209,7 +234,7 @@ impl<'a> StyleSheetParser<'a> { | TokenKind::Symbol(Symbol::Colon) | TokenKind::Symbol(Symbol::Mul) | TokenKind::Symbol(Symbol::Period) => rules.extend( - self.eat_rules(&Selector(Vec::new()), &mut self.global_variables.clone()), + self.eat_rules(&Selector(Vec::new()), &mut self.global_scope.clone()), ), TokenKind::Whitespace(_) => { self.lexer.next(); @@ -231,7 +256,7 @@ impl<'a> StyleSheetParser<'a> { self.error(pos, "unexpected variable use at toplevel"); } let val = self.eat_variable_value(); - self.global_variables.insert(name, val); + self.global_scope.vars.insert(name, val); } TokenKind::MultilineComment(comment) => { self.lexer.next(); @@ -250,6 +275,30 @@ impl<'a> StyleSheetParser<'a> { Ok(StyleSheet { rules }) } + fn eat_mixin(&mut self) { + let Token { pos, .. } = self.lexer.next().unwrap(); + self.devour_whitespace(); + let name = if let Some(Token { kind: TokenKind::Ident(s), .. }) = self.lexer.next() { + s + } else { + self.error(pos, "expected identifier after mixin declaration") + }; + self.devour_whitespace(); + let args = match self.lexer.next() { + Some(Token { kind: TokenKind::Symbol(Symbol::OpenParen), .. }) => self.eat_func_args(), + Some(Token { kind: TokenKind::Symbol(Symbol::OpenBracket), .. }) => FuncArgs::new(), + _ => self.error(pos, "expected `(` or `{`"), + }; + + let body = self.lexer.by_ref().take_while(|x| x.kind != TokenKind::Symbol(Symbol::CloseBrace)).collect(); + + self.global_scope.mixins.insert(name, Mixin::new(self.global_scope.clone(), args, body)); + } + + fn eat_func_args(&mut self) -> FuncArgs { + todo!() + } + fn eat_at_rule(&mut self) { if let Some(Token { kind: TokenKind::AtRule(ref rule), @@ -287,6 +336,7 @@ impl<'a> StyleSheetParser<'a> { .collect::(); self.debug(pos, &message); } + AtRule::Mixin => self.eat_mixin(), _ => todo!("encountered unimplemented at rule"), } } @@ -307,7 +357,7 @@ impl<'a> StyleSheetParser<'a> { } = tok { iter2.extend( - self.global_variables + self.global_scope.vars .get(name) .unwrap_or_else(|| self.error(pos, "Undefined variable")) .clone(), @@ -324,15 +374,15 @@ impl<'a> StyleSheetParser<'a> { fn eat_rules( &mut self, super_selector: &Selector, - vars: &mut HashMap>, + scope: &mut Scope, ) -> Vec { let mut stmts = Vec::new(); - while let Ok(tok) = self.eat_expr(vars, super_selector) { + while let Ok(tok) = self.eat_expr(scope, super_selector) { match tok { Expr::Style(s) => stmts.push(Stmt::Style(s)), Expr::Selector(s) => { self.scope += 1; - let rules = self.eat_rules(&super_selector.clone().zip(s.clone()), vars); + let rules = self.eat_rules(&super_selector.clone().zip(s.clone()), scope); stmts.push(Stmt::RuleSet(RuleSet { super_selector: super_selector.clone(), selector: s, @@ -345,10 +395,10 @@ impl<'a> StyleSheetParser<'a> { } Expr::VariableDecl(name, val) => { if self.scope == 0 { - vars.insert(name.clone(), val.clone()); - self.global_variables.insert(name, val); + scope.vars.insert(name.clone(), val.clone()); + self.global_scope.vars.insert(name, val); } else { - vars.insert(name, val); + scope.vars.insert(name, val); } } Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s)), @@ -359,7 +409,7 @@ impl<'a> StyleSheetParser<'a> { fn eat_expr( &mut self, - vars: &HashMap>, + scope: &Scope, super_selector: &Selector, ) -> Result { let mut values = Vec::with_capacity(5); @@ -368,7 +418,7 @@ impl<'a> StyleSheetParser<'a> { TokenKind::Symbol(Symbol::SemiColon) | TokenKind::Symbol(Symbol::CloseBrace) => { self.lexer.next(); self.devour_whitespace(); - return Ok(Expr::Style(Style::from_tokens(&values, vars)?)); + return Ok(Expr::Style(Style::from_tokens(&values, scope)?)); } TokenKind::Symbol(Symbol::OpenBrace) => { self.lexer.next(); @@ -376,7 +426,7 @@ impl<'a> StyleSheetParser<'a> { return Ok(Expr::Selector(Selector::from_tokens( values.iter().peekable(), super_selector, - vars, + scope, ))); } TokenKind::Variable(_) => { @@ -493,7 +543,7 @@ impl<'a> StyleSheetParser<'a> { } fn main() -> SassResult<()> { - let mut stdout = std::io::stdout(); + let mut stdout = std::io::BufWriter::new(std::io::stdout()); let s = StyleSheet::from_path("input.scss")?; // dbg!(s); // s.pretty_print(&mut stdout)?; @@ -608,7 +658,7 @@ mod test_css { test!(selector_el_following_el, "a + b {\n color: red;\n}\n"); test!(selector_el_preceding_el, "a ~ b {\n color: red;\n}\n"); test!(selector_pseudo, ":pseudo {\n color: red;\n}\n"); - test!(selector_el_pseudo_and, "a:pseudo {\n color: red;\n}\n"); + test!(selector_el_and_pseudo, "a:pseudo {\n color: red;\n}\n"); test!( selector_el_pseudo_descendant, "a :pseudo {\n color: red;\n}\n" diff --git a/src/selector.rs b/src/selector.rs index 3ec4361..2531681 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -1,6 +1,5 @@ use crate::common::Symbol; -use crate::{Token, TokenKind}; -use std::collections::HashMap; +use crate::{Scope, Token, TokenKind}; use std::fmt::{self, Display}; use std::iter::Peekable; use std::slice::Iter; @@ -180,7 +179,7 @@ mod test_selector_display { struct SelectorParser<'a> { tokens: Peekable>, super_selector: &'a Selector, - vars: &'a HashMap>, + scope: &'a Scope, } /// Methods to handle dealing with interpolation @@ -250,7 +249,7 @@ impl<'a> SelectorParser<'a> { let mut val = Vec::with_capacity(25); let v = match variable { TokenKind::Variable(ref v) => { - self.vars.get(v).expect("todo! expected variable to exist") + self.scope.vars.get(v).expect("todo! expected variable to exist") } _ => todo!("expected variable"), } @@ -270,12 +269,12 @@ impl<'a> SelectorParser<'a> { const fn new( tokens: Peekable>, super_selector: &'a Selector, - vars: &'a HashMap>, + scope: &'a Scope, ) -> SelectorParser<'a> { SelectorParser { tokens, super_selector, - vars, + scope, } } @@ -378,9 +377,9 @@ impl Selector { pub fn from_tokens<'a>( tokens: Peekable>, super_selector: &'a Selector, - vars: &'a HashMap>, + scope: &'a Scope, ) -> Selector { - SelectorParser::new(tokens, super_selector, vars).all_selectors() + SelectorParser::new(tokens, super_selector, scope).all_selectors() } pub fn zip(self, other: Selector) -> Selector { diff --git a/src/style.rs b/src/style.rs index 630479d..78f6b3a 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,6 +1,5 @@ use crate::common::Symbol; -use crate::{Token, TokenKind}; -use std::collections::HashMap; +use crate::{Scope, Token, TokenKind}; use std::fmt::{self, Display}; use std::iter::Peekable; use std::slice::Iter; @@ -19,30 +18,30 @@ impl Display for Style { } impl Style { - pub fn from_tokens(tokens: &[Token], vars: &HashMap>) -> Result { - Ok(StyleParser::new(tokens, vars)?.parse()) + pub fn from_tokens(tokens: &[Token], scope: &Scope) -> Result { + Ok(StyleParser::new(tokens, scope)?.parse()) } } struct StyleParser<'a> { tokens: Peekable>, - vars: &'a HashMap>, + scope: &'a Scope, } impl<'a> StyleParser<'a> { - fn new(tokens: &'a [Token], vars: &'a HashMap>) -> Result { + fn new(tokens: &'a [Token], scope: &'a Scope) -> Result { if tokens.is_empty() { return Err(()); } let tokens = tokens.iter().peekable(); - Ok(StyleParser { tokens, vars }) + Ok(StyleParser { tokens, scope }) } fn deref_variable(&mut self, variable: &TokenKind) -> String { let mut val = String::with_capacity(25); let mut v = match variable { TokenKind::Variable(ref v) => { - self.vars.get(v).expect("todo! expected variable to exist") + self.scope.vars.get(v).expect("todo! expected variable to exist") } _ => panic!("expected variable"), }