2020-04-18 18:53:18 -04:00
|
|
|
/*! # grass
|
|
|
|
An implementation of the sass specification in pure rust.
|
|
|
|
|
|
|
|
All functionality is currently exposed through [`StyleSheet`].
|
|
|
|
|
2020-05-05 19:01:40 -04:00
|
|
|
Spec progress as of 2020-05-01:
|
2020-04-18 18:53:18 -04:00
|
|
|
|
|
|
|
| Passing | Failing | Total |
|
|
|
|
|---------|---------|-------|
|
2020-05-05 19:01:40 -04:00
|
|
|
| 2193 | 2900 | 5093 |
|
2020-04-18 18:53:18 -04:00
|
|
|
|
|
|
|
## Use as library
|
|
|
|
```
|
|
|
|
use grass::{SassResult, StyleSheet};
|
|
|
|
|
|
|
|
fn main() -> SassResult<()> {
|
2020-04-21 05:25:08 -04:00
|
|
|
let sass = StyleSheet::new("a { b { color: &; } }".to_string())?;
|
|
|
|
assert_eq!(sass, "a b {\n color: a b;\n}\n");
|
2020-04-18 18:53:18 -04:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## Use as binary
|
|
|
|
```bash
|
|
|
|
cargo install grass
|
|
|
|
grass input.scss
|
|
|
|
```
|
|
|
|
*/
|
2020-03-01 09:08:13 -05:00
|
|
|
|
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,
|
2020-05-24 17:41:24 -04:00
|
|
|
// this is too pedantic -- some things don't need docs!
|
2020-01-20 11:00:01 -05:00
|
|
|
clippy::missing_docs_in_private_items,
|
|
|
|
clippy::unreachable,
|
2020-05-24 17:41:24 -04:00
|
|
|
// this disallows binding as well
|
2020-01-20 11:00:01 -05:00
|
|
|
clippy::wildcard_enum_match_arm,
|
|
|
|
// 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
|
2020-05-24 17:41:24 -04:00
|
|
|
// clippy::let_underscore_must_use,
|
2020-01-20 11:00:01 -05:00
|
|
|
// 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-05-24 17:41:24 -04:00
|
|
|
// filter isn't fallible
|
|
|
|
clippy::filter_map,
|
|
|
|
clippy::else_if_without_else,
|
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,
|
2020-03-30 10:47:19 -04:00
|
|
|
clippy::option_unwrap_used,
|
2020-02-14 18:28:09 -05:00
|
|
|
clippy::result_unwrap_used,
|
|
|
|
clippy::cast_possible_truncation,
|
|
|
|
clippy::single_match_else,
|
|
|
|
clippy::indexing_slicing,
|
2020-05-24 17:41:24 -04:00
|
|
|
// clippy::match_same_arms,
|
|
|
|
// clippy::or_fun_call,
|
2020-04-21 18:22:26 -04:00
|
|
|
clippy::redundant_pub_crate,
|
2020-01-20 11:00:01 -05:00
|
|
|
)]
|
2020-01-20 13:39:20 -05:00
|
|
|
#![cfg_attr(feature = "nightly", feature(track_caller))]
|
2020-05-01 15:43:43 -04:00
|
|
|
#![cfg_attr(feature = "profiling", inline(never))]
|
2020-04-27 15:53:43 -04:00
|
|
|
|
2020-05-24 12:47:04 -04:00
|
|
|
use std::convert::TryFrom;
|
2020-04-20 03:45:28 -04:00
|
|
|
use std::iter::Iterator;
|
2020-01-20 11:00:01 -05:00
|
|
|
|
2020-05-25 13:09:20 -04:00
|
|
|
#[cfg(target_pointer_width = "64")]
|
|
|
|
pub(crate) use beef::lean::Cow;
|
|
|
|
#[cfg(not(target_pointer_width = "64"))]
|
|
|
|
pub(crate) use beef::Cow;
|
|
|
|
|
2020-04-27 15:53:43 -04:00
|
|
|
use codemap::{Span, Spanned};
|
2020-04-12 19:37:12 -04:00
|
|
|
|
2020-04-20 03:45:28 -04:00
|
|
|
use peekmore::{PeekMore, PeekMoreIterator};
|
|
|
|
|
2020-04-27 15:53:43 -04:00
|
|
|
use crate::atrule::{AtRule, AtRuleKind, Function, Mixin};
|
2020-03-29 13:28:17 -04:00
|
|
|
pub use crate::error::{SassError, SassResult};
|
2020-04-27 15:53:43 -04:00
|
|
|
use crate::scope::{insert_global_var, Scope};
|
2020-02-24 15:07:18 -05:00
|
|
|
use crate::selector::Selector;
|
2020-01-20 11:00:01 -05:00
|
|
|
use crate::style::Style;
|
2020-04-27 15:53:43 -04:00
|
|
|
pub use crate::stylesheet::StyleSheet;
|
2020-03-29 13:28:17 -04:00
|
|
|
pub(crate) use crate::token::Token;
|
|
|
|
use crate::utils::{
|
2020-05-21 11:48:38 -04:00
|
|
|
devour_whitespace, eat_comment, eat_ident, eat_variable_value, peek_ident_no_interpolation,
|
|
|
|
peek_whitespace, read_until_closing_curly_brace, read_until_closing_paren, read_until_newline,
|
|
|
|
VariableDecl,
|
2020-03-29 13:28:17 -04:00
|
|
|
};
|
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 error;
|
|
|
|
mod imports;
|
|
|
|
mod lexer;
|
2020-04-05 23:20:47 -04:00
|
|
|
mod output;
|
2020-03-17 20:13:53 -04:00
|
|
|
mod scope;
|
2020-01-20 11:00:01 -05:00
|
|
|
mod selector;
|
|
|
|
mod style;
|
2020-04-27 15:53:43 -04:00
|
|
|
mod stylesheet;
|
2020-03-19 19:32:11 -04:00
|
|
|
mod token;
|
2020-03-19 16:24:31 -04:00
|
|
|
mod unit;
|
2020-01-20 11:00:01 -05:00
|
|
|
mod utils;
|
2020-01-25 09:58:53 -05:00
|
|
|
mod value;
|
2020-01-20 11:00:01 -05:00
|
|
|
|
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)
|
2020-02-29 16:13:57 -05:00
|
|
|
Style(Box<Style>),
|
2020-01-20 11:00:01 -05:00
|
|
|
/// 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
|
|
|
}
|
|
|
|
|
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 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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,
|
2020-04-12 19:37:12 -04:00
|
|
|
rules: Vec<Spanned<Stmt>>,
|
2020-01-20 11:00:01 -05:00
|
|
|
// 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>),
|
2020-02-01 19:33:56 -05:00
|
|
|
/// Several styles
|
|
|
|
Styles(Vec<Style>),
|
2020-01-20 11:00:01 -05:00
|
|
|
/// 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>>),
|
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
|
|
|
/// A multiline comment: `/* foobar */`
|
|
|
|
MultilineComment(String),
|
2020-02-22 15:34:32 -05:00
|
|
|
AtRule(AtRule),
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn eat_expr<I: Iterator<Item = Token>>(
|
2020-04-20 03:45:28 -04:00
|
|
|
toks: &mut PeekMoreIterator<I>,
|
2020-03-01 08:20:59 -05:00
|
|
|
scope: &mut Scope,
|
2020-01-20 11:00:01 -05:00
|
|
|
super_selector: &Selector,
|
2020-04-26 21:29:09 -04:00
|
|
|
content: Option<&[Spanned<Stmt>]>,
|
2020-04-12 19:37:12 -04:00
|
|
|
) -> SassResult<Option<Spanned<Expr>>> {
|
2020-01-20 11:00:01 -05:00
|
|
|
let mut values = Vec::with_capacity(5);
|
2020-05-02 12:25:53 -04:00
|
|
|
let mut span = match toks.peek() {
|
|
|
|
Some(tok) => tok.pos(),
|
|
|
|
None => return Ok(None),
|
2020-04-12 19:37:12 -04:00
|
|
|
};
|
2020-01-20 11:00:01 -05:00
|
|
|
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
|
|
|
':' => {
|
2020-05-17 11:09:21 -04:00
|
|
|
let tok = toks.next().unwrap();
|
|
|
|
values.push(tok);
|
2020-02-01 19:33:56 -05:00
|
|
|
if devour_whitespace(toks) {
|
2020-02-01 21:59:23 -05:00
|
|
|
let prop = Style::parse_property(
|
2020-04-20 03:45:28 -04:00
|
|
|
&mut values.into_iter().peekmore(),
|
2020-02-01 19:33:56 -05:00
|
|
|
scope,
|
|
|
|
super_selector,
|
2020-05-22 14:20:31 -04:00
|
|
|
String::new(),
|
2020-05-17 11:09:21 -04:00
|
|
|
tok.pos,
|
2020-02-17 07:18:54 -05:00
|
|
|
)?;
|
2020-04-12 19:37:12 -04:00
|
|
|
return Ok(Some(Spanned {
|
|
|
|
node: Style::from_tokens(toks, scope, super_selector, prop)?,
|
|
|
|
span,
|
|
|
|
}));
|
2020-02-01 19:33:56 -05:00
|
|
|
}
|
|
|
|
}
|
2020-03-29 13:28:17 -04:00
|
|
|
';' => {
|
2020-05-17 00:35:07 -04:00
|
|
|
let span_before = toks.next().unwrap().pos;
|
2020-01-20 11:00:01 -05:00
|
|
|
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
|
2020-04-20 03:45:28 -04:00
|
|
|
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 {
|
2020-05-22 14:20:31 -04:00
|
|
|
property: String::new(),
|
2020-04-12 19:37:12 -04:00
|
|
|
value: Value::Null.span(span),
|
|
|
|
})),
|
|
|
|
span,
|
|
|
|
}));
|
2020-02-29 15:54:13 -05:00
|
|
|
}
|
2020-05-17 00:35:07 -04:00
|
|
|
let property = Style::parse_property(
|
|
|
|
&mut v,
|
|
|
|
scope,
|
|
|
|
super_selector,
|
2020-05-22 14:20:31 -04:00
|
|
|
String::new(),
|
2020-05-17 00:35:07 -04:00
|
|
|
span_before,
|
|
|
|
)?;
|
2020-05-24 15:30:06 -04:00
|
|
|
let value = Style::parse_value(&mut v, scope, super_selector, span_before)?;
|
2020-04-12 19:37:12 -04:00
|
|
|
return Ok(Some(Spanned {
|
|
|
|
node: Expr::Style(Box::new(Style { property, value })),
|
|
|
|
span,
|
|
|
|
}));
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
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-05-25 00:57:59 -04:00
|
|
|
if let Some(Token { kind: ';', .. }) = toks.peek() {
|
2020-04-04 03:00:38 -04:00
|
|
|
toks.next();
|
|
|
|
}
|
|
|
|
devour_whitespace(toks);
|
2020-01-20 18:09:25 -05:00
|
|
|
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-04-20 03:45:28 -04:00
|
|
|
let mut v = values.into_iter().peekmore();
|
2020-05-17 00:35:07 -04:00
|
|
|
let property = Style::parse_property(
|
|
|
|
&mut v,
|
|
|
|
scope,
|
|
|
|
super_selector,
|
2020-05-22 14:20:31 -04:00
|
|
|
String::new(),
|
2020-05-17 00:35:07 -04:00
|
|
|
tok.pos,
|
|
|
|
)?;
|
2020-05-24 15:30:06 -04:00
|
|
|
let value = Style::parse_value(&mut v, scope, super_selector, tok.pos)?;
|
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
|
|
|
'{' => {
|
2020-01-20 11:00:01 -05:00
|
|
|
toks.next();
|
|
|
|
devour_whitespace(toks);
|
2020-04-12 19:37:12 -04:00
|
|
|
return Ok(Some(Spanned {
|
|
|
|
node: Expr::Selector(Selector::from_tokens(
|
2020-04-20 03:45:28 -04:00
|
|
|
&mut values.into_iter().peekmore(),
|
2020-04-12 19:37:12 -04:00
|
|
|
scope,
|
|
|
|
super_selector,
|
|
|
|
)?),
|
|
|
|
span,
|
|
|
|
}));
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
2020-03-29 13:28:17 -04:00
|
|
|
'$' => {
|
|
|
|
let tok = toks.next().unwrap();
|
2020-05-21 00:41:04 -04:00
|
|
|
|
|
|
|
if toks.peek().ok_or(("Expected identifier.", tok.pos))?.kind == '=' {
|
2020-03-29 13:28:17 -04:00
|
|
|
values.push(tok);
|
|
|
|
values.push(toks.next().unwrap());
|
|
|
|
continue;
|
|
|
|
}
|
2020-05-21 00:41:04 -04:00
|
|
|
|
2020-05-24 15:53:51 -04:00
|
|
|
let name = peek_ident_no_interpolation(toks, false, tok.pos)?;
|
2020-05-21 00:41:04 -04:00
|
|
|
let whitespace = peek_whitespace(toks);
|
|
|
|
|
|
|
|
if toks.peek().ok_or(("expected \":\".", name.span))?.kind == ':' {
|
2020-05-21 11:48:38 -04:00
|
|
|
toks.take(name.node.chars().count() + whitespace + 1)
|
|
|
|
.for_each(drop);
|
2020-01-20 11:00:01 -05:00
|
|
|
devour_whitespace(toks);
|
2020-03-17 20:13:53 -04:00
|
|
|
let VariableDecl {
|
|
|
|
val,
|
|
|
|
default,
|
|
|
|
global,
|
2020-05-24 15:30:06 -04:00
|
|
|
} = eat_variable_value(toks, scope, super_selector, name.span)?;
|
2020-03-17 20:13:53 -04:00
|
|
|
if global {
|
2020-04-12 19:37:12 -04:00
|
|
|
insert_global_var(&name.node, val.clone())?;
|
2020-03-24 00:43:09 -04:00
|
|
|
}
|
2020-04-23 18:14:42 -04:00
|
|
|
let var_exists = scope.var_exists(&name.node);
|
2020-04-24 20:13:22 -04:00
|
|
|
if !(default && var_exists) {
|
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!()
|
|
|
|
}
|
2020-01-20 11:00:01 -05:00
|
|
|
} else {
|
2020-04-01 19:35:04 -04:00
|
|
|
values.push(tok);
|
2020-05-21 00:41:04 -04:00
|
|
|
toks.reset_view();
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
|
|
|
}
|
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,
|
|
|
|
}));
|
2020-01-20 11:00:01 -05:00
|
|
|
} else {
|
|
|
|
values.push(tok);
|
|
|
|
}
|
|
|
|
}
|
2020-03-29 13:28:17 -04:00
|
|
|
'@' => {
|
2020-05-17 00:08:50 -04:00
|
|
|
let span = toks.next().unwrap().pos();
|
2020-05-24 12:47:04 -04:00
|
|
|
let rule = eat_ident(toks, scope, super_selector, span)?;
|
2020-04-12 19:37:12 -04:00
|
|
|
devour_whitespace(toks);
|
|
|
|
let rule = AtRule::from_tokens(
|
2020-05-24 12:47:04 -04:00
|
|
|
AtRuleKind::try_from(&rule)?,
|
2020-04-12 19:37:12 -04:00
|
|
|
span,
|
|
|
|
toks,
|
|
|
|
scope,
|
|
|
|
super_selector,
|
2020-04-26 21:29:09 -04:00
|
|
|
content,
|
2020-04-12 19:37:12 -04:00
|
|
|
)?;
|
|
|
|
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"),
|
2020-05-24 17:41:24 -04:00
|
|
|
a => Expr::AtRule(a),
|
2020-04-12 19:37:12 -04:00
|
|
|
},
|
|
|
|
span,
|
|
|
|
}));
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
2020-03-29 13:28:17 -04:00
|
|
|
'#' => {
|
2020-05-21 12:21:52 -04:00
|
|
|
let next = toks.next().unwrap();
|
|
|
|
values.push(next);
|
|
|
|
match toks.peek() {
|
|
|
|
Some(Token { kind: '{', .. }) => {
|
|
|
|
let next = toks.next().unwrap();
|
|
|
|
values.push(next);
|
2020-05-24 10:04:30 -04:00
|
|
|
values.extend(read_until_closing_curly_brace(toks)?);
|
2020-05-21 12:21:52 -04:00
|
|
|
if let Some(tok) = toks.next() {
|
|
|
|
values.push(tok);
|
|
|
|
} else {
|
|
|
|
return Err(("expected \"}\".", next.pos).into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Some(..) => {}
|
|
|
|
None => return Err(("expected \"{\".", next.pos).into()),
|
2020-01-20 11:00:01 -05:00
|
|
|
}
|
|
|
|
}
|
2020-04-19 20:22:31 -04:00
|
|
|
'\\' => {
|
2020-05-21 13:39:37 -04:00
|
|
|
let next = toks.next().unwrap();
|
|
|
|
values.push(next);
|
|
|
|
values.push(toks.next().ok_or(("expected \"}\".", next.pos))?);
|
2020-04-19 20:22:31 -04:00
|
|
|
}
|
2020-04-24 19:13:38 -04:00
|
|
|
// todo: this should only apply to special functions
|
|
|
|
// it is causing us to emit nothing on malformed input
|
|
|
|
'(' => {
|
|
|
|
values.push(toks.next().unwrap());
|
2020-05-24 10:04:30 -04:00
|
|
|
values.extend(read_until_closing_paren(toks)?);
|
2020-04-24 19:13:38 -04:00
|
|
|
}
|
2020-03-29 13:28:17 -04:00
|
|
|
_ => values.push(toks.next().unwrap()),
|
2020-01-20 11:00:01 -05:00
|
|
|
};
|
|
|
|
}
|
2020-05-24 07:43:54 -04:00
|
|
|
|
|
|
|
// if `values` is not empty, there was an unexpected toplevel token
|
|
|
|
// that should be part of a selector
|
|
|
|
if let Some(v) = values.pop() {
|
|
|
|
return Err(("expected \"{\".", v.pos).into());
|
|
|
|
}
|
|
|
|
|
2020-01-20 11:00:01 -05:00
|
|
|
Ok(None)
|
|
|
|
}
|