Refactor how scope is handled and basic mixin parsing

This commit is contained in:
ConnorSkees 2020-01-12 17:44:49 -05:00
parent 9b4228f14c
commit 107a7d996e
3 changed files with 81 additions and 33 deletions

View File

@ -38,6 +38,7 @@ use crate::css::Css;
use crate::error::SassError; use crate::error::SassError;
use crate::format::PrettyPrinter; use crate::format::PrettyPrinter;
use crate::lexer::Lexer; use crate::lexer::Lexer;
use crate::mixin::{CallArgs, FuncArgs, Mixin};
use crate::selector::{Attribute, Selector}; use crate::selector::{Attribute, Selector};
use crate::style::Style; use crate::style::Style;
use crate::units::Unit; use crate::units::Unit;
@ -49,6 +50,7 @@ mod error;
mod format; mod format;
mod imports; mod imports;
mod lexer; mod lexer;
mod mixin;
mod selector; mod selector;
mod style; mod style;
mod units; mod units;
@ -115,12 +117,14 @@ pub enum Stmt {
/// Represents a single rule set. Rule sets can contain other rule sets /// Represents a single rule set. Rule sets can contain other rule sets
/// ///
/// ```scss
/// a { /// a {
/// color: blue; /// color: blue;
/// b { /// b {
/// color: red; /// color: red;
/// } /// }
/// } /// }
/// ```
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct RuleSet { pub struct RuleSet {
selector: Selector, selector: Selector,
@ -148,7 +152,7 @@ enum Expr {
impl StyleSheet { impl StyleSheet {
pub fn new(input: &str) -> SassResult<StyleSheet> { pub fn new(input: &str) -> SassResult<StyleSheet> {
StyleSheetParser { StyleSheetParser {
global_variables: HashMap::new(), global_scope: Scope::new(),
lexer: Lexer::new(input).peekable(), lexer: Lexer::new(input).peekable(),
rules: Vec::new(), rules: Vec::new(),
scope: 0, scope: 0,
@ -159,7 +163,7 @@ impl StyleSheet {
pub fn from_path<P: AsRef<Path> + Into<String>>(p: P) -> SassResult<StyleSheet> { pub fn from_path<P: AsRef<Path> + Into<String>>(p: P) -> SassResult<StyleSheet> {
StyleSheetParser { StyleSheetParser {
global_variables: HashMap::new(), global_scope: Scope::new(),
lexer: Lexer::new(&fs::read_to_string(p.as_ref())?).peekable(), lexer: Lexer::new(&fs::read_to_string(p.as_ref())?).peekable(),
rules: Vec::new(), rules: Vec::new(),
scope: 0, scope: 0,
@ -190,13 +194,34 @@ impl StyleSheet {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct StyleSheetParser<'a> { struct StyleSheetParser<'a> {
global_variables: HashMap<String, Vec<Token>>, global_scope: Scope,
lexer: Peekable<Lexer<'a>>, lexer: Peekable<Lexer<'a>>,
rules: Vec<Stmt>, rules: Vec<Stmt>,
scope: u32, scope: u32,
file: String, file: String,
} }
#[derive(Debug, Clone, std::default::Default)]
pub struct Scope {
pub vars: HashMap<String, Vec<Token>>,
pub mixins: HashMap<String, Mixin>,
}
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> { impl<'a> StyleSheetParser<'a> {
fn parse_toplevel(mut self) -> SassResult<StyleSheet> { fn parse_toplevel(mut self) -> SassResult<StyleSheet> {
let mut rules = Vec::new(); let mut rules = Vec::new();
@ -209,7 +234,7 @@ impl<'a> StyleSheetParser<'a> {
| TokenKind::Symbol(Symbol::Colon) | TokenKind::Symbol(Symbol::Colon)
| TokenKind::Symbol(Symbol::Mul) | TokenKind::Symbol(Symbol::Mul)
| TokenKind::Symbol(Symbol::Period) => rules.extend( | 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(_) => { TokenKind::Whitespace(_) => {
self.lexer.next(); self.lexer.next();
@ -231,7 +256,7 @@ impl<'a> StyleSheetParser<'a> {
self.error(pos, "unexpected variable use at toplevel"); self.error(pos, "unexpected variable use at toplevel");
} }
let val = self.eat_variable_value(); let val = self.eat_variable_value();
self.global_variables.insert(name, val); self.global_scope.vars.insert(name, val);
} }
TokenKind::MultilineComment(comment) => { TokenKind::MultilineComment(comment) => {
self.lexer.next(); self.lexer.next();
@ -250,6 +275,30 @@ impl<'a> StyleSheetParser<'a> {
Ok(StyleSheet { rules }) 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) { fn eat_at_rule(&mut self) {
if let Some(Token { if let Some(Token {
kind: TokenKind::AtRule(ref rule), kind: TokenKind::AtRule(ref rule),
@ -287,6 +336,7 @@ impl<'a> StyleSheetParser<'a> {
.collect::<String>(); .collect::<String>();
self.debug(pos, &message); self.debug(pos, &message);
} }
AtRule::Mixin => self.eat_mixin(),
_ => todo!("encountered unimplemented at rule"), _ => todo!("encountered unimplemented at rule"),
} }
} }
@ -307,7 +357,7 @@ impl<'a> StyleSheetParser<'a> {
} = tok } = tok
{ {
iter2.extend( iter2.extend(
self.global_variables self.global_scope.vars
.get(name) .get(name)
.unwrap_or_else(|| self.error(pos, "Undefined variable")) .unwrap_or_else(|| self.error(pos, "Undefined variable"))
.clone(), .clone(),
@ -324,15 +374,15 @@ impl<'a> StyleSheetParser<'a> {
fn eat_rules( fn eat_rules(
&mut self, &mut self,
super_selector: &Selector, super_selector: &Selector,
vars: &mut HashMap<String, Vec<Token>>, scope: &mut Scope,
) -> Vec<Stmt> { ) -> Vec<Stmt> {
let mut stmts = Vec::new(); 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 { match tok {
Expr::Style(s) => stmts.push(Stmt::Style(s)), Expr::Style(s) => stmts.push(Stmt::Style(s)),
Expr::Selector(s) => { Expr::Selector(s) => {
self.scope += 1; 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 { stmts.push(Stmt::RuleSet(RuleSet {
super_selector: super_selector.clone(), super_selector: super_selector.clone(),
selector: s, selector: s,
@ -345,10 +395,10 @@ impl<'a> StyleSheetParser<'a> {
} }
Expr::VariableDecl(name, val) => { Expr::VariableDecl(name, val) => {
if self.scope == 0 { if self.scope == 0 {
vars.insert(name.clone(), val.clone()); scope.vars.insert(name.clone(), val.clone());
self.global_variables.insert(name, val); self.global_scope.vars.insert(name, val);
} else { } else {
vars.insert(name, val); scope.vars.insert(name, val);
} }
} }
Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s)), Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s)),
@ -359,7 +409,7 @@ impl<'a> StyleSheetParser<'a> {
fn eat_expr( fn eat_expr(
&mut self, &mut self,
vars: &HashMap<String, Vec<Token>>, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> Result<Expr, ()> { ) -> Result<Expr, ()> {
let mut values = Vec::with_capacity(5); let mut values = Vec::with_capacity(5);
@ -368,7 +418,7 @@ impl<'a> StyleSheetParser<'a> {
TokenKind::Symbol(Symbol::SemiColon) | TokenKind::Symbol(Symbol::CloseBrace) => { TokenKind::Symbol(Symbol::SemiColon) | TokenKind::Symbol(Symbol::CloseBrace) => {
self.lexer.next(); self.lexer.next();
self.devour_whitespace(); 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) => { TokenKind::Symbol(Symbol::OpenBrace) => {
self.lexer.next(); self.lexer.next();
@ -376,7 +426,7 @@ impl<'a> StyleSheetParser<'a> {
return Ok(Expr::Selector(Selector::from_tokens( return Ok(Expr::Selector(Selector::from_tokens(
values.iter().peekable(), values.iter().peekable(),
super_selector, super_selector,
vars, scope,
))); )));
} }
TokenKind::Variable(_) => { TokenKind::Variable(_) => {
@ -493,7 +543,7 @@ impl<'a> StyleSheetParser<'a> {
} }
fn main() -> SassResult<()> { 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")?; let s = StyleSheet::from_path("input.scss")?;
// dbg!(s); // dbg!(s);
// s.pretty_print(&mut stdout)?; // 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_following_el, "a + b {\n color: red;\n}\n");
test!(selector_el_preceding_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_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!( test!(
selector_el_pseudo_descendant, selector_el_pseudo_descendant,
"a :pseudo {\n color: red;\n}\n" "a :pseudo {\n color: red;\n}\n"

View File

@ -1,6 +1,5 @@
use crate::common::Symbol; use crate::common::Symbol;
use crate::{Token, TokenKind}; use crate::{Scope, Token, TokenKind};
use std::collections::HashMap;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::iter::Peekable; use std::iter::Peekable;
use std::slice::Iter; use std::slice::Iter;
@ -180,7 +179,7 @@ mod test_selector_display {
struct SelectorParser<'a> { struct SelectorParser<'a> {
tokens: Peekable<Iter<'a, Token>>, tokens: Peekable<Iter<'a, Token>>,
super_selector: &'a Selector, super_selector: &'a Selector,
vars: &'a HashMap<String, Vec<Token>>, scope: &'a Scope,
} }
/// Methods to handle dealing with interpolation /// Methods to handle dealing with interpolation
@ -250,7 +249,7 @@ impl<'a> SelectorParser<'a> {
let mut val = Vec::with_capacity(25); let mut val = Vec::with_capacity(25);
let v = match variable { let v = match variable {
TokenKind::Variable(ref v) => { 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"), _ => todo!("expected variable"),
} }
@ -270,12 +269,12 @@ impl<'a> SelectorParser<'a> {
const fn new( const fn new(
tokens: Peekable<Iter<'a, Token>>, tokens: Peekable<Iter<'a, Token>>,
super_selector: &'a Selector, super_selector: &'a Selector,
vars: &'a HashMap<String, Vec<Token>>, scope: &'a Scope,
) -> SelectorParser<'a> { ) -> SelectorParser<'a> {
SelectorParser { SelectorParser {
tokens, tokens,
super_selector, super_selector,
vars, scope,
} }
} }
@ -378,9 +377,9 @@ impl Selector {
pub fn from_tokens<'a>( pub fn from_tokens<'a>(
tokens: Peekable<Iter<'a, Token>>, tokens: Peekable<Iter<'a, Token>>,
super_selector: &'a Selector, super_selector: &'a Selector,
vars: &'a HashMap<String, Vec<Token>>, scope: &'a Scope,
) -> Selector { ) -> Selector {
SelectorParser::new(tokens, super_selector, vars).all_selectors() SelectorParser::new(tokens, super_selector, scope).all_selectors()
} }
pub fn zip(self, other: Selector) -> Selector { pub fn zip(self, other: Selector) -> Selector {

View File

@ -1,6 +1,5 @@
use crate::common::Symbol; use crate::common::Symbol;
use crate::{Token, TokenKind}; use crate::{Scope, Token, TokenKind};
use std::collections::HashMap;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::iter::Peekable; use std::iter::Peekable;
use std::slice::Iter; use std::slice::Iter;
@ -19,30 +18,30 @@ impl Display for Style {
} }
impl Style { impl Style {
pub fn from_tokens(tokens: &[Token], vars: &HashMap<String, Vec<Token>>) -> Result<Self, ()> { pub fn from_tokens(tokens: &[Token], scope: &Scope) -> Result<Self, ()> {
Ok(StyleParser::new(tokens, vars)?.parse()) Ok(StyleParser::new(tokens, scope)?.parse())
} }
} }
struct StyleParser<'a> { struct StyleParser<'a> {
tokens: Peekable<Iter<'a, Token>>, tokens: Peekable<Iter<'a, Token>>,
vars: &'a HashMap<String, Vec<Token>>, scope: &'a Scope,
} }
impl<'a> StyleParser<'a> { impl<'a> StyleParser<'a> {
fn new(tokens: &'a [Token], vars: &'a HashMap<String, Vec<Token>>) -> Result<Self, ()> { fn new(tokens: &'a [Token], scope: &'a Scope) -> Result<Self, ()> {
if tokens.is_empty() { if tokens.is_empty() {
return Err(()); return Err(());
} }
let tokens = tokens.iter().peekable(); let tokens = tokens.iter().peekable();
Ok(StyleParser { tokens, vars }) Ok(StyleParser { tokens, scope })
} }
fn deref_variable(&mut self, variable: &TokenKind) -> String { fn deref_variable(&mut self, variable: &TokenKind) -> String {
let mut val = String::with_capacity(25); let mut val = String::with_capacity(25);
let mut v = match variable { let mut v = match variable {
TokenKind::Variable(ref v) => { 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"), _ => panic!("expected variable"),
} }