Refactor how scope is handled and basic mixin parsing
This commit is contained in:
parent
9b4228f14c
commit
107a7d996e
84
src/main.rs
84
src/main.rs
@ -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"
|
||||||
|
@ -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 {
|
||||||
|
15
src/style.rs
15
src/style.rs
@ -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"),
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user