2020-01-20 11:00:01 -05:00
|
|
|
#![warn(
|
|
|
|
clippy::all,
|
|
|
|
clippy::restriction,
|
|
|
|
clippy::pedantic,
|
|
|
|
clippy::nursery,
|
|
|
|
clippy::cargo
|
|
|
|
)]
|
|
|
|
#![deny(missing_debug_implementations)]
|
|
|
|
#![allow(
|
|
|
|
// explicit return makes some things look ugly
|
|
|
|
clippy::implicit_return,
|
|
|
|
// Self { .. } is less explicit than Foo { .. }
|
|
|
|
clippy::use_self,
|
|
|
|
// this is way too pedantic -- some things don't need docs!
|
|
|
|
clippy::missing_docs_in_private_items,
|
|
|
|
// unreachable!() has many valid use cases
|
|
|
|
clippy::unreachable,
|
|
|
|
// _ => {} has many valid use cases
|
|
|
|
clippy::wildcard_enum_match_arm,
|
|
|
|
// .expect() has many valid use cases, like when we know a value is `Some(..)`
|
|
|
|
clippy::option_expect_used,
|
|
|
|
// this is too pedantic -- we are allowed to add numbers!
|
|
|
|
clippy::integer_arithmetic,
|
|
|
|
// this is too pedantic for now -- the library is changing too quickly for
|
|
|
|
// good docs to be written
|
|
|
|
clippy::missing_errors_doc,
|
|
|
|
// this incorrectly results in errors for types that derive `Debug`
|
|
|
|
// https://github.com/rust-lang/rust-clippy/issues/4980
|
|
|
|
clippy::let_underscore_must_use,
|
|
|
|
// this is too pedantic -- it results in some names being less explicit
|
|
|
|
// than they should
|
2020-02-02 10:27:08 -05:00
|
|
|
clippy::module_name_repetitions,
|
|
|
|
clippy::option_unwrap_used,
|
2020-02-08 17:03:43 -05:00
|
|
|
// this is too pedantic -- it is sometimes useful to break up `impl`s
|
|
|
|
clippy::multiple_inherent_impl,
|
2020-02-14 18:28:09 -05:00
|
|
|
|
|
|
|
// temporarily allowed while under heavy development.
|
|
|
|
// eventually these allows should be refactored away
|
|
|
|
// to no longer be necessary
|
|
|
|
clippy::as_conversions,
|
|
|
|
clippy::todo,
|
|
|
|
clippy::too_many_lines,
|
|
|
|
clippy::panic,
|
|
|
|
clippy::result_unwrap_used,
|
|
|
|
clippy::result_expect_used,
|
|
|
|
clippy::cast_possible_truncation,
|
|
|
|
clippy::single_match_else,
|
|
|
|
clippy::indexing_slicing,
|
2020-02-16 18:03:19 -05:00
|
|
|
clippy::match_same_arms,
|
|
|
|
clippy::or_fun_call,
|
2020-01-20 11:00:01 -05:00
|
|
|
)]
|
2020-01-20 13:39:20 -05:00
|
|
|
#![cfg_attr(feature = "nightly", feature(track_caller))]
|
2020-01-20 11:00:01 -05:00
|
|
|
// todo! handle erroring on styles at the toplevel
|
|
|
|
use std::fmt::{self, Display};
|
|
|
|
use std::fs;
|
2020-01-20 12:13:52 -05:00
|
|
|
use std::io::Write;
|
2020-01-20 11:00:01 -05:00
|
|
|
use std::iter::{Iterator, Peekable};
|
|
|
|
use std::path::Path;
|
|
|
|
|
2020-01-25 13:07:55 -05:00
|
|
|
use crate::atrule::{AtRule, AtRuleKind};
|
|
|
|
use crate::common::{Keyword, Op, Pos, Scope, Symbol, Whitespace};
|
2020-01-20 11:00:01 -05:00
|
|
|
use crate::css::Css;
|
2020-02-16 10:28:15 -05:00
|
|
|
use crate::error::SassError;
|
2020-02-16 11:59:04 -05:00
|
|
|
pub use crate::error::SassResult;
|
2020-01-20 11:00:01 -05:00
|
|
|
use crate::format::PrettyPrinter;
|
2020-01-25 13:20:21 -05:00
|
|
|
use crate::function::Function;
|
2020-01-20 11:00:01 -05:00
|
|
|
use crate::imports::import;
|
|
|
|
use crate::lexer::Lexer;
|
|
|
|
use crate::mixin::{eat_include, Mixin};
|
|
|
|
use crate::selector::{Attribute, Selector};
|
|
|
|
use crate::style::Style;
|
2020-02-01 21:59:23 -05:00
|
|
|
use crate::utils::{devour_whitespace, eat_variable_value, IsComment, IsWhitespace, VariableDecl};
|
2020-01-26 09:13:39 -05:00
|
|
|
use crate::value::Value;
|
2020-01-20 11:00:01 -05:00
|
|
|
|
2020-01-25 11:00:29 -05:00
|
|
|
mod args;
|
2020-01-25 12:43:07 -05:00
|
|
|
mod atrule;
|
2020-01-25 20:58:30 -05:00
|
|
|
mod builtin;
|
2020-01-20 11:00:01 -05:00
|
|
|
mod color;
|
|
|
|
mod common;
|
|
|
|
mod css;
|
|
|
|
mod error;
|
|
|
|
mod format;
|
|
|
|
mod function;
|
|
|
|
mod imports;
|
|
|
|
mod lexer;
|
|
|
|
mod mixin;
|
|
|
|
mod selector;
|
|
|
|
mod style;
|
|
|
|
mod units;
|
|
|
|
mod utils;
|
2020-01-25 09:58:53 -05:00
|
|
|
mod value;
|
2020-01-20 11:00:01 -05:00
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
2020-01-20 13:15:47 -05:00
|
|
|
pub(crate) struct Token {
|
2020-01-20 11:00:01 -05:00
|
|
|
pos: Pos,
|
|
|
|
pub kind: TokenKind,
|
|
|
|
}
|
|
|
|
|
2020-02-16 21:34:52 -05:00
|
|
|
impl Token {
|
|
|
|
pub fn equals_symbol(&self, s: Symbol) -> bool {
|
|
|
|
self.kind.equals_symbol(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-20 11:00:01 -05:00
|
|
|
impl IsWhitespace for Token {
|
|
|
|
fn is_whitespace(&self) -> bool {
|
|
|
|
if let TokenKind::Whitespace(_) = self.kind {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl IsWhitespace for &Token {
|
|
|
|
fn is_whitespace(&self) -> bool {
|
|
|
|
if let TokenKind::Whitespace(_) = self.kind {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-25 09:54:38 -05:00
|
|
|
impl IsComment for Token {
|
|
|
|
fn is_comment(&self) -> bool {
|
|
|
|
if let TokenKind::MultilineComment(_) = self.kind {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl IsComment for &Token {
|
|
|
|
fn is_comment(&self) -> bool {
|
|
|
|
if let TokenKind::MultilineComment(_) = self.kind {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-20 11:00:01 -05:00
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
2020-01-20 13:15:47 -05:00
|
|
|
pub(crate) enum TokenKind {
|
2020-01-20 11:00:01 -05:00
|
|
|
Ident(String),
|
|
|
|
Symbol(Symbol),
|
|
|
|
AtRule(AtRuleKind),
|
|
|
|
Keyword(Keyword),
|
|
|
|
Number(String),
|
|
|
|
Whitespace(Whitespace),
|
|
|
|
Variable(String),
|
|
|
|
Attribute(Attribute),
|
|
|
|
Op(Op),
|
|
|
|
MultilineComment(String),
|
|
|
|
Interpolation,
|
|
|
|
}
|
|
|
|
|
2020-02-16 21:34:52 -05:00
|
|
|
impl TokenKind {
|
|
|
|
pub fn equals_symbol(&self, s: Symbol) -> bool {
|
|
|
|
self == &TokenKind::Symbol(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-20 11:00:01 -05:00
|
|
|
impl Display for TokenKind {
|
2020-01-20 12:17:07 -05:00
|
|
|
#[inline]
|
2020-01-20 11:00:01 -05:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
TokenKind::Ident(s) | TokenKind::Number(s) => write!(f, "{}", s),
|
|
|
|
TokenKind::Symbol(s) => write!(f, "{}", s),
|
|
|
|
TokenKind::AtRule(s) => write!(f, "{}", s),
|
|
|
|
TokenKind::Op(s) => write!(f, "{}", s),
|
|
|
|
TokenKind::Whitespace(s) => write!(f, "{}", s),
|
|
|
|
TokenKind::Attribute(s) => write!(f, "{}", s),
|
|
|
|
TokenKind::Keyword(kw) => write!(f, "{}", kw),
|
|
|
|
TokenKind::MultilineComment(s) => write!(f, "/*{}*/", s),
|
2020-02-16 21:34:52 -05:00
|
|
|
TokenKind::Variable(s) => write!(f, "{}", s),
|
2020-01-20 11:00:01 -05:00
|
|
|
TokenKind::Interpolation => {
|
|
|
|
panic!("we don't want to format TokenKind::Interpolation using Display")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents a parsed SASS stylesheet with nesting
|
2020-01-26 15:27:38 -05:00
|
|
|
#[derive(Debug, Clone)]
|
2020-01-20 11:00:01 -05:00
|
|
|
pub struct StyleSheet(Vec<Stmt>);
|
|
|
|
|
2020-01-26 15:27:38 -05:00
|
|
|
#[derive(Clone, Debug)]
|
2020-01-20 13:15:47 -05:00
|
|
|
pub(crate) enum Stmt {
|
2020-01-20 11:00:01 -05:00
|
|
|
/// A [`Style`](/grass/style/struct.Style)
|
|
|
|
Style(Style),
|
|
|
|
/// A [`RuleSet`](/grass/struct.RuleSet.html)
|
|
|
|
RuleSet(RuleSet),
|
|
|
|
/// A multiline comment: `/* foo bar */`
|
|
|
|
MultilineComment(String),
|
2020-01-26 15:27:38 -05:00
|
|
|
/// A CSS rule: `@charset "UTF-8";`
|
|
|
|
AtRule(AtRule),
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents a single rule set. Rule sets can contain other rule sets
|
|
|
|
///
|
|
|
|
/// ```scss
|
|
|
|
/// a {
|
|
|
|
/// color: blue;
|
|
|
|
/// b {
|
|
|
|
/// color: red;
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
/// ```
|
2020-01-26 15:27:38 -05:00
|
|
|
#[derive(Clone, Debug)]
|
2020-01-20 13:15:47 -05:00
|
|
|
pub(crate) struct RuleSet {
|
2020-01-20 11:00:01 -05:00
|
|
|
selector: Selector,
|
|
|
|
rules: Vec<Stmt>,
|
|
|
|
// potential optimization: we don't *need* to own the selector
|
|
|
|
super_selector: Selector,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// An intermediate representation of what are essentially single lines
|
|
|
|
/// todo! rename this
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
enum Expr {
|
|
|
|
/// A style: `color: red`
|
|
|
|
Style(Style),
|
2020-02-01 19:33:56 -05:00
|
|
|
/// Several styles
|
|
|
|
Styles(Vec<Style>),
|
2020-01-20 11:00:01 -05:00
|
|
|
/// A collection of styles, from a mixin or function
|
|
|
|
// Styles(Vec<Style>),
|
|
|
|
/// A full selector `a > h1`
|
|
|
|
Selector(Selector),
|
|
|
|
/// A variable declaration `$var: 1px`
|
2020-02-16 18:03:19 -05:00
|
|
|
VariableDecl(String, Box<Value>),
|
2020-01-20 11:00:01 -05:00
|
|
|
/// A mixin declaration `@mixin foo {}`
|
2020-02-02 10:27:08 -05:00
|
|
|
MixinDecl(String, Box<Mixin>),
|
|
|
|
FunctionDecl(String, Box<Function>),
|
2020-01-20 11:00:01 -05:00
|
|
|
/// An include statement `@include foo;`
|
|
|
|
Include(Vec<Stmt>),
|
|
|
|
/// A multiline comment: `/* foobar */`
|
|
|
|
MultilineComment(String),
|
|
|
|
Debug(Pos, String),
|
|
|
|
Warn(Pos, String),
|
|
|
|
// /// Function call: `calc(10vw - 1px)`
|
|
|
|
// FuncCall(String, Vec<Token>),
|
|
|
|
}
|
|
|
|
|
2020-02-14 14:55:21 -05:00
|
|
|
/// Print the internal representation of a parsed stylesheet
|
|
|
|
///
|
|
|
|
/// Very closely resembles the original SASS, but contains only things translatable
|
|
|
|
/// to pure CSS: functions, variables, values, and mixins have all been evaluated.
|
|
|
|
///
|
|
|
|
/// Use `StyleSheet::print_as_css` to properly convert to CSS.
|
|
|
|
impl Display for StyleSheet {
|
|
|
|
#[inline]
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2020-02-16 18:26:35 -05:00
|
|
|
PrettyPrinter::new(f).pretty_print(self).unwrap();
|
|
|
|
Ok(())
|
2020-02-14 14:55:21 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-20 11:00:01 -05:00
|
|
|
impl StyleSheet {
|
2020-01-20 12:17:07 -05:00
|
|
|
#[inline]
|
2020-01-20 11:00:01 -05:00
|
|
|
pub fn new(input: &str) -> SassResult<StyleSheet> {
|
|
|
|
Ok(StyleSheet(
|
|
|
|
StyleSheetParser {
|
|
|
|
global_scope: Scope::new(),
|
|
|
|
lexer: Lexer::new(input).peekable(),
|
|
|
|
rules: Vec::new(),
|
|
|
|
scope: 0,
|
|
|
|
file: String::from("stdin"),
|
|
|
|
}
|
|
|
|
.parse_toplevel()?
|
|
|
|
.0,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2020-01-20 12:17:07 -05:00
|
|
|
#[inline]
|
2020-01-20 11:00:01 -05:00
|
|
|
pub fn from_path<P: AsRef<Path> + Into<String>>(p: P) -> SassResult<StyleSheet> {
|
|
|
|
Ok(StyleSheet(
|
|
|
|
StyleSheetParser {
|
|
|
|
global_scope: Scope::new(),
|
2020-01-20 12:17:07 -05:00
|
|
|
lexer: Lexer::new(&String::from_utf8(fs::read(p.as_ref())?)?).peekable(),
|
2020-01-20 11:00:01 -05:00
|
|
|
rules: Vec::new(),
|
|
|
|
scope: 0,
|
|
|
|
file: p.into(),
|
|
|
|
}
|
|
|
|
.parse_toplevel()?
|
|
|
|
.0,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn export_from_path<P: AsRef<Path> + Into<String>>(
|
|
|
|
p: P,
|
|
|
|
) -> SassResult<(Vec<Stmt>, Scope)> {
|
|
|
|
Ok(StyleSheetParser {
|
|
|
|
global_scope: Scope::new(),
|
2020-01-20 16:00:37 -05:00
|
|
|
lexer: Lexer::new(&String::from_utf8(fs::read(p.as_ref())?)?).peekable(),
|
2020-01-20 11:00:01 -05:00
|
|
|
rules: Vec::new(),
|
|
|
|
scope: 0,
|
|
|
|
file: p.into(),
|
|
|
|
}
|
|
|
|
.parse_toplevel()?)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Write the internal representation as CSS to `buf`
|
2020-01-20 16:00:37 -05:00
|
|
|
///
|
2020-02-14 14:36:16 -05:00
|
|
|
/// ```
|
2020-01-20 13:15:47 -05:00
|
|
|
/// use std::io::{BufWriter, stdout};
|
|
|
|
/// use grass::{SassResult, StyleSheet};
|
2020-01-20 16:00:37 -05:00
|
|
|
///
|
2020-01-20 13:15:47 -05:00
|
|
|
/// fn main() -> SassResult<()> {
|
|
|
|
/// let mut buf = BufWriter::new(stdout());
|
|
|
|
/// StyleSheet::from_path("input.scss")?.print_as_css(&mut buf)
|
|
|
|
/// }
|
|
|
|
/// ```
|
2020-01-20 12:17:07 -05:00
|
|
|
#[inline]
|
2020-01-20 12:13:52 -05:00
|
|
|
pub fn print_as_css<W: Write>(self, buf: &mut W) -> SassResult<()> {
|
2020-01-20 11:00:01 -05:00
|
|
|
Css::from_stylesheet(self).pretty_print(buf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
struct StyleSheetParser<'a> {
|
|
|
|
global_scope: Scope,
|
|
|
|
lexer: Peekable<Lexer<'a>>,
|
|
|
|
rules: Vec<Stmt>,
|
|
|
|
scope: u32,
|
|
|
|
file: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> StyleSheetParser<'a> {
|
|
|
|
fn parse_toplevel(mut self) -> SassResult<(Vec<Stmt>, Scope)> {
|
|
|
|
let mut rules: Vec<Stmt> = Vec::new();
|
2020-02-17 09:37:34 -05:00
|
|
|
while let Some(Token { kind, .. }) = self.lexer.peek() {
|
2020-01-20 11:00:01 -05:00
|
|
|
match kind {
|
|
|
|
TokenKind::Ident(_)
|
|
|
|
| TokenKind::Attribute(_)
|
|
|
|
| TokenKind::Interpolation
|
|
|
|
| TokenKind::Symbol(Symbol::Hash)
|
|
|
|
| TokenKind::Symbol(Symbol::Colon)
|
|
|
|
| TokenKind::Symbol(Symbol::Mul)
|
2020-01-29 20:02:02 -05:00
|
|
|
| TokenKind::Symbol(Symbol::Percent)
|
2020-01-20 11:00:01 -05:00
|
|
|
| TokenKind::Symbol(Symbol::Period) => rules
|
2020-02-16 10:54:25 -05:00
|
|
|
.extend(self.eat_rules(&Selector(Vec::new()), &mut self.global_scope.clone())?),
|
2020-01-20 11:00:01 -05:00
|
|
|
TokenKind::Whitespace(_) => {
|
|
|
|
self.lexer.next();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
TokenKind::Variable(_) => {
|
|
|
|
let Token { pos, kind } = self
|
|
|
|
.lexer
|
|
|
|
.next()
|
|
|
|
.expect("this must exist because we have already peeked");
|
|
|
|
let name = match kind {
|
|
|
|
TokenKind::Variable(n) => n,
|
|
|
|
_ => unsafe { std::hint::unreachable_unchecked() },
|
|
|
|
};
|
|
|
|
devour_whitespace(&mut self.lexer);
|
|
|
|
if self
|
|
|
|
.lexer
|
|
|
|
.next()
|
|
|
|
.unwrap_or_else(|| self.error(pos, "expected value after variable"))
|
|
|
|
.kind
|
|
|
|
!= TokenKind::Symbol(Symbol::Colon)
|
|
|
|
{
|
|
|
|
self.error(pos, "unexpected variable use at toplevel");
|
|
|
|
}
|
2020-02-17 09:37:34 -05:00
|
|
|
let VariableDecl { val, default } =
|
|
|
|
eat_variable_value(&mut self.lexer, &self.global_scope)?;
|
2020-02-08 17:26:01 -05:00
|
|
|
if !default || self.global_scope.get_var(&name).is_err() {
|
|
|
|
self.global_scope.insert_var(&name, val);
|
2020-01-29 21:02:32 -05:00
|
|
|
}
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
|
|
|
TokenKind::MultilineComment(_) => {
|
|
|
|
let comment = match self
|
|
|
|
.lexer
|
|
|
|
.next()
|
|
|
|
.expect("this must exist because we have already peeked")
|
|
|
|
.kind
|
|
|
|
{
|
|
|
|
TokenKind::MultilineComment(c) => c,
|
|
|
|
_ => unsafe { std::hint::unreachable_unchecked() },
|
|
|
|
};
|
|
|
|
rules.push(Stmt::MultilineComment(comment));
|
|
|
|
}
|
2020-02-17 09:37:34 -05:00
|
|
|
TokenKind::AtRule(AtRuleKind::Include) => rules.extend(eat_include(
|
|
|
|
&mut self.lexer,
|
|
|
|
&self.global_scope,
|
|
|
|
&Selector(Vec::new()),
|
|
|
|
)?),
|
2020-01-20 11:00:01 -05:00
|
|
|
TokenKind::AtRule(AtRuleKind::Import) => {
|
|
|
|
let Token { pos, .. } = self
|
|
|
|
.lexer
|
|
|
|
.next()
|
|
|
|
.expect("this must exist because we have already peeked");
|
|
|
|
devour_whitespace(&mut self.lexer);
|
|
|
|
let mut file_name = String::new();
|
|
|
|
match self
|
|
|
|
.lexer
|
|
|
|
.next()
|
|
|
|
.unwrap_or_else(|| self.error(pos, "expected value after @import"))
|
|
|
|
.kind
|
|
|
|
{
|
|
|
|
TokenKind::Symbol(Symbol::DoubleQuote) => {
|
|
|
|
while let Some(tok) = self.lexer.next() {
|
|
|
|
if tok.kind == TokenKind::Symbol(Symbol::DoubleQuote) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
file_name.push_str(&tok.kind.to_string());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TokenKind::Symbol(Symbol::SingleQuote) => {
|
|
|
|
while let Some(tok) = self.lexer.next() {
|
|
|
|
if tok.kind == TokenKind::Symbol(Symbol::SingleQuote) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
file_name.push_str(&tok.kind.to_string());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => todo!("expected ' or \" after @import"),
|
|
|
|
}
|
|
|
|
let Token { kind, pos } = self
|
|
|
|
.lexer
|
|
|
|
.next()
|
|
|
|
.expect("this must exist because we have already peeked");
|
|
|
|
if kind != TokenKind::Symbol(Symbol::SemiColon) {
|
|
|
|
self.error(pos, "expected `;` after @import declaration");
|
|
|
|
}
|
|
|
|
|
|
|
|
let (new_rules, new_scope) = import(file_name)?;
|
|
|
|
rules.extend(new_rules);
|
2020-02-08 17:26:01 -05:00
|
|
|
self.global_scope.extend(new_scope);
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
|
|
|
TokenKind::AtRule(_) => {
|
|
|
|
if let Some(Token {
|
|
|
|
kind: TokenKind::AtRule(ref rule),
|
|
|
|
pos,
|
|
|
|
}) = self.lexer.next()
|
|
|
|
{
|
2020-02-17 09:22:41 -05:00
|
|
|
match AtRule::from_tokens(rule, pos, &mut self.lexer, &self.global_scope)? {
|
2020-01-25 20:58:52 -05:00
|
|
|
AtRule::Mixin(name, mixin) => {
|
2020-02-08 17:26:01 -05:00
|
|
|
self.global_scope.insert_mixin(&name, *mixin);
|
2020-01-25 20:58:52 -05:00
|
|
|
}
|
|
|
|
AtRule::Function(name, func) => {
|
2020-02-08 17:26:01 -05:00
|
|
|
self.global_scope.insert_fn(&name, *func);
|
2020-01-25 20:58:52 -05:00
|
|
|
}
|
2020-02-17 09:37:34 -05:00
|
|
|
AtRule::Charset(toks) => {
|
|
|
|
rules.push(Stmt::AtRule(AtRule::Charset(toks)))
|
|
|
|
}
|
2020-01-25 13:07:55 -05:00
|
|
|
AtRule::Error(pos, message) => self.error(pos, &message),
|
|
|
|
AtRule::Warn(pos, message) => self.warn(pos, &message),
|
|
|
|
AtRule::Debug(pos, message) => self.debug(pos, &message),
|
2020-02-17 09:37:34 -05:00
|
|
|
AtRule::Return(_) => {
|
|
|
|
return Err("This at-rule is not allowed here.".into())
|
|
|
|
}
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-01-26 10:53:52 -05:00
|
|
|
TokenKind::Symbol(Symbol::BitAnd) => {
|
2020-02-17 09:37:34 -05:00
|
|
|
return Err(
|
2020-02-17 09:47:14 -05:00
|
|
|
"Base-level rules cannot contain the parent-selector-referencing character '&'.".into(),
|
2020-02-17 09:37:34 -05:00
|
|
|
)
|
2020-01-26 10:53:52 -05:00
|
|
|
}
|
2020-01-26 13:50:19 -05:00
|
|
|
_ => match dbg!(self.lexer.next()) {
|
2020-01-20 11:00:01 -05:00
|
|
|
Some(Token { pos, .. }) => self.error(pos, "unexpected toplevel token"),
|
|
|
|
_ => unsafe { std::hint::unreachable_unchecked() },
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
Ok((rules, self.global_scope))
|
|
|
|
}
|
|
|
|
|
2020-02-16 10:54:25 -05:00
|
|
|
fn eat_rules(&mut self, super_selector: &Selector, scope: &mut Scope) -> SassResult<Vec<Stmt>> {
|
2020-01-20 11:00:01 -05:00
|
|
|
let mut stmts = Vec::new();
|
2020-02-16 10:54:25 -05:00
|
|
|
while let Some(expr) = eat_expr(&mut self.lexer, scope, super_selector)? {
|
2020-01-20 18:09:25 -05:00
|
|
|
match expr {
|
2020-01-20 11:00:01 -05:00
|
|
|
Expr::Style(s) => stmts.push(Stmt::Style(s)),
|
2020-02-02 10:27:08 -05:00
|
|
|
#[allow(clippy::redundant_closure)]
|
2020-02-02 11:17:23 -05:00
|
|
|
Expr::Styles(s) => stmts.extend(s.into_iter().map(Stmt::Style)),
|
2020-01-20 11:00:01 -05:00
|
|
|
Expr::MixinDecl(name, mixin) => {
|
2020-02-08 17:26:01 -05:00
|
|
|
scope.insert_mixin(&name, *mixin);
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
2020-01-25 13:20:21 -05:00
|
|
|
Expr::FunctionDecl(name, func) => {
|
2020-02-08 17:26:01 -05:00
|
|
|
scope.insert_fn(&name, *func);
|
2020-01-25 13:20:21 -05:00
|
|
|
}
|
2020-01-20 11:00:01 -05:00
|
|
|
Expr::Selector(s) => {
|
|
|
|
self.scope += 1;
|
2020-02-16 10:54:25 -05:00
|
|
|
let rules = self.eat_rules(&super_selector.zip(&s), scope)?;
|
2020-01-20 11:00:01 -05:00
|
|
|
stmts.push(Stmt::RuleSet(RuleSet {
|
|
|
|
super_selector: super_selector.clone(),
|
|
|
|
selector: s,
|
|
|
|
rules,
|
|
|
|
}));
|
|
|
|
self.scope -= 1;
|
|
|
|
if self.scope == 0 {
|
2020-02-16 10:54:25 -05:00
|
|
|
return Ok(stmts);
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Expr::VariableDecl(name, val) => {
|
|
|
|
if self.scope == 0 {
|
2020-02-16 18:03:19 -05:00
|
|
|
scope.insert_var(&name, *val.clone());
|
|
|
|
self.global_scope.insert_var(&name, *val);
|
2020-01-20 11:00:01 -05:00
|
|
|
} else {
|
2020-02-16 18:03:19 -05:00
|
|
|
scope.insert_var(&name, *val);
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Expr::Include(rules) => stmts.extend(rules),
|
|
|
|
Expr::Debug(pos, ref message) => self.debug(pos, message),
|
|
|
|
Expr::Warn(pos, ref message) => self.warn(pos, message),
|
|
|
|
Expr::MultilineComment(s) => stmts.push(Stmt::MultilineComment(s)),
|
|
|
|
}
|
|
|
|
}
|
2020-02-16 10:54:25 -05:00
|
|
|
Ok(stmts)
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
|
|
|
|
toks: &mut Peekable<I>,
|
|
|
|
scope: &Scope,
|
|
|
|
super_selector: &Selector,
|
2020-02-16 10:54:25 -05:00
|
|
|
) -> SassResult<Option<Expr>> {
|
2020-01-20 11:00:01 -05:00
|
|
|
let mut values = Vec::with_capacity(5);
|
|
|
|
while let Some(tok) = toks.peek() {
|
|
|
|
match &tok.kind {
|
2020-02-01 19:33:56 -05:00
|
|
|
TokenKind::Symbol(Symbol::Colon) => {
|
|
|
|
let tok = toks.next();
|
|
|
|
if devour_whitespace(toks) {
|
2020-02-01 21:59:23 -05:00
|
|
|
let prop = Style::parse_property(
|
2020-02-01 19:33:56 -05:00
|
|
|
&mut values.into_iter().peekable(),
|
|
|
|
scope,
|
|
|
|
super_selector,
|
|
|
|
String::new(),
|
2020-02-17 07:18:54 -05:00
|
|
|
)?;
|
2020-02-01 21:59:23 -05:00
|
|
|
return Ok(Some(Style::from_tokens(toks, scope, super_selector, prop)?));
|
2020-02-01 19:33:56 -05:00
|
|
|
} else {
|
|
|
|
values.push(tok.unwrap());
|
|
|
|
}
|
|
|
|
}
|
2020-01-20 18:09:25 -05:00
|
|
|
TokenKind::Symbol(Symbol::SemiColon) => {
|
2020-01-20 11:00:01 -05:00
|
|
|
toks.next();
|
|
|
|
devour_whitespace(toks);
|
2020-02-01 21:59:23 -05:00
|
|
|
// special edge case where there was no space between the colon
|
|
|
|
// in a style `color:red`. todo: refactor
|
2020-02-02 10:27:08 -05:00
|
|
|
let mut v = values.into_iter().peekable();
|
2020-02-17 07:18:54 -05:00
|
|
|
let property = Style::parse_property(&mut v, scope, super_selector, String::new())?;
|
2020-02-16 10:54:25 -05:00
|
|
|
let value = Style::parse_value(&mut v, scope, super_selector)?;
|
2020-02-01 21:59:23 -05:00
|
|
|
return Ok(Some(Expr::Style(Style { property, value })));
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
2020-01-20 18:09:25 -05:00
|
|
|
TokenKind::Symbol(Symbol::CloseCurlyBrace) => {
|
|
|
|
if values.is_empty() {
|
|
|
|
toks.next();
|
|
|
|
devour_whitespace(toks);
|
|
|
|
return Ok(None);
|
2020-02-01 23:22:36 -05:00
|
|
|
} else {
|
|
|
|
// special edge case where there was no space between the colon
|
|
|
|
// and no semicolon following the style
|
|
|
|
// in a style `color:red`. todo: refactor
|
2020-02-02 10:27:08 -05:00
|
|
|
let mut v = values.into_iter().peekable();
|
|
|
|
let property =
|
2020-02-17 07:18:54 -05:00
|
|
|
Style::parse_property(&mut v, scope, super_selector, String::new())?;
|
2020-02-16 10:54:25 -05:00
|
|
|
let value = Style::parse_value(&mut v, scope, super_selector)?;
|
2020-02-01 23:22:36 -05:00
|
|
|
return Ok(Some(Expr::Style(Style { property, value })));
|
2020-01-20 18:09:25 -05:00
|
|
|
}
|
|
|
|
}
|
2020-01-20 11:00:01 -05:00
|
|
|
TokenKind::Symbol(Symbol::OpenCurlyBrace) => {
|
|
|
|
toks.next();
|
|
|
|
devour_whitespace(toks);
|
|
|
|
return Ok(Some(Expr::Selector(Selector::from_tokens(
|
|
|
|
&mut values.into_iter().peekable(),
|
|
|
|
scope,
|
2020-02-17 07:18:54 -05:00
|
|
|
)?)));
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
|
|
|
TokenKind::Variable(_) => {
|
|
|
|
let tok = toks
|
|
|
|
.next()
|
|
|
|
.expect("this must exist because we have already peeked");
|
|
|
|
let name = match tok.kind {
|
|
|
|
TokenKind::Variable(n) => n,
|
|
|
|
_ => unsafe { std::hint::unreachable_unchecked() },
|
|
|
|
};
|
|
|
|
if let TokenKind::Symbol(Symbol::Colon) =
|
|
|
|
toks.peek().expect("expected something after variable").kind
|
|
|
|
{
|
|
|
|
toks.next();
|
|
|
|
devour_whitespace(toks);
|
2020-01-29 21:02:32 -05:00
|
|
|
let VariableDecl { val, default } = eat_variable_value(toks, scope)?;
|
2020-02-08 17:26:01 -05:00
|
|
|
if !default || scope.get_var(&name).is_err() {
|
2020-02-16 18:03:19 -05:00
|
|
|
return Ok(Some(Expr::VariableDecl(name, Box::new(val))));
|
2020-01-29 21:02:32 -05:00
|
|
|
}
|
2020-01-20 11:00:01 -05:00
|
|
|
} else {
|
|
|
|
values.push(Token {
|
|
|
|
kind: TokenKind::Variable(name),
|
|
|
|
pos: tok.pos,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TokenKind::MultilineComment(_) => {
|
|
|
|
let tok = toks
|
|
|
|
.next()
|
|
|
|
.expect("this must exist because we have already peeked");
|
|
|
|
devour_whitespace(toks);
|
|
|
|
if values.is_empty() {
|
|
|
|
let s = match tok.kind {
|
|
|
|
TokenKind::MultilineComment(s) => s,
|
|
|
|
_ => unsafe { std::hint::unreachable_unchecked() },
|
|
|
|
};
|
|
|
|
return Ok(Some(Expr::MultilineComment(s)));
|
|
|
|
} else {
|
|
|
|
values.push(tok);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TokenKind::AtRule(AtRuleKind::Include) => {
|
|
|
|
return Ok(Some(Expr::Include(eat_include(
|
|
|
|
toks,
|
|
|
|
scope,
|
|
|
|
super_selector,
|
|
|
|
)?)));
|
|
|
|
}
|
|
|
|
TokenKind::AtRule(_) => {
|
|
|
|
if let Some(Token {
|
|
|
|
kind: TokenKind::AtRule(ref rule),
|
|
|
|
pos,
|
|
|
|
}) = toks.next()
|
|
|
|
{
|
2020-02-17 09:22:41 -05:00
|
|
|
return match AtRule::from_tokens(rule, pos, toks, scope)? {
|
2020-02-02 10:27:08 -05:00
|
|
|
AtRule::Mixin(name, mixin) => Ok(Some(Expr::MixinDecl(name, mixin))),
|
|
|
|
AtRule::Function(name, func) => Ok(Some(Expr::FunctionDecl(name, func))),
|
2020-01-26 15:27:38 -05:00
|
|
|
AtRule::Charset(_) => todo!("@charset as expr"),
|
2020-01-25 13:07:55 -05:00
|
|
|
AtRule::Debug(a, b) => Ok(Some(Expr::Debug(a, b))),
|
|
|
|
AtRule::Warn(a, b) => Ok(Some(Expr::Warn(a, b))),
|
2020-02-16 10:54:25 -05:00
|
|
|
AtRule::Error(pos, err) => Err(SassError::new(err, pos)),
|
2020-01-25 13:49:25 -05:00
|
|
|
AtRule::Return(_) => todo!("@return in unexpected location!"),
|
2020-01-25 20:58:52 -05:00
|
|
|
};
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
|
|
|
}
|
2020-01-20 18:56:23 -05:00
|
|
|
TokenKind::Interpolation => values.extend(eat_interpolation(toks)),
|
2020-01-20 11:00:01 -05:00
|
|
|
_ => match toks.next() {
|
|
|
|
Some(tok) => values.push(tok),
|
|
|
|
_ => unsafe { std::hint::unreachable_unchecked() },
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
|
2020-01-20 18:56:23 -05:00
|
|
|
fn eat_interpolation<I: Iterator<Item = Token>>(toks: &mut Peekable<I>) -> Vec<Token> {
|
|
|
|
let mut vals = Vec::new();
|
|
|
|
let mut n = 0;
|
2020-01-25 13:25:38 -05:00
|
|
|
for tok in toks {
|
2020-01-20 18:56:23 -05:00
|
|
|
match tok.kind {
|
|
|
|
TokenKind::Symbol(Symbol::OpenCurlyBrace) => n += 1,
|
|
|
|
TokenKind::Symbol(Symbol::CloseCurlyBrace) => n -= 1,
|
|
|
|
TokenKind::Interpolation => n += 1,
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
vals.push(tok);
|
|
|
|
if n == 0 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
vals
|
|
|
|
}
|
|
|
|
|
2020-01-20 11:00:01 -05:00
|
|
|
/// Functions that print to stdout or stderr
|
|
|
|
impl<'a> StyleSheetParser<'a> {
|
|
|
|
fn debug(&self, pos: Pos, message: &str) {
|
|
|
|
eprintln!("{}:{} Debug: {}", self.file, pos.line(), message);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn warn(&self, pos: Pos, message: &str) {
|
|
|
|
eprintln!(
|
|
|
|
"Warning: {}\n\t{} {}:{} todo!(scope)",
|
|
|
|
message,
|
|
|
|
self.file,
|
|
|
|
pos.line(),
|
|
|
|
pos.column()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn error(&self, pos: Pos, message: &str) -> ! {
|
|
|
|
eprintln!("Error: {}", message);
|
|
|
|
eprintln!(
|
|
|
|
"{} {}:{} todo!(scope) on line {} at column {}",
|
|
|
|
self.file,
|
|
|
|
pos.line(),
|
|
|
|
pos.column(),
|
|
|
|
pos.line(),
|
|
|
|
pos.column()
|
|
|
|
);
|
|
|
|
let padding = vec![' '; format!("{}", pos.line()).len() + 1]
|
|
|
|
.iter()
|
|
|
|
.collect::<String>();
|
|
|
|
eprintln!("{}|", padding);
|
|
|
|
eprint!("{} | ", pos.line());
|
|
|
|
eprintln!("todo! get line to print as error");
|
|
|
|
eprintln!(
|
|
|
|
"{}| {}^",
|
|
|
|
padding,
|
|
|
|
vec![' '; pos.column() as usize].iter().collect::<String>()
|
|
|
|
);
|
|
|
|
eprintln!("{}|", padding);
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
}
|