grass/src/lib.rs

752 lines
28 KiB
Rust
Raw Normal View History

/*! # grass
An implementation of the sass specification in pure rust.
All functionality is currently exposed through [`StyleSheet`].
2020-04-21 04:59:03 -04:00
Spec progress as of 2020-04-21:
| Passing | Failing | Total |
|---------|---------|-------|
2020-04-21 04:59:03 -04:00
| 2150 | 2943 | 5093 |
## Use as library
```
use grass::{SassResult, StyleSheet};
fn main() -> SassResult<()> {
let sass = StyleSheet::new("a { b { color: &; } }".to_string())?;
assert_eq!(sass, "a b {\n color: a b;\n}\n");
Ok(())
}
```
## Use as binary
```bash
cargo install grass
grass input.scss
```
*/
2020-03-01 09:08:13 -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,
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-03-30 15:43:15 -04:00
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::option_unwrap_used,
2020-02-14 18:28:09 -05:00
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-04-21 18:22:26 -04:00
clippy::redundant_pub_crate,
)]
#![cfg_attr(feature = "nightly", feature(track_caller))]
use std::fs;
use std::iter::Iterator;
use std::path::Path;
2020-04-12 19:37:12 -04:00
use codemap::{CodeMap, Span, Spanned};
use peekmore::{PeekMore, PeekMoreIterator};
2020-04-24 18:08:20 -04:00
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
2020-03-22 15:08:13 -04:00
use crate::atrule::{eat_include, AtRule, AtRuleKind, Function, Mixin};
2020-03-29 13:28:17 -04:00
pub use crate::error::{SassError, SassResult};
use crate::imports::import;
use crate::lexer::Lexer;
use crate::output::Css;
2020-04-23 18:14:42 -04:00
use crate::scope::{
global_var_exists, insert_global_fn, insert_global_mixin, insert_global_var, Scope,
GLOBAL_SCOPE,
};
use crate::selector::Selector;
use crate::style::Style;
2020-03-29 13:28:17 -04:00
pub(crate) use crate::token::Token;
use crate::utils::{
devour_whitespace, eat_comment, eat_ident, eat_ident_no_interpolation, eat_variable_value,
2020-04-22 10:57:57 -04:00
parse_quoted_string, read_until_closing_curly_brace, read_until_newline, VariableDecl,
read_until_closing_paren
2020-03-29 13:28:17 -04:00
};
use crate::value::Value;
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;
mod color;
mod common;
mod error;
mod imports;
mod lexer;
mod output;
mod scope;
mod selector;
mod style;
2020-03-19 19:32:11 -04:00
mod token;
mod unit;
mod utils;
2020-01-25 09:58:53 -05:00
mod value;
/// Represents a parsed SASS stylesheet with nesting
2020-04-24 18:08:20 -04:00
#[cfg_attr(feature = "wasm", wasm_bindgen)]
2020-01-26 15:27:38 -05:00
#[derive(Debug, Clone)]
2020-04-12 19:37:12 -04:00
pub struct StyleSheet(Vec<Spanned<Stmt>>);
2020-01-26 15:27:38 -05:00
#[derive(Clone, Debug)]
pub(crate) enum Stmt {
/// A [`Style`](/grass/style/struct.Style)
2020-02-29 16:13:57 -05:00
Style(Box<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-04-12 19:37:12 -04:00
impl Stmt {
2020-04-21 18:22:26 -04:00
const fn span(self, span: Span) -> Spanned<Self> {
2020-04-12 19:37:12 -04:00
Spanned { node: self, span }
}
}
/// 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)]
pub(crate) struct RuleSet {
selector: Selector,
2020-04-12 19:37:12 -04:00
rules: Vec<Spanned<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`
2020-02-22 17:57:13 -05:00
Style(Box<Style>),
/// Several styles
Styles(Vec<Style>),
/// A full selector `a > h1`
Selector(Selector),
/// A variable declaration `$var: 1px`
2020-04-12 19:37:12 -04:00
VariableDecl(String, Box<Spanned<Value>>),
/// A mixin declaration `@mixin foo {}`
2020-02-02 10:27:08 -05:00
MixinDecl(String, Box<Mixin>),
FunctionDecl(String, Box<Function>),
/// A multiline comment: `/* foobar */`
MultilineComment(String),
2020-02-22 15:34:32 -05:00
AtRule(AtRule),
}
2020-04-12 19:37:12 -04:00
fn raw_to_parse_error(map: &CodeMap, err: SassError) -> SassError {
let (message, span) = err.raw();
SassError::from_loc(message, map.look_up_span(span))
}
2020-04-24 18:08:20 -04:00
#[cfg(feature = "wasm")]
#[wasm_bindgen]
impl StyleSheet {
pub fn new(input: String) -> Result<String, JsValue> {
let mut map = CodeMap::new();
let file = map.add_file("stdin".into(), input);
Ok(Css::from_stylesheet(StyleSheet(
StyleSheetParser {
lexer: Lexer::new(&file).peekmore(),
nesting: 0,
map: &map,
}
.parse_toplevel()
.map_err(|e| raw_to_parse_error(&map, e).to_string())?
.0,
))
.map_err(|e| raw_to_parse_error(&map, e).to_string())?
.pretty_print()
.map_err(|e| raw_to_parse_error(&map, e).to_string())?)
}
}
impl StyleSheet {
/// Write CSS to `buf`, constructed from a string
///
/// ```
/// use grass::{SassResult, StyleSheet};
///
/// fn main() -> SassResult<()> {
/// let sass = StyleSheet::new("a { b { color: red; } }".to_string())?;
/// assert_eq!(sass, "a b {\n color: red;\n}\n");
/// Ok(())
/// }
/// ```
2020-01-20 12:17:07 -05:00
#[inline]
2020-04-24 18:08:20 -04:00
#[cfg(not(feature = "wasm"))]
pub fn new(input: String) -> SassResult<String> {
2020-04-12 19:37:12 -04:00
let mut map = CodeMap::new();
let file = map.add_file("stdin".into(), input);
Css::from_stylesheet(StyleSheet(
StyleSheetParser {
lexer: Lexer::new(&file).peekmore(),
2020-04-12 19:37:12 -04:00
nesting: 0,
map: &map,
}
.parse_toplevel()
.map_err(|e| raw_to_parse_error(&map, e))?
.0,
))
.map_err(|e| raw_to_parse_error(&map, e))?
.pretty_print()
.map_err(|e| raw_to_parse_error(&map, e))
}
/// Write CSS to `buf`, constructed from a path
///
/// ```
/// use grass::{SassResult, StyleSheet};
///
/// fn main() -> SassResult<()> {
/// let sass = StyleSheet::from_path("input.scss")?;
/// Ok(())
/// }
/// ```
2020-01-20 12:17:07 -05:00
#[inline]
2020-04-24 18:08:20 -04:00
#[cfg(not(feature = "wasm"))]
pub fn from_path<P: AsRef<Path> + Into<String> + Clone>(p: P) -> SassResult<String> {
2020-04-12 19:37:12 -04:00
let mut map = CodeMap::new();
2020-04-21 18:39:19 -04:00
let file = map.add_file(p.clone().into(), String::from_utf8(fs::read(p)?)?);
Css::from_stylesheet(StyleSheet(
StyleSheetParser {
lexer: Lexer::new(&file).peekmore(),
2020-04-12 19:37:12 -04:00
nesting: 0,
map: &map,
}
.parse_toplevel()
.map_err(|e| raw_to_parse_error(&map, e))?
.0,
))
.map_err(|e| raw_to_parse_error(&map, e))?
.pretty_print()
.map_err(|e| raw_to_parse_error(&map, e))
}
2020-04-12 19:37:12 -04:00
pub(crate) fn export_from_path<P: AsRef<Path> + Into<String> + Clone>(
2020-04-21 18:39:19 -04:00
p: &P,
2020-04-12 19:37:12 -04:00
) -> SassResult<(Vec<Spanned<Stmt>>, Scope)> {
let mut map = CodeMap::new();
2020-04-21 18:39:19 -04:00
let file = map.add_file(p.clone().into(), String::from_utf8(fs::read(p)?)?);
Ok(StyleSheetParser {
lexer: Lexer::new(&file).peekmore(),
nesting: 0,
map: &map,
}
.parse_toplevel()?)
}
2020-04-12 19:37:12 -04:00
pub(crate) fn from_stmts(s: Vec<Spanned<Stmt>>) -> StyleSheet {
2020-02-22 12:00:32 -05:00
StyleSheet(s)
}
}
struct StyleSheetParser<'a> {
lexer: PeekMoreIterator<Lexer<'a>>,
2020-04-12 19:37:12 -04:00
nesting: u32,
map: &'a CodeMap,
}
impl<'a> StyleSheetParser<'a> {
2020-04-12 19:37:12 -04:00
fn parse_toplevel(mut self) -> SassResult<(Vec<Spanned<Stmt>>, Scope)> {
let mut rules: Vec<Spanned<Stmt>> = Vec::new();
2020-02-17 09:37:34 -05:00
while let Some(Token { kind, .. }) = self.lexer.peek() {
match kind {
2020-04-04 21:05:22 -04:00
'a'..='z' | 'A'..='Z' | '_' | '-' | '0'..='9'
2020-04-01 22:31:10 -04:00
| '[' | '#' | ':' | '*' | '%' | '.' | '>' => rules
2020-04-04 14:53:08 -04:00
.extend(self.eat_rules(&Selector::new(), &mut Scope::new())?),
2020-03-29 13:28:17 -04:00
&'\t' | &'\n' | ' ' => {
self.lexer.next();
continue;
}
2020-03-29 13:28:17 -04:00
'$' => {
self.lexer.next();
let name = eat_ident(&mut self.lexer, &Scope::new(), &Selector::new())?;
devour_whitespace(&mut self.lexer);
2020-04-12 19:37:12 -04:00
let Token { kind, pos } = self
.lexer
.next()
2020-04-12 19:37:12 -04:00
.unwrap();
if kind != ':' {
return Err(("expected \":\".", pos).into());
}
let VariableDecl { val, default, .. } =
2020-04-04 14:53:08 -04:00
eat_variable_value(&mut self.lexer, &Scope::new(), &Selector::new())?;
2020-04-23 18:14:42 -04:00
if (default && !global_var_exists(&name)) || !default {
insert_global_var(&name.node, val)?;
}
}
2020-03-29 13:28:17 -04:00
'/' => {
self.lexer.next();
if '*' == self.lexer.peek().unwrap().kind {
self.lexer.next();
2020-04-12 19:37:12 -04:00
let comment = eat_comment(&mut self.lexer, &Scope::new(), &Selector::new())?;
rules.push(comment.map_node(Stmt::MultilineComment));
2020-03-29 13:28:17 -04:00
} else if '/' == self.lexer.peek().unwrap().kind {
read_until_newline(&mut self.lexer);
devour_whitespace(&mut self.lexer);
} else {
todo!()
}
}
2020-03-29 13:28:17 -04:00
'@' => {
self.lexer.next();
2020-04-12 19:37:12 -04:00
let Spanned { node: at_rule_kind, span } = eat_ident(&mut self.lexer, &Scope::new(), &Selector::new())?;
2020-03-29 13:28:17 -04:00
if at_rule_kind.is_empty() {
2020-04-12 19:37:12 -04:00
return Err(("Expected identifier.", span).into());
2020-03-29 13:28:17 -04:00
}
match AtRuleKind::from(at_rule_kind.as_str()) {
AtRuleKind::Include => rules.extend(eat_include(
&mut self.lexer,
2020-04-04 14:53:08 -04:00
&Scope::new(),
2020-03-29 13:28:17 -04:00
&Selector::new(),
)?),
AtRuleKind::Import => {
devour_whitespace(&mut self.lexer);
let mut file_name = String::new();
match self
.lexer
.next()
.unwrap()
.kind
{
q @ '"' | q @ '\'' => {
file_name.push_str(
&parse_quoted_string(
&mut self.lexer,
&Scope::new(),
q,
&Selector::new())?
.node.unquote().to_css_string(span)?);
}
2020-03-29 13:28:17 -04:00
_ => todo!("expected ' or \" after @import"),
}
2020-03-29 13:28:17 -04:00
if self.lexer.next().unwrap().kind != ';' {
todo!("no semicolon after @import");
}
2020-03-29 13:28:17 -04:00
let (new_rules, new_scope) = import(file_name)?;
rules.extend(new_rules);
GLOBAL_SCOPE.with(|s| {
s.borrow_mut().extend(new_scope);
});
}
2020-03-29 13:28:17 -04:00
v => {
let rule = AtRule::from_tokens(&v, span, &mut self.lexer, &mut Scope::new(), &Selector::new())?;
2020-04-12 19:37:12 -04:00
match rule.node {
AtRule::Mixin(name, mixin) => {
insert_global_mixin(&name, *mixin);
}
AtRule::Function(name, func) => {
insert_global_fn(&name, *func);
}
AtRule::Charset => continue,
AtRule::Warn(message) => self.warn(rule.span, &message),
AtRule::Debug(message) => self.debug(rule.span, &message),
AtRule::Return(_) => {
return Err(
("This at-rule is not allowed here.", rule.span).into()
)
2020-04-12 19:37:12 -04:00
}
AtRule::For(f) => rules.extend(f.ruleset_eval(&mut Scope::new(), &Selector::new())?),
2020-04-23 18:14:42 -04:00
AtRule::While(w) => rules.extend(w.ruleset_eval(&mut Scope::new(), &Selector::new(), true)?),
AtRule::Include(s)
| AtRule::Each(s) => rules.extend(s),
AtRule::Content => return Err(
("@content is only allowed within mixin declarations.", rule.span
).into()),
2020-04-12 19:37:12 -04:00
AtRule::If(i) => {
rules.extend(i.eval(&mut Scope::new(), &Selector::new())?);
2020-03-29 13:28:17 -04:00
}
2020-04-12 19:37:12 -04:00
AtRule::AtRoot(root_rules) => rules.extend(root_rules),
AtRule::Unknown(..) => rules.push(rule.map_node(Stmt::AtRule)),
2020-03-29 13:28:17 -04:00
}
2020-04-12 19:37:12 -04:00
}
}
2020-03-29 13:28:17 -04:00
},
'&' => {
2020-02-17 09:37:34 -05:00
return Err(
2020-04-12 19:37:12 -04:00
("Base-level rules cannot contain the parent-selector-referencing character '&'.", self.lexer.next().unwrap().pos()).into(),
2020-02-17 09:37:34 -05:00
)
2020-01-26 10:53:52 -05:00
}
c if c.is_control() => {
2020-04-12 19:37:12 -04:00
return Err(("expected selector.", self.lexer.next().unwrap().pos()).into());
}
2020-01-26 13:50:19 -05:00
_ => match dbg!(self.lexer.next()) {
2020-04-04 21:05:22 -04:00
Some(..) => todo!("unexpected toplevel token"),
_ => unsafe { std::hint::unreachable_unchecked() },
2020-03-29 13:28:17 -04:00
}
};
}
Ok((rules, GLOBAL_SCOPE.with(|s| s.borrow().clone())))
}
2020-04-12 19:37:12 -04:00
fn eat_rules(
&mut self,
super_selector: &Selector,
scope: &mut Scope,
) -> SassResult<Vec<Spanned<Stmt>>> {
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-04-12 19:37:12 -04:00
let span = expr.span;
match expr.node {
Expr::Style(s) => stmts.push(Spanned {
node: Stmt::Style(s),
span,
}),
2020-02-29 15:28:48 -05:00
Expr::AtRule(a) => match a {
AtRule::For(f) => stmts.extend(f.ruleset_eval(scope, super_selector)?),
2020-04-23 18:14:42 -04:00
AtRule::While(w) => {
stmts.extend(w.ruleset_eval(scope, super_selector, false)?)
}
2020-04-23 15:22:35 -04:00
AtRule::Include(s) | AtRule::Each(s) => stmts.extend(s),
2020-03-24 22:13:38 -04:00
AtRule::If(i) => stmts.extend(i.eval(scope, super_selector)?),
2020-03-10 21:23:47 -04:00
AtRule::Content => {
2020-04-12 19:37:12 -04:00
return Err((
"@content is only allowed within mixin declarations.",
expr.span,
)
.into())
}
AtRule::Return(..) => {
return Err(("This at-rule is not allowed here.", expr.span).into())
2020-03-10 21:23:47 -04:00
}
2020-04-06 13:13:03 -04:00
AtRule::AtRoot(root_stmts) => stmts.extend(root_stmts),
2020-04-12 19:37:12 -04:00
AtRule::Debug(ref message) => self.debug(expr.span, message),
AtRule::Warn(ref message) => self.warn(expr.span, message),
AtRule::Mixin(..) | AtRule::Function(..) => todo!(),
AtRule::Charset => todo!(),
r @ AtRule::Unknown(..) => stmts.push(Spanned {
2020-04-12 19:37:12 -04:00
node: Stmt::AtRule(r),
span,
}),
2020-02-29 15:28:48 -05:00
},
2020-04-12 19:37:12 -04:00
Expr::Styles(s) => stmts.extend(
s.into_iter()
.map(Box::new)
.map(Stmt::Style)
.map(|style| Spanned { node: style, span }),
),
Expr::MixinDecl(name, mixin) => {
scope.insert_mixin(&name, *mixin);
}
2020-01-25 13:20:21 -05:00
Expr::FunctionDecl(name, func) => {
scope.insert_fn(&name, *func);
2020-01-25 13:20:21 -05:00
}
Expr::Selector(s) => {
2020-04-12 19:37:12 -04:00
self.nesting += 1;
2020-02-16 10:54:25 -05:00
let rules = self.eat_rules(&super_selector.zip(&s), scope)?;
2020-04-12 19:37:12 -04:00
stmts.push(Spanned {
node: Stmt::RuleSet(RuleSet {
super_selector: super_selector.clone(),
selector: s,
rules,
}),
span,
});
self.nesting -= 1;
if self.nesting == 0 {
2020-02-16 10:54:25 -05:00
return Ok(stmts);
}
}
Expr::VariableDecl(name, val) => {
2020-04-12 19:37:12 -04:00
if self.nesting == 0 {
scope.insert_var(&name, *val.clone())?;
insert_global_var(&name, *val)?;
} else {
scope.insert_var(&name, *val)?;
}
}
2020-04-12 19:37:12 -04:00
Expr::MultilineComment(s) => stmts.push(Spanned {
node: Stmt::MultilineComment(s),
span,
}),
}
}
2020-02-16 10:54:25 -05:00
Ok(stmts)
}
}
pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>,
scope: &mut Scope,
super_selector: &Selector,
2020-04-12 19:37:12 -04:00
) -> SassResult<Option<Spanned<Expr>>> {
let mut values = Vec::with_capacity(5);
2020-04-12 19:37:12 -04:00
let mut span = if let Some(tok) = toks.peek() {
tok.pos()
} else {
return Ok(None);
};
while let Some(tok) = toks.peek() {
2020-04-12 19:37:12 -04:00
span = span.merge(tok.pos());
2020-04-06 13:13:03 -04:00
match tok.kind {
2020-03-29 13:28:17 -04:00
':' => {
let tok = toks.next();
if devour_whitespace(toks) {
2020-02-01 21:59:23 -05:00
let prop = Style::parse_property(
&mut values.into_iter().peekmore(),
scope,
super_selector,
String::new(),
)?;
2020-04-12 19:37:12 -04:00
return Ok(Some(Spanned {
node: Style::from_tokens(toks, scope, super_selector, prop)?,
span,
}));
} else {
values.push(tok.unwrap());
}
}
2020-03-29 13:28:17 -04: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
2020-02-29 15:54:13 -05:00
// in a style, e.g. `color:red`. todo: refactor
let mut v = values.into_iter().peekmore();
2020-02-29 15:54:13 -05:00
devour_whitespace(&mut v);
if v.peek().is_none() {
2020-03-29 13:28:17 -04:00
devour_whitespace(toks);
2020-04-12 19:37:12 -04:00
return Ok(Some(Spanned {
node: Expr::Style(Box::new(Style {
property: String::new(),
value: Value::Null.span(span),
})),
span,
}));
2020-02-29 15:54:13 -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-04-12 19:37:12 -04:00
return Ok(Some(Spanned {
node: Expr::Style(Box::new(Style { property, value })),
span,
}));
}
2020-03-29 13:28:17 -04:00
'}' => {
2020-01-20 18:09:25 -05:00
if values.is_empty() {
toks.next();
devour_whitespace(toks);
2020-04-04 03:00:38 -04:00
if toks.peek().is_some() && toks.peek().unwrap().kind == ';' {
toks.next();
}
devour_whitespace(toks);
2020-01-20 18:09:25 -05:00
return Ok(None);
} 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
let mut v = values.into_iter().peekmore();
2020-02-02 10:27:08 -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-04-12 19:37:12 -04:00
return Ok(Some(Spanned {
node: Expr::Style(Box::new(Style { property, value })),
span,
}));
2020-01-20 18:09:25 -05:00
}
}
2020-03-29 13:28:17 -04:00
'{' => {
toks.next();
devour_whitespace(toks);
2020-04-12 19:37:12 -04:00
return Ok(Some(Spanned {
node: Expr::Selector(Selector::from_tokens(
&mut values.into_iter().peekmore(),
2020-04-12 19:37:12 -04:00
scope,
super_selector,
)?),
span,
}));
}
2020-03-29 13:28:17 -04:00
'$' => {
let tok = toks.next().unwrap();
if toks.peek().unwrap().kind == '=' {
values.push(tok);
values.push(toks.next().unwrap());
continue;
}
2020-04-20 03:20:08 -04:00
let name = eat_ident_no_interpolation(toks, false)?;
devour_whitespace(toks);
2020-03-29 13:28:17 -04:00
if toks.peek().unwrap().kind == ':' {
toks.next();
devour_whitespace(toks);
let VariableDecl {
val,
default,
global,
} = eat_variable_value(toks, scope, super_selector)?;
if global {
2020-04-12 19:37:12 -04:00
insert_global_var(&name.node, val.clone())?;
}
2020-04-23 18:14:42 -04:00
let var_exists = scope.var_exists(&name.node);
if (default && !var_exists) || !default {
2020-04-12 19:37:12 -04:00
return Ok(Some(Spanned {
node: Expr::VariableDecl(name.node, Box::new(val)),
span,
}));
2020-01-29 21:02:32 -05:00
}
2020-04-23 18:14:42 -04:00
if !values.is_empty() {
todo!()
}
} else {
values.push(tok);
2020-04-12 19:37:12 -04:00
let mut current_pos = 0;
values.extend(name.chars().map(|x| {
let len = x.len_utf8() as u64;
let tok = Token::new(span.subspan(current_pos, current_pos + len), x);
current_pos += len;
tok
}));
}
}
2020-03-29 13:28:17 -04:00
'/' => {
let tok = toks.next().unwrap();
2020-04-12 19:37:12 -04:00
let peeked = toks.peek().ok_or(("expected more input.", tok.pos()))?;
2020-03-29 13:28:17 -04:00
if peeked.kind == '/' {
read_until_newline(toks);
devour_whitespace(toks);
continue;
} else if values.is_empty() && peeked.kind == '*' {
toks.next();
2020-03-31 01:00:25 -04:00
let comment = eat_comment(toks, scope, super_selector)?;
devour_whitespace(toks);
2020-04-12 19:37:12 -04:00
return Ok(Some(Spanned {
node: Expr::MultilineComment(comment.node),
span: comment.span,
}));
} else {
values.push(tok);
}
}
2020-03-29 13:28:17 -04:00
'@' => {
2020-04-12 19:37:12 -04:00
toks.next();
let Spanned { node: ident, span } = eat_ident(toks, scope, super_selector)?;
devour_whitespace(toks);
let rule = AtRule::from_tokens(
&AtRuleKind::from(ident.as_str()),
span,
toks,
scope,
super_selector,
)?;
return Ok(Some(Spanned {
node: match rule.node {
AtRule::Mixin(name, mixin) => Expr::MixinDecl(name, mixin),
AtRule::Function(name, func) => Expr::FunctionDecl(name, func),
AtRule::Charset => todo!("@charset as expr"),
d @ AtRule::Debug(..) => Expr::AtRule(d),
w @ AtRule::Warn(..) => Expr::AtRule(w),
a @ AtRule::Return(_) => Expr::AtRule(a),
c @ AtRule::Content => Expr::AtRule(c),
f @ AtRule::If(..) => Expr::AtRule(f),
f @ AtRule::For(..) => Expr::AtRule(f),
f @ AtRule::While(..) => Expr::AtRule(f),
f @ AtRule::Each(..) => Expr::AtRule(f),
u @ AtRule::Unknown(..) => Expr::AtRule(u),
u @ AtRule::AtRoot(..) => Expr::AtRule(u),
u @ AtRule::Include(..) => Expr::AtRule(u),
},
span,
}));
}
2020-03-29 13:28:17 -04:00
'#' => {
values.push(toks.next().unwrap());
if toks.peek().unwrap().kind == '{' {
values.push(toks.next().unwrap());
2020-04-22 10:57:57 -04:00
values.extend(read_until_closing_curly_brace(toks));
values.push(toks.next().unwrap());
}
}
2020-04-19 20:22:31 -04:00
'\\' => {
values.push(toks.next().unwrap());
values.push(toks.next().unwrap());
}
// todo: this should only apply to special functions
// it is causing us to emit nothing on malformed input
'(' => {
values.push(toks.next().unwrap());
values.extend(read_until_closing_paren(toks));
}
2020-03-29 13:28:17 -04:00
_ => values.push(toks.next().unwrap()),
};
}
Ok(None)
}
/// Functions that print to stdout or stderr
impl<'a> StyleSheetParser<'a> {
2020-04-12 19:37:12 -04:00
fn debug(&self, span: Span, message: &str) {
let loc = self.map.look_up_span(span);
eprintln!(
"{}:{} Debug: {}",
loc.file.name(),
loc.begin.line + 1,
message
);
}
2020-04-12 19:37:12 -04:00
fn warn(&self, span: Span, message: &str) {
let loc = self.map.look_up_span(span);
eprintln!(
2020-04-12 19:37:12 -04:00
"Warning: {}\n {} {}:{} root stylesheet",
message,
2020-04-12 19:37:12 -04:00
loc.file.name(),
loc.begin.line + 1,
loc.begin.column + 1
);
}
}