refactor parsing into multiple files
This commit is contained in:
parent
c5f5c4d464
commit
4d068596e3
121
src/parse/function.rs
Normal file
121
src/parse/function.rs
Normal file
@ -0,0 +1,121 @@
|
||||
use std::mem;
|
||||
|
||||
use codemap::Spanned;
|
||||
use peekmore::PeekMore;
|
||||
|
||||
use super::{Parser, Stmt};
|
||||
use crate::{
|
||||
args::CallArgs,
|
||||
atrule::Function,
|
||||
error::SassResult,
|
||||
utils::{read_until_closing_curly_brace, read_until_semicolon_or_closing_curly_brace},
|
||||
value::Value,
|
||||
Token,
|
||||
};
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
pub(super) fn parse_function(&mut self) -> SassResult<()> {
|
||||
if self.in_mixin {
|
||||
return Err((
|
||||
"Mixins may not contain function declarations.",
|
||||
self.span_before,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
self.whitespace_or_comment();
|
||||
let Spanned { node: name, span } = self.parse_identifier()?;
|
||||
self.whitespace_or_comment();
|
||||
let args = match self.toks.next() {
|
||||
Some(Token { kind: '(', .. }) => self.parse_func_args()?,
|
||||
Some(Token { pos, .. }) => return Err(("expected \"(\".", pos).into()),
|
||||
None => return Err(("expected \"(\".", span).into()),
|
||||
};
|
||||
|
||||
self.whitespace();
|
||||
|
||||
let mut body = read_until_closing_curly_brace(self.toks)?;
|
||||
body.push(self.toks.next().unwrap());
|
||||
self.whitespace();
|
||||
|
||||
let function = Function::new(self.scopes.last().clone(), args, body, span);
|
||||
|
||||
if self.at_root {
|
||||
self.global_scope.insert_fn(name, function);
|
||||
} else {
|
||||
self.scopes.last_mut().insert_fn(name, function);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn parse_return(&mut self) -> SassResult<Value> {
|
||||
let toks = read_until_semicolon_or_closing_curly_brace(self.toks)?;
|
||||
let v = self.parse_value_from_vec(toks)?;
|
||||
if let Some(Token { kind: ';', .. }) = self.toks.peek() {
|
||||
self.toks.next();
|
||||
}
|
||||
Ok(v.node)
|
||||
}
|
||||
|
||||
pub fn eval_function(&mut self, mut function: Function, args: CallArgs) -> SassResult<Value> {
|
||||
self.scopes.push(self.scopes.last().clone());
|
||||
self.eval_fn_args(&mut function, args)?;
|
||||
let mut return_value = Parser {
|
||||
toks: &mut function.body.into_iter().peekmore(),
|
||||
map: self.map,
|
||||
path: self.path,
|
||||
scopes: self.scopes,
|
||||
global_scope: self.global_scope,
|
||||
super_selectors: self.super_selectors,
|
||||
span_before: self.span_before,
|
||||
content: self.content.clone(),
|
||||
in_mixin: self.in_mixin,
|
||||
in_function: true,
|
||||
in_control_flow: self.in_control_flow,
|
||||
at_root: false,
|
||||
at_root_has_selector: self.at_root_has_selector,
|
||||
}
|
||||
.parse()?;
|
||||
self.scopes.pop();
|
||||
debug_assert!(return_value.len() <= 1);
|
||||
match return_value
|
||||
.pop()
|
||||
.ok_or(("Function finished without @return.", self.span_before))?
|
||||
{
|
||||
Stmt::Return(v) => Ok(v),
|
||||
_ => todo!("should be unreachable"),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_fn_args(&mut self, function: &mut Function, mut args: CallArgs) -> SassResult<()> {
|
||||
for (idx, arg) in function.args.0.iter_mut().enumerate() {
|
||||
if arg.is_variadic {
|
||||
let span = args.span();
|
||||
let arg_list = Value::ArgList(self.variadic_args(args)?);
|
||||
self.scopes.last_mut().insert_var(
|
||||
arg.name.clone(),
|
||||
Spanned {
|
||||
node: arg_list,
|
||||
span,
|
||||
},
|
||||
)?;
|
||||
break;
|
||||
}
|
||||
let val = match args.get(idx, arg.name.clone()) {
|
||||
Some(v) => self.parse_value_from_vec(v)?,
|
||||
None => match arg.default.as_mut() {
|
||||
Some(v) => self.parse_value_from_vec(mem::take(v))?,
|
||||
None => {
|
||||
return Err(
|
||||
(format!("Missing argument ${}.", &arg.name), args.span()).into()
|
||||
)
|
||||
}
|
||||
},
|
||||
};
|
||||
self.scopes
|
||||
.last_mut()
|
||||
.insert_var(mem::take(&mut arg.name), val)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
105
src/parse/import.rs
Normal file
105
src/parse/import.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use std::{
|
||||
ffi::{OsStr, OsString},
|
||||
fs,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use peekmore::PeekMore;
|
||||
|
||||
use crate::{error::SassResult, Token};
|
||||
|
||||
use crate::lexer::Lexer;
|
||||
|
||||
use super::{Parser, Stmt};
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
pub(super) fn import(&mut self) -> SassResult<Vec<Stmt>> {
|
||||
self.whitespace();
|
||||
let mut file_name = String::new();
|
||||
let next = match self.toks.next() {
|
||||
Some(v) => v,
|
||||
None => todo!("expected input after @import"),
|
||||
};
|
||||
match next.kind {
|
||||
q @ '"' | q @ '\'' => {
|
||||
file_name.push_str(
|
||||
&self
|
||||
.parse_quoted_string(q)?
|
||||
.node
|
||||
.unquote()
|
||||
.to_css_string(self.span_before)?,
|
||||
);
|
||||
}
|
||||
_ => return Err(("Expected string.", next.pos()).into()),
|
||||
}
|
||||
if let Some(t) = self.toks.peek() {
|
||||
if t.kind == ';' {
|
||||
self.toks.next();
|
||||
}
|
||||
}
|
||||
|
||||
self.whitespace();
|
||||
|
||||
let path: &Path = file_name.as_ref();
|
||||
|
||||
let mut rules = Vec::new();
|
||||
let path_buf = if path.is_absolute() {
|
||||
// todo: test for absolute path imports
|
||||
path.into()
|
||||
} else {
|
||||
self.path
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new(""))
|
||||
.join(path)
|
||||
};
|
||||
// todo: will panic if path ended in `..`
|
||||
let name = path_buf.file_name().unwrap();
|
||||
if path_buf.extension() == Some(OsStr::new(".css")) {
|
||||
// || name.starts_with("http://") || name.starts_with("https://") {
|
||||
todo!("css imports")
|
||||
}
|
||||
let mut p1 = path_buf.clone();
|
||||
p1.push(OsString::from("index.scss"));
|
||||
let mut p2 = path_buf.clone();
|
||||
p2.push(OsString::from("_index.scss"));
|
||||
let paths = [
|
||||
path_buf.with_file_name(name).with_extension("scss"),
|
||||
path_buf.with_file_name(format!("_{}.scss", name.to_str().unwrap())),
|
||||
path_buf,
|
||||
p1,
|
||||
p2,
|
||||
];
|
||||
for name in &paths {
|
||||
if name.is_file() {
|
||||
let file = self.map.add_file(
|
||||
name.to_string_lossy().into(),
|
||||
String::from_utf8(fs::read(name)?)?,
|
||||
);
|
||||
let rules2 = Parser {
|
||||
toks: &mut Lexer::new(&file)
|
||||
.collect::<Vec<Token>>()
|
||||
.into_iter()
|
||||
.peekmore(),
|
||||
map: &mut self.map,
|
||||
path: name.as_ref(),
|
||||
scopes: &mut self.scopes,
|
||||
global_scope: &mut self.global_scope,
|
||||
super_selectors: self.super_selectors,
|
||||
span_before: file.span.subspan(0, 0),
|
||||
content: self.content.clone(),
|
||||
in_mixin: self.in_mixin,
|
||||
in_function: self.in_function,
|
||||
in_control_flow: self.in_control_flow,
|
||||
at_root: self.at_root,
|
||||
at_root_has_selector: self.at_root_has_selector,
|
||||
}
|
||||
.parse()?;
|
||||
|
||||
rules.extend(rules2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(rules)
|
||||
}
|
||||
}
|
155
src/parse/mixin.rs
Normal file
155
src/parse/mixin.rs
Normal file
@ -0,0 +1,155 @@
|
||||
use std::mem;
|
||||
|
||||
use codemap::Spanned;
|
||||
|
||||
use crate::{
|
||||
args::{CallArgs, FuncArgs},
|
||||
atrule::Mixin,
|
||||
error::SassResult,
|
||||
scope::Scope,
|
||||
utils::read_until_closing_curly_brace,
|
||||
value::Value,
|
||||
Token,
|
||||
};
|
||||
|
||||
use super::{Parser, Stmt};
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
pub(super) fn parse_mixin(&mut self) -> SassResult<()> {
|
||||
self.whitespace();
|
||||
let Spanned { node: name, span } = self.parse_identifier()?;
|
||||
self.whitespace();
|
||||
let args = match self.toks.next() {
|
||||
Some(Token { kind: '(', .. }) => self.parse_func_args()?,
|
||||
Some(Token { kind: '{', .. }) => FuncArgs::new(),
|
||||
Some(t) => return Err(("expected \"{\".", t.pos()).into()),
|
||||
None => return Err(("expected \"{\".", span).into()),
|
||||
};
|
||||
|
||||
self.whitespace();
|
||||
|
||||
let mut body = read_until_closing_curly_brace(self.toks)?;
|
||||
body.push(self.toks.next().unwrap());
|
||||
|
||||
// todo: `@include` can only give content when `@content` is present within the body
|
||||
// if `@content` is *not* present and `@include` attempts to give a body, we throw an error
|
||||
// `Error: Mixin doesn't accept a content block.`
|
||||
//
|
||||
// this is blocked on figuring out just how to check for this. presumably we could have a check
|
||||
// not when parsing initially, but rather when `@include`ing to see if an `@content` was found.
|
||||
|
||||
let mixin = Mixin::new(Scope::new(), args, body, false);
|
||||
|
||||
if self.at_root {
|
||||
self.global_scope.insert_mixin(name, mixin);
|
||||
} else {
|
||||
self.scopes.last_mut().insert_mixin(name, mixin);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn parse_include(&mut self) -> SassResult<Vec<Stmt>> {
|
||||
self.whitespace_or_comment();
|
||||
let name = self.parse_identifier()?;
|
||||
|
||||
self.whitespace_or_comment();
|
||||
|
||||
let mut has_content = false;
|
||||
|
||||
let args = match self.toks.next() {
|
||||
Some(Token { kind: ';', .. }) => CallArgs::new(name.span),
|
||||
Some(Token { kind: '(', .. }) => {
|
||||
let tmp = self.parse_call_args()?;
|
||||
self.whitespace_or_comment();
|
||||
if let Some(tok) = self.toks.peek() {
|
||||
match tok.kind {
|
||||
';' => {
|
||||
self.toks.next();
|
||||
}
|
||||
'{' => {
|
||||
self.toks.next();
|
||||
has_content = true
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
tmp
|
||||
}
|
||||
Some(Token { kind: '{', .. }) => {
|
||||
has_content = true;
|
||||
CallArgs::new(name.span)
|
||||
}
|
||||
Some(Token { pos, .. }) => return Err(("expected \"{\".", pos).into()),
|
||||
None => return Err(("expected \"{\".", name.span).into()),
|
||||
};
|
||||
|
||||
self.whitespace();
|
||||
|
||||
let content = if has_content {
|
||||
Some(self.parse_content()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut mixin = self.scopes.last().get_mixin(name, self.global_scope)?;
|
||||
self.eval_mixin_args(&mut mixin, args)?;
|
||||
|
||||
self.scopes.push(mixin.scope);
|
||||
|
||||
let body = Parser {
|
||||
toks: &mut mixin.body,
|
||||
map: self.map,
|
||||
path: self.path,
|
||||
scopes: self.scopes,
|
||||
global_scope: self.global_scope,
|
||||
super_selectors: self.super_selectors,
|
||||
span_before: self.span_before,
|
||||
in_mixin: true,
|
||||
in_function: self.in_function,
|
||||
in_control_flow: self.in_control_flow,
|
||||
content,
|
||||
at_root: false,
|
||||
at_root_has_selector: self.at_root_has_selector,
|
||||
}
|
||||
.parse()?;
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
pub(super) fn parse_content(&mut self) -> SassResult<Vec<Stmt>> {
|
||||
self.parse_stmt()
|
||||
}
|
||||
|
||||
fn eval_mixin_args(&mut self, mixin: &mut Mixin, mut args: CallArgs) -> SassResult<()> {
|
||||
let mut scope = self.scopes.last().clone();
|
||||
for (idx, arg) in mixin.args.0.iter_mut().enumerate() {
|
||||
if arg.is_variadic {
|
||||
let span = args.span();
|
||||
// todo: does this get the most recent scope?
|
||||
let arg_list = Value::ArgList(self.variadic_args(args)?);
|
||||
mixin.scope.insert_var(
|
||||
arg.name.clone(),
|
||||
Spanned {
|
||||
node: arg_list,
|
||||
span,
|
||||
},
|
||||
)?;
|
||||
break;
|
||||
}
|
||||
let val = match args.get(idx, arg.name.clone()) {
|
||||
Some(v) => self.parse_value_from_vec(v)?,
|
||||
None => match arg.default.as_mut() {
|
||||
Some(v) => self.parse_value_from_vec(mem::take(v))?,
|
||||
None => {
|
||||
return Err(
|
||||
(format!("Missing argument ${}.", &arg.name), args.span()).into()
|
||||
)
|
||||
}
|
||||
},
|
||||
};
|
||||
scope.insert_var(arg.name.clone(), val.clone())?;
|
||||
mixin.scope.insert_var(mem::take(&mut arg.name), val)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
773
src/parse/mod.rs
773
src/parse/mod.rs
@ -1,29 +1,20 @@
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
ffi::{OsStr, OsString},
|
||||
fs, mem,
|
||||
path::Path,
|
||||
vec::IntoIter,
|
||||
};
|
||||
use std::{convert::TryFrom, path::Path, vec::IntoIter};
|
||||
|
||||
use codemap::{CodeMap, Span, Spanned};
|
||||
use num_traits::cast::ToPrimitive;
|
||||
use peekmore::{PeekMore, PeekMoreIterator};
|
||||
|
||||
use crate::{
|
||||
args::{CallArgs, FuncArgs},
|
||||
atrule::{AtRuleKind, Function, Mixin},
|
||||
common::{Brackets, Identifier, ListSeparator},
|
||||
atrule::AtRuleKind,
|
||||
common::{Brackets, ListSeparator},
|
||||
error::SassResult,
|
||||
lexer::Lexer,
|
||||
scope::Scope,
|
||||
selector::{Selector, SelectorParser},
|
||||
style::Style,
|
||||
unit::Unit,
|
||||
utils::{
|
||||
is_name, is_name_start, peek_ident_no_interpolation, read_until_closing_curly_brace,
|
||||
read_until_closing_paren, read_until_closing_quote, read_until_newline,
|
||||
read_until_open_curly_brace, read_until_semicolon_or_closing_curly_brace,
|
||||
peek_ident_no_interpolation, read_until_closing_curly_brace, read_until_open_curly_brace,
|
||||
read_until_semicolon_or_closing_curly_brace,
|
||||
},
|
||||
value::{Number, Value},
|
||||
{Cow, Token},
|
||||
@ -33,31 +24,19 @@ use common::{Branch, NeverEmptyVec, SelectorOrStyle};
|
||||
|
||||
mod args;
|
||||
pub mod common;
|
||||
mod function;
|
||||
mod ident;
|
||||
mod import;
|
||||
mod mixin;
|
||||
mod style;
|
||||
mod value;
|
||||
mod variable;
|
||||
|
||||
pub(crate) enum Comment {
|
||||
Silent,
|
||||
Loud(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct VariableValue {
|
||||
value: Spanned<Value>,
|
||||
global: bool,
|
||||
default: bool,
|
||||
}
|
||||
|
||||
impl VariableValue {
|
||||
pub const fn new(value: Spanned<Value>, global: bool, default: bool) -> Self {
|
||||
Self {
|
||||
value,
|
||||
global,
|
||||
default,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum Stmt {
|
||||
RuleSet {
|
||||
@ -296,256 +275,6 @@ impl<'a> Parser<'a> {
|
||||
Ok(stmts)
|
||||
}
|
||||
|
||||
/// Determines whether the parser is looking at a style or a selector
|
||||
///
|
||||
/// When parsing the children of a style rule, property declarations,
|
||||
/// namespaced variable declarations, and nested style rules can all begin
|
||||
/// with bare identifiers. In order to know which statement type to produce,
|
||||
/// we need to disambiguate them. We use the following criteria:
|
||||
///
|
||||
/// * If the entity starts with an identifier followed by a period and a
|
||||
/// dollar sign, it's a variable declaration. This is the simplest case,
|
||||
/// because `.$` is used in and only in variable declarations.
|
||||
///
|
||||
/// * If the entity doesn't start with an identifier followed by a colon,
|
||||
/// it's a selector. There are some additional mostly-unimportant cases
|
||||
/// here to support various declaration hacks.
|
||||
///
|
||||
/// * If the colon is followed by another colon, it's a selector.
|
||||
///
|
||||
/// * Otherwise, if the colon is followed by anything other than
|
||||
/// interpolation or a character that's valid as the beginning of an
|
||||
/// identifier, it's a declaration.
|
||||
///
|
||||
/// * If the colon is followed by interpolation or a valid identifier, try
|
||||
/// parsing it as a declaration value. If this fails, backtrack and parse
|
||||
/// it as a selector.
|
||||
///
|
||||
/// * If the declaration value is valid but is followed by "{", backtrack and
|
||||
/// parse it as a selector anyway. This ensures that ".foo:bar {" is always
|
||||
/// parsed as a selector and never as a property with nested properties
|
||||
/// beneath it.
|
||||
// todo: potentially we read the property to a string already since properties
|
||||
// are more common than selectors? this seems to be annihilating our performance
|
||||
fn parse_style_value_when_no_space_after_semicolon(&mut self) -> Option<Vec<Token>> {
|
||||
let mut toks = Vec::new();
|
||||
while let Some(tok) = self.toks.peek() {
|
||||
match tok.kind {
|
||||
';' | '}' => {
|
||||
self.toks.reset_view();
|
||||
break;
|
||||
}
|
||||
'{' => {
|
||||
self.toks.reset_view();
|
||||
return None;
|
||||
}
|
||||
'(' => {
|
||||
toks.push(*tok);
|
||||
self.toks.peek_forward(1);
|
||||
let mut scope = 0;
|
||||
while let Some(tok) = self.toks.peek() {
|
||||
match tok.kind {
|
||||
')' => {
|
||||
if scope == 0 {
|
||||
toks.push(*tok);
|
||||
self.toks.peek_forward(1);
|
||||
break;
|
||||
} else {
|
||||
scope -= 1;
|
||||
toks.push(*tok);
|
||||
self.toks.peek_forward(1);
|
||||
}
|
||||
}
|
||||
'(' => {
|
||||
toks.push(*tok);
|
||||
self.toks.peek_forward(1);
|
||||
scope += 1;
|
||||
}
|
||||
_ => {
|
||||
toks.push(*tok);
|
||||
self.toks.peek_forward(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
toks.push(*tok);
|
||||
self.toks.peek_forward(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(toks)
|
||||
}
|
||||
|
||||
fn is_selector_or_style(&mut self) -> SassResult<SelectorOrStyle> {
|
||||
if let Some(first_char) = self.toks.peek() {
|
||||
if first_char.kind == '#' {
|
||||
if !matches!(self.toks.peek_forward(1), Some(Token { kind: '{', .. })) {
|
||||
self.toks.reset_view();
|
||||
return Ok(SelectorOrStyle::Selector(String::new()));
|
||||
}
|
||||
self.toks.reset_view();
|
||||
} else if !is_name_start(first_char.kind) && first_char.kind != '-' {
|
||||
return Ok(SelectorOrStyle::Selector(String::new()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut property = self.parse_identifier()?.node;
|
||||
let whitespace_after_property = self.whitespace();
|
||||
|
||||
if let Some(Token { kind: ':', .. }) = self.toks.peek() {
|
||||
self.toks.next();
|
||||
if let Some(Token { kind, .. }) = self.toks.peek() {
|
||||
return Ok(match kind {
|
||||
':' => {
|
||||
if whitespace_after_property {
|
||||
property.push(' ');
|
||||
}
|
||||
property.push(':');
|
||||
SelectorOrStyle::Selector(property)
|
||||
}
|
||||
c if is_name(*c) => {
|
||||
if let Some(toks) = self.parse_style_value_when_no_space_after_semicolon() {
|
||||
let len = toks.len();
|
||||
if let Ok(val) = self.parse_value_from_vec(toks) {
|
||||
self.toks.take(len).for_each(drop);
|
||||
return Ok(SelectorOrStyle::Style(property, Some(Box::new(val))));
|
||||
}
|
||||
}
|
||||
|
||||
if whitespace_after_property {
|
||||
property.push(' ');
|
||||
}
|
||||
property.push(':');
|
||||
return Ok(SelectorOrStyle::Selector(property));
|
||||
}
|
||||
_ => SelectorOrStyle::Style(property, None),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if whitespace_after_property {
|
||||
property.push(' ');
|
||||
}
|
||||
return Ok(SelectorOrStyle::Selector(property));
|
||||
}
|
||||
Err(("expected \"{\".", self.span_before).into())
|
||||
}
|
||||
|
||||
fn parse_property(&mut self, mut super_property: String) -> SassResult<String> {
|
||||
let property = self.parse_identifier()?;
|
||||
self.whitespace_or_comment();
|
||||
if let Some(Token { kind: ':', .. }) = self.toks.peek() {
|
||||
self.toks.next();
|
||||
self.whitespace_or_comment();
|
||||
} else {
|
||||
return Err(("Expected \":\".", property.span).into());
|
||||
}
|
||||
|
||||
if super_property.is_empty() {
|
||||
Ok(property.node)
|
||||
} else {
|
||||
super_property.reserve(1 + property.node.len());
|
||||
super_property.push('-');
|
||||
super_property.push_str(&property.node);
|
||||
Ok(super_property)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_style_value(&mut self) -> SassResult<Spanned<Value>> {
|
||||
self.parse_value()
|
||||
}
|
||||
|
||||
fn parse_style_group(&mut self, super_property: String) -> SassResult<Vec<Style>> {
|
||||
let mut styles = Vec::new();
|
||||
self.whitespace();
|
||||
while let Some(tok) = self.toks.peek().cloned() {
|
||||
match tok.kind {
|
||||
'{' => {
|
||||
self.toks.next();
|
||||
self.whitespace();
|
||||
loop {
|
||||
let property = self.parse_property(super_property.clone())?;
|
||||
if let Some(tok) = self.toks.peek() {
|
||||
if tok.kind == '{' {
|
||||
styles.append(&mut self.parse_style_group(property)?);
|
||||
self.whitespace();
|
||||
if let Some(tok) = self.toks.peek() {
|
||||
if tok.kind == '}' {
|
||||
self.toks.next();
|
||||
self.whitespace();
|
||||
return Ok(styles);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let value = self.parse_style_value()?;
|
||||
match self.toks.peek() {
|
||||
Some(Token { kind: '}', .. }) => {
|
||||
styles.push(Style { property, value });
|
||||
}
|
||||
Some(Token { kind: ';', .. }) => {
|
||||
self.toks.next();
|
||||
self.whitespace();
|
||||
styles.push(Style { property, value });
|
||||
}
|
||||
Some(Token { kind: '{', .. }) => {
|
||||
styles.push(Style {
|
||||
property: property.clone(),
|
||||
value,
|
||||
});
|
||||
styles.append(&mut self.parse_style_group(property)?);
|
||||
}
|
||||
Some(..) | None => {
|
||||
self.whitespace();
|
||||
styles.push(Style { property, value });
|
||||
}
|
||||
}
|
||||
if let Some(tok) = self.toks.peek() {
|
||||
match tok.kind {
|
||||
'}' => {
|
||||
self.toks.next();
|
||||
self.whitespace();
|
||||
return Ok(styles);
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let value = self.parse_style_value()?;
|
||||
let t = self
|
||||
.toks
|
||||
.peek()
|
||||
.ok_or(("expected more input.", value.span))?;
|
||||
match t.kind {
|
||||
';' => {
|
||||
self.toks.next();
|
||||
self.whitespace();
|
||||
}
|
||||
'{' => {
|
||||
let mut v = vec![Style {
|
||||
property: super_property.clone(),
|
||||
value,
|
||||
}];
|
||||
v.append(&mut self.parse_style_group(super_property)?);
|
||||
return Ok(v);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return Ok(vec![Style {
|
||||
property: super_property,
|
||||
value,
|
||||
}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(styles)
|
||||
}
|
||||
|
||||
pub fn parse_selector(
|
||||
&mut self,
|
||||
allows_parent: bool,
|
||||
@ -638,155 +367,6 @@ impl<'a> Parser<'a> {
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_variable_declaration(&mut self) -> SassResult<()> {
|
||||
assert!(matches!(self.toks.next(), Some(Token { kind: '$', .. })));
|
||||
let ident: Identifier = self.parse_identifier_no_interpolation(false)?.node.into();
|
||||
self.whitespace();
|
||||
if !matches!(self.toks.next(), Some(Token { kind: ':', .. })) {
|
||||
return Err(("expected \":\".", self.span_before).into());
|
||||
}
|
||||
let value = self.parse_variable_value()?;
|
||||
|
||||
if value.global && !value.default {
|
||||
self.global_scope
|
||||
.insert_var(ident.clone(), value.value.clone())?;
|
||||
}
|
||||
|
||||
if value.default {
|
||||
if self.at_root && !self.in_control_flow {
|
||||
if !self.global_scope.var_exists_no_global(&ident) {
|
||||
self.global_scope.insert_var(ident, value.value)?;
|
||||
}
|
||||
} else {
|
||||
if value.global && !self.global_scope.var_exists_no_global(&ident) {
|
||||
self.global_scope
|
||||
.insert_var(ident.clone(), value.value.clone())?;
|
||||
}
|
||||
if !self.scopes.last().var_exists_no_global(&ident) {
|
||||
self.scopes.last_mut().insert_var(ident, value.value)?;
|
||||
}
|
||||
}
|
||||
} else if self.at_root {
|
||||
if self.in_control_flow {
|
||||
if self.global_scope.var_exists_no_global(&ident) {
|
||||
self.global_scope.insert_var(ident, value.value)?;
|
||||
} else {
|
||||
self.scopes.last_mut().insert_var(ident, value.value)?;
|
||||
}
|
||||
} else {
|
||||
self.global_scope.insert_var(ident, value.value)?;
|
||||
}
|
||||
} else {
|
||||
let len = self.scopes.len();
|
||||
for (_, scope) in self
|
||||
.scopes
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.filter(|(i, _)| *i != len)
|
||||
{
|
||||
if scope.var_exists_no_global(&ident) {
|
||||
scope.insert_var(ident.clone(), value.value.clone())?;
|
||||
}
|
||||
}
|
||||
self.scopes.last_mut().insert_var(ident, value.value)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_variable_value(&mut self) -> SassResult<VariableValue> {
|
||||
let mut default = false;
|
||||
let mut global = false;
|
||||
|
||||
let mut val_toks = Vec::new();
|
||||
let mut nesting = 0;
|
||||
while let Some(tok) = self.toks.peek() {
|
||||
match tok.kind {
|
||||
';' => {
|
||||
self.toks.next();
|
||||
break;
|
||||
}
|
||||
'\\' => {
|
||||
val_toks.push(self.toks.next().unwrap());
|
||||
if self.toks.peek().is_some() {
|
||||
val_toks.push(self.toks.next().unwrap());
|
||||
}
|
||||
}
|
||||
'"' | '\'' => {
|
||||
let quote = self.toks.next().unwrap();
|
||||
val_toks.push(quote);
|
||||
val_toks.extend(read_until_closing_quote(self.toks, quote.kind)?);
|
||||
}
|
||||
'#' => {
|
||||
val_toks.push(self.toks.next().unwrap());
|
||||
match self.toks.peek() {
|
||||
Some(Token { kind: '{', .. }) => nesting += 1,
|
||||
Some(Token { kind: ';', .. }) => break,
|
||||
Some(Token { kind: '}', .. }) => {
|
||||
if nesting == 0 {
|
||||
break;
|
||||
} else {
|
||||
nesting -= 1;
|
||||
}
|
||||
}
|
||||
Some(..) | None => {}
|
||||
}
|
||||
val_toks.push(self.toks.next().unwrap());
|
||||
}
|
||||
'{' => break,
|
||||
'}' => {
|
||||
if nesting == 0 {
|
||||
break;
|
||||
} else {
|
||||
nesting -= 1;
|
||||
val_toks.push(self.toks.next().unwrap());
|
||||
}
|
||||
}
|
||||
'/' => {
|
||||
let next = self.toks.next().unwrap();
|
||||
match self.toks.peek() {
|
||||
Some(Token { kind: '/', .. }) => read_until_newline(self.toks),
|
||||
Some(..) | None => val_toks.push(next),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
'(' => {
|
||||
val_toks.push(self.toks.next().unwrap());
|
||||
val_toks.extend(read_until_closing_paren(self.toks)?);
|
||||
}
|
||||
'!' => {
|
||||
let pos = tok.pos();
|
||||
if self.toks.peek_forward(1).is_none() {
|
||||
return Err(("Expected identifier.", pos).into());
|
||||
}
|
||||
// todo: it should not be possible to declare the same flag more than once
|
||||
let mut ident = peek_ident_no_interpolation(self.toks, false, pos)?;
|
||||
ident.node.make_ascii_lowercase();
|
||||
match ident.node.as_str() {
|
||||
"global" => {
|
||||
self.toks.take(7).for_each(drop);
|
||||
global = true;
|
||||
}
|
||||
"default" => {
|
||||
self.toks.take(8).for_each(drop);
|
||||
default = true;
|
||||
}
|
||||
"important" => {
|
||||
self.toks.reset_view();
|
||||
val_toks.push(self.toks.next().unwrap());
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
return Err(("Invalid flag name.", ident.span).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => val_toks.push(self.toks.next().unwrap()),
|
||||
}
|
||||
}
|
||||
let val = self.parse_value_from_vec(val_toks)?;
|
||||
Ok(VariableValue::new(val, global, default))
|
||||
}
|
||||
|
||||
/// Eat and return the contents of a comment.
|
||||
///
|
||||
/// This function assumes that the starting "/" has already been consumed
|
||||
@ -905,145 +485,6 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
fn parse_mixin(&mut self) -> SassResult<()> {
|
||||
self.whitespace();
|
||||
let Spanned { node: name, span } = self.parse_identifier()?;
|
||||
self.whitespace();
|
||||
let args = match self.toks.next() {
|
||||
Some(Token { kind: '(', .. }) => self.parse_func_args()?,
|
||||
Some(Token { kind: '{', .. }) => FuncArgs::new(),
|
||||
Some(t) => return Err(("expected \"{\".", t.pos()).into()),
|
||||
None => return Err(("expected \"{\".", span).into()),
|
||||
};
|
||||
|
||||
self.whitespace();
|
||||
|
||||
let mut body = read_until_closing_curly_brace(self.toks)?;
|
||||
body.push(self.toks.next().unwrap());
|
||||
|
||||
// todo: `@include` can only give content when `@content` is present within the body
|
||||
// if `@content` is *not* present and `@include` attempts to give a body, we throw an error
|
||||
// `Error: Mixin doesn't accept a content block.`
|
||||
//
|
||||
// this is blocked on figuring out just how to check for this. presumably we could have a check
|
||||
// not when parsing initially, but rather when `@include`ing to see if an `@content` was found.
|
||||
|
||||
let mixin = Mixin::new(Scope::new(), args, body, false);
|
||||
|
||||
if self.at_root {
|
||||
self.global_scope.insert_mixin(name, mixin);
|
||||
} else {
|
||||
self.scopes.last_mut().insert_mixin(name, mixin);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_include(&mut self) -> SassResult<Vec<Stmt>> {
|
||||
self.whitespace_or_comment();
|
||||
let name = self.parse_identifier()?;
|
||||
|
||||
self.whitespace_or_comment();
|
||||
|
||||
let mut has_content = false;
|
||||
|
||||
let args = match self.toks.next() {
|
||||
Some(Token { kind: ';', .. }) => CallArgs::new(name.span),
|
||||
Some(Token { kind: '(', .. }) => {
|
||||
let tmp = self.parse_call_args()?;
|
||||
self.whitespace_or_comment();
|
||||
if let Some(tok) = self.toks.peek() {
|
||||
match tok.kind {
|
||||
';' => {
|
||||
self.toks.next();
|
||||
}
|
||||
'{' => {
|
||||
self.toks.next();
|
||||
has_content = true
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
tmp
|
||||
}
|
||||
Some(Token { kind: '{', .. }) => {
|
||||
has_content = true;
|
||||
CallArgs::new(name.span)
|
||||
}
|
||||
Some(Token { pos, .. }) => return Err(("expected \"{\".", pos).into()),
|
||||
None => return Err(("expected \"{\".", name.span).into()),
|
||||
};
|
||||
|
||||
self.whitespace();
|
||||
|
||||
let content = if has_content {
|
||||
Some(self.parse_content()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut mixin = self.scopes.last().get_mixin(name, self.global_scope)?;
|
||||
self.eval_mixin_args(&mut mixin, args)?;
|
||||
|
||||
self.scopes.push(mixin.scope);
|
||||
|
||||
let body = Parser {
|
||||
toks: &mut mixin.body,
|
||||
map: self.map,
|
||||
path: self.path,
|
||||
scopes: self.scopes,
|
||||
global_scope: self.global_scope,
|
||||
super_selectors: self.super_selectors,
|
||||
span_before: self.span_before,
|
||||
in_mixin: true,
|
||||
in_function: self.in_function,
|
||||
in_control_flow: self.in_control_flow,
|
||||
content,
|
||||
at_root: false,
|
||||
at_root_has_selector: self.at_root_has_selector,
|
||||
}
|
||||
.parse()?;
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
fn parse_content(&mut self) -> SassResult<Vec<Stmt>> {
|
||||
self.parse_stmt()
|
||||
}
|
||||
|
||||
fn parse_function(&mut self) -> SassResult<()> {
|
||||
if self.in_mixin {
|
||||
return Err((
|
||||
"Mixins may not contain function declarations.",
|
||||
self.span_before,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
self.whitespace_or_comment();
|
||||
let Spanned { node: name, span } = self.parse_identifier()?;
|
||||
self.whitespace_or_comment();
|
||||
let args = match self.toks.next() {
|
||||
Some(Token { kind: '(', .. }) => self.parse_func_args()?,
|
||||
Some(Token { pos, .. }) => return Err(("expected \"(\".", pos).into()),
|
||||
None => return Err(("expected \"(\".", span).into()),
|
||||
};
|
||||
|
||||
self.whitespace();
|
||||
|
||||
let mut body = read_until_closing_curly_brace(self.toks)?;
|
||||
body.push(self.toks.next().unwrap());
|
||||
self.whitespace();
|
||||
|
||||
let function = Function::new(self.scopes.last().clone(), args, body, span);
|
||||
|
||||
if self.at_root {
|
||||
self.global_scope.insert_fn(name, function);
|
||||
} else {
|
||||
self.scopes.last_mut().insert_fn(name, function);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_if(&mut self) -> SassResult<Vec<Stmt>> {
|
||||
self.whitespace_or_comment();
|
||||
let mut branches = Vec::new();
|
||||
@ -1516,110 +957,6 @@ impl<'a> Parser<'a> {
|
||||
Ok(stmts)
|
||||
}
|
||||
|
||||
fn parse_return(&mut self) -> SassResult<Value> {
|
||||
let toks = read_until_semicolon_or_closing_curly_brace(self.toks)?;
|
||||
let v = self.parse_value_from_vec(toks)?;
|
||||
if let Some(Token { kind: ';', .. }) = self.toks.peek() {
|
||||
self.toks.next();
|
||||
}
|
||||
Ok(v.node)
|
||||
}
|
||||
|
||||
pub fn eval_function(&mut self, mut function: Function, args: CallArgs) -> SassResult<Value> {
|
||||
self.scopes.push(self.scopes.last().clone());
|
||||
self.eval_fn_args(&mut function, args)?;
|
||||
let mut return_value = Parser {
|
||||
toks: &mut function.body.into_iter().peekmore(),
|
||||
map: self.map,
|
||||
path: self.path,
|
||||
scopes: self.scopes,
|
||||
global_scope: self.global_scope,
|
||||
super_selectors: self.super_selectors,
|
||||
span_before: self.span_before,
|
||||
content: self.content.clone(),
|
||||
in_mixin: self.in_mixin,
|
||||
in_function: true,
|
||||
in_control_flow: self.in_control_flow,
|
||||
at_root: false,
|
||||
at_root_has_selector: self.at_root_has_selector,
|
||||
}
|
||||
.parse()?;
|
||||
self.scopes.pop();
|
||||
debug_assert!(return_value.len() <= 1);
|
||||
match return_value
|
||||
.pop()
|
||||
.ok_or(("Function finished without @return.", self.span_before))?
|
||||
{
|
||||
Stmt::Return(v) => Ok(v),
|
||||
_ => todo!("should be unreachable"),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_fn_args(&mut self, function: &mut Function, mut args: CallArgs) -> SassResult<()> {
|
||||
for (idx, arg) in function.args.0.iter_mut().enumerate() {
|
||||
if arg.is_variadic {
|
||||
let span = args.span();
|
||||
let arg_list = Value::ArgList(self.variadic_args(args)?);
|
||||
self.scopes.last_mut().insert_var(
|
||||
arg.name.clone(),
|
||||
Spanned {
|
||||
node: arg_list,
|
||||
span,
|
||||
},
|
||||
)?;
|
||||
break;
|
||||
}
|
||||
let val = match args.get(idx, arg.name.clone()) {
|
||||
Some(v) => self.parse_value_from_vec(v)?,
|
||||
None => match arg.default.as_mut() {
|
||||
Some(v) => self.parse_value_from_vec(mem::take(v))?,
|
||||
None => {
|
||||
return Err(
|
||||
(format!("Missing argument ${}.", &arg.name), args.span()).into()
|
||||
)
|
||||
}
|
||||
},
|
||||
};
|
||||
self.scopes
|
||||
.last_mut()
|
||||
.insert_var(mem::take(&mut arg.name), val)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn eval_mixin_args(&mut self, mixin: &mut Mixin, mut args: CallArgs) -> SassResult<()> {
|
||||
let mut scope = self.scopes.last().clone();
|
||||
for (idx, arg) in mixin.args.0.iter_mut().enumerate() {
|
||||
if arg.is_variadic {
|
||||
let span = args.span();
|
||||
// todo: does this get the most recent scope?
|
||||
let arg_list = Value::ArgList(self.variadic_args(args)?);
|
||||
mixin.scope.insert_var(
|
||||
arg.name.clone(),
|
||||
Spanned {
|
||||
node: arg_list,
|
||||
span,
|
||||
},
|
||||
)?;
|
||||
break;
|
||||
}
|
||||
let val = match args.get(idx, arg.name.clone()) {
|
||||
Some(v) => self.parse_value_from_vec(v)?,
|
||||
None => match arg.default.as_mut() {
|
||||
Some(v) => self.parse_value_from_vec(mem::take(v))?,
|
||||
None => {
|
||||
return Err(
|
||||
(format!("Missing argument ${}.", &arg.name), args.span()).into()
|
||||
)
|
||||
}
|
||||
},
|
||||
};
|
||||
scope.insert_var(arg.name.clone(), val.clone())?;
|
||||
mixin.scope.insert_var(mem::take(&mut arg.name), val)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_unknown_at_rule(&mut self, name: String) -> SassResult<Stmt> {
|
||||
let mut params = String::new();
|
||||
self.whitespace();
|
||||
@ -1685,96 +1022,6 @@ impl<'a> Parser<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
fn import(&mut self) -> SassResult<Vec<Stmt>> {
|
||||
self.whitespace();
|
||||
let mut file_name = String::new();
|
||||
let next = match self.toks.next() {
|
||||
Some(v) => v,
|
||||
None => todo!("expected input after @import"),
|
||||
};
|
||||
match next.kind {
|
||||
q @ '"' | q @ '\'' => {
|
||||
file_name.push_str(
|
||||
&self
|
||||
.parse_quoted_string(q)?
|
||||
.node
|
||||
.unquote()
|
||||
.to_css_string(self.span_before)?,
|
||||
);
|
||||
}
|
||||
_ => return Err(("Expected string.", next.pos()).into()),
|
||||
}
|
||||
if let Some(t) = self.toks.peek() {
|
||||
if t.kind == ';' {
|
||||
self.toks.next();
|
||||
}
|
||||
}
|
||||
|
||||
self.whitespace();
|
||||
|
||||
let path: &Path = file_name.as_ref();
|
||||
|
||||
let mut rules = Vec::new();
|
||||
let path_buf = if path.is_absolute() {
|
||||
// todo: test for absolute path imports
|
||||
path.into()
|
||||
} else {
|
||||
self.path
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new(""))
|
||||
.join(path)
|
||||
};
|
||||
// todo: will panic if path ended in `..`
|
||||
let name = path_buf.file_name().unwrap();
|
||||
if path_buf.extension() == Some(OsStr::new(".css")) {
|
||||
// || name.starts_with("http://") || name.starts_with("https://") {
|
||||
todo!("css imports")
|
||||
}
|
||||
let mut p1 = path_buf.clone();
|
||||
p1.push(OsString::from("index.scss"));
|
||||
let mut p2 = path_buf.clone();
|
||||
p2.push(OsString::from("_index.scss"));
|
||||
let paths = [
|
||||
path_buf.with_file_name(name).with_extension("scss"),
|
||||
path_buf.with_file_name(format!("_{}.scss", name.to_str().unwrap())),
|
||||
path_buf,
|
||||
p1,
|
||||
p2,
|
||||
];
|
||||
for name in &paths {
|
||||
if name.is_file() {
|
||||
let file = self.map.add_file(
|
||||
name.to_string_lossy().into(),
|
||||
String::from_utf8(fs::read(name)?)?,
|
||||
);
|
||||
let rules2 = Parser {
|
||||
toks: &mut Lexer::new(&file)
|
||||
.collect::<Vec<Token>>()
|
||||
.into_iter()
|
||||
.peekmore(),
|
||||
map: &mut self.map,
|
||||
path: name.as_ref(),
|
||||
scopes: &mut self.scopes,
|
||||
global_scope: &mut self.global_scope,
|
||||
super_selectors: self.super_selectors,
|
||||
span_before: file.span.subspan(0, 0),
|
||||
content: self.content.clone(),
|
||||
in_mixin: self.in_mixin,
|
||||
in_function: self.in_function,
|
||||
in_control_flow: self.in_control_flow,
|
||||
at_root: self.at_root,
|
||||
at_root_has_selector: self.at_root_has_selector,
|
||||
}
|
||||
.parse()?;
|
||||
|
||||
rules.extend(rules2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(rules)
|
||||
}
|
||||
|
||||
fn parse_media(&mut self) -> SassResult<Stmt> {
|
||||
let mut params = String::new();
|
||||
self.whitespace();
|
||||
|
265
src/parse/style.rs
Normal file
265
src/parse/style.rs
Normal file
@ -0,0 +1,265 @@
|
||||
use codemap::Spanned;
|
||||
|
||||
use crate::{
|
||||
error::SassResult,
|
||||
style::Style,
|
||||
utils::{is_name, is_name_start},
|
||||
value::Value,
|
||||
Token,
|
||||
};
|
||||
|
||||
use super::common::SelectorOrStyle;
|
||||
|
||||
use super::Parser;
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
/// Determines whether the parser is looking at a style or a selector
|
||||
///
|
||||
/// When parsing the children of a style rule, property declarations,
|
||||
/// namespaced variable declarations, and nested style rules can all begin
|
||||
/// with bare identifiers. In order to know which statement type to produce,
|
||||
/// we need to disambiguate them. We use the following criteria:
|
||||
///
|
||||
/// * If the entity starts with an identifier followed by a period and a
|
||||
/// dollar sign, it's a variable declaration. This is the simplest case,
|
||||
/// because `.$` is used in and only in variable declarations.
|
||||
///
|
||||
/// * If the entity doesn't start with an identifier followed by a colon,
|
||||
/// it's a selector. There are some additional mostly-unimportant cases
|
||||
/// here to support various declaration hacks.
|
||||
///
|
||||
/// * If the colon is followed by another colon, it's a selector.
|
||||
///
|
||||
/// * Otherwise, if the colon is followed by anything other than
|
||||
/// interpolation or a character that's valid as the beginning of an
|
||||
/// identifier, it's a declaration.
|
||||
///
|
||||
/// * If the colon is followed by interpolation or a valid identifier, try
|
||||
/// parsing it as a declaration value. If this fails, backtrack and parse
|
||||
/// it as a selector.
|
||||
///
|
||||
/// * If the declaration value is valid but is followed by "{", backtrack and
|
||||
/// parse it as a selector anyway. This ensures that ".foo:bar {" is always
|
||||
/// parsed as a selector and never as a property with nested properties
|
||||
/// beneath it.
|
||||
// todo: potentially we read the property to a string already since properties
|
||||
// are more common than selectors? this seems to be annihilating our performance
|
||||
fn parse_style_value_when_no_space_after_semicolon(&mut self) -> Option<Vec<Token>> {
|
||||
let mut toks = Vec::new();
|
||||
while let Some(tok) = self.toks.peek() {
|
||||
match tok.kind {
|
||||
';' | '}' => {
|
||||
self.toks.reset_view();
|
||||
break;
|
||||
}
|
||||
'{' => {
|
||||
self.toks.reset_view();
|
||||
return None;
|
||||
}
|
||||
'(' => {
|
||||
toks.push(*tok);
|
||||
self.toks.peek_forward(1);
|
||||
let mut scope = 0;
|
||||
while let Some(tok) = self.toks.peek() {
|
||||
match tok.kind {
|
||||
')' => {
|
||||
if scope == 0 {
|
||||
toks.push(*tok);
|
||||
self.toks.peek_forward(1);
|
||||
break;
|
||||
} else {
|
||||
scope -= 1;
|
||||
toks.push(*tok);
|
||||
self.toks.peek_forward(1);
|
||||
}
|
||||
}
|
||||
'(' => {
|
||||
toks.push(*tok);
|
||||
self.toks.peek_forward(1);
|
||||
scope += 1;
|
||||
}
|
||||
_ => {
|
||||
toks.push(*tok);
|
||||
self.toks.peek_forward(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
toks.push(*tok);
|
||||
self.toks.peek_forward(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(toks)
|
||||
}
|
||||
|
||||
pub(super) fn is_selector_or_style(&mut self) -> SassResult<SelectorOrStyle> {
|
||||
if let Some(first_char) = self.toks.peek() {
|
||||
if first_char.kind == '#' {
|
||||
if !matches!(self.toks.peek_forward(1), Some(Token { kind: '{', .. })) {
|
||||
self.toks.reset_view();
|
||||
return Ok(SelectorOrStyle::Selector(String::new()));
|
||||
}
|
||||
self.toks.reset_view();
|
||||
} else if !is_name_start(first_char.kind) && first_char.kind != '-' {
|
||||
return Ok(SelectorOrStyle::Selector(String::new()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut property = self.parse_identifier()?.node;
|
||||
let whitespace_after_property = self.whitespace();
|
||||
|
||||
if let Some(Token { kind: ':', .. }) = self.toks.peek() {
|
||||
self.toks.next();
|
||||
if let Some(Token { kind, .. }) = self.toks.peek() {
|
||||
return Ok(match kind {
|
||||
':' => {
|
||||
if whitespace_after_property {
|
||||
property.push(' ');
|
||||
}
|
||||
property.push(':');
|
||||
SelectorOrStyle::Selector(property)
|
||||
}
|
||||
c if is_name(*c) => {
|
||||
if let Some(toks) = self.parse_style_value_when_no_space_after_semicolon() {
|
||||
let len = toks.len();
|
||||
if let Ok(val) = self.parse_value_from_vec(toks) {
|
||||
self.toks.take(len).for_each(drop);
|
||||
return Ok(SelectorOrStyle::Style(property, Some(Box::new(val))));
|
||||
}
|
||||
}
|
||||
|
||||
if whitespace_after_property {
|
||||
property.push(' ');
|
||||
}
|
||||
property.push(':');
|
||||
return Ok(SelectorOrStyle::Selector(property));
|
||||
}
|
||||
_ => SelectorOrStyle::Style(property, None),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if whitespace_after_property {
|
||||
property.push(' ');
|
||||
}
|
||||
return Ok(SelectorOrStyle::Selector(property));
|
||||
}
|
||||
Err(("expected \"{\".", self.span_before).into())
|
||||
}
|
||||
|
||||
fn parse_property(&mut self, mut super_property: String) -> SassResult<String> {
|
||||
let property = self.parse_identifier()?;
|
||||
self.whitespace_or_comment();
|
||||
if let Some(Token { kind: ':', .. }) = self.toks.peek() {
|
||||
self.toks.next();
|
||||
self.whitespace_or_comment();
|
||||
} else {
|
||||
return Err(("Expected \":\".", property.span).into());
|
||||
}
|
||||
|
||||
if super_property.is_empty() {
|
||||
Ok(property.node)
|
||||
} else {
|
||||
super_property.reserve(1 + property.node.len());
|
||||
super_property.push('-');
|
||||
super_property.push_str(&property.node);
|
||||
Ok(super_property)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_style_value(&mut self) -> SassResult<Spanned<Value>> {
|
||||
self.parse_value()
|
||||
}
|
||||
|
||||
pub(super) fn parse_style_group(&mut self, super_property: String) -> SassResult<Vec<Style>> {
|
||||
let mut styles = Vec::new();
|
||||
self.whitespace();
|
||||
while let Some(tok) = self.toks.peek().cloned() {
|
||||
match tok.kind {
|
||||
'{' => {
|
||||
self.toks.next();
|
||||
self.whitespace();
|
||||
loop {
|
||||
let property = self.parse_property(super_property.clone())?;
|
||||
if let Some(tok) = self.toks.peek() {
|
||||
if tok.kind == '{' {
|
||||
styles.append(&mut self.parse_style_group(property)?);
|
||||
self.whitespace();
|
||||
if let Some(tok) = self.toks.peek() {
|
||||
if tok.kind == '}' {
|
||||
self.toks.next();
|
||||
self.whitespace();
|
||||
return Ok(styles);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let value = self.parse_style_value()?;
|
||||
match self.toks.peek() {
|
||||
Some(Token { kind: '}', .. }) => {
|
||||
styles.push(Style { property, value });
|
||||
}
|
||||
Some(Token { kind: ';', .. }) => {
|
||||
self.toks.next();
|
||||
self.whitespace();
|
||||
styles.push(Style { property, value });
|
||||
}
|
||||
Some(Token { kind: '{', .. }) => {
|
||||
styles.push(Style {
|
||||
property: property.clone(),
|
||||
value,
|
||||
});
|
||||
styles.append(&mut self.parse_style_group(property)?);
|
||||
}
|
||||
Some(..) | None => {
|
||||
self.whitespace();
|
||||
styles.push(Style { property, value });
|
||||
}
|
||||
}
|
||||
if let Some(tok) = self.toks.peek() {
|
||||
match tok.kind {
|
||||
'}' => {
|
||||
self.toks.next();
|
||||
self.whitespace();
|
||||
return Ok(styles);
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let value = self.parse_style_value()?;
|
||||
let t = self
|
||||
.toks
|
||||
.peek()
|
||||
.ok_or(("expected more input.", value.span))?;
|
||||
match t.kind {
|
||||
';' => {
|
||||
self.toks.next();
|
||||
self.whitespace();
|
||||
}
|
||||
'{' => {
|
||||
let mut v = vec![Style {
|
||||
property: super_property.clone(),
|
||||
value,
|
||||
}];
|
||||
v.append(&mut self.parse_style_group(super_property)?);
|
||||
return Ok(v);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return Ok(vec![Style {
|
||||
property: super_property,
|
||||
value,
|
||||
}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(styles)
|
||||
}
|
||||
}
|
@ -629,14 +629,14 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn eat_calc_args(&mut self, buf: &mut String) -> SassResult<()> {
|
||||
fn eat_calc_args(&mut self, buf: &mut String) -> SassResult<()> {
|
||||
buf.reserve(2);
|
||||
buf.push('(');
|
||||
let mut nesting = 0;
|
||||
while let Some(tok) = self.toks.next() {
|
||||
match tok.kind {
|
||||
' ' | '\t' | '\n' => {
|
||||
devour_whitespace(self.toks);
|
||||
self.whitespace();
|
||||
buf.push(' ');
|
||||
}
|
||||
'#' => {
|
||||
@ -668,7 +668,7 @@ impl<'a> Parser<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn eat_progid(&mut self) -> SassResult<String> {
|
||||
fn eat_progid(&mut self) -> SassResult<String> {
|
||||
let mut string = String::new();
|
||||
let mut span = self.toks.peek().unwrap().pos();
|
||||
while let Some(tok) = self.toks.next() {
|
||||
@ -687,7 +687,7 @@ impl<'a> Parser<'a> {
|
||||
Ok(string)
|
||||
}
|
||||
|
||||
pub(crate) fn try_eat_url(&mut self) -> SassResult<Option<String>> {
|
||||
fn try_eat_url(&mut self) -> SassResult<Option<String>> {
|
||||
let mut buf = String::from("url(");
|
||||
let mut peek_counter = 0;
|
||||
peek_counter += peek_whitespace(self.toks);
|
||||
@ -756,7 +756,7 @@ impl<'a> Parser<'a> {
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn peek_escape(&mut self) -> SassResult<String> {
|
||||
fn peek_escape(&mut self) -> SassResult<String> {
|
||||
let mut value = 0;
|
||||
let first = match self.toks.peek() {
|
||||
Some(t) => *t,
|
||||
|
182
src/parse/variable.rs
Normal file
182
src/parse/variable.rs
Normal file
@ -0,0 +1,182 @@
|
||||
use codemap::Spanned;
|
||||
|
||||
use crate::{
|
||||
common::Identifier,
|
||||
error::SassResult,
|
||||
utils::{
|
||||
peek_ident_no_interpolation, read_until_closing_paren, read_until_closing_quote,
|
||||
read_until_newline,
|
||||
},
|
||||
value::Value,
|
||||
Token,
|
||||
};
|
||||
|
||||
use super::Parser;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct VariableValue {
|
||||
value: Spanned<Value>,
|
||||
global: bool,
|
||||
default: bool,
|
||||
}
|
||||
|
||||
impl VariableValue {
|
||||
pub const fn new(value: Spanned<Value>, global: bool, default: bool) -> Self {
|
||||
Self {
|
||||
value,
|
||||
global,
|
||||
default,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
pub(super) fn parse_variable_declaration(&mut self) -> SassResult<()> {
|
||||
assert!(matches!(self.toks.next(), Some(Token { kind: '$', .. })));
|
||||
let ident: Identifier = self.parse_identifier_no_interpolation(false)?.node.into();
|
||||
self.whitespace();
|
||||
if !matches!(self.toks.next(), Some(Token { kind: ':', .. })) {
|
||||
return Err(("expected \":\".", self.span_before).into());
|
||||
}
|
||||
let value = self.parse_variable_value()?;
|
||||
|
||||
if value.global && !value.default {
|
||||
self.global_scope
|
||||
.insert_var(ident.clone(), value.value.clone())?;
|
||||
}
|
||||
|
||||
if value.default {
|
||||
if self.at_root && !self.in_control_flow {
|
||||
if !self.global_scope.var_exists_no_global(&ident) {
|
||||
self.global_scope.insert_var(ident, value.value)?;
|
||||
}
|
||||
} else {
|
||||
if value.global && !self.global_scope.var_exists_no_global(&ident) {
|
||||
self.global_scope
|
||||
.insert_var(ident.clone(), value.value.clone())?;
|
||||
}
|
||||
if !self.scopes.last().var_exists_no_global(&ident) {
|
||||
self.scopes.last_mut().insert_var(ident, value.value)?;
|
||||
}
|
||||
}
|
||||
} else if self.at_root {
|
||||
if self.in_control_flow {
|
||||
if self.global_scope.var_exists_no_global(&ident) {
|
||||
self.global_scope.insert_var(ident, value.value)?;
|
||||
} else {
|
||||
self.scopes.last_mut().insert_var(ident, value.value)?;
|
||||
}
|
||||
} else {
|
||||
self.global_scope.insert_var(ident, value.value)?;
|
||||
}
|
||||
} else {
|
||||
let len = self.scopes.len();
|
||||
for (_, scope) in self
|
||||
.scopes
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.filter(|(i, _)| *i != len)
|
||||
{
|
||||
if scope.var_exists_no_global(&ident) {
|
||||
scope.insert_var(ident.clone(), value.value.clone())?;
|
||||
}
|
||||
}
|
||||
self.scopes.last_mut().insert_var(ident, value.value)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_variable_value(&mut self) -> SassResult<VariableValue> {
|
||||
let mut default = false;
|
||||
let mut global = false;
|
||||
|
||||
let mut val_toks = Vec::new();
|
||||
let mut nesting = 0;
|
||||
while let Some(tok) = self.toks.peek() {
|
||||
match tok.kind {
|
||||
';' => {
|
||||
self.toks.next();
|
||||
break;
|
||||
}
|
||||
'\\' => {
|
||||
val_toks.push(self.toks.next().unwrap());
|
||||
if self.toks.peek().is_some() {
|
||||
val_toks.push(self.toks.next().unwrap());
|
||||
}
|
||||
}
|
||||
'"' | '\'' => {
|
||||
let quote = self.toks.next().unwrap();
|
||||
val_toks.push(quote);
|
||||
val_toks.extend(read_until_closing_quote(self.toks, quote.kind)?);
|
||||
}
|
||||
'#' => {
|
||||
val_toks.push(self.toks.next().unwrap());
|
||||
match self.toks.peek() {
|
||||
Some(Token { kind: '{', .. }) => nesting += 1,
|
||||
Some(Token { kind: ';', .. }) => break,
|
||||
Some(Token { kind: '}', .. }) => {
|
||||
if nesting == 0 {
|
||||
break;
|
||||
} else {
|
||||
nesting -= 1;
|
||||
}
|
||||
}
|
||||
Some(..) | None => {}
|
||||
}
|
||||
val_toks.push(self.toks.next().unwrap());
|
||||
}
|
||||
'{' => break,
|
||||
'}' => {
|
||||
if nesting == 0 {
|
||||
break;
|
||||
} else {
|
||||
nesting -= 1;
|
||||
val_toks.push(self.toks.next().unwrap());
|
||||
}
|
||||
}
|
||||
'/' => {
|
||||
let next = self.toks.next().unwrap();
|
||||
match self.toks.peek() {
|
||||
Some(Token { kind: '/', .. }) => read_until_newline(self.toks),
|
||||
Some(..) | None => val_toks.push(next),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
'(' => {
|
||||
val_toks.push(self.toks.next().unwrap());
|
||||
val_toks.extend(read_until_closing_paren(self.toks)?);
|
||||
}
|
||||
'!' => {
|
||||
let pos = tok.pos();
|
||||
if self.toks.peek_forward(1).is_none() {
|
||||
return Err(("Expected identifier.", pos).into());
|
||||
}
|
||||
// todo: it should not be possible to declare the same flag more than once
|
||||
let mut ident = peek_ident_no_interpolation(self.toks, false, pos)?;
|
||||
ident.node.make_ascii_lowercase();
|
||||
match ident.node.as_str() {
|
||||
"global" => {
|
||||
self.toks.take(7).for_each(drop);
|
||||
global = true;
|
||||
}
|
||||
"default" => {
|
||||
self.toks.take(8).for_each(drop);
|
||||
default = true;
|
||||
}
|
||||
"important" => {
|
||||
self.toks.reset_view();
|
||||
val_toks.push(self.toks.next().unwrap());
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
return Err(("Invalid flag name.", ident.span).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => val_toks.push(self.toks.next().unwrap()),
|
||||
}
|
||||
}
|
||||
let val = self.parse_value_from_vec(val_toks)?;
|
||||
Ok(VariableValue::new(val, global, default))
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user