2020-01-04 22:55:04 -05:00
|
|
|
#![warn(
|
|
|
|
clippy::all,
|
|
|
|
clippy::restriction,
|
|
|
|
clippy::pedantic,
|
|
|
|
clippy::nursery,
|
|
|
|
// clippy::cargo
|
|
|
|
)]
|
|
|
|
#![deny(missing_debug_implementations)]
|
|
|
|
#![allow(
|
|
|
|
dead_code,
|
|
|
|
clippy::pub_enum_variant_names,
|
|
|
|
clippy::implicit_return,
|
|
|
|
clippy::use_self,
|
|
|
|
clippy::missing_docs_in_private_items,
|
|
|
|
clippy::todo,
|
|
|
|
clippy::dbg_macro,
|
|
|
|
clippy::unreachable,
|
|
|
|
clippy::wildcard_enum_match_arm,
|
|
|
|
clippy::option_expect_used,
|
|
|
|
clippy::panic,
|
|
|
|
clippy::unused_self,
|
|
|
|
clippy::too_many_lines,
|
|
|
|
clippy::integer_arithmetic,
|
|
|
|
clippy::missing_errors_doc
|
|
|
|
)]
|
|
|
|
use std::collections::HashMap;
|
2020-01-06 17:06:37 -05:00
|
|
|
use std::fmt::{self, Display};
|
2020-01-04 22:55:04 -05:00
|
|
|
use std::fs;
|
|
|
|
use std::io;
|
2020-01-06 17:06:37 -05:00
|
|
|
use std::iter::{Iterator, Peekable};
|
2020-01-04 22:55:04 -05:00
|
|
|
|
2020-01-06 00:40:29 -05:00
|
|
|
use crate::common::{Keyword, Op, Pos, Symbol, Whitespace};
|
2020-01-05 12:45:51 -05:00
|
|
|
use crate::css::Css;
|
2020-01-06 18:55:51 -05:00
|
|
|
use crate::error::SassError;
|
2020-01-04 22:55:04 -05:00
|
|
|
use crate::format::PrettyPrinter;
|
|
|
|
use crate::lexer::Lexer;
|
|
|
|
use crate::selector::Selector;
|
2020-01-06 16:50:16 -05:00
|
|
|
use crate::style::Style;
|
2020-01-04 22:55:04 -05:00
|
|
|
use crate::units::Unit;
|
|
|
|
|
|
|
|
mod color;
|
|
|
|
mod common;
|
2020-01-05 12:45:51 -05:00
|
|
|
mod css;
|
2020-01-04 22:55:04 -05:00
|
|
|
mod error;
|
|
|
|
mod format;
|
|
|
|
mod lexer;
|
|
|
|
mod selector;
|
2020-01-06 16:50:16 -05:00
|
|
|
mod style;
|
2020-01-04 22:55:04 -05:00
|
|
|
mod units;
|
|
|
|
|
2020-01-06 18:55:51 -05:00
|
|
|
type SassResult<T> = Result<T, SassError>;
|
|
|
|
|
2020-01-04 22:55:04 -05:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
pub enum TokenKind {
|
|
|
|
Ident(String),
|
|
|
|
Symbol(Symbol),
|
|
|
|
Function(String, Vec<String>),
|
|
|
|
AtRule(String),
|
|
|
|
Keyword(Keyword),
|
|
|
|
Number(String),
|
|
|
|
Unit(Unit),
|
|
|
|
Whitespace(Whitespace),
|
|
|
|
Variable(String),
|
|
|
|
Selector(Selector),
|
|
|
|
Style(Vec<Token>),
|
2020-01-06 00:40:29 -05:00
|
|
|
Op(Op),
|
2020-01-05 19:21:58 -05:00
|
|
|
// todo! preserve multi-line comments
|
|
|
|
MultilineComment(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Display for TokenKind {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
TokenKind::Ident(s) | TokenKind::Number(s) | TokenKind::AtRule(s) => write!(f, "{}", s),
|
|
|
|
TokenKind::Symbol(s) => write!(f, "{}", s),
|
2020-01-06 00:40:29 -05:00
|
|
|
TokenKind::Op(s) => write!(f, "{}", s),
|
2020-01-05 19:21:58 -05:00
|
|
|
TokenKind::Unit(s) => write!(f, "{}", s),
|
|
|
|
TokenKind::Whitespace(s) => write!(f, "{}", s),
|
|
|
|
TokenKind::Selector(s) => write!(f, "{}", s),
|
|
|
|
TokenKind::Function(name, args) => write!(f, "{}({})", name, args.join(", ")),
|
|
|
|
TokenKind::Keyword(kw) => write!(f, "{}", kw),
|
|
|
|
TokenKind::MultilineComment(s) => write!(f, "/*{}*/", s),
|
|
|
|
TokenKind::Variable(s) => write!(f, "${}", s),
|
|
|
|
TokenKind::Style(_) => panic!("TokenKind should not be used to format styles"),
|
|
|
|
}
|
|
|
|
}
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
pub struct Token {
|
|
|
|
pos: Pos,
|
|
|
|
pub kind: TokenKind,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
|
|
pub struct StyleSheet {
|
|
|
|
rules: Vec<Stmt>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
pub enum Stmt {
|
|
|
|
Style(Style),
|
|
|
|
RuleSet(RuleSet),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
pub struct RuleSet {
|
|
|
|
selector: Selector,
|
|
|
|
rules: Vec<Stmt>,
|
|
|
|
// potential optimization: we don't *need* to own the selector
|
2020-01-05 12:37:50 -05:00
|
|
|
super_selector: Selector,
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
enum Expr {
|
|
|
|
Style(Style),
|
|
|
|
Selector(Selector),
|
2020-01-05 19:21:58 -05:00
|
|
|
VariableDecl(String, Vec<Token>),
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl StyleSheet {
|
|
|
|
#[must_use]
|
2020-01-06 18:55:51 -05:00
|
|
|
pub fn new(input: &str) -> SassResult<StyleSheet> {
|
2020-01-04 22:55:04 -05:00
|
|
|
StyleSheetParser {
|
2020-01-05 19:21:58 -05:00
|
|
|
global_variables: HashMap::new(),
|
2020-01-04 22:55:04 -05:00
|
|
|
lexer: Lexer::new(input).peekable(),
|
|
|
|
rules: Vec::new(),
|
2020-01-06 18:55:51 -05:00
|
|
|
scope: 0,
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
.parse_toplevel()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn pretty_print<W: std::io::Write>(&self, buf: W) -> io::Result<()> {
|
|
|
|
PrettyPrinter::new(buf).pretty_print(self)
|
|
|
|
}
|
2020-01-05 12:37:02 -05:00
|
|
|
|
|
|
|
pub fn pretty_print_selectors<W: std::io::Write>(&self, buf: W) -> io::Result<()> {
|
|
|
|
PrettyPrinter::new(buf).pretty_print_preserve_super_selectors(self)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn print_as_css<W: std::io::Write>(self, buf: &mut W) -> io::Result<()> {
|
|
|
|
Css::from_stylesheet(self).pretty_print(buf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 22:55:04 -05:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
struct StyleSheetParser<'a> {
|
2020-01-05 19:21:58 -05:00
|
|
|
global_variables: HashMap<String, Vec<Token>>,
|
2020-01-04 22:55:04 -05:00
|
|
|
lexer: Peekable<Lexer<'a>>,
|
|
|
|
rules: Vec<Stmt>,
|
2020-01-06 18:55:51 -05:00
|
|
|
scope: u32,
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> StyleSheetParser<'a> {
|
2020-01-06 18:55:51 -05:00
|
|
|
fn parse_toplevel(&mut self) -> SassResult<StyleSheet> {
|
2020-01-04 22:55:04 -05:00
|
|
|
let mut rules = Vec::new();
|
|
|
|
while let Some(tok) = self.lexer.peek() {
|
2020-01-05 19:21:58 -05:00
|
|
|
match tok.kind.clone() {
|
2020-01-04 22:55:04 -05:00
|
|
|
TokenKind::Ident(_)
|
|
|
|
| TokenKind::Selector(_)
|
|
|
|
| TokenKind::Symbol(Symbol::Hash)
|
|
|
|
| TokenKind::Symbol(Symbol::Colon)
|
|
|
|
| TokenKind::Symbol(Symbol::Mul)
|
2020-01-05 19:21:58 -05:00
|
|
|
| TokenKind::Symbol(Symbol::Period) => rules
|
|
|
|
.extend(self.eat_rules(&Selector::None, &mut self.global_variables.clone())),
|
2020-01-04 22:55:04 -05:00
|
|
|
TokenKind::Whitespace(_) | TokenKind::Symbol(_) => {
|
|
|
|
self.lexer.next();
|
|
|
|
continue;
|
|
|
|
}
|
2020-01-05 19:21:58 -05:00
|
|
|
TokenKind::Variable(name) => {
|
|
|
|
self.lexer.next();
|
|
|
|
self.devour_whitespace();
|
|
|
|
if self
|
|
|
|
.lexer
|
|
|
|
.next()
|
|
|
|
.expect("expected something after variable")
|
2020-01-06 18:55:51 -05:00
|
|
|
// .unwrap_or(Err(SassError::new("expected value after variable", this_tok.pos))?)
|
2020-01-05 19:21:58 -05:00
|
|
|
.kind
|
|
|
|
!= TokenKind::Symbol(Symbol::Colon)
|
|
|
|
{
|
|
|
|
panic!("unexpected variable use at toplevel")
|
|
|
|
}
|
|
|
|
let val = self.eat_variable_value();
|
|
|
|
self.global_variables.insert(name, val);
|
|
|
|
}
|
2020-01-04 22:55:04 -05:00
|
|
|
_ => todo!("unexpected toplevel token"),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
StyleSheet { rules }
|
|
|
|
}
|
|
|
|
|
2020-01-05 19:21:58 -05:00
|
|
|
fn eat_variable_value(&mut self) -> Vec<Token> {
|
|
|
|
self.devour_whitespace();
|
|
|
|
self.lexer
|
|
|
|
.by_ref()
|
|
|
|
.take_while(|x| x.kind != TokenKind::Symbol(Symbol::SemiColon))
|
|
|
|
.collect::<Vec<Token>>()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn eat_rules(
|
|
|
|
&mut self,
|
|
|
|
super_selector: &Selector,
|
|
|
|
vars: &mut HashMap<String, Vec<Token>>,
|
|
|
|
) -> Vec<Stmt> {
|
2020-01-04 22:55:04 -05:00
|
|
|
let mut stmts = Vec::new();
|
2020-01-05 19:21:58 -05:00
|
|
|
while let Ok(tok) = self.eat_expr(vars) {
|
2020-01-04 22:55:04 -05:00
|
|
|
match tok {
|
|
|
|
Expr::Style(s) => stmts.push(Stmt::Style(s)),
|
|
|
|
Expr::Selector(s) => {
|
2020-01-05 19:21:58 -05:00
|
|
|
let rules = self.eat_rules(&super_selector.clone().zip(s.clone()), vars);
|
2020-01-04 22:55:04 -05:00
|
|
|
stmts.push(Stmt::RuleSet(RuleSet {
|
|
|
|
super_selector: super_selector.clone(),
|
|
|
|
selector: s,
|
|
|
|
rules,
|
|
|
|
}));
|
|
|
|
}
|
2020-01-05 19:21:58 -05:00
|
|
|
Expr::VariableDecl(name, val) => {
|
|
|
|
vars.insert(name, val);
|
|
|
|
}
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
stmts
|
|
|
|
}
|
|
|
|
|
2020-01-05 19:21:58 -05:00
|
|
|
fn eat_expr(&mut self, vars: &HashMap<String, Vec<Token>>) -> Result<Expr, ()> {
|
2020-01-04 22:55:04 -05:00
|
|
|
let mut values = Vec::with_capacity(5);
|
|
|
|
while let Some(tok) = self.lexer.next() {
|
|
|
|
match tok.kind {
|
|
|
|
TokenKind::Symbol(Symbol::SemiColon) | TokenKind::Symbol(Symbol::CloseBrace) => {
|
|
|
|
self.devour_whitespace();
|
2020-01-05 19:21:58 -05:00
|
|
|
return Ok(Expr::Style(Style::from_tokens(&values, vars)?));
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|
|
|
|
TokenKind::Symbol(Symbol::OpenBrace) => {
|
|
|
|
self.devour_whitespace();
|
|
|
|
return Ok(Expr::Selector(Selector::from_tokens(
|
|
|
|
values.iter().peekable(),
|
|
|
|
)));
|
|
|
|
}
|
2020-01-05 19:21:58 -05:00
|
|
|
TokenKind::Variable(name) => {
|
|
|
|
if self
|
|
|
|
.lexer
|
|
|
|
.next()
|
|
|
|
.expect("expected something after variable")
|
|
|
|
.kind
|
|
|
|
== TokenKind::Symbol(Symbol::Colon)
|
|
|
|
{
|
|
|
|
self.devour_whitespace();
|
|
|
|
return Ok(Expr::VariableDecl(name, self.eat_variable_value()));
|
|
|
|
} else {
|
|
|
|
values.push(Token {
|
|
|
|
kind: TokenKind::Variable(name),
|
|
|
|
pos: tok.pos,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2020-01-04 22:55:04 -05:00
|
|
|
_ => values.push(tok.clone()),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
Err(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn devour_whitespace(&mut self) {
|
|
|
|
while let Some(tok) = self.lexer.peek() {
|
|
|
|
match tok.kind {
|
|
|
|
TokenKind::Whitespace(_) => {
|
|
|
|
self.lexer.next();
|
|
|
|
}
|
|
|
|
_ => break,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-06 18:55:51 -05:00
|
|
|
fn main() -> SassResult<()> {
|
2020-01-04 22:55:04 -05:00
|
|
|
let input = fs::read_to_string("input.scss")?;
|
|
|
|
let mut stdout = std::io::stdout();
|
2020-01-06 18:55:51 -05:00
|
|
|
let s = StyleSheet::new(&input)?;
|
2020-01-04 22:55:04 -05:00
|
|
|
// dbg!(s);
|
2020-01-05 12:52:50 -05:00
|
|
|
// s.pretty_print(&mut stdout)?;
|
|
|
|
// s.pretty_print_selectors(&mut stdout)?;
|
|
|
|
s.print_as_css(&mut stdout)?;
|
|
|
|
// dbg!(Css::from_stylesheet(s));
|
2020-01-04 22:55:04 -05:00
|
|
|
// println!("{}", s);
|
|
|
|
// drop(input);
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-01-05 20:26:34 -05:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test_css {
|
|
|
|
use super::StyleSheet;
|
|
|
|
macro_rules! test {
|
|
|
|
($func:ident, $input:literal) => {
|
|
|
|
#[test]
|
|
|
|
fn $func() {
|
|
|
|
let mut buf = Vec::new();
|
2020-01-06 18:55:51 -05:00
|
|
|
StyleSheet::new($input).expect(concat!("failed to parse on ", $input))
|
2020-01-05 20:26:34 -05:00
|
|
|
.print_as_css(&mut buf)
|
|
|
|
.expect(concat!("failed to pretty print on ", $input));
|
|
|
|
assert_eq!(
|
|
|
|
String::from($input),
|
|
|
|
String::from_utf8(buf).expect("produced invalid utf8")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
($func:ident, $input:literal, $output:literal) => {
|
|
|
|
#[test]
|
|
|
|
fn $func() {
|
|
|
|
let mut buf = Vec::new();
|
2020-01-06 18:55:51 -05:00
|
|
|
StyleSheet::new($input).expect(concat!("failed to parse on ", $input))
|
2020-01-05 20:26:34 -05:00
|
|
|
.print_as_css(&mut buf)
|
|
|
|
.expect(concat!("failed to pretty print on ", $input));
|
|
|
|
assert_eq!(
|
|
|
|
String::from($output),
|
|
|
|
String::from_utf8(buf).expect("produced invalid utf8")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-01-05 20:51:14 -05:00
|
|
|
test!(
|
|
|
|
nesting_el_mul_el,
|
|
|
|
"a, b {\n a, b {\n color: red\n}\n}\n",
|
|
|
|
"a a, b a, a b, b b {\n color: red;\n}\n"
|
|
|
|
);
|
2020-01-05 20:26:34 -05:00
|
|
|
test!(basic_style, "a {\n color: red;\n}\n");
|
|
|
|
test!(two_styles, "a {\n color: red;\n color: blue;\n}\n");
|
|
|
|
test!(selector_mul, "a, b {\n color: red;\n}\n");
|
2020-01-05 20:51:14 -05:00
|
|
|
}
|