refactor parsing into multiple files

This commit is contained in:
ConnorSkees 2020-06-16 22:34:01 -04:00
parent c5f5c4d464
commit 4d068596e3
7 changed files with 843 additions and 768 deletions

121
src/parse/function.rs Normal file
View 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
View 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
View 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(())
}
}

View File

@ -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
View 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)
}
}

View File

@ -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
View 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))
}
}