diff --git a/Cargo.toml b/Cargo.toml index 80fe9d4..5fc5e54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ codemap = "0.1.3" peekmore = "0.5.2" wasm-bindgen = { version = "0.2.63", optional = true } beef = "0.4.4" +bitflags = "1.2.1" # criterion is not a dev-dependency because it makes tests take too # long to compile, and you cannot make dev-dependencies optional criterion = { version = "0.3.2", optional = true } diff --git a/src/builtin/meta.rs b/src/builtin/meta.rs index 725c798..c809781 100644 --- a/src/builtin/meta.rs +++ b/src/builtin/meta.rs @@ -6,7 +6,7 @@ use crate::{ args::CallArgs, common::QuoteKind, error::SassResult, - parse::Parser, + parse::{Flags, Parser}, unit::Unit, value::{SassFunction, Value}, }; @@ -232,7 +232,7 @@ fn call(mut args: CallArgs, parser: &mut Parser<'_>) -> SassResult { #[allow(clippy::needless_pass_by_value)] fn content_exists(args: CallArgs, parser: &mut Parser<'_>) -> SassResult { args.max_args(0)?; - if !parser.in_mixin { + if !parser.flags.contains(Flags::IN_MIXIN) { return Err(( "content-exists() may only be called within a mixin.", parser.span_before, diff --git a/src/lib.rs b/src/lib.rs index 4a7916d..8b6ea86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,7 +94,7 @@ pub(crate) use crate::token::Token; use crate::{ lexer::Lexer, output::Css, - parse::{common::NeverEmptyVec, Parser}, + parse::{common::NeverEmptyVec, Flags, Parser}, scope::Scope, selector::{Extender, Selector}, }; @@ -149,13 +149,10 @@ pub fn from_path(p: &str) -> Result { super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)), span_before: empty_span, content: &mut Vec::new(), - in_mixin: false, - in_function: false, - in_control_flow: false, + flags: Flags::empty(), at_root: true, at_root_has_selector: false, extender: &mut Extender::new(empty_span), - in_keyframes: false, } .parse() .map_err(|e| raw_to_parse_error(&map, *e))?; @@ -194,13 +191,10 @@ pub fn from_string(p: String) -> Result { super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)), span_before: empty_span, content: &mut Vec::new(), - in_mixin: false, - in_function: false, - in_control_flow: false, + flags: Flags::empty(), at_root: true, at_root_has_selector: false, extender: &mut Extender::new(empty_span), - in_keyframes: false, } .parse() .map_err(|e| raw_to_parse_error(&map, *e))?; @@ -230,13 +224,10 @@ pub fn from_string(p: String) -> std::result::Result { super_selectors: &mut NeverEmptyVec::new(Selector::new(empty_span)), span_before: empty_span, content: &mut Vec::new(), - in_mixin: false, - in_function: false, - in_control_flow: false, + flags: Flags::empty(), at_root: true, at_root_has_selector: false, extender: &mut Extender::new(empty_span), - in_keyframes: false, } .parse() .map_err(|e| raw_to_parse_error(&map, *e).to_string())?; diff --git a/src/parse/function.rs b/src/parse/function.rs index 01fdccd..5a280c8 100644 --- a/src/parse/function.rs +++ b/src/parse/function.rs @@ -11,7 +11,7 @@ use crate::{ Token, }; -use super::{NeverEmptyVec, Parser, Stmt}; +use super::{Flags, NeverEmptyVec, Parser, Stmt}; /// Names that functions are not allowed to have const FORBIDDEN_IDENTIFIERS: [&str; 7] = @@ -22,11 +22,11 @@ impl<'a> Parser<'a> { self.whitespace_or_comment(); let Spanned { node: name, span } = self.parse_identifier()?; - if self.in_mixin { + if self.flags.contains(Flags::IN_MIXIN) { return Err(("Mixins may not contain function declarations.", span).into()); } - if self.in_control_flow { + if self.flags.contains(Flags::IN_CONTROL_FLOW) { return Err(("Functions may not be declared in control directives.", span).into()); } @@ -88,13 +88,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: true, - in_control_flow: self.in_control_flow, + flags: self.flags | Flags::IN_FUNCTION, at_root: false, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse()?; diff --git a/src/parse/import.rs b/src/parse/import.rs index cd6a509..24e37bf 100644 --- a/src/parse/import.rs +++ b/src/parse/import.rs @@ -83,13 +83,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: file.span.subspan(0, 0), content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: self.in_control_flow, + flags: self.flags, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse(); } diff --git a/src/parse/keyframes.rs b/src/parse/keyframes.rs index 232ecc7..a083b71 100644 --- a/src/parse/keyframes.rs +++ b/src/parse/keyframes.rs @@ -10,7 +10,7 @@ use crate::{ Token, }; -use super::Parser; +use super::{Flags, Parser}; impl fmt::Display for KeyframesSelector { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -160,13 +160,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: self.in_control_flow, + flags: self.flags, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, }) .parse_keyframes_selector()?; @@ -192,11 +189,8 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: self.in_control_flow, + flags: self.flags | Flags::IN_KEYFRAMES, at_root: false, - in_keyframes: true, at_root_has_selector: self.at_root_has_selector, extender: self.extender, } diff --git a/src/parse/mixin.rs b/src/parse/mixin.rs index 16df87a..5083d68 100644 --- a/src/parse/mixin.rs +++ b/src/parse/mixin.rs @@ -10,7 +10,7 @@ use crate::{ Token, }; -use super::{NeverEmptyVec, Parser, Stmt}; +use super::{Flags, NeverEmptyVec, Parser, Stmt}; impl<'a> Parser<'a> { pub(super) fn parse_mixin(&mut self) -> SassResult<()> { @@ -127,14 +127,11 @@ impl<'a> Parser<'a> { 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, + flags: self.flags | Flags::IN_MIXIN, content: self.content, at_root: false, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse()?; @@ -144,7 +141,7 @@ impl<'a> Parser<'a> { } pub(super) fn parse_content_rule(&mut self) -> SassResult> { - if self.in_mixin { + if self.flags.contains(Flags::IN_MIXIN) { let mut scope = self .content .last() @@ -175,14 +172,11 @@ impl<'a> Parser<'a> { global_scope: self.global_scope, super_selectors: self.super_selectors, span_before: self.span_before, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: self.in_control_flow, + flags: self.flags, content: self.content, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse()? } else { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 32cfb8f..5811165 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -66,11 +66,21 @@ pub(crate) enum Stmt { KeyframesRuleSet(Box), } +bitflags::bitflags! { + // todo: try to remove the flag IN_CONTROL_FLOW + /// Flags to indicate the context during parsing. + pub struct Flags: u8 { + const IN_MIXIN = 1; + const IN_FUNCTION = 1 << 1; + const IN_CONTROL_FLOW = 1 << 2; + const IN_KEYFRAMES = 1 << 3; + } +} + /// We could use a generic for the toks, but it makes the API /// much simpler to work with if it isn't generic. The performance /// hit (if there is one) is not important for now. -// todo: refactor `in_mixin`, in_function`, and `at_root` into state machine enum -#[allow(clippy::struct_excessive_bools)] +// todo: merge at_root and at_root_has_selector into an enum pub(crate) struct Parser<'a> { pub toks: &'a mut PeekMoreIterator>, pub map: &'a mut CodeMap, @@ -80,10 +90,7 @@ pub(crate) struct Parser<'a> { pub super_selectors: &'a mut NeverEmptyVec, pub span_before: Span, pub content: &'a mut Vec, - pub in_mixin: bool, - pub in_function: bool, - pub in_control_flow: bool, - pub in_keyframes: bool, + pub flags: Flags, /// Whether this parser is at the root of the document /// E.g. not inside a style, mixin, or function pub at_root: bool, @@ -98,7 +105,7 @@ impl<'a> Parser<'a> { let mut stmts = Vec::new(); while self.toks.peek().is_some() { stmts.append(&mut self.parse_stmt()?); - if self.in_function && !stmts.is_empty() { + if self.flags.contains(Flags::IN_FUNCTION) && !stmts.is_empty() { return Ok(stmts); } self.at_root = true; @@ -109,7 +116,7 @@ impl<'a> Parser<'a> { fn parse_stmt(&mut self) -> SassResult> { let mut stmts = Vec::new(); while let Some(Token { kind, pos }) = self.toks.peek() { - if self.in_function && !stmts.is_empty() { + if self.flags.contains(Flags::IN_FUNCTION) && !stmts.is_empty() { return Ok(stmts); } self.span_before = *pos; @@ -125,7 +132,7 @@ impl<'a> Parser<'a> { AtRuleKind::Include => stmts.append(&mut self.parse_include()?), AtRuleKind::Function => self.parse_function()?, AtRuleKind::Return => { - if self.in_function { + if self.flags.contains(Flags::IN_FUNCTION) { return Ok(vec![Stmt::Return(self.parse_return()?)]); } else { return Err(( @@ -235,7 +242,7 @@ impl<'a> Parser<'a> { // dart-sass seems to special-case the error message here? '!' | '{' => return Err(("expected \"}\".", *pos).into()), _ => { - if self.in_keyframes { + if self.flags.contains(Flags::IN_KEYFRAMES) { match self.is_selector_or_style()? { SelectorOrStyle::Style(property, value) => { if let Some(value) = value { @@ -375,13 +382,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: self.in_control_flow, + flags: self.flags, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, }, allows_parent, true, @@ -607,13 +611,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: true, + flags: self.flags | Flags::IN_CONTROL_FLOW, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse(); } @@ -630,13 +631,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: true, + flags: self.flags | Flags::IN_CONTROL_FLOW, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse() } @@ -764,7 +762,7 @@ impl<'a> Parser<'a> { span: var.span, }, ); - if self.in_function { + if self.flags.contains(Flags::IN_FUNCTION) { let these_stmts = Parser { toks: &mut body.clone().into_iter().peekmore(), map: self.map, @@ -774,13 +772,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: true, + flags: self.flags | Flags::IN_CONTROL_FLOW, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse()?; if !these_stmts.is_empty() { @@ -797,13 +792,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: true, + flags: self.flags | Flags::IN_CONTROL_FLOW, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse()?, ); @@ -838,7 +830,7 @@ impl<'a> Parser<'a> { let mut val = self.parse_value_from_vec(cond.clone())?; self.scopes.push(self.scopes.last().clone()); while val.node.is_true() { - if self.in_function { + if self.flags.contains(Flags::IN_FUNCTION) { let these_stmts = Parser { toks: &mut body.clone().into_iter().peekmore(), map: self.map, @@ -848,13 +840,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: true, + flags: self.flags | Flags::IN_CONTROL_FLOW, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse()?; if !these_stmts.is_empty() { @@ -871,13 +860,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: true, + flags: self.flags | Flags::IN_CONTROL_FLOW, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse()?, ); @@ -972,7 +958,7 @@ impl<'a> Parser<'a> { } } - if self.in_function { + if self.flags.contains(Flags::IN_FUNCTION) { let these_stmts = Parser { toks: &mut body.clone().into_iter().peekmore(), map: self.map, @@ -982,13 +968,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: true, + flags: self.flags | Flags::IN_CONTROL_FLOW, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse()?; if !these_stmts.is_empty() { @@ -1005,13 +988,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: true, + flags: self.flags | Flags::IN_CONTROL_FLOW, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse()?, ); @@ -1102,13 +1082,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: self.in_control_flow, + flags: self.flags, at_root: false, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse_stmt()?; @@ -1171,13 +1148,10 @@ impl<'a> Parser<'a> { super_selectors: &mut NeverEmptyVec::new(at_rule_selector.clone()), span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: self.in_control_flow, + flags: self.flags, at_root: true, at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse()? .into_iter() @@ -1213,13 +1187,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: self.in_control_flow, + flags: self.flags, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse_selector(false, true, String::new())?; @@ -1292,13 +1263,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: self.in_control_flow, + flags: self.flags, at_root: false, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse()?; diff --git a/src/parse/value/parse.rs b/src/parse/value/parse.rs index 7f84229..4983b06 100644 --- a/src/parse/value/parse.rs +++ b/src/parse/value/parse.rs @@ -188,13 +188,10 @@ impl<'a> Parser<'a> { super_selectors: self.super_selectors, span_before: self.span_before, content: self.content, - in_mixin: self.in_mixin, - in_function: self.in_function, - in_control_flow: self.in_control_flow, + flags: self.flags, at_root: self.at_root, at_root_has_selector: self.at_root_has_selector, extender: self.extender, - in_keyframes: self.in_keyframes, } .parse_value() } diff --git a/src/parse/variable.rs b/src/parse/variable.rs index 4bd9791..146d074 100644 --- a/src/parse/variable.rs +++ b/src/parse/variable.rs @@ -11,7 +11,7 @@ use crate::{ Token, }; -use super::Parser; +use super::{Flags, Parser}; #[derive(Debug)] struct VariableValue { @@ -46,7 +46,7 @@ impl<'a> Parser<'a> { } if value.default { - if self.at_root && !self.in_control_flow { + if self.at_root && !self.flags.contains(Flags::IN_CONTROL_FLOW) { if !self.global_scope.var_exists_no_global(&ident) { self.global_scope.insert_var(ident, value.value); } @@ -60,7 +60,7 @@ impl<'a> Parser<'a> { } } } else if self.at_root { - if self.in_control_flow { + if self.flags.contains(Flags::IN_CONTROL_FLOW) { if self.global_scope.var_exists_no_global(&ident) { self.global_scope.insert_var(ident, value.value); } else { diff --git a/src/value/mod.rs b/src/value/mod.rs index bc2ce1a..9f4716b 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -335,13 +335,10 @@ impl Value { super_selectors: parser.super_selectors, span_before: parser.span_before, content: parser.content, - in_mixin: parser.in_mixin, - in_function: parser.in_function, - in_control_flow: parser.in_control_flow, + flags: parser.flags, at_root: parser.at_root, at_root_has_selector: parser.at_root_has_selector, extender: parser.extender, - in_keyframes: parser.in_keyframes, } .parse_selector(allows_parent, true, String::new()) }