From 4d068596e3ffad14f1463ad5933f9b252754b1f7 Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Tue, 16 Jun 2020 22:34:01 -0400 Subject: [PATCH] refactor parsing into multiple files --- src/parse/function.rs | 121 +++++++ src/parse/import.rs | 105 ++++++ src/parse/mixin.rs | 155 +++++++++ src/parse/mod.rs | 773 +----------------------------------------- src/parse/style.rs | 265 +++++++++++++++ src/parse/value.rs | 10 +- src/parse/variable.rs | 182 ++++++++++ 7 files changed, 843 insertions(+), 768 deletions(-) create mode 100644 src/parse/function.rs create mode 100644 src/parse/import.rs create mode 100644 src/parse/mixin.rs create mode 100644 src/parse/style.rs create mode 100644 src/parse/variable.rs diff --git a/src/parse/function.rs b/src/parse/function.rs new file mode 100644 index 0000000..20aed43 --- /dev/null +++ b/src/parse/function.rs @@ -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 { + 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 { + 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(()) + } +} diff --git a/src/parse/import.rs b/src/parse/import.rs new file mode 100644 index 0000000..68e21a9 --- /dev/null +++ b/src/parse/import.rs @@ -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> { + 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::>() + .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) + } +} diff --git a/src/parse/mixin.rs b/src/parse/mixin.rs new file mode 100644 index 0000000..ff64b0c --- /dev/null +++ b/src/parse/mixin.rs @@ -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> { + 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> { + 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(()) + } +} diff --git a/src/parse/mod.rs b/src/parse/mod.rs index d262a7e..b0f395a 100644 --- a/src/parse/mod.rs +++ b/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, - global: bool, - default: bool, -} - -impl VariableValue { - pub const fn new(value: Spanned, 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> { - 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 { - 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 { - 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> { - self.parse_value() - } - - fn parse_style_group(&mut self, super_property: String) -> SassResult> { - 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 { - 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> { - 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> { - 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> { 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 { - 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 { - 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 { let mut params = String::new(); self.whitespace(); @@ -1685,96 +1022,6 @@ impl<'a> Parser<'a> { }) } - fn import(&mut self) -> SassResult> { - 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::>() - .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 { let mut params = String::new(); self.whitespace(); diff --git a/src/parse/style.rs b/src/parse/style.rs new file mode 100644 index 0000000..18c98ee --- /dev/null +++ b/src/parse/style.rs @@ -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> { + 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 { + 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 { + 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> { + self.parse_value() + } + + pub(super) fn parse_style_group(&mut self, super_property: String) -> SassResult> { + 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) + } +} diff --git a/src/parse/value.rs b/src/parse/value.rs index 4262e2d..5b7ae5d 100644 --- a/src/parse/value.rs +++ b/src/parse/value.rs @@ -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 { + fn eat_progid(&mut self) -> SassResult { 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> { + fn try_eat_url(&mut self) -> SassResult> { 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 { + fn peek_escape(&mut self) -> SassResult { let mut value = 0; let first = match self.toks.peek() { Some(t) => *t, diff --git a/src/parse/variable.rs b/src/parse/variable.rs new file mode 100644 index 0000000..a66fe1d --- /dev/null +++ b/src/parse/variable.rs @@ -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, + global: bool, + default: bool, +} + +impl VariableValue { + pub const fn new(value: Spanned, 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 { + 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)) + } +}