diff --git a/Makefile.conf b/Makefile.conf index f93497437..279830130 100644 --- a/Makefile.conf +++ b/Makefile.conf @@ -11,8 +11,9 @@ SOURCES = \ ast_supports.cpp \ ast_sel_cmp.cpp \ ast_sel_unify.cpp \ + ast_sel_super.cpp \ + ast_sel_weave.cpp \ ast_selectors.cpp \ - node.cpp \ context.cpp \ constants.cpp \ fn_utils.cpp \ @@ -37,19 +38,22 @@ SOURCES = \ position.cpp \ lexer.cpp \ parser.cpp \ + parser_selectors.cpp \ prelexer.cpp \ eval.cpp \ + eval_selectors.cpp \ expand.cpp \ listize.cpp \ cssize.cpp \ - extend.cpp \ + extender.cpp \ + extension.cpp \ + stylesheet.cpp \ output.cpp \ inspect.cpp \ emitter.cpp \ check_nesting.cpp \ remove_placeholders.cpp \ sass.cpp \ - sass_util.cpp \ sass_values.cpp \ sass_context.cpp \ sass_functions.cpp \ @@ -60,7 +64,6 @@ SOURCES = \ c2ast.cpp \ to_value.cpp \ source_map.cpp \ - subset_map.cpp \ error_handling.cpp \ memory/SharedPtr.cpp \ utf8_string.cpp \ diff --git a/include/sass/base.h b/include/sass/base.h index a91dbc953..132da693a 100644 --- a/include/sass/base.h +++ b/include/sass/base.h @@ -68,7 +68,8 @@ enum Sass_Output_Style { SASS_STYLE_COMPRESSED, // only used internaly SASS_STYLE_INSPECT, - SASS_STYLE_TO_SASS + SASS_STYLE_TO_SASS, + SASS_STYLE_TO_CSS }; // to allocate buffer to be filled diff --git a/src/ast.cpp b/src/ast.cpp index 2ac93e8fd..d50d9cc77 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -1,17 +1,8 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include "ast.hpp" -#include "context.hpp" -#include "node.hpp" -#include "eval.hpp" -#include "extend.hpp" -#include "emitter.hpp" -#include "color_maps.hpp" -#include "ast_fwd_decl.hpp" -#include -#include -#include -#include -#include #include #include @@ -69,7 +60,7 @@ namespace Sass { pstate_.offset += pstate - pstate_ + pstate.offset; } - const std::string AST_Node::to_string(Sass_Inspect_Options opt) const + std::string AST_Node::to_string(Sass_Inspect_Options opt) const { Sass_Output_Options out(opt); Emitter emitter(out); @@ -80,19 +71,21 @@ namespace Sass { return i.get_buffer(); } - const std::string AST_Node::to_string() const + std::string AST_Node::to_css(Sass_Inspect_Options opt) const { - return to_string({ NESTED, 5 }); + opt.output_style = TO_CSS; + Sass_Output_Options out(opt); + Emitter emitter(out); + Inspect i(emitter); + i.in_declaration = true; + // ToDo: inspect should be const + const_cast(this)->perform(&i); + return i.get_buffer(); } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Expression_Obj Hashed::at(Expression_Obj k) const + std::string AST_Node::to_string() const { - if (elements_.count(k)) - { return elements_.at(k); } - else { return {}; } + return to_string({ NESTED, 5 }); } ///////////////////////////////////////////////////////////////////////// @@ -137,6 +130,14 @@ namespace Sass { is_root_(ptr->is_root_) { } + bool Block::isInvisible() const + { + for (auto& item : elements()) { + if (!item->is_invisible()) return false; + } + return true; + } + bool Block::has_content() { for (size_t i = 0, L = elements().size(); i < L; ++i) { @@ -163,19 +164,20 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Ruleset::Ruleset(ParserState pstate, Selector_List_Obj s, Block_Obj b) - : Has_Block(pstate, b), selector_(s), is_root_(false) + Ruleset::Ruleset(ParserState pstate, SelectorListObj s, Block_Obj b) + : Has_Block(pstate, b), selector_(s), schema_(), is_root_(false) { statement_type(RULESET); } Ruleset::Ruleset(const Ruleset* ptr) : Has_Block(ptr), selector_(ptr->selector_), + schema_(ptr->schema_), is_root_(ptr->is_root_) { statement_type(RULESET); } bool Ruleset::is_invisible() const { - if (Selector_List* sl = Cast(selector())) { - for (size_t i = 0, L = sl->length(); i < L; ++i) - if (!(*sl)[i]->has_placeholder()) return false; + if (const SelectorList * sl = Cast(selector())) { + for (size_t i = 0, L = sl->length(); i < L; i += 1) + if (!(*sl)[i]->isInvisible()) return false; } return true; } @@ -212,30 +214,7 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Media_Block::Media_Block(ParserState pstate, List_Obj mqs, Block_Obj b) - : Has_Block(pstate, b), media_queries_(mqs) - { statement_type(MEDIA); } - Media_Block::Media_Block(const Media_Block* ptr) - : Has_Block(ptr), media_queries_(ptr->media_queries_) - { statement_type(MEDIA); } - - bool Media_Block::is_invisible() const { - for (size_t i = 0, L = block()->length(); i < L; ++i) { - Statement_Obj stm = block()->at(i); - if (!stm->is_invisible()) return false; - } - return true; - } - - bool Media_Block::bubbles() - { - return true; - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Directive::Directive(ParserState pstate, std::string kwd, Selector_List_Obj sel, Block_Obj b, Expression_Obj val) + Directive::Directive(ParserState pstate, std::string kwd, SelectorListObj sel, Block_Obj b, Expression_Obj val) : Has_Block(pstate, b), keyword_(kwd), selector_(sel), value_(val) // set value manually if needed { statement_type(DIRECTIVE); } Directive::Directive(const Directive* ptr) @@ -450,11 +429,19 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Extension::Extension(ParserState pstate, Selector_List_Obj s) - : Statement(pstate), selector_(s) + ExtendRule::ExtendRule(ParserState pstate, SelectorListObj s) + : Statement(pstate), isOptional_(false), selector_(s), schema_() { statement_type(EXTEND); } - Extension::Extension(const Extension* ptr) - : Statement(ptr), selector_(ptr->selector_) + ExtendRule::ExtendRule(ParserState pstate, Selector_Schema_Obj s) + : Statement(pstate), isOptional_(false), selector_(), schema_(s) + { + statement_type(EXTEND); + } + ExtendRule::ExtendRule(const ExtendRule* ptr) + : Statement(ptr), + isOptional_(ptr->isOptional_), + selector_(ptr->selector_), + schema_(ptr->schema_) { statement_type(EXTEND); } ///////////////////////////////////////////////////////////////////////// @@ -923,8 +910,13 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// + // If you forget to add a class here you will get + // undefined reference to `vtable for Sass::Class' + IMPLEMENT_AST_OPERATORS(Ruleset); - IMPLEMENT_AST_OPERATORS(Media_Block); + IMPLEMENT_AST_OPERATORS(MediaRule); + IMPLEMENT_AST_OPERATORS(CssMediaRule); + IMPLEMENT_AST_OPERATORS(CssMediaQuery); IMPLEMENT_AST_OPERATORS(Import); IMPLEMENT_AST_OPERATORS(Import_Stub); IMPLEMENT_AST_OPERATORS(Directive); @@ -934,7 +926,7 @@ namespace Sass { IMPLEMENT_AST_OPERATORS(For); IMPLEMENT_AST_OPERATORS(If); IMPLEMENT_AST_OPERATORS(Mixin_Call); - IMPLEMENT_AST_OPERATORS(Extension); + IMPLEMENT_AST_OPERATORS(ExtendRule); IMPLEMENT_AST_OPERATORS(Media_Query); IMPLEMENT_AST_OPERATORS(Media_Query_Expression); IMPLEMENT_AST_OPERATORS(Debug); diff --git a/src/ast.hpp b/src/ast.hpp index 51d03caa2..e35176055 100644 --- a/src/ast.hpp +++ b/src/ast.hpp @@ -5,47 +5,22 @@ // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" -#include -#include -#include -#include -#include -#include #include -#include +#include + #include "sass/base.h" +#include "ast_helpers.hpp" #include "ast_fwd_decl.hpp" +#include "ast_def_macros.hpp" -#include "util.hpp" -#include "units.hpp" -#include "context.hpp" +#include "file.hpp" #include "position.hpp" -#include "constants.hpp" #include "operation.hpp" -#include "position.hpp" -#include "inspect.hpp" -#include "source_map.hpp" #include "environment.hpp" -#include "error_handling.hpp" -#include "ast_def_macros.hpp" -#include "ast_fwd_decl.hpp" -#include "source_map.hpp" #include "fn_utils.hpp" -#include "sass.h" - namespace Sass { - // easier to search with name - const bool DELAYED = true; - - // ToDo: should this really be hardcoded - // Note: most methods follow precision option - const double NUMBER_EPSILON = 1e-12; - - // macro to test if numbers are equal within a small error margin - #define NEAR_EQUAL(lhs, rhs) std::fabs(lhs - rhs) < NUMBER_EPSILON - // ToDo: where does this fit best? // We don't share this with C-API? class Operand { @@ -101,8 +76,9 @@ namespace Sass { virtual size_t hash() const { return 0; } virtual std::string inspect() const { return to_string({ INSPECT, 5 }); } virtual std::string to_sass() const { return to_string({ TO_SASS, 5 }); } - virtual const std::string to_string(Sass_Inspect_Options opt) const; - virtual const std::string to_string() const; + virtual std::string to_string(Sass_Inspect_Options opt) const; + virtual std::string to_css(Sass_Inspect_Options opt) const; + virtual std::string to_string() const; virtual void cloneChildren() {}; // generic find function (not fully implemented yet) // ToDo: add specific implementions to all children @@ -110,6 +86,20 @@ namespace Sass { void update_pstate(const ParserState& pstate); Offset off() { return pstate(); } Position pos() { return pstate(); } + + // Some obects are not meant to be compared + // ToDo: maybe fallback to pointer comparison? + virtual bool operator== (const AST_Node& rhs) const { + throw std::runtime_error("operator== not implemented"); + } + + // We can give some reasonable implementations by using + // inverst operators on the specialized implementations + virtual bool operator!= (const AST_Node& rhs) const { + // Unequal if not equal + return !(*this == rhs); + } + ATTACH_ABSTRACT_AST_OPERATIONS(AST_Node); ATTACH_ABSTRACT_CRTP_PERFORM_METHODS() }; @@ -228,37 +218,112 @@ namespace Sass { public: Vectorized(size_t s = 0) : hash_(0) { elements_.reserve(s); } + Vectorized(std::vector vec) : + elements_(std::move(vec)), + hash_(0) + {} virtual ~Vectorized() = 0; size_t length() const { return elements_.size(); } bool empty() const { return elements_.empty(); } void clear() { return elements_.clear(); } - T last() const { return elements_.back(); } - T first() const { return elements_.front(); } + T& last() { return elements_.back(); } + T& first() { return elements_.front(); } + const T last() const { return elements_.back(); } + const T first() const { return elements_.front(); } + + bool operator== (const Vectorized& rhs) const { + // Abort early if sizes do not match + if (length() != rhs.length()) return false; + // Otherwise test each node for object equalicy in order + return std::equal(begin(), end(), rhs.begin(), ObjEqualityFn); + } + + bool operator!= (const Vectorized& rhs) const { + return !(*this == rhs); + } + T& operator[](size_t i) { return elements_[i]; } virtual const T& at(size_t i) const { return elements_.at(i); } virtual T& at(size_t i) { return elements_.at(i); } const T& get(size_t i) const { return elements_[i]; } const T& operator[](size_t i) const { return elements_[i]; } - virtual void append(T element) + + // Implicitly get the std::vector from our object + // Makes the Vector directly assignable to std::vector + // You are responsible to make a copy if needed + // Note: since this returns the real object, we can't + // Note: guarantee that the hash will not get out of sync + operator std::vector&() { return elements_; } + operator const std::vector&() const { return elements_; } + + // Explicitly request all elements as a real std::vector + // You are responsible to make a copy if needed + // Note: since this returns the real object, we can't + // Note: guarantee that the hash will not get out of sync + std::vector& elements() { return elements_; } + const std::vector& elements() const { return elements_; } + + // Insert all items from compatible vector + void concat(const std::vector& v) + { + if (!v.empty()) reset_hash(); + elements().insert(end(), v.begin(), v.end()); + } + + // Syntatic sugar for pointers + void concat(const Vectorized* v) { - if (element) { - reset_hash(); - elements_.push_back(element); - adjust_after_pushing(element); + if (v != nullptr) { + return concat(*v); } } - virtual void concat(Vectorized* v) + + // Insert one item on the front + void unshift(T element) { - for (size_t i = 0, L = v->length(); i < L; ++i) this->append((*v)[i]); + reset_hash(); + elements_.insert(begin(), element); } - Vectorized& unshift(T element) + + // Remove and return item on the front + // ToDo: handle empty vectors + T shift() { + reset_hash(); + T first = get(0); + elements_.erase(begin()); + return first; + } + + // Insert one item on the back + // ToDo: rename this to push + void append(T element) { - elements_.insert(elements_.begin(), element); - return *this; + reset_hash(); + elements_.insert(end(), element); + // ToDo: Mostly used by parameters and arguments + // ToDo: Find a more elegant way to support this + adjust_after_pushing(element); + } + + // Check if an item already exists + // Uses underlying object `operator==` + // E.g. compares the actual objects + bool contains(const T& el) const { + for (const T& rhs : elements_) { + // Test the underlying objects for equality + // A std::find checks for pointer equality + if (ObjEqualityFn(el, rhs)) { + return true; + } + } + return false; + } + + // This might be better implemented as `operator=`? + void elements(std::vector e) { + reset_hash(); + elements_ = std::move(e); } - std::vector& elements() { return elements_; } - const std::vector& elements() const { return elements_; } - std::vector& elements(std::vector& e) { elements_ = e; return elements_; } virtual size_t hash() const { @@ -280,8 +345,8 @@ namespace Sass { typename std::vector::iterator begin() { return elements_.begin(); } typename std::vector::const_iterator end() const { return elements_.end(); } typename std::vector::const_iterator begin() const { return elements_.begin(); } - typename std::vector::iterator erase(typename std::vector::iterator el) { return elements_.erase(el); } - typename std::vector::const_iterator erase(typename std::vector::const_iterator el) { return elements_.erase(el); } + typename std::vector::iterator erase(typename std::vector::iterator el) { reset_hash(); return elements_.erase(el); } + typename std::vector::const_iterator erase(typename std::vector::const_iterator el) { reset_hash(); return elements_.erase(el); } }; template @@ -291,36 +356,61 @@ namespace Sass { // Mixin class for AST nodes that should behave like a hash table. Uses an // extra internally to maintain insertion order for interation. ///////////////////////////////////////////////////////////////////////////// + template class Hashed { private: - ExpressionMap elements_; - std::vector list_; + std::unordered_map< + K, T, ObjHash, ObjEquality + > elements_; + + std::vector _keys; + std::vector _values; protected: mutable size_t hash_; - Expression_Obj duplicate_key_; + K duplicate_key_; void reset_hash() { hash_ = 0; } void reset_duplicate_key() { duplicate_key_ = {}; } - virtual void adjust_after_pushing(std::pair p) { } + virtual void adjust_after_pushing(std::pair p) { } public: Hashed(size_t s = 0) - : elements_(ExpressionMap(s)), - list_(std::vector()), + : elements_(), + _keys(), + _values(), hash_(0), duplicate_key_({}) - { elements_.reserve(s); list_.reserve(s); } + { + _keys.reserve(s); + _values.reserve(s); + elements_.reserve(s); + } virtual ~Hashed(); - size_t length() const { return list_.size(); } - bool empty() const { return list_.empty(); } - bool has(Expression_Obj k) const { return elements_.count(k) == 1; } - Expression_Obj at(Expression_Obj k) const; + size_t length() const { return _keys.size(); } + bool empty() const { return _keys.empty(); } + bool has(K k) const { + return elements_.find(k) != elements_.end(); + } + T at(K k) const { + if (elements_.count(k)) + { + return elements_.at(k); + } + else { return {}; } + } bool has_duplicate_key() const { return duplicate_key_ != nullptr; } - Expression_Obj get_duplicate_key() const { return duplicate_key_; } - const ExpressionMap elements() { return elements_; } - Hashed& operator<<(std::pair p) + K get_duplicate_key() const { return duplicate_key_; } + const std::unordered_map< + K, T, ObjHash, ObjEquality + >& elements() { return elements_; } + Hashed& operator<<(std::pair p) { reset_hash(); - if (!has(p.first)) list_.push_back(p.first); - else if (!duplicate_key_) duplicate_key_ = p.first; + if (!has(p.first)) { + _keys.push_back(p.first); + _values.push_back(p.second); + } + else if (!duplicate_key_) { + duplicate_key_ = p.first; + } elements_[p.first] = p.second; @@ -331,7 +421,8 @@ namespace Sass { { if (length() == 0) { this->elements_ = h->elements_; - this->list_ = h->list_; + this->_values = h->_values; + this->_keys = h->_keys; return *this; } @@ -342,8 +433,12 @@ namespace Sass { reset_duplicate_key(); return *this; } - const ExpressionMap& pairs() const { return elements_; } - const std::vector& keys() const { return list_; } + const std::unordered_map< + K, T, ObjHash, ObjEquality + >& pairs() const { return elements_; } + + const std::vector& keys() const { return _keys; } + const std::vector& values() const { return _values; } // std::unordered_map::iterator end() { return elements_.end(); } // std::unordered_map::iterator begin() { return elements_.begin(); } @@ -351,8 +446,8 @@ namespace Sass { // std::unordered_map::const_iterator begin() const { return elements_.begin(); } }; - inline Hashed::~Hashed() { } - + template + inline Hashed::~Hashed() { } ///////////////////////////////////////////////////////////////////////// // Abstract base class for statements. This side of the AST hierarchy @@ -411,6 +506,7 @@ namespace Sass { void adjust_after_pushing(Statement_Obj s) override {} public: Block(ParserState pstate, size_t s = 0, bool r = false); + bool isInvisible() const; bool has_content() override; ATTACH_AST_OPERATIONS(Block) ATTACH_CRTP_PERFORM_METHODS() @@ -434,10 +530,11 @@ namespace Sass { // of style declarations. ///////////////////////////////////////////////////////////////////////////// class Ruleset final : public Has_Block { - ADD_PROPERTY(Selector_List_Obj, selector) + ADD_PROPERTY(SelectorListObj, selector) + ADD_PROPERTY(Selector_Schema_Obj, schema) ADD_PROPERTY(bool, is_root); public: - Ruleset(ParserState pstate, Selector_List_Obj s = {}, Block_Obj b = {}); + Ruleset(ParserState pstate, SelectorListObj s = {}, Block_Obj b = {}); bool is_invisible() const override; ATTACH_AST_OPERATIONS(Ruleset) ATTACH_CRTP_PERFORM_METHODS() @@ -468,29 +565,16 @@ namespace Sass { ATTACH_CRTP_PERFORM_METHODS() }; - ///////////////// - // Media queries. - ///////////////// - class Media_Block final : public Has_Block { - ADD_PROPERTY(List_Obj, media_queries) - public: - Media_Block(ParserState pstate, List_Obj mqs, Block_Obj b); - bool bubbles() override; - bool is_invisible() const override; - ATTACH_AST_OPERATIONS(Media_Block) - ATTACH_CRTP_PERFORM_METHODS() - }; - /////////////////////////////////////////////////////////////////////// // At-rules -- arbitrary directives beginning with "@" that may have an // optional statement block. /////////////////////////////////////////////////////////////////////// class Directive final : public Has_Block { ADD_CONSTREF(std::string, keyword) - ADD_PROPERTY(Selector_List_Obj, selector) + ADD_PROPERTY(SelectorListObj, selector) ADD_PROPERTY(Expression_Obj, value) public: - Directive(ParserState pstate, std::string kwd, Selector_List_Obj sel = {}, Block_Obj b = {}, Expression_Obj val = {}); + Directive(ParserState pstate, std::string kwd, SelectorListObj sel = {}, Block_Obj b = {}, Expression_Obj val = {}); bool bubbles() override; bool is_media(); bool is_keyframes(); @@ -504,7 +588,7 @@ namespace Sass { class Keyframe_Rule final : public Has_Block { // according to css spec, this should be // = | - ADD_PROPERTY(Selector_List_Obj, name) + ADD_PROPERTY(SelectorListObj, name) public: Keyframe_Rule(ParserState pstate, Block_Obj b); ATTACH_AST_OPERATIONS(Keyframe_Rule) @@ -677,17 +761,6 @@ namespace Sass { ATTACH_CRTP_PERFORM_METHODS() }; - //////////////////////////////// - // The Sass `@extend` directive. - //////////////////////////////// - class Extension final : public Statement { - ADD_PROPERTY(Selector_List_Obj, selector) - public: - Extension(ParserState pstate, Selector_List_Obj s); - ATTACH_AST_OPERATIONS(Extension) - ATTACH_CRTP_PERFORM_METHODS() - }; - ///////////////////////////////////////////////////////////////////////////// // Definitions for both mixins and functions. The two cases are distinguished // by a type tag. @@ -806,9 +879,96 @@ namespace Sass { ATTACH_CRTP_PERFORM_METHODS() }; - ///////////////// - // Media queries. - ///////////////// + + // A Media Ruleset before it has been evaluated + // Could be already final or an interpolation + class MediaRule final : public Has_Block { + ADD_PROPERTY(List_Obj, schema) + public: + MediaRule(ParserState pstate, Block_Obj block = {}); + + bool bubbles() override { return true; }; + bool is_invisible() const override { return false; }; + ATTACH_AST_OPERATIONS(MediaRule) + ATTACH_CRTP_PERFORM_METHODS() + }; + + // A Media Ruleset after it has been evaluated + // Representing the static or resulting css + class CssMediaRule final : public Has_Block, + public Vectorized { + public: + CssMediaRule(ParserState pstate, Block_Obj b); + bool bubbles() override { return true; }; + bool isInvisible() const { return empty(); } + bool is_invisible() const override { return false; }; + + public: + // Hash and equality implemtation from vector + size_t hash() const override { return Vectorized::hash(); } + // Check if two instances are considered equal + bool operator== (const CssMediaRule& rhs) const { + return Vectorized::operator== (rhs); + } + bool operator!=(const CssMediaRule& rhs) const { + // Invert from equality + return !(*this == rhs); + } + + ATTACH_AST_OPERATIONS(CssMediaRule) + ATTACH_CRTP_PERFORM_METHODS() + }; + + // Media Queries after they have been evaluated + // Representing the static or resulting css + class CssMediaQuery final : public AST_Node { + + // The modifier, probably either "not" or "only". + // This may be `null` if no modifier is in use. + ADD_PROPERTY(std::string, modifier); + + // The media type, for example "screen" or "print". + // This may be `null`. If so, [features] will not be empty. + ADD_PROPERTY(std::string, type); + + // Feature queries, including parentheses. + ADD_PROPERTY(std::vector, features); + + public: + CssMediaQuery(ParserState pstate); + + // Check if two instances are considered equal + bool operator== (const CssMediaQuery& rhs) const; + bool operator!=(const CssMediaQuery& rhs) const { + // Invert from equality + return !(*this == rhs); + } + + // Returns true if this query is empty + // Meaning it has no type and features + bool empty() const { + return type_.empty() + && modifier_.empty() + && features_.empty(); + } + + // Whether this media query matches all media types. + bool matchesAllTypes() const { + return type_.empty() || Util::equalsLiteral("all", type_); + } + + // Merges this with [other] and adds a query that matches the intersection + // of both inputs to [result]. Returns false if the result is unrepresentable + CssMediaQuery_Obj merge(CssMediaQuery_Obj& other); + + ATTACH_AST_OPERATIONS(CssMediaQuery) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////////// + // Media queries (replaced by MediaRule at al). + // ToDo: only used for interpolation case + //////////////////////////////////////////////////// class Media_Query final : public Expression, public Vectorized { ADD_PROPERTY(String_Obj, media_type) @@ -822,6 +982,7 @@ namespace Sass { //////////////////////////////////////////////////// // Media expressions (for use inside media queries). + // ToDo: only used for interpolation case //////////////////////////////////////////////////// class Media_Query_Expression final : public Expression { ADD_PROPERTY(Expression_Obj, feature) diff --git a/src/ast_def_macros.hpp b/src/ast_def_macros.hpp index d610aa835..3a0b050f6 100644 --- a/src/ast_def_macros.hpp +++ b/src/ast_def_macros.hpp @@ -101,6 +101,14 @@ public: \ #endif +#define ATTACH_VIRTUAL_CMP_OPERATIONS(klass) \ + virtual bool operator==(const klass& rhs) const = 0; \ + virtual bool operator!=(const klass& rhs) const { return !(*this == rhs); }; \ + +#define ATTACH_CMP_OPERATIONS(klass) \ + virtual bool operator==(const klass& rhs) const; \ + virtual bool operator!=(const klass& rhs) const { return !(*this == rhs); }; \ + #ifdef DEBUG_SHARED_PTR #define IMPLEMENT_AST_OPERATORS(klass) \ diff --git a/src/ast_fwd_decl.cpp b/src/ast_fwd_decl.cpp index cd54fa240..814d3f662 100644 --- a/src/ast_fwd_decl.cpp +++ b/src/ast_fwd_decl.cpp @@ -25,6 +25,7 @@ namespace Sass { IMPLEMENT_BASE_CAST(String_Constant) IMPLEMENT_BASE_CAST(Supports_Condition) IMPLEMENT_BASE_CAST(Selector) - IMPLEMENT_BASE_CAST(Simple_Selector) + IMPLEMENT_BASE_CAST(SelectorComponent) + IMPLEMENT_BASE_CAST(SimpleSelector) } diff --git a/src/ast_fwd_decl.hpp b/src/ast_fwd_decl.hpp index b0067de04..4c7b6f5af 100644 --- a/src/ast_fwd_decl.hpp +++ b/src/ast_fwd_decl.hpp @@ -1,17 +1,12 @@ #ifndef SASS_AST_FWD_DECL_H #define SASS_AST_FWD_DECL_H -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "memory/SharedPtr.hpp" +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + #include "sass/functions.h" +#include "memory/SharedPtr.hpp" ///////////////////////////////////////////// // Forward declarations for the AST visitors. @@ -22,7 +17,7 @@ namespace Sass { class Has_Block; - class Simple_Selector; + class SimpleSelector; class Parent_Reference; @@ -36,11 +31,13 @@ namespace Sass { class Bubble; class Trace; - class Media_Block; + class MediaRule; + class CssMediaRule; + class CssMediaQuery; + class Supports_Block; class Directive; - class Keyframe_Rule; class At_Root_Block; class Assignment; @@ -59,7 +56,7 @@ namespace Sass { class While; class Return; class Content; - class Extension; + class ExtendRule; class Definition; class List; @@ -80,6 +77,7 @@ namespace Sass { class Color_HSLA; class Boolean; class String; + class Null; class String_Schema; class String_Constant; @@ -92,12 +90,8 @@ namespace Sass { class Supports_Negation; class Supports_Declaration; class Supports_Interpolation; - - - class Null; - + class At_Root_Query; - class Parent_Selector; class Parameter; class Parameters; class Argument; @@ -113,20 +107,26 @@ namespace Sass { class Attribute_Selector; class Pseudo_Selector; - class Wrapped_Selector; - class Compound_Selector; - class Complex_Selector; - class Selector_List; - + + class SelectorComponent; + class SelectorCombinator; + class CompoundSelector; + class ComplexSelector; + class SelectorList; // common classes class Context; class Expand; class Eval; + class Extension; + // declare classes that are instances of memory nodes - // #define IMPL_MEM_OBJ(type) using type##_Obj = SharedImpl - #define IMPL_MEM_OBJ(type) typedef SharedImpl type##_Obj + // Note: also add a mapping without underscore + // ToDo: move to camelCase vars in the future + #define IMPL_MEM_OBJ(type) \ + typedef SharedImpl type##Obj; \ + typedef SharedImpl type##_Obj; \ IMPL_MEM_OBJ(AST_Node); IMPL_MEM_OBJ(Statement); @@ -134,7 +134,9 @@ namespace Sass { IMPL_MEM_OBJ(Ruleset); IMPL_MEM_OBJ(Bubble); IMPL_MEM_OBJ(Trace); - IMPL_MEM_OBJ(Media_Block); + IMPL_MEM_OBJ(MediaRule); + IMPL_MEM_OBJ(CssMediaRule); + IMPL_MEM_OBJ(CssMediaQuery); IMPL_MEM_OBJ(Supports_Block); IMPL_MEM_OBJ(Directive); IMPL_MEM_OBJ(Keyframe_Rule); @@ -155,7 +157,7 @@ namespace Sass { IMPL_MEM_OBJ(While); IMPL_MEM_OBJ(Return); IMPL_MEM_OBJ(Content); - IMPL_MEM_OBJ(Extension); + IMPL_MEM_OBJ(ExtendRule); IMPL_MEM_OBJ(Definition); IMPL_MEM_OBJ(Mixin_Call); IMPL_MEM_OBJ(Value); @@ -187,7 +189,6 @@ namespace Sass { IMPL_MEM_OBJ(Supports_Interpolation); IMPL_MEM_OBJ(At_Root_Query); IMPL_MEM_OBJ(Null); - IMPL_MEM_OBJ(Parent_Selector); IMPL_MEM_OBJ(Parent_Reference); IMPL_MEM_OBJ(Parameter); IMPL_MEM_OBJ(Parameters); @@ -195,107 +196,29 @@ namespace Sass { IMPL_MEM_OBJ(Arguments); IMPL_MEM_OBJ(Selector); IMPL_MEM_OBJ(Selector_Schema); - IMPL_MEM_OBJ(Simple_Selector); + IMPL_MEM_OBJ(SimpleSelector); IMPL_MEM_OBJ(Placeholder_Selector); IMPL_MEM_OBJ(Type_Selector); IMPL_MEM_OBJ(Class_Selector); IMPL_MEM_OBJ(Id_Selector); IMPL_MEM_OBJ(Attribute_Selector); IMPL_MEM_OBJ(Pseudo_Selector); - IMPL_MEM_OBJ(Wrapped_Selector); - IMPL_MEM_OBJ(Compound_Selector); - IMPL_MEM_OBJ(Complex_Selector); - IMPL_MEM_OBJ(Selector_List); - // ########################################################################### - // Implement compare, order and hashing operations for AST Nodes - // ########################################################################### - - struct HashNodes { - template - size_t operator() (const T& ex) const { - return ex.isNull() ? 0 : ex->hash(); - } - }; - template - bool OrderFunction(const T& lhs, const T& rhs) { - return !lhs.isNull() && !rhs.isNull() && *lhs < *rhs; - }; - struct OrderNodes { - template - bool operator() (const T& lhs, const T& rhs) const { - return OrderFunction(lhs, rhs); - } - }; - template - bool CompareFunction(const T& lhs, const T& rhs) { - // code around sass logic issue. 1px == 1 is true - // but both items are still different keys in maps - if (dynamic_cast(lhs.ptr())) - if (dynamic_cast(rhs.ptr())) - return lhs->hash() == rhs->hash(); - return !lhs.isNull() && !rhs.isNull() && *lhs == *rhs; - } - struct CompareNodes { - template - bool operator() (const T& lhs, const T& rhs) const { - return CompareFunction(lhs, rhs); - } - }; - - struct HashPtr { - template - size_t operator() (const T *ref) const { - return ref->hash(); - } - }; - struct ComparePtrs { - template - bool operator() (const T *lhs, const T *rhs) const { - return *lhs == *rhs; - } - }; + IMPL_MEM_OBJ(SelectorComponent); + IMPL_MEM_OBJ(SelectorCombinator); + IMPL_MEM_OBJ(CompoundSelector); + IMPL_MEM_OBJ(ComplexSelector); + IMPL_MEM_OBJ(SelectorList); // ########################################################################### // some often used typedefs // ########################################################################### - typedef std::unordered_map< - Expression_Obj, // key - Expression_Obj, // value - HashNodes, // hasher - CompareNodes // compare - > ExpressionMap; - typedef std::unordered_set< - Expression_Obj, // value - HashNodes, // hasher - CompareNodes // compare - > ExpressionSet; - - typedef std::string SubSetMapKey; - typedef std::vector SubSetMapKeys; - - typedef std::pair SubSetMapPair; - typedef std::pair SubSetMapLookup; - typedef std::vector SubSetMapPairs; - typedef std::vector SubSetMapLookups; - - typedef std::pair SubSetMapResult; - typedef std::vector SubSetMapResults; - - typedef std::set SelectorSet; - - typedef std::deque ComplexSelectorDeque; - typedef std::set SimpleSelectorSet; - typedef std::set ComplexSelectorSet; - typedef std::set CompoundSelectorSet; - typedef std::unordered_set SimpleSelectorDict; - typedef std::vector BlockStack; typedef std::vector CalleeStack; typedef std::vector CallStack; - typedef std::vector MediaStack; - typedef std::vector SelectorStack; + typedef std::vector MediaStack; + typedef std::vector SelectorStack; typedef std::vector ImporterStack; // only to switch implementations for testing @@ -334,7 +257,8 @@ namespace Sass { DECLARE_BASE_CAST(String_Constant) DECLARE_BASE_CAST(Supports_Condition) DECLARE_BASE_CAST(Selector) - DECLARE_BASE_CAST(Simple_Selector) + DECLARE_BASE_CAST(SimpleSelector) + DECLARE_BASE_CAST(SelectorComponent) } diff --git a/src/ast_helpers.hpp b/src/ast_helpers.hpp new file mode 100644 index 000000000..37baa2124 --- /dev/null +++ b/src/ast_helpers.hpp @@ -0,0 +1,291 @@ +#ifndef SASS_AST_HELPERS_H +#define SASS_AST_HELPERS_H + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include +#include "util_string.hpp" + +namespace Sass { + + // ########################################################################### + // ########################################################################### + + // easier to search with name + const bool DELAYED = true; + + // ToDo: should this really be hardcoded + // Note: most methods follow precision option + const double NUMBER_EPSILON = 1e-12; + + // macro to test if numbers are equal within a small error margin + #define NEAR_EQUAL(lhs, rhs) std::fabs(lhs - rhs) < NUMBER_EPSILON + + // ########################################################################### + // We define various functions and functors here. + // Functions satisfy the BinaryPredicate requirement + // Functors are structs used for e.g. unordered_map + // ########################################################################### + + // ########################################################################### + // Implement compare and hashing operations for raw pointers + // ########################################################################### + + template + size_t PtrHashFn(const T* ptr) { + return std::hash()((size_t)ptr); + } + + struct PtrHash { + template + size_t operator() (const T* ptr) const { + return PtrHashFn(ptr); + } + }; + + template + bool PtrEqualityFn(const T* lhs, const T* rhs) { + return lhs == rhs; // compare raw pointers + } + + struct PtrEquality { + template + bool operator() (const T* lhs, const T* rhs) const { + return PtrEqualityFn(lhs, rhs); + } + }; + + // ########################################################################### + // Implement compare and hashing operations for AST Nodes + // ########################################################################### + + // TODO: get rid of funtions and use ObjEquality + + template + // Hash the raw pointer instead of object + size_t ObjPtrHashFn(const T& obj) { + return PtrHashFn(obj.ptr()); + } + + struct ObjPtrHash { + template + // Hash the raw pointer instead of object + size_t operator() (const T& obj) const { + return ObjPtrHashFn(obj); + } + }; + + template + // Hash the object and its content + size_t ObjHashFn(const T& obj) { + return obj ? obj->hash() : 0; + } + + struct ObjHash { + template + // Hash the object and its content + size_t operator() (const T& obj) const { + return ObjHashFn(obj); + } + }; + + template + // Hash the object behind pointer + size_t PtrObjHashFn(const T* obj) { + return obj ? obj->hash() : 0; + } + + struct PtrObjHash { + template + // Hash the object behind pointer + size_t operator() (const T* obj) const { + return PtrObjHashFn(obj); + } + }; + + template + // Compare raw pointers to the object + bool ObjPtrEqualityFn(const T& lhs, const T& rhs) { + return PtrEqualityFn(lhs.ptr(), rhs.ptr()); + } + + struct ObjPtrEquality { + template + // Compare raw pointers to the object + bool operator() (const T& lhs, const T& rhs) const { + return ObjPtrEqualityFn(lhs, rhs); + } + }; + + template + // Compare the objects behind the pointers + bool PtrObjEqualityFn(const T* lhs, const T* rhs) { + if (lhs == nullptr) return rhs == nullptr; + else if (rhs == nullptr) return false; + else return *lhs == *rhs; + } + + struct PtrObjEquality { + template + // Compare the objects behind the pointers + bool operator() (const T* lhs, const T* rhs) const { + return PtrObjEqualityFn(lhs, rhs); + } + }; + + template + // Compare the objects and its contents + bool ObjEqualityFn(const T& lhs, const T& rhs) { + return PtrObjEqualityFn(lhs.ptr(), rhs.ptr()); + } + + struct ObjEquality { + template + // Compare the objects and its contents + bool operator() (const T& lhs, const T& rhs) const { + return ObjEqualityFn(lhs, rhs); + } + }; + + // ########################################################################### + // Implement ordering operations for AST Nodes + // ########################################################################### + + template + // Compare the objects behind pointers + bool PtrObjLessThanFn(const T* lhs, const T* rhs) { + if (lhs == nullptr) return rhs != nullptr; + else if (rhs == nullptr) return false; + else return *lhs < *rhs; + } + + struct PtrObjLessThan { + template + // Compare the objects behind pointers + bool operator() (const T* lhs, const T* rhs) const { + return PtrObjLessThanFn(lhs, rhs); + } + }; + + template + // Compare the objects and its content + bool ObjLessThanFn(const T& lhs, const T& rhs) { + return PtrObjLessThanFn(lhs.ptr(), rhs.ptr()); + }; + + struct ObjLessThan { + template + // Compare the objects and its content + bool operator() (const T& lhs, const T& rhs) const { + return ObjLessThanFn(lhs, rhs); + } + }; + + // ########################################################################### + // Some STL helper functions + // ########################################################################### + + // Check if all elements are equal + template + bool ListEquality(const X& lhs, const Y& rhs, + bool(*cmp)(const XT*, const YT*)) + { + return lhs.size() == rhs.size() && + std::equal(lhs.begin(), lhs.end(), + rhs.begin(), cmp); + } + + // Return if Vector is empty + template + bool listIsEmpty(T* cnt) { + return cnt && cnt->empty(); + } + + // Erase items from vector that match predicate + template + void listEraseItemIf(T& vec, UnaryPredicate* predicate) + { + vec.erase(std::remove_if(vec.begin(), vec.end(), predicate), vec.end()); + } + + // Check that every item in `lhs` is also in `rhs` + // Note: this works by comparing the raw pointers + template + bool listIsSubsetOrEqual(const T& lhs, const T& rhs) { + for (const auto& item : lhs) { + if (std::find(rhs.begin(), rhs.end(), item) == rhs.end()) + return false; + } + return true; + } + + // ########################################################################## + // Returns whether [name] is the name of a pseudo-element + // that can be written with pseudo-class syntax (CSS2 vs CSS3): + // `:before`, `:after`, `:first-line`, or `:first-letter` + // ########################################################################## + inline bool isFakePseudoElement(const std::string& name) + { + return Util::equalsLiteral("after", name) + || Util::equalsLiteral("before", name) + || Util::equalsLiteral("first-line", name) + || Util::equalsLiteral("first-letter", name); + } + + // ########################################################################## + // Names of pseudo selectors that take selectors as arguments, + // and that are subselectors of their arguments. + // For example, `.foo` is a superselector of `:matches(.foo)`. + // ########################################################################## + inline bool isSubselectorPseudo(const std::string& norm) + { + return Util::equalsLiteral("any", norm) + || Util::equalsLiteral("matches", norm) + || Util::equalsLiteral("nth-child", norm) + || Util::equalsLiteral("nth-last-child", norm); + } + // EO isSubselectorPseudo + + // ########################################################################### + // Pseudo-class selectors that take unadorned selectors as arguments. + // ########################################################################### + inline bool isSelectorPseudoClass(const std::string& test) + { + return Util::equalsLiteral("not", test) + || Util::equalsLiteral("matches", test) + || Util::equalsLiteral("current", test) + || Util::equalsLiteral("any", test) + || Util::equalsLiteral("has", test) + || Util::equalsLiteral("host", test) + || Util::equalsLiteral("host-context", test); + } + // EO isSelectorPseudoClass + + // ########################################################################### + // Pseudo-element selectors that take unadorned selectors as arguments. + // ########################################################################### + inline bool isSelectorPseudoElement(const std::string& test) + { + return Util::equalsLiteral("slotted", test); + } + // EO isSelectorPseudoElement + + // ########################################################################### + // Pseudo-element selectors that has binominals + // ########################################################################### + inline bool isSelectorPseudoBinominal(const std::string& test) + { + return Util::equalsLiteral("nth-child", test) + || Util::equalsLiteral("nth-last-child", test); + } + // isSelectorPseudoBinominal + + // ########################################################################### + // ########################################################################### + +} + +#endif diff --git a/src/ast_sel_cmp.cpp b/src/ast_sel_cmp.cpp index fe1357991..a865b05d0 100644 --- a/src/ast_sel_cmp.cpp +++ b/src/ast_sel_cmp.cpp @@ -2,51 +2,17 @@ // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" -#include "ast.hpp" -#include "context.hpp" -#include "node.hpp" -#include "eval.hpp" -#include "extend.hpp" -#include "emitter.hpp" -#include "color_maps.hpp" -#include "ast_fwd_decl.hpp" -#include -#include -#include -#include -#include -#include -#include - #include "ast_selectors.hpp" namespace Sass { /*#########################################################################*/ + // Compare against base class on right hand side + // try to find the most specialized implementation /*#########################################################################*/ - bool Selector_List::operator== (const Selector& rhs) const - { - if (auto sl = Cast(&rhs)) { return *this == *sl; } - else if (auto ss = Cast(&rhs)) { return *this == *ss; } - else if (auto cpx = Cast(&rhs)) { return *this == *cpx; } - else if (auto cpd = Cast(&rhs)) { return *this == *cpd; } - else if (auto ls = Cast(&rhs)) { return *this == *ls; } - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Selector_List::operator< (const Selector& rhs) const - { - if (auto sl = Cast(&rhs)) { return *this < *sl; } - else if (auto ss = Cast(&rhs)) { return *this < *ss; } - else if (auto cpx = Cast(&rhs)) { return *this < *cpx; } - else if (auto cpd = Cast(&rhs)) { return *this < *cpd; } - else if (auto ls = Cast(&rhs)) { return *this < *ls; } - throw std::runtime_error("invalid selector base classes to compare"); - } - // Selector lists can be compared to comma lists - bool Selector_List::operator== (const Expression& rhs) const + bool SelectorList::operator== (const Expression& rhs) const { if (auto l = Cast(&rhs)) { return *this == *l; } if (auto s = Cast(&rhs)) { return *this == *s; } @@ -55,531 +21,322 @@ namespace Sass { } // Selector lists can be compared to comma lists - bool Selector_List::operator< (const Expression& rhs) const - { - if (auto l = Cast(&rhs)) { return *this < *l; } - if (auto s = Cast(&rhs)) { return *this < *s; } - if (Cast(&rhs) || Cast(&rhs)) { return true; } - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Complex_Selector::operator== (const Selector& rhs) const - { - if (auto sl = Cast(&rhs)) return *this == *sl; - if (auto ss = Cast(&rhs)) return *this == *ss; - if (auto cs = Cast(&rhs)) return *this == *cs; - if (auto ch = Cast(&rhs)) return *this == *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Complex_Selector::operator< (const Selector& rhs) const - { - if (auto sl = Cast(&rhs)) return *this < *sl; - if (auto ss = Cast(&rhs)) return *this < *ss; - if (auto cs = Cast(&rhs)) return *this < *cs; - if (auto ch = Cast(&rhs)) return *this < *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Compound_Selector::operator== (const Selector& rhs) const - { - if (auto sl = Cast(&rhs)) return *this == *sl; - if (auto ss = Cast(&rhs)) return *this == *ss; - if (auto cs = Cast(&rhs)) return *this == *cs; - if (auto ch = Cast(&rhs)) return *this == *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Compound_Selector::operator< (const Selector& rhs) const + bool SelectorList::operator== (const Selector& rhs) const { - if (auto sl = Cast(&rhs)) return *this < *sl; - if (auto ss = Cast(&rhs)) return *this < *ss; - if (auto cs = Cast(&rhs)) return *this < *cs; - if (auto ch = Cast(&rhs)) return *this < *ch; + if (auto sel = Cast(&rhs)) { return *this == *sel; } + if (auto sel = Cast(&rhs)) { return *this == *sel; } + if (auto sel = Cast(&rhs)) { return *this == *sel; } + if (auto sel = Cast(&rhs)) { return *this == *sel; } + if (auto list = Cast(&rhs)) { return *this == *list; } throw std::runtime_error("invalid selector base classes to compare"); } - bool Selector_Schema::operator== (const Selector& rhs) const + bool ComplexSelector::operator== (const Selector& rhs) const { - if (auto sl = Cast(&rhs)) return *this == *sl; - if (auto ss = Cast(&rhs)) return *this == *ss; - if (auto cs = Cast(&rhs)) return *this == *cs; - if (auto ch = Cast(&rhs)) return *this == *ch; + if (auto sel = Cast(&rhs)) { return *this == *sel; } + if (auto sel = Cast(&rhs)) { return *sel == *this; } + if (auto sel = Cast(&rhs)) { return *this == *sel; } + if (auto sel = Cast(&rhs)) { return *this == *sel; } throw std::runtime_error("invalid selector base classes to compare"); } - bool Selector_Schema::operator< (const Selector& rhs) const + bool SelectorCombinator::operator== (const Selector& rhs) const { - if (auto sl = Cast(&rhs)) return *this < *sl; - if (auto ss = Cast(&rhs)) return *this < *ss; - if (auto cs = Cast(&rhs)) return *this < *cs; - if (auto ch = Cast(&rhs)) return *this < *ch; - throw std::runtime_error("invalid selector base classes to compare"); + if (auto cpx = Cast(&rhs)) { return *this == *cpx; } + return false; } - bool Simple_Selector::operator== (const Selector& rhs) const + bool CompoundSelector::operator== (const Selector& rhs) const { - if (auto sl = Cast(&rhs)) return *this == *sl; - if (auto ss = Cast(&rhs)) return *this == *ss; - if (auto cs = Cast(&rhs)) return *this == *cs; - if (auto ch = Cast(&rhs)) return *this == *ch; + if (auto sel = Cast(&rhs)) { return *this == *sel; } + if (auto sel = Cast(&rhs)) { return *this == *sel; } + if (auto sel = Cast(&rhs)) { return *this == *sel; } + if (auto sel = Cast(&rhs)) { return *this == *sel; } throw std::runtime_error("invalid selector base classes to compare"); } - bool Simple_Selector::operator< (const Selector& rhs) const + bool SimpleSelector::operator== (const Selector& rhs) const { - if (auto sl = Cast(&rhs)) return *this < *sl; - if (auto ss = Cast(&rhs)) return *this < *ss; - if (auto cs = Cast(&rhs)) return *this < *cs; - if (auto ch = Cast(&rhs)) return *this < *ch; + if (auto sel = Cast(&rhs)) { return *this == *sel; } + if (auto sel = Cast(&rhs)) { return *this == *sel; } + if (auto sel = Cast(&rhs)) { return *this == *sel; } + if (auto sel = Cast(&rhs)) return *this == *sel; throw std::runtime_error("invalid selector base classes to compare"); } /*#########################################################################*/ /*#########################################################################*/ - bool Selector_List::operator< (const Selector_List& rhs) const - { - size_t l = rhs.length(); - if (length() < l) l = length(); - for (size_t i = 0; i < l; i ++) { - if (*at(i) < *rhs.at(i)) return true; - if (*at(i) != *rhs.at(i)) return false; - } - return false; - } - - bool Selector_List::operator== (const Selector_List& rhs) const - { - if (&rhs == this) return true; - if (rhs.length() != length()) return false; - std::unordered_set lhs_set; - lhs_set.reserve(length()); - for (const Complex_Selector_Obj &element : elements()) { - lhs_set.insert(element.ptr()); - } - for (const Complex_Selector_Obj &element : rhs.elements()) { - if (lhs_set.find(element.ptr()) == lhs_set.end()) return false; - } - return true; - } - - bool Compound_Selector::operator< (const Compound_Selector& rhs) const - { - size_t L = std::min(length(), rhs.length()); - for (size_t i = 0; i < L; ++i) - { - Simple_Selector* l = (*this)[i]; - Simple_Selector* r = rhs[i]; - if (!l && !r) return false; - else if (!r) return false; - else if (!l) return true; - else if (*l != *r) - { return *l < *r; } - } - // just compare the length now - return length() < rhs.length(); - } - - bool Compound_Selector::operator== (const Compound_Selector& rhs) const + bool SelectorList::operator== (const SelectorList& rhs) const { if (&rhs == this) return true; if (rhs.length() != length()) return false; - std::unordered_set lhs_set; + std::unordered_set lhs_set; lhs_set.reserve(length()); - for (const Simple_Selector_Obj &element : elements()) { + for (const ComplexSelectorObj& element : elements()) { lhs_set.insert(element.ptr()); } - // there is no break?! - for (const Simple_Selector_Obj &element : rhs.elements()) { + for (const ComplexSelectorObj& element : rhs.elements()) { if (lhs_set.find(element.ptr()) == lhs_set.end()) return false; } return true; } - bool Complex_Selector::operator< (const Complex_Selector& rhs) const - { - // const iterators for tails - const Complex_Selector* l = this; - const Complex_Selector* r = &rhs; - Compound_Selector* l_h = NULL; - Compound_Selector* r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - // process all tails - while (true) - { - #ifdef DEBUG - // skip empty ancestor first - if (l && l->is_empty_ancestor()) - { - l_h = NULL; - l = l->tail(); - if(l) l_h = l->head(); - continue; - } - // skip empty ancestor first - if (r && r->is_empty_ancestor()) - { - r_h = NULL; - r = r->tail(); - if (r) r_h = r->head(); - continue; - } - #endif - // check for valid selectors - if (!l) return !!r; - if (!r) return false; - // both are null - else if (!l_h && !r_h) - { - // check combinator after heads - if (l->combinator() != r->combinator()) - { return l->combinator() < r->combinator(); } - // advance to next tails - l = l->tail(); - r = r->tail(); - // fetch the next headers - l_h = NULL; r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - } - // one side is null - else if (!r_h) return true; - else if (!l_h) return false; - // heads ok and equal - else if (*l_h == *r_h) - { - // check combinator after heads - if (l->combinator() != r->combinator()) - { return l->combinator() < r->combinator(); } - // advance to next tails - l = l->tail(); - r = r->tail(); - // fetch the next headers - l_h = NULL; r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - } - // heads are not equal - else return *l_h < *r_h; - } - } - bool Complex_Selector::operator== (const Complex_Selector& rhs) const - { - // const iterators for tails - const Complex_Selector* l = this; - const Complex_Selector* r = &rhs; - Compound_Selector* l_h = NULL; - Compound_Selector* r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - // process all tails - while (true) - { - // check the pointers - if (!r) return !l; - if (!l) return !r; - // both are null - if (!l_h && !r_h) - { - // check combinator after heads - if (l->combinator() != r->combinator()) - { return l->combinator() < r->combinator(); } - // advance to next tails - l = l->tail(); - r = r->tail(); - // fetch the next heads - l_h = NULL; r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - } - // equals if other head is empty - else if ((!l_h && !r_h) || - (!l_h && r_h->empty()) || - (!r_h && l_h->empty()) || - (l_h && r_h && *l_h == *r_h)) - { - // check combinator after heads - if (l->combinator() != r->combinator()) - { return l->combinator() == r->combinator(); } - // advance to next tails - l = l->tail(); - r = r->tail(); - // fetch the next heads - l_h = NULL; r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - } - // abort - else break; - } - // unreachable - return false; - } /*#########################################################################*/ + // Compare SelectorList against all other selector types /*#########################################################################*/ - bool Selector_List::operator== (const Complex_Selector& rhs) const - { - size_t len = length(); - if (len > 1) return false; - if (len == 0) return rhs.empty(); - return *at(0) == rhs; - } - - bool Selector_List::operator< (const Complex_Selector& rhs) const + bool SelectorList::operator== (const ComplexSelector& rhs) const { - size_t len = length(); - if (len > 1) return false; - if (len == 0) return !rhs.empty(); - return *at(0) < rhs; + // If both are empty they are equal + if (empty() && rhs.empty()) return true; + // Must have exactly one item + if (length() != 1) return false; + // Compare simple selectors + return *get(0) == rhs; } - bool Selector_List::operator== (const Compound_Selector& rhs) const + bool SelectorList::operator== (const CompoundSelector& rhs) const { - size_t len = length(); - if (len > 1) return false; - if (len == 0) return rhs.empty(); - return *at(0) == rhs; + // If both are empty they are equal + if (empty() && rhs.empty()) return true; + // Must have exactly one item + if (length() != 1) return false; + // Compare simple selectors + return *get(0) == rhs; } - bool Selector_List::operator< (const Compound_Selector& rhs) const + bool SelectorList::operator== (const SimpleSelector& rhs) const { - size_t len = length(); - if (len > 1) return false; - if (len == 0) return !rhs.empty(); - return *at(0) < rhs; + // If both are empty they are equal + if (empty() && rhs.empty()) return true; + // Must have exactly one item + if (length() != 1) return false; + // Compare simple selectors + return *get(0) == rhs; } - bool Selector_List::operator== (const Simple_Selector& rhs) const - { - size_t len = length(); - if (len > 1) return false; - if (len == 0) return rhs.empty(); - return *at(0) == rhs; - } + /*#########################################################################*/ + // Compare ComplexSelector against itself + /*#########################################################################*/ - bool Selector_List::operator< (const Simple_Selector& rhs) const + bool ComplexSelector::operator== (const ComplexSelector& rhs) const { size_t len = length(); - if (len > 1) return false; - if (len == 0) return !rhs.empty(); - return *at(0) < rhs; + size_t rlen = rhs.length(); + if (len != rlen) return false; + for (size_t i = 0; i < len; i += 1) { + if (*get(i) != *rhs.get(i)) return false; + } + return true; } - /***************************************************************************/ - /***************************************************************************/ + /*#########################################################################*/ + // Compare ComplexSelector against all other selector types + /*#########################################################################*/ - bool Complex_Selector::operator== (const Selector_List& rhs) const + bool ComplexSelector::operator== (const SelectorList& rhs) const { - size_t len = rhs.length(); - if (len > 1) return false; - if (len == 0) return empty(); - return *this == *rhs.at(0); + // If both are empty they are equal + if (empty() && rhs.empty()) return true; + // Must have exactly one item + if (rhs.length() != 1) return false; + // Compare complex selector + return *this == *rhs.get(0); } - bool Complex_Selector::operator< (const Selector_List& rhs) const + bool ComplexSelector::operator== (const CompoundSelector& rhs) const { - size_t len = rhs.length(); - if (len > 1) return true; - if (len == 0) return false; - return *this < *rhs.at(0); + // If both are empty they are equal + if (empty() && rhs.empty()) return true; + // Must have exactly one item + if (length() != 1) return false; + // Compare compound selector + return *get(0) == rhs; } - bool Complex_Selector::operator== (const Compound_Selector& rhs) const + bool ComplexSelector::operator== (const SimpleSelector& rhs) const { - if (tail()) return false; - if (!head()) return rhs.empty(); - return *head() == rhs; + // If both are empty they are equal + if (empty() && rhs.empty()) return true; + // Must have exactly one item + if (length() != 1) return false; + // Compare simple selectors + return *get(0) == rhs; } - bool Complex_Selector::operator< (const Compound_Selector& rhs) const + /*#########################################################################*/ + // Compare SelectorCombinator against itself + /*#########################################################################*/ + + bool SelectorCombinator::operator==(const SelectorCombinator& rhs) const { - if (tail()) return false; - if (!head()) return !rhs.empty(); - return *head() < rhs; + return combinator() == rhs.combinator(); } - bool Complex_Selector::operator== (const Simple_Selector& rhs) const + /*#########################################################################*/ + // Compare SelectorCombinator against SelectorComponent + /*#########################################################################*/ + + bool SelectorCombinator::operator==(const SelectorComponent& rhs) const { - if (tail()) return false; - if (!head()) return rhs.empty(); - return *head() == rhs; + if (const SelectorCombinator * sel = rhs.getCombinator()) { + return *this == *sel; + } + return false; } - bool Complex_Selector::operator< (const Simple_Selector& rhs) const + bool CompoundSelector::operator==(const SelectorComponent& rhs) const { - if (tail()) return false; - if (!head()) return !rhs.empty(); - return *head() < rhs; + if (const CompoundSelector * sel = rhs.getCompound()) { + return *this == *sel; + } + return false; } - /***************************************************************************/ - /***************************************************************************/ + /*#########################################################################*/ + // Compare CompoundSelector against itself + /*#########################################################################*/ + // ToDo: Verifiy implementation + /*#########################################################################*/ - bool Compound_Selector::operator== (const Selector_List& rhs) const + bool CompoundSelector::operator== (const CompoundSelector& rhs) const { - size_t len = rhs.length(); - if (len > 1) return false; - if (len == 0) return empty(); - return *this == *rhs.at(0); + // std::cerr << "comp vs comp\n"; + if (&rhs == this) return true; + if (rhs.length() != length()) return false; + std::unordered_set lhs_set; + lhs_set.reserve(length()); + for (const SimpleSelectorObj& element : elements()) { + lhs_set.insert(element.ptr()); + } + // there is no break?! + for (const SimpleSelectorObj& element : rhs.elements()) { + if (lhs_set.find(element.ptr()) == lhs_set.end()) return false; + } + return true; } - bool Compound_Selector::operator< (const Selector_List& rhs) const - { - size_t len = rhs.length(); - if (len > 1) return true; - if (len == 0) return false; - return *this < *rhs.at(0); - } - bool Compound_Selector::operator== (const Complex_Selector& rhs) const - { - if (rhs.tail()) return false; - if (!rhs.head()) return empty(); - return *this == *rhs.head(); - } + /*#########################################################################*/ + // Compare CompoundSelector against all other selector types + /*#########################################################################*/ - bool Compound_Selector::operator< (const Complex_Selector& rhs) const + bool CompoundSelector::operator== (const SelectorList& rhs) const { - if (rhs.tail()) return true; - if (!rhs.head()) return false; - return *this < *rhs.head(); + // If both are empty they are equal + if (empty() && rhs.empty()) return true; + // Must have exactly one item + if (rhs.length() != 1) return false; + // Compare complex selector + return *this == *rhs.get(0); } - bool Compound_Selector::operator< (const Simple_Selector& rhs) const + bool CompoundSelector::operator== (const ComplexSelector& rhs) const { - size_t len = length(); - if (len > 1) return false; - if (len == 0) return rhs.empty(); - return *at(0) == rhs; + // If both are empty they are equal + if (empty() && rhs.empty()) return true; + // Must have exactly one item + if (rhs.length() != 1) return false; + // Compare compound selector + return *this == *rhs.get(0); } - bool Compound_Selector::operator== (const Simple_Selector& rhs) const + bool CompoundSelector::operator== (const SimpleSelector& rhs) const { - size_t len = length(); - if (len > 1) return false; - if (len == 0) return !rhs.empty(); - return *at(0) < rhs; + // If both are empty they are equal + if (empty() && rhs.empty()) return false; + // Must have exactly one item + size_t rlen = length(); + if (rlen > 1) return false; + if (rlen == 0) return true; + // Compare simple selectors + return *get(0) < rhs; } - /***************************************************************************/ - /***************************************************************************/ - - bool Simple_Selector::operator== (const Selector_List& rhs) const - { - return rhs.length() == 1 && *this == *rhs.at(0); - } + /*#########################################################################*/ + // Compare SimpleSelector against itself (upcast from abstract base) + /*#########################################################################*/ - bool Simple_Selector::operator< (const Selector_List& rhs) const - { - size_t len = rhs.length(); - if (len > 1) return true; - if (len == 0) return false; - return *this < *rhs.at(0); - } + // DOES NOT EXIST FOR ABSTRACT BASE CLASS - bool Simple_Selector::operator== (const Complex_Selector& rhs) const - { - return !rhs.tail() && rhs.head() && - rhs.combinator() == Complex_Selector::ANCESTOR_OF && - *this == *rhs.head(); - } + /*#########################################################################*/ + // Compare SimpleSelector against all other selector types + /*#########################################################################*/ - bool Simple_Selector::operator< (const Complex_Selector& rhs) const + bool SimpleSelector::operator== (const SelectorList& rhs) const { - if (rhs.tail()) return true; - if (!rhs.head()) return false; - return *this < *rhs.head(); + // If both are empty they are equal + if (empty() && rhs.empty()) return true; + // Must have exactly one item + if (rhs.length() != 1) return false; + // Compare complex selector + return *this == *rhs.get(0); } - bool Simple_Selector::operator== (const Compound_Selector& rhs) const + bool SimpleSelector::operator== (const ComplexSelector& rhs) const { - return rhs.length() == 1 && *this == *rhs.at(0); + // If both are empty they are equal + if (empty() && rhs.empty()) return true; + // Must have exactly one item + if (rhs.length() != 1) return false; + // Compare compound selector + return *this == *rhs.get(0); } - bool Simple_Selector::operator< (const Compound_Selector& rhs) const + bool SimpleSelector::operator== (const CompoundSelector& rhs) const { - size_t len = rhs.length(); - if (len > 1) return true; - if (len == 0) return false; - return *this < *rhs.at(0); + // If both are empty they are equal + if (empty() && rhs.empty()) return false; + // Must have exactly one item + if (rhs.length() != 1) return false; + // Compare simple selector + return *this == *rhs.get(0); } /*#########################################################################*/ /*#########################################################################*/ - bool Simple_Selector::operator== (const Simple_Selector& rhs) const - { - switch (simple_type()) { - case ID_SEL: return (const Id_Selector&) *this == rhs; break; - case TYPE_SEL: return (const Type_Selector&) *this == rhs; break; - case CLASS_SEL: return (const Class_Selector&) *this == rhs; break; - case PARENT_SEL: return (const Parent_Selector&) *this == rhs; break; - case PSEUDO_SEL: return (const Pseudo_Selector&) *this == rhs; break; - case WRAPPED_SEL: return (const Wrapped_Selector&) *this == rhs; break; - case ATTRIBUTE_SEL: return (const Attribute_Selector&) *this == rhs; break; - case PLACEHOLDER_SEL: return (const Placeholder_Selector&) *this == rhs; break; - } - return false; - } - - /***************************************************************************/ - /***************************************************************************/ - - bool Id_Selector::operator== (const Simple_Selector& rhs) const + bool Id_Selector::operator== (const SimpleSelector& rhs) const { auto sel = Cast(&rhs); return sel ? *this == *sel : false; } - bool Type_Selector::operator== (const Simple_Selector& rhs) const + bool Type_Selector::operator== (const SimpleSelector& rhs) const { auto sel = Cast(&rhs); return sel ? *this == *sel : false; } - bool Class_Selector::operator== (const Simple_Selector& rhs) const + bool Class_Selector::operator== (const SimpleSelector& rhs) const { auto sel = Cast(&rhs); return sel ? *this == *sel : false; } - bool Parent_Selector::operator== (const Simple_Selector& rhs) const - { - auto sel = Cast(&rhs); - return sel ? *this == *sel : false; - } - - bool Pseudo_Selector::operator== (const Simple_Selector& rhs) const + bool Pseudo_Selector::operator== (const SimpleSelector& rhs) const { auto sel = Cast(&rhs); return sel ? *this == *sel : false; } - bool Wrapped_Selector::operator== (const Simple_Selector& rhs) const - { - auto sel = Cast(&rhs); - return sel ? *this == *sel : false; - } - - bool Attribute_Selector::operator== (const Simple_Selector& rhs) const + bool Attribute_Selector::operator== (const SimpleSelector& rhs) const { auto sel = Cast(&rhs); return sel ? *this == *sel : false; } - bool Placeholder_Selector::operator== (const Simple_Selector& rhs) const + bool Placeholder_Selector::operator== (const SimpleSelector& rhs) const { auto sel = Cast(&rhs); return sel ? *this == *sel : false; } - /***************************************************************************/ - /***************************************************************************/ + /*#########################################################################*/ + /*#########################################################################*/ bool Id_Selector::operator== (const Id_Selector& rhs) const { @@ -598,309 +355,38 @@ namespace Sass { return name() == rhs.name(); } - bool Parent_Selector::operator== (const Parent_Selector& rhs) const - { - // Parent has no namespacing - return name() == rhs.name(); - } - - bool Pseudo_Selector::operator== (const Pseudo_Selector& rhs) const - { - std::string lname = name(); - std::string rname = rhs.name(); - if (is_pseudo_class_element(lname)) { - if (rname[0] == ':' && rname[1] == ':') { - lname = lname.substr(1, std::string::npos); - } - } - // right hand is special pseudo (single colon) - if (is_pseudo_class_element(rname)) { - if (lname[0] == ':' && lname[1] == ':') { - lname = lname.substr(1, std::string::npos); - } - } - // Pseudo has no namespacing - if (lname != rname) return false; - String_Obj lhs_ex = expression(); - String_Obj rhs_ex = rhs.expression(); - if (rhs_ex && lhs_ex) return *lhs_ex == *rhs_ex; - else return lhs_ex.ptr() == rhs_ex.ptr(); - } - - bool Wrapped_Selector::operator== (const Wrapped_Selector& rhs) const - { - // Wrapped has no namespacing - if (name() != rhs.name()) return false; - return *(selector()) == *(rhs.selector()); - } - - bool Attribute_Selector::operator== (const Attribute_Selector& rhs) const - { - // get optional value state - bool no_lhs_val = value().isNull(); - bool no_rhs_val = rhs.value().isNull(); - // both are null, therefore equal - if (no_lhs_val && no_rhs_val) { - return (name() == rhs.name()) - && (matcher() == rhs.matcher()) - && (is_ns_eq(rhs)); - } - // both are defined, evaluate - if (no_lhs_val == no_rhs_val) { - return (name() == rhs.name()) - && (matcher() == rhs.matcher()) - && (is_ns_eq(rhs)) - && (*value() == *rhs.value()); - } - // not equal - return false; - } - bool Placeholder_Selector::operator== (const Placeholder_Selector& rhs) const { // Placeholder has no namespacing return name() == rhs.name(); } - /*#########################################################################*/ - /*#########################################################################*/ - - bool Simple_Selector::operator< (const Simple_Selector& rhs) const - { - switch (simple_type()) { - case ID_SEL: return (const Id_Selector&) *this < rhs; break; - case TYPE_SEL: return (const Type_Selector&) *this < rhs; break; - case CLASS_SEL: return (const Class_Selector&) *this < rhs; break; - case PARENT_SEL: return (const Parent_Selector&) *this < rhs; break; - case PSEUDO_SEL: return (const Pseudo_Selector&) *this < rhs; break; - case WRAPPED_SEL: return (const Wrapped_Selector&) *this < rhs; break; - case ATTRIBUTE_SEL: return (const Attribute_Selector&) *this < rhs; break; - case PLACEHOLDER_SEL: return (const Placeholder_Selector&) *this < rhs; break; - } - return false; - } - - /***************************************************************************/ - /***************************************************************************/ - - bool Id_Selector::operator< (const Simple_Selector& rhs) const - { - switch (rhs.simple_type()) { - case TYPE_SEL: return '#' < 's'; break; - case CLASS_SEL: return '#' < '.'; break; - case PARENT_SEL: return '#' < '&'; break; - case PSEUDO_SEL: return '#' < ':'; break; - case WRAPPED_SEL: return '#' < '('; break; - case ATTRIBUTE_SEL: return '#' < '['; break; - case PLACEHOLDER_SEL: return '#' < '%'; break; - case ID_SEL: /* let if fall through */ break; - } - const Id_Selector& sel = - (const Id_Selector&) rhs; - return *this < sel; - } - - bool Type_Selector::operator< (const Simple_Selector& rhs) const - { - switch (rhs.simple_type()) { - case ID_SEL: return 'e' < '#'; break; - case CLASS_SEL: return 'e' < '.'; break; - case PARENT_SEL: return 'e' < '&'; break; - case PSEUDO_SEL: return 'e' < ':'; break; - case WRAPPED_SEL: return 'e' < '('; break; - case ATTRIBUTE_SEL: return 'e' < '['; break; - case PLACEHOLDER_SEL: return 'e' < '%'; break; - case TYPE_SEL: /* let if fall through */ break; - } - const Type_Selector& sel = - (const Type_Selector&) rhs; - return *this < sel; - } - - bool Class_Selector::operator< (const Simple_Selector& rhs) const - { - switch (rhs.simple_type()) { - case ID_SEL: return '.' < '#'; break; - case TYPE_SEL: return '.' < 's'; break; - case PARENT_SEL: return '.' < '&'; break; - case PSEUDO_SEL: return '.' < ':'; break; - case WRAPPED_SEL: return '.' < '('; break; - case ATTRIBUTE_SEL: return '.' < '['; break; - case PLACEHOLDER_SEL: return '.' < '%'; break; - case CLASS_SEL: /* let if fall through */ break; - } - const Class_Selector& sel = - (const Class_Selector&) rhs; - return *this < sel; - } - - bool Pseudo_Selector::operator< (const Simple_Selector& rhs) const - { - switch (rhs.simple_type()) { - case ID_SEL: return ':' < '#'; break; - case TYPE_SEL: return ':' < 's'; break; - case CLASS_SEL: return ':' < '.'; break; - case PARENT_SEL: return ':' < '&'; break; - case WRAPPED_SEL: return ':' < '('; break; - case ATTRIBUTE_SEL: return ':' < '['; break; - case PLACEHOLDER_SEL: return ':' < '%'; break; - case PSEUDO_SEL: /* let if fall through */ break; - } - const Pseudo_Selector& sel = - (const Pseudo_Selector&) rhs; - return *this < sel; - } - - bool Wrapped_Selector::operator< (const Simple_Selector& rhs) const - { - switch (rhs.simple_type()) { - case ID_SEL: return '(' < '#'; break; - case TYPE_SEL: return '(' < 's'; break; - case CLASS_SEL: return '(' < '.'; break; - case PARENT_SEL: return '(' < '&'; break; - case PSEUDO_SEL: return '(' < ':'; break; - case ATTRIBUTE_SEL: return '(' < '['; break; - case PLACEHOLDER_SEL: return '(' < '%'; break; - case WRAPPED_SEL: /* let if fall through */ break; - } - const Wrapped_Selector& sel = - (const Wrapped_Selector&) rhs; - return *this < sel; - } - - bool Parent_Selector::operator< (const Simple_Selector& rhs) const - { - switch (rhs.simple_type()) { - case ID_SEL: return '&' < '#'; break; - case TYPE_SEL: return '&' < 's'; break; - case CLASS_SEL: return '&' < '.'; break; - case PSEUDO_SEL: return '&' < ':'; break; - case WRAPPED_SEL: return '&' < '('; break; - case ATTRIBUTE_SEL: return '&' < '['; break; - case PLACEHOLDER_SEL: return '&' < '%'; break; - case PARENT_SEL: /* let if fall through */ break; - } - const Parent_Selector& sel = - (const Parent_Selector&) rhs; - return *this < sel; - } - - bool Attribute_Selector::operator< (const Simple_Selector& rhs) const - { - switch (rhs.simple_type()) { - case ID_SEL: return '[' < '#'; break; - case TYPE_SEL: return '[' < 'e'; break; - case CLASS_SEL: return '[' < '.'; break; - case PARENT_SEL: return '[' < '&'; break; - case PSEUDO_SEL: return '[' < ':'; break; - case WRAPPED_SEL: return '[' < '('; break; - case PLACEHOLDER_SEL: return '[' < '%'; break; - case ATTRIBUTE_SEL: /* let if fall through */ break; - } - const Attribute_Selector& sel = - (const Attribute_Selector&) rhs; - return *this < sel; - } - - bool Placeholder_Selector::operator< (const Simple_Selector& rhs) const - { - switch (rhs.simple_type()) { - case ID_SEL: return '%' < '#'; break; - case TYPE_SEL: return '%' < 's'; break; - case CLASS_SEL: return '%' < '.'; break; - case PARENT_SEL: return '%' < '&'; break; - case PSEUDO_SEL: return '%' < ':'; break; - case WRAPPED_SEL: return '%' < '('; break; - case ATTRIBUTE_SEL: return '%' < '['; break; - case PLACEHOLDER_SEL: /* let if fall through */ break; - } - const Placeholder_Selector& sel = - (const Placeholder_Selector&) rhs; - return *this < sel; - } - - /***************************************************************************/ - /***************************************************************************/ - - bool Id_Selector::operator< (const Id_Selector& rhs) const - { - // ID has no namespacing - return name() < rhs.name(); - } - - bool Type_Selector::operator< (const Type_Selector& rhs) const - { - return has_ns_ == rhs.has_ns_ - ? (ns_ == rhs.ns_ - ? name_ < rhs.name_ - : ns_ < rhs.ns_) - : has_ns_ < rhs.has_ns_; - } - - bool Class_Selector::operator< (const Class_Selector& rhs) const - { - // Class has no namespacing - return name() < rhs.name(); - } - - bool Parent_Selector::operator< (const Parent_Selector& rhs) const - { - // Parent has no namespacing - return name() < rhs.name(); - } - - bool Pseudo_Selector::operator< (const Pseudo_Selector& rhs) const + bool Attribute_Selector::operator== (const Attribute_Selector& rhs) const { - std::string lname = name(); - std::string rname = rhs.name(); - if (is_pseudo_class_element(lname)) { - if (rname[0] == ':' && rname[1] == ':') { - lname = lname.substr(1, std::string::npos); - } - } - // right hand is special pseudo (single colon) - if (is_pseudo_class_element(rname)) { - if (lname[0] == ':' && lname[1] == ':') { - lname = lname.substr(1, std::string::npos); - } + // smaller return, equal go on, bigger abort + if (is_ns_eq(rhs)) { + if (name() != rhs.name()) return false; + if (matcher() != rhs.matcher()) return false; + const String* lhs_val = value(); + const String* rhs_val = rhs.value(); + return PtrObjEquality()(lhs_val, rhs_val); } - // Peudo has no namespacing - if (lname != rname) - { return lname < rname; } - String_Obj lhs_ex = expression(); - String_Obj rhs_ex = rhs.expression(); - if (rhs_ex && lhs_ex) return *lhs_ex < *rhs_ex; - else return lhs_ex.ptr() < rhs_ex.ptr(); - } - - bool Wrapped_Selector::operator< (const Wrapped_Selector& rhs) const - { - // Wrapped has no namespacing - if (name() != rhs.name()) - { return name() < rhs.name(); } - return *(selector()) < *(rhs.selector()); + else { return false; } } - bool Attribute_Selector::operator< (const Attribute_Selector& rhs) const + bool Pseudo_Selector::operator== (const Pseudo_Selector& rhs) const { if (is_ns_eq(rhs)) { - if (name() != rhs.name()) - { return name() < rhs.name(); } - if (matcher() != rhs.matcher()) - { return matcher() < rhs.matcher(); } - bool no_lhs_val = value().isNull(); - bool no_rhs_val = rhs.value().isNull(); - if (no_lhs_val && no_rhs_val) return false; // equal - else if (no_lhs_val) return true; // lhs is null - else if (no_rhs_val) return false; // rhs is null - return *value() < *rhs.value(); // both are given - } else { return ns() < rhs.ns(); } - } - - bool Placeholder_Selector::operator< (const Placeholder_Selector& rhs) const - { - // Placeholder has no namespacing - return name() < rhs.name(); + if (name() != rhs.name()) return false; + if (isElement() != rhs.isElement()) return false; + const String* lhs_arg = argument(); + const String* rhs_arg = rhs.argument(); + if (!PtrObjEquality()(lhs_arg, rhs_arg)) return false; + const SelectorList* lhs_sel = selector(); + const SelectorList* rhs_sel = rhs.selector(); + return PtrObjEquality()(lhs_sel, rhs_sel); + } + else { return false; } } /*#########################################################################*/ diff --git a/src/ast_sel_super.cpp b/src/ast_sel_super.cpp new file mode 100644 index 000000000..bc73f6ef7 --- /dev/null +++ b/src/ast_sel_super.cpp @@ -0,0 +1,539 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include "ast.hpp" + +#include "util_string.hpp" + +namespace Sass { + + // ########################################################################## + // To compare/debug against libsass you can use debugger.hpp: + // c++: std::cerr << "result " << debug_vec(compound) << "\n"; + // dart: stderr.writeln("result " + compound.toString()); + // ########################################################################## + + // ########################################################################## + // Returns whether [list1] is a superselector of [list2]. + // That is, whether [list1] matches every element that + // [list2] matches, as well as possibly additional elements. + // ########################################################################## + bool listIsSuperslector( + const std::vector& list1, + const std::vector& list2); + + // ########################################################################## + // Returns whether [complex1] is a superselector of [complex2]. + // That is, whether [complex1] matches every element that + // [complex2] matches, as well as possibly additional elements. + // ########################################################################## + bool complexIsSuperselector( + const std::vector& complex1, + const std::vector& complex2); + + // ########################################################################## + // Returns all pseudo selectors in [compound] that have + // a selector argument, and that have the given [name]. + // ########################################################################## + std::vector selectorPseudoNamed( + CompoundSelectorObj compound, std::string name) + { + std::vector rv; + for (SimpleSelectorObj sel : compound->elements()) { + if (Pseudo_Selector_Obj pseudo = Cast(sel)) { + if (pseudo->isClass() && pseudo->selector()) { + if (sel->name() == name) { + rv.push_back(sel); + } + } + } + } + return rv; + } + // EO selectorPseudoNamed + + // ########################################################################## + // Returns whether [simple1] is a superselector of [simple2]. + // That is, whether [simple1] matches every element that + // [simple2] matches, as well as possibly additional elements. + // ########################################################################## + bool simpleIsSuperselector( + const SimpleSelectorObj& simple1, + const SimpleSelectorObj& simple2) + { + // If they are equal they are superselectors + if (ObjEqualityFn(simple1, simple2)) { + return true; + } + // Some selector pseudoclasses can match normal selectors. + if (const Pseudo_Selector* pseudo = Cast(simple2)) { + if (pseudo->selector() && isSubselectorPseudo(pseudo->normalized())) { + for (auto complex : pseudo->selector()->elements()) { + // Make sure we have exacly one items + if (complex->length() != 1) { + return false; + } + // That items must be a compound selector + if (auto compound = Cast(complex->at(0))) { + // It must contain the lhs simple selector + if (!compound->contains(simple1)) { + return false; + } + } + } + return true; + } + } + return false; + } + // EO simpleIsSuperselector + + // ########################################################################## + // Returns whether [simple] is a superselector of [compound]. + // That is, whether [simple] matches every element that + // [compound] matches, as well as possibly additional elements. + // ########################################################################## + bool simpleIsSuperselectorOfCompound( + const SimpleSelectorObj& simple, + const CompoundSelectorObj& compound) + { + for (SimpleSelectorObj simple2 : compound->elements()) { + if (simpleIsSuperselector(simple, simple2)) { + return true; + } + } + return false; + } + // EO simpleIsSuperselectorOfCompound + + // ########################################################################## + // ########################################################################## + bool typeIsSuperselectorOfCompound( + const Type_Selector_Obj& type, + const CompoundSelectorObj& compound) + { + for (const SimpleSelectorObj& simple : compound->elements()) { + if (const Type_Selector_Obj& rhs = Cast(simple)) { + if (*type != *rhs) return true; + } + } + return false; + } + // EO typeIsSuperselectorOfCompound + + // ########################################################################## + // ########################################################################## + bool idIsSuperselectorOfCompound( + const Id_Selector_Obj& id, + const CompoundSelectorObj& compound) + { + for (const SimpleSelectorObj& simple : compound->elements()) { + if (const Id_Selector_Obj& rhs = Cast(simple)) { + if (*id != *rhs) return true; + } + } + return false; + } + // EO idIsSuperselectorOfCompound + + // ########################################################################## + // ########################################################################## + bool pseudoIsSuperselectorOfPseudo( + const Pseudo_Selector_Obj& pseudo1, + const Pseudo_Selector_Obj& pseudo2, + const ComplexSelectorObj& parent + ) + { + if (!pseudo2->selector()) return false; + if (pseudo1->name() == pseudo2->name()) { + SelectorListObj list = pseudo2->selector(); + return listIsSuperslector(list->elements(), { parent }); + } + return false; + } + // EO pseudoIsSuperselectorOfPseudo + + // ########################################################################## + // ########################################################################## + bool pseudoNotIsSuperselectorOfCompound( + const Pseudo_Selector_Obj& pseudo1, + const CompoundSelectorObj& compound2, + const ComplexSelectorObj& parent) + { + for (const SimpleSelectorObj& simple2 : compound2->elements()) { + if (const Type_Selector_Obj& type2 = Cast(simple2)) { + if (const CompoundSelectorObj& compound1 = Cast(parent->last())) { + if (typeIsSuperselectorOfCompound(type2, compound1)) return true; + } + } + else if (const Id_Selector_Obj& id2 = Cast(simple2)) { + if (const CompoundSelectorObj& compound1 = Cast(parent->last())) { + if (idIsSuperselectorOfCompound(id2, compound1)) return true; + } + } + else if (const Pseudo_Selector_Obj& pseudo2 = Cast(simple2)) { + if (pseudoIsSuperselectorOfPseudo(pseudo1, pseudo2, parent)) return true; + } + } + return false; + } + // pseudoNotIsSuperselectorOfCompound + + // ########################################################################## + // Returns whether [pseudo1] is a superselector of [compound2]. + // That is, whether [pseudo1] matches every element that [compound2] + // matches, as well as possibly additional elements. This assumes that + // [pseudo1]'s `selector` argument is not `null`. If [parents] is passed, + // it represents the parents of [compound2]. This is relevant for pseudo + // selectors with selector arguments, where we may need to know if the + // parent selectors in the selector argument match [parents]. + // ########################################################################## + bool selectorPseudoIsSuperselector( + const Pseudo_Selector_Obj& pseudo1, + const CompoundSelectorObj& compound2, + // ToDo: is this really the most convenient way to do this? + std::vector::const_iterator parents_from, + std::vector::const_iterator parents_to) + { + + // ToDo: move normalization function + std::string name(Util::unvendor(pseudo1->name())); + + if (name == "matches" || name == "any") { + std::vector pseudos = + selectorPseudoNamed(compound2, pseudo1->name()); + SelectorListObj selector1 = pseudo1->selector(); + for (Pseudo_Selector_Obj pseudo2 : pseudos) { + SelectorListObj selector = pseudo2->selector(); + if (selector1->isSuperselectorOf(selector)) { + return true; + } + } + + for (ComplexSelectorObj complex1 : selector1->elements()) { + std::vector parents; + for (auto cur = parents_from; cur != parents_to; cur++) { + parents.push_back(*cur); + } + parents.push_back(compound2); + if (complexIsSuperselector(complex1->elements(), parents)) { + return true; + } + } + + } + else if (name == "has" || name == "host" || name == "host-context" || name == "slotted") { + std::vector pseudos = + selectorPseudoNamed(compound2, pseudo1->name()); + SelectorListObj selector1 = pseudo1->selector(); + for (Pseudo_Selector_Obj pseudo2 : pseudos) { + SelectorListObj selector = pseudo2->selector(); + if (selector1->isSuperselectorOf(selector)) { + return true; + } + } + + } + else if (name == "not") { + for (ComplexSelectorObj complex : pseudo1->selector()->elements()) { + if (!pseudoNotIsSuperselectorOfCompound(pseudo1, compound2, complex)) return false; + } + return true; + } + else if (name == "current") { + std::vector pseudos = + selectorPseudoNamed(compound2, "current"); + for (Pseudo_Selector_Obj pseudo2 : pseudos) { + if (ObjEqualityFn(pseudo1, pseudo2)) return true; + } + + } + else if (name == "nth-child" || name == "nth-last-child") { + for (auto simple2 : compound2->elements()) { + if (Pseudo_Selector_Obj pseudo2 = simple2->getPseudoSelector()) { + if (pseudo1->name() != pseudo2->name()) continue; + if (!ObjEqualityFn(pseudo1->argument(), pseudo2->argument())) continue; + if (pseudo1->selector()->isSuperselectorOf(pseudo2->selector())) return true; + } + } + return false; + } + + return false; + + } + // EO selectorPseudoIsSuperselector + + // ########################################################################## + // Returns whether [compound1] is a superselector of [compound2]. + // That is, whether [compound1] matches every element that [compound2] + // matches, as well as possibly additional elements. If [parents] is + // passed, it represents the parents of [compound2]. This is relevant + // for pseudo selectors with selector arguments, where we may need to + // know if the parent selectors in the selector argument match [parents]. + // ########################################################################## + bool compoundIsSuperselector( + const CompoundSelectorObj& compound1, + const CompoundSelectorObj& compound2, + // ToDo: is this really the most convenient way to do this? + const std::vector::const_iterator parents_from, + const std::vector::const_iterator parents_to) + { + // Every selector in [compound1.components] must have + // a matching selector in [compound2.components]. + for (SimpleSelectorObj simple1 : compound1->elements()) { + Pseudo_Selector_Obj pseudo1 = Cast(simple1); + if (pseudo1 && pseudo1->selector()) { + if (!selectorPseudoIsSuperselector(pseudo1, compound2, parents_from, parents_to)) { + return false; + } + } + else if (!simpleIsSuperselectorOfCompound(simple1, compound2)) { + return false; + } + } + // [compound1] can't be a superselector of a selector + // with pseudo-elements that [compound2] doesn't share. + for (SimpleSelectorObj simple2 : compound2->elements()) { + Pseudo_Selector_Obj pseudo2 = Cast(simple2); + if (pseudo2 && pseudo2->isElement()) { + if (!simpleIsSuperselectorOfCompound(pseudo2, compound1)) { + return false; + } + } + } + return true; + } + // EO compoundIsSuperselector + + // ########################################################################## + // Returns whether [compound1] is a superselector of [compound2]. + // That is, whether [compound1] matches every element that [compound2] + // matches, as well as possibly additional elements. If [parents] is + // passed, it represents the parents of [compound2]. This is relevant + // for pseudo selectors with selector arguments, where we may need to + // know if the parent selectors in the selector argument match [parents]. + // ########################################################################## + bool compoundIsSuperselector( + const CompoundSelectorObj& compound1, + const CompoundSelectorObj& compound2, + const std::vector& parents) + { + return compoundIsSuperselector( + compound1, compound2, + parents.begin(), parents.end() + ); + } + // EO compoundIsSuperselector + + // ########################################################################## + // Returns whether [complex1] is a superselector of [complex2]. + // That is, whether [complex1] matches every element that + // [complex2] matches, as well as possibly additional elements. + // ########################################################################## + bool complexIsSuperselector( + const std::vector& complex1, + const std::vector& complex2) + { + + // Selectors with trailing operators are neither superselectors nor subselectors. + if (!complex1.empty() && Cast(complex1.back())) return false; + if (!complex2.empty() && Cast(complex2.back())) return false; + + size_t i1 = 0, i2 = 0; + while (true) { + + size_t remaining1 = complex1.size() - i1; + size_t remaining2 = complex2.size() - i2; + + if (remaining1 == 0 || remaining2 == 0) { + return false; + } + // More complex selectors are never + // superselectors of less complex ones. + if (remaining1 > remaining2) { + return false; + } + + // Selectors with leading operators are + // neither superselectors nor subselectors. + if (Cast(complex1[i1])) { + return false; + } + if (Cast(complex2[i2])) { + return false; + } + + CompoundSelectorObj compound1 = Cast(complex1[i1]); + CompoundSelectorObj compound2 = Cast(complex2.back()); + + if (remaining1 == 1) { + std::vector::const_iterator parents_to = complex2.end(); + std::vector::const_iterator parents_from = complex2.begin(); + std::advance(parents_from, i2 + 1); // equivalent to dart `.skip(i2 + 1)` + bool rv = compoundIsSuperselector(compound1, compound2, parents_from, parents_to); + std::vector pp; + + std::vector::const_iterator end = parents_to; + std::vector::const_iterator beg = parents_from; + while (beg != end) { + pp.push_back(*beg); + beg++; + } + + return rv; + } + + // Find the first index where `complex2.sublist(i2, afterSuperselector)` + // is a subselector of [compound1]. We stop before the superselector + // would encompass all of [complex2] because we know [complex1] has + // more than one element, and consuming all of [complex2] wouldn't + // leave anything for the rest of [complex1] to match. + size_t afterSuperselector = i2 + 1; + for (; afterSuperselector < complex2.size(); afterSuperselector++) { + SelectorComponentObj component2 = complex2[afterSuperselector - 1]; + if (CompoundSelectorObj compound2 = Cast(component2)) { + std::vector::const_iterator parents_to = complex2.begin(); + std::vector::const_iterator parents_from = complex2.begin(); + // complex2.take(afterSuperselector - 1).skip(i2 + 1) + std::advance(parents_from, i2 + 1); // equivalent to dart `.skip` + std::advance(parents_to, afterSuperselector); // equivalent to dart `.take` + if (compoundIsSuperselector(compound1, compound2, parents_from, parents_to)) { + break; + } + } + } + if (afterSuperselector == complex2.size()) { + return false; + } + + SelectorComponentObj component1 = complex1[i1 + 1], + component2 = complex2[afterSuperselector]; + + SelectorCombinatorObj combinator1 = Cast(component1); + SelectorCombinatorObj combinator2 = Cast(component2); + + if (!combinator1.isNull()) { + + if (combinator2.isNull()) { + return false; + } + // `.a ~ .b` is a superselector of `.a + .b`, + // but otherwise the combinators must match. + if (combinator1->isGeneralCombinator()) { + if (combinator2->isChildCombinator()) { + return false; + } + } + else if (*combinator1 != *combinator2) { + return false; + } + + // `.foo > .baz` is not a superselector of `.foo > .bar > .baz` or + // `.foo > .bar .baz`, despite the fact that `.baz` is a superselector of + // `.bar > .baz` and `.bar .baz`. Same goes for `+` and `~`. + if (remaining1 == 3 && remaining2 > 3) { + return false; + } + + i1 += 2; i2 = afterSuperselector + 1; + + } + else if (!combinator2.isNull()) { + if (!combinator2->isChildCombinator()) { + return false; + } + i1 += 1; i2 = afterSuperselector + 1; + } + else { + i1 += 1; i2 = afterSuperselector; + } + } + + return false; + + } + // EO complexIsSuperselector + + // ########################################################################## + // Like [complexIsSuperselector], but compares [complex1] + // and [complex2] as though they shared an implicit base + // [SimpleSelector]. For example, `B` is not normally a + // superselector of `B A`, since it doesn't match elements + // that match `A`. However, it *is* a parent superselector, + // since `B X` is a superselector of `B A X`. + // ########################################################################## + bool complexIsParentSuperselector( + const std::vector& complex1, + const std::vector& complex2) + { + // Try some simple heuristics to see if we can avoid allocations. + if (complex1.empty() && complex2.empty()) return false; + if (Cast(complex1.front())) return false; + if (Cast(complex2.front())) return false; + if (complex1.size() > complex2.size()) return false; + // TODO(nweiz): There's got to be a way to do this without a bunch of extra allocations... + std::vector cplx1(complex1); + std::vector cplx2(complex2); + CompoundSelectorObj base = SASS_MEMORY_NEW(CompoundSelector, "[tmp]"); + cplx1.push_back(base); cplx2.push_back(base); + return complexIsSuperselector(cplx1, cplx2); + } + // EO complexIsParentSuperselector + + // ########################################################################## + // Returns whether [list] has a superselector for [complex]. + // That is, whether an item in [list] matches every element that + // [complex] matches, as well as possibly additional elements. + // ########################################################################## + bool listHasSuperslectorForComplex( + std::vector list, + ComplexSelectorObj complex) + { + // Return true if every [complex] selector on [list2] + // is a super selector of the full selector [list1]. + for (ComplexSelectorObj lhs : list) { + if (complexIsSuperselector(lhs->elements(), complex->elements())) { + return true; + } + } + return false; + } + // listIsSuperslectorOfComplex + + // ########################################################################## + // Returns whether [list1] is a superselector of [list2]. + // That is, whether [list1] matches every element that + // [list2] matches, as well as possibly additional elements. + // ########################################################################## + bool listIsSuperslector( + const std::vector& list1, + const std::vector& list2) + { + // Return true if every [complex] selector on [list2] + // is a super selector of the full selector [list1]. + for (ComplexSelectorObj complex : list2) { + if (!listHasSuperslectorForComplex(list1, complex)) { + return false; + } + } + return true; + } + // EO listIsSuperslector + + // ########################################################################## + // Implement selector methods (dispatch to functions) + // ########################################################################## + bool SelectorList::isSuperselectorOf(const SelectorList* sub) const + { + return listIsSuperslector(elements(), sub->elements()); + } + bool ComplexSelector::isSuperselectorOf(const ComplexSelector* sub) const + { + return complexIsSuperselector(elements(), sub->elements()); + } + + // ########################################################################## + // ########################################################################## + +} diff --git a/src/ast_sel_unify.cpp b/src/ast_sel_unify.cpp index 00d898c56..d3cb88861 100644 --- a/src/ast_sel_unify.cpp +++ b/src/ast_sel_unify.cpp @@ -1,280 +1,275 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" -#include "ast.hpp" -#include "context.hpp" -#include "node.hpp" -#include "eval.hpp" -#include "extend.hpp" -#include "emitter.hpp" -#include "color_maps.hpp" -#include "ast_fwd_decl.hpp" -#include -#include -#include -#include -#include -#include -#include - -#include "ast_selectors.hpp" -// #define DEBUG_UNIFY +#include "ast.hpp" namespace Sass { - Compound_Selector* Compound_Selector::unify_with(Compound_Selector* rhs) + // ########################################################################## + // Returns the contents of a [SelectorList] that matches only + // elements that are matched by both [complex1] and [complex2]. + // If no such list can be produced, returns `null`. + // ########################################################################## + // ToDo: fine-tune API to avoid unecessary wrapper allocations + // ########################################################################## + std::vector> unifyComplex( + const std::vector>& complexes) { - #ifdef DEBUG_UNIFY - const std::string debug_call = "unify(Compound[" + this->to_string() + "], Compound[" + rhs->to_string() + "])"; - std::cerr << debug_call << std::endl; - #endif + SASS_ASSERT(!complexes.empty(), "Can't unify empty list"); + if (complexes.size() == 1) return complexes; + + CompoundSelectorObj unifiedBase = SASS_MEMORY_NEW(CompoundSelector, ParserState("[phony]")); + for (auto complex : complexes) { + SelectorComponentObj base = complex.back(); + if (CompoundSelector * comp = base->getCompound()) { + if (unifiedBase->empty()) { + unifiedBase->concat(comp); + } + else { + for (SimpleSelectorObj simple : comp->elements()) { + unifiedBase = simple->unifyWith(unifiedBase); + if (unifiedBase.isNull()) return {}; + } + } + } + else { + return {}; + } + } + + std::vector> complexesWithoutBases; + for (size_t i = 0; i < complexes.size(); i += 1) { + std::vector sel = complexes[i]; + sel.pop_back(); // remove last item (base) from the list + complexesWithoutBases.push_back(std::move(sel)); + } + + complexesWithoutBases.back().push_back(unifiedBase); + + return weave(complexesWithoutBases); + + } + // EO unifyComplex + + // ########################################################################## + // Returns a [CompoundSelector] that matches only elements + // that are matched by both [compound1] and [compound2]. + // If no such selector can be produced, returns `null`. + // ########################################################################## + CompoundSelector* CompoundSelector::unifyWith(CompoundSelector* rhs) + { if (empty()) return rhs; - Compound_Selector_Obj unified = SASS_MEMORY_COPY(rhs); - for (const Simple_Selector_Obj& sel : elements()) { - unified = sel->unify_with(unified); + CompoundSelectorObj unified = SASS_MEMORY_COPY(rhs); + for (const SimpleSelectorObj& sel : elements()) { + unified = sel->unifyWith(unified); if (unified.isNull()) break; } - - #ifdef DEBUG_UNIFY - std::cerr << "> " << debug_call << " = Compound[" << unified->to_string() << "]" << std::endl; - #endif return unified.detach(); } - - Compound_Selector* Simple_Selector::unify_with(Compound_Selector* rhs) + // EO CompoundSelector::unifyWith(CompoundSelector*) + + // ########################################################################## + // Returns the compoments of a [CompoundSelector] that matches only elements + // matched by both this and [compound]. By default, this just returns a copy + // of [compound] with this selector added to the end, or returns the original + // array if this selector already exists in it. Returns `null` if unification + // is impossible—for example, if there are multiple ID selectors. + // ########################################################################## + // This is implemented in `selector/simple.dart` as `SimpleSelector::unify` + // ########################################################################## + CompoundSelector* SimpleSelector::unifyWith(CompoundSelector* rhs) { - #ifdef DEBUG_UNIFY - const std::string debug_call = "unify(Simple[" + this->to_string() + "], Compound[" + rhs->to_string() + "])"; - std::cerr << debug_call << std::endl; - #endif if (rhs->length() == 1) { - if (rhs->at(0)->is_universal()) { - Compound_Selector* this_compound = SASS_MEMORY_NEW(Compound_Selector, pstate(), 1); + if (rhs->get(0)->is_universal()) { + CompoundSelector* this_compound = SASS_MEMORY_NEW(CompoundSelector, pstate()); this_compound->append(SASS_MEMORY_COPY(this)); - Compound_Selector* unified = rhs->at(0)->unify_with(this_compound); + CompoundSelector* unified = rhs->get(0)->unifyWith(this_compound); if (unified == nullptr || unified != this_compound) delete this_compound; - - #ifdef DEBUG_UNIFY - std::cerr << "> " << debug_call << " = " << "Compound[" << unified->to_string() << "]" << std::endl; - #endif return unified; } } - for (const Simple_Selector_Obj& sel : rhs->elements()) { + for (const SimpleSelectorObj& sel : rhs->elements()) { if (*this == *sel) { - #ifdef DEBUG_UNIFY - std::cerr << "> " << debug_call << " = " << "Compound[" << rhs->to_string() << "]" << std::endl; - #endif return rhs; } } - const int lhs_order = this->unification_order(); - size_t i = rhs->length(); - while (i > 0 && lhs_order < rhs->at(i - 1)->unification_order()) --i; - rhs->insert(rhs->begin() + i, this); - #ifdef DEBUG_UNIFY - std::cerr << "> " << debug_call << " = " << "Compound[" << rhs->to_string() << "]" << std::endl; - #endif - return rhs; - } - Simple_Selector* Type_Selector::unify_with(Simple_Selector* rhs) - { - #ifdef DEBUG_UNIFY - const std::string debug_call = "unify(Type[" + this->to_string() + "], Simple[" + rhs->to_string() + "])"; - std::cerr << debug_call << std::endl; - #endif + CompoundSelectorObj result = SASS_MEMORY_NEW(CompoundSelector, rhs->pstate()); - bool rhs_ns = false; - if (!(is_ns_eq(*rhs) || rhs->is_universal_ns())) { - if (!is_universal_ns()) { - #ifdef DEBUG_UNIFY - std::cerr << "> " << debug_call << " = nullptr" << std::endl; - #endif - return nullptr; - } - rhs_ns = true; - } - bool rhs_name = false; - if (!(name_ == rhs->name() || rhs->is_universal())) { - if (!(is_universal())) { - #ifdef DEBUG_UNIFY - std::cerr << "> " << debug_call << " = nullptr" << std::endl; - #endif - return nullptr; + bool addedThis = false; + for (auto simple : rhs->elements()) { + // Make sure pseudo selectors always come last. + if (!addedThis && simple->getPseudoSelector()) { + result->append(this); + addedThis = true; } - rhs_name = true; + result->append(simple); } - if (rhs_ns) { - ns(rhs->ns()); - has_ns(rhs->has_ns()); + + if (!addedThis) { + result->append(this); } - if (rhs_name) name(rhs->name()); - #ifdef DEBUG_UNIFY - std::cerr << "> " << debug_call << " = Simple[" << this->to_string() << "]" << std::endl; - #endif - return this; + return result.detach(); + } + // EO SimpleSelector::unifyWith(CompoundSelector*) - Compound_Selector* Type_Selector::unify_with(Compound_Selector* rhs) + // ########################################################################## + // This is implemented in `selector/type.dart` as `PseudoSelector::unify` + // ########################################################################## + CompoundSelector* Type_Selector::unifyWith(CompoundSelector* rhs) { - #ifdef DEBUG_UNIFY - const std::string debug_call = "unify(Type[" + this->to_string() + "], Compound[" + rhs->to_string() + "])"; - std::cerr << debug_call << std::endl; - #endif - if (rhs->empty()) { rhs->append(this); - #ifdef DEBUG_UNIFY - std::cerr << "> " << debug_call << " = Compound[" << rhs->to_string() << "]" << std::endl; - #endif return rhs; } - Type_Selector* rhs_0 = Cast(rhs->at(0)); - if (rhs_0 != nullptr) { - Simple_Selector* unified = unify_with(rhs_0); + Type_Selector* type = Cast(rhs->at(0)); + if (type != nullptr) { + SimpleSelector* unified = unifyWith(type); if (unified == nullptr) { - #ifdef DEBUG_UNIFY - std::cerr << "> " << debug_call << " = nullptr" << std::endl; - #endif return nullptr; } rhs->elements()[0] = unified; - } else if (!is_universal() || (has_ns_ && ns_ != "*")) { + } + else if (!is_universal() || (has_ns_ && ns_ != "*")) { rhs->insert(rhs->begin(), this); } - #ifdef DEBUG_UNIFY - std::cerr << "> " << debug_call << " = Compound[" << rhs->to_string() << "]" << std::endl; - #endif return rhs; } - Compound_Selector* Class_Selector::unify_with(Compound_Selector* rhs) - { - rhs->has_line_break(has_line_break()); - return Simple_Selector::unify_with(rhs); - } - - Compound_Selector* Id_Selector::unify_with(Compound_Selector* rhs) + // ########################################################################## + // This is implemented in `selector/id.dart` as `PseudoSelector::unify` + // ########################################################################## + CompoundSelector* Id_Selector::unifyWith(CompoundSelector* rhs) { - for (const Simple_Selector_Obj& sel : rhs->elements()) { - if (Id_Selector* id_sel = Cast(sel)) { + for (const SimpleSelector* sel : rhs->elements()) { + if (const Id_Selector* id_sel = Cast(sel)) { if (id_sel->name() != name()) return nullptr; } } - rhs->has_line_break(has_line_break()); - return Simple_Selector::unify_with(rhs); - } - - Compound_Selector* Pseudo_Selector::unify_with(Compound_Selector* rhs) - { - if (is_pseudo_element()) { - for (const Simple_Selector_Obj& sel : rhs->elements()) { - if (Pseudo_Selector* pseudo_sel = Cast(sel)) { - if (pseudo_sel->is_pseudo_element() && pseudo_sel->name() != name()) return nullptr; - } - } - } - return Simple_Selector::unify_with(rhs); + return SimpleSelector::unifyWith(rhs); } - Selector_List* Complex_Selector::unify_with(Complex_Selector* rhs) + // ########################################################################## + // This is implemented in `selector/pseudo.dart` as `PseudoSelector::unify` + // ########################################################################## + CompoundSelector* Pseudo_Selector::unifyWith(CompoundSelector* compound) { - #ifdef DEBUG_UNIFY - const std::string debug_call = "unify(Complex[" + this->to_string() + "], Complex[" + rhs->to_string() + "])"; - std::cerr << debug_call << std::endl; - #endif - - // get last tails (on the right side) - Complex_Selector* l_last = this->mutable_last(); - Complex_Selector* r_last = rhs->mutable_last(); - - // check valid pointers (assertion) - SASS_ASSERT(l_last, "lhs is null"); - SASS_ASSERT(r_last, "rhs is null"); - - // Not sure about this check, but closest way I could check - // was to see if this is a ruby 'SimpleSequence' equivalent. - // It seems to do the job correctly as some specs react to this - if (l_last->combinator() != Combinator::ANCESTOR_OF) return nullptr; - if (r_last->combinator() != Combinator::ANCESTOR_OF) return nullptr; - - // get the headers for the last tails - Compound_Selector* l_last_head = l_last->head(); - Compound_Selector* r_last_head = r_last->head(); - - // check valid head pointers (assertion) - SASS_ASSERT(l_last_head, "lhs head is null"); - SASS_ASSERT(r_last_head, "rhs head is null"); - - // get the unification of the last compound selectors - Compound_Selector_Obj unified = r_last_head->unify_with(l_last_head); - - // abort if we could not unify heads - if (unified == nullptr) return nullptr; - - // move the head - if (l_last_head->is_universal()) l_last->head({}); - r_last->head(unified); - #ifdef DEBUG_UNIFY - std::cerr << "> " << debug_call << " before weave: lhs=" << this->to_string() << " rhs=" << rhs->to_string() << std::endl; - #endif + if (compound->length() == 1 && compound->first()->is_universal()) { + // std::cerr << "implement universal pseudo\n"; + } - // create nodes from both selectors - Node lhsNode = complexSelectorToNode(this); - Node rhsNode = complexSelectorToNode(rhs); + for (const SimpleSelectorObj& sel : compound->elements()) { + if (*this == *sel) { + return compound; + } + } - // Complex_Selector_Obj fake = unified->to_complex(); - // Node unified_node = complexSelectorToNode(fake); - // // add to permutate the list? - // rhsNode.plus(unified_node); + CompoundSelectorObj result = SASS_MEMORY_NEW(CompoundSelector, compound->pstate()); + + bool addedThis = false; + for (auto simple : compound->elements()) { + // Make sure pseudo selectors always come last. + if (Pseudo_Selector_Obj pseudo = simple->getPseudoSelector()) { + if (pseudo->isElement()) { + // A given compound selector may only contain one pseudo element. If + // [compound] has a different one than [this], unification fails. + if (isElement()) { + return {}; + } + // Otherwise, this is a pseudo selector and + // should come before pseduo elements. + result->append(this); + addedThis = true; + } + } + result->append(simple); + } - // do some magic we inherit from node and extend - Node node = subweave(lhsNode, rhsNode); - Selector_List_Obj result = SASS_MEMORY_NEW(Selector_List, pstate(), node.collection()->size()); - for (auto &item : *node.collection()) { - result->append(nodeToComplexSelector(Node::naiveTrim(item))); + if (!addedThis) { + result->append(this); } - #ifdef DEBUG_UNIFY - std::cerr << "> " << debug_call << " = " << result->to_string() << std::endl; - #endif + return result.detach(); - // only return if list has some entries - return result->length() ? result.detach() : nullptr; } + // EO Pseudo_Selector::unifyWith(CompoundSelector* + + // ########################################################################## + // This is implemented in `extend/functions.dart` as `unifyUniversalAndElement` + // Returns a [SimpleSelector] that matches only elements that are matched by + // both [selector1] and [selector2], which must both be either [UniversalSelector]s + // or [TypeSelector]s. If no such selector can be produced, returns `null`. + // Note: libsass handles universal selector directly within the type selector + // ########################################################################## + SimpleSelector* Type_Selector::unifyWith(const SimpleSelector* rhs) + { + bool rhs_ns = false; + if (!(is_ns_eq(*rhs) || rhs->is_universal_ns())) { + if (!is_universal_ns()) { + return nullptr; + } + rhs_ns = true; + } + bool rhs_name = false; + if (!(name_ == rhs->name() || rhs->is_universal())) { + if (!(is_universal())) { + return nullptr; + } + rhs_name = true; + } + if (rhs_ns) { + ns(rhs->ns()); + has_ns(rhs->has_ns()); + } + if (rhs_name) name(rhs->name()); + return this; + } + // EO Type_Selector::unifyWith(const SimpleSelector*) - Selector_List* Selector_List::unify_with(Selector_List* rhs) { - #ifdef DEBUG_UNIFY - const std::string debug_call = "unify(List[" + this->to_string() + "], List[" + rhs->to_string() + "])"; - std::cerr << debug_call << std::endl; - #endif + // ########################################################################## + // Unify two complex selectors. Internally calls `unifyComplex` + // and then wraps the result in newly create ComplexSelectors. + // ########################################################################## + SelectorList* ComplexSelector::unifyWith(ComplexSelector* rhs) + { + SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate()); + std::vector> rv = + unifyComplex({ elements(), rhs->elements() }); + for (std::vector items : rv) { + ComplexSelectorObj sel = SASS_MEMORY_NEW(ComplexSelector, pstate()); + sel->elements() = std::move(items); + list->append(sel); + } + return list.detach(); + } + // EO ComplexSelector::unifyWith(ComplexSelector*) - std::vector result; - // Unify all of children with RHS's children, storing the results in `unified_complex_selectors` - for (Complex_Selector_Obj& seq1 : elements()) { - for (Complex_Selector_Obj& seq2 : rhs->elements()) { - Complex_Selector_Obj seq1_copy = SASS_MEMORY_CLONE(seq1); - Complex_Selector_Obj seq2_copy = SASS_MEMORY_CLONE(seq2); - Selector_List_Obj unified = seq1_copy->unify_with(seq2_copy); - if (unified) { - result.reserve(result.size() + unified->length()); - std::copy(unified->begin(), unified->end(), std::back_inserter(result)); + // ########################################################################## + // only called from the sass function `selector-unify` + // ########################################################################## + SelectorList* SelectorList::unifyWith(SelectorList* rhs) + { + SelectorList* slist = SASS_MEMORY_NEW(SelectorList, pstate()); + // Unify all of children with RHS's children, + // storing the results in `unified_complex_selectors` + for (ComplexSelectorObj& seq1 : elements()) { + for (ComplexSelectorObj& seq2 : rhs->elements()) { + if (SelectorListObj unified = seq1->unifyWith(seq2)) { + std::move(unified->begin(), unified->end(), + std::inserter(slist->elements(), slist->end())); } } } - - // Creates the final Selector_List by combining all the complex selectors - Selector_List* final_result = SASS_MEMORY_NEW(Selector_List, pstate(), result.size()); - for (Complex_Selector_Obj& sel : result) { - final_result->append(sel); - } - #ifdef DEBUG_UNIFY - std::cerr << "> " << debug_call << " = " << final_result->to_string() << std::endl; - #endif - return final_result; + return slist; } + // EO SelectorList::unifyWith(SelectorList*) + + // ########################################################################## + // ########################################################################## } diff --git a/src/ast_sel_weave.cpp b/src/ast_sel_weave.cpp new file mode 100644 index 000000000..e543ecc36 --- /dev/null +++ b/src/ast_sel_weave.cpp @@ -0,0 +1,612 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include "ast.hpp" +#include "permutate.hpp" +#include "dart_helpers.hpp" + +namespace Sass { + + // ########################################################################## + // Returns whether or not [compound] contains a `::root` selector. + // ########################################################################## + bool hasRoot(const CompoundSelector* compound) + { + // Libsass does not yet know the root selector + return false; + } + // EO hasRoot + + // ########################################################################## + // Returns whether a [CompoundSelector] may contain only + // one simple selector of the same type as [simple]. + // ########################################################################## + bool isUnique(const SimpleSelector* simple) + { + if (Cast(simple)) return true; + if (const Pseudo_Selector * pseudo = Cast(simple)) { + if (pseudo->is_pseudo_element()) return true; + } + return false; + } + // EO isUnique + + // ########################################################################## + // Returns whether [complex1] and [complex2] need to be unified to + // produce a valid combined selector. This is necessary when both + // selectors contain the same unique simple selector, such as an ID. + // ########################################################################## + bool mustUnify( + const std::vector& complex1, + const std::vector& complex2) + { + + std::vector uniqueSelectors1; + for (const SelectorComponent* component : complex1) { + if (const CompoundSelector * compound = component->getCompound()) { + for (const SimpleSelector* sel : compound->elements()) { + if (isUnique(sel)) { + uniqueSelectors1.push_back(sel); + } + } + } + } + if (uniqueSelectors1.empty()) return false; + + // ToDo: unsure if this is correct + for (const SelectorComponent* component : complex2) { + if (const CompoundSelector * compound = component->getCompound()) { + for (const SimpleSelector* sel : compound->elements()) { + if (isUnique(sel)) { + for (auto check : uniqueSelectors1) { + if (*check == *sel) return true; + } + } + } + } + } + + return false; + + } + // EO isUnique + + // ########################################################################## + // Helper function used by `weaveParents` + // ########################################################################## + bool cmpGroups( + const std::vector& group1, + const std::vector& group2, + std::vector& select) + { + + if (group1.size() == group2.size() && std::equal(group1.begin(), group1.end(), group2.begin(), PtrObjEqualityFn)) { + select = group1; + return true; + } + + if (!Cast(group1.front())) { + select = {}; + return false; + } + if (!Cast(group2.front())) { + select = {}; + return false; + } + + if (complexIsParentSuperselector(group1, group2)) { + select = group2; + return true; + } + if (complexIsParentSuperselector(group2, group1)) { + select = group1; + return true; + } + + if (!mustUnify(group1, group2)) { + select = {}; + return false; + } + + std::vector> unified + = unifyComplex({ group1, group2 }); + if (unified.empty()) return false; + if (unified.size() > 1) return false; + select = unified.front(); + return true; + } + // EO cmpGroups + + // ########################################################################## + // Helper function used by `weaveParents` + // ########################################################################## + template + bool checkForEmptyChild(const T& item) { + return item.empty(); + } + // EO checkForEmptyChild + + // ########################################################################## + // Helper function used by `weaveParents` + // ########################################################################## + bool cmpChunkForEmptySequence( + const std::vector>& seq, + const std::vector& group) + { + return seq.empty(); + } + // EO cmpChunkForEmptySequence + + // ########################################################################## + // Helper function used by `weaveParents` + // ########################################################################## + bool cmpChunkForParentSuperselector( + const std::vector< std::vector>& seq, + const std::vector& group) + { + return seq.empty() || complexIsParentSuperselector(seq.front(), group); + } + // EO cmpChunkForParentSuperselector + + // ########################################################################## + // Returns all orderings of initial subseqeuences of [queue1] and [queue2]. + // The [done] callback is used to determine the extent of the initial + // subsequences. It's called with each queue until it returns `true`. + // Destructively removes the initial subsequences of [queue1] and [queue2]. + // For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|` denoting + // the boundary of the initial subsequence), this would return `[(A B C 1 2), + // (1 2 A B C)]`. The queues would then contain `(D E)` and `(3 4 5)`. + // ########################################################################## + template + std::vector> getChunks( + std::vector& queue1, std::vector& queue2, + const T& group, bool(*done)(const std::vector&, const T&) + ) { + + std::vector chunk1; + while (!done(queue1, group)) { + chunk1.push_back(queue1.front()); + queue1.erase(queue1.begin()); + } + + std::vector chunk2; + while (!done(queue2, group)) { + chunk2.push_back(queue2.front()); + queue2.erase(queue2.begin()); + } + + if (chunk1.empty() && chunk2.empty()) return {}; + else if (chunk1.empty()) return { chunk2 }; + else if (chunk2.empty()) return { chunk1 }; + + std::vector choice1(chunk1), choice2(chunk2); + std::move(std::begin(chunk2), std::end(chunk2), + std::inserter(choice1, std::end(choice1))); + std::move(std::begin(chunk1), std::end(chunk1), + std::inserter(choice2, std::end(choice2))); + return { choice1, choice2 }; + } + // EO getChunks + + // ########################################################################## + // If the first element of [queue] has a `::root` + // selector, removes and returns that element. + // ########################################################################## + CompoundSelectorObj getFirstIfRoot(std::vector& queue) { + if (queue.empty()) return {}; + SelectorComponent* first = queue.front(); + if (CompoundSelector* sel = Cast(first)) { + if (!hasRoot(sel)) return {}; + queue.erase(queue.begin()); + return sel; + } + return {}; + } + // EO getFirstIfRoot + + // ########################################################################## + // Returns [complex], grouped into sub-lists such that no sub-list + // contains two adjacent [ComplexSelector]s. For example, + // `(A B > C D + E ~ > G)` is grouped into `[(A) (B > C) (D + E ~ > G)]`. + // ########################################################################## + std::vector> groupSelectors( + const std::vector& components) + { + bool lastWasCompound = false; + std::vector group; + std::vector> groups; + for (size_t i = 0; i < components.size(); i += 1) { + if (CompoundSelector* compound = components[i]->getCompound()) { + if (lastWasCompound) { + groups.push_back(group); + group.clear(); + } + group.push_back(compound); + lastWasCompound = true; + } + else if (SelectorCombinator* combinator = components[i]->getCombinator()) { + group.push_back(combinator); + lastWasCompound = false; + } + } + if (!group.empty()) { + groups.push_back(group); + } + return groups; + } + // EO groupSelectors + + // ########################################################################## + // Extracts leading [Combinator]s from [components1] and [components2] + // and merges them together into a single list of combinators. + // If there are no combinators to be merged, returns an empty list. + // If the combinators can't be merged, returns `null`. + // ########################################################################## + bool mergeInitialCombinators( + std::vector& components1, + std::vector& components2, + std::vector& result) + { + + std::vector combinators1; + while (!components1.empty() && Cast(components1.front())) { + SelectorCombinatorObj front = Cast(components1.front()); + components1.erase(components1.begin()); + combinators1.push_back(front); + } + + std::vector combinators2; + while (!components2.empty() && Cast(components2.front())) { + SelectorCombinatorObj front = Cast(components2.front()); + components2.erase(components2.begin()); + combinators2.push_back(front); + } + + // If neither sequence of combinators is a subsequence + // of the other, they cannot be merged successfully. + std::vector LCS = lcs(combinators1, combinators2); + + if (ListEquality(LCS, combinators1, PtrObjEqualityFn)) { + result = combinators2; + return true; + } + if (ListEquality(LCS, combinators2, PtrObjEqualityFn)) { + result = combinators1; + return true; + } + + return false; + + } + // EO mergeInitialCombinators + + // ########################################################################## + // Extracts trailing [Combinator]s, and the selectors to which they apply, + // from [components1] and [components2] and merges them together into a + // single list. If there are no combinators to be merged, returns an + // empty list. If the sequences can't be merged, returns `null`. + // ########################################################################## + bool mergeFinalCombinators( + std::vector& components1, + std::vector& components2, + std::vector>>& result) + { + + if (components1.empty() || !Cast(components1.back())) { + if (components2.empty() || !Cast(components2.back())) { + return true; + } + } + + std::vector combinators1; + while (!components1.empty() && Cast(components1.back())) { + SelectorCombinatorObj back = Cast(components1.back()); + components1.erase(components1.end() - 1); + combinators1.push_back(back); + } + + std::vector combinators2; + while (!components2.empty() && Cast(components2.back())) { + SelectorCombinatorObj back = Cast(components2.back()); + components2.erase(components2.end() - 1); + combinators2.push_back(back); + } + + // reverse now as we used push_back (faster than new alloc) + std::reverse(combinators1.begin(), combinators1.end()); + std::reverse(combinators2.begin(), combinators2.end()); + + if (combinators1.size() > 1 || combinators2.size() > 1) { + // If there are multiple combinators, something hacky's going on. If one + // is a supersequence of the other, use that, otherwise give up. + auto LCS = lcs(combinators1, combinators2); + if (ListEquality(LCS, combinators1, PtrObjEqualityFn)) { + result.push_back({ combinators2 }); + } + else if (ListEquality(LCS, combinators2, PtrObjEqualityFn)) { + result.push_back({ combinators1 }); + } + else { + return false; + } + return true; + } + + // This code looks complicated, but it's actually just a bunch of special + // cases for interactions between different combinators. + SelectorCombinatorObj combinator1, combinator2; + if (!combinators1.empty()) combinator1 = combinators1.back(); + if (!combinators2.empty()) combinator2 = combinators2.back(); + + if (!combinator1.isNull() && !combinator2.isNull()) { + + CompoundSelector* compound1 = Cast(components1.back()); + CompoundSelector* compound2 = Cast(components2.back()); + + components1.pop_back(); + components2.pop_back(); + + if (combinator1->isGeneralCombinator() && combinator2->isGeneralCombinator()) { + + if (compound1->isSuperselectorOf(compound2)) { + result.push_back({ { compound2, combinator2 } }); + } + else if (compound2->isSuperselectorOf(compound1)) { + result.push_back({ { compound1, combinator1 } }); + } + else { + std::vector> choices; + choices.push_back({ compound1, combinator1, compound2, combinator2 }); + choices.push_back({ compound2, combinator2, compound1, combinator1 }); + if (CompoundSelector* unified = compound1->unifyWith(compound2)) { + choices.push_back({ unified, combinator1 }); + } + result.push_back(choices); + } + } + else if ((combinator1->isGeneralCombinator() && combinator2->isAdjacentCombinator()) || + (combinator1->isAdjacentCombinator() && combinator2->isGeneralCombinator())) { + + CompoundSelector* followingSiblingSelector = combinator1->isGeneralCombinator() ? compound1 : compound2; + CompoundSelector* nextSiblingSelector = combinator1->isGeneralCombinator() ? compound2 : compound1; + SelectorCombinator* followingSiblingCombinator = combinator1->isGeneralCombinator() ? combinator1 : combinator2; + SelectorCombinator* nextSiblingCombinator = combinator1->isGeneralCombinator() ? combinator2 : combinator1; + + if (followingSiblingSelector->isSuperselectorOf(nextSiblingSelector)) { + result.push_back({ { nextSiblingSelector, nextSiblingCombinator } }); + } + else { + CompoundSelectorObj unified = compound1->unifyWith(compound2); + std::vector> items; + + if (!unified.isNull()) { + items.push_back({ + unified, nextSiblingCombinator + }); + } + + items.insert(items.begin(), { + followingSiblingSelector, + followingSiblingCombinator, + nextSiblingSelector, + nextSiblingCombinator, + }); + + result.push_back(items); + } + + } + else if (combinator1->isChildCombinator() && (combinator2->isAdjacentCombinator() || combinator2->isGeneralCombinator())) { + result.push_back({ { compound2, combinator2 } }); + components1.push_back(compound1); + components1.push_back(combinator1); + } + else if (combinator2->isChildCombinator() && (combinator1->isAdjacentCombinator() || combinator1->isGeneralCombinator())) { + result.push_back({ { compound1, combinator1 } }); + components2.push_back(compound2); + components2.push_back(combinator2); + } + else if (*combinator1 == *combinator2) { + CompoundSelectorObj unified = compound1->unifyWith(compound2); + if (unified.isNull()) return false; + result.push_back({ { unified, combinator1 } }); + } + else { + return false; + } + + return mergeFinalCombinators(components1, components2, result); + + } + else if (!combinator1.isNull()) { + + if (combinator1->isChildCombinator() && !components2.empty()) { + const CompoundSelector* back1 = Cast(components1.back()); + const CompoundSelector* back2 = Cast(components2.back()); + if (back1 && back2 && back2->isSuperselectorOf(back1)) { + components2.pop_back(); + } + } + + result.push_back({ { components1.back(), combinator1 } }); + + components1.pop_back(); + + return mergeFinalCombinators(components1, components2, result); + + } + + if (combinator2->isChildCombinator() && !components1.empty()) { + const CompoundSelector* back1 = Cast(components1.back()); + const CompoundSelector* back2 = Cast(components2.back()); + if (back1 && back2 && back1->isSuperselectorOf(back2)) { + components1.pop_back(); + } + } + + result.push_back({ { components2.back(), combinator2 } }); + + components2.pop_back(); + + return mergeFinalCombinators(components1, components2, result); + + } + // EO mergeFinalCombinators + + // ########################################################################## + // Expands "parenthesized selectors" in [complexes]. That is, if + // we have `.A .B {@extend .C}` and `.D .C {...}`, this conceptually + // expands into `.D .C, .D (.A .B)`, and this function translates + // `.D (.A .B)` into `.D .A .B, .A .D .B`. For thoroughness, `.A.D .B` + // would also be required, but including merged selectors results in + // exponential output for very little gain. The selector `.D (.A .B)` + // is represented as the list `[[.D], [.A, .B]]`. + // ########################################################################## + std::vector> weave( + const std::vector>& complexes) { + + std::vector> prefixes; + + prefixes.push_back(complexes.at(0)); + + for (size_t i = 1; i < complexes.size(); i += 1) { + + if (complexes[i].empty()) { + continue; + } + const std::vector& complex = complexes[i]; + SelectorComponent* target = complex.back(); + if (complex.size() == 1) { + for (auto& prefix : prefixes) { + prefix.push_back(target); + } + continue; + } + + std::vector parents(complex); + + parents.pop_back(); + + std::vector> newPrefixes; + for (std::vector prefix : prefixes) { + std::vector> + parentPrefixes = weaveParents(prefix, parents); + if (parentPrefixes.empty()) continue; + for (auto& parentPrefix : parentPrefixes) { + parentPrefix.push_back(target); + newPrefixes.push_back(parentPrefix); + } + } + prefixes = newPrefixes; + + } + return prefixes; + + } + // EO weave + + // ########################################################################## + // Interweaves [parents1] and [parents2] as parents of the same target + // selector. Returns all possible orderings of the selectors in the + // inputs (including using unification) that maintain the relative + // ordering of the input. For example, given `.foo .bar` and `.baz .bang`, + // this would return `.foo .bar .baz .bang`, `.foo .bar.baz .bang`, + // `.foo .baz .bar .bang`, `.foo .baz .bar.bang`, `.foo .baz .bang .bar`, + // and so on until `.baz .bang .foo .bar`. Semantically, for selectors A + // and B, this returns all selectors `AB_i` such that the union over all i + // of elements matched by `AB_i X` is identical to the intersection of all + // elements matched by `A X` and all elements matched by `B X`. Some `AB_i` + // are elided to reduce the size of the output. + // ########################################################################## + std::vector> weaveParents( + std::vector queue1, + std::vector queue2) + { + + std::vector leads; + std::vector>> trails; + if (!mergeInitialCombinators(queue1, queue2, leads)) return {}; + if (!mergeFinalCombinators(queue1, queue2, trails)) return {}; + // list comes out in reverse order for performance + std::reverse(trails.begin(), trails.end()); + + // Make sure there's at most one `:root` in the output. + // Note: does not yet do anything in libsass (no root selector) + CompoundSelectorObj root1 = getFirstIfRoot(queue1); + CompoundSelectorObj root2 = getFirstIfRoot(queue2); + + if (!root1.isNull() && !root2.isNull()) { + CompoundSelectorObj root = root1->unifyWith(root2); + if (root.isNull()) return {}; // null + queue1.insert(queue1.begin(), root); + queue2.insert(queue2.begin(), root); + } + else if (!root1.isNull()) { + queue2.insert(queue2.begin(), root1); + } + else if (!root2.isNull()) { + queue1.insert(queue1.begin(), root2); + } + + // group into sub-lists so no sub-list contains two adjacent ComplexSelectors. + std::vector> groups1 = groupSelectors(queue1); + std::vector> groups2 = groupSelectors(queue2); + + // The main array to store our choices that will be permutated + std::vector>> choices; + + // append initial combinators + choices.push_back({ leads }); + + std::vector> LCS = + lcs>(groups1, groups2, cmpGroups); + + for (auto group : LCS) { + + // Create junks from groups1 and groups2 + std::vector>> + chunks = getChunks>( + groups1, groups2, group, cmpChunkForParentSuperselector); + + // Create expanded array by flattening chunks2 inner + std::vector> + expanded = flattenInner(chunks); + + // Prepare data structures + choices.push_back(expanded); + choices.push_back({ group }); + groups1.erase(groups1.begin()); + groups2.erase(groups2.begin()); + + } + + // Create junks from groups1 and groups2 + std::vector>> + chunks = getChunks>( + groups1, groups2, {}, cmpChunkForEmptySequence); + + // Append chunks with inner arrays flattened + choices.emplace_back(std::move(flattenInner(chunks))); + + // append all trailing selectors to choices + std::move(std::begin(trails), std::end(trails), + std::inserter(choices, std::end(choices))); + + // move all non empty items to the front, then erase the trailing ones + choices.erase(std::remove_if(choices.begin(), choices.end(), checkForEmptyChild + >>), choices.end()); + + // permutate all possible paths through selectors + std::vector> + results = flattenInner(permutate(choices)); + + return results; + + } + // EO weaveParents + + // ########################################################################## + // ########################################################################## + +} diff --git a/src/ast_selectors.cpp b/src/ast_selectors.cpp index a8576489f..dd2113712 100644 --- a/src/ast_selectors.cpp +++ b/src/ast_selectors.cpp @@ -1,21 +1,10 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include "ast.hpp" -#include "context.hpp" -#include "node.hpp" -#include "eval.hpp" -#include "extend.hpp" -#include "emitter.hpp" -#include "color_maps.hpp" -#include "ast_fwd_decl.hpp" -#include "ast_selectors.hpp" -#include -#include -#include -#include -#include -#include -#include -#include +#include "permutate.hpp" +#include "util_string.hpp" namespace Sass { @@ -24,31 +13,14 @@ namespace Sass { Selector::Selector(ParserState pstate) : Expression(pstate), - has_line_feed_(false), - has_line_break_(false), - is_optional_(false), - media_block_(0), hash_(0) { concrete_type(SELECTOR); } Selector::Selector(const Selector* ptr) : Expression(ptr), - has_line_feed_(ptr->has_line_feed_), - has_line_break_(ptr->has_line_break_), - is_optional_(ptr->is_optional_), - media_block_(ptr->media_block_), hash_(ptr->hash_) { concrete_type(SELECTOR); } - void Selector::set_media_block(Media_Block* mb) - { - media_block(mb); - } - - bool Selector::has_parent_ref() const - { - return false; - } bool Selector::has_real_parent_ref() const { @@ -62,14 +34,12 @@ namespace Sass { : AST_Node(pstate), contents_(c), connect_parent_(true), - media_block_(NULL), hash_(0) { } Selector_Schema::Selector_Schema(const Selector_Schema* ptr) : AST_Node(ptr), contents_(ptr->contents_), connect_parent_(ptr->connect_parent_), - media_block_(ptr->media_block_), hash_(ptr->hash_) { } @@ -85,30 +55,21 @@ namespace Sass { return hash_; } - bool Selector_Schema::has_parent_ref() const - { - if (String_Schema_Obj schema = Cast(contents())) { - if (schema->empty()) return false; - const auto& first = *schema->at(0); - return typeid(first) == typeid(Parent_Selector); - } - return false; - } - bool Selector_Schema::has_real_parent_ref() const { - if (String_Schema_Obj schema = Cast(contents())) { - if (schema->empty()) return false; - const auto& first = *schema->at(0); - return typeid(first) == typeid(Parent_Reference); - } + // Note: disabled since it does not seem to do anything? + // if (String_Schema_Obj schema = Cast(contents())) { + // if (schema->empty()) return false; + // const auto first = schema->first(); + // return Cast(first); + // } return false; } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Simple_Selector::Simple_Selector(ParserState pstate, std::string n) + SimpleSelector::SimpleSelector(ParserState pstate, std::string n) : Selector(pstate), ns_(""), name_(n), has_ns_(false) { size_t pos = n.find('|'); @@ -119,127 +80,105 @@ namespace Sass { name_ = n.substr(pos + 1); } } - Simple_Selector::Simple_Selector(const Simple_Selector* ptr) + SimpleSelector::SimpleSelector(const SimpleSelector* ptr) : Selector(ptr), ns_(ptr->ns_), name_(ptr->name_), has_ns_(ptr->has_ns_) { } - std::string Simple_Selector::ns_name() const + std::string SimpleSelector::ns_name() const { - std::string name(""); - if (has_ns_) - name += ns_ + "|"; - return name + name_; + if (!has_ns_) return name_; + else return ns_ + "|" + name_; } - size_t Simple_Selector::hash() const + size_t SimpleSelector::hash() const { if (hash_ == 0) { - hash_combine(hash_, std::hash()(SELECTOR)); - hash_combine(hash_, std::hash()(simple_type())); - if (!name_.empty()) hash_combine(hash_, std::hash()(name())); - if (has_ns_) hash_combine(hash_, std::hash()(ns())); + hash_combine(hash_, name()); + hash_combine(hash_, (int)SELECTOR); + hash_combine(hash_, (int)simple_type()); + if (has_ns_) hash_combine(hash_, ns()); } return hash_; } - bool Simple_Selector::empty() const { + bool SimpleSelector::empty() const { return ns().empty() && name().empty(); } // namespace compare functions - bool Simple_Selector::is_ns_eq(const Simple_Selector& r) const + bool SimpleSelector::is_ns_eq(const SimpleSelector& r) const { return has_ns_ == r.has_ns_ && ns_ == r.ns_; } // namespace query functions - bool Simple_Selector::is_universal_ns() const + bool SimpleSelector::is_universal_ns() const { return has_ns_ && ns_ == "*"; } - bool Simple_Selector::is_empty_ns() const + bool SimpleSelector::is_empty_ns() const { return !has_ns_ || ns_ == ""; } - bool Simple_Selector::has_empty_ns() const + bool SimpleSelector::has_empty_ns() const { return has_ns_ && ns_ == ""; } - bool Simple_Selector::has_qualified_ns() const + bool SimpleSelector::has_qualified_ns() const { return has_ns_ && ns_ != "" && ns_ != "*"; } // name query functions - bool Simple_Selector::is_universal() const + bool SimpleSelector::is_universal() const { return name_ == "*"; } - bool Simple_Selector::has_placeholder() + bool SimpleSelector::has_placeholder() { return false; } - bool Simple_Selector::has_parent_ref() const - { - return false; - }; - - bool Simple_Selector::has_real_parent_ref() const + bool SimpleSelector::has_real_parent_ref() const { return false; }; - bool Simple_Selector::is_pseudo_element() const + bool SimpleSelector::is_pseudo_element() const { return false; } - bool Simple_Selector::is_superselector_of(const Compound_Selector* sub) const + CompoundSelectorObj SimpleSelector::wrapInCompound() { - return false; + CompoundSelectorObj selector = + SASS_MEMORY_NEW(CompoundSelector, pstate()); + selector->append(this); + return selector; } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Parent_Selector::Parent_Selector(ParserState pstate, bool r) - : Simple_Selector(pstate, "&"), real_(r) - { simple_type(PARENT_SEL); } - Parent_Selector::Parent_Selector(const Parent_Selector* ptr) - : Simple_Selector(ptr), real_(ptr->real_) - { simple_type(PARENT_SEL); } - - bool Parent_Selector::has_parent_ref() const - { - return true; - }; - - bool Parent_Selector::has_real_parent_ref() const + ComplexSelectorObj SimpleSelector::wrapInComplex() { - return real(); - }; - - unsigned long Parent_Selector::specificity() const - { - return 0; + ComplexSelectorObj selector = + SASS_MEMORY_NEW(ComplexSelector, pstate()); + selector->append(wrapInCompound()); + return selector; } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// Placeholder_Selector::Placeholder_Selector(ParserState pstate, std::string n) - : Simple_Selector(pstate, n) + : SimpleSelector(pstate, n) { simple_type(PLACEHOLDER_SEL); } Placeholder_Selector::Placeholder_Selector(const Placeholder_Selector* ptr) - : Simple_Selector(ptr) + : SimpleSelector(ptr) { simple_type(PLACEHOLDER_SEL); } unsigned long Placeholder_Selector::specificity() const { @@ -253,10 +192,10 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////// Type_Selector::Type_Selector(ParserState pstate, std::string n) - : Simple_Selector(pstate, n) + : SimpleSelector(pstate, n) { simple_type(TYPE_SEL); } Type_Selector::Type_Selector(const Type_Selector* ptr) - : Simple_Selector(ptr) + : SimpleSelector(ptr) { simple_type(TYPE_SEL); } unsigned long Type_Selector::specificity() const @@ -269,10 +208,10 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////// Class_Selector::Class_Selector(ParserState pstate, std::string n) - : Simple_Selector(pstate, n) + : SimpleSelector(pstate, n) { simple_type(CLASS_SEL); } Class_Selector::Class_Selector(const Class_Selector* ptr) - : Simple_Selector(ptr) + : SimpleSelector(ptr) { simple_type(CLASS_SEL); } unsigned long Class_Selector::specificity() const @@ -284,10 +223,10 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////// Id_Selector::Id_Selector(ParserState pstate, std::string n) - : Simple_Selector(pstate, n) + : SimpleSelector(pstate, n) { simple_type(ID_SEL); } Id_Selector::Id_Selector(const Id_Selector* ptr) - : Simple_Selector(ptr) + : SimpleSelector(ptr) { simple_type(ID_SEL); } unsigned long Id_Selector::specificity() const @@ -299,10 +238,10 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////// Attribute_Selector::Attribute_Selector(ParserState pstate, std::string n, std::string m, String_Obj v, char o) - : Simple_Selector(pstate, n), matcher_(m), value_(v), modifier_(o) + : SimpleSelector(pstate, n), matcher_(m), value_(v), modifier_(o) { simple_type(ATTRIBUTE_SEL); } Attribute_Selector::Attribute_Selector(const Attribute_Selector* ptr) - : Simple_Selector(ptr), + : SimpleSelector(ptr), matcher_(ptr->matcher_), value_(ptr->value_), modifier_(ptr->modifier_) @@ -311,7 +250,7 @@ namespace Sass { size_t Attribute_Selector::hash() const { if (hash_ == 0) { - hash_combine(hash_, Simple_Selector::hash()); + hash_combine(hash_, SimpleSelector::hash()); hash_combine(hash_, std::hash()(matcher())); if (value_) hash_combine(hash_, value_->hash()); } @@ -326,11 +265,21 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Pseudo_Selector::Pseudo_Selector(ParserState pstate, std::string n, String_Obj expr) - : Simple_Selector(pstate, n), expression_(expr) + Pseudo_Selector::Pseudo_Selector(ParserState pstate, std::string name, bool element) + : SimpleSelector(pstate, name), + normalized_(Util::unvendor(name)), + argument_({}), + selector_({}), + isSyntacticClass_(!element), + isClass_(!element && !isFakePseudoElement(normalized_)) { simple_type(PSEUDO_SEL); } Pseudo_Selector::Pseudo_Selector(const Pseudo_Selector* ptr) - : Simple_Selector(ptr), expression_(ptr->expression_) + : SimpleSelector(ptr), + normalized_(ptr->normalized()), + argument_(ptr->argument()), + selector_(ptr->selector()), + isSyntacticClass_(ptr->isSyntacticClass()), + isClass_(ptr->isClass()) { simple_type(PSEUDO_SEL); } // A pseudo-element is made of two colons (::) followed by the name. @@ -343,15 +292,15 @@ namespace Sass { // introduced in this specification. bool Pseudo_Selector::is_pseudo_element() const { - return (name_[0] == ':' && name_[1] == ':') - || is_pseudo_class_element(name_); + return isElement(); } size_t Pseudo_Selector::hash() const { if (hash_ == 0) { - hash_combine(hash_, Simple_Selector::hash()); - if (expression_) hash_combine(hash_, expression_->hash()); + hash_combine(hash_, SimpleSelector::hash()); + if (selector_) hash_combine(hash_, selector_->hash()); + if (argument_) hash_combine(hash_, argument_->hash()); } return hash_; } @@ -363,1097 +312,704 @@ namespace Sass { return Constants::Specificity_Pseudo; } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Wrapped_Selector::Wrapped_Selector(ParserState pstate, std::string n, Selector_List_Obj sel) - : Simple_Selector(pstate, n), selector_(sel) - { simple_type(WRAPPED_SEL); } - Wrapped_Selector::Wrapped_Selector(const Wrapped_Selector* ptr) - : Simple_Selector(ptr), selector_(ptr->selector_) - { simple_type(WRAPPED_SEL); } - - bool Wrapped_Selector::is_superselector_of(const Wrapped_Selector* sub) const + Pseudo_Selector_Obj Pseudo_Selector::withSelector(SelectorListObj selector) { - if (this->name() != sub->name()) return false; - if (this->name() == ":current") return false; - if (Selector_List_Obj rhs_list = Cast(sub->selector())) { - if (Selector_List_Obj lhs_list = Cast(selector())) { - return lhs_list->is_superselector_of(rhs_list); - } - } - coreError("is_superselector expected a Selector_List", sub->pstate()); - return false; + Pseudo_Selector_Obj pseudo = SASS_MEMORY_COPY(this); + pseudo->selector(selector); + return pseudo; } - // Selectors inside the negation pseudo-class are counted like any - // other, but the negation itself does not count as a pseudo-class. - - void Wrapped_Selector::cloneChildren() + bool Pseudo_Selector::empty() const { - selector(SASS_MEMORY_CLONE(selector())); + // Only considered empty if selector is + // available but has no items in it. + return selector() && selector()->empty(); } - size_t Wrapped_Selector::hash() const + void Pseudo_Selector::cloneChildren() { - if (hash_ == 0) { - hash_combine(hash_, Simple_Selector::hash()); - if (selector_) hash_combine(hash_, selector_->hash()); - } - return hash_; - } - - bool Wrapped_Selector::has_parent_ref() const { - if (!selector()) return false; - return selector()->has_parent_ref(); + if (selector().isNull()) selector({}); + else selector(SASS_MEMORY_CLONE(selector())); } - bool Wrapped_Selector::has_real_parent_ref() const { + bool Pseudo_Selector::has_real_parent_ref() const { if (!selector()) return false; return selector()->has_real_parent_ref(); } - unsigned long Wrapped_Selector::specificity() const - { - return selector_ ? selector_->specificity() : 0; - } - - bool Wrapped_Selector::find ( bool (*f)(AST_Node_Obj) ) - { - // check children first - if (selector_) { - if (selector_->find(f)) return true; - } - // execute last - return f(this); - } - ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Compound_Selector::Compound_Selector(ParserState pstate, size_t s) + SelectorList::SelectorList(ParserState pstate, size_t s) : Selector(pstate), - Vectorized(s), - extended_(false), - has_parent_reference_(false) + Vectorized(s), + is_optional_(false) { } - - Compound_Selector::Compound_Selector(const Compound_Selector* ptr) - : Selector(ptr), - Vectorized(*ptr), - extended_(ptr->extended_), - has_parent_reference_(ptr->has_parent_reference_) + SelectorList::SelectorList(const SelectorList* ptr) + : Selector(ptr), + Vectorized(*ptr), + is_optional_(ptr->is_optional_) { } - bool Compound_Selector::contains_placeholder() { - for (size_t i = 0, L = length(); i < L; ++i) { - if ((*this)[i]->has_placeholder()) return true; - } - return false; - }; - - void Compound_Selector::cloneChildren() - { - for (size_t i = 0, l = length(); i < l; i++) { - at(i) = SASS_MEMORY_CLONE(at(i)); - } - } - - bool Compound_Selector::find ( bool (*f)(AST_Node_Obj) ) + size_t SelectorList::hash() const { - // check children first - for (Simple_Selector_Obj sel : elements()) { - if (sel->find(f)) return true; - } - // execute last - return f(this); - } - - bool Compound_Selector::has_parent_ref() const - { - for (Simple_Selector_Obj s : *this) { - if (s && s->has_parent_ref()) return true; + if (Selector::hash_ == 0) { + hash_combine(Selector::hash_, Vectorized::hash()); } - return false; + return Selector::hash_; } - bool Compound_Selector::has_real_parent_ref() const + bool SelectorList::has_real_parent_ref() const { - for (Simple_Selector_Obj s : *this) { + for (ComplexSelectorObj s : elements()) { if (s && s->has_real_parent_ref()) return true; } return false; } - bool Compound_Selector::is_superselector_of(const Selector_List* rhs, std::string wrapped) const + void SelectorList::cloneChildren() { - for (Complex_Selector_Obj item : rhs->elements()) { - if (is_superselector_of(item, wrapped)) return true; + for (size_t i = 0, l = length(); i < l; i++) { + at(i) = SASS_MEMORY_CLONE(at(i)); } - return false; } - bool Compound_Selector::is_superselector_of(const Complex_Selector* rhs, std::string wrapped) const + unsigned long SelectorList::specificity() const { - if (rhs->head()) return is_superselector_of(rhs->head(), wrapped); - return false; + return 0; } - bool Compound_Selector::is_superselector_of(const Compound_Selector* rhs, std::string wrapping) const + bool SelectorList::isInvisible() const { - // Check if pseudo-elements are the same between the selectors - { - std::array, 2> pseudosets; - std::array compounds = {{this, rhs}}; - for (int i = 0; i < 2; ++i) { - for (const Simple_Selector_Obj& el : compounds[i]->elements()) { - if (el->is_pseudo_element()) { - std::string pseudo(el->to_string()); - // strip off colons to ensure :after matches ::after since ruby sass is forgiving - pseudosets[i].insert(pseudo.substr(pseudo.find_first_not_of(":"))); - } - } - } - if (pseudosets[0] != pseudosets[1]) return false; - } - - { - const Simple_Selector* lbase = this->base(); - const Simple_Selector* rbase = rhs->base(); - if (lbase && rbase) { - return *lbase == *rbase && - contains_all(std::unordered_set(rhs->begin(), rhs->end()), - std::unordered_set(this->begin(), this->end())); - } + for (size_t i = 0; i < length(); i += 1) { + if (!get(i)->isInvisible()) return false; } - - std::unordered_set lset; - for (size_t i = 0, iL = length(); i < iL; ++i) - { - const Selector* wlhs = (*this)[i].ptr(); - // very special case for wrapped matches selector - if (const Wrapped_Selector* wrapped = Cast(wlhs)) { - if (wrapped->name() == ":not") { - if (Selector_List_Obj not_list = Cast(wrapped->selector())) { - if (not_list->is_superselector_of(rhs, wrapped->name())) return false; - } else { - throw std::runtime_error("wrapped not selector is not a list"); - } - } - if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") { - wlhs = wrapped->selector(); - if (Selector_List_Obj list = Cast(wrapped->selector())) { - if (const Compound_Selector* comp = Cast(rhs)) { - if (!wrapping.empty() && wrapping != wrapped->name()) return false; - if (wrapping.empty() || wrapping != wrapped->name()) {; - if (list->is_superselector_of(comp, wrapped->name())) return true; - } - } - } - } - Simple_Selector* rhs_sel = nullptr; - if (rhs->elements().size() > i) rhs_sel = (*rhs)[i]; - if (Wrapped_Selector* wrapped_r = Cast(rhs_sel)) { - if (wrapped->name() == wrapped_r->name()) { - if (wrapped->is_superselector_of(wrapped_r)) { - continue; - }} - } - } - lset.insert(wlhs); - } - - if (lset.empty()) return true; - - std::unordered_set rset; - for (size_t n = 0, nL = rhs->length(); n < nL; ++n) - { - Selector_Obj r = (*rhs)[n]; - if (Wrapped_Selector_Obj wrapped = Cast(r)) { - if (wrapped->name() == ":not") { - if (Selector_List_Obj ls = Cast(wrapped->selector())) { - ls->remove_parent_selectors(); // unverified - if (is_superselector_of(ls, wrapped->name())) return false; - } - } - if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") { - if (!wrapping.empty()) { - if (wrapping != wrapped->name()) return false; - } - if (Selector_List_Obj ls = Cast(wrapped->selector())) { - ls->remove_parent_selectors(); // unverified - return (is_superselector_of(ls, wrapped->name())); - } - } - } - rset.insert(r); - } - - return contains_all(rset, lset); + return true; } - bool Compound_Selector::is_universal() const - { - return length() == 1 && (*this)[0]->is_universal(); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - // create complex selector (ancestor of) from compound selector - Complex_Selector_Obj Compound_Selector::to_complex() + ComplexSelector::ComplexSelector(ParserState pstate) + : Selector(pstate), + Vectorized(), + chroots_(false), + hasPreLineFeed_(false) { - // create an intermediate complex selector - return SASS_MEMORY_NEW(Complex_Selector, - pstate(), - Complex_Selector::ANCESTOR_OF, - this, - {}); } - - Simple_Selector* Compound_Selector::base() const { - if (length() == 0) return 0; - // ToDo: why is this needed? - if (Cast((*this)[0])) - return (*this)[0]; - return 0; + ComplexSelector::ComplexSelector(const ComplexSelector* ptr) + : Selector(ptr), + Vectorized(ptr->elements()), + chroots_(ptr->chroots()), + hasPreLineFeed_(ptr->hasPreLineFeed()) + { } - size_t Compound_Selector::hash() const + void ComplexSelector::cloneChildren() { - if (Selector::hash_ == 0) { - hash_combine(Selector::hash_, std::hash()(SELECTOR)); - if (length()) hash_combine(Selector::hash_, Vectorized::hash()); + for (size_t i = 0, l = length(); i < l; i++) { + at(i) = SASS_MEMORY_CLONE(at(i)); } - return Selector::hash_; } - unsigned long Compound_Selector::specificity() const + unsigned long ComplexSelector::specificity() const { int sum = 0; - for (size_t i = 0, L = length(); i < L; ++i) - { sum += (*this)[i]->specificity(); } + for (auto component : elements()) { + sum += component->specificity(); + } return sum; } - bool Compound_Selector::has_placeholder() + bool ComplexSelector::isInvisible() const { - if (length() == 0) return false; - if (Simple_Selector_Obj ss = elements().front()) { - if (ss->has_placeholder()) return true; + if (length() == 0) return true; + for (size_t i = 0; i < length(); i += 1) { + if (CompoundSelectorObj compound = get(i)->getCompound()) { + if (compound->isInvisible()) return true; + } } return false; } - bool Compound_Selector::is_empty_reference() + SelectorListObj ComplexSelector::wrapInList() { - return length() == 1 && - Cast((*this)[0]); + SelectorListObj selector = + SASS_MEMORY_NEW(SelectorList, pstate()); + selector->append(this); + return selector; } - void Compound_Selector::append(Simple_Selector_Obj element) + size_t ComplexSelector::hash() const { - Vectorized::append(element); - pstate_.offset += element->pstate().offset; + if (Selector::hash_ == 0) { + hash_combine(Selector::hash_, Vectorized::hash()); + // ToDo: this breaks some extend lookup + // hash_combine(Selector::hash_, chroots_); + } + return Selector::hash_; } - Compound_Selector* Compound_Selector::minus(Compound_Selector* rhs) - { - Compound_Selector* result = SASS_MEMORY_NEW(Compound_Selector, pstate()); - // result->has_parent_reference(has_parent_reference()); - - // not very efficient because it needs to preserve order - for (size_t i = 0, L = length(); i < L; ++i) - { - bool found = false; - for (size_t j = 0, M = rhs->length(); j < M; ++j) - { - if (*get(i) == *rhs->get(j)) - { - found = true; - break; - } - } - if (!found) result->append(get(i)); + bool ComplexSelector::has_placeholder() const { + for (size_t i = 0, L = length(); i < L; ++i) { + if (get(i)->has_placeholder()) return true; } - - return result; + return false; } - void Compound_Selector::mergeSources(ComplexSelectorSet& sources) + bool ComplexSelector::has_real_parent_ref() const { - for (ComplexSelectorSet::iterator iterator = sources.begin(), endIterator = sources.end(); iterator != endIterator; ++iterator) { - this->sources_.insert(SASS_MEMORY_CLONE(*iterator)); + for (auto item : elements()) { + if (item->has_real_parent_ref()) return true; } + return false; } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Complex_Selector::Complex_Selector(ParserState pstate, - Combinator c, - Compound_Selector_Obj h, - Complex_Selector_Obj t, - String_Obj r) + SelectorComponent::SelectorComponent(ParserState pstate, bool postLineBreak) : Selector(pstate), - combinator_(c), - head_(h), tail_(t), - reference_(r) - {} - Complex_Selector::Complex_Selector(const Complex_Selector* ptr) - : Selector(ptr), - combinator_(ptr->combinator_), - head_(ptr->head_), tail_(ptr->tail_), - reference_(ptr->reference_) - {} - - bool Complex_Selector::empty() const { - return (!tail() || tail()->empty()) - && (!head() || head()->empty()) - && combinator_ == ANCESTOR_OF; - } - - Complex_Selector_Obj Complex_Selector::skip_empty_reference() - { - if ((!head_ || !head_->length() || head_->is_empty_reference()) && - combinator() == Combinator::ANCESTOR_OF) - { - if (!tail_) return {}; - tail_->has_line_feed_ = this->has_line_feed_; - // tail_->has_line_break_ = this->has_line_break_; - return tail_->skip_empty_reference(); - } - return this; + hasPostLineBreak_(postLineBreak) + { } - bool Complex_Selector::is_empty_ancestor() const + SelectorComponent::SelectorComponent(const SelectorComponent* ptr) + : Selector(ptr), + hasPostLineBreak_(ptr->hasPostLineBreak()) + { } + + void SelectorComponent::cloneChildren() { - return (!head() || head()->length() == 0) && - combinator() == Combinator::ANCESTOR_OF; } - size_t Complex_Selector::hash() const + unsigned long SelectorComponent::specificity() const { - if (hash_ == 0) { - if (head_) { - hash_combine(hash_, head_->hash()); - } else { - hash_combine(hash_, std::hash()(SELECTOR)); - } - if (tail_) hash_combine(hash_, tail_->hash()); - if (combinator_ != ANCESTOR_OF) hash_combine(hash_, std::hash()(combinator_)); - } - return hash_; + return 0; } - unsigned long Complex_Selector::specificity() const + // Wrap the compound selector with a complex selector + ComplexSelector* SelectorComponent::wrapInComplex() { - int sum = 0; - if (head()) sum += head()->specificity(); - if (tail()) sum += tail()->specificity(); - return sum; + auto complex = SASS_MEMORY_NEW(ComplexSelector, pstate()); + complex->append(this); + return complex; } - void Complex_Selector::set_media_block(Media_Block* mb) { - media_block(mb); - if (tail_) tail_->set_media_block(mb); - if (head_) head_->set_media_block(mb); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SelectorCombinator::SelectorCombinator(ParserState pstate, SelectorCombinator::Combinator combinator, bool postLineBreak) + : SelectorComponent(pstate, postLineBreak), + combinator_(combinator) + { } + SelectorCombinator::SelectorCombinator(const SelectorCombinator* ptr) + : SelectorComponent(ptr->pstate(), false), + combinator_(ptr->combinator()) + { } - bool Complex_Selector::has_placeholder() { - if (head_ && head_->has_placeholder()) return true; - if (tail_ && tail_->has_placeholder()) return true; - return false; + void SelectorCombinator::cloneChildren() + { } - const ComplexSelectorSet Complex_Selector::sources() + unsigned long SelectorCombinator::specificity() const { - //s = Set.new - //seq.map {|sseq_or_op| s.merge sseq_or_op.sources if sseq_or_op.is_a?(SimpleSequence)} - //s + return 0; + } - ComplexSelectorSet srcs; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - Compound_Selector_Obj pHead = head(); - Complex_Selector_Obj pTail = tail(); + CompoundSelector::CompoundSelector(ParserState pstate, bool postLineBreak) + : SelectorComponent(pstate, postLineBreak), + Vectorized(), + hasRealParent_(false), + extended_(false) + { + } + CompoundSelector::CompoundSelector(const CompoundSelector* ptr) + : SelectorComponent(ptr), + Vectorized(*ptr), + hasRealParent_(ptr->hasRealParent()), + extended_(ptr->extended()) + { } - if (pHead) { - const ComplexSelectorSet& headSources = pHead->sources(); - srcs.insert(headSources.begin(), headSources.end()); + size_t CompoundSelector::hash() const + { + if (Selector::hash_ == 0) { + hash_combine(Selector::hash_, Vectorized::hash()); + hash_combine(Selector::hash_, hasRealParent_); } + return Selector::hash_; + } - if (pTail) { - const ComplexSelectorSet& tailSources = pTail->sources(); - srcs.insert(tailSources.begin(), tailSources.end()); + bool CompoundSelector::has_real_parent_ref() const + { + if (hasRealParent()) return true; + // ToDo: dart sass has another check? + // if (Cast(front)) { + // if (front->ns() != "") return false; + // } + for (const SimpleSelector* s : elements()) { + if (s && s->has_real_parent_ref()) return true; } - - return srcs; + return false; } - void Complex_Selector::addSources(ComplexSelectorSet& sources) + bool CompoundSelector::has_placeholder() const { - // members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m} - Complex_Selector* pIter = this; - while (pIter) { - Compound_Selector* pHead = pIter->head(); - - if (pHead) { - pHead->mergeSources(sources); - } - - pIter = pIter->tail(); + if (length() == 0) return false; + for (SimpleSelectorObj ss : elements()) { + if (ss->has_placeholder()) return true; } + return false; } - void Complex_Selector::clearSources() + void CompoundSelector::cloneChildren() { - Complex_Selector* pIter = this; - while (pIter) { - Compound_Selector* pHead = pIter->head(); - - if (pHead) { - pHead->clearSources(); - } - - pIter = pIter->tail(); + for (size_t i = 0, l = length(); i < l; i++) { + at(i) = SASS_MEMORY_CLONE(at(i)); } } - bool Complex_Selector::find ( bool (*f)(AST_Node_Obj) ) + unsigned long CompoundSelector::specificity() const { - // check children first - if (head_ && head_->find(f)) return true; - if (tail_ && tail_->find(f)) return true; - // execute last - return f(this); + int sum = 0; + for (size_t i = 0, L = length(); i < L; ++i) + { sum += get(i)->specificity(); } + return sum; } - bool Complex_Selector::has_parent_ref() const + bool CompoundSelector::isInvisible() const { - return (head() && head()->has_parent_ref()) || - (tail() && tail()->has_parent_ref()); + for (size_t i = 0; i < length(); i += 1) { + if (!get(i)->isInvisible()) return false; + } + return true; } - bool Complex_Selector::has_real_parent_ref() const + bool CompoundSelector::isSuperselectorOf(const CompoundSelector* sub, std::string wrapped) const { - return (head() && head()->has_real_parent_ref()) || - (tail() && tail()->has_real_parent_ref()); + CompoundSelector* rhs2 = const_cast(sub); + CompoundSelector* lhs2 = const_cast(this); + return compoundIsSuperselector(lhs2, rhs2, {}); } - bool Complex_Selector::is_superselector_of(const Compound_Selector* rhs, std::string wrapping) const + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + MediaRule::MediaRule(ParserState pstate, Block_Obj block) : + Has_Block(pstate, block), + schema_({}) { - return last()->head() && last()->head()->is_superselector_of(rhs, wrapping); + statement_type(MEDIA); } - bool Complex_Selector::is_superselector_of(const Complex_Selector* rhs, std::string wrapping) const + MediaRule::MediaRule(const MediaRule* ptr) : + Has_Block(ptr), + schema_(ptr->schema_) { - const Complex_Selector* lhs = this; - // check for selectors with leading or trailing combinators - if (!lhs->head() || !rhs->head()) - { return false; } - const Complex_Selector* l_innermost = lhs->last(); - if (l_innermost->combinator() != Complex_Selector::ANCESTOR_OF) - { return false; } - const Complex_Selector* r_innermost = rhs->last(); - if (r_innermost->combinator() != Complex_Selector::ANCESTOR_OF) - { return false; } - // more complex (i.e., longer) selectors are always more specific - size_t l_len = lhs->length(), r_len = rhs->length(); - if (l_len > r_len) - { return false; } - - if (l_len == 1) - { return lhs->head()->is_superselector_of(rhs->last()->head(), wrapping); } + statement_type(MEDIA); + } - // we have to look one tail deeper, since we cary the - // combinator around for it (which is important here) - if (rhs->tail() && lhs->tail() && combinator() != Complex_Selector::ANCESTOR_OF) { - Complex_Selector_Obj lhs_tail = lhs->tail(); - Complex_Selector_Obj rhs_tail = rhs->tail(); - if (lhs_tail->combinator() != rhs_tail->combinator()) return false; - if (lhs_tail->head() && !rhs_tail->head()) return false; - if (!lhs_tail->head() && rhs_tail->head()) return false; - if (lhs_tail->head() && rhs_tail->head()) { - if (!lhs_tail->head()->is_superselector_of(rhs_tail->head())) return false; - } - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - bool found = false; - const Complex_Selector* marker = rhs; - for (size_t i = 0, L = rhs->length(); i < L; ++i) { - if (i == L-1) - { return false; } - if (lhs->head() && marker->head() && lhs->head()->is_superselector_of(marker->head(), wrapping)) - { found = true; break; } - marker = marker->tail(); - } - if (!found) - { return false; } - - /* - Hmm, I hope I have the logic right: - - if lhs has a combinator: - if !(marker has a combinator) return false - if !(lhs.combinator == '~' ? marker.combinator != '>' : lhs.combinator == marker.combinator) return false - return lhs.tail-without-innermost.is_superselector_of(marker.tail-without-innermost) - else if marker has a combinator: - if !(marker.combinator == ">") return false - return lhs.tail.is_superselector_of(marker.tail) - else - return lhs.tail.is_superselector_of(marker.tail) - */ - if (lhs->combinator() != Complex_Selector::ANCESTOR_OF) - { - if (marker->combinator() == Complex_Selector::ANCESTOR_OF) - { return false; } - if (!(lhs->combinator() == Complex_Selector::PRECEDES ? marker->combinator() != Complex_Selector::PARENT_OF : lhs->combinator() == marker->combinator())) - { return false; } - return lhs->tail()->is_superselector_of(marker->tail()); - } - else if (marker->combinator() != Complex_Selector::ANCESTOR_OF) - { - if (marker->combinator() != Complex_Selector::PARENT_OF) - { return false; } - return lhs->tail()->is_superselector_of(marker->tail()); - } - return lhs->tail()->is_superselector_of(marker->tail()); - } - - size_t Complex_Selector::length() const - { - // TODO: make this iterative - if (!tail()) return 1; - return 1 + tail()->length(); - } - - // append another complex selector at the end - // check if we need to append some headers - // then we need to check for the combinator - // only then we can safely set the new tail - void Complex_Selector::append(Complex_Selector_Obj ss, Backtraces& traces) - { - - Complex_Selector_Obj t = ss->tail(); - Combinator c = ss->combinator(); - String_Obj r = ss->reference(); - Compound_Selector_Obj h = ss->head(); - - if (ss->has_line_feed()) has_line_feed(true); - if (ss->has_line_break()) has_line_break(true); - - // append old headers - if (h && h->length()) { - if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) { - traces.push_back(Backtrace(pstate())); - throw Exception::InvalidParent(this, traces, ss); - } else if (last()->head_ && last()->head_->length()) { - Compound_Selector_Obj rh = last()->head(); - size_t i; - size_t L = h->length(); - if (Cast(h->first())) { - if (Class_Selector* cs = Cast(rh->last())) { - Class_Selector* sqs = SASS_MEMORY_COPY(cs); - sqs->name(sqs->name() + (*h)[0]->name()); - sqs->pstate((*h)[0]->pstate()); - (*rh)[rh->length()-1] = sqs; - rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append((*h)[i]); - } else if (Id_Selector* is = Cast(rh->last())) { - Id_Selector* sqs = SASS_MEMORY_COPY(is); - sqs->name(sqs->name() + (*h)[0]->name()); - sqs->pstate((*h)[0]->pstate()); - (*rh)[rh->length()-1] = sqs; - rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append((*h)[i]); - } else if (Type_Selector* ts = Cast(rh->last())) { - Type_Selector* tss = SASS_MEMORY_COPY(ts); - tss->name(tss->name() + (*h)[0]->name()); - tss->pstate((*h)[0]->pstate()); - (*rh)[rh->length()-1] = tss; - rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append((*h)[i]); - } else if (Placeholder_Selector* ps = Cast(rh->last())) { - Placeholder_Selector* pss = SASS_MEMORY_COPY(ps); - pss->name(pss->name() + (*h)[0]->name()); - pss->pstate((*h)[0]->pstate()); - (*rh)[rh->length()-1] = pss; - rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append((*h)[i]); - } else { - last()->head_->concat(h); - } - } else { - last()->head_->concat(h); - } - } else if (last()->head_) { - last()->head_->concat(h); - } - } else { - // std::cerr << "has no or empty head\n"; - } + CssMediaRule::CssMediaRule(ParserState pstate, Block_Obj block) : + Has_Block(pstate, block), + Vectorized() + { + statement_type(MEDIA); + } - Complex_Selector* last = mutable_last(); - if (last) { - if (last->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) { - Complex_Selector* inter = SASS_MEMORY_NEW(Complex_Selector, pstate()); - inter->reference(r); - inter->combinator(c); - inter->tail(t); - last->tail(inter); - } else { - if (last->combinator() == ANCESTOR_OF) { - last->combinator(c); - last->reference(r); - } - last->tail(t); - } - } + CssMediaRule::CssMediaRule(const CssMediaRule* ptr) : + Has_Block(ptr), + Vectorized(*ptr) + { + statement_type(MEDIA); + } + CssMediaQuery::CssMediaQuery(ParserState pstate) : + AST_Node(pstate), + modifier_(""), + type_(""), + features_() + { } - Selector_List* Complex_Selector::resolve_parent_refs(SelectorStack& pstack, Backtraces& traces, bool implicit_parent) + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool CssMediaQuery::operator==(const CssMediaQuery& rhs) const { - Complex_Selector_Obj tail = this->tail(); - Compound_Selector_Obj head = this->head(); - Selector_List* parents = pstack.back(); + return type_ == rhs.type_ + && modifier_ == rhs.modifier_ + && features_ == rhs.features_; + } - if (!this->has_real_parent_ref() && !implicit_parent) { - Selector_List* retval = SASS_MEMORY_NEW(Selector_List, pstate(), 1); - retval->append(this); - return retval; - } + // Implemented after dart-sass (maybe move to other class?) + CssMediaQuery_Obj CssMediaQuery::merge(CssMediaQuery_Obj& other) + { - // first resolve_parent_refs the tail (which may return an expanded list) - Selector_List_Obj tails = tail ? tail->resolve_parent_refs(pstack, traces, implicit_parent) : 0; + std::string ourType(this->type()); + std::string theirType(other->type()); + std::string ourModifier(this->modifier()); + std::string theirModifier(other->modifier()); - if (head && head->length() > 0) { + std::transform(ourType.begin(), ourType.end(), ourType.begin(), ::tolower); + std::transform(theirType.begin(), theirType.end(), theirType.begin(), ::tolower); + std::transform(ourModifier.begin(), ourModifier.end(), ourModifier.begin(), ::tolower); + std::transform(theirModifier.begin(), theirModifier.end(), theirModifier.begin(), ::tolower); - Selector_List_Obj retval; - // we have a parent selector in a simple compound list - // mix parent complex selector into the compound list - if (Cast((*head)[0])) { - retval = SASS_MEMORY_NEW(Selector_List, pstate()); + std::string type; + std::string modifier; + std::vector features; - // it turns out that real parent references reach - // across @at-root rules, which comes unexpected - if (parents == NULL && head->has_real_parent_ref()) { - int i = pstack.size() - 1; - while (!parents && i > -1) { - parents = pstack.at(i--); - } - } + if (ourType.empty() && theirType.empty()) { + CssMediaQuery_Obj query = SASS_MEMORY_NEW(CssMediaQuery, pstate()); + std::vector f1(this->features()); + std::vector f2(other->features()); + features.insert(features.end(), f1.begin(), f1.end()); + features.insert(features.end(), f2.begin(), f2.end()); + query->features(features); + return query; + } - if (parents && parents->length()) { - if (tails && tails->length() > 0) { - for (size_t n = 0, nL = tails->length(); n < nL; ++n) { - for (size_t i = 0, iL = parents->length(); i < iL; ++i) { - Complex_Selector_Obj t = (*tails)[n]; - Complex_Selector_Obj parent = (*parents)[i]; - Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent); - Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this); - ss->tail(t ? SASS_MEMORY_CLONE(t) : NULL); - Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); - // remove parent selector from sequence - if (h->length()) { - h->erase(h->begin()); - ss->head(h); - } else { - ss->head({}); - } - // adjust for parent selector (1 char) - // if (h->length()) { - // ParserState state(h->at(0)->pstate()); - // state.offset.column += 1; - // state.column -= 1; - // (*h)[0]->pstate(state); - // } - // keep old parser state - s->pstate(pstate()); - // append new tail - s->append(ss, traces); - retval->append(s); - } - } - } - // have no tails but parents - // loop above is inside out - else { - for (size_t i = 0, iL = parents->length(); i < iL; ++i) { - Complex_Selector_Obj parent = (*parents)[i]; - Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent); - Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this); - // this is only if valid if the parent has no trailing op - // otherwise we cannot append more simple selectors to head - if (parent->last()->combinator() != ANCESTOR_OF) { - traces.push_back(Backtrace(pstate())); - throw Exception::InvalidParent(parent, traces, ss); - } - ss->tail(tail ? SASS_MEMORY_CLONE(tail) : NULL); - Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); - // remove parent selector from sequence - if (h->length()) { - h->erase(h->begin()); - ss->head(h); - } else { - ss->head({}); - } - // \/ IMO ruby sass bug \/ - ss->has_line_feed(false); - // adjust for parent selector (1 char) - // if (h->length()) { - // ParserState state(h->at(0)->pstate()); - // state.offset.column += 1; - // state.column -= 1; - // (*h)[0]->pstate(state); - // } - // keep old parser state - s->pstate(pstate()); - // append new tail - s->append(ss, traces); - retval->append(s); - } - } + if ((ourModifier == "not") != (theirModifier == "not")) { + if (ourType == theirType) { + std::vector negativeFeatures = + ourModifier == "not" ? this->features() : other->features(); + std::vector positiveFeatures = + ourModifier == "not" ? other->features() : this->features(); + + // If the negative features are a subset of the positive features, the + // query is empty. For example, `not screen and (color)` has no + // intersection with `screen and (color) and (grid)`. + // However, `not screen and (color)` *does* intersect with `screen and + // (grid)`, because it means `not (screen and (color))` and so it allows + // a screen with no color but with a grid. + if (listIsSubsetOrEqual(negativeFeatures, positiveFeatures)) { + return SASS_MEMORY_NEW(CssMediaQuery, pstate()); } - // have no parent but some tails else { - if (tails && tails->length() > 0) { - for (size_t n = 0, nL = tails->length(); n < nL; ++n) { - Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this); - cpy->tail(SASS_MEMORY_CLONE(tails->at(n))); - cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate())); - for (size_t i = 1, L = this->head()->length(); i < L; ++i) - cpy->head()->append((*this->head())[i]); - if (!cpy->head()->length()) cpy->head({}); - retval->append(cpy->skip_empty_reference()); - } - } - // have no parent nor tails - else { - Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this); - cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate())); - for (size_t i = 1, L = this->head()->length(); i < L; ++i) - cpy->head()->append((*this->head())[i]); - if (!cpy->head()->length()) cpy->head({}); - retval->append(cpy->skip_empty_reference()); - } + return {}; } } - // no parent selector in head - else { - retval = this->tails(tails); + else if (this->matchesAllTypes() || other->matchesAllTypes()) { + return {}; } - for (Simple_Selector_Obj ss : head->elements()) { - if (Wrapped_Selector* ws = Cast(ss)) { - if (Selector_List* sl = Cast(ws->selector())) { - if (parents) ws->selector(sl->resolve_parent_refs(pstack, traces, implicit_parent)); - } - } + if (ourModifier == "not") { + modifier = theirModifier; + type = theirType; + features = other->features(); + } + else { + modifier = ourModifier; + type = ourType; + features = this->features(); } - - return retval.detach(); - } - // has no head - return this->tails(tails); - } - - Selector_List* Complex_Selector::tails(Selector_List* tails) - { - Selector_List* rv = SASS_MEMORY_NEW(Selector_List, pstate_); - if (tails && tails->length()) { - for (size_t i = 0, iL = tails->length(); i < iL; ++i) { - Complex_Selector_Obj pr = SASS_MEMORY_CLONE(this); - pr->tail(tails->at(i)); - rv->append(pr); + else if (ourModifier == "not") { + SASS_ASSERT(theirModifier == "not", "modifiers not is sync"); + + // CSS has no way of representing "neither screen nor print". + if (ourType != theirType) return {}; + + auto moreFeatures = this->features().size() > other->features().size() + ? this->features() + : other->features(); + auto fewerFeatures = this->features().size() > other->features().size() + ? other->features() + : this->features(); + + // If one set of features is a superset of the other, + // use those features because they're strictly narrower. + if (listIsSubsetOrEqual(fewerFeatures, moreFeatures)) { + modifier = ourModifier; // "not" + type = ourType; + features = moreFeatures; + } + else { + // Otherwise, there's no way to + // represent the intersection. + return {}; } + } else { - rv->append(this); - } - return rv; - } - - // return the last tail that is defined - const Complex_Selector* Complex_Selector::first() const - { - // declare variables used in loop - const Complex_Selector* cur = this; - const Compound_Selector* head; - // processing loop - while (cur) - { - // get the head - head = cur->head_.ptr(); - // abort (and return) if it is not a parent selector - if (!head || head->length() != 1 || !Cast((*head)[0])) { - break; + if (this->matchesAllTypes()) { + modifier = theirModifier; + // Omit the type if either input query did, since that indicates that they + // aren't targeting a browser that requires "all and". + type = (other->matchesAllTypes() && ourType.empty()) ? "" : theirType; + std::vector f1(this->features()); + std::vector f2(other->features()); + features.insert(features.end(), f1.begin(), f1.end()); + features.insert(features.end(), f2.begin(), f2.end()); + } + else if (other->matchesAllTypes()) { + modifier = ourModifier; + type = ourType; + std::vector f1(this->features()); + std::vector f2(other->features()); + features.insert(features.end(), f1.begin(), f1.end()); + features.insert(features.end(), f2.begin(), f2.end()); + } + else if (ourType != theirType) { + return SASS_MEMORY_NEW(CssMediaQuery, pstate()); + } + else { + modifier = ourModifier.empty() ? theirModifier : ourModifier; + type = ourType; + std::vector f1(this->features()); + std::vector f2(other->features()); + features.insert(features.end(), f1.begin(), f1.end()); + features.insert(features.end(), f2.begin(), f2.end()); } - // advance to next - cur = cur->tail_; } - // result - return cur; + + CssMediaQuery_Obj query = SASS_MEMORY_NEW(CssMediaQuery, pstate()); + query->modifier(modifier == ourModifier ? this->modifier() : other->modifier()); + query->type(ourType.empty() ? other->type() : this->type()); + query->features(features); + return query; } - Complex_Selector* Complex_Selector::mutable_first() + CssMediaQuery::CssMediaQuery(const CssMediaQuery* ptr) : + AST_Node(*ptr), + modifier_(ptr->modifier_), + type_(ptr->type_), + features_(ptr->features_) { - return const_cast(first()); } - // return the last tail that is defined - const Complex_Selector* Complex_Selector::last() const + ///////////////////////////////////////////////////////////////////////// + // ToDo: finalize specificity implementation + ///////////////////////////////////////////////////////////////////////// + + size_t SelectorList::maxSpecificity() const { - const Complex_Selector* cur = this; - const Complex_Selector* nxt = cur; - // loop until last - while (nxt) { - cur = nxt; - nxt = cur->tail_.ptr(); + size_t specificity = 0; + for (auto complex : elements()) { + specificity = std::max(specificity, complex->maxSpecificity()); } - return cur; + return specificity; } - Complex_Selector* Complex_Selector::mutable_last() + size_t SelectorList::minSpecificity() const { - return const_cast(last()); + size_t specificity = 0; + for (auto complex : elements()) { + specificity = std::min(specificity, complex->minSpecificity()); + } + return specificity; } - Complex_Selector::Combinator Complex_Selector::clear_innermost() + size_t CompoundSelector::maxSpecificity() const { - Combinator c; - if (!tail() || tail()->tail() == nullptr) - { c = combinator(); combinator(ANCESTOR_OF); tail({}); } - else - { c = tail_->clear_innermost(); } - return c; + size_t specificity = 0; + for (auto simple : elements()) { + specificity += simple->maxSpecificity(); + } + return specificity; } - void Complex_Selector::set_innermost(Complex_Selector_Obj val, Combinator c) + size_t CompoundSelector::minSpecificity() const { - if (!tail_) - { tail_ = val; combinator(c); } - else - { tail_->set_innermost(val, c); } + size_t specificity = 0; + for (auto simple : elements()) { + specificity += simple->minSpecificity(); + } + return specificity; } - void Complex_Selector::cloneChildren() + size_t ComplexSelector::maxSpecificity() const { - if (head()) head(SASS_MEMORY_CLONE(head())); - if (tail()) tail(SASS_MEMORY_CLONE(tail())); + size_t specificity = 0; + for (auto component : elements()) { + specificity += component->maxSpecificity(); + } + return specificity; } - // it's a superselector if every selector of the right side - // list is a superselector of the given left side selector - bool Complex_Selector::is_superselector_of(const Selector_List* sub, std::string wrapping) const + size_t ComplexSelector::minSpecificity() const { - // Check every rhs selector against left hand list - for(size_t i = 0, L = sub->length(); i < L; ++i) { - if (!is_superselector_of((*sub)[i], wrapping)) return false; + size_t specificity = 0; + for (auto component : elements()) { + specificity += component->minSpecificity(); } - return true; + return specificity; } ///////////////////////////////////////////////////////////////////////// + // ToDo: this might be done easier with new selector format ///////////////////////////////////////////////////////////////////////// - Selector_List::Selector_List(ParserState pstate, size_t s) - : Selector(pstate), - Vectorized(s), - schema_({}), - wspace_(0) - { } - Selector_List::Selector_List(const Selector_List* ptr) - : Selector(ptr), - Vectorized(*ptr), - schema_(ptr->schema_), - wspace_(ptr->wspace_) - { } - - bool Selector_List::find ( bool (*f)(AST_Node_Obj) ) + std::vector + CompoundSelector::resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent) { - // check children first - for (Complex_Selector_Obj sel : elements()) { - if (sel->find(f)) return true; - } - // execute last - return f(this); - } - Selector_List_Obj Selector_List::eval(Eval& eval) - { - Selector_List_Obj list = schema() ? - eval(schema()) : eval(this); - list->schema(schema()); - return list; - } + auto parent = pstack.back(); + std::vector rv; - Selector_List* Selector_List::resolve_parent_refs(SelectorStack& pstack, Backtraces& traces, bool implicit_parent) - { - if (!this->has_parent_ref()) return this; - Selector_List* ss = SASS_MEMORY_NEW(Selector_List, pstate()); - for (size_t si = 0, sL = this->length(); si < sL; ++si) { - Selector_List_Obj rv = at(si)->resolve_parent_refs(pstack, traces, implicit_parent); - ss->concat(rv); + for (SimpleSelectorObj simple : elements()) { + if (Pseudo_Selector * pseudo = Cast(simple)) { + if (SelectorList* sel = Cast(pseudo->selector())) { + if (parent) { + pseudo->selector(sel->resolve_parent_refs( + pstack, traces, implicit_parent)); + } + } + } } - return ss; - } - void Selector_List::cloneChildren() - { - for (size_t i = 0, l = length(); i < l; i++) { - at(i) = SASS_MEMORY_CLONE(at(i)); - } - } + // Mix with parents from stack + if (hasRealParent()) { - // remove parent selector references - // basically unwraps parsed selectors - void Selector_List::remove_parent_selectors() - { - // Check every rhs selector against left hand list - for(size_t i = 0, L = length(); i < L; ++i) { - if (!(*this)[i]->head()) continue; - if ((*this)[i]->head()->is_empty_reference()) { - // simply move to the next tail if we have "no" combinator - if ((*this)[i]->combinator() == Complex_Selector::ANCESTOR_OF) { - if ((*this)[i]->tail()) { - if ((*this)[i]->has_line_feed()) { - (*this)[i]->tail()->has_line_feed(true); + if (parent.isNull()) { + return { wrapInComplex() }; + } + else { + for (auto complex : parent->elements()) { + // The parent complex selector has a compound selector + if (CompoundSelectorObj tail = Cast(complex->last())) { + // Create a copy to alter it + complex = SASS_MEMORY_COPY(complex); + tail = SASS_MEMORY_COPY(tail); + + // Check if we can merge front with back + if (length() > 0 && tail->length() > 0) { + SimpleSelectorObj back = tail->last(); + SimpleSelectorObj front = first(); + auto simple_back = Cast(back); + auto simple_front = Cast(front); + if (simple_front && simple_back) { + simple_back = SASS_MEMORY_COPY(simple_back); + auto name = simple_back->name(); + name += simple_front->name(); + simple_back->name(name); + tail->elements().back() = simple_back; + tail->elements().insert(tail->end(), + begin() + 1, end()); + } + else { + tail->concat(this); + } + } + else { + tail->concat(this); } - (*this)[i] = (*this)[i]->tail(); + + complex->elements().back() = tail; + // Append to results + rv.push_back(complex); + } + else { + // Can't insert parent that ends with a combinator + // where the parent selector is followed by something + if (parent && length() > 0) { + throw Exception::InvalidParent(parent, traces, this); + } + // Create a copy to alter it + complex = SASS_MEMORY_COPY(complex); + // Just append ourself + complex->append(this); + // Append to results + rv.push_back(complex); } - } - // otherwise remove the first item from head - else { - (*this)[i]->head()->erase((*this)[i]->head()->begin()); } } } - } - - bool Selector_List::has_parent_ref() const - { - for (Complex_Selector_Obj s : elements()) { - if (s && s->has_parent_ref()) return true; - } - return false; - } - bool Selector_List::has_real_parent_ref() const - { - for (Complex_Selector_Obj s : elements()) { - if (s && s->has_real_parent_ref()) return true; + // No parents + else { + // Create a new wrapper to wrap ourself + auto complex = SASS_MEMORY_NEW(ComplexSelector, pstate()); + // Just append ourself + complex->append(this); + // Append to results + rv.push_back(complex); } - return false; - } - void Selector_List::adjust_after_pushing(Complex_Selector_Obj c) - { - // if (c->has_reference()) has_reference(true); - } - - // it's a superselector if every selector of the right side - // list is a superselector of the given left side selector - bool Selector_List::is_superselector_of(const Selector_List* sub, std::string wrapping) const - { - // Check every rhs selector against left hand list - for(size_t i = 0, L = sub->length(); i < L; ++i) { - if (!is_superselector_of((*sub)[i], wrapping)) return false; - } - return true; - } - - // it's a superselector if every selector on the right side - // is a superselector of any one of the left side selectors - bool Selector_List::is_superselector_of(const Compound_Selector* sub, std::string wrapping) const - { - // Check every lhs selector against right hand - for(size_t i = 0, L = length(); i < L; ++i) { - if ((*this)[i]->is_superselector_of(sub, wrapping)) return true; - } - return false; - } + return rv; - // it's a superselector if every selector on the right side - // is a superselector of any one of the left side selectors - bool Selector_List::is_superselector_of(const Complex_Selector* sub, std::string wrapping) const - { - // Check every lhs selector against right hand - for(size_t i = 0, L = length(); i < L; ++i) { - if ((*this)[i]->is_superselector_of(sub)) return true; - } - return false; } - void Selector_List::populate_extends(Selector_List_Obj extendee, Subset_Map& extends) + /* better return std::vector? only - is empty container anyway? */ + SelectorList* ComplexSelector::resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent) { - Selector_List* extender = this; - for (auto complex_sel : extendee->elements()) { - Complex_Selector_Obj c = complex_sel; + std::vector> vars; + auto parent = pstack.back(); - // Ignore any parent selectors, until we find the first non Selectorerence head - Compound_Selector_Obj compound_sel = c->head(); - Complex_Selector_Obj pIter = complex_sel; - while (pIter) { - Compound_Selector_Obj pHead = pIter->head(); - if (pHead && Cast(pHead->elements()[0]) == NULL) { - compound_sel = pHead; - break; - } + if (has_real_parent_ref() && !parent) { + throw Exception::TopLevelParent(traces, pstate()); + } - pIter = pIter->tail(); - } + if (!chroots() && parent) { - if (!pIter->head() || pIter->tail()) { - coreError("nested selectors may not be extended", c->pstate()); + if (!has_real_parent_ref() && !implicit_parent) { + SelectorList* retval = SASS_MEMORY_NEW(SelectorList, pstate(), 1); + retval->append(this); + return retval; } - compound_sel->is_optional(extendee->is_optional()); + vars.push_back(parent->elements()); + } - for (size_t i = 0, L = extender->length(); i < L; ++i) { - extends.put(compound_sel, std::make_pair((*extender)[i], compound_sel)); + for (auto sel : elements()) { + if (CompoundSelectorObj comp = Cast(sel)) { + auto asd = comp->resolve_parent_refs(pstack, traces, implicit_parent); + if (asd.size() > 0) vars.push_back(asd); + } + else { + // ToDo: merge together sequences whenever possible + auto cont = SASS_MEMORY_NEW(ComplexSelector, pstate()); + cont->append(sel); + vars.push_back({ cont }); } } - }; - size_t Selector_List::hash() const - { - if (Selector::hash_ == 0) { - if (empty()) { - hash_combine(Selector::hash_, std::hash()(SELECTOR)); - } else { - hash_combine(Selector::hash_, Vectorized::hash()); + // Need complex selectors to preserve linefeeds + std::vector> res = permutateAlt(vars); + + // std::reverse(std::begin(res), std::end(res)); + + auto lst = SASS_MEMORY_NEW(SelectorList, pstate()); + for (auto items : res) { + if (items.size() > 0) { + ComplexSelectorObj first = SASS_MEMORY_COPY(items[0]); + first->hasPreLineFeed(first->hasPreLineFeed() || (!has_real_parent_ref() && hasPreLineFeed())); + // ToDo: remove once we know how to handle line feeds + // ToDo: currently a mashup between ruby and dart sass + // if (has_real_parent_ref()) first->has_line_feed(false); + // first->has_line_break(first->has_line_break() || has_line_break()); + first->chroots(true); // has been resolved by now + for (size_t i = 1; i < items.size(); i += 1) { + first->concat(items[i]); + } + lst->append(first); } } - return Selector::hash_; - } - unsigned long Selector_List::specificity() const - { - unsigned long sum = 0; - unsigned long specificity; - for (size_t i = 0, L = length(); i < L; ++i) - { - specificity = (*this)[i]->specificity(); - if (sum < specificity) sum = specificity; - } - return sum; - } + return lst; - void Selector_List::set_media_block(Media_Block* mb) - { - media_block(mb); - for (Complex_Selector_Obj cs : elements()) { - cs->set_media_block(mb); - } } - bool Selector_List::has_placeholder() + SelectorList* SelectorList::resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent) { - for (Complex_Selector_Obj cs : elements()) { - if (cs->has_placeholder()) return true; + SelectorList* rv = SASS_MEMORY_NEW(SelectorList, pstate()); + for (auto sel : elements()) { + // Note: this one is tricky as we get back a pointer from resolve parents ... + SelectorListObj res = sel->resolve_parent_refs(pstack, traces, implicit_parent); + // Note: ... and concat will only append the items in elements + // Therefore by passing it directly, the container will leak! + rv->concat(res); } - return false; + return rv; } ///////////////////////////////////////////////////////////////////////// @@ -1461,15 +1017,14 @@ namespace Sass { IMPLEMENT_AST_OPERATORS(Selector_Schema); IMPLEMENT_AST_OPERATORS(Placeholder_Selector); - IMPLEMENT_AST_OPERATORS(Parent_Selector); IMPLEMENT_AST_OPERATORS(Attribute_Selector); - IMPLEMENT_AST_OPERATORS(Compound_Selector); - IMPLEMENT_AST_OPERATORS(Complex_Selector); IMPLEMENT_AST_OPERATORS(Type_Selector); IMPLEMENT_AST_OPERATORS(Class_Selector); IMPLEMENT_AST_OPERATORS(Id_Selector); IMPLEMENT_AST_OPERATORS(Pseudo_Selector); - IMPLEMENT_AST_OPERATORS(Wrapped_Selector); - IMPLEMENT_AST_OPERATORS(Selector_List); + IMPLEMENT_AST_OPERATORS(SelectorCombinator); + IMPLEMENT_AST_OPERATORS(CompoundSelector); + IMPLEMENT_AST_OPERATORS(ComplexSelector); + IMPLEMENT_AST_OPERATORS(SelectorList); } diff --git a/src/ast_selectors.hpp b/src/ast_selectors.hpp index b7257083a..23b563802 100644 --- a/src/ast_selectors.hpp +++ b/src/ast_selectors.hpp @@ -4,68 +4,57 @@ // sass.hpp must go before all system headers to get the // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include "sass/base.h" -#include "ast_fwd_decl.hpp" - -#include "util.hpp" -#include "units.hpp" -#include "context.hpp" -#include "position.hpp" -#include "constants.hpp" -#include "operation.hpp" -#include "position.hpp" -#include "inspect.hpp" -#include "source_map.hpp" -#include "environment.hpp" -#include "error_handling.hpp" -#include "ast_def_macros.hpp" -#include "ast_fwd_decl.hpp" -#include "source_map.hpp" -#include "fn_utils.hpp" - -#include "sass.h" +#include "ast.hpp" namespace Sass { + ///////////////////////////////////////////////////////////////////////// + // Some helper functions + ///////////////////////////////////////////////////////////////////////// + + bool compoundIsSuperselector( + const CompoundSelectorObj& compound1, + const CompoundSelectorObj& compound2, + const std::vector& parents); + + bool complexIsParentSuperselector( + const std::vector& complex1, + const std::vector& complex2); + + std::vector> weave( + const std::vector>& complexes); + + std::vector> weaveParents( + std::vector parents1, + std::vector parents2); + + std::vector unifyCompound( + const std::vector& compound1, + const std::vector& compound2); + + std::vector> unifyComplex( + const std::vector>& complexes); + ///////////////////////////////////////// // Abstract base class for CSS selectors. ///////////////////////////////////////// class Selector : public Expression { - // line break before list separator - ADD_PROPERTY(bool, has_line_feed) - // line break after list separator - ADD_PROPERTY(bool, has_line_break) - // maybe we have optional flag - ADD_PROPERTY(bool, is_optional) - // must not be a reference counted object - // otherwise we create circular references - ADD_PROPERTY(Media_Block*, media_block) protected: mutable size_t hash_; public: Selector(ParserState pstate); virtual ~Selector() = 0; size_t hash() const override = 0; - virtual unsigned long specificity() const = 0; - virtual int unification_order() const = 0; - virtual void set_media_block(Media_Block* mb); - virtual bool has_parent_ref() const; virtual bool has_real_parent_ref() const; + // you should reset this to null on containers + virtual unsigned long specificity() const = 0; + // by default we return the regular specificity + // you must override this for all containers + virtual size_t maxSpecificity() const { return specificity(); } + virtual size_t minSpecificity() const { return specificity(); } // dispatch to correct handlers - virtual bool operator<(const Selector& rhs) const = 0; - virtual bool operator==(const Selector& rhs) const = 0; - bool operator>(const Selector& rhs) const { return rhs < *this; }; - bool operator!=(const Selector& rhs) const { return !(rhs == *this); }; - ATTACH_VIRTUAL_AST_OPERATIONS(Selector); + ATTACH_VIRTUAL_CMP_OPERATIONS(Selector) + ATTACH_VIRTUAL_AST_OPERATIONS(Selector) }; inline Selector::~Selector() { } @@ -74,19 +63,14 @@ namespace Sass { // re-parsed into a normal selector class. ///////////////////////////////////////////////////////////////////////// class Selector_Schema final : public AST_Node { - ADD_PROPERTY(String_Obj, contents) + ADD_PROPERTY(String_Schema_Obj, contents) ADD_PROPERTY(bool, connect_parent); - // must not be a reference counted object - // otherwise we create circular references - ADD_PROPERTY(Media_Block*, media_block) // store computed hash mutable size_t hash_; public: Selector_Schema(ParserState pstate, String_Obj c); - bool has_parent_ref() const; + bool has_real_parent_ref() const; - bool operator<(const Selector& rhs) const; - bool operator==(const Selector& rhs) const; // selector schema is not yet a final selector, so we do not // have a specificity for it yet. We need to virtual unsigned long specificity() const; @@ -98,15 +82,13 @@ namespace Sass { //////////////////////////////////////////// // Abstract base class for simple selectors. //////////////////////////////////////////// - class Simple_Selector : public Selector { + class SimpleSelector : public Selector { public: enum Simple_Type { ID_SEL, TYPE_SEL, CLASS_SEL, PSEUDO_SEL, - PARENT_SEL, - WRAPPED_SEL, ATTRIBUTE_SEL, PLACEHOLDER_SEL, }; @@ -116,12 +98,12 @@ namespace Sass { ADD_PROPERTY(Simple_Type, simple_type) HASH_PROPERTY(bool, has_ns) public: - Simple_Selector(ParserState pstate, std::string n = ""); + SimpleSelector(ParserState pstate, std::string n = ""); virtual std::string ns_name() const; size_t hash() const override; - bool empty() const; + virtual bool empty() const; // namespace compare functions - bool is_ns_eq(const Simple_Selector& r) const; + bool is_ns_eq(const SimpleSelector& r) const; // namespace query functions bool is_universal_ns() const; bool is_empty_ns() const; @@ -131,81 +113,45 @@ namespace Sass { bool is_universal() const; virtual bool has_placeholder(); - virtual ~Simple_Selector() = 0; - virtual Compound_Selector* unify_with(Compound_Selector*); + virtual ~SimpleSelector() = 0; + virtual CompoundSelector* unifyWith(CompoundSelector*); - virtual bool has_parent_ref() const override; - virtual bool has_real_parent_ref() const override; + /* helper function for syntax sugar */ + virtual Id_Selector* getIdSelector() { return NULL; } + virtual Type_Selector* getTypeSelector() { return NULL; } + virtual Pseudo_Selector* getPseudoSelector() { return NULL; } + + ComplexSelectorObj wrapInComplex(); + CompoundSelectorObj wrapInCompound(); + + virtual bool isInvisible() const { return false; } virtual bool is_pseudo_element() const; - virtual bool is_superselector_of(const Compound_Selector* sub) const; + virtual bool has_real_parent_ref() const override; - bool operator<(const Selector& rhs) const final override; bool operator==(const Selector& rhs) const final override; - virtual bool operator<(const Selector_List& rhs) const; - virtual bool operator==(const Selector_List& rhs) const; - virtual bool operator<(const Complex_Selector& rhs) const; - virtual bool operator==(const Complex_Selector& rhs) const; - virtual bool operator<(const Compound_Selector& rhs) const; - virtual bool operator==(const Compound_Selector& rhs) const; - virtual bool operator<(const Simple_Selector& rhs) const; - virtual bool operator==(const Simple_Selector& rhs) const; - - ATTACH_VIRTUAL_AST_OPERATIONS(Simple_Selector); - ATTACH_CRTP_PERFORM_METHODS(); - }; - inline Simple_Selector::~Simple_Selector() { } - - ////////////////////////////////// - // The Parent Selector Expression. - ////////////////////////////////// - class Parent_Selector final : public Simple_Selector { - // a real parent selector is given by the user - // others are added implicitly to connect the - // selector scopes automatically when rendered - // a Parent_Reference is never seen in selectors - // and is only used in values (e.g. `prop: #{&};`) - ADD_PROPERTY(bool, real) - public: - Parent_Selector(ParserState pstate, bool r = true); + virtual bool operator==(const SelectorList& rhs) const; + virtual bool operator==(const ComplexSelector& rhs) const; + virtual bool operator==(const CompoundSelector& rhs) const; - virtual bool has_parent_ref() const override; - virtual bool has_real_parent_ref() const override; + ATTACH_VIRTUAL_CMP_OPERATIONS(SimpleSelector); + ATTACH_VIRTUAL_AST_OPERATIONS(SimpleSelector); + ATTACH_CRTP_PERFORM_METHODS(); - virtual unsigned long specificity() const override; - int unification_order() const override - { - throw std::runtime_error("unification_order for Parent_Selector is undefined"); - } - std::string type() const override { return "selector"; } - static std::string type_name() { return "selector"; } - bool operator<(const Simple_Selector& rhs) const final override; - bool operator==(const Simple_Selector& rhs) const final override; - bool operator<(const Parent_Selector& rhs) const; - bool operator==(const Parent_Selector& rhs) const; - ATTACH_AST_OPERATIONS(Parent_Selector) - ATTACH_CRTP_PERFORM_METHODS() }; - + inline SimpleSelector::~SimpleSelector() { } ///////////////////////////////////////////////////////////////////////// // Placeholder selectors (e.g., "%foo") for use in extend-only selectors. ///////////////////////////////////////////////////////////////////////// - class Placeholder_Selector final : public Simple_Selector { + class Placeholder_Selector final : public SimpleSelector { public: Placeholder_Selector(ParserState pstate, std::string n); - - int unification_order() const override - { - return Constants::UnificationOrder_Placeholder; - } - virtual ~Placeholder_Selector() {}; + bool isInvisible() const override { return true; } virtual unsigned long specificity() const override; virtual bool has_placeholder() override; - bool operator<(const Simple_Selector& rhs) const override; - bool operator==(const Simple_Selector& rhs) const override; - bool operator<(const Placeholder_Selector& rhs) const; - bool operator==(const Placeholder_Selector& rhs) const; + bool operator==(const SimpleSelector& rhs) const override; + ATTACH_CMP_OPERATIONS(Placeholder_Selector) ATTACH_AST_OPERATIONS(Placeholder_Selector) ATTACH_CRTP_PERFORM_METHODS() }; @@ -213,20 +159,15 @@ namespace Sass { ///////////////////////////////////////////////////////////////////// // Type selectors (and the universal selector) -- e.g., div, span, *. ///////////////////////////////////////////////////////////////////// - class Type_Selector final : public Simple_Selector { + class Type_Selector final : public SimpleSelector { public: Type_Selector(ParserState pstate, std::string n); virtual unsigned long specificity() const override; - int unification_order() const override - { - return Constants::UnificationOrder_Element; - } - Simple_Selector* unify_with(Simple_Selector*); - Compound_Selector* unify_with(Compound_Selector*) override; - bool operator<(const Simple_Selector& rhs) const final override; - bool operator==(const Simple_Selector& rhs) const final override; - bool operator<(const Type_Selector& rhs) const; - bool operator==(const Type_Selector& rhs) const; + SimpleSelector* unifyWith(const SimpleSelector*); + CompoundSelector* unifyWith(CompoundSelector*) override; + Type_Selector* getTypeSelector() override { return this; } + bool operator==(const SimpleSelector& rhs) const final override; + ATTACH_CMP_OPERATIONS(Type_Selector) ATTACH_AST_OPERATIONS(Type_Selector) ATTACH_CRTP_PERFORM_METHODS() }; @@ -234,19 +175,12 @@ namespace Sass { //////////////////////////////////////////////// // Class selectors -- i.e., .foo. //////////////////////////////////////////////// - class Class_Selector final : public Simple_Selector { + class Class_Selector final : public SimpleSelector { public: Class_Selector(ParserState pstate, std::string n); virtual unsigned long specificity() const override; - int unification_order() const override - { - return Constants::UnificationOrder_Class; - } - Compound_Selector* unify_with(Compound_Selector*) override; - bool operator<(const Simple_Selector& rhs) const final override; - bool operator==(const Simple_Selector& rhs) const final override; - bool operator<(const Class_Selector& rhs) const; - bool operator==(const Class_Selector& rhs) const; + bool operator==(const SimpleSelector& rhs) const final override; + ATTACH_CMP_OPERATIONS(Class_Selector) ATTACH_AST_OPERATIONS(Class_Selector) ATTACH_CRTP_PERFORM_METHODS() }; @@ -254,19 +188,14 @@ namespace Sass { //////////////////////////////////////////////// // ID selectors -- i.e., #foo. //////////////////////////////////////////////// - class Id_Selector final : public Simple_Selector { + class Id_Selector final : public SimpleSelector { public: Id_Selector(ParserState pstate, std::string n); virtual unsigned long specificity() const override; - int unification_order() const override - { - return Constants::UnificationOrder_Id; - } - Compound_Selector* unify_with(Compound_Selector*) override; - bool operator<(const Simple_Selector& rhs) const final override; - bool operator==(const Simple_Selector& rhs) const final override; - bool operator<(const Id_Selector& rhs) const; - bool operator==(const Id_Selector& rhs) const; + CompoundSelector* unifyWith(CompoundSelector*) override; + Id_Selector* getIdSelector() final override { return this; } + bool operator==(const SimpleSelector& rhs) const final override; + ATTACH_CMP_OPERATIONS(Id_Selector) ATTACH_AST_OPERATIONS(Id_Selector) ATTACH_CRTP_PERFORM_METHODS() }; @@ -274,7 +203,7 @@ namespace Sass { /////////////////////////////////////////////////// // Attribute selectors -- e.g., [src*=".jpg"], etc. /////////////////////////////////////////////////// - class Attribute_Selector final : public Simple_Selector { + class Attribute_Selector final : public SimpleSelector { ADD_CONSTREF(std::string, matcher) // this cannot be changed to obj atm!!!!!!????!!!!!!! ADD_PROPERTY(String_Obj, value) // might be interpolated @@ -283,14 +212,8 @@ namespace Sass { Attribute_Selector(ParserState pstate, std::string n, std::string m, String_Obj v, char o = 0); size_t hash() const override; virtual unsigned long specificity() const override; - int unification_order() const override - { - return Constants::UnificationOrder_Attribute; - } - bool operator<(const Simple_Selector& rhs) const final override; - bool operator==(const Simple_Selector& rhs) const final override; - bool operator<(const Attribute_Selector& rhs) const; - bool operator==(const Attribute_Selector& rhs) const; + bool operator==(const SimpleSelector& rhs) const final override; + ATTACH_CMP_OPERATIONS(Attribute_Selector) ATTACH_AST_OPERATIONS(Attribute_Selector) ATTACH_CRTP_PERFORM_METHODS() }; @@ -298,270 +221,291 @@ namespace Sass { ////////////////////////////////////////////////////////////////// // Pseudo selectors -- e.g., :first-child, :nth-of-type(...), etc. ////////////////////////////////////////////////////////////////// - /* '::' starts a pseudo-element, ':' a pseudo-class */ - /* Except :first-line, :first-letter, :before and :after */ - /* Note that pseudo-elements are restricted to one per selector */ - /* and occur only in the last simple_selector_sequence. */ - inline bool is_pseudo_class_element(const std::string& name) - { - return name == ":before" || - name == ":after" || - name == ":first-line" || - name == ":first-letter"; - } - // Pseudo Selector cannot have any namespace? - class Pseudo_Selector final : public Simple_Selector { - ADD_PROPERTY(String_Obj, expression) + class Pseudo_Selector final : public SimpleSelector { + ADD_PROPERTY(std::string, normalized) + ADD_PROPERTY(String_Obj, argument) + ADD_PROPERTY(SelectorListObj, selector) + ADD_PROPERTY(bool, isSyntacticClass) + ADD_PROPERTY(bool, isClass) public: - Pseudo_Selector(ParserState pstate, std::string n, String_Obj expr = {}); + Pseudo_Selector(ParserState pstate, std::string n, bool element = false); virtual bool is_pseudo_element() const override; size_t hash() const override; + + bool empty() const override; + + bool has_real_parent_ref() const override; + + // Whether this is a pseudo-element selector. + // This is `true` if and only if [isClass] is `false`. + bool isElement() const { return !isClass(); } + + // Whether this is syntactically a pseudo-element selector. + // This is `true` if and only if [isSyntacticClass] is `false`. + bool isSyntacticElement() const { return !isSyntacticClass(); } + virtual unsigned long specificity() const override; - int unification_order() const override - { - if (is_pseudo_element()) - return Constants::UnificationOrder_PseudoElement; - return Constants::UnificationOrder_PseudoClass; - } - bool operator<(const Simple_Selector& rhs) const final override; - bool operator==(const Simple_Selector& rhs) const final override; - bool operator<(const Pseudo_Selector& rhs) const; - bool operator==(const Pseudo_Selector& rhs) const; - Compound_Selector* unify_with(Compound_Selector*) override; + Pseudo_Selector_Obj withSelector(SelectorListObj selector); + + CompoundSelector* unifyWith(CompoundSelector*) override; + Pseudo_Selector* getPseudoSelector() final override { return this; } + bool operator==(const SimpleSelector& rhs) const final override; + ATTACH_CMP_OPERATIONS(Pseudo_Selector) ATTACH_AST_OPERATIONS(Pseudo_Selector) + void cloneChildren() override; ATTACH_CRTP_PERFORM_METHODS() }; - ///////////////////////////////////////////////// - // Wrapped selector -- pseudo selector that takes a list of selectors as argument(s) e.g., :not(:first-of-type), :-moz-any(ol p.blah, ul, menu, dir) - ///////////////////////////////////////////////// - class Wrapped_Selector final : public Simple_Selector { - ADD_PROPERTY(Selector_List_Obj, selector) + + //////////////////////////////////////////////////////////////////////////// + // Complex Selectors are the most important class of selectors. + // A Selector List consists of Complex Selectors (separated by comma) + // Complex Selectors are itself a list of Compounds and Combinators + // Between each item there is an implicit ancestor of combinator + //////////////////////////////////////////////////////////////////////////// + class ComplexSelector final : public Selector, public Vectorized { + ADD_PROPERTY(bool, chroots) + // line break before list separator + ADD_PROPERTY(bool, hasPreLineFeed) public: - Wrapped_Selector(ParserState pstate, std::string n, Selector_List_Obj sel); - using Simple_Selector::is_superselector_of; - bool is_superselector_of(const Wrapped_Selector* sub) const; - // Selectors inside the negation pseudo-class are counted like any - // other, but the negation itself does not count as a pseudo-class. + ComplexSelector(ParserState pstate); + + // Returns true if the first components + // is a compound selector and fullfills + // a few other criterias. + bool isInvisible() const; + size_t hash() const override; - bool has_parent_ref() const override; - bool has_real_parent_ref() const override; - unsigned long specificity() const override; - int unification_order() const override - { - return Constants::UnificationOrder_Wrapped; - } - bool find ( bool (*f)(AST_Node_Obj) ) override; - bool operator<(const Simple_Selector& rhs) const final override; - bool operator==(const Simple_Selector& rhs) const final override; - bool operator<(const Wrapped_Selector& rhs) const; - bool operator==(const Wrapped_Selector& rhs) const; void cloneChildren() override; - ATTACH_AST_OPERATIONS(Wrapped_Selector) + bool has_placeholder() const; + bool has_real_parent_ref() const override; + + SelectorList* resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent = true); + virtual unsigned long specificity() const override; + + SelectorList* unifyWith(ComplexSelector* rhs); + + bool isSuperselectorOf(const ComplexSelector* sub) const; + + SelectorListObj wrapInList(); + + size_t maxSpecificity() const override; + size_t minSpecificity() const override; + + bool operator==(const Selector& rhs) const override; + bool operator==(const SelectorList& rhs) const; + bool operator==(const CompoundSelector& rhs) const; + bool operator==(const SimpleSelector& rhs) const; + + ATTACH_CMP_OPERATIONS(ComplexSelector) + ATTACH_AST_OPERATIONS(ComplexSelector) ATTACH_CRTP_PERFORM_METHODS() }; //////////////////////////////////////////////////////////////////////////// - // Simple selector sequences. Maintains flags indicating whether it contains - // any parent references or placeholders, to simplify expansion. + // Base class for complex selector components //////////////////////////////////////////////////////////////////////////// - class Compound_Selector final : public Selector, public Vectorized { - private: - ComplexSelectorSet sources_; - ADD_PROPERTY(bool, extended); - ADD_PROPERTY(bool, has_parent_reference); - protected: - void adjust_after_pushing(Simple_Selector_Obj s) override - { - // if (s->has_reference()) has_reference(true); - // if (s->has_placeholder()) has_placeholder(true); - } + class SelectorComponent : public Selector { + // line break after list separator + ADD_PROPERTY(bool, hasPostLineBreak) public: - Compound_Selector(ParserState pstate, size_t s = 0); - bool contains_placeholder(); - void append(Simple_Selector_Obj element) override; - bool is_universal() const; - Complex_Selector_Obj to_complex(); - Compound_Selector* unify_with(Compound_Selector* rhs); - // virtual Placeholder_Selector* find_placeholder(); - bool has_parent_ref() const override; - bool has_real_parent_ref() const override; - Simple_Selector* base() const; - bool is_superselector_of(const Compound_Selector* sub, std::string wrapped = "") const; - bool is_superselector_of(const Complex_Selector* sub, std::string wrapped = "") const; - bool is_superselector_of(const Selector_List* sub, std::string wrapped = "") const; - size_t hash() const override; - virtual unsigned long specificity() const override; - virtual bool has_placeholder(); - bool is_empty_reference(); - int unification_order() const override - { - throw std::runtime_error("unification_order for Compound_Selector is undefined"); - } - bool find ( bool (*f)(AST_Node_Obj) ) override; - - bool operator<(const Selector& rhs) const override; - bool operator==(const Selector& rhs) const override; - bool operator<(const Selector_List& rhs) const; - bool operator==(const Selector_List& rhs) const; - bool operator<(const Complex_Selector& rhs) const; - bool operator==(const Complex_Selector& rhs) const; - bool operator<(const Compound_Selector& rhs) const; - bool operator==(const Compound_Selector& rhs) const; - bool operator<(const Simple_Selector& rhs) const; - bool operator==(const Simple_Selector& rhs) const; - - ComplexSelectorSet& sources() { return sources_; } - void clearSources() { sources_.clear(); } - void mergeSources(ComplexSelectorSet& sources); - - Compound_Selector* minus(Compound_Selector* rhs); + SelectorComponent(ParserState pstate, bool postLineBreak = false); + size_t hash() const override = 0; void cloneChildren() override; - ATTACH_AST_OPERATIONS(Compound_Selector) - ATTACH_CRTP_PERFORM_METHODS() + + + // By default we consider instances not empty + virtual bool empty() const { return false; } + + virtual bool has_placeholder() const = 0; + bool has_real_parent_ref() const override = 0; + + ComplexSelector* wrapInComplex(); + + size_t maxSpecificity() const override { return 0; } + size_t minSpecificity() const override { return 0; } + + virtual bool isCompound() const { return false; }; + virtual bool isCombinator() const { return false; }; + + /* helper function for syntax sugar */ + virtual CompoundSelector* getCompound() { return NULL; } + virtual SelectorCombinator* getCombinator() { return NULL; } + virtual const CompoundSelector* getCompound() const { return NULL; } + virtual const SelectorCombinator* getCombinator() const { return NULL; } + + virtual unsigned long specificity() const override; + bool operator==(const Selector& rhs) const override = 0; + ATTACH_VIRTUAL_CMP_OPERATIONS(SelectorComponent); + ATTACH_VIRTUAL_AST_OPERATIONS(SelectorComponent); }; //////////////////////////////////////////////////////////////////////////// - // General selectors -- i.e., simple sequences combined with one of the four - // CSS selector combinators (">", "+", "~", and whitespace). Essentially a - // linked list. + // A specific combinator between compound selectors //////////////////////////////////////////////////////////////////////////// - class Complex_Selector final : public Selector { + class SelectorCombinator final : public SelectorComponent { public: - enum Combinator { ANCESTOR_OF, PARENT_OF, PRECEDES, ADJACENT_TO, REFERENCE }; + + // Enumerate all possible selector combinators. There is some + // discrepancy with dart-sass. Opted to name them as in CSS33 + enum Combinator { CHILD /* > */, GENERAL /* ~ */, ADJACENT /* + */}; + private: + + // Store the type of this combinator HASH_CONSTREF(Combinator, combinator) - HASH_PROPERTY(Compound_Selector_Obj, head) - HASH_PROPERTY(Complex_Selector_Obj, tail) - HASH_PROPERTY(String_Obj, reference); + public: - bool contains_placeholder() { - if (head() && head()->contains_placeholder()) return true; - if (tail() && tail()->contains_placeholder()) return true; - return false; - }; - Complex_Selector(ParserState pstate, - Combinator c = ANCESTOR_OF, - Compound_Selector_Obj h = {}, - Complex_Selector_Obj t = {}, - String_Obj r = {}); + SelectorCombinator(ParserState pstate, Combinator combinator, bool postLineBreak = false); - bool empty() const; + bool has_real_parent_ref() const override { return false; } + bool has_placeholder() const override { return false; } - bool has_parent_ref() const override; - bool has_real_parent_ref() const override; - Complex_Selector_Obj skip_empty_reference(); + /* helper function for syntax sugar */ + SelectorCombinator* getCombinator() final override { return this; } + const SelectorCombinator* getCombinator() const final override { return this; } - // can still have a tail - bool is_empty_ancestor() const; + // Query type of combinator + bool isCombinator() const override { return true; }; - Selector_List* tails(Selector_List* tails); + // Matches the right-hand selector if it's a direct child of the left- + // hand selector in the DOM tree. Dart-sass also calls this `child` + // https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator + bool isChildCombinator() const { return combinator_ == CHILD; } // > - // front returns the first real tail - // skips over parent and empty ones - const Complex_Selector* first() const; - Complex_Selector* mutable_first(); + // Matches the right-hand selector if it comes after the left-hand + // selector in the DOM tree. Dart-sass class this `followingSibling` + // https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_combinator + bool isGeneralCombinator() const { return combinator_ == GENERAL; } // ~ - // last returns the last real tail - const Complex_Selector* last() const; - Complex_Selector* mutable_last(); + // Matches the right-hand selector if it's immediately adjacent to the + // left-hand selector in the DOM tree. Dart-sass calls this `nextSibling` + // https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator + bool isAdjacentCombinator() const { return combinator_ == ADJACENT; } // + - size_t length() const; - Selector_List* resolve_parent_refs(SelectorStack& pstack, Backtraces& traces, bool implicit_parent = true); - bool is_superselector_of(const Compound_Selector* sub, std::string wrapping = "") const; - bool is_superselector_of(const Complex_Selector* sub, std::string wrapping = "") const; - bool is_superselector_of(const Selector_List* sub, std::string wrapping = "") const; - Selector_List* unify_with(Complex_Selector* rhs); - Combinator clear_innermost(); - void append(Complex_Selector_Obj, Backtraces& traces); - void set_innermost(Complex_Selector_Obj, Combinator); + size_t maxSpecificity() const override { return 0; } + size_t minSpecificity() const override { return 0; } - size_t hash() const override; + size_t hash() const override { + return std::hash()(combinator_); + } + void cloneChildren() override; virtual unsigned long specificity() const override; - virtual void set_media_block(Media_Block* mb) override; - virtual bool has_placeholder(); - int unification_order() const override - { - throw std::runtime_error("unification_order for Complex_Selector is undefined"); + bool operator==(const Selector& rhs) const override; + bool operator==(const SelectorComponent& rhs) const override; + + ATTACH_CMP_OPERATIONS(SelectorCombinator) + ATTACH_AST_OPERATIONS(SelectorCombinator) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////////////////////////////////// + // A compound selector consists of multiple simple selectors + //////////////////////////////////////////////////////////////////////////// + class CompoundSelector final : public SelectorComponent, public Vectorized { + ADD_PROPERTY(bool, hasRealParent) + ADD_PROPERTY(bool, extended) + public: + CompoundSelector(ParserState pstate, bool postLineBreak = false); + + // Returns true if this compound selector + // fullfills various criterias. + bool isInvisible() const; + + bool empty() const override { + return Vectorized::empty(); } - bool find ( bool (*f)(AST_Node_Obj) ) override; - bool operator<(const Selector& rhs) const override; - bool operator==(const Selector& rhs) const override; - bool operator<(const Selector_List& rhs) const; - bool operator==(const Selector_List& rhs) const; - bool operator<(const Complex_Selector& rhs) const; - bool operator==(const Complex_Selector& rhs) const; - bool operator<(const Compound_Selector& rhs) const; - bool operator==(const Compound_Selector& rhs) const; - bool operator<(const Simple_Selector& rhs) const; - bool operator==(const Simple_Selector& rhs) const; - - const ComplexSelectorSet sources(); - void addSources(ComplexSelectorSet& sources); - void clearSources(); + size_t hash() const override; + CompoundSelector* unifyWith(CompoundSelector* rhs); + + /* helper function for syntax sugar */ + CompoundSelector* getCompound() final override { return this; } + const CompoundSelector* getCompound() const final override { return this; } + + bool isSuperselectorOf(const CompoundSelector* sub, std::string wrapped = "") const; void cloneChildren() override; - ATTACH_AST_OPERATIONS(Complex_Selector) + bool has_real_parent_ref() const override; + bool has_placeholder() const override; + std::vector resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent = true); + + virtual bool isCompound() const override { return true; }; + virtual unsigned long specificity() const override; + + size_t maxSpecificity() const override; + size_t minSpecificity() const override; + + bool operator==(const Selector& rhs) const override; + + bool operator==(const SelectorComponent& rhs) const override; + + bool operator==(const SelectorList& rhs) const; + bool operator==(const ComplexSelector& rhs) const; + bool operator==(const SimpleSelector& rhs) const; + + ATTACH_CMP_OPERATIONS(CompoundSelector) + ATTACH_AST_OPERATIONS(CompoundSelector) ATTACH_CRTP_PERFORM_METHODS() }; /////////////////////////////////// // Comma-separated selector groups. /////////////////////////////////// - class Selector_List final : public Selector, public Vectorized { - ADD_PROPERTY(Selector_Schema_Obj, schema) - ADD_CONSTREF(std::vector, wspace) - protected: - void adjust_after_pushing(Complex_Selector_Obj c) override; + class SelectorList final : public Selector, public Vectorized { + private: + // maybe we have optional flag + // ToDo: should be at ExtendRule? + ADD_PROPERTY(bool, is_optional) public: - Selector_List(ParserState pstate, size_t s = 0); + SelectorList(ParserState pstate, size_t s = 0); std::string type() const override { return "list"; } - // remove parent selector references - // basically unwraps parsed selectors - bool has_parent_ref() const override; - bool has_real_parent_ref() const override; - void remove_parent_selectors(); - Selector_List* resolve_parent_refs(SelectorStack& pstack, Backtraces& traces, bool implicit_parent = true); - bool is_superselector_of(const Compound_Selector* sub, std::string wrapping = "") const; - bool is_superselector_of(const Complex_Selector* sub, std::string wrapping = "") const; - bool is_superselector_of(const Selector_List* sub, std::string wrapping = "") const; - Selector_List* unify_with(Selector_List*); - void populate_extends(Selector_List_Obj, Subset_Map&); - Selector_List_Obj eval(Eval& eval); - size_t hash() const override; + + SelectorList* unifyWith(SelectorList*); + + // Returns true if all complex selectors + // can have real parents, meaning every + // first component does allow for it + bool isInvisible() const; + + void cloneChildren() override; + bool has_real_parent_ref() const override; + SelectorList* resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent = true); virtual unsigned long specificity() const override; - virtual void set_media_block(Media_Block* mb) override; - virtual bool has_placeholder(); - int unification_order() const override - { - throw std::runtime_error("unification_order for Selector_List is undefined"); - } - bool find ( bool (*f)(AST_Node_Obj) ) override; - bool operator<(const Selector& rhs) const override; + + bool isSuperselectorOf(const SelectorList* sub) const; + + size_t maxSpecificity() const override; + size_t minSpecificity() const override; + bool operator==(const Selector& rhs) const override; - bool operator<(const Selector_List& rhs) const; - bool operator==(const Selector_List& rhs) const; - bool operator<(const Complex_Selector& rhs) const; - bool operator==(const Complex_Selector& rhs) const; - bool operator<(const Compound_Selector& rhs) const; - bool operator==(const Compound_Selector& rhs) const; - bool operator<(const Simple_Selector& rhs) const; - bool operator==(const Simple_Selector& rhs) const; + bool operator==(const ComplexSelector& rhs) const; + bool operator==(const CompoundSelector& rhs) const; + bool operator==(const SimpleSelector& rhs) const; // Selector Lists can be compared to comma lists - bool operator<(const Expression& rhs) const override; bool operator==(const Expression& rhs) const override; - void cloneChildren() override; - ATTACH_AST_OPERATIONS(Selector_List) + + ATTACH_CMP_OPERATIONS(SelectorList) + ATTACH_AST_OPERATIONS(SelectorList) ATTACH_CRTP_PERFORM_METHODS() }; - // compare function for sorting and probably other other uses - struct cmp_complex_selector { inline bool operator() (const Complex_Selector_Obj l, const Complex_Selector_Obj r) { return (*l < *r); } }; - struct cmp_compound_selector { inline bool operator() (const Compound_Selector_Obj l, const Compound_Selector_Obj r) { return (*l < *r); } }; - struct cmp_simple_selector { inline bool operator() (const Simple_Selector_Obj l, const Simple_Selector_Obj r) { return (*l < *r); } }; + //////////////////////////////// + // The Sass `@extend` directive. + //////////////////////////////// + class ExtendRule final : public Statement { + ADD_PROPERTY(bool, isOptional) + // This should be a simple selector only! + ADD_PROPERTY(SelectorListObj, selector) + ADD_PROPERTY(Selector_Schema_Obj, schema) + public: + ExtendRule(ParserState pstate, SelectorListObj s); + ExtendRule(ParserState pstate, Selector_Schema_Obj s); + ATTACH_AST_OPERATIONS(ExtendRule) + ATTACH_CRTP_PERFORM_METHODS() + }; } diff --git a/src/ast_supports.cpp b/src/ast_supports.cpp index 60136d63e..c2a1d095c 100644 --- a/src/ast_supports.cpp +++ b/src/ast_supports.cpp @@ -1,24 +1,8 @@ // sass.hpp must go before all system headers to get the // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" - #include "ast.hpp" -#include "context.hpp" -#include "node.hpp" -#include "eval.hpp" -#include "extend.hpp" -#include "emitter.hpp" -#include "color_maps.hpp" -#include "ast_fwd_decl.hpp" -#include -#include -#include -#include -#include -#include -#include - -#include "ast_values.hpp" + namespace Sass { diff --git a/src/ast_values.cpp b/src/ast_values.cpp index 8473e4104..c8fbcb7e1 100644 --- a/src/ast_values.cpp +++ b/src/ast_values.cpp @@ -1,24 +1,7 @@ // sass.hpp must go before all system headers to get the // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" - #include "ast.hpp" -#include "context.hpp" -#include "node.hpp" -#include "eval.hpp" -#include "extend.hpp" -#include "emitter.hpp" -#include "color_maps.hpp" -#include "ast_fwd_decl.hpp" -#include -#include -#include -#include -#include -#include -#include - -#include "ast_values.hpp" namespace Sass { @@ -85,6 +68,24 @@ namespace Sass { // don't set children } + bool List::operator< (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + if (length() < r->length()) return true; + if (length() > r->length()) return false; + const auto& left = elements(); + const auto& right = r->elements(); + for (size_t i = 0; i < left.size(); i += 1) { + if (*left[i] < *right[i]) return true; + if (*left[i] == *right[i]) continue; + return false; + } + return false; + } + // compare/sort by type + return type() < rhs.type(); + } + bool List::operator== (const Expression& rhs) const { if (auto r = Cast(&rhs)) { @@ -143,6 +144,31 @@ namespace Sass { Hashed(*ptr) { concrete_type(MAP); } + bool Map::operator< (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + if (length() < r->length()) return true; + if (length() > r->length()) return false; + const auto& lkeys = keys(); + const auto& rkeys = r->keys(); + for (size_t i = 0; i < lkeys.size(); i += 1) { + if (*lkeys[i] < *rkeys[i]) return true; + if (*lkeys[i] == *rkeys[i]) continue; + return false; + } + const auto& lvals = values(); + const auto& rvals = r->values(); + for (size_t i = 0; i < lvals.size(); i += 1) { + if (*lvals[i] < *rvals[i]) return true; + if (*lvals[i] == *rvals[i]) continue; + return false; + } + return false; + } + // compare/sort by type + return type() < rhs.type(); + } + bool Map::operator== (const Expression& rhs) const { if (auto r = Cast(&rhs)) { @@ -232,6 +258,17 @@ namespace Sass { is_delayed(delayed); } + bool Binary_Expression::operator<(const Expression& rhs) const + { + if (auto m = Cast(&rhs)) { + return type() < m->type() || + *left() < *m->left() || + *right() < *m->right(); + } + // compare/sort by type + return type() < rhs.type(); + } + bool Binary_Expression::operator==(const Expression& rhs) const { if (auto m = Cast(&rhs)) { @@ -263,6 +300,22 @@ namespace Sass { : Value(ptr), definition_(ptr->definition_), is_css_(ptr->is_css_) { concrete_type(FUNCTION_VAL); } + bool Function::operator< (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + auto d1 = Cast(definition()); + auto d2 = Cast(r->definition()); + if (d1 == nullptr) return d2 != nullptr; + else if (d2 == nullptr) return false; + if (is_css() == r->is_css()) { + return d1 < d2; + } + return r->is_css(); + } + // compare/sort by type + return type() < rhs.type(); + } + bool Function::operator== (const Expression& rhs) const { if (auto r = Cast(&rhs)) { @@ -436,6 +489,14 @@ namespace Sass { return hash_; } + bool Number::operator< (const Expression& rhs) const + { + if (auto n = Cast(&rhs)) { + return *this < *n; + } + return false; + } + bool Number::operator== (const Expression& rhs) const { if (auto n = Cast(&rhs)) { @@ -502,6 +563,21 @@ namespace Sass { hash_(ptr->hash_) { concrete_type(COLOR); } + bool Color::operator< (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + return *this < *r; + } + else if (auto r = Cast(&rhs)) { + return *this < *r; + } + else if (auto r = Cast(&rhs)) { + return a_ < r->a(); + } + // compare/sort by type + return type() < rhs.type(); + } + bool Color::operator== (const Expression& rhs) const { if (auto r = Cast(&rhs)) { @@ -531,6 +607,23 @@ namespace Sass { b_(ptr->b_) { concrete_type(COLOR); } + bool Color_RGBA::operator< (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + if (r_ < r->r()) return true; + if (r_ > r->r()) return false; + if (g_ < r->g()) return true; + if (g_ > r->g()) return false; + if (b_ < r->b()) return true; + if (b_ > r->b()) return false; + if (a_ < r->a()) return true; + if (a_ > r->a()) return false; + return false; // is equal + } + // compare/sort by type + return type() < rhs.type(); + } + bool Color_RGBA::operator== (const Expression& rhs) const { if (auto r = Cast(&rhs)) { @@ -616,6 +709,23 @@ namespace Sass { // hash_(ptr->hash_) { concrete_type(COLOR); } + bool Color_HSLA::operator< (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + if (h_ < r->h()) return true; + if (h_ > r->h()) return false; + if (s_ < r->s()) return true; + if (s_ > r->s()) return false; + if (l_ < r->l()) return true; + if (l_ > r->l()) return false; + if (a_ < r->a()) return true; + if (a_ > r->a()) return false; + return false; // is equal + } + // compare/sort by type + return type() < rhs.type(); + } + bool Color_HSLA::operator== (const Expression& rhs) const { if (auto r = Cast(&rhs)) { @@ -687,6 +797,15 @@ namespace Sass { : Value(ptr), message_(ptr->message_) { concrete_type(C_ERROR); } + bool Custom_Error::operator< (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + return message() < r->message(); + } + // compare/sort by type + return type() < rhs.type(); + } + bool Custom_Error::operator== (const Expression& rhs) const { if (auto r = Cast(&rhs)) { @@ -706,6 +825,15 @@ namespace Sass { : Value(ptr), message_(ptr->message_) { concrete_type(C_WARNING); } + bool Custom_Warning::operator< (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + return message() < r->message(); + } + // compare/sort by type + return type() < rhs.type(); + } + bool Custom_Warning::operator== (const Expression& rhs) const { if (auto r = Cast(&rhs)) { @@ -728,15 +856,23 @@ namespace Sass { hash_(ptr->hash_) { concrete_type(BOOLEAN); } - bool Boolean::operator== (const Expression& rhs) const + bool Boolean::operator< (const Expression& rhs) const { if (auto r = Cast(&rhs)) { - return (value() == r->value()); + return (value() < r->value()); } return false; } - size_t Boolean::hash() const + bool Boolean::operator== (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + return (value() == r->value()); + } + return false; + } + + size_t Boolean::hash() const { if (hash_ == 0) { hash_ = std::hash()(value_); @@ -784,6 +920,23 @@ namespace Sass { return length() && last()->is_right_interpolant(); } + bool String_Schema::operator< (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + if (length() < r->length()) return true; + if (length() > r->length()) return false; + for (size_t i = 0, L = length(); i < L; ++i) { + if (*get(i) < *r->get(i)) return true; + if (*get(i) == *r->get(i)) continue; + return false; + } + // Is equal + return false; + } + // compare/sort by type + return type() < rhs.type(); + } + bool String_Schema::operator== (const Expression& rhs) const { if (auto r = Cast(&rhs)) { @@ -824,22 +977,21 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////// String_Constant::String_Constant(ParserState pstate, std::string val, bool css) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(val, css)), hash_(0) + : String(pstate), quote_mark_(0), value_(read_css_string(val, css)), hash_(0) { } String_Constant::String_Constant(ParserState pstate, const char* beg, bool css) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg), css)), hash_(0) + : String(pstate), quote_mark_(0), value_(read_css_string(std::string(beg), css)), hash_(0) { } String_Constant::String_Constant(ParserState pstate, const char* beg, const char* end, bool css) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg, end-beg), css)), hash_(0) + : String(pstate), quote_mark_(0), value_(read_css_string(std::string(beg, end-beg), css)), hash_(0) { } String_Constant::String_Constant(ParserState pstate, const Token& tok, bool css) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(tok.begin, tok.end), css)), hash_(0) + : String(pstate), quote_mark_(0), value_(read_css_string(std::string(tok.begin, tok.end), css)), hash_(0) { } String_Constant::String_Constant(const String_Constant* ptr) : String(ptr), quote_mark_(ptr->quote_mark_), - can_compress_whitespace_(ptr->can_compress_whitespace_), value_(ptr->value_), hash_(ptr->hash_) { } @@ -848,11 +1000,24 @@ namespace Sass { return value_.empty() && quote_mark_ == 0; } + bool String_Constant::operator< (const Expression& rhs) const + { + if (auto qstr = Cast(&rhs)) { + return value() < qstr->value(); + } + else if (auto cstr = Cast(&rhs)) { + return value() < cstr->value(); + } + // compare/sort by type + return type() < rhs.type(); + } + bool String_Constant::operator== (const Expression& rhs) const { if (auto qstr = Cast(&rhs)) { return value() == qstr->value(); - } else if (auto cstr = Cast(&rhs)) { + } + else if (auto cstr = Cast(&rhs)) { return value() == cstr->value(); } return false; @@ -894,11 +1059,24 @@ namespace Sass { : String_Constant(ptr) { } + bool String_Quoted::operator< (const Expression& rhs) const + { + if (auto qstr = Cast(&rhs)) { + return value() < qstr->value(); + } + else if (auto cstr = Cast(&rhs)) { + return value() < cstr->value(); + } + // compare/sort by type + return type() < rhs.type(); + } + bool String_Quoted::operator== (const Expression& rhs) const { if (auto qstr = Cast(&rhs)) { return value() == qstr->value(); - } else if (auto cstr = Cast(&rhs)) { + } + else if (auto cstr = Cast(&rhs)) { return value() == cstr->value(); } return false; @@ -919,9 +1097,18 @@ namespace Sass { Null::Null(const Null* ptr) : Value(ptr) { concrete_type(NULL_VAL); } + bool Null::operator< (const Expression& rhs) const + { + if (Cast(&rhs)) { + return false; + } + // compare/sort by type + return type() < rhs.type(); + } + bool Null::operator== (const Expression& rhs) const { - return Cast(&rhs) != NULL; + return Cast(&rhs) != nullptr; } size_t Null::hash() const diff --git a/src/ast_values.hpp b/src/ast_values.hpp index f95bb0274..b9f9d087e 100644 --- a/src/ast_values.hpp +++ b/src/ast_values.hpp @@ -4,35 +4,7 @@ // sass.hpp must go before all system headers to get the // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include "sass/base.h" -#include "ast_fwd_decl.hpp" - -#include "util.hpp" -#include "units.hpp" -#include "context.hpp" -#include "position.hpp" -#include "constants.hpp" -#include "operation.hpp" -#include "position.hpp" -#include "inspect.hpp" -#include "source_map.hpp" -#include "environment.hpp" -#include "error_handling.hpp" -#include "ast_def_macros.hpp" -#include "ast_fwd_decl.hpp" -#include "source_map.hpp" -#include "fn_utils.hpp" - -#include "sass.h" +#include "ast.hpp" namespace Sass { @@ -52,8 +24,23 @@ namespace Sass { class Value : public PreValue { public: Value(ParserState pstate, bool d = false, bool e = false, bool i = false, Type ct = NONE); - ATTACH_VIRTUAL_AST_OPERATIONS(Value); + + // Some obects are not meant to be compared + // ToDo: maybe fallback to pointer comparison? + virtual bool operator< (const Expression& rhs) const override = 0; virtual bool operator== (const Expression& rhs) const override = 0; + + // We can give some reasonable implementations by using + // inverst operators on the specialized implementations + virtual bool operator> (const Expression& rhs) const { + return rhs < *this; + } + virtual bool operator!= (const Expression& rhs) const { + return !(*this == rhs); + } + + ATTACH_VIRTUAL_AST_OPERATIONS(Value); + }; /////////////////////////////////////////////////////////////////////// @@ -81,6 +68,8 @@ namespace Sass { virtual size_t hash() const override; virtual size_t size() const; virtual void set_delayed(bool delayed) override; + + virtual bool operator< (const Expression& rhs) const override; virtual bool operator== (const Expression& rhs) const override; ATTACH_AST_OPERATIONS(List) @@ -90,7 +79,7 @@ namespace Sass { /////////////////////////////////////////////////////////////////////// // Key value paris. /////////////////////////////////////////////////////////////////////// - class Map : public Value, public Hashed { + class Map : public Value, public Hashed { void adjust_after_pushing(std::pair p) override { is_expanded(false); } public: Map(ParserState pstate, size_t size = 0); @@ -100,6 +89,8 @@ namespace Sass { List_Obj to_list(ParserState& pstate); virtual size_t hash() const override; + + virtual bool operator< (const Expression& rhs) const override; virtual bool operator== (const Expression& rhs) const override; ATTACH_AST_OPERATIONS(Map) @@ -130,6 +121,7 @@ namespace Sass { virtual void set_delayed(bool delayed) override; + virtual bool operator< (const Expression& rhs) const override; virtual bool operator==(const Expression& rhs) const override; virtual size_t hash() const override; @@ -154,6 +146,7 @@ namespace Sass { std::string name(); + bool operator< (const Expression& rhs) const override; bool operator== (const Expression& rhs) const override; ATTACH_AST_OPERATIONS(Function) @@ -230,6 +223,7 @@ namespace Sass { bool operator< (const Number& rhs) const; bool operator== (const Number& rhs) const; + bool operator< (const Expression& rhs) const override; bool operator== (const Expression& rhs) const override; ATTACH_AST_OPERATIONS(Number) ATTACH_CRTP_PERFORM_METHODS() @@ -251,6 +245,7 @@ namespace Sass { virtual size_t hash() const override = 0; + bool operator< (const Expression& rhs) const override; bool operator== (const Expression& rhs) const override; virtual Color_RGBA* copyAsRGBA() const = 0; @@ -283,6 +278,7 @@ namespace Sass { Color_HSLA* copyAsHSLA() const override; Color_HSLA* toHSLA() override { return copyAsHSLA(); } + bool operator< (const Expression& rhs) const override; bool operator== (const Expression& rhs) const override; ATTACH_AST_OPERATIONS(Color_RGBA) @@ -311,6 +307,7 @@ namespace Sass { Color_HSLA* copyAsHSLA() const override; Color_HSLA* toHSLA() override { return this; } + bool operator< (const Expression& rhs) const override; bool operator== (const Expression& rhs) const override; ATTACH_AST_OPERATIONS(Color_HSLA) @@ -324,6 +321,7 @@ namespace Sass { ADD_CONSTREF(std::string, message) public: Custom_Error(ParserState pstate, std::string msg); + bool operator< (const Expression& rhs) const override; bool operator== (const Expression& rhs) const override; ATTACH_AST_OPERATIONS(Custom_Error) ATTACH_CRTP_PERFORM_METHODS() @@ -336,6 +334,7 @@ namespace Sass { ADD_CONSTREF(std::string, message) public: Custom_Warning(ParserState pstate, std::string msg); + bool operator< (const Expression& rhs) const override; bool operator== (const Expression& rhs) const override; ATTACH_AST_OPERATIONS(Custom_Warning) ATTACH_CRTP_PERFORM_METHODS() @@ -358,6 +357,7 @@ namespace Sass { bool is_false() override { return !value_; } + bool operator< (const Expression& rhs) const override; bool operator== (const Expression& rhs) const override; ATTACH_AST_OPERATIONS(Boolean) @@ -377,6 +377,9 @@ namespace Sass { virtual bool operator<(const Expression& rhs) const override { return this->to_string() < rhs.to_string(); }; + virtual bool operator==(const Expression& rhs) const override { + return this->to_string() == rhs.to_string(); + }; ATTACH_VIRTUAL_AST_OPERATIONS(String); ATTACH_CRTP_PERFORM_METHODS() }; @@ -403,6 +406,7 @@ namespace Sass { size_t hash() const override; virtual void set_delayed(bool delayed) override; + bool operator< (const Expression& rhs) const override; bool operator==(const Expression& rhs) const override; ATTACH_AST_OPERATIONS(String_Schema) ATTACH_CRTP_PERFORM_METHODS() @@ -413,7 +417,6 @@ namespace Sass { //////////////////////////////////////////////////////// class String_Constant : public String { ADD_PROPERTY(char, quote_mark) - ADD_PROPERTY(bool, can_compress_whitespace) HASH_CONSTREF(std::string, value) protected: mutable size_t hash_; @@ -427,6 +430,7 @@ namespace Sass { bool is_invisible() const override; virtual void rtrim() override; size_t hash() const override; + bool operator< (const Expression& rhs) const override; bool operator==(const Expression& rhs) const override; // quotes are forced on inspection virtual std::string inspect() const override; @@ -442,6 +446,7 @@ namespace Sass { String_Quoted(ParserState pstate, std::string val, char q = 0, bool keep_utf8_escapes = false, bool skip_unquoting = false, bool strict_unquoting = true, bool css = true); + bool operator< (const Expression& rhs) const override; bool operator==(const Expression& rhs) const override; // quotes are forced on inspection std::string inspect() const override; @@ -463,6 +468,7 @@ namespace Sass { size_t hash() const override; + bool operator< (const Expression& rhs) const override; bool operator== (const Expression& rhs) const override; ATTACH_AST_OPERATIONS(Null) @@ -477,8 +483,11 @@ namespace Sass { Parent_Reference(ParserState pstate); std::string type() const override { return "parent"; } static std::string type_name() { return "parent"; } + bool operator< (const Expression& rhs) const override { + return false; // they are always equal + } bool operator==(const Expression& rhs) const override { - return true; // can they ever be not equal? + return true; // they are always equal }; ATTACH_AST_OPERATIONS(Parent_Reference) ATTACH_CRTP_PERFORM_METHODS() diff --git a/src/cencode.c b/src/cencode.c index 9109f4b22..3932fcc3e 100644 --- a/src/cencode.c +++ b/src/cencode.c @@ -46,9 +46,8 @@ int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, result = (fragment & 0x0fc) >> 2; *codechar++ = base64_encode_value(result); result = (fragment & 0x003) << 4; - #ifndef _MSC_VER - /* fall through */ - #endif + /* fall through */ + case step_B: if (plainchar == plaintextend) { @@ -60,9 +59,8 @@ int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, result |= (fragment & 0x0f0) >> 4; *codechar++ = base64_encode_value(result); result = (fragment & 0x00f) << 2; - #ifndef _MSC_VER - /* fall through */ - #endif + /* fall through */ + case step_C: if (plainchar == plaintextend) { diff --git a/src/check_nesting.cpp b/src/check_nesting.cpp index b6ab1faa2..649b8ae1f 100644 --- a/src/check_nesting.cpp +++ b/src/check_nesting.cpp @@ -1,9 +1,7 @@ // sass.hpp must go before all system headers to get the // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" - -#include - +#include "ast.hpp" #include "check_nesting.hpp" namespace Sass { @@ -145,7 +143,7 @@ namespace Sass { if (is_charset(node)) { this->invalid_charset_parent(this->parent, node); } - if (Cast(node)) + if (Cast(node)) { this->invalid_extend_parent(this->parent, node); } // if (Cast(node)) @@ -388,7 +386,8 @@ namespace Sass { { return Cast(n) || Cast(n) || - Cast(n) || - Cast(n); + Cast(n) || + Cast(n) || + Cast(n); } } diff --git a/src/check_nesting.hpp b/src/check_nesting.hpp index 8a9d19c2e..6a04bfe97 100644 --- a/src/check_nesting.hpp +++ b/src/check_nesting.hpp @@ -1,8 +1,12 @@ #ifndef SASS_CHECK_NESTING_H #define SASS_CHECK_NESTING_H +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" #include "ast.hpp" #include "operation.hpp" +#include namespace Sass { diff --git a/src/constants.cpp b/src/constants.cpp index aa8eddf27..8d9b64a4a 100644 --- a/src/constants.cpp +++ b/src/constants.cpp @@ -45,6 +45,7 @@ namespace Sass { extern const char for_kwd[] = "@for"; extern const char from_kwd[] = "from"; extern const char to_kwd[] = "to"; + extern const char of_kwd[] = "of"; extern const char through_kwd[] = "through"; extern const char each_kwd[] = "@each"; extern const char in_kwd[] = "in"; @@ -161,6 +162,10 @@ namespace Sass { extern const char uri_chars[] = ":;/?!%&#@|[]{}'`^\"*+-.,_=~"; extern const char real_uri_chars[] = "#%&"; + extern const char selector_combinator_child[] = ">"; + extern const char selector_combinator_general[] = "~"; + extern const char selector_combinator_adjacent[] = "+"; + // some specific constant character classes // they must be static to be useable by lexer extern const char static_ops[] = "*/%"; diff --git a/src/constants.hpp b/src/constants.hpp index 7297e5dfa..81ada2749 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -43,6 +43,7 @@ namespace Sass { extern const char for_kwd[]; extern const char from_kwd[]; extern const char to_kwd[]; + extern const char of_kwd[]; extern const char through_kwd[]; extern const char each_kwd[]; extern const char in_kwd[]; @@ -161,6 +162,11 @@ namespace Sass { extern const char uri_chars[]; extern const char real_uri_chars[]; + // constants for selector combinators + extern const char selector_combinator_child[]; + extern const char selector_combinator_general[]; + extern const char selector_combinator_adjacent[]; + // some specific constant character classes // they must be static to be useable by lexer extern const char static_ops[]; diff --git a/src/context.cpp b/src/context.cpp index 088a70d3f..df5f4a34d 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -1,44 +1,22 @@ // sass.hpp must go before all system headers to get the // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" - -#include -#include -#include -#include -#include -#include - #include "ast.hpp" -#include "util.hpp" -#include "sass.h" -#include "context.hpp" -#include "plugins.hpp" -#include "constants.hpp" -#include "parser.hpp" -#include "file.hpp" -#include "inspect.hpp" -#include "output.hpp" -#include "expand.hpp" -#include "eval.hpp" -#include "check_nesting.hpp" -#include "cssize.hpp" -#include "listize.hpp" -#include "extend.hpp" + #include "remove_placeholders.hpp" #include "sass_functions.hpp" -#include "backtrace.hpp" -#include "sass2scss.h" -#include "prelexer.hpp" -#include "emitter.hpp" -#include "fn_utils.hpp" +#include "check_nesting.hpp" +#include "fn_selectors.hpp" +#include "fn_strings.hpp" +#include "fn_numbers.hpp" +#include "fn_colors.hpp" #include "fn_miscs.hpp" -#include "fn_maps.hpp" #include "fn_lists.hpp" -#include "fn_colors.hpp" -#include "fn_numbers.hpp" -#include "fn_strings.hpp" -#include "fn_selectors.hpp" +#include "fn_maps.hpp" +#include "context.hpp" +#include "expand.hpp" +#include "parser.hpp" +#include "cssize.hpp" namespace Sass { using namespace Constants; @@ -81,10 +59,10 @@ namespace Sass { strings(), resources(), sheets(), - subset_map(), import_stack(), callee_stack(), traces(), + extender(Extender::NORMAL, traces), c_compiler(NULL), c_headers (std::vector()), @@ -160,7 +138,7 @@ namespace Sass { } // clear inner structures (vectors) and input source resources.clear(); import_stack.clear(); - subset_map.clear(), sheets.clear(); + sheets.clear(); } Data_Context::~Data_Context() @@ -649,8 +627,6 @@ namespace Sass { return compile(); } - - // parse root block from includes Block_Obj Context::compile() { @@ -678,23 +654,23 @@ namespace Sass { } // expand and eval the tree root = expand(root); + + Extension unsatisfied; + // check that all extends were used + if (extender.checkForUnsatisfiedExtends(unsatisfied)) { + throw Exception::UnsatisfiedExtend(traces, unsatisfied); + } + // check nesting check_nesting(root); // merge and bubble certain rules root = cssize(root); - // should we extend something? - if (!subset_map.empty()) { - // create crtp visitor object - Extend extend(subset_map); - extend.setEval(expand.eval); - // extend tree nodes - extend(root); - } // clean up by removing empty placeholders // ToDo: maybe we can do this somewhere else? Remove_Placeholders remove_placeholders; root->perform(&remove_placeholders); + // return processed tree return root; } diff --git a/src/context.hpp b/src/context.hpp index d18cd3817..1d25112e1 100644 --- a/src/context.hpp +++ b/src/context.hpp @@ -1,27 +1,19 @@ #ifndef SASS_CONTEXT_H #define SASS_CONTEXT_H -#include -#include -#include +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include "ast.hpp" + #define BUFFERSIZE 255 #include "b64/encode.h" -#include "ast_fwd_decl.hpp" -#include "kwd_arg_macros.hpp" -#include "ast_fwd_decl.hpp" #include "sass_context.hpp" -#include "environment.hpp" -#include "source_map.hpp" -#include "subset_map.hpp" -#include "backtrace.hpp" -#include "output.hpp" +#include "stylesheet.hpp" #include "plugins.hpp" -#include "file.hpp" - - -struct Sass_Function; +#include "output.hpp" namespace Sass { @@ -52,10 +44,10 @@ namespace Sass { std::vector strings; std::vector resources; std::map sheets; - Subset_Map subset_map; ImporterStack import_stack; std::vector callee_stack; std::vector traces; + Extender extender; struct Sass_Compiler* c_compiler; @@ -68,10 +60,6 @@ namespace Sass { std::vector plugin_paths; // relative paths to load plugins std::vector include_paths; // lookup paths for includes - - - - void apply_custom_headers(Block_Obj root, const char* path, ParserState pstate); std::vector c_headers; diff --git a/src/cssize.cpp b/src/cssize.cpp index 48ec8406a..0e131e845 100644 --- a/src/cssize.cpp +++ b/src/cssize.cpp @@ -12,8 +12,7 @@ namespace Sass { Cssize::Cssize(Context& ctx) - : ctx(ctx), - traces(ctx.traces), + : traces(ctx.traces), block_stack(BlockStack()), p_stack(std::vector()) { } @@ -215,20 +214,23 @@ namespace Sass { return 0; } - Statement* Cssize::operator()(Media_Block* m) + Statement* Cssize::operator()(CssMediaRule* m) { if (parent()->statement_type() == Statement::RULESET) - { return bubble(m); } + { + return bubble(m); + } if (parent()->statement_type() == Statement::MEDIA) - { return SASS_MEMORY_NEW(Bubble, m->pstate(), m); } + { + return SASS_MEMORY_NEW(Bubble, m->pstate(), m); + } p_stack.push_back(m); - Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, - m->pstate(), - m->media_queries(), - operator()(m->block())); + CssMediaRuleObj mm = SASS_MEMORY_NEW(CssMediaRule, m->pstate(), m->block()); + mm->concat(m->elements()); + mm->block(operator()(m->block())); mm->tabs(m->tabs()); p_stack.pop_back(); @@ -352,24 +354,24 @@ namespace Sass { return bubble; } - Statement* Cssize::bubble(Media_Block* m) + Statement* Cssize::bubble(CssMediaRule* m) { Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); Block* bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); Ruleset* new_rule = SASS_MEMORY_NEW(Ruleset, - parent->pstate(), - parent->selector(), - bb); + parent->pstate(), + parent->selector(), + bb); new_rule->tabs(parent->tabs()); new_rule->block()->concat(m->block()); Block* wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); wrapper_block->append(new_rule); - Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, - m->pstate(), - m->media_queries(), - wrapper_block); + CssMediaRuleObj mm = SASS_MEMORY_NEW(CssMediaRule, + m->pstate(), + wrapper_block); + mm->concat(m->elements()); mm->tabs(m->tabs()); @@ -381,12 +383,12 @@ namespace Sass { return Cast(s) || s->bubbles(); } - Block* Cssize::flatten(Block* b) + Block* Cssize::flatten(const Block* b) { Block* result = SASS_MEMORY_NEW(Block, b->pstate(), 0, b->is_root()); for (size_t i = 0, L = b->length(); i < L; ++i) { Statement* ss = b->at(i); - if (Block* bb = Cast(ss)) { + if (const Block* bb = Cast(ss)) { Block_Obj bs = flatten(bb); for (size_t j = 0, K = bs->length(); j < K; ++j) { result->append(bs->at(j)); @@ -440,7 +442,7 @@ namespace Sass { previous_parent->block()->concat(slice); } else { - previous_parent = Cast(SASS_MEMORY_COPY(parent)); + previous_parent = SASS_MEMORY_COPY(parent); previous_parent->block(slice); previous_parent->tabs(parent->tabs()); @@ -451,36 +453,24 @@ namespace Sass { for (size_t j = 0, K = slice->length(); j < K; ++j) { - Statement* ss; + Statement_Obj ss; Statement_Obj stm = slice->at(j); // this has to go now here (too bad) Bubble_Obj node = Cast(stm); - Media_Block* m1 = NULL; - Media_Block* m2 = NULL; - if (parent) m1 = Cast(parent); - if (node) m2 = Cast(node->node()); - if (!parent || - parent->statement_type() != Statement::MEDIA || - node->node()->statement_type() != Statement::MEDIA || - (m1 && m2 && *m1->media_queries() == *m2->media_queries()) - ) - { - ss = node->node(); - } - else - { - List_Obj mq = merge_media_queries( - Cast(node->node()), - Cast(parent) - ); - if (!mq->length()) continue; - if (Media_Block* b = Cast(node->node())) { - b->media_queries(mq); - } + + CssMediaRule* rule1 = NULL; + CssMediaRule* rule2 = NULL; + if (parent) rule1 = Cast(parent); + if (node) rule2 = Cast(node->node()); + if (rule1 || rule2) { ss = node->node(); } - if (!ss) continue; + ss = node->node(); + + if (!ss) { + continue; + } ss->tabs(ss->tabs() + node->tabs()); ss->group_end(node->group_end()); @@ -516,7 +506,7 @@ namespace Sass { { for (size_t i = 0, L = b->length(); i < L; ++i) { Statement_Obj ith = b->at(i)->perform(this); - if (Block* bb = Cast(ith)) { + if (Block_Obj bb = Cast(ith)) { for (size_t j = 0, K = bb->length(); j < K; ++j) { cur->append(bb->at(j)); } @@ -527,78 +517,4 @@ namespace Sass { } } - List* Cssize::merge_media_queries(Media_Block* m1, Media_Block* m2) - { - List* qq = SASS_MEMORY_NEW(List, - m1->media_queries()->pstate(), - m1->media_queries()->length(), - SASS_COMMA); - - for (size_t i = 0, L = m1->media_queries()->length(); i < L; i++) { - for (size_t j = 0, K = m2->media_queries()->length(); j < K; j++) { - Expression_Obj l1 = m1->media_queries()->at(i); - Expression_Obj l2 = m2->media_queries()->at(j); - Media_Query* mq1 = Cast(l1); - Media_Query* mq2 = Cast(l2); - Media_Query* mq = merge_media_query(mq1, mq2); - if (mq) qq->append(mq); - } - } - - return qq; - } - - - Media_Query* Cssize::merge_media_query(Media_Query* mq1, Media_Query* mq2) - { - - std::string type; - std::string mod; - - std::string m1 = std::string(mq1->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); - std::string t1 = mq1->media_type() ? mq1->media_type()->to_string(ctx.c_options) : ""; - std::string m2 = std::string(mq2->is_restricted() ? "only" : mq2->is_negated() ? "not" : ""); - std::string t2 = mq2->media_type() ? mq2->media_type()->to_string(ctx.c_options) : ""; - - - if (t1.empty()) t1 = t2; - if (t2.empty()) t2 = t1; - - if ((m1 == "not") ^ (m2 == "not")) { - if (t1 == t2) { - return 0; - } - type = m1 == "not" ? t2 : t1; - mod = m1 == "not" ? m2 : m1; - } - else if (m1 == "not" && m2 == "not") { - if (t1 != t2) { - return 0; - } - type = t1; - mod = "not"; - } - else if (t1 != t2) { - return 0; - } else { - type = t1; - mod = m1.empty() ? m2 : m1; - } - - Media_Query* mm = SASS_MEMORY_NEW(Media_Query, - mq1->pstate(), - {}, - mq1->length() + mq2->length(), - mod == "not", - mod == "only"); - - if (!type.empty()) { - mm->media_type(SASS_MEMORY_NEW(String_Quoted, mq1->pstate(), type)); - } - - mm->concat(mq2); - mm->concat(mq1); - - return mm; - } } diff --git a/src/cssize.hpp b/src/cssize.hpp index 6742826a5..bc4aedd9d 100644 --- a/src/cssize.hpp +++ b/src/cssize.hpp @@ -12,7 +12,6 @@ namespace Sass { class Cssize : public Operation_CRTP { - Context& ctx; Backtraces& traces; BlockStack block_stack; std::vector p_stack; @@ -21,12 +20,10 @@ namespace Sass { Cssize(Context&); ~Cssize() { } - Selector_List* selector(); - Block* operator()(Block*); Statement* operator()(Ruleset*); // Statement* operator()(Bubble*); - Statement* operator()(Media_Block*); + Statement* operator()(CssMediaRule*); Statement* operator()(Supports_Block*); Statement* operator()(At_Root_Block*); Statement* operator()(Directive*); @@ -44,7 +41,7 @@ namespace Sass { // Statement* operator()(Each*); // Statement* operator()(While*); // Statement* operator()(Return*); - // Statement* operator()(Extension*); + // Statement* operator()(ExtendRule*); // Statement* operator()(Definition*); // Statement* operator()(Mixin_Call*); // Statement* operator()(Content*); @@ -54,16 +51,13 @@ namespace Sass { std::vector> slice_by_bubble(Block*); Statement* bubble(Directive*); Statement* bubble(At_Root_Block*); - Statement* bubble(Media_Block*); + Statement* bubble(CssMediaRule*); Statement* bubble(Supports_Block*); Block* debubble(Block* children, Statement* parent = 0); - Block* flatten(Block*); + Block* flatten(const Block*); bool bubblable(Statement*); - List* merge_media_queries(Media_Block*, Media_Block*); - Media_Query* merge_media_query(Media_Query*, Media_Query*); - // generic fallback template Statement* fallback(U x) diff --git a/src/dart_helpers.hpp b/src/dart_helpers.hpp new file mode 100644 index 000000000..d5bd75941 --- /dev/null +++ b/src/dart_helpers.hpp @@ -0,0 +1,199 @@ +#ifndef SASS_DART_HELPERS_H +#define SASS_DART_HELPERS_H + +#include +#include +#include +#include + +namespace Sass { + + // ########################################################################## + // Flatten `vector>` to `vector` + // ########################################################################## + template + T flatten(const std::vector& all) + { + T flattened; + for (const auto& sub : all) { + std::copy(std::begin(sub), std::end(sub), + std::back_inserter(flattened)); + } + return flattened; + } + + // ########################################################################## + // Expands each element of this Iterable into zero or more elements. + // Calls a function on every element and ads all results to flat array + // ########################################################################## + // Equivalent to dart `cnt.any` + // Pass additional closure variables to `fn` + template + T expand(const T& cnt, U fn, Args... args) { + T flattened; + for (const auto& sub : cnt) { + auto rv = fn(sub, args...); + flattened.insert(flattened.end(), + rv.begin(), rv.end()); + } + return flattened; + } + + // ########################################################################## + // ########################################################################## + template + T flattenInner(const std::vector& vec) + { + T outer; + for (const auto& sub : vec) { + outer.emplace_back(std::move(flatten(sub))); + } + return outer; + } + // EO flattenInner + + // ########################################################################## + // Equivalent to dart `cnt.any` + // Pass additional closure variables to `fn` + // ########################################################################## + template + bool hasAny(const T& cnt, U fn, Args... args) { + for (const auto& sub : cnt) { + if (fn(sub, args...)) { + return true; + } + } + return false; + } + // EO hasAny + + // ########################################################################## + // Equivalent to dart `cnt.take(len).any` + // Pass additional closure variables to `fn` + // ########################################################################## + template + bool hasSubAny(const T& cnt, size_t len, U fn, Args... args) { + for (size_t i = 0; i < len; i++) { + if (fn(cnt[i], args...)) { + return true; + } + } + return false; + } + + // ########################################################################## + // Default predicate for lcs algorithm + // ########################################################################## + template + inline bool lcsIdentityCmp(const T& X, const T& Y, T& result) + { + // Assert equality + if (!ObjEqualityFn(X, Y)) { + return false; + } + // Store in reference + result = X; + // Return success + return true; + } + // EO lcsIdentityCmp + + // ########################################################################## + // Longest common subsequence with predicate + // ########################################################################## + template + std::vector lcs( + const std::vector& X, const std::vector& Y, + bool(*select)(const T&, const T&, T&) = lcsIdentityCmp) + { + + std::size_t m = X.size(), mm = X.size() + 1; + std::size_t n = Y.size(), nn = Y.size() + 1; + + if (m == 0) return {}; + if (n == 0) return {}; + + // MSVC does not support variable-length arrays + // To circumvent, allocate one array on the heap + // Then use a macro to access via double index + // e.g. `size_t L[m][n]` is supported by gcc + size_t* len = new size_t[mm * nn + 1]; + bool* acc = new bool[mm * nn + 1]; + T* res = new T[mm * nn + 1]; + + #define LEN(x, y) len[(x) * nn + (y)] + #define ACC(x, y) acc[(x) * nn + (y)] + #define RES(x, y) res[(x) * nn + (y)] + + /* Following steps build L[m+1][n+1] in bottom up fashion. Note + that L[i][j] contains length of LCS of X[0..i-1] and Y[0..j-1] */ + for (size_t i = 0; i <= m; i++) { + for (size_t j = 0; j <= n; j++) { + if (i == 0 || j == 0) + LEN(i, j) = 0; + else { + ACC(i - 1, j - 1) = select(X[i - 1], Y[j - 1], RES(i - 1, j - 1)); + if (ACC(i - 1, j - 1)) + LEN(i, j) = LEN(i - 1, j - 1) + 1; + else + LEN(i, j) = std::max(LEN(i - 1, j), LEN(i, j - 1)); + } + } + } + + // Following code is used to print LCS + std::vector lcs; + std::size_t index = LEN(m, n); + lcs.reserve(index); + + // Start from the right-most-bottom-most corner + // and one by one store objects in lcs[] + std::size_t i = m, j = n; + while (i > 0 && j > 0) { + + // If current objects in X[] and Y are same, + // then current object is part of LCS + if (ACC(i - 1, j - 1)) + { + // Put the stored object in result + // Note: we push instead of unshift + // Note: reverse the vector later + // ToDo: is deque more performant? + lcs.push_back(RES(i - 1, j - 1)); + // reduce values of i, j and index + i -= 1; j -= 1; index -= 1; + } + + // If not same, then find the larger of two and + // go in the direction of larger value + else if (LEN(i - 1, j) > LEN(i, j - 1)) { + i--; + } + else { + j--; + } + + } + + // reverse now as we used push_back + std::reverse(lcs.begin(), lcs.end()); + + // Delete temp memory on heap + delete[] len; + delete[] acc; + delete[] res; + + #undef LEN + #undef ACC + #undef RES + + return lcs; + } + // EO lcs + + // ########################################################################## + // ########################################################################## + +} + +#endif diff --git a/src/debugger.hpp b/src/debugger.hpp index 53b0668e5..62a645e53 100644 --- a/src/debugger.hpp +++ b/src/debugger.hpp @@ -1,27 +1,286 @@ #ifndef SASS_DEBUGGER_H #define SASS_DEBUGGER_H +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include +#include #include #include -#include "node.hpp" +#include "ast.hpp" #include "ast_fwd_decl.hpp" +#include "extension.hpp" + +#include "ordered_map.hpp" using namespace Sass; inline void debug_ast(AST_Node* node, std::string ind = "", Env* env = 0); -inline void debug_ast(const AST_Node* node, std::string ind = "", Env* env = 0) { - debug_ast(const_cast(node), ind, env); +inline std::string debug_vec(const AST_Node* node) { + if (node == NULL) return "null"; + else return node->to_string(); } -inline void debug_sources_set(ComplexSelectorSet& set, std::string ind = "") -{ - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - for(auto const &pair : set) { - debug_ast(pair, ind + ""); - // debug_ast(set[pair], ind + "first: "); +inline std::string debug_dude(std::vector> vec) { + std::stringstream out; + out << "{"; + bool joinOut = false; + for (auto ct : vec) { + if (joinOut) out << ", "; + joinOut = true; + out << "{"; + bool joinIn = false; + for (auto nr : ct) { + if (joinIn) out << ", "; + joinIn = true; + out << nr; + } + out << "}"; + } + out << "}"; + return out.str(); +} + +inline std::string debug_vec(std::string& str) { + return str; +} + +inline std::string debug_vec(Extension& ext) { + std::stringstream out; + out << debug_vec(ext.extender); + out << " {@extend "; + out << debug_vec(ext.target); + if (ext.isOptional) { + out << " !optional"; + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_vec(std::vector vec) { + std::stringstream out; + out << "["; + for (size_t i = 0; i < vec.size(); i += 1) { + if (i > 0) out << ", "; + out << debug_vec(vec[i]); + } + out << "]"; + return out.str(); +} + +template +inline std::string debug_vec(std::queue vec) { + std::stringstream out; + out << "{"; + for (size_t i = 0; i < vec.size(); i += 1) { + if (i > 0) out << ", "; + out << debug_vec(vec[i]); + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_vec(std::map vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(it->first) // string (key) + << ": " + << debug_vec(it->second); // string's value + joinit = true; + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_vec(const ordered_map& vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(*it); // string (key) + // << debug_vec(it->second); // string's value + joinit = true; + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_vec(std::unordered_map vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(it->first) // string (key) + << ": " + << debug_vec(it->second); // string's value + joinit = true; + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_keys(std::unordered_map vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(it->first); // string (key) + joinit = true; } - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + out << "}"; + return out.str(); +} + +inline std::string debug_vec(ExtListSelSet& vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(*it); // string (key) + joinit = true; + } + out << "}"; + return out.str(); +} + +/* +template +inline std::string debug_values(tsl::ordered_map vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(const_cast(it->second)); // string's value + joinit = true; + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_vec(tsl::ordered_map vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(it->first) // string (key) + << ": " + << debug_vec(const_cast(it->second)); // string's value + joinit = true; + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_vals(tsl::ordered_map vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(const_cast(it->second)); // string's value + joinit = true; + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_keys(tsl::ordered_map vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(it->first); + joinit = true; + } + out << "}"; + return out.str(); +} +*/ + +template +inline std::string debug_vec(std::set vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto item : vec) { + if (joinit) out << ", "; + out << debug_vec(item); + joinit = true; + } + out << "}"; + return out.str(); +} + +/* +template +inline std::string debug_vec(tsl::ordered_set vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto item : vec) { + if (joinit) out << ", "; + out << debug_vec(item); + joinit = true; + } + out << "}"; + return out.str(); +} +*/ + +template +inline std::string debug_vec(std::unordered_set vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto item : vec) { + if (joinit) out << ", "; + out << debug_vec(item); + joinit = true; + } + out << "}"; + return out.str(); +} + +inline std::string debug_bool(bool val) { + return val ? "true" : "false"; +} +inline std::string debug_vec(ExtSmplSelSet* node) { + if (node == NULL) return "null"; + else return debug_vec(*node); +} + +inline void debug_ast(const AST_Node* node, std::string ind = "", Env* env = 0) { + debug_ast(const_cast(node), ind, env); } inline std::string str_replace(std::string str, const std::string& oldStr, const std::string& newStr) @@ -53,7 +312,7 @@ inline std::string pstate_source_position(AST_Node* node) std::stringstream str; Position start(node->pstate()); Position end(start + node->pstate().offset); - str << (start.file == std::string::npos ? -1 : start.file) + str << (start.file == std::string::npos ? 99999999 : start.file) << "@[" << start.line << ":" << start.column << "]" << "-[" << end.line << ":" << end.column << "]"; #ifdef DEBUG_SHARED_PTR @@ -90,127 +349,95 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) std::cerr << std::endl; debug_ast(root_block->expression(), ind + ":", env); debug_ast(root_block->block(), ind + " ", env); - } else if (Cast(node)) { - Selector_List* selector = Cast(node); - std::cerr << ind << "Selector_List " << selector; + } else if (Cast(node)) { + SelectorList* selector = Cast(node); + std::cerr << ind << "SelectorList " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; - std::cerr << " [@media:" << selector->media_block() << "]"; - std::cerr << (selector->is_invisible() ? " [INVISIBLE]": " -"); - std::cerr << (selector->has_placeholder() ? " [PLACEHOLDER]": " -"); - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << (selector->is_invisible() ? " [is_invisible]" : " -"); + std::cerr << (selector->isInvisible() ? " [isInvisible]" : " -"); + std::cerr << (selector->has_real_parent_ref() ? " [real-parent]": " -"); std::cerr << std::endl; - debug_ast(selector->schema(), ind + "#{} "); - for(const Complex_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + for(const ComplexSelector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } -// } else if (Cast(node)) { -// Expression* expression = Cast(node); -// std::cerr << ind << "Expression " << expression << " " << expression->concrete_type() << std::endl; + } else if (Cast(node)) { + ComplexSelector* selector = Cast(node); + std::cerr << ind << "ComplexSelector " << selector + << " (" << pstate_source_position(node) << ")" + << " <" << selector->hash() << ">" + << " [" << (selector->chroots() ? "CHROOT" : "CONNECT") << "]" + << " [length:" << longToHex(selector->length()) << "]" + << " [weight:" << longToHex(selector->specificity()) << "]" + << (selector->is_invisible() ? " [is_invisible]" : " -") + << (selector->isInvisible() ? " [isInvisible]" : " -") + << (selector->hasPreLineFeed() ? " [hasPreLineFeed]" : " -") - } else if (Cast(node)) { - Parent_Reference* selector = Cast(node); - std::cerr << ind << "Parent_Reference " << selector; -// if (selector->not_selector()) cerr << " [in_declaration]"; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; -// debug_ast(selector->selector(), ind + "->", env); + // << (selector->is_invisible() ? " [INVISIBLE]": " -") + // << (selector->has_placeholder() ? " [PLACEHOLDER]": " -") + // << (selector->is_optional() ? " [is_optional]": " -") + << (selector->has_real_parent_ref() ? " [real parent]": " -") + // << (selector->has_line_feed() ? " [line-feed]": " -") + // << (selector->has_line_break() ? " [line-break]": " -") + << " -- \n"; - } else if (Cast(node)) { - Parent_Selector* selector = Cast(node); - std::cerr << ind << "Parent_Selector " << selector; -// if (selector->not_selector()) cerr << " [in_declaration]"; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " [" << (selector->real() ? "REAL" : "FAKE") << "]"; - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; -// debug_ast(selector->selector(), ind + "->", env); + for(const SelectorComponentObj& i : selector->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Complex_Selector* selector = Cast(node); - std::cerr << ind << "Complex_Selector " << selector + } else if (Cast(node)) { + SelectorCombinator* selector = Cast(node); + std::cerr << ind << "SelectorCombinator " << selector << " (" << pstate_source_position(node) << ")" << " <" << selector->hash() << ">" - << " [length:" << longToHex(selector->length()) << "]" << " [weight:" << longToHex(selector->specificity()) << "]" - << " [@media:" << selector->media_block() << "]" - << (selector->is_invisible() ? " [INVISIBLE]": " -") - << (selector->has_placeholder() ? " [PLACEHOLDER]": " -") - << (selector->is_optional() ? " [is_optional]": " -") - << (selector->has_parent_ref() ? " [has parent]": " -") - << (selector->has_line_feed() ? " [line-feed]": " -") - << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_real_parent_ref() ? " [real parent]": " -") << " -- "; + std::string del; switch (selector->combinator()) { - case Complex_Selector::PARENT_OF: del = ">"; break; - case Complex_Selector::PRECEDES: del = "~"; break; - case Complex_Selector::ADJACENT_TO: del = "+"; break; - case Complex_Selector::ANCESTOR_OF: del = " "; break; - case Complex_Selector::REFERENCE: del = "//"; break; + case SelectorCombinator::CHILD: del = ">"; break; + case SelectorCombinator::GENERAL: del = "~"; break; + case SelectorCombinator::ADJACENT: del = "+"; break; } - // if (del = "/") del += selector->reference()->perform(&to_string) + "/"; - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; - debug_ast(selector->head(), ind + " " /* + "[" + del + "]" */, env); - if (selector->tail()) { - debug_ast(selector->tail(), ind + "{" + del + "}", env); - } else if(del != " ") { - std::cerr << ind << " |" << del << "| {trailing op}" << std::endl; - } - ComplexSelectorSet set = selector->sources(); - // debug_sources_set(set, ind + " @--> "); - } else if (Cast(node)) { - Compound_Selector* selector = Cast(node); - std::cerr << ind << "Compound_Selector " << selector; + + std::cerr << "[" << del << "]" << "\n"; + + } else if (Cast(node)) { + CompoundSelector* selector = Cast(node); + std::cerr << ind << "CompoundSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; + std::cerr << (selector->hasRealParent() ? " [REAL PARENT]" : "") << ">"; std::cerr << " [weight:" << longToHex(selector->specificity()) << "]"; - std::cerr << " [@media:" << selector->media_block() << "]"; - std::cerr << (selector->extended() ? " [extended]": " -"); - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; - for(const Simple_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Wrapped_Selector* selector = Cast(node); - std::cerr << ind << "Wrapped_Selector " << selector; + std::cerr << (selector->hasPostLineBreak() ? " [hasPostLineBreak]" : " -"); + std::cerr << (selector->is_invisible() ? " [is_invisible]" : " -"); + std::cerr << (selector->isInvisible() ? " [isInvisible]" : " -"); + std::cerr << "\n"; + for(const SimpleSelector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + + } else if (Cast(node)) { + Parent_Reference* selector = Cast(node); + std::cerr << ind << "Parent_Reference " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << std::endl; - debug_ast(selector->selector(), ind + " () ", env); + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; + } else if (Cast(node)) { Pseudo_Selector* selector = Cast(node); std::cerr << ind << "Pseudo_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << (selector->isClass() ? " [isClass]": " -"); + std::cerr << (selector->isSyntacticClass() ? " [isSyntacticClass]": " -"); std::cerr << std::endl; - debug_ast(selector->expression(), ind + " <= ", env); + debug_ast(selector->argument(), ind + " <= ", env); + debug_ast(selector->selector(), ind + " || ", env); } else if (Cast(node)) { Attribute_Selector* selector = Cast(node); std::cerr << ind << "Attribute_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << std::endl; debug_ast(selector->value(), ind + "[" + selector->matcher() + "] ", env); } else if (Cast(node)) { @@ -219,10 +446,6 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << std::endl; } else if (Cast(node)) { Id_Selector* selector = Cast(node); @@ -230,10 +453,6 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << std::endl; } else if (Cast(node)) { Type_Selector* selector = Cast(node); @@ -241,10 +460,6 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">"; std::cerr << std::endl; } else if (Cast(node)) { @@ -253,23 +468,18 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) std::cerr << ind << "Placeholder_Selector [" << selector->ns_name() << "] " << selector; std::cerr << " (" << pstate_source_position(selector) << ")" << " <" << selector->hash() << ">" - << " [@media:" << selector->media_block() << "]" - << (selector->is_optional() ? " [is_optional]": " -") - << (selector->has_line_break() ? " [line-break]": " -") - << (selector->has_line_feed() ? " [line-feed]": " -") + << (selector->isInvisible() ? " [isInvisible]" : " -") << std::endl; - } else if (Cast(node)) { - Simple_Selector* selector = Cast(node); - std::cerr << ind << "Simple_Selector " << selector; + } else if (Cast(node)) { + SimpleSelector* selector = Cast(node); + std::cerr << ind << "SimpleSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << std::endl; } else if (Cast(node)) { Selector_Schema* selector = Cast(node); std::cerr << ind << "Selector_Schema " << selector; std::cerr << " (" << pstate_source_position(node) << ")" - << " [@media:" << selector->media_block() << "]" << (selector->connect_parent() ? " [connect-parent]": " -") << std::endl; @@ -279,9 +489,7 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) } else if (Cast(node)) { Selector* selector = Cast(node); std::cerr << ind << "Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (selector->has_line_break() ? " [line-break]": " -") - << (selector->has_line_feed() ? " [line-feed]": " -") + std::cerr << " (" << pstate_source_position(node) << ")" << std::endl; } else if (Cast(node)) { @@ -302,14 +510,33 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) << std::endl; debug_ast(block->media_type(), ind + " "); for(const auto& i : block->elements()) { debug_ast(i, ind + " ", env); } - - } else if (Cast(node)) { - Media_Block* block = Cast(node); - std::cerr << ind << "Media_Block " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->media_queries(), ind + " =@ "); - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } + else if (Cast(node)) { + MediaRule* rule = Cast(node); + std::cerr << ind << "MediaRule " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << " " << rule->tabs() << std::endl; + debug_ast(rule->schema(), ind + " =@ "); + debug_ast(rule->block(), ind + " "); + } + else if (Cast(node)) { + CssMediaRule* rule = Cast(node); + std::cerr << ind << "CssMediaRule " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << " " << rule->tabs() << std::endl; + for (auto item : rule->elements()) { + debug_ast(item, ind + " == "); + } + debug_ast(rule->block(), ind + " "); + } + else if (Cast(node)) { + CssMediaQuery* query = Cast(node); + std::cerr << ind << "CssMediaQuery " << query; + std::cerr << " (" << pstate_source_position(query) << ")"; + std::cerr << " [" << (query->modifier()) << "] "; + std::cerr << " [" << (query->type()) << "] "; + std::cerr << " " << debug_vec(query->features()); + std::cerr << std::endl; } else if (Cast(node)) { Supports_Block* block = Cast(node); std::cerr << ind << "Supports_Block " << block; @@ -349,6 +576,7 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) std::cerr << ind << "Block " << root_block; std::cerr << " (" << pstate_source_position(node) << ")"; if (root_block->is_root()) std::cerr << " [root]"; + if (root_block->isInvisible()) std::cerr << " [isInvisible]"; std::cerr << " " << root_block->tabs() << std::endl; for(const Statement_Obj& i : root_block->elements()) { debug_ast(i, ind + " ", env); } } else if (Cast(node)) { @@ -387,10 +615,11 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) Return* block = Cast(node); std::cerr << ind << "Return " << block; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - } else if (Cast(node)) { - Extension* block = Cast(node); - std::cerr << ind << "Extension " << block; + std::cerr << " " << block->tabs(); + std::cerr << " [" << block->value()->to_string() << "]" << std::endl; + } else if (Cast(node)) { + ExtendRule* block = Cast(node); + std::cerr << ind << "ExtendRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; debug_ast(block->selector(), ind + "-> ", env); @@ -724,54 +953,6 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) if (ind == "") std::cerr << "####################################################################\n"; } -inline void debug_node(Node* node, std::string ind = "") -{ - if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; - if (node->isCombinator()) { - std::cerr << ind; - std::cerr << "Combinator "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - switch (node->combinator()) { - case Complex_Selector::ADJACENT_TO: std::cerr << "{+} "; break; - case Complex_Selector::PARENT_OF: std::cerr << "{>} "; break; - case Complex_Selector::PRECEDES: std::cerr << "{~} "; break; - case Complex_Selector::REFERENCE: std::cerr << "{@} "; break; - case Complex_Selector::ANCESTOR_OF: std::cerr << "{ } "; break; - } - std::cerr << std::endl; - // debug_ast(node->combinator(), ind + " "); - } else if (node->isSelector()) { - std::cerr << ind; - std::cerr << "Selector "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - debug_ast(node->selector(), ind + " "); - } else if (node->isCollection()) { - std::cerr << ind; - std::cerr << "Collection "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - for(auto n : (*node->collection())) { - debug_node(&n, ind + " "); - } - } else if (node->isNil()) { - std::cerr << ind; - std::cerr << "Nil "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - } else { - std::cerr << ind; - std::cerr << "OTHER "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - } - if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; -} /* inline void debug_ast(const AST_Node* node, std::string ind = "", Env* env = 0) @@ -779,29 +960,5 @@ inline void debug_ast(const AST_Node* node, std::string ind = "", Env* env = 0) debug_ast(const_cast(node), ind, env); } */ -inline void debug_node(const Node* node, std::string ind = "") -{ - debug_node(const_cast(node), ind); -} - -inline void debug_subset_map(Sass::Subset_Map& map, std::string ind = "") -{ - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - for(auto const &it : map.values()) { - debug_ast(it.first, ind + "first: "); - debug_ast(it.second, ind + "second: "); - } - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; -} - -inline void debug_subset_entries(SubSetMapPairs* entries, std::string ind = "") -{ - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - for(auto const &pair : *entries) { - debug_ast(pair.first, ind + "first: "); - debug_ast(pair.second, ind + "second: "); - } - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; -} #endif // SASS_DEBUGGER diff --git a/src/emitter.cpp b/src/emitter.cpp index c0334b250..27dcd9b6e 100644 --- a/src/emitter.cpp +++ b/src/emitter.cpp @@ -1,10 +1,9 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" -#include "util.hpp" -#include "context.hpp" -#include "output.hpp" #include "emitter.hpp" #include "util_string.hpp" -#include "utf8_string.hpp" +#include "util.hpp" namespace Sass { diff --git a/src/emitter.hpp b/src/emitter.hpp index 8b22de80b..ceb4df584 100644 --- a/src/emitter.hpp +++ b/src/emitter.hpp @@ -5,8 +5,6 @@ // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" -#include - #include "sass/base.h" #include "source_map.hpp" #include "ast_fwd_decl.hpp" diff --git a/src/environment.hpp b/src/environment.hpp index 08452ea32..606388657 100644 --- a/src/environment.hpp +++ b/src/environment.hpp @@ -1,6 +1,11 @@ #ifndef SASS_ENVIRONMENT_H #define SASS_ENVIRONMENT_H +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include #include #include "ast_fwd_decl.hpp" #include "ast_def_macros.hpp" diff --git a/src/error_handling.cpp b/src/error_handling.cpp index c0cf739eb..7aa5df31c 100644 --- a/src/error_handling.cpp +++ b/src/error_handling.cpp @@ -132,6 +132,27 @@ namespace Sass { prefix = err.errtype(); } + TopLevelParent::TopLevelParent(Backtraces traces, ParserState pstate) + : Base(pstate, "Top-level selectors may not contain the parent selector \"&\".", traces) + { + + } + + UnsatisfiedExtend::UnsatisfiedExtend(Backtraces traces, Extension extension) + : Base(extension.target->pstate(), "The target selector was not found.\n" + "Use \"@extend " + extension.target->to_string() + " !optional\" to avoid this error.", traces) + { + + } + + ExtendAcrossMedia::ExtendAcrossMedia(Backtraces traces, Extension extension) + : Base(extension.target->pstate(), "You may not @extend selectors across media queries.\n" + "Use \"@extend " + extension.target->to_string() + " !optional\" to avoid this error.", traces) + { + + } + + } diff --git a/src/error_handling.hpp b/src/error_handling.hpp index 24fedec4c..d5157d6dc 100644 --- a/src/error_handling.hpp +++ b/src/error_handling.hpp @@ -1,6 +1,10 @@ #ifndef SASS_ERROR_HANDLING_H #define SASS_ERROR_HANDLING_H +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + #include #include #include @@ -205,9 +209,27 @@ namespace Sass { }; class SassValueError : public Base { - public: - SassValueError(Backtraces traces, ParserState pstate, OperationError& err); - virtual ~SassValueError() throw() {}; + public: + SassValueError(Backtraces traces, ParserState pstate, OperationError& err); + virtual ~SassValueError() throw() {}; + }; + + class TopLevelParent : public Base { + public: + TopLevelParent(Backtraces traces, ParserState pstate); + virtual ~TopLevelParent() throw() {}; + }; + + class UnsatisfiedExtend : public Base { + public: + UnsatisfiedExtend(Backtraces traces, Extension extension); + virtual ~UnsatisfiedExtend() throw() {}; + }; + + class ExtendAcrossMedia : public Base { + public: + ExtendAcrossMedia(Backtraces traces, Extension extension); + virtual ~ExtendAcrossMedia() throw() {}; }; } diff --git a/src/eval.cpp b/src/eval.cpp index 71bd0aea2..82c473073 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -72,28 +72,11 @@ namespace Sass { return exp.env_stack; } - Selector_List_Obj Eval::selector() - { - return exp.selector(); - } - std::vector& Eval::callee_stack() { return ctx.callee_stack; } - - SelectorStack& Eval::selector_stack() - { - return exp.selector_stack; - } - - bool& Eval::old_at_root_without_rule() - { - return exp.old_at_root_without_rule; - } - - Expression* Eval::operator()(Block* b) { Expression* val = 0; @@ -251,9 +234,8 @@ namespace Sass { if (expr->concrete_type() == Expression::MAP) { map = Cast(expr); } - else if (Selector_List* ls = Cast(expr)) { - Listize listize; - Expression_Obj rv = ls->perform(&listize); + else if (SelectorList * ls = Cast(expr)) { + Expression_Obj rv = Listize::perform(ls); list = Cast(rv); } else if (expr->concrete_type() != Expression::LIST) { @@ -286,7 +268,7 @@ namespace Sass { } } else { - if (list->length() == 1 && Cast(list)) { + if (list->length() == 1 && Cast(list)) { list = Cast(list); } for (size_t i = 0, L = list->length(); i < L; ++i) { @@ -490,6 +472,7 @@ namespace Sass { return 0; } + Expression* Eval::operator()(List* l) { // special case for unevaluated map @@ -995,7 +978,7 @@ namespace Sass { c->name(), args); if (args->has_named_arguments()) { - error("Function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces); + error("Plain CSS function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces); } String_Quoted* str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), @@ -1065,7 +1048,7 @@ namespace Sass { result = body->perform(this); } else if (func) { - result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.selector_stack); + result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.getSelectorStack(), exp.originalStack); } if (!result) { error(std::string("Function ") + c->name() + " finished without @return", c->pstate(), traces); @@ -1214,11 +1197,6 @@ namespace Sass { if (Cast(ex)) { return; } - // parent selector needs another go - if (Cast(ex)) { - // XXX: this is never hit via spec tests - ex = ex->perform(this); - } // parent selector needs another go if (Cast(ex)) { // XXX: this is never hit via spec tests @@ -1254,7 +1232,6 @@ namespace Sass { // Selector_List // String_Quoted // String_Constant - // Parent_Selector // Binary_Expression else { // ex = ex->perform(this); @@ -1512,69 +1489,7 @@ namespace Sass { return 0; } - Selector_List* Eval::operator()(Selector_List* s) - { - SelectorStack rv; - Selector_List_Obj sl = SASS_MEMORY_NEW(Selector_List, s->pstate()); - sl->is_optional(s->is_optional()); - sl->media_block(s->media_block()); - sl->is_optional(s->is_optional()); - for (size_t i = 0, iL = s->length(); i < iL; ++i) { - rv.push_back(operator()((*s)[i])); - } - - // we should actually permutate parent first - // but here we have permutated the selector first - size_t round = 0; - while (round != std::string::npos) { - bool abort = true; - for (size_t i = 0, iL = rv.size(); i < iL; ++i) { - if (rv[i]->length() > round) { - sl->append((*rv[i])[round]); - abort = false; - } - } - if (abort) { - round = std::string::npos; - } else { - ++ round; - } - - } - return sl.detach(); - } - - - Selector_List* Eval::operator()(Complex_Selector* s) - { - bool implicit_parent = !exp.old_at_root_without_rule; - if (is_in_selector_schema) exp.selector_stack.push_back({}); - Selector_List_Obj resolved = s->resolve_parent_refs(exp.selector_stack, traces, implicit_parent); - if (is_in_selector_schema) exp.selector_stack.pop_back(); - for (size_t i = 0; i < resolved->length(); i++) { - Complex_Selector* is = resolved->at(i)->mutable_first(); - while (is) { - if (is->head()) { - is->head(operator()(is->head())); - } - is = is->tail(); - } - } - return resolved.detach(); - } - - Compound_Selector* Eval::operator()(Compound_Selector* s) - { - for (size_t i = 0; i < s->length(); i++) { - Simple_Selector* ss = s->at(i); - // skip parents here (called via resolve_parent_refs) - if (ss == NULL || Cast(ss)) continue; - s->at(i) = Cast(ss->perform(this)); - } - return s; - } - - Selector_List* Eval::operator()(Selector_Schema* s) + SelectorList* Eval::operator()(Selector_Schema* s) { LOCAL_FLAG(is_in_selector_schema, true); // the parser will look for a brace to end the selector @@ -1584,74 +1499,32 @@ namespace Sass { char* temp_cstr = sass_copy_c_string(result_str.c_str()); ctx.strings.push_back(temp_cstr); // attach to context Parser p = Parser::from_c_str(temp_cstr, ctx, traces, s->pstate()); - p.last_media_block = s->media_block(); - // a selector schema may or may not connect to parent? - bool chroot = s->connect_parent() == false; - Selector_List_Obj sl = p.parse_selector_list(chroot); - flag_is_in_selector_schema.reset(); - return operator()(sl); - } - Expression* Eval::operator()(Parent_Selector* p) - { - if (Selector_List_Obj pr = selector()) { - exp.selector_stack.pop_back(); - Selector_List_Obj rv = operator()(pr); - exp.selector_stack.push_back(rv); - return rv.detach(); - } else { - return SASS_MEMORY_NEW(Null, p->pstate()); - } + // If a schema contains a reference to parent it is already + // connected to it, so don't connect implicitly anymore + SelectorListObj parsed = p.parseSelectorList(true); + flag_is_in_selector_schema.reset(); + return parsed.detach(); } Expression* Eval::operator()(Parent_Reference* p) { - if (Selector_List_Obj pr = selector()) { - exp.selector_stack.pop_back(); - Selector_List_Obj rv = operator()(pr); - exp.selector_stack.push_back(rv); - return rv.detach(); + if (SelectorListObj pr = exp.original()) { + return operator()(pr); } else { return SASS_MEMORY_NEW(Null, p->pstate()); } } - Simple_Selector* Eval::operator()(Simple_Selector* s) + SimpleSelector* Eval::operator()(SimpleSelector* s) { return s; } - // hotfix to avoid invalid nested `:not` selectors - // probably the wrong place, but this should ultimately - // be fixed by implement superselector correctly for `:not` - // first use of "find" (ATM only implemented for selectors) - bool hasNotSelector(AST_Node_Obj obj) { - if (Wrapped_Selector* w = Cast(obj)) { - return w->name() == ":not"; - } - return false; - } - - Wrapped_Selector* Eval::operator()(Wrapped_Selector* s) + Pseudo_Selector* Eval::operator()(Pseudo_Selector* pseudo) { - - if (s->name() == ":not") { - if (exp.selector_stack.back()) { - if (s->selector()->find(hasNotSelector)) { - s->selector()->clear(); - s->name(" "); - } else { - for (size_t i = 0; i < s->selector()->length(); ++i) { - Complex_Selector* cs = s->selector()->at(i); - if (cs->tail()) { - s->selector()->clear(); - s->name(" "); - } - } - } - } - } - return s; + // ToDo: should we eval selector? + return pseudo; }; } diff --git a/src/eval.hpp b/src/eval.hpp index dcaf14aa2..c27db2fff 100644 --- a/src/eval.hpp +++ b/src/eval.hpp @@ -1,7 +1,11 @@ #ifndef SASS_EVAL_H #define SASS_EVAL_H +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" #include "ast.hpp" + #include "context.hpp" #include "listize.hpp" #include "operation.hpp" @@ -31,12 +35,8 @@ namespace Sass { Env* environment(); EnvStack& env_stack(); const std::string cwd(); - Selector_List_Obj selector(); CalleeStack& callee_stack(); - SelectorStack& selector_stack(); - bool& old_at_root_without_rule(); struct Sass_Inspect_Options& options(); - struct Sass_Inspect_Options options2(); struct Sass_Compiler* compiler(); // for evaluating function bodies @@ -64,7 +64,6 @@ namespace Sass { Expression* operator()(String_Schema*); Expression* operator()(String_Quoted*); Expression* operator()(String_Constant*); - // Expression* operator()(Selector_List*); Media_Query* operator()(Media_Query*); Expression* operator()(Media_Query_Expression*); Expression* operator()(At_Root_Query*); @@ -78,23 +77,22 @@ namespace Sass { Expression* operator()(Comment*); // these will return selectors - Selector_List* operator()(Selector_List*); - Selector_List* operator()(Complex_Selector*); - Compound_Selector* operator()(Compound_Selector*); - Simple_Selector* operator()(Simple_Selector* s); - Wrapped_Selector* operator()(Wrapped_Selector* s); + SelectorList* operator()(SelectorList*); + SelectorList* operator()(ComplexSelector*); + CompoundSelector* operator()(CompoundSelector*); + SelectorComponent* operator()(SelectorComponent*); + SimpleSelector* operator()(SimpleSelector* s); + Pseudo_Selector* operator()(Pseudo_Selector* s); // they don't have any specific implementation (yet) Id_Selector* operator()(Id_Selector* s) { return s; }; Class_Selector* operator()(Class_Selector* s) { return s; }; - Pseudo_Selector* operator()(Pseudo_Selector* s) { return s; }; Type_Selector* operator()(Type_Selector* s) { return s; }; Attribute_Selector* operator()(Attribute_Selector* s) { return s; }; Placeholder_Selector* operator()(Placeholder_Selector* s) { return s; }; // actual evaluated selectors - Selector_List* operator()(Selector_Schema*); - Expression* operator()(Parent_Selector*); + SelectorList* operator()(Selector_Schema*); Expression* operator()(Parent_Reference*); // generic fallback diff --git a/src/eval_selectors.cpp b/src/eval_selectors.cpp new file mode 100644 index 000000000..cc7d3409c --- /dev/null +++ b/src/eval_selectors.cpp @@ -0,0 +1,75 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include "expand.hpp" +#include "eval.hpp" +#include "ast.hpp" + + +namespace Sass { + + SelectorList* Eval::operator()(SelectorList* s) + { + std::vector rv; + SelectorListObj sl = SASS_MEMORY_NEW(SelectorList, s->pstate()); + for (size_t i = 0, iL = s->length(); i < iL; ++i) { + rv.push_back(operator()(s->get(i))); + } + + // we should actually permutate parent first + // but here we have permutated the selector first + size_t round = 0; + while (round != std::string::npos) { + bool abort = true; + for (size_t i = 0, iL = rv.size(); i < iL; ++i) { + if (rv[i]->length() > round) { + sl->append((*rv[i])[round]); + abort = false; + } + } + if (abort) { + round = std::string::npos; + } + else { + ++round; + } + + } + return sl.detach(); + } + + SelectorComponent* Eval::operator()(SelectorComponent* s) + { + return {}; + } + + SelectorList* Eval::operator()(ComplexSelector* s) + { + bool implicit_parent = !exp.old_at_root_without_rule; + if (is_in_selector_schema) exp.pushToSelectorStack({}); + SelectorListObj other = s->resolve_parent_refs( + exp.getSelectorStack(), traces, implicit_parent); + if (is_in_selector_schema) exp.popFromSelectorStack(); + + for (size_t i = 0; i < other->length(); i++) { + ComplexSelectorObj sel = other->at(i); + for (size_t n = 0; n < sel->length(); n++) { + if (CompoundSelectorObj comp = Cast(sel->at(n))) { + sel->at(n) = operator()(comp); + } + } + } + + return other.detach(); + } + + CompoundSelector* Eval::operator()(CompoundSelector* s) + { + for (size_t i = 0; i < s->length(); i++) { + SimpleSelector* ss = s->at(i); + // skip parents here (called via resolve_parent_refs) + s->at(i) = Cast(ss->perform(this)); + } + return s; + } +} diff --git a/src/expand.cpp b/src/expand.cpp index ef5683826..79882a562 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -19,7 +19,7 @@ namespace Sass { // simple endless recursion protection const size_t maxRecursion = 500; - Expand::Expand(Context& ctx, Env* env, SelectorStack* stack) + Expand::Expand(Context& ctx, Env* env, SelectorStack* stack, SelectorStack* originals) : ctx(ctx), traces(ctx.traces), eval(Eval(*this)), @@ -27,19 +27,32 @@ namespace Sass { in_keyframes(false), at_root_without_rule(false), old_at_root_without_rule(false), - env_stack(EnvStack()), - block_stack(BlockStack()), - call_stack(CallStack()), - selector_stack(SelectorStack()), - media_stack(MediaStack()) + env_stack(), + block_stack(), + call_stack(), + selector_stack(), + originalStack(), + mediaStack() { env_stack.push_back(nullptr); env_stack.push_back(env); block_stack.push_back(nullptr); call_stack.push_back({}); - if (stack == NULL) { selector_stack.push_back({}); } - else { selector_stack.insert(selector_stack.end(), stack->begin(), stack->end()); } - media_stack.push_back(nullptr); + if (stack == NULL) { pushToSelectorStack({}); } + else { + for (auto item : *stack) { + if (item.isNull()) pushToSelectorStack({}); + else pushToSelectorStack(item); + } + } + if (originals == NULL) { pushToOriginalStack({}); } + else { + for (auto item : *stack) { + if (item.isNull()) pushToOriginalStack({}); + else pushToOriginalStack(item); + } + } + mediaStack.push_back({}); } Env* Expand::environment() @@ -49,11 +62,63 @@ namespace Sass { return 0; } - Selector_List_Obj Expand::selector() + SelectorStack Expand::getSelectorStack() + { + return selector_stack; + } + + SelectorListObj& Expand::selector() + { + if (selector_stack.size() > 0) { + auto& sel = selector_stack.back(); + if (sel.isNull()) return sel; + return sel; + } + // Avoid the need to return copies + // We always want an empty first item + selector_stack.push_back({}); + return selector_stack.back();; + } + + SelectorListObj& Expand::original() + { + if (originalStack.size() > 0) { + auto& sel = originalStack.back(); + if (sel.isNull()) return sel; + return sel; + } + // Avoid the need to return copies + // We always want an empty first item + originalStack.push_back({}); + return originalStack.back(); + } + + SelectorListObj Expand::popFromSelectorStack() + { + SelectorListObj last = selector_stack.back(); + if (selector_stack.size() > 0) + selector_stack.pop_back(); + if (last.isNull()) return {}; + return last; + } + + void Expand::pushToSelectorStack(SelectorListObj selector) + { + selector_stack.push_back(selector); + } + + SelectorListObj Expand::popFromOriginalStack() + { + SelectorListObj last = originalStack.back(); + if (originalStack.size() > 0) + originalStack.pop_back(); + if (last.isNull()) return {}; + return last; + } + + void Expand::pushToOriginalStack(SelectorListObj selector) { - if (selector_stack.size() > 0) - return selector_stack.back(); - return {}; + originalStack.push_back(selector); } // blocks create new variable scopes @@ -87,69 +152,55 @@ namespace Sass { if (in_keyframes) { Block* bb = operator()(r->block()); Keyframe_Rule_Obj k = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), bb); - if (r->selector()) { - if (Selector_List* s = r->selector()) { - selector_stack.push_back({}); - k->name(s->eval(eval)); - selector_stack.pop_back(); + if (r->schema()) { + pushToSelectorStack({}); + k->name(eval(r->schema())); + popFromSelectorStack(); + } + else if (r->selector()) { + if (SelectorListObj s = r->selector()) { + pushToSelectorStack({}); + k->name(eval(s)); + popFromSelectorStack(); } } + return k.detach(); } - // reset when leaving scope - LOCAL_FLAG(at_root_without_rule, false); - - // `&` is allowed in `@at-root`! - bool has_parent_selector = false; - for (size_t i = 0, L = selector_stack.size(); i < L && !has_parent_selector; i++) { - Selector_List_Obj ll = selector_stack.at(i); - has_parent_selector = ll != nullptr && ll->length() > 0; - } - - Selector_List_Obj sel = r->selector(); - if (sel) sel = sel->eval(eval); - - // check for parent selectors in base level rules - if (r->is_root() || (block_stack.back() && block_stack.back()->is_root())) { - if (Selector_List* selector_list = Cast(r->selector())) { - for (Complex_Selector_Obj complex_selector : selector_list->elements()) { - Complex_Selector* tail = complex_selector; - while (tail) { - if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { - Parent_Selector* ptr = Cast(header); - if (ptr == NULL || (!ptr->real() || has_parent_selector)) continue; - std::string sel_str(complex_selector->to_string(ctx.c_options)); - error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), traces); - } - tail = tail->tail(); - } - } - } - } - else { - if (sel->length() == 0 || sel->has_parent_ref()) { - if (sel->has_real_parent_ref() && !has_parent_selector) { - error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), traces); - } + if (r->schema()) { + SelectorListObj sel = eval(r->schema()); + r->selector(sel); + bool chroot = sel->has_real_parent_ref(); + for (auto complex : sel->elements()) { + complex->chroots(chroot); } + } + // reset when leaving scope + LOCAL_FLAG(at_root_without_rule, false); + + SelectorListObj evaled = eval(r->selector()); // do not connect parent again - sel->remove_parent_selectors(); - selector_stack.push_back(sel); Env env(environment()); if (block_stack.back()->is_root()) { env_stack.push_back(&env); } - sel->set_media_block(media_stack.back()); Block_Obj blk; + pushToSelectorStack(evaled); + // The copy is needed for parent reference evaluation + // dart-sass stores it as `originalSelector` member + pushToOriginalStack(SASS_MEMORY_COPY(evaled)); + ctx.extender.addSelector(evaled, mediaStack.back()); if (r->block()) blk = operator()(r->block()); + popFromOriginalStack(); + popFromSelectorStack(); Ruleset* rr = SASS_MEMORY_NEW(Ruleset, r->pstate(), - sel, + evaled, blk); - selector_stack.pop_back(); + if (block_stack.back()->is_root()) { env_stack.pop_back(); } @@ -170,31 +221,44 @@ namespace Sass { return ff.detach(); } - Statement* Expand::operator()(Media_Block* m) + std::vector Expand::mergeMediaQueries( + const std::vector& lhs, + const std::vector& rhs) { - Media_Block_Obj cpy = SASS_MEMORY_COPY(m); - // Media_Blocks are prone to have circular references - // Copy could leak memory if it does not get picked up - // Looks like we are able to reset block reference for copy - // Good as it will ensure a low memory overhead for this fix - // So this is a cheap solution with a minimal price - ctx.ast_gc.push_back(cpy); cpy->block({}); - Expression_Obj mq = eval(m->media_queries()); - std::string str_mq(mq->to_string(ctx.c_options)); + std::vector queries; + for (CssMediaQuery_Obj query1 : lhs) { + for (CssMediaQuery_Obj query2 : rhs) { + CssMediaQuery_Obj result = query1->merge(query2); + if (result && !result->empty()) { + queries.push_back(result); + } + } + } + return queries; + } + + Statement* Expand::operator()(MediaRule* m) + { + Expression_Obj mq = eval(m->schema()); + std::string str_mq(mq->to_css(ctx.c_options)); char* str = sass_copy_c_string(str_mq.c_str()); ctx.strings.push_back(str); - Parser p(Parser::from_c_str(str, ctx, traces, mq->pstate())); - mq = p.parse_media_queries(); // re-assign now - cpy->media_queries(mq); - media_stack.push_back(cpy); - Block_Obj blk = operator()(m->block()); - Media_Block* mm = SASS_MEMORY_NEW(Media_Block, - m->pstate(), - mq, - blk); - media_stack.pop_back(); - mm->tabs(m->tabs()); - return mm; + Parser parser(Parser::from_c_str(str, ctx, traces, mq->pstate())); + // Create a new CSS only representation of the media rule + CssMediaRuleObj css = SASS_MEMORY_NEW(CssMediaRule, m->pstate(), m->block()); + std::vector parsed = parser.parseCssMediaQueries(); + if (mediaStack.size() && mediaStack.back()) { + auto& parent = mediaStack.back()->elements(); + css->concat(mergeMediaQueries(parent, parsed)); + } + else { + css->concat(parsed); + } + mediaStack.push_back(css); + css->block(operator()(m->block())); + mediaStack.pop_back(); + return css.detach(); + } Statement* Expand::operator()(At_Root_Block* a) @@ -222,12 +286,12 @@ namespace Sass { { LOCAL_FLAG(in_keyframes, a->is_keyframes()); Block* ab = a->block(); - Selector_List* as = a->selector(); + SelectorList* as = a->selector(); Expression* av = a->value(); - selector_stack.push_back({}); + pushToSelectorStack({}); if (av) av = av->perform(&eval); if (as) as = eval(as); - selector_stack.pop_back(); + popFromSelectorStack(); Block* bb = ab ? operator()(ab) : NULL; Directive* aa = SASS_MEMORY_NEW(Directive, a->pstate(), @@ -497,9 +561,8 @@ namespace Sass { if (expr->concrete_type() == Expression::MAP) { map = Cast(expr); } - else if (Selector_List* ls = Cast(expr)) { - Listize listize; - Expression_Obj rv = ls->perform(&listize); + else if (SelectorList * ls = Cast(expr)) { + Expression_Obj rv = Listize::perform(ls); list = Cast(rv); } else if (expr->concrete_type() != Expression::LIST) { @@ -534,7 +597,7 @@ namespace Sass { } else { // bool arglist = list->is_arglist(); - if (list->length() == 1 && Cast(list)) { + if (list->length() == 1 && Cast(list)) { list = Cast(list); } for (size_t i = 0, L = list->length(); i < L; ++i) { @@ -595,87 +658,49 @@ namespace Sass { return 0; } + Statement* Expand::operator()(ExtendRule* e) + { - void Expand::expand_selector_list(Selector_Obj s, Selector_List_Obj extender) { - - if (Selector_List_Obj sl = Cast(s)) { - for (Complex_Selector_Obj complex_selector : sl->elements()) { - Complex_Selector_Obj tail = complex_selector; - while (tail) { - if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { - if (Cast(header) == NULL) continue; // skip all others - std::string sel_str(complex_selector->to_string(ctx.c_options)); - error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), traces); - } - tail = tail->tail(); - } - } + // evaluate schema first + if (e->schema()) { + e->selector(eval(e->schema())); + e->isOptional(e->selector()->is_optional()); } + // evaluate the selector + e->selector(eval(e->selector())); - Selector_List_Obj contextualized = Cast(s->perform(&eval)); - if (contextualized == nullptr) return; - for (auto complex_sel : contextualized->elements()) { - Complex_Selector_Obj c = complex_sel; - if (!c->head() || c->tail()) { - std::string sel_str(contextualized->to_string(ctx.c_options)); - error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), traces); - } - Compound_Selector_Obj target = c->head(); - if (contextualized->is_optional()) target->is_optional(true); - for (size_t i = 0, L = extender->length(); i < L; ++i) { - Complex_Selector_Obj sel = (*extender)[i]; - if (!(sel->head() && sel->head()->length() > 0 && - Cast((*sel->head())[0]))) - { - Compound_Selector_Obj hh = SASS_MEMORY_NEW(Compound_Selector, (*extender)[i]->pstate()); - hh->media_block((*extender)[i]->media_block()); - Complex_Selector_Obj ssel = SASS_MEMORY_NEW(Complex_Selector, (*extender)[i]->pstate()); - ssel->media_block((*extender)[i]->media_block()); - if (sel->has_line_feed()) ssel->has_line_feed(true); - Parent_Selector_Obj ps = SASS_MEMORY_NEW(Parent_Selector, (*extender)[i]->pstate()); - ps->media_block((*extender)[i]->media_block()); - hh->append(ps); - ssel->tail(sel); - ssel->head(hh); - sel = ssel; - } - // if (c->has_line_feed()) sel->has_line_feed(true); - ctx.subset_map.put(target, std::make_pair(sel, target)); + + auto list = e->selector(); + if (list) { + for (auto complex : list->elements()) { + + if (complex->length() != 1) { + std::cerr << "complex selectors may not be extended." << "\n"; exit(1); } - } - } + if (auto compound = complex->first()->getCompound()) { - Statement* Expand::operator()(Extension* e) - { - if (Selector_List_Obj extender = selector()) { - Selector_List* sl = e->selector(); - // abort on invalid selector - if (sl == NULL) return NULL; - if (Selector_Schema* schema = sl->schema()) { - if (schema->has_real_parent_ref()) { - // put root block on stack again (ignore parents) - // selector schema must not connect in eval! - block_stack.push_back(block_stack.at(1)); - sl = eval(sl->schema()); - block_stack.pop_back(); - } else { - selector_stack.push_back({}); - sl = eval(sl->schema()); - selector_stack.pop_back(); + if (compound->length() != 1) { + std::cerr << + "compound selectors may no longer be extended.\n" + "Consider `@extend ${compound.components.join(', ')}` instead.\n" + "See http://bit.ly/ExtendCompound for details.\n"; } + + // Pass every selector we ever see to extender (to make them findable for extend) + ctx.extender.addExtension(selector(), compound->first(), e, mediaStack.back()); + } - for (Complex_Selector_Obj cs : sl->elements()) { - if (!cs.isNull() && !cs->head().isNull()) { - cs->head()->media_block(media_stack.back()); - } + else { + std::cerr << "complex selectors may not be extended." << "\n"; exit(1); } - selector_stack.push_back({}); - expand_selector_list(sl, extender); - selector_stack.pop_back(); } - return 0; + } + return nullptr; + + + return nullptr; } Statement* Expand::operator()(Definition* d) @@ -705,6 +730,7 @@ namespace Sass { Statement* Expand::operator()(Mixin_Call* c) { + if (recursions > maxRecursion) { throw Exception::StackError(traces, *c); } @@ -785,11 +811,6 @@ namespace Sass { Env* env = environment(); // convert @content directives into mixin calls to the underlying thunk if (!env->has("@content[m]")) return 0; - - if (block_stack.back()->is_root()) { - selector_stack.push_back({}); - } - Arguments_Obj args = c->arguments(); if (!args) args = SASS_MEMORY_NEW(Arguments, c->pstate()); @@ -799,11 +820,6 @@ namespace Sass { args); Trace_Obj trace = Cast(call->perform(this)); - - if (block_stack.back()->is_root()) { - selector_stack.pop_back(); - } - return trace.detach(); } diff --git a/src/expand.hpp b/src/expand.hpp index 34ddb8961..db5852ce4 100644 --- a/src/expand.hpp +++ b/src/expand.hpp @@ -19,7 +19,15 @@ namespace Sass { public: Env* environment(); - Selector_List_Obj selector(); + SelectorListObj& selector(); + SelectorListObj& original(); + SelectorListObj popFromSelectorStack(); + SelectorStack getSelectorStack(); + void pushToSelectorStack(SelectorListObj selector); + + SelectorListObj popFromOriginalStack(); + + void pushToOriginalStack(SelectorListObj selector); Context& ctx; Backtraces& traces; @@ -33,21 +41,30 @@ namespace Sass { EnvStack env_stack; BlockStack block_stack; CallStack call_stack; + private: SelectorStack selector_stack; - MediaStack media_stack; + public: + SelectorStack originalStack; + MediaStack mediaStack; Boolean_Obj bool_true; private: - void expand_selector_list(Selector_Obj, Selector_List_Obj extender); + + std::vector mergeMediaQueries(const std::vector& lhs, const std::vector& rhs); public: - Expand(Context&, Env*, SelectorStack* stack = NULL); + Expand(Context&, Env*, SelectorStack* stack = nullptr, SelectorStack* original = nullptr); ~Expand() { } Block* operator()(Block*); Statement* operator()(Ruleset*); - Statement* operator()(Media_Block*); + + Statement* operator()(MediaRule*); + + // Css Ruleset is already static + // Statement* operator()(CssMediaRule*); + Statement* operator()(Supports_Block*); Statement* operator()(At_Root_Block*); Statement* operator()(Directive*); @@ -64,7 +81,7 @@ namespace Sass { Statement* operator()(Each*); Statement* operator()(While*); Statement* operator()(Return*); - Statement* operator()(Extension*); + Statement* operator()(ExtendRule*); Statement* operator()(Definition*); Statement* operator()(Mixin_Call*); Statement* operator()(Content*); diff --git a/src/extend.cpp b/src/extend.cpp deleted file mode 100644 index 3470dc93a..000000000 --- a/src/extend.cpp +++ /dev/null @@ -1,2132 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "extend.hpp" -#include "context.hpp" -#include "backtrace.hpp" -#include "paths.hpp" -#include "parser.hpp" -#include "expand.hpp" -#include "node.hpp" -#include "sass_util.hpp" -#include "remove_placeholders.hpp" -#include "debug.hpp" -#include -#include -#include - -/* - NOTES: - - - The print* functions print to cerr. This allows our testing frameworks (like sass-spec) to ignore the output, which - is very helpful when debugging. The format of the output is mainly to wrap things in square brackets to match what - ruby already outputs (to make comparisons easier). - - - For the direct porting effort, we're trying to port method-for-method until we get all the tests passing. - Where applicable, I've tried to include the ruby code above the function for reference until all our tests pass. - The ruby code isn't always directly portable, so I've tried to include any modified ruby code that was actually - used for the porting. - - - DO NOT try to optimize yet. We get a tremendous benefit out of comparing the output of each stage of the extend to the ruby - output at the same stage. This makes it much easier to determine where problems are. Try to keep as close to - the ruby code as you can until we have all the sass-spec tests passing. Then, we should optimize. However, if you see - something that could probably be optimized, let's not forget it. Add a // TODO: or // IMPROVEMENT: comment. - - - Coding conventions in this file (these may need to be changed before merging back into master) - - Very basic hungarian notation: - p prefix for pointers (pSelector) - no prefix for value types and references (selector) - - Use STL iterators where possible - - prefer verbose naming over terse naming - - use typedefs for STL container types for make maintenance easier - - - You may see a lot of comments that say "// TODO: is this the correct combinator?". See the comment referring to combinators - in extendCompoundSelector for a more extensive explanation of my confusion. I think our divergence in data model from ruby - sass causes this to be necessary. - - - GLOBAL TODOS: - - - wrap the contents of the print functions in DEBUG preprocesser conditionals so they will be optimized away in non-debug mode. - - - consider making the extend* functions member functions to avoid passing around ctx and subset_map map around. This has the - drawback that the implementation details of the operator are then exposed to the outside world, which is not ideal and - can cause additional compile time dependencies. - - - mark the helper methods in this file static to given them compilation unit linkage. - - - implement parent directive matching - - - fix compilation warnings for unused Extend members if we really don't need those references anymore. - */ - - -namespace Sass { - - - -#ifdef DEBUG - - // TODO: move the ast specific ostream operators into ast.hpp/ast.cpp - std::ostream& operator<<(std::ostream& os, const Complex_Selector::Combinator combinator) { - switch (combinator) { - case Complex_Selector::ANCESTOR_OF: os << "\" \""; break; - case Complex_Selector::PARENT_OF: os << "\">\""; break; - case Complex_Selector::PRECEDES: os << "\"~\""; break; - case Complex_Selector::ADJACENT_TO: os << "\"+\""; break; - case Complex_Selector::REFERENCE: os << "\"/\""; break; - } - - return os; - } - - - std::ostream& operator<<(std::ostream& os, Compound_Selector& compoundSelector) { - for (size_t i = 0, L = compoundSelector.length(); i < L; ++i) { - if (i > 0) os << ", "; - os << compoundSelector[i]->to_string(); - } - return os; - } - - std::ostream& operator<<(std::ostream& os, Simple_Selector& simpleSelector) { - os << simpleSelector.to_string(); - return os; - } - - // Print a string representation of a Compound_Selector - static void printSimpleSelector(Simple_Selector* pSimpleSelector, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - - if (pSimpleSelector) { - std::cerr << "[" << *pSimpleSelector << "]"; - } else { - std::cerr << "NULL"; - } - - if (newline) { - std::cerr << std::endl; - } - } - - // Print a string representation of a Compound_Selector - static void printCompoundSelector(Compound_Selector* pCompoundSelector, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - - if (pCompoundSelector) { - std::cerr << "[" << *pCompoundSelector << "]"; - } else { - std::cerr << "NULL"; - } - - if (newline) { - std::cerr << std::endl; - } - } - - - std::ostream& operator<<(std::ostream& os, Complex_Selector& complexSelector) { - - os << "["; - Complex_Selector* pIter = &complexSelector; - bool first = true; - while (pIter) { - if (pIter->combinator() != Complex_Selector::ANCESTOR_OF) { - if (!first) { - os << ", "; - } - first = false; - os << pIter->combinator(); - } - - if (!first) { - os << ", "; - } - first = false; - - if (pIter->head()) { - os << pIter->head()->to_string(); - } else { - os << "NULL_HEAD"; - } - - pIter = pIter->tail(); - } - os << "]"; - - return os; - } - - - // Print a string representation of a Complex_Selector - static void printComplexSelector(Complex_Selector* pComplexSelector, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - - if (pComplexSelector) { - std::cerr << *pComplexSelector; - } else { - std::cerr << "NULL"; - } - - if (newline) { - std::cerr << std::endl; - } - } - - static void printSelsNewSeqPairCollection(SubSetMapLookups& collection, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - bool first = true; - std::cerr << "["; - for(SubSetMapLookup& pair : collection) { - if (first) { - first = false; - } else { - std::cerr << ", "; - } - std::cerr << "["; - Compound_Selector* pSels = pair.first; - Complex_Selector* pNewSelector = pair.second; - std::cerr << "[" << *pSels << "], "; - printComplexSelector(pNewSelector, NULL, false); - } - std::cerr << "]"; - - if (newline) { - std::cerr << std::endl; - } - } - - // Print a string representation of a ComplexSelectorSet - static void printSourcesSet(ComplexSelectorSet& sources, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - - // Convert to a deque of strings so we can sort since order doesn't matter in a set. This should cut down on - // the differences we see when debug printing. - typedef std::deque SourceStrings; - SourceStrings sourceStrings; - for (ComplexSelectorSet::iterator iterator = sources.begin(), iteratorEnd = sources.end(); iterator != iteratorEnd; ++iterator) { - Complex_Selector* pSource = *iterator; - std::stringstream sstream; - sstream << complexSelectorToNode(pSource); - sourceStrings.push_back(sstream.str()); - } - - // Sort to get consistent output - std::sort(sourceStrings.begin(), sourceStrings.end()); - - std::cerr << "ComplexSelectorSet["; - for (SourceStrings::iterator iterator = sourceStrings.begin(), iteratorEnd = sourceStrings.end(); iterator != iteratorEnd; ++iterator) { - std::string source = *iterator; - if (iterator != sourceStrings.begin()) { - std::cerr << ", "; - } - std::cerr << source; - } - std::cerr << "]"; - - if (newline) { - std::cerr << std::endl; - } - } - - - std::ostream& operator<<(std::ostream& os, SubSetMapPairs& entries) { - os << "SUBSET_MAP_ENTRIES["; - - for (SubSetMapPairs::iterator iterator = entries.begin(), endIterator = entries.end(); iterator != endIterator; ++iterator) { - Complex_Selector_Obj pExtComplexSelector = iterator->first; // The selector up to where the @extend is (ie, the thing to merge) - Compound_Selector_Obj pExtCompoundSelector = iterator->second; // The stuff after the @extend - - if (iterator != entries.begin()) { - os << ", "; - } - - os << "("; - - if (pExtComplexSelector) { - std::cerr << *pExtComplexSelector; - } else { - std::cerr << "NULL"; - } - - os << " -> "; - - if (pExtCompoundSelector) { - std::cerr << *pExtCompoundSelector; - } else { - std::cerr << "NULL"; - } - - os << ")"; - - } - - os << "]"; - - return os; - } -#endif - - static bool parentSuperselector(Complex_Selector* pOne, Complex_Selector* pTwo) { - // TODO: figure out a better way to create a Complex_Selector from scratch - // TODO: There's got to be a better way. This got ugly quick... - Type_Selector_Obj fakeParent = SASS_MEMORY_NEW(Type_Selector, ParserState("[FAKE]"), "temp"); - Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/); - fakeHead->elements().push_back(fakeParent); - Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, {} /*tail*/); - - pOne->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); - pTwo->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); - - bool isSuperselector = pOne->is_superselector_of(pTwo); - - pOne->clear_innermost(); - pTwo->clear_innermost(); - - return isSuperselector; - } - - void nodeToComplexSelectorDeque(const Node& node, ComplexSelectorDeque& out) { - for (NodeDeque::iterator iter = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) { - Node& child = *iter; - out.push_back(nodeToComplexSelector(child)); - } - } - - Node complexSelectorDequeToNode(const ComplexSelectorDeque& deque) { - Node result = Node::createCollection(); - - for (ComplexSelectorDeque::const_iterator iter = deque.begin(), iterEnd = deque.end(); iter != iterEnd; iter++) { - Complex_Selector_Obj pChild = *iter; - result.collection()->push_back(complexSelectorToNode(pChild)); - } - - return result; - } - - class LcsCollectionComparator { - public: - LcsCollectionComparator() {} - - bool operator()(Complex_Selector_Obj pOne, Complex_Selector_Obj pTwo, Complex_Selector_Obj& pOut) const { - /* - This code is based on the following block from ruby sass' subweave - do |s1, s2| - next s1 if s1 == s2 - next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence) - next s2 if parent_superselector?(s1, s2) - next s1 if parent_superselector?(s2, s1) - end - */ - - if (*pOne == *pTwo) { - pOut = pOne; - return true; - } - - if (pOne->combinator() != Complex_Selector::ANCESTOR_OF || pTwo->combinator() != Complex_Selector::ANCESTOR_OF) { - return false; - } - - if (parentSuperselector(pOne, pTwo)) { - pOut = pTwo; - return true; - } - - if (parentSuperselector(pTwo, pOne)) { - pOut = pOne; - return true; - } - - return false; - } - }; - - - /* - This is the equivalent of ruby's Sass::Util.lcs_backtrace. - - # Computes a single longest common subsequence for arrays x and y. - # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS - */ - void lcs_backtrace(const LCSTable& c, ComplexSelectorDeque& x, ComplexSelectorDeque& y, int i, int j, const LcsCollectionComparator& comparator, ComplexSelectorDeque& out) { - //DEBUG_PRINTLN(LCS, "LCSBACK: X=" << x << " Y=" << y << " I=" << i << " J=" << j) - // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output - - if (i == 0 || j == 0) { - DEBUG_PRINTLN(LCS, "RETURNING EMPTY") - return; - } - - - Complex_Selector_Obj pCompareOut; - if (comparator(x[i], y[j], pCompareOut)) { - DEBUG_PRINTLN(LCS, "RETURNING AFTER ELEM COMPARE") - lcs_backtrace(c, x, y, i - 1, j - 1, comparator, out); - out.push_back(pCompareOut); - return; - } - - if (c[i][j - 1] > c[i - 1][j]) { - DEBUG_PRINTLN(LCS, "RETURNING AFTER TABLE COMPARE") - lcs_backtrace(c, x, y, i, j - 1, comparator, out); - return; - } - - DEBUG_PRINTLN(LCS, "FINAL RETURN") - lcs_backtrace(c, x, y, i - 1, j, comparator, out); - return; - } - - /* - This is the equivalent of ruby's Sass::Util.lcs_table. - - # Calculates the memoization table for the Least Common Subsequence algorithm. - # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS - */ - void lcs_table(const ComplexSelectorDeque& x, const ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, LCSTable& out) { - //DEBUG_PRINTLN(LCS, "LCSTABLE: X=" << x << " Y=" << y) - // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output - - LCSTable c(x.size(), std::vector(y.size())); - - // These shouldn't be necessary since the vector will be initialized to 0 already. - // x.size.times {|i| c[i][0] = 0} - // y.size.times {|j| c[0][j] = 0} - - for (size_t i = 1; i < x.size(); i++) { - for (size_t j = 1; j < y.size(); j++) { - Complex_Selector_Obj pCompareOut; - - if (comparator(x[i], y[j], pCompareOut)) { - c[i][j] = c[i - 1][j - 1] + 1; - } else { - c[i][j] = std::max(c[i][j - 1], c[i - 1][j]); - } - } - } - - out = c; - } - - /* - This is the equivalent of ruby's Sass::Util.lcs. - - # Computes a single longest common subsequence for `x` and `y`. - # If there are more than one longest common subsequences, - # the one returned is that which starts first in `x`. - - # @param x [NodeCollection] - # @param y [NodeCollection] - # @comparator An equality check between elements of `x` and `y`. - # @return [NodeCollection] The LCS - - http://en.wikipedia.org/wiki/Longest_common_subsequence_problem - */ - void lcs(ComplexSelectorDeque& x, ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, ComplexSelectorDeque& out) { - //DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y) - // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output - - x.push_front({}); - y.push_front({}); - - LCSTable table; - lcs_table(x, y, comparator, table); - - return lcs_backtrace(table, x, y, static_cast(x.size()) - 1, static_cast(y.size()) - 1, comparator, out); - } - - - /* - This is the equivalent of ruby's Sequence.trim. - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - # Avoid truly horrific quadratic behavior. TODO: I think there - # may be a way to get perfect trimming without going quadratic. - return seqses if seqses.size > 100 - - # Keep the results in a separate array so we can be sure we aren't - # comparing against an already-trimmed selector. This ensures that two - # identical selectors don't mutually trim one another. - result = seqses.dup - - # This is n^2 on the sequences, but only comparing between - # separate sequences should limit the quadratic behavior. - seqses.each_with_index do |seqs1, i| - tempResult = [] - - for seq1 in seqs1 do - max_spec = 0 - for seq in _sources(seq1) do - max_spec = [max_spec, seq.specificity].max - end - - - isMoreSpecificOuter = false - for seqs2 in result do - if seqs1.equal?(seqs2) then - next - end - - # Second Law of Extend: the specificity of a generated selector - # should never be less than the specificity of the extending - # selector. - # - # See https://github.com/nex3/sass/issues/324. - isMoreSpecificInner = false - for seq2 in seqs2 do - isMoreSpecificInner = _specificity(seq2) >= max_spec && _superselector?(seq2, seq1) - if isMoreSpecificInner then - break - end - end - - if isMoreSpecificInner then - isMoreSpecificOuter = true - break - end - end - - if !isMoreSpecificOuter then - tempResult.push(seq1) - end - end - - result[i] = tempResult - - end - - result - */ - /* - - IMPROVEMENT: We could probably work directly in the output trimmed deque. - */ - Node Extend::trim(Node& seqses, bool isReplace) { - // See the comments in the above ruby code before embarking on understanding this function. - - // Avoid poor performance in extreme cases. - if (seqses.collection()->size() > 100) { - return seqses; - } - - - DEBUG_PRINTLN(TRIM, "TRIM: " << seqses) - - - Node result = Node::createCollection(); - result.plus(seqses); - - DEBUG_PRINTLN(TRIM, "RESULT INITIAL: " << result) - - // Normally we use the standard STL iterators, but in this case, we need to access the result collection by index since we're - // iterating the input collection, computing a value, and then setting the result in the output collection. We have to keep track - // of the index manually. - int toTrimIndex = 0; - - for (NodeDeque::iterator seqsesIter = seqses.collection()->begin(), seqsesIterEnd = seqses.collection()->end(); seqsesIter != seqsesIterEnd; ++seqsesIter) { - Node& seqs1 = *seqsesIter; - - DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1 << " " << toTrimIndex) - - Node tempResult = Node::createCollection(); - tempResult.got_line_feed = seqs1.got_line_feed; - - for (NodeDeque::iterator seqs1Iter = seqs1.collection()->begin(), seqs1EndIter = seqs1.collection()->end(); seqs1Iter != seqs1EndIter; ++seqs1Iter) { - Node& seq1 = *seqs1Iter; - - Complex_Selector_Obj pSeq1 = nodeToComplexSelector(seq1); - - // Compute the maximum specificity. This requires looking at the "sources" of the sequence. See SimpleSequence.sources in the ruby code - // for a good description of sources. - // - // TODO: I'm pretty sure there's a bug in the sources code. It was implemented for sass-spec's 182_test_nested_extend_loop test. - // While the test passes, I compared the state of each trim call to verify correctness. The last trim call had incorrect sources. We - // had an extra source that the ruby version did not have. Without a failing test case, this is going to be extra hard to find. My - // best guess at this point is that we're cloning an object somewhere and maintaining the sources when we shouldn't be. This is purely - // a guess though. - unsigned long maxSpecificity = isReplace ? pSeq1->specificity() : 0; - ComplexSelectorSet sources = pSeq1->sources(); - - DEBUG_PRINTLN(TRIM, "TRIM SEQ1: " << seq1) - DEBUG_EXEC(TRIM, printSourcesSet(sources, "TRIM SOURCES: ")) - - for (ComplexSelectorSet::iterator sourcesSetIterator = sources.begin(), sourcesSetIteratorEnd = sources.end(); sourcesSetIterator != sourcesSetIteratorEnd; ++sourcesSetIterator) { - const Complex_Selector_Obj& pCurrentSelector = *sourcesSetIterator; - maxSpecificity = std::max(maxSpecificity, pCurrentSelector->specificity()); - } - - DEBUG_PRINTLN(TRIM, "MAX SPECIFICITY: " << maxSpecificity) - - bool isMoreSpecificOuter = false; - - int resultIndex = 0; - - for (NodeDeque::iterator resultIter = result.collection()->begin(), resultIterEnd = result.collection()->end(); resultIter != resultIterEnd; ++resultIter) { - Node& seqs2 = *resultIter; - - DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1) - DEBUG_PRINTLN(TRIM, "SEQS2: " << seqs2) - - // Do not compare the same sequence to itself. The ruby call we're trying to - // emulate is: seqs1.equal?(seqs2). equal? is an object comparison, not an equivalency comparision. - // Since we have the same pointers in seqes and results, we can do a pointer comparision. seqs1 is - // derived from seqses and seqs2 is derived from result. - if (seqs1.collection() == seqs2.collection()) { - DEBUG_PRINTLN(TRIM, "CONTINUE") - continue; - } - - bool isMoreSpecificInner = false; - - for (NodeDeque::iterator seqs2Iter = seqs2.collection()->begin(), seqs2IterEnd = seqs2.collection()->end(); seqs2Iter != seqs2IterEnd; ++seqs2Iter) { - Node& seq2 = *seqs2Iter; - - Complex_Selector_Obj pSeq2 = nodeToComplexSelector(seq2); - - DEBUG_PRINTLN(TRIM, "SEQ2 SPEC: " << pSeq2->specificity()) - DEBUG_PRINTLN(TRIM, "IS SPEC: " << pSeq2->specificity() << " >= " << maxSpecificity << " " << (pSeq2->specificity() >= maxSpecificity ? "true" : "false")) - DEBUG_PRINTLN(TRIM, "IS SUPER: " << (pSeq2->is_superselector_of(pSeq1) ? "true" : "false")) - - isMoreSpecificInner = pSeq2->specificity() >= maxSpecificity && pSeq2->is_superselector_of(pSeq1); - - if (isMoreSpecificInner) { - DEBUG_PRINTLN(TRIM, "FOUND MORE SPECIFIC") - break; - } - } - - // If we found something more specific, we're done. Let the outer loop know and stop iterating. - if (isMoreSpecificInner) { - isMoreSpecificOuter = true; - break; - } - - resultIndex++; - } - - if (!isMoreSpecificOuter) { - DEBUG_PRINTLN(TRIM, "PUSHING: " << seq1) - tempResult.collection()->push_back(seq1); - } - - } - - DEBUG_PRINTLN(TRIM, "RESULT BEFORE ASSIGN: " << result) - DEBUG_PRINTLN(TRIM, "TEMP RESULT: " << toTrimIndex << " " << tempResult) - (*result.collection())[toTrimIndex] = tempResult; - - toTrimIndex++; - - DEBUG_PRINTLN(TRIM, "RESULT: " << result) - } - - return result; - } - - - - static bool parentSuperselector(const Node& one, const Node& two) { - // TODO: figure out a better way to create a Complex_Selector from scratch - // TODO: There's got to be a better way. This got ugly quick... - Type_Selector_Obj fakeParent = SASS_MEMORY_NEW(Type_Selector, ParserState("[FAKE]"), "temp"); - Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/); - fakeHead->elements().push_back(fakeParent); - Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, {} /*tail*/); - - Complex_Selector_Obj pOneWithFakeParent = nodeToComplexSelector(one); - pOneWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); - Complex_Selector_Obj pTwoWithFakeParent = nodeToComplexSelector(two); - pTwoWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); - - return pOneWithFakeParent->is_superselector_of(pTwoWithFakeParent); - } - - - class ParentSuperselectorChunker { - public: - ParentSuperselectorChunker(Node& lcs) : mLcs(lcs) {} - Node& mLcs; - - bool operator()(const Node& seq) const { - // {|s| parent_superselector?(s.first, lcs.first)} - if (seq.collection()->size() == 0) return false; - return parentSuperselector(seq.collection()->front(), mLcs.collection()->front()); - } - }; - - class SubweaveEmptyChunker { - public: - bool operator()(const Node& seq) const { - // {|s| s.empty?} - - return seq.collection()->empty(); - } - }; - - /* - # Takes initial subsequences of `seq1` and `seq2` and returns all - # orderings of those subsequences. The initial subsequences are determined - # by a block. - # - # Destructively removes the initial subsequences of `seq1` and `seq2`. - # - # For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|` - # denoting the boundary of the initial subsequence), this would return - # `[(A B C 1 2), (1 2 A B C)]`. The sequences would then be `(D E)` and - # `(3 4 5)`. - # - # @param seq1 [Array] - # @param seq2 [Array] - # @yield [a] Used to determine when to cut off the initial subsequences. - # Called repeatedly for each sequence until it returns true. - # @yieldparam a [Array] A final subsequence of one input sequence after - # cutting off some initial subsequence. - # @yieldreturn [Boolean] Whether or not to cut off the initial subsequence - # here. - # @return [Array] All possible orderings of the initial subsequences. - def chunks(seq1, seq2) - chunk1 = [] - chunk1 << seq1.shift until yield seq1 - chunk2 = [] - chunk2 << seq2.shift until yield seq2 - return [] if chunk1.empty? && chunk2.empty? - return [chunk2] if chunk1.empty? - return [chunk1] if chunk2.empty? - [chunk1 + chunk2, chunk2 + chunk1] - end - */ - template - static Node chunks(Node& seq1, Node& seq2, const ChunkerType& chunker) { - Node chunk1 = Node::createCollection(); - while (seq1.collection()->size() && !chunker(seq1)) { - chunk1.collection()->push_back(seq1.collection()->front()); - seq1.collection()->pop_front(); - } - - Node chunk2 = Node::createCollection(); - while (!seq2.collection()->empty() && !chunker(seq2)) { - chunk2.collection()->push_back(seq2.collection()->front()); - seq2.collection()->pop_front(); - } - - if (chunk1.collection()->empty() && chunk2.collection()->empty()) { - DEBUG_PRINTLN(CHUNKS, "RETURNING BOTH EMPTY") - return Node::createCollection(); - } - - if (chunk1.collection()->empty()) { - Node chunk2Wrapper = Node::createCollection(); - chunk2Wrapper.collection()->push_back(chunk2); - DEBUG_PRINTLN(CHUNKS, "RETURNING ONE EMPTY") - return chunk2Wrapper; - } - - if (chunk2.collection()->empty()) { - Node chunk1Wrapper = Node::createCollection(); - chunk1Wrapper.collection()->push_back(chunk1); - DEBUG_PRINTLN(CHUNKS, "RETURNING TWO EMPTY") - return chunk1Wrapper; - } - - Node perms = Node::createCollection(); - - Node firstPermutation = Node::createCollection(); - firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end()); - firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end()); - perms.collection()->push_back(firstPermutation); - - Node secondPermutation = Node::createCollection(); - secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end()); - secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end()); - perms.collection()->push_back(secondPermutation); - - DEBUG_PRINTLN(CHUNKS, "RETURNING PERM") - - return perms; - } - - - static Node groupSelectors(Node& seq) { - Node newSeq = Node::createCollection(); - - Node tail = Node::createCollection(); - tail.plus(seq); - - while (!tail.collection()->empty()) { - Node head = Node::createCollection(); - - do { - head.collection()->push_back(tail.collection()->front()); - tail.collection()->pop_front(); - } while (!tail.collection()->empty() && (head.collection()->back().isCombinator() || tail.collection()->front().isCombinator())); - - newSeq.collection()->push_back(head); - } - - return newSeq; - } - - - static void getAndRemoveInitialOps(Node& seq, Node& ops) { - NodeDeque& seqCollection = *(seq.collection()); - NodeDeque& opsCollection = *(ops.collection()); - - while (seqCollection.size() > 0 && seqCollection.front().isCombinator()) { - opsCollection.push_back(seqCollection.front()); - seqCollection.pop_front(); - } - } - - - static void getAndRemoveFinalOps(Node& seq, Node& ops) { - NodeDeque& seqCollection = *(seq.collection()); - NodeDeque& opsCollection = *(ops.collection()); - - while (seqCollection.size() > 0 && seqCollection.back().isCombinator()) { - opsCollection.push_back(seqCollection.back()); // Purposefully reversed to match ruby code - seqCollection.pop_back(); - } - } - - - /* - def merge_initial_ops(seq1, seq2) - ops1, ops2 = [], [] - ops1 << seq1.shift while seq1.first.is_a?(String) - ops2 << seq2.shift while seq2.first.is_a?(String) - - newline = false - newline ||= !!ops1.shift if ops1.first == "\n" - newline ||= !!ops2.shift if ops2.first == "\n" - - # If neither sequence is a subsequence of the other, they cannot be - # merged successfully - lcs = Sass::Util.lcs(ops1, ops2) - return unless lcs == ops1 || lcs == ops2 - return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2) - end - */ - static Node mergeInitialOps(Node& seq1, Node& seq2) { - Node ops1 = Node::createCollection(); - Node ops2 = Node::createCollection(); - - getAndRemoveInitialOps(seq1, ops1); - getAndRemoveInitialOps(seq2, ops2); - - // TODO: Do we have this information available to us? - // newline = false - // newline ||= !!ops1.shift if ops1.first == "\n" - // newline ||= !!ops2.shift if ops2.first == "\n" - - // If neither sequence is a subsequence of the other, they cannot be merged successfully - DefaultLcsComparator lcsDefaultComparator; - Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator); - - if (!(opsLcs == ops1 || opsLcs == ops2)) { - return Node::createNil(); - } - - // TODO: more newline logic - // return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2) - - return (ops1.collection()->size() > ops2.collection()->size() ? ops1 : ops2); - } - - - /* - def merge_final_ops(seq1, seq2, res = []) - - - # This code looks complicated, but it's actually just a bunch of special - # cases for interactions between different combinators. - op1, op2 = ops1.first, ops2.first - if op1 && op2 - sel1 = seq1.pop - sel2 = seq2.pop - if op1 == '~' && op2 == '~' - if sel1.superselector?(sel2) - res.unshift sel2, '~' - elsif sel2.superselector?(sel1) - res.unshift sel1, '~' - else - merged = sel1.unify(sel2.members, sel2.subject?) - res.unshift [ - [sel1, '~', sel2, '~'], - [sel2, '~', sel1, '~'], - ([merged, '~'] if merged) - ].compact - end - elsif (op1 == '~' && op2 == '+') || (op1 == '+' && op2 == '~') - if op1 == '~' - tilde_sel, plus_sel = sel1, sel2 - else - tilde_sel, plus_sel = sel2, sel1 - end - - if tilde_sel.superselector?(plus_sel) - res.unshift plus_sel, '+' - else - merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?) - res.unshift [ - [tilde_sel, '~', plus_sel, '+'], - ([merged, '+'] if merged) - ].compact - end - elsif op1 == '>' && %w[~ +].include?(op2) - res.unshift sel2, op2 - seq1.push sel1, op1 - elsif op2 == '>' && %w[~ +].include?(op1) - res.unshift sel1, op1 - seq2.push sel2, op2 - elsif op1 == op2 - return unless merged = sel1.unify(sel2.members, sel2.subject?) - res.unshift merged, op1 - else - # Unknown selector combinators can't be unified - return - end - return merge_final_ops(seq1, seq2, res) - elsif op1 - seq2.pop if op1 == '>' && seq2.last && seq2.last.superselector?(seq1.last) - res.unshift seq1.pop, op1 - return merge_final_ops(seq1, seq2, res) - else # op2 - seq1.pop if op2 == '>' && seq1.last && seq1.last.superselector?(seq2.last) - res.unshift seq2.pop, op2 - return merge_final_ops(seq1, seq2, res) - end - end - */ - static Node mergeFinalOps(Node& seq1, Node& seq2, Node& res) { - - Node ops1 = Node::createCollection(); - Node ops2 = Node::createCollection(); - - getAndRemoveFinalOps(seq1, ops1); - getAndRemoveFinalOps(seq2, ops2); - - // TODO: do we have newlines to remove? - // ops1.reject! {|o| o == "\n"} - // ops2.reject! {|o| o == "\n"} - - if (ops1.collection()->empty() && ops2.collection()->empty()) { - return res; - } - - if (ops1.collection()->size() > 1 || ops2.collection()->size() > 1) { - DefaultLcsComparator lcsDefaultComparator; - Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator); - - // If there are multiple operators, something hacky's going on. If one is a supersequence of the other, use that, otherwise give up. - - if (!(opsLcs == ops1 || opsLcs == ops2)) { - return Node::createNil(); - } - - if (ops1.collection()->size() > ops2.collection()->size()) { - res.collection()->insert(res.collection()->begin(), ops1.collection()->rbegin(), ops1.collection()->rend()); - } else { - res.collection()->insert(res.collection()->begin(), ops2.collection()->rbegin(), ops2.collection()->rend()); - } - - return res; - } - - if (!ops1.collection()->empty() && !ops2.collection()->empty()) { - - Node op1 = ops1.collection()->front(); - Node op2 = ops2.collection()->front(); - - Node sel1 = seq1.collection()->back(); - seq1.collection()->pop_back(); - - Node sel2 = seq2.collection()->back(); - seq2.collection()->pop_back(); - - if (op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::PRECEDES) { - - if (sel1.selector()->is_superselector_of(sel2.selector())) { - - res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/); - res.collection()->push_front(sel2); - - } else if (sel2.selector()->is_superselector_of(sel1.selector())) { - - res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/); - res.collection()->push_front(sel1); - - } else { - - DEBUG_PRINTLN(ALL, "sel1: " << sel1) - DEBUG_PRINTLN(ALL, "sel2: " << sel2) - - Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result - // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?) - Compound_Selector* pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head()); - pMergedWrapper->head(pMerged); - - DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) - - Node newRes = Node::createCollection(); - - Node firstPerm = Node::createCollection(); - firstPerm.collection()->push_back(sel1); - firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - firstPerm.collection()->push_back(sel2); - firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - newRes.collection()->push_back(firstPerm); - - Node secondPerm = Node::createCollection(); - secondPerm.collection()->push_back(sel2); - secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - secondPerm.collection()->push_back(sel1); - secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - newRes.collection()->push_back(secondPerm); - - if (pMerged) { - Node mergedPerm = Node::createCollection(); - mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper)); - mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - newRes.collection()->push_back(mergedPerm); - } - - res.collection()->push_front(newRes); - - DEBUG_PRINTLN(ALL, "RESULT: " << res) - - } - - } else if (((op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::ADJACENT_TO)) || ((op1.combinator() == Complex_Selector::ADJACENT_TO && op2.combinator() == Complex_Selector::PRECEDES))) { - - Node tildeSel = sel1; - Node plusSel = sel2; - Node plusOp = op2; - if (op1.combinator() != Complex_Selector::PRECEDES) { - tildeSel = sel2; - plusSel = sel1; - plusOp = op1; - } - - if (tildeSel.selector()->is_superselector_of(plusSel.selector())) { - - res.collection()->push_front(plusOp); - res.collection()->push_front(plusSel); - - } else { - - DEBUG_PRINTLN(ALL, "PLUS SEL: " << plusSel) - DEBUG_PRINTLN(ALL, "TILDE SEL: " << tildeSel) - - Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(plusSel.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result - // TODO: does subject matter? Ruby: merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?) - Compound_Selector* pMerged = plusSel.selector()->head()->unify_with(tildeSel.selector()->head()); - pMergedWrapper->head(pMerged); - - DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) - - Node newRes = Node::createCollection(); - - Node firstPerm = Node::createCollection(); - firstPerm.collection()->push_back(tildeSel); - firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - firstPerm.collection()->push_back(plusSel); - firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO)); - newRes.collection()->push_back(firstPerm); - - if (pMerged) { - Node mergedPerm = Node::createCollection(); - mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper)); - mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO)); - newRes.collection()->push_back(mergedPerm); - } - - res.collection()->push_front(newRes); - - DEBUG_PRINTLN(ALL, "RESULT: " << res) - - } - } else if (op1.combinator() == Complex_Selector::PARENT_OF && (op2.combinator() == Complex_Selector::PRECEDES || op2.combinator() == Complex_Selector::ADJACENT_TO)) { - - res.collection()->push_front(op2); - res.collection()->push_front(sel2); - - seq1.collection()->push_back(sel1); - seq1.collection()->push_back(op1); - - } else if (op2.combinator() == Complex_Selector::PARENT_OF && (op1.combinator() == Complex_Selector::PRECEDES || op1.combinator() == Complex_Selector::ADJACENT_TO)) { - - res.collection()->push_front(op1); - res.collection()->push_front(sel1); - - seq2.collection()->push_back(sel2); - seq2.collection()->push_back(op2); - - } else if (op1.combinator() == op2.combinator()) { - - DEBUG_PRINTLN(ALL, "sel1: " << sel1) - DEBUG_PRINTLN(ALL, "sel2: " << sel2) - - Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result - // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?) - Compound_Selector* pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head()); - pMergedWrapper->head(pMerged); - - DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) - - if (!pMerged) { - return Node::createNil(); - } - - res.collection()->push_front(op1); - res.collection()->push_front(Node::createSelector(pMergedWrapper)); - - DEBUG_PRINTLN(ALL, "RESULT: " << res) - - } else { - return Node::createNil(); - } - - return mergeFinalOps(seq1, seq2, res); - - } else if (!ops1.collection()->empty()) { - - Node op1 = ops1.collection()->front(); - - if (op1.combinator() == Complex_Selector::PARENT_OF && !seq2.collection()->empty() && seq2.collection()->back().selector()->is_superselector_of(seq1.collection()->back().selector())) { - seq2.collection()->pop_back(); - } - - // TODO: consider unshift(NodeCollection, Node) - res.collection()->push_front(op1); - res.collection()->push_front(seq1.collection()->back()); - seq1.collection()->pop_back(); - - return mergeFinalOps(seq1, seq2, res); - - } else { // !ops2.collection()->empty() - - Node op2 = ops2.collection()->front(); - - if (op2.combinator() == Complex_Selector::PARENT_OF && !seq1.collection()->empty() && seq1.collection()->back().selector()->is_superselector_of(seq2.collection()->back().selector())) { - seq1.collection()->pop_back(); - } - - res.collection()->push_front(op2); - res.collection()->push_front(seq2.collection()->back()); - seq2.collection()->pop_back(); - - return mergeFinalOps(seq1, seq2, res); - - } - - } - - - /* - This is the equivalent of ruby's Sequence.subweave. - - Here is the original subweave code for reference during porting. - - def subweave(seq1, seq2) - return [seq2] if seq1.empty? - return [seq1] if seq2.empty? - - seq1, seq2 = seq1.dup, seq2.dup - return unless init = merge_initial_ops(seq1, seq2) - return unless fin = merge_final_ops(seq1, seq2) - seq1 = group_selectors(seq1) - seq2 = group_selectors(seq2) - lcs = Sass::Util.lcs(seq2, seq1) do |s1, s2| - next s1 if s1 == s2 - next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence) - next s2 if parent_superselector?(s1, s2) - next s1 if parent_superselector?(s2, s1) - end - - diff = [[init]] - until lcs.empty? - diff << chunks(seq1, seq2) {|s| parent_superselector?(s.first, lcs.first)} << [lcs.shift] - seq1.shift - seq2.shift - end - diff << chunks(seq1, seq2) {|s| s.empty?} - diff += fin.map {|sel| sel.is_a?(Array) ? sel : [sel]} - diff.reject! {|c| c.empty?} - - result = Sass::Util.paths(diff).map {|p| p.flatten}.reject {|p| path_has_two_subjects?(p)} - - result - end - */ - Node subweave(Node& one, Node& two) { - // Check for the simple cases - if (one.collection()->size() == 0) { - Node out = Node::createCollection(); - out.collection()->push_back(two); - return out; - } - if (two.collection()->size() == 0) { - Node out = Node::createCollection(); - out.collection()->push_back(one); - return out; - } - - Node seq1 = Node::createCollection(); - seq1.plus(one); - Node seq2 = Node::createCollection(); - seq2.plus(two); - - DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE ONE: " << seq1) - DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE TWO: " << seq2) - - Node init = mergeInitialOps(seq1, seq2); - if (init.isNil()) { - return Node::createNil(); - } - - DEBUG_PRINTLN(SUBWEAVE, "INIT: " << init) - - Node res = Node::createCollection(); - Node fin = mergeFinalOps(seq1, seq2, res); - if (fin.isNil()) { - return Node::createNil(); - } - - DEBUG_PRINTLN(SUBWEAVE, "FIN: " << fin) - - - // Moving this line up since fin isn't modified between now and when it happened before - // fin.map {|sel| sel.is_a?(Array) ? sel : [sel]} - - for (NodeDeque::iterator finIter = fin.collection()->begin(), finEndIter = fin.collection()->end(); - finIter != finEndIter; ++finIter) { - - Node& childNode = *finIter; - - if (!childNode.isCollection()) { - Node wrapper = Node::createCollection(); - wrapper.collection()->push_back(childNode); - childNode = wrapper; - } - - } - - DEBUG_PRINTLN(SUBWEAVE, "FIN MAPPED: " << fin) - - - - Node groupSeq1 = groupSelectors(seq1); - DEBUG_PRINTLN(SUBWEAVE, "SEQ1: " << groupSeq1) - - Node groupSeq2 = groupSelectors(seq2); - DEBUG_PRINTLN(SUBWEAVE, "SEQ2: " << groupSeq2) - - - ComplexSelectorDeque groupSeq1Converted; - nodeToComplexSelectorDeque(groupSeq1, groupSeq1Converted); - - ComplexSelectorDeque groupSeq2Converted; - nodeToComplexSelectorDeque(groupSeq2, groupSeq2Converted); - - ComplexSelectorDeque out; - LcsCollectionComparator collectionComparator; - lcs(groupSeq2Converted, groupSeq1Converted, collectionComparator, out); - Node seqLcs = complexSelectorDequeToNode(out); - - DEBUG_PRINTLN(SUBWEAVE, "SEQLCS: " << seqLcs) - - - Node initWrapper = Node::createCollection(); - initWrapper.collection()->push_back(init); - Node diff = Node::createCollection(); - diff.collection()->push_back(initWrapper); - - DEBUG_PRINTLN(SUBWEAVE, "DIFF INIT: " << diff) - - - while (!seqLcs.collection()->empty()) { - ParentSuperselectorChunker superselectorChunker(seqLcs); - Node chunksResult = chunks(groupSeq1, groupSeq2, superselectorChunker); - diff.collection()->push_back(chunksResult); - - Node lcsWrapper = Node::createCollection(); - lcsWrapper.collection()->push_back(seqLcs.collection()->front()); - seqLcs.collection()->pop_front(); - diff.collection()->push_back(lcsWrapper); - - if (groupSeq1.collection()->size()) groupSeq1.collection()->pop_front(); - if (groupSeq2.collection()->size()) groupSeq2.collection()->pop_front(); - } - - DEBUG_PRINTLN(SUBWEAVE, "DIFF POST LCS: " << diff) - - - DEBUG_PRINTLN(SUBWEAVE, "CHUNKS: ONE=" << groupSeq1 << " TWO=" << groupSeq2) - - - SubweaveEmptyChunker emptyChunker; - Node chunksResult = chunks(groupSeq1, groupSeq2, emptyChunker); - diff.collection()->push_back(chunksResult); - - - DEBUG_PRINTLN(SUBWEAVE, "DIFF POST CHUNKS: " << diff) - - - diff.collection()->insert(diff.collection()->end(), fin.collection()->begin(), fin.collection()->end()); - - DEBUG_PRINTLN(SUBWEAVE, "DIFF POST FIN MAPPED: " << diff) - - // JMA - filter out the empty nodes (use a new collection, since iterator erase() invalidates the old collection) - Node diffFiltered = Node::createCollection(); - for (NodeDeque::iterator diffIter = diff.collection()->begin(), diffEndIter = diff.collection()->end(); - diffIter != diffEndIter; ++diffIter) { - Node& node = *diffIter; - if (node.collection() && !node.collection()->empty()) { - diffFiltered.collection()->push_back(node); - } - } - diff = diffFiltered; - - DEBUG_PRINTLN(SUBWEAVE, "DIFF POST REJECT: " << diff) - - - Node pathsResult = paths(diff); - - DEBUG_PRINTLN(SUBWEAVE, "PATHS: " << pathsResult) - - - // We're flattening in place - for (NodeDeque::iterator pathsIter = pathsResult.collection()->begin(), pathsEndIter = pathsResult.collection()->end(); - pathsIter != pathsEndIter; ++pathsIter) { - - Node& child = *pathsIter; - child = flatten(child); - } - - DEBUG_PRINTLN(SUBWEAVE, "FLATTENED: " << pathsResult) - - - /* - TODO: implement - rejected = mapped.reject {|p| path_has_two_subjects?(p)} - $stderr.puts "REJECTED: #{rejected}" - */ - - - return pathsResult; - - } - /* - // disabled to avoid clang warning [-Wunused-function] - static Node subweaveNaive(const Node& one, const Node& two) { - Node out = Node::createCollection(); - - // Check for the simple cases - if (one.isNil()) { - out.collection()->push_back(two.klone()); - } else if (two.isNil()) { - out.collection()->push_back(one.klone()); - } else { - // Do the naive implementation. pOne = A B and pTwo = C D ...yields... A B C D and C D A B - // See https://gist.github.com/nex3/7609394 for details. - - Node firstPerm = one.klone(); - Node twoCloned = two.klone(); - firstPerm.plus(twoCloned); - out.collection()->push_back(firstPerm); - - Node secondPerm = two.klone(); - Node oneCloned = one.klone(); - secondPerm.plus(oneCloned ); - out.collection()->push_back(secondPerm); - } - - return out; - } - */ - - - /* - This is the equivalent of ruby's Sequence.weave. - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - def weave(path) - # This function works by moving through the selector path left-to-right, - # building all possible prefixes simultaneously. These prefixes are - # `befores`, while the remaining parenthesized suffixes is `afters`. - befores = [[]] - afters = path.dup - - until afters.empty? - current = afters.shift.dup - last_current = [current.pop] - - tempResult = [] - - for before in befores do - sub = subweave(before, current) - if sub.nil? - next - end - - for seqs in sub do - tempResult.push(seqs + last_current) - end - end - - befores = tempResult - - end - - return befores - end - */ - /* - def weave(path) - befores = [[]] - afters = path.dup - - until afters.empty? - current = afters.shift.dup - - last_current = [current.pop] - - - tempResult = [] - - for before in befores do - sub = subweave(before, current) - - if sub.nil? - next [] - end - - - for seqs in sub do - toPush = seqs + last_current - - tempResult.push(seqs + last_current) - end - - end - - befores = tempResult - - end - - return befores - end - */ - Node Extend::weave(Node& path) { - - DEBUG_PRINTLN(WEAVE, "WEAVE: " << path) - - Node befores = Node::createCollection(); - befores.collection()->push_back(Node::createCollection()); - - Node afters = Node::createCollection(); - afters.plus(path); - - while (!afters.collection()->empty()) { - Node current = afters.collection()->front().klone(); - afters.collection()->pop_front(); - DEBUG_PRINTLN(WEAVE, "CURRENT: " << current) - if (current.collection()->size() == 0) continue; - - Node last_current = Node::createCollection(); - last_current.collection()->push_back(current.collection()->back()); - current.collection()->pop_back(); - DEBUG_PRINTLN(WEAVE, "CURRENT POST POP: " << current) - DEBUG_PRINTLN(WEAVE, "LAST CURRENT: " << last_current) - - Node tempResult = Node::createCollection(); - - for (NodeDeque::iterator beforesIter = befores.collection()->begin(), beforesEndIter = befores.collection()->end(); beforesIter != beforesEndIter; beforesIter++) { - Node& before = *beforesIter; - - Node sub = subweave(before, current); - - DEBUG_PRINTLN(WEAVE, "SUB: " << sub) - - if (sub.isNil()) { - return Node::createCollection(); - } - - for (NodeDeque::iterator subIter = sub.collection()->begin(), subEndIter = sub.collection()->end(); subIter != subEndIter; subIter++) { - Node& seqs = *subIter; - - Node toPush = Node::createCollection(); - toPush.plus(seqs); - toPush.plus(last_current); - - // move line feed from inner to outer selector (very hacky indeed) - if (last_current.collection() && last_current.collection()->front().selector()) { - toPush.got_line_feed = last_current.collection()->front().got_line_feed; - last_current.collection()->front().selector()->has_line_feed(false); - last_current.collection()->front().got_line_feed = false; - } - - tempResult.collection()->push_back(toPush); - - } - } - - befores = tempResult; - - } - - return befores; - } - - - - /* - This is the equivalent of ruby's SimpleSequence.do_extend. - - // TODO: I think I have some modified ruby code to put here. Check. - */ - /* - ISSUES: - - Previous TODO: Do we need to group the results by extender? - - What does subject do in?: next unless unified = seq.members.last.unify(self_without_sel, subject?) - - IMPROVEMENT: The search for uniqueness at the end is not ideal since it's has to loop over everything... - - IMPROVEMENT: Check if the final search for uniqueness is doing anything that extendComplexSelector isn't already doing... - */ - template - class GroupByToAFunctor { - public: - KeyType operator()(SubSetMapPair& extPair) const { - Complex_Selector_Obj pSelector = extPair.first; - return pSelector; - } - }; - Node Extend::extendCompoundSelector(Compound_Selector* pSelector, CompoundSelectorSet& seen, bool isReplace) { - - /* this turned out to be too much overhead - probably due to holding a "Node" object - // check if we already extended this selector - // we can do this since subset_map is "static" - auto memoized = memoizeCompound.find(pSelector); - if (memoized != memoizeCompound.end()) { - return memoized->second.klone(); - } - */ - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND: ")) - // TODO: Ruby has another loop here to skip certain members? - - // let RESULTS be an empty list of complex selectors - Node results = Node::createCollection(); - // extendedSelectors.got_line_feed = true; - - SubSetMapPairs entries = subset_map.get_v(pSelector); - - GroupByToAFunctor extPairKeyFunctor; - SubSetMapResults arr; - group_by_to_a(entries, extPairKeyFunctor, arr); - - SubSetMapLookups holder; - - // for each (EXTENDER, TARGET) in MAP.get(COMPOUND): - for (SubSetMapResult& groupedPair : arr) { - - Complex_Selector_Obj seq = groupedPair.first; - SubSetMapPairs& group = groupedPair.second; - - DEBUG_EXEC(EXTEND_COMPOUND, printComplexSelector(seq, "SEQ: ")) - - Compound_Selector_Obj pSels = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate()); - for (SubSetMapPair& pair : group) { - pair.second->extended(true); - pSels->concat(pair.second); - } - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSels, "SELS: ")) - - // The selector up to where the @extend is (ie, the thing to merge) - Complex_Selector* pExtComplexSelector = seq; - - // TODO: This can return a Compound_Selector with no elements. Should that just be returning NULL? - // RUBY: self_without_sel = Sass::Util.array_minus(members, sels) - Compound_Selector_Obj pSelectorWithoutExtendSelectors = pSelector->minus(pSels); - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "MEMBERS: ")) - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "SELF_WO_SEL: ")) - - Compound_Selector_Obj pInnermostCompoundSelector = pExtComplexSelector->last()->head(); - - if (!pInnermostCompoundSelector) { - pInnermostCompoundSelector = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate()); - } - Compound_Selector_Obj pUnifiedSelector = pInnermostCompoundSelector->unify_with(pSelectorWithoutExtendSelectors); - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pInnermostCompoundSelector, "LHS: ")) - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "RHS: ")) - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pUnifiedSelector, "UNIFIED: ")) - - // RUBY: next unless unified - if (!pUnifiedSelector || pUnifiedSelector->length() == 0) { - continue; - } - - // TODO: implement the parent directive match (if necessary based on test failures) - // next if group.map {|e, _| check_directives_match!(e, parent_directives)}.none? - - // TODO: This seems a little fishy to me. See if it causes any problems. From the ruby, we should be able to just - // get rid of the last Compound_Selector and replace it with this one. I think the reason this code is more - // complex is that Complex_Selector contains a combinator, but in ruby combinators have already been filtered - // out and aren't operated on. - Complex_Selector_Obj pNewSelector = SASS_MEMORY_CLONE(pExtComplexSelector); // ->first(); - - Complex_Selector_Obj pNewInnerMost = SASS_MEMORY_NEW(Complex_Selector, pSelector->pstate(), Complex_Selector::ANCESTOR_OF, pUnifiedSelector, {}); - - Complex_Selector::Combinator combinator = pNewSelector->clear_innermost(); - pNewSelector->set_innermost(pNewInnerMost, combinator); - -#ifdef DEBUG - ComplexSelectorSet debugSet; - debugSet = pNewSelector->sources(); - if (debugSet.size() > 0) { - throw std::runtime_error("The new selector should start with no sources. Something needs to be cloned to fix this."); - } - debugSet = pExtComplexSelector->sources(); - if (debugSet.size() > 0) { - throw std::runtime_error("The extension selector from our subset map should not have sources. These will bleed to the new selector. Something needs to be cloned to fix this."); - } -#endif - - - // if (pSelector && pSelector->has_line_feed()) pNewInnerMost->has_line_feed(true); - // Set the sources on our new Complex_Selector to the sources of this simple sequence plus the thing we're extending. - DEBUG_PRINTLN(EXTEND_COMPOUND, "SOURCES SETTING ON NEW SEQ: " << complexSelectorToNode(pNewSelector)) - - DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet oldSet = pNewSelector->sources(); printSourcesSet(oldSet, "SOURCES NEW SEQ BEGIN: ")) - - // I actually want to create a copy here (performance!) - ComplexSelectorSet newSourcesSet = pSelector->sources(); // XXX - DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, "SOURCES THIS EXTEND: ")) - - newSourcesSet.insert(pExtComplexSelector); - DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, "SOURCES WITH NEW SOURCE: ")) - - // RUBY: new_seq.add_sources!(sources + [seq]) - pNewSelector->addSources(newSourcesSet); - - DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet newSet = pNewSelector->sources(); printSourcesSet(newSet, "SOURCES ON NEW SELECTOR AFTER ADD: ")) - DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(pSelector->sources(), "SOURCES THIS EXTEND WHICH SHOULD BE SAME STILL: ")) - - - if (pSels->has_line_feed()) pNewSelector->has_line_feed(true); - - holder.push_back(std::make_pair(pSels, pNewSelector)); - } - - - for (SubSetMapLookup& pair : holder) { - - Compound_Selector_Obj pSels = pair.first; - Complex_Selector_Obj pNewSelector = pair.second; - - - // RUBY??: next [] if seen.include?(sels) - if (seen.find(pSels) != seen.end()) { - continue; - } - - - CompoundSelectorSet recurseSeen(seen); - recurseSeen.insert(pSels); - - - DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND: " << complexSelectorToNode(pNewSelector)) - Node recurseExtendedSelectors = extendComplexSelector(pNewSelector, recurseSeen, isReplace, false); // !:isOriginal - - DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND RETURN: " << recurseExtendedSelectors) - - for (NodeDeque::iterator iterator = recurseExtendedSelectors.collection()->begin(), endIterator = recurseExtendedSelectors.collection()->end(); - iterator != endIterator; ++iterator) { - Node newSelector = *iterator; - -// DEBUG_PRINTLN(EXTEND_COMPOUND, "EXTENDED AT THIS POINT: " << results) -// DEBUG_PRINTLN(EXTEND_COMPOUND, "SELECTOR EXISTS ALREADY: " << newSelector << " " << results.contains(newSelector, false /*simpleSelectorOrderDependent*/)); - - if (!results.contains(newSelector)) { -// DEBUG_PRINTLN(EXTEND_COMPOUND, "ADDING NEW SELECTOR") - results.collection()->push_back(newSelector); - } - } - } - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND END: ")) - - // this turned out to be too much overhead - // memory results in a map table - since extending is very expensive - // memoizeCompound.insert(std::pair(pSelector, results)); - - return results; - } - - - // check if selector has something to be extended by subset_map - bool Extend::complexSelectorHasExtension(Complex_Selector* selector, CompoundSelectorSet& seen) { - - bool hasExtension = false; - - Complex_Selector_Obj pIter = selector; - - while (!hasExtension && pIter) { - Compound_Selector_Obj pHead = pIter->head(); - - if (pHead) { - SubSetMapPairs entries = subset_map.get_v(pHead); - for (SubSetMapPair ext : entries) { - // check if both selectors have the same media block parent - // if (ext.first->media_block() == pComplexSelector->media_block()) continue; - if (ext.second->media_block() == 0) continue; - if (pHead->media_block() && - ext.second->media_block()->media_queries() && - pHead->media_block()->media_queries() - ) { - std::string query_left(ext.second->media_block()->media_queries()->to_string()); - std::string query_right(pHead->media_block()->media_queries()->to_string()); - if (query_left == query_right) continue; - } - - // fail if one goes across media block boundaries - std::stringstream err; - std::string cwd(Sass::File::get_cwd()); - ParserState pstate(ext.second->pstate()); - std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); - err << "You may not @extend an outer selector from within @media.\n"; - err << "You may only @extend selectors within the same directive.\n"; - err << "From \"@extend " << ext.second->to_string() << "\""; - err << " on line " << pstate.line+1 << " of " << rel_path << "\n"; - error(err.str(), selector->pstate(), eval->exp.traces); - } - if (entries.size() > 0) hasExtension = true; - } - - pIter = pIter->tail(); - } - - return hasExtension; - } - - - /* - This is the equivalent of ruby's Sequence.do_extend. - - // TODO: I think I have some modified ruby code to put here. Check. - */ - /* - ISSUES: - - check to automatically include combinators doesn't transfer over to libsass' data model where - the combinator and compound selector are one unit - next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence) - */ - Node Extend::extendComplexSelector(Complex_Selector* selector, CompoundSelectorSet& seen, bool isReplace, bool isOriginal) { - - // check if we already extended this selector - // we can do this since subset_map is "static" - auto memoized = memoizeComplex.find(selector); - if (memoized != memoizeComplex.end()) { - return memoized->second; - } - - // convert the input selector to extend node format - Node complexSelector = complexSelectorToNode(selector); - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector) - - // let CHOICES be an empty list of selector-lists - // create new collection to hold the results - Node choices = Node::createCollection(); - - // for each compound selector COMPOUND in COMPLEX: - for (Node& sseqOrOp : *complexSelector.collection()) { - - DEBUG_PRINTLN(EXTEND_COMPLEX, "LOOP: " << sseqOrOp) - - // If it's not a selector (meaning it's a combinator), just include it automatically - // RUBY: next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence) - if (!sseqOrOp.isSelector()) { - // Wrap our Combinator in two collections to match ruby. This is essentially making a collection Node - // with one collection child. The collection child represents a Complex_Selector that is only a combinator. - Node outer = Node::createCollection(); - Node inner = Node::createCollection(); - outer.collection()->push_back(inner); - inner.collection()->push_back(sseqOrOp); - choices.collection()->push_back(outer); - continue; - } - - // verified now that node is a valid selector - Complex_Selector_Obj sseqSel = sseqOrOp.selector(); - Compound_Selector_Obj sseqHead = sseqSel->head(); - - // let EXTENDED be extend_compound(COMPOUND, SEEN) - // extend the compound selector against the given subset_map - // RUBY: extended = sseq_or_op.do_extend(extends, parent_directives, replace, seen) - Node extended = extendCompoundSelector(sseqHead, seen, isReplace); // slow(17%)! - if (sseqOrOp.got_line_feed) extended.got_line_feed = true; - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED: " << extended) - - // Prepend the Compound_Selector based on the choices logic; choices seems to be extend but with a ruby - // Array instead of a Sequence due to the member mapping: choices = extended.map {|seq| seq.members} - // RUBY: extended.first.add_sources!([self]) if original && !has_placeholder? - if (isOriginal && !selector->has_placeholder()) { - ComplexSelectorSet srcset; - srcset.insert(selector); - sseqSel->addSources(srcset); - // DEBUG_PRINTLN(EXTEND_COMPLEX, "ADD SOURCES: " << *pComplexSelector) - } - - bool isSuperselector = false; - // if no complex selector in EXTENDED is a superselector of COMPOUND: - for (Node& childNode : *extended.collection()) { - Complex_Selector_Obj pExtensionSelector = nodeToComplexSelector(childNode); - if (pExtensionSelector->is_superselector_of(sseqSel)) { - isSuperselector = true; - break; - } - } - - if (!isSuperselector) { - // add a complex selector composed only of COMPOUND to EXTENDED - if (sseqOrOp.got_line_feed) sseqSel->has_line_feed(sseqOrOp.got_line_feed); - extended.collection()->push_front(complexSelectorToNode(sseqSel)); - } - - DEBUG_PRINTLN(EXTEND_COMPLEX, "CHOICES UNSHIFTED: " << extended) - - // add EXTENDED to CHOICES - // Aggregate our current extensions - choices.collection()->push_back(extended); - } - - - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED NOT EXPANDED: " << choices) - - - - // Ruby Equivalent: paths - Node paths = Sass::paths(choices); - - DEBUG_PRINTLN(EXTEND_COMPLEX, "PATHS: " << paths) - - // let WEAVES be an empty list of selector lists - Node weaves = Node::createCollection(); - - // for each list of complex selectors PATH in paths(CHOICES): - for (Node& path : *paths.collection()) { - // add weave(PATH) to WEAVES - Node weaved = weave(path); // slow(12%)! - weaved.got_line_feed = path.got_line_feed; - weaves.collection()->push_back(weaved); - } - - DEBUG_PRINTLN(EXTEND_COMPLEX, "WEAVES: " << weaves) - - // Ruby Equivalent: trim - Node trimmed(trim(weaves, isReplace)); // slow(19%)! - - DEBUG_PRINTLN(EXTEND_COMPLEX, "TRIMMED: " << trimmed) - - // Ruby Equivalent: flatten - Node flattened(flatten(trimmed, 1)); - - DEBUG_PRINTLN(EXTEND_COMPLEX, "FLATTENED: " << flattened) - - // memory results in a map table - since extending is very expensive - memoizeComplex.insert(std::pair(selector, flattened)); - - // return trim(WEAVES) - return flattened; - } - - - - /* - This is the equivalent of ruby's CommaSequence.do_extend. - */ - // We get a selector list with has something to extend and a subset_map with - // all extenders. Pick the ones that match our selectors in the list. - Selector_List* Extend::extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace, bool& extendedSomething, CompoundSelectorSet& seen) { - - Selector_List_Obj pNewSelectors = SASS_MEMORY_NEW(Selector_List, pSelectorList->pstate(), pSelectorList->length()); - - // check if we already extended this selector - // we can do this since subset_map is "static" - auto memoized = memoizeList.find(pSelectorList); - if (memoized != memoizeList.end()) { - extendedSomething = true; - return memoized->second; - } - - extendedSomething = false; - // process each comlplex selector in the selector list. - // Find the ones that can be extended by given subset_map. - for (size_t index = 0, length = pSelectorList->length(); index < length; index++) { - Complex_Selector_Obj pSelector = (*pSelectorList)[index]; - - // ruby sass seems to keep a list of things that have extensions and then only extend those. We don't currently do that. - // Since it's not that expensive to check if an extension exists in the subset map and since it can be relatively expensive to - // run through the extend code (which does a data model transformation), check if there is anything to extend before doing - // the extend. We might be able to optimize extendComplexSelector, but this approach keeps us closer to ruby sass (which helps - // when debugging). - if (!complexSelectorHasExtension(pSelector, seen)) { - pNewSelectors->append(pSelector); - continue; - } - - // complexSelectorHasExtension was true! - extendedSomething = true; - - // now do the actual extension of the complex selector - Node extendedSelectors = extendComplexSelector(pSelector, seen, isReplace, true); - - if (!pSelector->has_placeholder()) { - Node nSelector(complexSelectorToNode(pSelector)); - if (!extendedSelectors.contains(nSelector)) { - pNewSelectors->append(pSelector); - continue; - } - } - - bool doReplace = isReplace; - for (Node& childNode : *extendedSelectors.collection()) { - // When it is a replace, skip the first one, unless there is only one - if(doReplace && extendedSelectors.collection()->size() > 1 ) { - doReplace = false; - continue; - } - pNewSelectors->append(nodeToComplexSelector(childNode)); - } - } - - Remove_Placeholders remove_placeholders; - // it seems that we have to remove the place holders early here - // normally we do this as the very last step (compare to ruby sass) - pNewSelectors = remove_placeholders.remove_placeholders(pNewSelectors); - - // unwrap all wrapped selectors with inner lists - for (Complex_Selector_Obj cur : pNewSelectors->elements()) { - // process tails - while (cur) { - // process header - if (cur->head() && seen.find(cur->head()) == seen.end()) { - CompoundSelectorSet recseen(seen); - recseen.insert(cur->head()); - // create a copy since we add multiple items if stuff get unwrapped - Compound_Selector_Obj cpy_head = SASS_MEMORY_NEW(Compound_Selector, cur->pstate()); - for (Simple_Selector_Obj hs : *cur->head()) { - if (Wrapped_Selector_Obj ws = Cast(hs)) { - ws->selector(SASS_MEMORY_CLONE(ws->selector())); - if (Selector_List_Obj sl = Cast(ws->selector())) { - // special case for ruby ass - if (sl->empty()) { - // this seems inconsistent but it is how ruby sass seems to remove parentheses - cpy_head->append(SASS_MEMORY_NEW(Type_Selector, hs->pstate(), ws->name())); - } - // has wrapped not selectors - else if (ws->name() == ":not") { - // extend the inner list of wrapped selector - bool extended = false; - Selector_List_Obj ext_sl = extendSelectorList(sl, false, extended, recseen); - for (size_t i = 0; i < ext_sl->length(); i += 1) { - if (Complex_Selector_Obj ext_cs = ext_sl->at(i)) { - // create clones for wrapped selector and the inner list - Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(ws); - Selector_List_Obj cpy_ws_sl = SASS_MEMORY_NEW(Selector_List, sl->pstate()); - // remove parent selectors from inner selector - Compound_Selector_Obj ext_head; - if (ext_cs->first()) ext_head = ext_cs->first()->head(); - if (ext_head && ext_head && ext_head->length() > 0) { - cpy_ws_sl->append(ext_cs->mutable_first()); - } - // assign list to clone - cpy_ws->selector(cpy_ws_sl); - // append the clone - cpy_head->append(cpy_ws); - } - } - if (eval && extended) { - eval->exp.selector_stack.push_back(pNewSelectors); - cpy_head->perform(eval); - eval->exp.selector_stack.pop_back(); - } - } - // has wrapped selectors - else { - Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(ws); - Selector_List_Obj ext_sl = extendSelectorList(sl, recseen); - cpy_ws->selector(ext_sl); - cpy_head->append(cpy_ws); - } - } else { - cpy_head->append(hs); - } - } else { - cpy_head->append(hs); - } - } - // replace header - cur->head(cpy_head); - } - // process tail - cur = cur->tail(); - } - } - - // memory results in a map table - since extending is very expensive - memoizeList.insert(std::pair(pSelectorList, pNewSelectors)); - - return pNewSelectors.detach(); - - } - - - bool shouldExtendBlock(Block_Obj b) { - - // If a block is empty, there's no reason to extend it since any rules placed on this block - // won't have any output. The main benefit of this is for structures like: - // - // .a { - // .b { - // x: y; - // } - // } - // - // We end up visiting two rulesets (one with the selector .a and the other with the selector .a .b). - // In this case, we don't want to try to pull rules onto .a since they won't get output anyway since - // there are no child statements. However .a .b should have extensions applied. - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - - if (Cast(stm)) { - // Do nothing. This doesn't count as a statement that causes extension since we'll - // iterate over this rule set in a future visit and try to extend it. - } - else { - return true; - } - } - - return false; - - } - - - // Extend a ruleset by extending the selectors and updating them on the ruleset. The block's rules don't need to change. - // Every Ruleset in the whole tree is calling this function. We decide if there - // was is @extend that matches our selector. If we find one, we will go further - // and call the extend magic for our selector. The subset_map contains all blocks - // where @extend was found. Pick the ones that match our selector! - void Extend::extendObjectWithSelectorAndBlock(Ruleset* pObject) { - - DEBUG_PRINTLN(EXTEND_OBJECT, "FOUND SELECTOR: " << Cast(pObject->selector())->to_string()) - - // Ruby sass seems to filter nodes that don't have any content well before we get here. - // I'm not sure the repercussions of doing so, so for now, let's just not extend things - // that won't be output later. Profiling shows this may us 0.2% or so. - if (!shouldExtendBlock(pObject->block())) { - DEBUG_PRINTLN(EXTEND_OBJECT, "RETURNING WITHOUT EXTEND ATTEMPT") - return; - } - - bool extendedSomething = false; - - CompoundSelectorSet seen; - Selector_List_Obj pNewSelectorList = extendSelectorList(pObject->selector(), false, extendedSomething, seen); - - if (extendedSomething && pNewSelectorList) { - DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << pObject->selector()->to_string()) - DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND SETTING NEW SELECTORS: " << pNewSelectorList->to_string()) - pNewSelectorList->remove_parent_selectors(); - pObject->selector(pNewSelectorList); - } else { - DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND DID NOT TRY TO EXTEND ANYTHING") - } - } - - Extend::Extend(Subset_Map& ssm) - : subset_map(ssm), eval(NULL) - { } - - void Extend::setEval(Eval& e) { - eval = &e; - } - - void Extend::operator()(Block* b) - { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - stm->perform(this); - } - // do final check if everything was extended - // we set `extended` flag on extended selectors - if (b->is_root()) { - // debug_subset_map(subset_map); - for(auto const &it : subset_map.values()) { - const Complex_Selector* sel = nullptr; - const Compound_Selector* ext = nullptr; - if (it.first) sel = it.first->first(); - if (it.second) ext = it.second; - if (ext && (ext->extended() || ext->is_optional())) continue; - std::string str_sel(sel ? sel->to_string({ NESTED, 5 }) : "NULL"); - std::string str_ext(ext ? ext->to_string({ NESTED, 5 }) : "NULL"); - // debug_ast(sel, "sel: "); - // debug_ast(ext, "ext: "); - error("\"" + str_sel + "\" failed to @extend \"" + str_ext + "\".\n" - "The selector \"" + str_ext + "\" was not found.\n" - "Use \"@extend " + str_ext + " !optional\" if the" - " extend should be able to fail.", (ext ? ext->pstate() : NULL), eval->exp.traces); - } - } - - } - - void Extend::operator()(Ruleset* pRuleset) - { - extendObjectWithSelectorAndBlock( pRuleset ); - pRuleset->block()->perform(this); - } - - void Extend::operator()(Supports_Block* pFeatureBlock) - { - pFeatureBlock->block()->perform(this); - } - - void Extend::operator()(Media_Block* pMediaBlock) - { - pMediaBlock->block()->perform(this); - } - - void Extend::operator()(Directive* a) - { - // Selector_List* ls = Cast(a->selector()); - // selector_stack.push_back(ls); - if (a->block()) a->block()->perform(this); - // exp.selector_stack.pop_back(); - } -} diff --git a/src/extend.hpp b/src/extend.hpp deleted file mode 100644 index fdff61abb..000000000 --- a/src/extend.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef SASS_EXTEND_H -#define SASS_EXTEND_H - -#include -#include - -#include "ast.hpp" -#include "node.hpp" -#include "eval.hpp" -#include "operation.hpp" -#include "subset_map.hpp" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - Node subweave(Node& one, Node& two); - - class Extend : public Operation_CRTP { - - Subset_Map& subset_map; - Eval* eval; - - private: - - std::unordered_map< - Selector_List_Obj, // key - Selector_List_Obj, // value - HashNodes, // hasher - CompareNodes // compare - > memoizeList; - - std::unordered_map< - Complex_Selector_Obj, // key - Node, // value - HashNodes, // hasher - CompareNodes // compare - > memoizeComplex; - - /* this turned out to be too much overhead - re-evaluate once we store an ast selector - std::unordered_map< - Compound_Selector_Obj, // key - Node, // value - HashNodes, // hasher - CompareNodes // compare - > memoizeCompound; - */ - - void extendObjectWithSelectorAndBlock(Ruleset* pObject); - Node extendComplexSelector(Complex_Selector* sel, CompoundSelectorSet& seen, bool isReplace, bool isOriginal); - Node extendCompoundSelector(Compound_Selector* sel, CompoundSelectorSet& seen, bool isReplace); - bool complexSelectorHasExtension(Complex_Selector* selector, CompoundSelectorSet& seen); - Node trim(Node& seqses, bool isReplace); - Node weave(Node& path); - - public: - void setEval(Eval& eval); - Selector_List* extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace, bool& extendedSomething, CompoundSelectorSet& seen); - Selector_List* extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace = false) { - bool extendedSomething = false; - CompoundSelectorSet seen; - return extendSelectorList(pSelectorList, isReplace, extendedSomething, seen); - } - Selector_List* extendSelectorList(Selector_List_Obj pSelectorList, CompoundSelectorSet& seen) { - bool isReplace = false; - bool extendedSomething = false; - return extendSelectorList(pSelectorList, isReplace, extendedSomething, seen); - } - Extend(Subset_Map&); - ~Extend() { } - - void operator()(Block*); - void operator()(Ruleset*); - void operator()(Supports_Block*); - void operator()(Media_Block*); - void operator()(Directive*); - - // ignore missed types - template - void fallback(U x) {} - - }; - -} - -#endif diff --git a/src/extender.cpp b/src/extender.cpp new file mode 100644 index 000000000..345acf8bf --- /dev/null +++ b/src/extender.cpp @@ -0,0 +1,1171 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include "ast.hpp" + +#include "extender.hpp" +#include "permutate.hpp" +#include "dart_helpers.hpp" + +namespace Sass { + + // ########################################################################## + // Constructor without default [mode]. + // [traces] are needed to throw errors. + // ########################################################################## + Extender::Extender(Backtraces& traces) : + mode(NORMAL), + traces(traces), + selectors(), + extensions(), + extensionsByExtender(), + mediaContexts(), + sourceSpecificity(), + originals() + {} + + // ########################################################################## + // Constructor with specific [mode]. + // [traces] are needed to throw errors. + // ########################################################################## + Extender::Extender(ExtendMode mode, Backtraces& traces) : + mode(mode), + traces(traces), + selectors(), + extensions(), + extensionsByExtender(), + mediaContexts(), + sourceSpecificity(), + originals() + {} + + // ########################################################################## + // Extends [selector] with [source] extender and [targets] extendees. + // This works as though `source {@extend target}` were written in the + // stylesheet, with the exception that [target] can contain compound + // selectors which must be extended as a unit. + // ########################################################################## + SelectorListObj Extender::extend( + SelectorListObj& selector, + SelectorListObj& source, + SelectorListObj& targets, + Backtraces& traces) + { + return extendOrReplace(selector, source, targets, ExtendMode::TARGETS, traces); + } + // EO Extender::extend + + // ########################################################################## + // Returns a copy of [selector] with [targets] replaced by [source]. + // ########################################################################## + SelectorListObj Extender::replace( + SelectorListObj& selector, + SelectorListObj& source, + SelectorListObj& targets, + Backtraces& traces) + { + return extendOrReplace(selector, source, targets, ExtendMode::REPLACE, traces); + } + // EO Extender::replace + + // ########################################################################## + // A helper function for [extend] and [replace]. + // ########################################################################## + SelectorListObj Extender::extendOrReplace( + SelectorListObj& selector, + SelectorListObj& source, + SelectorListObj& targets, + ExtendMode mode, + Backtraces& traces) + { + ExtSelExtMapEntry extenders; + + for (auto complex : source->elements()) { + // Extension.oneOff(complex as ComplexSelector) + extenders.insert(complex, Extension(complex)); + } + + for (auto complex : targets->elements()) { + + if (complex->length() != 1) { + // throw "can't extend complex selector $complex." + } + + if (auto compound = complex->first()->getCompound()) { + + ExtSelExtMap extensions; + + for (auto simple : compound->elements()) { + extensions.insert(std::make_pair(simple, extenders)); + } + + Extender extender(mode, traces); + + if (!selector->is_invisible()) { + for (auto sel : selector->elements()) { + extender.originals.insert(sel); + } + } + + selector = extender.extendList(selector, extensions, {}); + + } + + } + + return selector; + + } + // EO extendOrReplace + + // ########################################################################## + // The set of all simple selectors in style rules handled + // by this extender. This includes simple selectors that + // were added because of downstream extensions. + // ########################################################################## + ExtSmplSelSet Extender::getSimpleSelectors() const + { + ExtSmplSelSet set; + for (auto& entry : selectors) { + set.insert(entry.first); + } + return set; + } + // EO getSimpleSelectors + + // ########################################################################## + // Check for extends that have not been satisfied. + // Returns true if any non-optional extension did not + // extend any selector. Updates the passed reference + // to point to that Extension for further analysis. + // ########################################################################## + bool Extender::checkForUnsatisfiedExtends(Extension& unsatisfied) const + { + ExtSmplSelSet originals = getSimpleSelectors(); + for (auto target : extensions) { + SimpleSelector* key = target.first; + ExtSelExtMapEntry& val = target.second; + if (originals.find(key) == originals.end()) { + const Extension& extension = val.front().second; + if (extension.isOptional) continue; + unsatisfied = extension; + return true; + } + } + return false; + } + // EO checkUnsatisfiedExtends + + // ########################################################################## + // Adds [selector] to this extender, with [selectorSpan] as the span covering + // the selector and [ruleSpan] as the span covering the entire style rule. + // Extends [selector] using any registered extensions, then returns an empty + // [ModifiableCssStyleRule] with the resulting selector. If any more relevant + // extensions are added, the returned rule is automatically updated. + // The [mediaContext] is the media query context in which the selector was + // defined, or `null` if it was defined at the top level of the document. + // ########################################################################## + void Extender::addSelector( + const SelectorListObj& selector, + const CssMediaRuleObj& mediaContext) + { + + // Note: dart-sass makes a copy here AFAICT + // Note: probably why we have originalStack + // SelectorListObj original = selector; + + if (!selector->isInvisible()) { + for (auto complex : selector->elements()) { + originals.insert(complex); + } + } + + if (!extensions.empty()) { + + SelectorListObj res = extendList(selector, extensions, mediaContext); + + selector->elements(res->elements()); + + } + + if (!mediaContext.isNull()) { + mediaContexts.insert(selector, mediaContext); + } + + registerSelector(selector, selector); + + } + // EO addSelector + + // ########################################################################## + // Registers the [SimpleSelector]s in [list] + // to point to [rule] in [selectors]. + // ########################################################################## + void Extender::registerSelector( + const SelectorListObj& list, + const SelectorListObj& rule) + { + if (list.isNull() || list->empty()) return; + for (auto complex : list->elements()) { + for (auto component : complex->elements()) { + if (auto compound = component->getCompound()) { + for (SimpleSelector* simple : compound->elements()) { + selectors[simple].insert(rule); + if (auto pseudo = simple->getPseudoSelector()) { + if (pseudo->selector()) { + auto sel = pseudo->selector(); + registerSelector(sel, rule); + } + } + } + } + } + } + } + // EO registerSelector + + // ########################################################################## + // Returns an extension that combines [left] and [right]. Throws + // a [SassException] if [left] and [right] have incompatible + // media contexts. Throws an [ArgumentError] if [left] + // and [right] don't have the same extender and target. + // ########################################################################## + Extension Extender::mergeExtension( + const Extension& lhs, + const Extension& rhs) + { + // If one extension is optional and doesn't add a + // special media context, it doesn't need to be merged. + if (rhs.isOptional && rhs.mediaContext.isNull()) return lhs; + if (lhs.isOptional && lhs.mediaContext.isNull()) return rhs; + + Extension rv(lhs); + // ToDo: is this right? + rv.isOptional = true; + rv.isOriginal = false; + return rv; + } + // EO mergeExtension + + // ########################################################################## + // Helper function to copy extension between maps + // ########################################################################## + void Extender::mapCopyExts( + ExtSelExtMap& dest, + const ExtSelExtMap& source) + { + for (auto it : source) { + SimpleSelectorObj key = it.first; + ExtSelExtMapEntry& inner = it.second; + ExtSelExtMap::iterator dmap = dest.find(key); + if (dmap == dest.end()) { + dest.insert(std::make_pair(key, inner)); + } + else { + ExtSelExtMapEntry& imap = dmap->second; + // ToDo: optimize ordered_map API! + // ToDo: we iterate and fetch the value + for (ComplexSelectorObj& it2 : inner) { + imap.insert(it2, inner.get(it2)); + } + } + } + } + // EO mapCopyExts + + // ########################################################################## + // Adds an extension to this extender. The [extender] is the selector for the + // style rule in which the extension is defined, and [target] is the selector + // passed to `@extend`. The [extend] provides the extend span and indicates + // whether the extension is optional. The [mediaContext] defines the media query + // context in which the extension is defined. It can only extend selectors + // within the same context. A `null` context indicates no media queries. + // ########################################################################## + // ToDo: rename extender to parent, since it is not involved in extending stuff + // ToDo: check why dart sass passes the ExtendRule around (is this the main selector?) + // ########################################################################## + // Note: this function could need some logic cleanup + // ########################################################################## + void Extender::addExtension( + SelectorListObj& extender, + SimpleSelectorObj& target, + // get get passed a pointer + ExtendRuleObj extend, + CssMediaRuleObj& mediaQueryContext) + { + + auto rules = selectors.find(target); + bool hasRule = rules != selectors.end(); + + ExtSelExtMapEntry newExtensions; + + auto existingExtensions = extensionsByExtender.find(target); + bool hasExistingExtensions = existingExtensions != extensionsByExtender.end(); + + ExtSelExtMapEntry& sources = extensions[target]; + + for (auto& complex : extender->elements()) { + + Extension state(complex); + // ToDo: fine-tune public API + state.target = target; + state.isOptional = extend->isOptional(); + state.mediaContext = mediaQueryContext; + + if (sources.hasKey(complex)) { + // If there's already an extend from [extender] to [target], + // we don't need to re-run the extension. We may need to + // mark the extension as mandatory, though. + // sources.insert(complex, mergeExtension(existingState->second, state); + // ToDo: implement behavior once use case is found!? + continue; + } + + sources.insert(complex, state); + + for (auto& component : complex->elements()) { + if (auto compound = component->getCompound()) { + for (auto& simple : compound->elements()) { + extensionsByExtender[simple].push_back(state); + if (sourceSpecificity.find(simple) == sourceSpecificity.end()) { + // Only source specificity for the original selector is relevant. + // Selectors generated by `@extend` don't get new specificity. + sourceSpecificity[simple] = complex->maxSpecificity(); + } + } + } + } + + if (hasRule || hasExistingExtensions) { + newExtensions.insert(complex, state); + } + + } + // EO foreach complex + + if (newExtensions.empty()) { + return; + } + + ExtSelExtMap newExtensionsByTarget; + newExtensionsByTarget.insert(std::make_pair(target, newExtensions)); + existingExtensions = extensionsByExtender.find(target); + if (hasExistingExtensions && !existingExtensions->second.empty()) { + auto additionalExtensions = + extendExistingExtensions(existingExtensions->second, newExtensionsByTarget); + if (!additionalExtensions.empty()) { + mapCopyExts(newExtensionsByTarget, additionalExtensions); + } + } + + if (hasRule) { + extendExistingStyleRules(selectors[target], newExtensionsByTarget); + } + + } + // EO addExtension + + // ########################################################################## + // Extend [extensions] using [newExtensions]. + // ########################################################################## + // Note: dart-sass throws an error in here + // ########################################################################## + void Extender::extendExistingStyleRules( + const ExtListSelSet& rules, + const ExtSelExtMap& newExtensions) + { + // Is a modifyableCssStyleRUle in dart sass + for (const SelectorListObj& rule : rules) { + const SelectorListObj& oldValue = SASS_MEMORY_COPY(rule); + CssMediaRuleObj mediaContext; + if (mediaContexts.hasKey(rule)) mediaContext = mediaContexts.get(rule); + SelectorListObj ext = extendList(rule, newExtensions, mediaContext); + // If no extends actually happenedit (for example becaues unification + // failed), we don't need to re-register the selector. + if (ObjEqualityFn(oldValue, ext)) continue; + rule->elements(ext->elements()); + registerSelector(rule, rule); + + } + } + // EO extendExistingStyleRules + + // ########################################################################## + // Extend [extensions] using [newExtensions]. Note that this does duplicate + // some work done by [_extendExistingStyleRules], but it's necessary to + // expand each extension's extender separately without reference to the full + // selector list, so that relevant results don't get trimmed too early. + // + // Returns extensions that should be added to [newExtensions] before + // extending selectors in order to properly handle extension loops such as: + // + // .c {x: y; @extend .a} + // .x.y.a {@extend .b} + // .z.b {@extend .c} + // + // Returns `null` (Note: empty map) if there are no extensions to add. + // ########################################################################## + // Note: maybe refactor to return `bool` (and pass reference) + // Note: dart-sass throws an error in here + // ########################################################################## + ExtSelExtMap Extender::extendExistingExtensions( + // Taking in a reference here makes MSVC debug stuck!? + const std::vector oldExtensions, + ExtSelExtMap& newExtensions) + { + + ExtSelExtMap additionalExtensions; + + for (Extension extension : oldExtensions) { + ExtSelExtMapEntry& sources = extensions[extension.target]; + std::vector selectors; + + selectors = extendComplex( + extension.extender, + newExtensions, + extension.mediaContext + ); + + if (selectors.empty()) { + continue; + } + + // ToDo: "catch" error from extend + + bool first = false; + bool containsExtension = ObjEqualityFn(selectors.front(), extension.extender); + for (const ComplexSelectorObj& complex : selectors) { + // If the output contains the original complex + // selector, there's no need to recreate it. + if (containsExtension && first) { + first = false; + continue; + } + + Extension withExtender = extension.withExtender(complex); + if (sources.hasKey(complex)) { + Extension source = sources.get(complex); + sources.insert(complex, mergeExtension( + source, withExtender)); + } + else { + sources.insert(complex, withExtender); + for (auto& component : complex->elements()) { + if (auto compound = component->getCompound()) { + for (auto& simple : compound->elements()) { + extensionsByExtender[simple].push_back(withExtender); + } + } + } + if (newExtensions.find(extension.target) != newExtensions.end()) { + additionalExtensions[extension.target].insert(complex, withExtender); + } + } + } + + // If [selectors] doesn't contain [extension.extender], + // for example if it was replaced due to :not() expansion, + // we must get rid of the old version. + if (!containsExtension) { + sources.erase(extension.extender); + } + + } + + return additionalExtensions; + + } + // EO extendExistingExtensions + + // ########################################################################## + // Extends [list] using [extensions]. + // ########################################################################## + SelectorListObj Extender::extendList( + const SelectorListObj& list, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext) + { + + // This could be written more simply using [List.map], but we want to + // avoid any allocations in the common case where no extends apply. + std::vector extended; + for (size_t i = 0; i < list->length(); i++) { + const ComplexSelectorObj& complex = list->get(i); + std::vector result = + extendComplex(complex, extensions, mediaQueryContext); + if (result.empty()) { + if (!extended.empty()) { + extended.push_back(complex); + } + } + else { + if (extended.empty()) { + for (size_t n = 0; n < i; n += 1) { + extended.push_back(list->get(n)); + } + } + for (auto sel : result) { + extended.push_back(sel); + } + } + } + + if (extended.empty()) { + return list; + } + + SelectorListObj rv = SASS_MEMORY_NEW(SelectorList, list->pstate()); + rv->concat(trim(extended, originals)); + return rv; + + } + // EO extendList + + // ########################################################################## + // Extends [complex] using [extensions], and + // returns the contents of a [SelectorList]. + // ########################################################################## + std::vector Extender::extendComplex( + // Taking in a reference here makes MSVC debug stuck!? + const ComplexSelectorObj complex, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext) + { + + // The complex selectors that each compound selector in [complex.components] + // can expand to. + // + // For example, given + // + // .a .b {...} + // .x .y {@extend .b} + // + // this will contain + // + // [ + // [.a], + // [.b, .x .y] + // ] + // + // This could be written more simply using [List.map], but we want to avoid + // any allocations in the common case where no extends apply. + + std::vector result; + std::vector> extendedNotExpanded; + bool isOriginal = originals.find(complex) != originals.end(); + for (size_t i = 0; i < complex->length(); i += 1) { + const SelectorComponentObj& component = complex->get(i); + if (CompoundSelector* compound = Cast(component)) { + std::vector extended = extendCompound( + compound, extensions, mediaQueryContext, isOriginal); + if (extended.empty()) { + if (!extendedNotExpanded.empty()) { + extendedNotExpanded.push_back({ + compound->wrapInComplex() + }); + } + } + else { + // Note: dart-sass checks for null!? + if (extendedNotExpanded.empty()) { + for (size_t n = 0; n < i; n++) { + extendedNotExpanded.push_back({ + complex->at(n)->wrapInComplex() + }); + } + } + extendedNotExpanded.push_back(extended); + } + } + else { + // Note: dart-sass checks for null!? + if (!extendedNotExpanded.empty()) { + extendedNotExpanded.push_back({ + component->wrapInComplex() + }); + } + } + } + + // Note: dart-sass checks for null!? + if (extendedNotExpanded.empty()) { + return {}; + } + + bool first = true; + + // ToDo: either change weave or paths to work with the same data? + std::vector> + paths = permutate(extendedNotExpanded); + + for (const std::vector& path : paths) { + // Unpack the inner complex selector to component list + std::vector> _paths; + for (const ComplexSelectorObj& sel : path) { + _paths.insert(_paths.end(), sel->elements()); + } + + std::vector> weaved = weave(_paths); + + for (std::vector& components : weaved) { + + ComplexSelectorObj cplx = SASS_MEMORY_NEW(ComplexSelector, "[phony]"); + cplx->hasPreLineFeed(complex->hasPreLineFeed()); + for (auto& pp : path) { + if (pp->hasPreLineFeed()) { + cplx->hasPreLineFeed(true); + } + } + cplx->elements(components); + + // Make sure that copies of [complex] retain their status + // as "original" selectors. This includes selectors that + // are modified because a :not() was extended into. + if (first && originals.find(complex) != originals.end()) { + originals.insert(cplx); + } + first = false; + + result.push_back(cplx); + + } + + } + + return result; + } + // EO extendComplex + + // ########################################################################## + // Returns a one-off [Extension] whose + // extender is composed solely of [simple]. + // ########################################################################## + Extension Extender::extensionForSimple( + const SimpleSelectorObj& simple) const + { + Extension extension(simple->wrapInComplex()); + extension.specificity = maxSourceSpecificity(simple); + extension.isOriginal = true; + return extension; + } + // Extender::extensionForSimple + + // ########################################################################## + // Returns a one-off [Extension] whose extender is composed + // solely of a compound selector containing [simples]. + // ########################################################################## + Extension Extender::extensionForCompound( + // Taking in a reference here makes MSVC debug stuck!? + const std::vector simples) const + { + CompoundSelectorObj compound = SASS_MEMORY_NEW(CompoundSelector, ParserState("[ext]")); + compound->concat(simples); + Extension extension(compound->wrapInComplex()); + // extension.specificity = sourceSpecificity[simple]; + extension.isOriginal = true; + return extension; + } + // EO extensionForCompound + + // ########################################################################## + // Extends [compound] using [extensions], and returns the + // contents of a [SelectorList]. The [inOriginal] parameter + // indicates whether this is in an original complex selector, + // meaning that [compound] should not be trimmed out. + // ########################################################################## + std::vector Extender::extendCompound( + const CompoundSelectorObj& compound, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext, + bool inOriginal) + { + + // If there's more than one target and they all need to + // match, we track which targets are actually extended. + ExtSmplSelSet targetsUsed2; + + ExtSmplSelSet* targetsUsed = nullptr; + + if (mode != ExtendMode::NORMAL && extensions.size() > 1) { + targetsUsed = &targetsUsed2; + } + + std::vector result; + // The complex selectors produced from each component of [compound]. + std::vector> options; + + for (size_t i = 0; i < compound->length(); i++) { + const SimpleSelectorObj& simple = compound->get(i); + auto extended = extendSimple(simple, extensions, mediaQueryContext, targetsUsed); + if (extended.empty()) { + if (!options.empty()) { + options.push_back({ extensionForSimple(simple) }); + } + } + else { + if (options.empty()) { + if (i != 0) { + std::vector in; + for (size_t n = 0; n < i; n += 1) { + in.push_back(compound->get(n)); + } + options.push_back({ extensionForCompound(in) }); + } + } + options.insert(options.end(), + extended.begin(), extended.end()); + } + } + + if (options.empty()) { + return {}; + } + + // If [_mode] isn't [ExtendMode.normal] and we didn't use all + // the targets in [extensions], extension fails for [compound]. + if (targetsUsed != nullptr) { + + if (targetsUsed->size() != extensions.size()) { + if (!targetsUsed->empty()) { + return {}; + } + } + } + + // Optimize for the simple case of a single simple + // selector that doesn't need any unification. + if (options.size() == 1) { + std::vector exts = options[0]; + for (size_t n = 0; n < exts.size(); n += 1) { + exts[n].assertCompatibleMediaContext(mediaQueryContext, traces); + result.push_back(exts[n].extender); + } + return result; + } + + // Find all paths through [options]. In this case, each path represents a + // different unification of the base selector. For example, if we have: + // + // .a.b {...} + // .w .x {@extend .a} + // .y .z {@extend .b} + // + // then [options] is `[[.a, .w .x], [.b, .y .z]]` and `paths(options)` is + // + // [ + // [.a, .b], + // [.a, .y .z], + // [.w .x, .b], + // [.w .x, .y .z] + // ] + // + // We then unify each path to get a list of complex selectors: + // + // [ + // [.a.b], + // [.y .a.z], + // [.w .x.b], + // [.w .y .x.z, .y .w .x.z] + // ] + + bool first = mode != ExtendMode::REPLACE; + std::vector unifiedPaths; + std::vector> prePaths = permutate(options); + + for (size_t i = 0; i < prePaths.size(); i += 1) { + std::vector> complexes; + const std::vector& path = prePaths[i]; + if (first) { + // The first path is always the original selector. We can't just + // return [compound] directly because pseudo selectors may be + // modified, but we don't have to do any unification. + first = false; + CompoundSelectorObj mergedSelector = + SASS_MEMORY_NEW(CompoundSelector, "[ext]"); + for (size_t n = 0; n < path.size(); n += 1) { + const ComplexSelectorObj& sel = path[n].extender; + if (CompoundSelectorObj compound = Cast(sel->last())) { + mergedSelector->concat(compound->elements()); + } + } + complexes.push_back({ mergedSelector }); + } + else { + std::vector originals; + std::vector> toUnify; + + for (auto& state : path) { + if (state.isOriginal) { + const ComplexSelectorObj& sel = state.extender; + if (const CompoundSelector* compound = Cast(sel->last())) { + originals.insert(originals.end(), compound->last()); + } + } + else { + toUnify.push_back(state.extender->elements()); + } + } + if (!originals.empty()) { + CompoundSelectorObj merged = + SASS_MEMORY_NEW(CompoundSelector, "[phony]"); + merged->concat(originals); + toUnify.insert(toUnify.begin(), { merged }); + } + complexes = unifyComplex(toUnify); + if (complexes.empty()) { + return {}; + } + + } + + bool lineBreak = false; + // var specificity = _sourceSpecificityFor(compound); + for (const Extension& state : path) { + state.assertCompatibleMediaContext(mediaQueryContext, traces); + lineBreak = lineBreak || state.extender->hasPreLineFeed(); + // specificity = math.max(specificity, state.specificity); + } + + for (std::vector& components : complexes) { + auto sel = SASS_MEMORY_NEW(ComplexSelector, "[ext]"); + sel->hasPreLineFeed(lineBreak); + sel->elements(components); + unifiedPaths.push_back(sel); + } + + } + + return unifiedPaths; + } + // EO extendCompound + + // ########################################################################## + // Extends [simple] without extending the + // contents of any selector pseudos it contains. + // ########################################################################## + std::vector Extender::extendWithoutPseudo( + const SimpleSelectorObj& simple, + const ExtSelExtMap& extensions, + ExtSmplSelSet* targetsUsed) const + { + + auto extension = extensions.find(simple); + if (extension == extensions.end()) return {}; + const ExtSelExtMapEntry& extenders = extension->second; + + if (targetsUsed != nullptr) { + targetsUsed->insert(simple); + } + if (mode == ExtendMode::REPLACE) { + return extenders.values(); + } + + const std::vector& + values = extenders.values(); + std::vector result; + result.reserve(values.size() + 1); + result.push_back(extensionForSimple(simple)); + result.insert(result.end(), values.begin(), values.end()); + return result; + } + // EO extendWithoutPseudo + + // ########################################################################## + // Extends [simple] and also extending the + // contents of any selector pseudos it contains. + // ########################################################################## + std::vector> Extender::extendSimple( + const SimpleSelectorObj& simple, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext, + ExtSmplSelSet* targetsUsed) + { + if (Pseudo_Selector* pseudo = Cast(simple)) { + if (pseudo->selector()) { + std::vector> merged; + std::vector extended = + extendPseudo(pseudo, extensions, mediaQueryContext); + for (Pseudo_Selector_Obj& extend : extended) { + SimpleSelectorObj simple = extend; + std::vector result = + extendWithoutPseudo(simple, extensions, targetsUsed); + if (result.empty()) result = { extensionForSimple(extend) }; + merged.push_back(result); + } + if (!extended.empty()) { + return merged; + } + } + } + std::vector result = + extendWithoutPseudo(simple, extensions, targetsUsed); + if (result.empty()) return {}; + return { result }; + } + // extendSimple + + // ########################################################################## + // Inner loop helper for [extendPseudo] function + // ########################################################################## + std::vector Extender::extendPseudoComplex( + const ComplexSelectorObj& complex, + const Pseudo_Selector_Obj& pseudo, + const CssMediaRuleObj& mediaQueryContext) + { + + if (complex->length() != 1) { return { complex }; } + auto compound = Cast(complex->get(0)); + if (compound == nullptr) { return { complex }; } + if (compound->length() != 1) { return { complex }; } + auto innerPseudo = Cast(compound->get(0)); + if (innerPseudo == nullptr) { return { complex }; } + if (!innerPseudo->selector()) { return { complex }; } + + std::string name(pseudo->normalized()); + + if (name == "not") { + // In theory, if there's a `:not` nested within another `:not`, the + // inner `:not`'s contents should be unified with the return value. + // For example, if `:not(.foo)` extends `.bar`, `:not(.bar)` should + // become `.foo:not(.bar)`. However, this is a narrow edge case and + // supporting it properly would make this code and the code calling it + // a lot more complicated, so it's not supported for now. + if (innerPseudo->normalized() != "matches") return {}; + return innerPseudo->selector()->elements(); + } + else if (name == "matches" && name == "any" && name == "current" && name == "nth-child" && name == "nth-last-child") { + // As above, we could theoretically support :not within :matches, but + // doing so would require this method and its callers to handle much + // more complex cases that likely aren't worth the pain. + if (innerPseudo->name() != pseudo->name()) return {}; + if (!ObjEquality()(innerPseudo->argument(), pseudo->argument())) return {}; + return innerPseudo->selector()->elements(); + } + else if (name == "has" && name == "host" && name == "host-context" && name == "slotted") { + // We can't expand nested selectors here, because each layer adds an + // additional layer of semantics. For example, `:has(:has(img))` + // doesn't match `
` but `:has(img)` does. + return { complex }; + } + + return {}; + + } + // EO extendPseudoComplex + + // ########################################################################## + // Extends [pseudo] using [extensions], and returns + // a list of resulting pseudo selectors. + // ########################################################################## + std::vector Extender::extendPseudo( + const Pseudo_Selector_Obj& pseudo, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext) + { + auto selector = pseudo->selector(); + SelectorListObj extended = extendList( + selector, extensions, mediaQueryContext); + if (!extended || !pseudo || !pseudo->selector()) { return {}; } + if (ObjEqualityFn(pseudo->selector(), extended)) { return {}; } + + // For `:not()`, we usually want to get rid of any complex selectors because + // that will cause the selector to fail to parse on all browsers at time of + // writing. We can keep them if either the original selector had a complex + // selector, or the result of extending has only complex selectors, because + // either way we aren't breaking anything that isn't already broken. + std::vector complexes = extended->elements(); + + if (pseudo->normalized() == "not") { + if (!hasAny(pseudo->selector()->elements(), hasMoreThanOne)) { + if (hasAny(extended->elements(), hasExactlyOne)) { + complexes.clear(); + for (auto& complex : extended->elements()) { + if (complex->length() <= 1) { + complexes.push_back(complex); + } + } + } + } + } + + std::vector expanded = expand( + complexes, extendPseudoComplex, pseudo, mediaQueryContext); + + // Older browsers support `:not`, but only with a single complex selector. + // In order to support those browsers, we break up the contents of a `:not` + // unless it originally contained a selector list. + if (pseudo->normalized() == "not") { + if (pseudo->selector()->length() == 1) { + std::vector pseudos; + for (size_t i = 0; i < expanded.size(); i += 1) { + pseudos.push_back(pseudo->withSelector( + expanded[i]->wrapInList() + )); + } + return pseudos; + } + } + + SelectorListObj list = SASS_MEMORY_NEW(SelectorList, "[phony]"); + list->concat(complexes); + return { pseudo->withSelector(list) }; + + } + // EO extendPseudo + + // ########################################################################## + // Rotates the element in list from [start] (inclusive) to [end] (exclusive) + // one index higher, looping the final element back to [start]. + // ########################################################################## + void Extender::rotateSlice( + std::vector& list, + size_t start, size_t end) + { + auto element = list[end - 1]; + for (size_t i = start; i < end; i++) { + auto next = list[i]; + list[i] = element; + element = next; + } + } + // EO rotateSlice + + // ########################################################################## + // Removes elements from [selectors] if they're subselectors of other + // elements. The [isOriginal] callback indicates which selectors are + // original to the document, and thus should never be trimmed. + // ########################################################################## + // Note: for adaption I pass in the set directly, there is some + // code path in selector-trim that might need this special callback + // ########################################################################## + std::vector Extender::trim( + const std::vector& selectors, + const ExtCplxSelSet& existing) const + { + + // Avoid truly horrific quadratic behavior. + // TODO(nweiz): I think there may be a way to get perfect trimming + // without going quadratic by building some sort of trie-like + // data structure that can be used to look up superselectors. + // TODO(mgreter): Check how this perfoms in C++ (up the limit) + if (selectors.size() > 100) return selectors; + + // This is n² on the sequences, but only comparing between separate sequences + // should limit the quadratic behavior. We iterate from last to first and reverse + // the result so that, if two selectors are identical, we keep the first one. + std::vector result; size_t numOriginals = 0; + + // Use label to quit outer loop + redo: + + for (size_t i = selectors.size() - 1; i != std::string::npos; i--) { + const ComplexSelectorObj& complex1 = selectors[i]; + // Check if selector in known in existing "originals" + // For custom behavior dart-sass had `isOriginal(complex1)` + if (existing.find(complex1) != existing.end()) { + // Make sure we don't include duplicate originals, which could + // happen if a style rule extends a component of its own selector. + for (size_t j = 0; j < numOriginals; j++) { + if (ObjEqualityFn(result[j], complex1)) { + rotateSlice(result, 0, j + 1); + goto redo; + } + } + result.insert(result.begin(), complex1); + numOriginals++; + continue; + } + + // The maximum specificity of the sources that caused [complex1] + // to be generated. In order for [complex1] to be removed, there + // must be another selector that's a superselector of it *and* + // that has specificity greater or equal to this. + size_t maxSpecificity = 0; + for (const SelectorComponentObj& component : complex1->elements()) { + if (const CompoundSelectorObj compound = Cast(component)) { + maxSpecificity = std::max(maxSpecificity, maxSourceSpecificity(compound)); + } + } + + + // Look in [result] rather than [selectors] for selectors after [i]. This + // ensures we aren't comparing against a selector that's already been trimmed, + // and thus that if there are two identical selectors only one is trimmed. + if (hasAny(result, dontTrimComplex, complex1, maxSpecificity)) { + continue; + } + + // Check if any element (up to [i]) from [selector] returns true + // when passed to [dontTrimComplex]. The arguments [complex1] and + // [maxSepcificity] will be passed to the invoked function. + if (hasSubAny(selectors, i, dontTrimComplex, complex1, maxSpecificity)) { + continue; + } + + // ToDo: Maybe use deque for front insert? + result.insert(result.begin(), complex1); + + } + + return result; + + } + // EO trim + + // ########################################################################## + // Returns the maximum specificity of the given [simple] source selector. + // ########################################################################## + size_t Extender::maxSourceSpecificity(const SimpleSelectorObj& simple) const + { + auto it = sourceSpecificity.find(simple); + if (it == sourceSpecificity.end()) return 0; + return it->second; + } + // EO maxSourceSpecificity(SimpleSelectorObj) + + // ########################################################################## + // Returns the maximum specificity for sources that went into producing [compound]. + // ########################################################################## + size_t Extender::maxSourceSpecificity(const CompoundSelectorObj& compound) const + { + size_t specificity = 0; + for (auto simple : compound->elements()) { + size_t src = maxSourceSpecificity(simple); + specificity = std::max(specificity, src); + } + return specificity; + } + // EO maxSourceSpecificity(CompoundSelectorObj) + + // ########################################################################## + // Helper function used as callbacks on lists + // ########################################################################## + bool Extender::dontTrimComplex( + const ComplexSelector* complex2, + const ComplexSelector* complex1, + const size_t maxSpecificity) + { + if (complex2->minSpecificity() < maxSpecificity) return false; + return complex2->isSuperselectorOf(complex1); + } + // EO dontTrimComplex + + // ########################################################################## + // Helper function used as callbacks on lists + // ########################################################################## + bool Extender::hasExactlyOne(const ComplexSelectorObj& vec) + { + return vec->length() == 1; + } + // EO hasExactlyOne + + // ########################################################################## + // Helper function used as callbacks on lists + // ########################################################################## + bool Extender::hasMoreThanOne(const ComplexSelectorObj& vec) + { + return vec->length() > 1; + } + // hasMoreThanOne + +} diff --git a/src/extender.hpp b/src/extender.hpp new file mode 100644 index 000000000..7a8d3450e --- /dev/null +++ b/src/extender.hpp @@ -0,0 +1,407 @@ +#ifndef SASS_EXTENDER_H +#define SASS_EXTENDER_H + +#include +#include +#include + +#include "ast_helpers.hpp" +#include "ast_fwd_decl.hpp" +#include "operation.hpp" +#include "extension.hpp" +#include "backtrace.hpp" +#include "ordered_map.hpp" + +namespace Sass { + + // ########################################################################## + // Different hash map types used by extender + // ########################################################################## + + // This is special (ptrs!) + typedef std::unordered_set< + ComplexSelectorObj, + ObjPtrHash, + ObjPtrEquality + > ExtCplxSelSet; + + typedef std::unordered_set< + SimpleSelectorObj, + ObjHash, + ObjEquality + > ExtSmplSelSet; + + typedef std::unordered_set< + SelectorListObj, + ObjPtrHash, + ObjPtrEquality + > ExtListSelSet; + + typedef std::unordered_map< + SimpleSelectorObj, + ExtListSelSet, + ObjHash, + ObjEquality + > ExtSelMap; + + typedef ordered_map< + ComplexSelectorObj, + Extension, + ObjHash, + ObjEquality + > ExtSelExtMapEntry; + + typedef std::unordered_map< + SimpleSelectorObj, + ExtSelExtMapEntry, + ObjHash, + ObjEquality + > ExtSelExtMap; + + typedef std::unordered_map < + SimpleSelectorObj, + std::vector< + Extension + >, + ObjHash, + ObjEquality + > ExtByExtMap; + + class Extender : public Operation_CRTP { + + public: + + enum ExtendMode { TARGETS, REPLACE, NORMAL, }; + + private: + + // ########################################################################## + // The mode that controls this extender's behavior. + // ########################################################################## + ExtendMode mode; + + // ########################################################################## + // Shared backtraces with context and expander. Needed the throw + // errors when e.g. extending accross media query boundaries. + // ########################################################################## + Backtraces& traces; + + // ########################################################################## + // A map from all simple selectors in the stylesheet to the rules that + // contain them.This is used to find which rules an `@extend` applies to. + // ########################################################################## + ExtSelMap selectors; + + // ########################################################################## + // A map from all extended simple selectors + // to the sources of those extensions. + // ########################################################################## + ExtSelExtMap extensions; + + // ########################################################################## + // A map from all simple selectors in extenders to + // the extensions that those extenders define. + // ########################################################################## + ExtByExtMap extensionsByExtender; + + // ########################################################################## + // A map from CSS rules to the media query contexts they're defined in. + // This tracks the contexts in which each style rule is defined. + // If a rule is defined at the top level, it doesn't have an entry. + // ########################################################################## + ordered_map< + SelectorListObj, + CssMediaRuleObj, + ObjPtrHash, + ObjPtrEquality + > mediaContexts; + + // ########################################################################## + // A map from [SimpleSelector]s to the specificity of their source selectors. + // This tracks the maximum specificity of the [ComplexSelector] that originally + // contained each [SimpleSelector]. This allows us to ensure we don't trim any + // selectors that need to exist to satisfy the [second law that of extend][]. + // [second law of extend]: https://github.com/sass/sass/issues/324#issuecomment-4607184 + // ########################################################################## + std::unordered_map< + SimpleSelectorObj, + size_t, + ObjHash, + ObjEquality + > sourceSpecificity; + + // ########################################################################## + // A set of [ComplexSelector]s that were originally part of their + // component [SelectorList]s, as opposed to being added by `@extend`. + // This allows us to ensure that we don't trim any selectors + // that need to exist to satisfy the [first law of extend][]. + // ########################################################################## + ExtCplxSelSet originals; + + public: + + // Constructor without default [mode]. + // [traces] are needed to throw errors. + Extender(Backtraces& traces); + + // ########################################################################## + // Constructor with specific [mode]. + // [traces] are needed to throw errors. + // ########################################################################## + Extender(ExtendMode mode, Backtraces& traces); + + // ########################################################################## + // Empty desctructor + // ########################################################################## + ~Extender() {}; + + // ########################################################################## + // Extends [selector] with [source] extender and [targets] extendees. + // This works as though `source {@extend target}` were written in the + // stylesheet, with the exception that [target] can contain compound + // selectors which must be extended as a unit. + // ########################################################################## + static SelectorListObj extend( + SelectorListObj& selector, + SelectorListObj& source, + SelectorListObj& target, + Backtraces& traces); + + // ########################################################################## + // Returns a copy of [selector] with [targets] replaced by [source]. + // ########################################################################## + static SelectorListObj replace( + SelectorListObj& selector, + SelectorListObj& source, + SelectorListObj& target, + Backtraces& traces); + + // ########################################################################## + // Adds [selector] to this extender, with [selectorSpan] as the span covering + // the selector and [ruleSpan] as the span covering the entire style rule. + // Extends [selector] using any registered extensions, then returns an empty + // [ModifiableCssStyleRule] with the resulting selector. If any more relevant + // extensions are added, the returned rule is automatically updated. + // The [mediaContext] is the media query context in which the selector was + // defined, or `null` if it was defined at the top level of the document. + // ########################################################################## + void addSelector( + const SelectorListObj& selector, + const CssMediaRuleObj& mediaContext); + + // ########################################################################## + // Registers the [SimpleSelector]s in [list] + // to point to [rule] in [selectors]. + // ########################################################################## + void registerSelector( + const SelectorListObj& list, + const SelectorListObj& rule); + + // ########################################################################## + // Adds an extension to this extender. The [extender] is the selector for the + // style rule in which the extension is defined, and [target] is the selector + // passed to `@extend`. The [extend] provides the extend span and indicates + // whether the extension is optional. The [mediaContext] defines the media query + // context in which the extension is defined. It can only extend selectors + // within the same context. A `null` context indicates no media queries. + // ########################################################################## + void addExtension( + SelectorListObj& extender, + SimpleSelectorObj& target, + // gets passed by pointer + ExtendRuleObj extend, + CssMediaRuleObj& mediaQueryContext); + + // ########################################################################## + // The set of all simple selectors in style rules handled + // by this extender. This includes simple selectors that + // were added because of downstream extensions. + // ########################################################################## + ExtSmplSelSet getSimpleSelectors() const; + + // ########################################################################## + // Check for extends that have not been satisfied. + // Returns true if any non-optional extension did not + // extend any selector. Updates the passed reference + // to point to that Extension for further analysis. + // ########################################################################## + bool checkForUnsatisfiedExtends( + Extension& unsatisfied) const; + + private: + + // ########################################################################## + // A helper function for [extend] and [replace]. + // ########################################################################## + static SelectorListObj extendOrReplace( + SelectorListObj& selector, + SelectorListObj& source, + SelectorListObj& target, + ExtendMode mode, + Backtraces& traces); + + // ########################################################################## + // Returns an extension that combines [left] and [right]. Throws + // a [SassException] if [left] and [right] have incompatible + // media contexts. Throws an [ArgumentError] if [left] + // and [right] don't have the same extender and target. + // ########################################################################## + static Extension mergeExtension( + const Extension& lhs, + const Extension& rhs); + + // ########################################################################## + // Helper function to copy extension between maps + // ########################################################################## + static void mapCopyExts( + ExtSelExtMap& dest, + const ExtSelExtMap& source); + + // ########################################################################## + // Extend [extensions] using [newExtensions]. + // ########################################################################## + // Note: dart-sass throws an error in here + // ########################################################################## + void extendExistingStyleRules( + const ExtListSelSet& rules, + const ExtSelExtMap& newExtensions); + + // ########################################################################## + // Extend [extensions] using [newExtensions]. Note that this does duplicate + // some work done by [_extendExistingStyleRules], but it's necessary to + // expand each extension's extender separately without reference to the full + // selector list, so that relevant results don't get trimmed too early. + // Returns `null` (Note: empty map) if there are no extensions to add. + // ########################################################################## + ExtSelExtMap extendExistingExtensions( + // Taking in a reference here makes MSVC debug stuck!? + const std::vector extensions, + ExtSelExtMap& newExtensions); + + // ########################################################################## + // Extends [list] using [extensions]. + // ########################################################################## + SelectorListObj extendList( + const SelectorListObj& list, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaContext); + + // ########################################################################## + // Extends [complex] using [extensions], and + // returns the contents of a [SelectorList]. + // ########################################################################## + std::vector extendComplex( + // Taking in a reference here makes MSVC debug stuck!? + const ComplexSelectorObj list, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext); + + // ########################################################################## + // Returns a one-off [Extension] whose + // extender is composed solely of [simple]. + // ########################################################################## + Extension extensionForSimple( + const SimpleSelectorObj& simple) const; + + // ########################################################################## + // Returns a one-off [Extension] whose extender is composed + // solely of a compound selector containing [simples]. + // ########################################################################## + Extension extensionForCompound( + // Taking in a reference here makes MSVC debug stuck!? + const std::vector simples) const; + + // ########################################################################## + // Extends [compound] using [extensions], and returns the + // contents of a [SelectorList]. The [inOriginal] parameter + // indicates whether this is in an original complex selector, + // meaning that [compound] should not be trimmed out. + // ########################################################################## + std::vector extendCompound( + const CompoundSelectorObj& compound, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext, + bool inOriginal = false); + + // ########################################################################## + // Extends [simple] without extending the + // contents of any selector pseudos it contains. + // ########################################################################## + std::vector extendWithoutPseudo( + const SimpleSelectorObj& simple, + const ExtSelExtMap& extensions, + ExtSmplSelSet* targetsUsed) const; + + // ########################################################################## + // Extends [simple] and also extending the + // contents of any selector pseudos it contains. + // ########################################################################## + std::vector> extendSimple( + const SimpleSelectorObj& simple, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext, + ExtSmplSelSet* targetsUsed); + + // ########################################################################## + // Inner loop helper for [extendPseudo] function + // ########################################################################## + static std::vector extendPseudoComplex( + const ComplexSelectorObj& complex, + const Pseudo_Selector_Obj& pseudo, + const CssMediaRuleObj& mediaQueryContext); + + // ########################################################################## + // Extends [pseudo] using [extensions], and returns + // a list of resulting pseudo selectors. + // ########################################################################## + std::vector extendPseudo( + const Pseudo_Selector_Obj& pseudo, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext); + + // ########################################################################## + // Rotates the element in list from [start] (inclusive) to [end] (exclusive) + // one index higher, looping the final element back to [start]. + // ########################################################################## + static void rotateSlice( + std::vector& list, + size_t start, size_t end); + + // ########################################################################## + // Removes elements from [selectors] if they're subselectors of other + // elements. The [isOriginal] callback indicates which selectors are + // original to the document, and thus should never be trimmed. + // ########################################################################## + std::vector trim( + const std::vector& selectors, + const ExtCplxSelSet& set) const; + + // ########################################################################## + // Returns the maximum specificity of the given [simple] source selector. + // ########################################################################## + size_t maxSourceSpecificity(const SimpleSelectorObj& simple) const; + + // ########################################################################## + // Returns the maximum specificity for sources that went into producing [compound]. + // ########################################################################## + size_t maxSourceSpecificity(const CompoundSelectorObj& compound) const; + + // ########################################################################## + // Helper function used as callbacks on lists + // ########################################################################## + static bool dontTrimComplex( + const ComplexSelector* complex2, + const ComplexSelector* complex1, + const size_t maxSpecificity); + + // ########################################################################## + // Helper function used as callbacks on lists + // ########################################################################## + static bool hasExactlyOne(const ComplexSelectorObj& vec); + static bool hasMoreThanOne(const ComplexSelectorObj& vec); + + }; + +} + +#endif diff --git a/src/extension.cpp b/src/extension.cpp new file mode 100644 index 000000000..0b10904d4 --- /dev/null +++ b/src/extension.cpp @@ -0,0 +1,43 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include "ast_helpers.hpp" +#include "extension.hpp" +#include "ast.hpp" + +namespace Sass { + + // ########################################################################## + // Static function to create a copy with a new extender + // ########################################################################## + Extension Extension::withExtender(ComplexSelectorObj newExtender) + { + Extension extension(newExtender); + extension.specificity = specificity; + extension.isOptional = isOptional; + extension.target = target; + return extension; + } + + // ########################################################################## + // Asserts that the [mediaContext] for a selector is + // compatible with the query context for this extender. + // ########################################################################## + void Extension::assertCompatibleMediaContext(CssMediaRuleObj mediaQueryContext, Backtraces& traces) const + { + + if (this->mediaContext.isNull()) return; + + if (mediaQueryContext && ObjPtrEqualityFn(mediaContext->block(), mediaQueryContext->block())) return; + + if (ObjEqualityFn(mediaQueryContext, mediaContext)) return; + + throw Exception::ExtendAcrossMedia(traces, *this); + + } + + // ########################################################################## + // ########################################################################## + +} diff --git a/src/extension.hpp b/src/extension.hpp new file mode 100644 index 000000000..d6e1d1d70 --- /dev/null +++ b/src/extension.hpp @@ -0,0 +1,89 @@ +#ifndef SASS_EXTENSION_H +#define SASS_EXTENSION_H + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include +#include +#include "ast_fwd_decl.hpp" +#include "backtrace.hpp" + +namespace Sass { + + class Extension { + + public: + + // The selector in which the `@extend` appeared. + ComplexSelectorObj extender; + + // The selector that's being extended. + // `null` for one-off extensions. + SimpleSelectorObj target; + + // The minimum specificity required for any + // selector generated from this extender. + size_t specificity; + + // Whether this extension is optional. + bool isOptional; + + // Whether this is a one-off extender representing a selector that was + // originally in the document, rather than one defined with `@extend`. + bool isOriginal; + + bool isSatisfied; + + // The media query context to which this extend is restricted, + // or `null` if it can apply within any context. + CssMediaRuleObj mediaContext; + + // Creates a one-off extension that's not intended to be modified over time. + // If [specificity] isn't passed, it defaults to `extender.maxSpecificity`. + Extension(ComplexSelectorObj extender) : + extender(extender), + target({}), + specificity(0), + isOptional(true), + isOriginal(false), + isSatisfied(false), + mediaContext({}) { + + } + + // Copy constructor + Extension(const Extension& extension) : + extender(extension.extender), + target(extension.target), + specificity(extension.specificity), + isOptional(extension.isOptional), + isOriginal(extension.isOriginal), + isSatisfied(extension.isSatisfied), + mediaContext(extension.mediaContext) { + + } + + // Default constructor + Extension() : + extender({}), + target({}), + specificity(0), + isOptional(false), + isOriginal(false), + isSatisfied(false), + mediaContext({}) { + } + + // Asserts that the [mediaContext] for a selector is + // compatible with the query context for this extender. + void assertCompatibleMediaContext(CssMediaRuleObj mediaContext, Backtraces& traces) const; + + Extension withExtender(ComplexSelectorObj newExtender); + + }; + +} + +#endif diff --git a/src/file.hpp b/src/file.hpp index 279b9e9f6..00a86adcf 100644 --- a/src/file.hpp +++ b/src/file.hpp @@ -1,6 +1,10 @@ #ifndef SASS_FILE_H #define SASS_FILE_H +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + #include #include @@ -108,17 +112,6 @@ namespace Sass { { } }; - // parsed stylesheet from loaded resource - class StyleSheet : public Resource { - public: - // parsed root block - Block_Obj root; - public: - StyleSheet(const Resource& res, Block_Obj root) - : Resource(res), root(root) - { } - }; - namespace File { static std::vector defaultExtensions = { ".scss", ".sass", ".css" }; diff --git a/src/fn_lists.cpp b/src/fn_lists.cpp index a8d4600b3..93f3252ef 100644 --- a/src/fn_lists.cpp +++ b/src/fn_lists.cpp @@ -1,3 +1,7 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + #include "listize.hpp" #include "operators.hpp" #include "fn_utils.hpp" @@ -31,8 +35,8 @@ namespace Sass { Signature length_sig = "length($list)"; BUILT_IN(length) { - if (Selector_List* sl = Cast(env["$list"])) { - return SASS_MEMORY_NEW(Number, pstate, (double)sl->length()); + if (SelectorList * sl = Cast(env["$list"])) { + return SASS_MEMORY_NEW(Number, pstate, (double) sl->length()); } Expression* v = ARG("$list", Expression); if (v->concrete_type() == Expression::MAP) { @@ -40,9 +44,9 @@ namespace Sass { return SASS_MEMORY_NEW(Number, pstate, (double)(map ? map->length() : 1)); } if (v->concrete_type() == Expression::SELECTOR) { - if (Compound_Selector* h = Cast(v)) { + if (CompoundSelector * h = Cast(v)) { return SASS_MEMORY_NEW(Number, pstate, (double)h->length()); - } else if (Selector_List* ls = Cast(v)) { + } else if (SelectorList * ls = Cast(v)) { return SASS_MEMORY_NEW(Number, pstate, (double)ls->length()); } else { return SASS_MEMORY_NEW(Number, pstate, 1); @@ -60,15 +64,13 @@ namespace Sass { { double nr = ARGVAL("$n"); Map* m = Cast(env["$list"]); - if (Selector_List* sl = Cast(env["$list"])) { + if (SelectorList * sl = Cast(env["$list"])) { size_t len = m ? m->length() : sl->length(); bool empty = m ? m->empty() : sl->empty(); if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); double index = std::floor(nr < 0 ? len + nr : nr - 1); if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); - // return (*sl)[static_cast(index)]; - Listize listize; - return Cast((*sl)[static_cast(index)]->perform(&listize)); + return Cast(Listize::perform(sl->get(static_cast(index)))); } List_Obj l = Cast(env["$list"]); if (nr == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate, traces); @@ -189,9 +191,8 @@ namespace Sass { Map_Obj m = Cast(env["$list"]); List_Obj l = Cast(env["$list"]); Expression_Obj v = ARG("$val", Expression); - if (Selector_List* sl = Cast(env["$list"])) { - Listize listize; - l = Cast(sl->perform(&listize)); + if (SelectorList * sl = Cast(env["$list"])) { + l = Cast(Listize::perform(sl)); } String_Constant_Obj sep = ARG("$separator", String_Constant); if (!l) { diff --git a/src/fn_miscs.cpp b/src/fn_miscs.cpp index 390cf1a71..fb188109b 100644 --- a/src/fn_miscs.cpp +++ b/src/fn_miscs.cpp @@ -143,7 +143,8 @@ namespace Sass { } } Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, name, args); - Expand expand(ctx, &d_env, &selector_stack); + + Expand expand(ctx, &d_env, &selector_stack, &original_stack); func->via_call(true); // calc invoke is allowed if (ff) func->func(ff); return Cast(func->perform(&expand.eval)); @@ -160,11 +161,9 @@ namespace Sass { } Signature if_sig = "if($condition, $if-true, $if-false)"; - // BUILT_IN(sass_if) - // { return ARG("$condition", Expression)->is_false() ? ARG("$if-false", Expression) : ARG("$if-true", Expression); } BUILT_IN(sass_if) { - Expand expand(ctx, &d_env, &selector_stack); + Expand expand(ctx, &d_env, &selector_stack, &original_stack); Expression_Obj cond = ARG("$condition", Expression)->perform(&expand.eval); bool is_true = !cond->is_false(); Expression_Obj res = ARG(is_true ? "$if-true" : "$if-false", Expression); @@ -178,9 +177,6 @@ namespace Sass { // MISCELLANEOUS FUNCTIONS ////////////////////////// - // value.check_deprecated_interp if value.is_a?(Sass::Script::Value::String) - // unquoted_string(value.to_sass) - Signature inspect_sig = "inspect($value)"; BUILT_IN(inspect) { @@ -208,7 +204,6 @@ namespace Sass { ctx.c_options.output_style = old_style; return SASS_MEMORY_NEW(String_Quoted, pstate, i.get_buffer()); } - // return v; } Signature content_exists_sig = "content-exists()"; diff --git a/src/fn_selectors.cpp b/src/fn_selectors.cpp index b64973ffd..34142bb81 100644 --- a/src/fn_selectors.cpp +++ b/src/fn_selectors.cpp @@ -1,5 +1,8 @@ +#include + #include "parser.hpp" -#include "extend.hpp" +#include "extender.hpp" +#include "listize.hpp" #include "fn_utils.hpp" #include "fn_selectors.hpp" @@ -13,24 +16,27 @@ namespace Sass { List* arglist = ARG("$selectors", List); // Not enough parameters - if( arglist->length() == 0 ) - error("$selectors: At least one selector must be passed for `selector-nest'", pstate, traces); + if (arglist->length() == 0) { + error( + "$selectors: At least one selector must be passed for `selector-nest'", + pstate, traces); + } // Parse args into vector of selectors SelectorStack parsedSelectors; for (size_t i = 0, L = arglist->length(); i < L; ++i) { Expression_Obj exp = Cast(arglist->value_at_index(i)); if (exp->concrete_type() == Expression::NULL_VAL) { - std::stringstream msg; - msg << "$selectors: null is not a valid selector: it must be a string,\n"; - msg << "a list of strings, or a list of lists of strings for 'selector-nest'"; - error(msg.str(), pstate, traces); + error( + "$selectors: null is not a valid selector: it must be a string,\n" + "a list of strings, or a list of lists of strings for 'selector-nest'", + pstate, traces); } if (String_Constant_Obj str = Cast(exp)) { str->quote_mark(0); } std::string exp_src = exp->to_string(ctx.c_options); - Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); + SelectorListObj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); parsedSelectors.push_back(sel); } @@ -39,25 +45,21 @@ namespace Sass { return SASS_MEMORY_NEW(Null, pstate); } - // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. + // Set the first element as the `result`, keep + // appending to as we go down the parsedSelector vector. SelectorStack::iterator itr = parsedSelectors.begin(); - Selector_List_Obj result = *itr; + SelectorListObj& result = *itr; ++itr; for(;itr != parsedSelectors.end(); ++itr) { - Selector_List_Obj child = *itr; - std::vector exploded; - selector_stack.push_back(result); - Selector_List_Obj rv = child->resolve_parent_refs(selector_stack, traces); - selector_stack.pop_back(); - for (size_t m = 0, mLen = rv->length(); m < mLen; ++m) { - exploded.push_back((*rv)[m]); - } - result->elements(exploded); + SelectorListObj& child = *itr; + original_stack.push_back(result); + SelectorListObj rv = child->resolve_parent_refs(original_stack, traces); + result->elements(rv->elements()); + original_stack.pop_back(); } - Listize listize; - return Cast(result->perform(&listize)); + return Cast(Listize::perform(result)); } Signature selector_append_sig = "selector-append($selectors...)"; @@ -66,113 +68,96 @@ namespace Sass { List* arglist = ARG("$selectors", List); // Not enough parameters - if( arglist->length() == 0 ) - error("$selectors: At least one selector must be passed for `selector-append'", pstate, traces); + if (arglist->empty()) { + error( + "$selectors: At least one selector must be " + "passed for `selector-append'", + pstate, traces); + } // Parse args into vector of selectors SelectorStack parsedSelectors; + parsedSelectors.push_back({}); for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj exp = Cast(arglist->value_at_index(i)); + Expression* exp = Cast(arglist->value_at_index(i)); if (exp->concrete_type() == Expression::NULL_VAL) { - std::stringstream msg; - msg << "$selectors: null is not a valid selector: it must be a string,\n"; - msg << "a list of strings, or a list of lists of strings for 'selector-append'"; - error(msg.str(), pstate, traces); + error( + "$selectors: null is not a valid selector: it must be a string,\n" + "a list of strings, or a list of lists of strings for 'selector-append'", + pstate, traces); } if (String_Constant* str = Cast(exp)) { str->quote_mark(0); } std::string exp_src = exp->to_string(); - Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces, - exp->pstate(), pstate.src, - /*allow_parent=*/false); - - parsedSelectors.push_back(sel); - } - - // Nothing to do - if( parsedSelectors.empty() ) { - return SASS_MEMORY_NEW(Null, pstate); - } - - // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. - SelectorStack::iterator itr = parsedSelectors.begin(); - Selector_List_Obj result = *itr; - ++itr; - - for(;itr != parsedSelectors.end(); ++itr) { - Selector_List_Obj child = *itr; - std::vector newElements; - - // For every COMPLEX_SELECTOR in `result` - // For every COMPLEX_SELECTOR in `child` - // let parentSeqClone equal a copy of result->elements[i] - // let childSeq equal child->elements[j] - // Append all of childSeq head elements into parentSeqClone - // Set the innermost tail of parentSeqClone, to childSeq's tail - // Replace result->elements with newElements - for (size_t i = 0, resultLen = result->length(); i < resultLen; ++i) { - for (size_t j = 0, childLen = child->length(); j < childLen; ++j) { - Complex_Selector_Obj parentSeqClone = SASS_MEMORY_CLONE((*result)[i]); - Complex_Selector_Obj childSeq = (*child)[j]; - Complex_Selector_Obj base = childSeq->tail(); - - // Must be a simple sequence - if( childSeq->combinator() != Complex_Selector::Combinator::ANCESTOR_OF ) { - error("Can't append \"" + childSeq->to_string() + "\" to \"" + - parentSeqClone->to_string() + "\" for `selector-append'", pstate, traces); - } + SelectorListObj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces, + exp->pstate(), pstate.src, + /*allow_parent=*/true); - // Cannot be a Universal selector - Type_Selector_Obj pType = Cast(childSeq->head()->first()); - if(pType && pType->name() == "*") { - error("Can't append \"" + childSeq->to_string() + "\" to \"" + - parentSeqClone->to_string() + "\" for `selector-append'", pstate, traces); - } + for (auto& complex : sel->elements()) { + if (complex->empty()) { + complex->append(SASS_MEMORY_NEW(CompoundSelector, "[phony]")); + } + if (CompoundSelector* comp = Cast(complex->first())) { + comp->hasRealParent(true); + complex->chroots(true); + } + } - // TODO: Add check for namespace stuff + if (parsedSelectors.size() > 1) { - Complex_Selector* lastComponent = parentSeqClone->mutable_last(); - if (lastComponent->head() == nullptr) { - std::string msg = "Parent \"" + parentSeqClone->to_string() + "\" is incompatible with \"" + base->to_string() + "\""; - error(msg, pstate, traces); + if (!sel->has_real_parent_ref()) { + auto parent = parsedSelectors.back(); + for (auto& complex : parent->elements()) { + if (CompoundSelector* comp = Cast(complex->first())) { + comp->hasRealParent(false); + } } - lastComponent->head()->concat(base->head()); - lastComponent->tail(base->tail()); - - newElements.push_back(parentSeqClone); + error("Can't append \"" + sel->to_string() + "\" to \"" + + parent->to_string() + "\" for `selector-append'", + pstate, traces); } + + // Build the resolved stack from the left. It's cheaper to directly + // calculate and update each resolved selcted from the left, than to + // recursively calculate them from the right side, as we would need + // to go through the whole stack depth to build the final results. + // E.g. 'a', 'b', 'x, y' => 'a' => 'a b' => 'a b x, a b y' + // vs 'a', 'b', 'x, y' => 'x' => 'b x' => 'a b x', 'y' ... + parsedSelectors.push_back(sel->resolve_parent_refs(parsedSelectors, traces, true)); + } + else { + parsedSelectors.push_back(sel); } + } - result->elements(newElements); + // Nothing to do + if( parsedSelectors.empty() ) { + return SASS_MEMORY_NEW(Null, pstate); } - Listize listize; - return Cast(result->perform(&listize)); + return Cast(Listize::perform(parsedSelectors.back())); } Signature selector_unify_sig = "selector-unify($selector1, $selector2)"; BUILT_IN(selector_unify) { - Selector_List_Obj selector1 = ARGSELS("$selector1"); - Selector_List_Obj selector2 = ARGSELS("$selector2"); - - Selector_List_Obj result = selector1->unify_with(selector2); - Listize listize; - return Cast(result->perform(&listize)); + SelectorListObj selector1 = ARGSELS("$selector1"); + SelectorListObj selector2 = ARGSELS("$selector2"); + SelectorListObj result = selector1->unifyWith(selector2); + return Cast(Listize::perform(result)); } Signature simple_selectors_sig = "simple-selectors($selector)"; BUILT_IN(simple_selectors) { - Compound_Selector_Obj sel = ARGSEL("$selector"); + CompoundSelectorObj sel = ARGSEL("$selector"); List* l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); for (size_t i = 0, L = sel->length(); i < L; ++i) { - Simple_Selector_Obj ss = (*sel)[i]; + const SimpleSelectorObj& ss = sel->get(i); std::string ss_string = ss->to_string() ; - l->append(SASS_MEMORY_NEW(String_Quoted, ss->pstate(), ss_string)); } @@ -182,51 +167,36 @@ namespace Sass { Signature selector_extend_sig = "selector-extend($selector, $extendee, $extender)"; BUILT_IN(selector_extend) { - Selector_List_Obj selector = ARGSELS("$selector"); - Selector_List_Obj extendee = ARGSELS("$extendee"); - Selector_List_Obj extender = ARGSELS("$extender"); - - Subset_Map subset_map; - extender->populate_extends(extendee, subset_map); - Extend extend(subset_map); - - Selector_List_Obj result = extend.extendSelectorList(selector, false); - - Listize listize; - return Cast(result->perform(&listize)); + SelectorListObj selector = ARGSELS("$selector"); + SelectorListObj target = ARGSELS("$extendee"); + SelectorListObj source = ARGSELS("$extender"); + SelectorListObj result = Extender::extend(selector, source, target, traces); + return Cast(Listize::perform(result)); } Signature selector_replace_sig = "selector-replace($selector, $original, $replacement)"; BUILT_IN(selector_replace) { - Selector_List_Obj selector = ARGSELS("$selector"); - Selector_List_Obj original = ARGSELS("$original"); - Selector_List_Obj replacement = ARGSELS("$replacement"); - Subset_Map subset_map; - replacement->populate_extends(original, subset_map); - Extend extend(subset_map); - - Selector_List_Obj result = extend.extendSelectorList(selector, true); - - Listize listize; - return Cast(result->perform(&listize)); + SelectorListObj selector = ARGSELS("$selector"); + SelectorListObj target = ARGSELS("$original"); + SelectorListObj source = ARGSELS("$replacement"); + SelectorListObj result = Extender::replace(selector, source, target, traces); + return Cast(Listize::perform(result)); } Signature selector_parse_sig = "selector-parse($selector)"; BUILT_IN(selector_parse) { - Selector_List_Obj sel = ARGSELS("$selector"); - - Listize listize; - return Cast(sel->perform(&listize)); + SelectorListObj selector = ARGSELS("$selector"); + return Cast(Listize::perform(selector)); } Signature is_superselector_sig = "is-superselector($super, $sub)"; BUILT_IN(is_superselector) { - Selector_List_Obj sel_sup = ARGSELS("$super"); - Selector_List_Obj sel_sub = ARGSELS("$sub"); - bool result = sel_sup->is_superselector_of(sel_sub); + SelectorListObj sel_sup = ARGSELS("$super"); + SelectorListObj sel_sub = ARGSELS("$sub"); + bool result = sel_sup->isSuperselectorOf(sel_sub); return SASS_MEMORY_NEW(Boolean, pstate, result); } diff --git a/src/fn_utils.cpp b/src/fn_utils.cpp index defee09f6..ae5f0ac96 100644 --- a/src/fn_utils.cpp +++ b/src/fn_utils.cpp @@ -118,7 +118,7 @@ namespace Sass { } } - Selector_List_Obj get_arg_sels(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { + SelectorListObj get_arg_sels(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { Expression_Obj exp = ARG(argname, Expression); if (exp->concrete_type() == Expression::NULL_VAL) { std::stringstream msg; @@ -133,7 +133,7 @@ namespace Sass { return Parser::parse_selector(exp_src.c_str(), ctx, traces, exp->pstate(), pstate.src, /*allow_parent=*/false); } - Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { + CompoundSelectorObj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { Expression_Obj exp = ARG(argname, Expression); if (exp->concrete_type() == Expression::NULL_VAL) { std::stringstream msg; @@ -144,13 +144,12 @@ namespace Sass { str->quote_mark(0); } std::string exp_src = exp->to_string(ctx.c_options); - Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx, traces, exp->pstate(), pstate.src, /*allow_parent=*/false); + SelectorListObj sel_list = Parser::parse_selector(exp_src.c_str(), ctx, traces, exp->pstate(), pstate.src, /*allow_parent=*/false); if (sel_list->length() == 0) return {}; - Complex_Selector_Obj first = sel_list->first(); - if (!first->tail()) return first->head(); - return first->tail()->head(); + return sel_list->first()->first(); } + } } diff --git a/src/fn_utils.hpp b/src/fn_utils.hpp index 834efe71a..85ce4bc1a 100644 --- a/src/fn_utils.hpp +++ b/src/fn_utils.hpp @@ -1,5 +1,10 @@ #ifndef SASS_FN_UTILS_H #define SASS_FN_UTILS_H + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + #include "units.hpp" #include "backtrace.hpp" #include "environment.hpp" @@ -15,7 +20,8 @@ namespace Sass { Signature sig, \ ParserState pstate, \ Backtraces& traces, \ - SelectorStack& selector_stack + SelectorStack selector_stack, \ + SelectorStack original_stack \ typedef const char* Signature; typedef PreValue* (*Native_Function)(FN_PROTOTYPE); @@ -46,8 +52,8 @@ namespace Sass { double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces); // colors only double get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, double lo, double hi); // colors only double get_arg_val(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces); // shared - Selector_List_Obj get_arg_sels(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx); // selectors only - Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx); // selectors only + SelectorListObj get_arg_sels(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx); // selectors only + CompoundSelectorObj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx); // selectors only } diff --git a/src/inspect.cpp b/src/inspect.cpp index 3dd6b700f..21a8a54b4 100644 --- a/src/inspect.cpp +++ b/src/inspect.cpp @@ -67,15 +67,58 @@ namespace Sass { append_scope_closer(); } - void Inspect::operator()(Media_Block* media_block) + void Inspect::operator()(MediaRule* rule) { append_indentation(); - append_token("@media", media_block); + append_token("@media", rule); append_mandatory_space(); in_media_block = true; - media_block->media_queries()->perform(this); in_media_block = false; - media_block->block()->perform(this); + if (rule->block()) { + rule->block()->perform(this); + } + } + + void Inspect::operator()(CssMediaRule* rule) + { + append_indentation(); + append_token("@media", rule); + append_mandatory_space(); + in_media_block = true; + bool joinIt = false; + for (auto query : rule->elements()) { + if (joinIt) { + append_comma_separator(); + append_optional_space(); + } + operator()(query); + joinIt = true; + } + if (rule->block()) { + rule->block()->perform(this); + } + } + + void Inspect::operator()(CssMediaQuery* query) + { + bool joinIt = false; + if (!query->modifier().empty()) { + append_string(query->modifier()); + append_mandatory_space(); + } + if (!query->type().empty()) { + append_string(query->type()); + joinIt = true; + } + for (auto feature : query->features()) { + if (joinIt) { + append_mandatory_space(); + append_string("and"); + append_mandatory_space(); + } + append_string(feature); + joinIt = true; + } } void Inspect::operator()(Supports_Block* feature_block) @@ -134,8 +177,7 @@ namespace Sass { append_colon_separator(); if (dec->value()->concrete_type() == Expression::SELECTOR) { - Listize listize; - Expression_Obj ls = dec->value()->perform(&listize); + Expression_Obj ls = Listize::perform(dec->value()); ls->perform(this); } else { dec->value()->perform(this); @@ -298,7 +340,7 @@ namespace Sass { append_delimiter(); } - void Inspect::operator()(Extension* extend) + void Inspect::operator()(ExtendRule* extend) { append_indentation(); append_token("@extend", extend); @@ -399,7 +441,7 @@ namespace Sass { list->length() == 1 && !list->from_selector() && !Cast(list->at(0)) && - !Cast(list->at(0)) + !Cast(list->at(0)) ) { append_string(lbracket(list)); } @@ -449,7 +491,7 @@ namespace Sass { list->length() == 1 && !list->from_selector() && !Cast(list->at(0)) && - !Cast(list->at(0)) + !Cast(list->at(0)) ) { append_string(","); append_string(rbracket(list)); @@ -530,7 +572,7 @@ namespace Sass { ss << std::fixed << n->value(); std::string res = ss.str(); - int s = res.length(); + size_t s = res.length(); // delete trailing zeros for(s = s - 1; s > 0; --s) @@ -562,6 +604,11 @@ namespace Sass { // add unit now res += n->unit(); + if (opt.output_style == TO_CSS && !n->is_valid_css_unit()) { + // traces.push_back(Backtrace(nr->pstate())); + throw Exception::InvalidValue({}, *n); + } + // output the final token append_token(res, n); } @@ -879,16 +926,14 @@ namespace Sass { s->contents()->perform(this); } - void Inspect::operator()(Parent_Selector* p) + void Inspect::operator()(Parent_Reference* p) { - if (p->real()) append_string("&"); + append_string("&"); } void Inspect::operator()(Placeholder_Selector* s) { append_token(s->name(), s); - if (s->has_line_break()) append_optional_linefeed(); - if (s->has_line_break()) append_indentation(); } @@ -900,15 +945,11 @@ namespace Sass { void Inspect::operator()(Class_Selector* s) { append_token(s->ns_name(), s); - if (s->has_line_break()) append_optional_linefeed(); - if (s->has_line_break()) append_indentation(); } void Inspect::operator()(Id_Selector* s) { append_token(s->ns_name(), s); - if (s->has_line_break()) append_optional_linefeed(); - if (s->has_line_break()) append_indentation(); } void Inspect::operator()(Attribute_Selector* s) @@ -932,109 +973,36 @@ namespace Sass { void Inspect::operator()(Pseudo_Selector* s) { - append_token(s->ns_name(), s); - if (s->expression()) { - append_string("("); - s->expression()->perform(this); - append_string(")"); - } - } - - void Inspect::operator()(Wrapped_Selector* s) - { - if (s->name() == " ") { - append_string(""); - } else { - bool was = in_wrapped; - in_wrapped = true; - append_token(s->name(), s); - append_string("("); - bool was_comma_array = in_comma_array; - in_comma_array = false; - s->selector()->perform(this); - in_comma_array = was_comma_array; - append_string(")"); - in_wrapped = was; - } - } - - void Inspect::operator()(Compound_Selector* s) - { - for (size_t i = 0, L = s->length(); i < L; ++i) { - (*s)[i]->perform(this); - } - if (s->has_line_break()) { - if (output_style() != COMPACT) { - append_optional_linefeed(); - } - } - } - - void Inspect::operator()(Complex_Selector* c) - { - Compound_Selector_Obj head = c->head(); - Complex_Selector_Obj tail = c->tail(); - Complex_Selector::Combinator comb = c->combinator(); - - if (comb == Complex_Selector::ANCESTOR_OF && (!head || head->empty())) { - if (tail) tail->perform(this); - return; - } - if (c->has_line_feed()) { - if (!(c->has_parent_ref())) { - append_optional_linefeed(); - append_indentation(); + if (s->name() != "") { + append_string(":"); + if (s->isSyntacticElement()) { + append_string(":"); } - } - - if (head && head->length() != 0) head->perform(this); - bool is_empty = !head || head->length() == 0 || head->is_empty_reference(); - bool is_tail = head && !head->is_empty_reference() && tail; - if (output_style() == COMPRESSED && comb != Complex_Selector::ANCESTOR_OF) scheduled_space = 0; - - switch (comb) { - case Complex_Selector::ANCESTOR_OF: - if (is_tail) append_mandatory_space(); - break; - case Complex_Selector::PARENT_OF: - append_optional_space(); - append_string(">"); - append_optional_space(); - break; - case Complex_Selector::ADJACENT_TO: - append_optional_space(); - append_string("+"); - append_optional_space(); - break; - case Complex_Selector::REFERENCE: - append_mandatory_space(); - append_string("/"); - if (c->reference()) c->reference()->perform(this); - append_string("/"); - append_mandatory_space(); - break; - case Complex_Selector::PRECEDES: - if (is_empty) append_optional_space(); - else append_mandatory_space(); - append_string("~"); - if (tail) append_mandatory_space(); - else append_optional_space(); - break; - default: break; - } - if (tail && comb != Complex_Selector::ANCESTOR_OF) { - if (c->has_line_break()) append_optional_linefeed(); - } - if (tail) tail->perform(this); - if (!tail && c->has_line_break()) { - if (output_style() == COMPACT) { - append_mandatory_space(); + append_token(s->ns_name(), s); + if (s->selector() || s->argument()) { + bool was = in_wrapped; + in_wrapped = true; + append_string("("); + if (s->argument()) { + s->argument()->perform(this); + } + if (s->selector() && s->argument()) { + append_mandatory_space(); + } + bool was_comma_array = in_comma_array; + in_comma_array = false; + if (s->selector()) { + s->selector()->perform(this); + } + in_comma_array = was_comma_array; + append_string(")"); + in_wrapped = was; } } } - void Inspect::operator()(Selector_List* g) + void Inspect::operator()(SelectorList* g) { if (g->empty()) { @@ -1049,7 +1017,7 @@ namespace Sass { // probably ruby sass eqivalent of element_needs_parens if (output_style() == TO_SASS && g->length() == 1 && (!Cast((*g)[0]) && - !Cast((*g)[0]))) { + !Cast((*g)[0]))) { append_string("("); } else if (!in_declaration && in_comma_array) { @@ -1059,6 +1027,7 @@ namespace Sass { if (in_declaration) in_comma_array = true; for (size_t i = 0, L = g->length(); i < L; ++i) { + if (!in_wrapped && i == 0) append_indentation(); if ((*g)[i] == nullptr) continue; schedule_mapping(g->at(i)->last()); @@ -1075,7 +1044,7 @@ namespace Sass { // probably ruby sass eqivalent of element_needs_parens if (output_style() == TO_SASS && g->length() == 1 && (!Cast((*g)[0]) && - !Cast((*g)[0]))) { + !Cast((*g)[0]))) { append_string(",)"); } else if (!in_declaration && in_comma_array) { @@ -1083,5 +1052,60 @@ namespace Sass { } } + void Inspect::operator()(ComplexSelector* sel) + { + bool many = false; + if (sel->hasPreLineFeed()) { + append_optional_linefeed(); + } + for (auto& item : sel->elements()) { + if (many) append_optional_space(); + item->perform(this); + many = true; + } + } + + void Inspect::operator()(SelectorComponent* sel) + { + // You should probably never call this method directly + // But in case anyone does, we will do the upcasting + if (auto comp = Cast(sel)) operator()(comp); + if (auto comb = Cast(sel)) operator()(comb); + } + + void Inspect::operator()(CompoundSelector* sel) + { + if (sel->hasRealParent()) { + append_string("&"); + } + for (auto& item : sel->elements()) { + item->perform(this); + } + // Add the post line break (from ruby sass) + // Dart sass uses another logic for newlines + if (sel->hasPostLineBreak()) { + if (output_style() != COMPACT) { + append_optional_linefeed(); + } + } + } + + void Inspect::operator()(SelectorCombinator* sel) + { + append_optional_space(); + switch (sel->combinator()) { + case SelectorCombinator::Combinator::CHILD: append_string(">"); break; + case SelectorCombinator::Combinator::GENERAL: append_string("~"); break; + case SelectorCombinator::Combinator::ADJACENT: append_string("+"); break; + } + append_optional_space(); + // Add the post line break (from ruby sass) + // Dart sass uses another logic for newlines + if (sel->hasPostLineBreak()) { + if (output_style() != COMPACT) { + // append_optional_linefeed(); + } + } + } } diff --git a/src/inspect.hpp b/src/inspect.hpp index cf2967d2c..a7aee00c7 100644 --- a/src/inspect.hpp +++ b/src/inspect.hpp @@ -23,7 +23,6 @@ namespace Sass { virtual void operator()(Ruleset*); virtual void operator()(Bubble*); virtual void operator()(Supports_Block*); - virtual void operator()(Media_Block*); virtual void operator()(At_Root_Block*); virtual void operator()(Directive*); virtual void operator()(Keyframe_Rule*); @@ -40,7 +39,7 @@ namespace Sass { virtual void operator()(Each*); virtual void operator()(While*); virtual void operator()(Return*); - virtual void operator()(Extension*); + virtual void operator()(ExtendRule*); virtual void operator()(Definition*); virtual void operator()(Mixin_Call*); virtual void operator()(Content*); @@ -67,11 +66,14 @@ namespace Sass { virtual void operator()(Supports_Negation*); virtual void operator()(Supports_Declaration*); virtual void operator()(Supports_Interpolation*); + virtual void operator()(MediaRule*); + virtual void operator()(CssMediaRule*); + virtual void operator()(CssMediaQuery*); virtual void operator()(Media_Query*); virtual void operator()(Media_Query_Expression*); virtual void operator()(At_Root_Query*); virtual void operator()(Null*); - virtual void operator()(Parent_Selector* p); + virtual void operator()(Parent_Reference* p); // parameters and arguments virtual void operator()(Parameter*); virtual void operator()(Parameters*); @@ -85,11 +87,11 @@ namespace Sass { virtual void operator()(Id_Selector*); virtual void operator()(Attribute_Selector*); virtual void operator()(Pseudo_Selector*); - virtual void operator()(Wrapped_Selector*); - virtual void operator()(Compound_Selector*); - virtual void operator()(Complex_Selector*); - virtual void operator()(Selector_List*); - + virtual void operator()(SelectorComponent*); + virtual void operator()(SelectorCombinator*); + virtual void operator()(CompoundSelector*); + virtual void operator()(ComplexSelector*); + virtual void operator()(SelectorList*); virtual std::string lbracket(List*); virtual std::string rbracket(List*); diff --git a/src/listize.cpp b/src/listize.cpp index c2dfa517e..41f5e7421 100644 --- a/src/listize.cpp +++ b/src/listize.cpp @@ -16,7 +16,13 @@ namespace Sass { Listize::Listize() { } - Expression* Listize::operator()(Selector_List* sel) + Expression* Listize::perform(AST_Node* node) + { + Listize listize; + return node->perform(&listize); + } + + Expression* Listize::operator()(SelectorList* sel) { List_Obj l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); l->from_selector(true); @@ -28,7 +34,7 @@ namespace Sass { return SASS_MEMORY_NEW(Null, l->pstate()); } - Expression* Listize::operator()(Compound_Selector* sel) + Expression* Listize::operator()(CompoundSelector* sel) { std::string str; for (size_t i = 0, L = sel->length(); i < L; ++i) { @@ -38,45 +44,25 @@ namespace Sass { return SASS_MEMORY_NEW(String_Quoted, sel->pstate(), str); } - Expression* Listize::operator()(Complex_Selector* sel) + Expression* Listize::operator()(ComplexSelector* sel) { - List_Obj l = SASS_MEMORY_NEW(List, sel->pstate(), 2); + List_Obj l = SASS_MEMORY_NEW(List, sel->pstate()); + // ToDo: investigate what this does + // Note: seems reated to parent ref l->from_selector(true); - Compound_Selector_Obj head = sel->head(); - if (head && !head->is_empty_reference()) - { - Expression* hh = head->perform(this); - if (hh) l->append(hh); - } - std::string reference = ! sel->reference() ? "" - : sel->reference()->to_string(); - switch(sel->combinator()) - { - case Complex_Selector::PARENT_OF: - l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), ">")); - break; - case Complex_Selector::ADJACENT_TO: - l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "+")); - break; - case Complex_Selector::REFERENCE: - l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "/" + reference + "/")); - break; - case Complex_Selector::PRECEDES: - l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "~")); - break; - case Complex_Selector::ANCESTOR_OF: - break; - default: break; + for (auto component : sel->elements()) { + if (CompoundSelectorObj compound = Cast(component)) { + if (!compound->empty()) { + Expression_Obj hh = compound->perform(this); + if (hh) l->append(hh); + } + } + else if (component) { + l->append(SASS_MEMORY_NEW(String_Quoted, component->pstate(), component->to_string())); + } } - Complex_Selector_Obj tail = sel->tail(); - if (tail) - { - Expression_Obj tt = tail->perform(this); - if (List* ls = Cast(tt)) - { l->concat(ls); } - } if (l->length() == 0) return 0; return l.detach(); } diff --git a/src/listize.hpp b/src/listize.hpp index 708b93c82..5da094df9 100644 --- a/src/listize.hpp +++ b/src/listize.hpp @@ -5,13 +5,8 @@ // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" -#include -#include - -#include "ast.hpp" -#include "context.hpp" +#include "ast_fwd_decl.hpp" #include "operation.hpp" -#include "environment.hpp" namespace Sass { @@ -19,13 +14,17 @@ namespace Sass { class Listize : public Operation_CRTP { + public: + + static Expression* perform(AST_Node* node); + public: Listize(); ~Listize() { } - Expression* operator()(Selector_List*); - Expression* operator()(Complex_Selector*); - Expression* operator()(Compound_Selector*); + Expression* operator()(SelectorList*); + Expression* operator()(ComplexSelector*); + Expression* operator()(CompoundSelector*); // generic fallback template diff --git a/src/memory/SharedPtr.hpp b/src/memory/SharedPtr.hpp index da2b0d53e..875bfe2e8 100644 --- a/src/memory/SharedPtr.hpp +++ b/src/memory/SharedPtr.hpp @@ -9,6 +9,10 @@ #include #include +// https://lokiastari.com/blog/2014/12/30/c-plus-plus-by-example-smart-pointer/index.html +// https://lokiastari.com/blog/2015/01/15/c-plus-plus-by-example-smart-pointer-part-ii/index.html +// https://lokiastari.com/blog/2015/01/23/c-plus-plus-by-example-smart-pointer-part-iii/index.html + namespace Sass { class SharedPtr; @@ -42,6 +46,16 @@ namespace Sass { #endif + // SharedObj is the base class for all objects that can be stored as a shared object + // It adds the reference counter and other values directly to the objects + // This gives a slight overhead when directly used as a stack object, but has some + // advantages for our code. It is safe to create two shared pointers from the same + // objects, as the "control block" is directly attached to it. This would lead + // to undefined behavior with std::shared_ptr. This also avoids the need to + // allocate additional control blocks and/or the need to dereference two + // pointers on each operation. This can be optimized in `std::shared_ptr` + // too by using `std::make_shared` (where the control block and the actual + // object are allocated in one continous memory block via one single call). class SharedObj { public: SharedObj() : refcount(0), detached(false) { @@ -51,7 +65,12 @@ namespace Sass { } virtual ~SharedObj() { #ifdef DEBUG_SHARED_PTR - all.clear(); + for (size_t i = 0; i < all.size(); i++) { + if (all[i] == this) { + all.erase(all.begin() + i); + break; + } + } #endif } @@ -70,7 +89,7 @@ namespace Sass { static void setTaint(bool val) { taint = val; } - virtual const std::string to_string() const = 0; + virtual std::string to_string() const = 0; protected: friend class SharedPtr; friend class Memory_Manager; @@ -85,6 +104,9 @@ namespace Sass { #endif }; + // SharedPtr is a intermediate (template-less) base class for SharedImpl. + // ToDo: there should be a way to include this in SharedImpl and to get + // ToDo: rid of all the static_cast that are now needed in SharedImpl. class SharedPtr { public: SharedPtr() : node(nullptr) {} @@ -114,6 +136,11 @@ namespace Sass { // Prevents all SharedPtrs from freeing this node until it is assigned to another SharedPtr. SharedObj* detach() { if (node != nullptr) node->detached = true; + #ifdef DEBUG_SHARED_PTR + if (node->dbg) { + std::cerr << "DETACHING NODE\n"; + } + #endif return node; } @@ -136,6 +163,11 @@ namespace Sass { #endif delete node; } + else if (node->refcount == 0) { + #ifdef DEBUG_SHARED_PTR + if (node->dbg) std::cerr << "NODE EVAEDED DELETE " << node << "\n"; + #endif + } } void incRefCount() { if (node == nullptr) return; @@ -149,7 +181,8 @@ namespace Sass { template class SharedImpl : private SharedPtr { - public: + + public: SharedImpl() : SharedPtr(nullptr) {} template @@ -172,9 +205,9 @@ namespace Sass { SharedPtr::operator=(static_cast&>(rhs))); } - operator const std::string() const { + operator std::string() const { if (node) return node->to_string(); - return "[nullptr]"; + return "null"; } using SharedPtr::isNull; @@ -185,6 +218,7 @@ namespace Sass { T* operator-> () const { return static_cast(this->obj()); }; T* ptr () const { return static_cast(this->obj()); }; T* detach() { return static_cast(SharedPtr::detach()); } + }; // Comparison operators, based on: diff --git a/src/node.cpp b/src/node.cpp deleted file mode 100644 index f02295484..000000000 --- a/src/node.cpp +++ /dev/null @@ -1,322 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include - -#include "node.hpp" -#include "context.hpp" -#include "parser.hpp" - -namespace Sass { - - - Node Node::createCombinator(const Complex_Selector::Combinator& combinator) { - NodeDequePtr null; - return Node(COMBINATOR, combinator, NULL /*pSelector*/, null /*pCollection*/); - } - - - Node Node::createSelector(const Complex_Selector& pSelector) { - NodeDequePtr null; - - Complex_Selector* pStripped = SASS_MEMORY_COPY(&pSelector); - pStripped->tail({}); - pStripped->combinator(Complex_Selector::ANCESTOR_OF); - - Node n(SELECTOR, Complex_Selector::ANCESTOR_OF, pStripped, null /*pCollection*/); - n.got_line_feed = pSelector.has_line_feed(); - return n; - } - - - Node Node::createCollection() { - NodeDequePtr pEmptyCollection = std::make_shared(); - return Node(COLLECTION, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, pEmptyCollection); - } - - - Node Node::createCollection(const NodeDeque& values) { - NodeDequePtr pShallowCopiedCollection = std::make_shared(values); - return Node(COLLECTION, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, pShallowCopiedCollection); - } - - - Node Node::createNil() { - NodeDequePtr null; - return Node(NIL, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, null /*pCollection*/); - } - - - Node::Node(const TYPE& type, Complex_Selector::Combinator combinator, Complex_Selector* pSelector, NodeDequePtr& pCollection) - : got_line_feed(false), mType(type), mCombinator(combinator), mpSelector(pSelector), mpCollection(pCollection) - { if (pSelector) got_line_feed = pSelector->has_line_feed(); } - - - Node Node::klone() const { - NodeDequePtr pNewCollection = std::make_shared(); - if (mpCollection) { - for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) { - Node& toClone = *iter; - pNewCollection->push_back(toClone.klone()); - } - } - - Node n(mType, mCombinator, mpSelector ? SASS_MEMORY_COPY(mpSelector) : NULL, pNewCollection); - n.got_line_feed = got_line_feed; - return n; - } - - - bool Node::contains(const Node& potentialChild) const { - bool found = false; - - for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) { - Node& toTest = *iter; - - if (toTest == potentialChild) { - found = true; - break; - } - } - - return found; - } - - - bool Node::operator==(const Node& rhs) const { - if (this->type() != rhs.type()) { - return false; - } - - if (this->isCombinator()) { - - return this->combinator() == rhs.combinator(); - - } else if (this->isNil()) { - - return true; // no state to check - - } else if (this->isSelector()){ - - return *this->selector() == *rhs.selector(); - - } else if (this->isCollection()) { - - if (this->collection()->size() != rhs.collection()->size()) { - return false; - } - - for (NodeDeque::iterator lhsIter = this->collection()->begin(), lhsIterEnd = this->collection()->end(), - rhsIter = rhs.collection()->begin(); lhsIter != lhsIterEnd; lhsIter++, rhsIter++) { - - if (*lhsIter != *rhsIter) { - return false; - } - - } - - return true; - - } - - // We shouldn't get here. - throw "Comparing unknown node types. A new type was probably added and this method wasn't implemented for it."; - } - - - void Node::plus(Node& rhs) { - if (!this->isCollection() || !rhs.isCollection()) { - throw "Both the current node and rhs must be collections."; - } - this->collection()->insert(this->collection()->end(), rhs.collection()->begin(), rhs.collection()->end()); - } - -#ifdef DEBUG - std::ostream& operator<<(std::ostream& os, const Node& node) { - - if (node.isCombinator()) { - - switch (node.combinator()) { - case Complex_Selector::ANCESTOR_OF: os << "\" \""; break; - case Complex_Selector::PARENT_OF: os << "\">\""; break; - case Complex_Selector::PRECEDES: os << "\"~\""; break; - case Complex_Selector::ADJACENT_TO: os << "\"+\""; break; - case Complex_Selector::REFERENCE: os << "\"/\""; break; - } - - } else if (node.isNil()) { - - os << "nil"; - - } else if (node.isSelector()){ - - os << node.selector()->head()->to_string(); - - } else if (node.isCollection()) { - - os << "["; - - for (NodeDeque::iterator iter = node.collection()->begin(), iterBegin = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) { - if (iter != iterBegin) { - os << ", "; - } - - os << (*iter); - } - - os << "]"; - - } - - return os; - - } -#endif - - - Node complexSelectorToNode(Complex_Selector* pToConvert) { - if (pToConvert == NULL) { - return Node::createNil(); - } - Node node = Node::createCollection(); - node.got_line_feed = pToConvert->has_line_feed(); - bool has_lf = pToConvert->has_line_feed(); - - // unwrap the selector from parent ref - if (pToConvert->head() && pToConvert->head()->has_parent_ref()) { - Complex_Selector_Obj tail = pToConvert->tail(); - if (tail) tail->has_line_feed(pToConvert->has_line_feed()); - pToConvert = tail; - } - - while (pToConvert) { - - bool empty_parent_ref = pToConvert->head() && pToConvert->head()->is_empty_reference(); - - // the first Complex_Selector may contain a dummy head pointer, skip it. - if (pToConvert->head() && !empty_parent_ref) { - node.collection()->push_back(Node::createSelector(*pToConvert)); - if (has_lf) node.collection()->back().got_line_feed = has_lf; - if (pToConvert->head() || empty_parent_ref) { - if (pToConvert->tail()) { - pToConvert->tail()->has_line_feed(pToConvert->has_line_feed()); - } - } - has_lf = false; - } - - if (pToConvert->combinator() != Complex_Selector::ANCESTOR_OF) { - node.collection()->push_back(Node::createCombinator(pToConvert->combinator())); - if (has_lf) node.collection()->back().got_line_feed = has_lf; - has_lf = false; - } - - if (pToConvert && empty_parent_ref && pToConvert->tail()) { - // pToConvert->tail()->has_line_feed(pToConvert->has_line_feed()); - } - - pToConvert = pToConvert->tail(); - } - - return node; - } - - - Complex_Selector* nodeToComplexSelector(const Node& toConvert) { - if (toConvert.isNil()) { - return NULL; - } - - - if (!toConvert.isCollection()) { - throw "The node to convert to a Complex_Selector* must be a collection type or nil."; - } - - - NodeDeque& childNodes = *toConvert.collection(); - - std::string noPath(""); - Complex_Selector_Obj pFirst = SASS_MEMORY_NEW(Complex_Selector, ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, {}, {}); - - Complex_Selector_Obj pCurrent = pFirst; - - if (toConvert.isSelector()) pFirst->has_line_feed(toConvert.got_line_feed); - if (toConvert.isCombinator()) pFirst->has_line_feed(toConvert.got_line_feed); - - for (NodeDeque::iterator childIter = childNodes.begin(), childIterEnd = childNodes.end(); childIter != childIterEnd; childIter++) { - - Node& child = *childIter; - - if (child.isSelector()) { - // JMA - need to clone the selector, because they can end up getting shared across Node - // collections, and can result in an infinite loop during the call to parentSuperselector() - pCurrent->tail(SASS_MEMORY_COPY(child.selector())); - // if (child.got_line_feed) pCurrent->has_line_feed(child.got_line_feed); - pCurrent = pCurrent->tail(); - } else if (child.isCombinator()) { - pCurrent->combinator(child.combinator()); - if (child.got_line_feed) pCurrent->has_line_feed(child.got_line_feed); - - // if the next node is also a combinator, create another Complex_Selector to hold it so it doesn't replace the current combinator - if (childIter+1 != childIterEnd) { - Node& nextNode = *(childIter+1); - if (nextNode.isCombinator()) { - pCurrent->tail(SASS_MEMORY_NEW(Complex_Selector, ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, {}, {})); - if (nextNode.got_line_feed) pCurrent->tail()->has_line_feed(nextNode.got_line_feed); - pCurrent = pCurrent->tail(); - } - } - } else { - throw "The node to convert's children must be only combinators or selectors."; - } - } - - // Put the dummy Compound_Selector in the first position, for consistency with the rest of libsass - Compound_Selector* fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[NODE]"), 1); - Parent_Selector* selectorRef = SASS_MEMORY_NEW(Parent_Selector, ParserState("[NODE]")); - fakeHead->elements().push_back(selectorRef); - if (toConvert.got_line_feed) pFirst->has_line_feed(toConvert.got_line_feed); - // pFirst->has_line_feed(pFirst->has_line_feed() || pFirst->tail()->has_line_feed() || toConvert.got_line_feed); - pFirst->head(fakeHead); - return SASS_MEMORY_COPY(pFirst); - } - - // A very naive trim function, which removes duplicates in a node - // This is only used in Complex_Selector::unify_with for now, may need modifications to fit other needs - Node Node::naiveTrim(Node& seqses) { - - std::vector res; - std::vector known; - - NodeDeque::reverse_iterator seqsesIter = seqses.collection()->rbegin(), - seqsesIterEnd = seqses.collection()->rend(); - - for (; seqsesIter != seqsesIterEnd; ++seqsesIter) - { - Node& seqs1 = *seqsesIter; - if( seqs1.isSelector() ) { - Complex_Selector_Obj sel = seqs1.selector(); - std::vector::iterator it; - bool found = false; - for (it = known.begin(); it != known.end(); ++it) { - if (**it == *sel) { found = true; break; } - } - if( !found ) { - known.push_back(seqs1.selector()); - res.push_back(&seqs1); - } - } else { - res.push_back(&seqs1); - } - } - - Node result = Node::createCollection(); - - for (size_t i = res.size() - 1; i != std::string::npos; --i) { - result.collection()->push_back(*res[i]); - } - - return result; - } -} diff --git a/src/node.hpp b/src/node.hpp deleted file mode 100644 index a19870972..000000000 --- a/src/node.hpp +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef SASS_NODE_H -#define SASS_NODE_H - -#include -#include - -#include "ast.hpp" - - -namespace Sass { - - - - - class Context; - - /* - There are a lot of stumbling blocks when trying to port the ruby extend code to C++. The biggest is the choice of - data type. The ruby code will pretty seamlessly switch types between an Array (libsass' - equivalent is the Complex_Selector) to a Sequence, which contains more metadata about the sequence than just the - selector info. They also have the ability to have arbitrary nestings of arrays like [1, [2]], which is hard to - implement using Array equivalents in C++ (like the deque or vector). They also have the ability to include nil - in the arrays, like [1, nil, 3], which has potential semantic differences than an empty array [1, [], 3]. To be - able to represent all of these as unique cases, we need to create a tree of variant objects. The tree nature allows - the inconsistent nesting levels. The variant nature (while making some of the C++ code uglier) allows the code to - more closely match the ruby code, which is a huge benefit when attempting to implement an complex algorithm like - the Extend operator. - - Note that the current libsass data model also pairs the combinator with the Complex_Selector that follows it, but - ruby sass has no such restriction, so we attempt to create a data structure that can handle them split apart. - */ - - class Node; - typedef std::deque NodeDeque; - typedef std::shared_ptr NodeDequePtr; - - class Node { - public: - enum TYPE { - SELECTOR, - COMBINATOR, - COLLECTION, - NIL - }; - - TYPE type() const { return mType; } - bool isCombinator() const { return mType == COMBINATOR; } - bool isSelector() const { return mType == SELECTOR; } - bool isCollection() const { return mType == COLLECTION; } - bool isNil() const { return mType == NIL; } - bool got_line_feed; - - Complex_Selector::Combinator combinator() const { return mCombinator; } - - Complex_Selector_Obj selector() { return mpSelector; } - Complex_Selector_Obj selector() const { return mpSelector; } - - NodeDequePtr collection() { return mpCollection; } - const NodeDequePtr collection() const { return mpCollection; } - - static Node createCombinator(const Complex_Selector::Combinator& combinator); - - // This method will klone the selector, stripping off the tail and combinator - static Node createSelector(const Complex_Selector& pSelector); - - static Node createCollection(); - static Node createCollection(const NodeDeque& values); - - static Node createNil(); - static Node naiveTrim(Node& seqses); - - Node klone() const; - - bool operator==(const Node& rhs) const; - inline bool operator!=(const Node& rhs) const { return !(*this == rhs); } - - - /* - COLLECTION FUNCTIONS - - Most types don't need any helper methods (nil and combinator due to their simplicity and - selector due to the fact that we leverage the non-node selector code on the Complex_Selector - whereever possible). The following methods are intended to be called on Node objects whose - type is COLLECTION only. - */ - - // rhs and this must be node collections. Shallow copy the nodes from rhs to the end of this. - // This function DOES NOT remove the nodes from rhs. - void plus(Node& rhs); - - // potentialChild must be a node collection of selectors/combinators. this must be a collection - // of collections of nodes/combinators. This method checks if potentialChild is a child of this - // Node. - bool contains(const Node& potentialChild) const; - - private: - // Private constructor; Use the static methods (like createCombinator and createSelector) - // to instantiate this object. This is more expressive, and it allows us to break apart each - // case into separate functions. - Node(const TYPE& type, Complex_Selector::Combinator combinator, Complex_Selector* pSelector, NodeDequePtr& pCollection); - - TYPE mType; - - // TODO: can we union these to save on memory? - Complex_Selector::Combinator mCombinator; - Complex_Selector_Obj mpSelector; - NodeDequePtr mpCollection; - }; - -#ifdef DEBUG - std::ostream& operator<<(std::ostream& os, const Node& node); -#endif - Node complexSelectorToNode(Complex_Selector* pToConvert); - Complex_Selector* nodeToComplexSelector(const Node& toConvert); - -} - -#endif diff --git a/src/operation.hpp b/src/operation.hpp index 87e0c2f9d..1830e4339 100644 --- a/src/operation.hpp +++ b/src/operation.hpp @@ -1,9 +1,14 @@ #ifndef SASS_OPERATION_H #define SASS_OPERATION_H +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + // base classes to implement curiously recurring template pattern (CRTP) // https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern +#include #include #include "ast_fwd_decl.hpp" @@ -47,7 +52,9 @@ namespace Sass { virtual T operator()(Bubble* x) = 0; virtual T operator()(Trace* x) = 0; virtual T operator()(Supports_Block* x) = 0; - virtual T operator()(Media_Block* x) = 0; + virtual T operator()(MediaRule* x) = 0; + virtual T operator()(CssMediaRule* x) = 0; + virtual T operator()(CssMediaQuery* x) = 0; virtual T operator()(At_Root_Block* x) = 0; virtual T operator()(Directive* x) = 0; virtual T operator()(Keyframe_Rule* x) = 0; @@ -65,7 +72,7 @@ namespace Sass { virtual T operator()(While* x) = 0; virtual T operator()(Return* x) = 0; virtual T operator()(Content* x) = 0; - virtual T operator()(Extension* x) = 0; + virtual T operator()(ExtendRule* x) = 0; virtual T operator()(Definition* x) = 0; virtual T operator()(Mixin_Call* x) = 0; // expressions @@ -92,10 +99,9 @@ namespace Sass { virtual T operator()(Supports_Negation* x) = 0; virtual T operator()(Supports_Declaration* x) = 0; virtual T operator()(Supports_Interpolation* x) = 0; - virtual T operator()(Media_Query* x) = 0; + virtual T operator()(Media_Query* x) = 0; virtual T operator()(Media_Query_Expression* x) = 0; virtual T operator()(At_Root_Query* x) = 0; - virtual T operator()(Parent_Selector* x) = 0; virtual T operator()(Parent_Reference* x) = 0; // parameters and arguments virtual T operator()(Parameter* x) = 0; @@ -110,10 +116,12 @@ namespace Sass { virtual T operator()(Id_Selector* x) = 0; virtual T operator()(Attribute_Selector* x) = 0; virtual T operator()(Pseudo_Selector* x) = 0; - virtual T operator()(Wrapped_Selector* x) = 0; - virtual T operator()(Compound_Selector* x)= 0; - virtual T operator()(Complex_Selector* x) = 0; - virtual T operator()(Selector_List* x) = 0; + virtual T operator()(SelectorComponent* x) = 0; + virtual T operator()(SelectorCombinator* x) = 0; + virtual T operator()(CompoundSelector* x) = 0; + virtual T operator()(ComplexSelector* x) = 0; + virtual T operator()(SelectorList* x) = 0; + }; // example: Operation_CRTP @@ -130,7 +138,9 @@ namespace Sass { T operator()(Bubble* x) { return static_cast(this)->fallback(x); } T operator()(Trace* x) { return static_cast(this)->fallback(x); } T operator()(Supports_Block* x) { return static_cast(this)->fallback(x); } - T operator()(Media_Block* x) { return static_cast(this)->fallback(x); } + T operator()(MediaRule* x) { return static_cast(this)->fallback(x); } + T operator()(CssMediaRule* x) { return static_cast(this)->fallback(x); } + T operator()(CssMediaQuery* x) { return static_cast(this)->fallback(x); } T operator()(At_Root_Block* x) { return static_cast(this)->fallback(x); } T operator()(Directive* x) { return static_cast(this)->fallback(x); } T operator()(Keyframe_Rule* x) { return static_cast(this)->fallback(x); } @@ -148,7 +158,7 @@ namespace Sass { T operator()(While* x) { return static_cast(this)->fallback(x); } T operator()(Return* x) { return static_cast(this)->fallback(x); } T operator()(Content* x) { return static_cast(this)->fallback(x); } - T operator()(Extension* x) { return static_cast(this)->fallback(x); } + T operator()(ExtendRule* x) { return static_cast(this)->fallback(x); } T operator()(Definition* x) { return static_cast(this)->fallback(x); } T operator()(Mixin_Call* x) { return static_cast(this)->fallback(x); } // expressions @@ -178,7 +188,6 @@ namespace Sass { T operator()(Media_Query* x) { return static_cast(this)->fallback(x); } T operator()(Media_Query_Expression* x) { return static_cast(this)->fallback(x); } T operator()(At_Root_Query* x) { return static_cast(this)->fallback(x); } - T operator()(Parent_Selector* x) { return static_cast(this)->fallback(x); } T operator()(Parent_Reference* x) { return static_cast(this)->fallback(x); } // parameters and arguments T operator()(Parameter* x) { return static_cast(this)->fallback(x); } @@ -193,14 +202,15 @@ namespace Sass { T operator()(Id_Selector* x) { return static_cast(this)->fallback(x); } T operator()(Attribute_Selector* x) { return static_cast(this)->fallback(x); } T operator()(Pseudo_Selector* x) { return static_cast(this)->fallback(x); } - T operator()(Wrapped_Selector* x) { return static_cast(this)->fallback(x); } - T operator()(Compound_Selector* x){ return static_cast(this)->fallback(x); } - T operator()(Complex_Selector* x) { return static_cast(this)->fallback(x); } - T operator()(Selector_List* x) { return static_cast(this)->fallback(x); } + T operator()(SelectorComponent* x) { return static_cast(this)->fallback(x); } + T operator()(SelectorCombinator* x) { return static_cast(this)->fallback(x); } + T operator()(CompoundSelector* x) { return static_cast(this)->fallback(x); } + T operator()(ComplexSelector* x) { return static_cast(this)->fallback(x); } + T operator()(SelectorList* x) { return static_cast(this)->fallback(x); } // fallback with specific type U // will be called if not overloaded - template T fallback(U x) + template inline T fallback(U x) { throw std::runtime_error( std::string(typeid(*this).name()) + ": CRTP not implemented for " + typeid(x).name()); diff --git a/src/operators.cpp b/src/operators.cpp index bf312b8f3..b09293988 100644 --- a/src/operators.cpp +++ b/src/operators.cpp @@ -2,6 +2,7 @@ // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" +#include #include "operators.hpp" namespace Sass { diff --git a/src/ordered_map.hpp b/src/ordered_map.hpp new file mode 100644 index 000000000..faa55d343 --- /dev/null +++ b/src/ordered_map.hpp @@ -0,0 +1,112 @@ +#ifndef SASS_ORDERED_MAP_H +#define SASS_ORDERED_MAP_H + +namespace Sass { + + // ########################################################################## + // Very simple and limited container for insert ordered hash map. + // Piggy-back implementation on std::unordered_map and std::vector + // ########################################################################## + template< + class Key, + class T, + class Hash = std::hash, + class KeyEqual = std::equal_to, + class Allocator = std::allocator> + > + class ordered_map { + + private: + + using map_type = typename std::unordered_map< Key, T, Hash, KeyEqual, Allocator>; + using map_iterator = typename std::unordered_map< Key, T, Hash, KeyEqual, Allocator>::iterator; + using map_const_iterator = typename std::unordered_map< Key, T, Hash, KeyEqual, Allocator>::const_iterator; + + // The main unordered map + map_type _map; + + // Keep insertion order + std::vector _keys; + std::vector _values; + + const KeyEqual _keyEqual; + + public: + + ordered_map() : + _keyEqual(KeyEqual()) + { + } + + ordered_map& operator= (const ordered_map& other) { + _map = other._map; + _keys = other._keys; + _values = other._values; + return *this; + } + + std::pair front() { + return std::make_pair( + _keys.front(), + _values.front() + ); + } + + bool empty() const { + return _keys.empty(); + } + + void insert(const Key& key, const T& val) { + if (!hasKey(key)) { + _values.push_back(val); + _keys.push_back(key); + } + _map[key] = val; + } + + bool hasKey(const Key& key) const { + return _map.find(key) != _map.end(); + } + + bool erase(const Key& key) { + _map.erase(key); + // find the position in the array + for (size_t i = 0; i < _keys.size(); i += 1) { + if (_keyEqual(key, _keys[i])) { + _keys.erase(_keys.begin() + i); + _values.erase(_values.begin() + i); + return true; + } + } + return false; + } + + const std::vector& keys() const { return _keys; } + const std::vector& values() const { return _values; } + + const T& get(const Key& key) { + if (hasKey(key)) { + return _map[key]; + } + throw std::runtime_error("Key does not exist"); + } + + using iterator = typename std::vector::iterator; + using const_iterator = typename std::vector::const_iterator; + using reverse_iterator = typename std::vector::reverse_iterator; + using const_reverse_iterator = typename std::vector::const_reverse_iterator; + + typename std::vector::iterator end() { return _keys.end(); } + typename std::vector::iterator begin() { return _keys.begin(); } + typename std::vector::reverse_iterator rend() { return _keys.rend(); } + typename std::vector::reverse_iterator rbegin() { return _keys.rbegin(); } + typename std::vector::const_iterator end() const { return _keys.end(); } + typename std::vector::const_iterator begin() const { return _keys.begin(); } + typename std::vector::const_reverse_iterator rend() const { return _keys.rend(); } + typename std::vector::const_reverse_iterator rbegin() const { return _keys.rbegin(); } + + }; + +} + +#endif diff --git a/src/output.cpp b/src/output.cpp index 6ff17af37..272dc3ae2 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -113,13 +113,15 @@ namespace Sass { void Output::operator()(Ruleset* r) { - Selector_Obj s = r->selector(); - Block_Obj b = r->block(); + Block_Obj b = r->block(); + SelectorListObj s = r->selector(); + + if (!s || s->empty()) return; // Filter out rulesets that aren't printable (process its children though) if (!Util::isPrintable(r, output_style())) { for (size_t i = 0, L = b->length(); i < L; ++i) { - const Statement_Obj& stm = b->at(i); + const Statement_Obj& stm = b->get(i); if (Cast(stm)) { if (!Cast(stm)) { stm->perform(this); @@ -129,7 +131,9 @@ namespace Sass { return; } - if (output_style() == NESTED) indentation += r->tabs(); + if (output_style() == NESTED) { + indentation += r->tabs(); + } if (opt.source_comments) { std::stringstream ss; append_indentation(); @@ -142,13 +146,13 @@ namespace Sass { if (s) s->perform(this); append_scope_opener(b); for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); + Statement_Obj stm = b->get(i); bool bPrintExpression = true; // Check print conditions if (Declaration* dec = Cast(stm)) { - if (String_Constant* valConst = Cast(dec->value())) { - std::string val(valConst->value()); - if (String_Quoted* qstr = Cast(valConst)) { + if (const String_Constant* valConst = Cast(dec->value())) { + const std::string& val = valConst->value(); + if (const String_Quoted* qstr = Cast(valConst)) { if (!qstr->quote_mark() && val.empty()) { bPrintExpression = false; } @@ -157,7 +161,7 @@ namespace Sass { else if (List* list = Cast(dec->value())) { bool all_invisible = true; for (size_t list_i = 0, list_L = list->length(); list_i < list_L; ++list_i) { - Expression* item = list->at(list_i); + Expression* item = list->get(list_i); if (!item->is_invisible()) all_invisible = false; } if (all_invisible && !list->is_bracketed()) bPrintExpression = false; @@ -188,7 +192,7 @@ namespace Sass { append_scope_opener(); for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); + Statement_Obj stm = b->get(i); stm->perform(this); if (i < L - 1) append_special_linefeed(); } @@ -205,7 +209,7 @@ namespace Sass { // Filter out feature blocks that aren't printable (process its children though) if (!Util::isPrintable(f, output_style())) { for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); + Statement_Obj stm = b->get(i); if (Cast(stm)) { stm->perform(this); } @@ -221,7 +225,7 @@ namespace Sass { append_scope_opener(); for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); + Statement_Obj stm = b->get(i); stm->perform(this); if (i < L - 1) append_special_linefeed(); } @@ -232,41 +236,21 @@ namespace Sass { } - void Output::operator()(Media_Block* m) + void Output::operator()(CssMediaRule* rule) { - if (m->is_invisible()) return; - - Block_Obj b = m->block(); - - // Filter out media blocks that aren't printable (process its children though) - if (!Util::isPrintable(m, output_style())) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm)) { - stm->perform(this); - } - } - return; + // Avoid null pointer exception + if (rule == nullptr) return; + // Skip empty/invisible rule + if (rule->isInvisible()) return; + // Avoid null pointer exception + if (rule->block() == nullptr) return; + // Skip empty/invisible rule + if (rule->block()->isInvisible()) return; + // Skip if block is empty/invisible + if (Util::isPrintable(rule, output_style())) { + // Let inspect do its magic + Inspect::operator()(rule); } - if (output_style() == NESTED) indentation += m->tabs(); - append_indentation(); - append_token("@media", m); - append_mandatory_space(); - in_media_block = true; - m->media_queries()->perform(this); - in_media_block = false; - append_scope_opener(); - - for (size_t i = 0, L = b->length(); i < L; ++i) { - if (b->at(i)) { - Statement_Obj stm = b->at(i); - stm->perform(this); - } - if (i < L - 1) append_special_linefeed(); - } - - if (output_style() == NESTED) indentation -= m->tabs(); - append_scope_closer(); } void Output::operator()(Directive* a) @@ -304,7 +288,7 @@ namespace Sass { bool format = kwd != "@font-face";; for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); + Statement_Obj stm = b->get(i); stm->perform(this); if (i < L - 1 && format) append_special_linefeed(); } @@ -326,9 +310,6 @@ namespace Sass { void Output::operator()(String_Constant* s) { std::string value(s->value()); - if (s->can_compress_whitespace() && output_style() == COMPRESSED) { - value.erase(std::remove_if(value.begin(), value.end(), ::isspace), value.end()); - } if (!in_comment && !in_custom_property) { append_token(string_to_output(value), s); } else { diff --git a/src/output.hpp b/src/output.hpp index df9ad1e2e..4d0e1bcf7 100644 --- a/src/output.hpp +++ b/src/output.hpp @@ -29,7 +29,7 @@ namespace Sass { virtual void operator()(Map*); virtual void operator()(Ruleset*); virtual void operator()(Supports_Block*); - virtual void operator()(Media_Block*); + virtual void operator()(CssMediaRule*); virtual void operator()(Directive*); virtual void operator()(Keyframe_Rule*); virtual void operator()(Import*); diff --git a/src/parser.cpp b/src/parser.cpp index 411b05263..12cecb6c8 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3,14 +3,7 @@ #include "sass.hpp" #include "parser.hpp" -#include "file.hpp" -#include "inspect.hpp" -#include "constants.hpp" -#include "util.hpp" -#include "prelexer.hpp" #include "color_maps.hpp" -#include "sass/functions.h" -#include "error_handling.hpp" #include "util_string.hpp" // Notes about delayed: some ast nodes can have delayed evaluation so @@ -25,10 +18,6 @@ // Another case with delayed values are colors. In compressed mode // only processed values get compressed (other are left as written). -#include -#include -#include -#include namespace Sass { using namespace Constants; @@ -70,11 +59,11 @@ namespace Sass { pstate.offset.line = 0; } - Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source, bool allow_parent) + SelectorListObj Parser::parse_selector(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source, bool allow_parent) { Parser p = Parser::from_c_str(beg, ctx, traces, pstate, source, allow_parent); // ToDo: remap the source-map entries somehow - return p.parse_selector_list(false); + return p.parseSelectorList(false); } bool Parser::peek_newline(const char* start) @@ -261,16 +250,23 @@ namespace Sass { else if (lex < kwd_extend >(true)) { Lookahead lookahead = lookahead_for_include(position); if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was "); - Selector_List_Obj target; + SelectorListObj target; if (!lookahead.has_interpolants) { - target = parse_selector_list(true); + LOCAL_FLAG(allow_parent, false); + auto selector = parseSelectorList(true); + auto extender = SASS_MEMORY_NEW(ExtendRule, pstate, selector); + extender->isOptional(selector && selector->is_optional()); + block->append(extender); } else { - target = SASS_MEMORY_NEW(Selector_List, pstate); - target->schema(parse_selector_schema(lookahead.found, true)); + LOCAL_FLAG(allow_parent, false); + auto selector = parse_selector_schema(lookahead.found, true); + auto extender = SASS_MEMORY_NEW(ExtendRule, pstate, selector); + // A schema is not optional yet, check once it is evaluated + // extender->isOptional(selector && selector->is_optional()); + block->append(extender); } - block->append(SASS_MEMORY_NEW(Extension, pstate, target)); } // selector may contain interpolations which need delayed evaluation @@ -283,7 +279,7 @@ namespace Sass { } // parse multiple specific keyword directives - else if (lex < kwd_media >(true)) { block->append(parse_media_block()); } + else if (lex < kwd_media >(true)) { block->append(parseMediaRule()); } else if (lex < kwd_at_root >(true)) { block->append(parse_at_root_block()); } else if (lex < kwd_include_directive >(true)) { block->append(parse_include_directive()); } else if (lex < kwd_content_directive >(true)) { block->append(parse_content_directive()); } @@ -294,9 +290,9 @@ namespace Sass { // ignore the @charset directive for now else if (lex< kwd_charset_directive >(true)) { parse_charset_directive(); } + else if (lex < exactly < else_kwd >>(true)) { error("Invalid CSS: @else must come after @if"); } + // generic at keyword (keep last) - else if (lex< re_special_directive >(true)) { block->append(parse_special_directive()); } - else if (lex< re_prefixed_directive >(true)) { block->append(parse_prefixed_directive()); } else if (lex< at_keyword >(true)) { block->append(parse_directive()); } else if (is_root && stack.back() != Scope::AtRoot /* && block->is_root() */) { @@ -530,10 +526,13 @@ namespace Sass { // create the connector object (add parts later) Ruleset_Obj ruleset = SASS_MEMORY_NEW(Ruleset, pstate); // parse selector static or as schema to be evaluated later - if (lookahead.parsable) ruleset->selector(parse_selector_list(false)); + if (lookahead.parsable) { + ruleset->selector(parseSelectorList(false)); + } else { - Selector_List_Obj list = SASS_MEMORY_NEW(Selector_List, pstate); - list->schema(parse_selector_schema(lookahead.position, false)); + SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate); + auto sc = parse_selector_schema(lookahead.position, false); + ruleset->schema(sc); ruleset->selector(list); } // then parse the inner block @@ -563,7 +562,6 @@ namespace Sass { // the selector schema is pretty much just a wrapper for the string schema Selector_Schema_Obj selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema); selector_schema->connect_parent(chroot == false); - selector_schema->media_block(last_media_block); // process until end while (i < end_of_selector) { @@ -674,213 +672,8 @@ namespace Sass { } // EO parse_include_directive - // parse a list of complex selectors - // this is the main entry point for most - Selector_List_Obj Parser::parse_selector_list(bool chroot) - { - bool reloop; - bool had_linefeed = false; - NESTING_GUARD(nestings); - Complex_Selector_Obj sel; - Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate); - group->media_block(last_media_block); - - if (peek_css< alternatives < end_of_file, exactly <'{'>, exactly <','> > >()) { - css_error("Invalid CSS", " after ", ": expected selector, was "); - } - - do { - reloop = false; - - had_linefeed = had_linefeed || peek_newline(); - - if (peek_css< alternatives < class_char < selector_list_delims > > >()) - break; // in case there are superfluous commas at the end - - // now parse the complex selector - sel = parse_complex_selector(chroot); - - if (!sel) return group.detach(); - - sel->has_line_feed(had_linefeed); - - had_linefeed = false; - - while (peek_css< exactly<','> >()) - { - lex< css_comments >(false); - // consume everything up and including the comma separator - reloop = lex< exactly<','> >() != 0; - // remember line break (also between some commas) - had_linefeed = had_linefeed || peek_newline(); - // remember line break (also between some commas) - } - group->append(sel); - } - while (reloop); - while (lex_css< kwd_optional >()) { - group->is_optional(true); - } - // update for end position - group->update_pstate(pstate); - if (sel) sel->mutable_last()->has_line_break(false); - return group.detach(); - } - // EO parse_selector_list - - // a complex selector combines a compound selector with another - // complex selector, with one of four combinator operations. - // the compound selector (head) is optional, since the combinator - // can come first in the whole selector sequence (like `> DIV'). - Complex_Selector_Obj Parser::parse_complex_selector(bool chroot) - { - - NESTING_GUARD(nestings); - String_Obj reference; - lex < block_comment >(); - advanceToNextToken(); - Complex_Selector_Obj sel = SASS_MEMORY_NEW(Complex_Selector, pstate); - - if (peek < end_of_file >()) return {}; - - // parse the left hand side - Compound_Selector_Obj lhs; - // special case if it starts with combinator ([+~>]) - if (!peek_css< class_char < selector_combinator_ops > >()) { - // parse the left hand side - lhs = parse_compound_selector(); - } - - - // parse combinator between lhs and rhs - Complex_Selector::Combinator combinator = Complex_Selector::ANCESTOR_OF; - if (lex< exactly<'+'> >()) combinator = Complex_Selector::ADJACENT_TO; - else if (lex< exactly<'~'> >()) combinator = Complex_Selector::PRECEDES; - else if (lex< exactly<'>'> >()) combinator = Complex_Selector::PARENT_OF; - else if (lex< sequence < exactly<'/'>, negate < exactly < '*' > > > >()) { - // comments are allowed, but not spaces? - combinator = Complex_Selector::REFERENCE; - if (!lex < re_reference_combinator >()) return {}; - reference = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - if (!lex < exactly < '/' > >()) return {}; // ToDo: error msg? - } - - if (!lhs && combinator == Complex_Selector::ANCESTOR_OF) return {}; - - // lex < block_comment >(); - sel->head(lhs); - sel->combinator(combinator); - sel->media_block(last_media_block); - - if (combinator == Complex_Selector::REFERENCE) sel->reference(reference); - // has linfeed after combinator? - sel->has_line_break(peek_newline()); - // sel->has_line_feed(has_line_feed); - - // check if we got the abort condition (ToDo: optimize) - if (!peek_css< class_char < complex_selector_delims > >()) { - // parse next selector in sequence - sel->tail(parse_complex_selector(true)); - } - - // add a parent selector if we are not in a root - // also skip adding parent ref if we only have refs - if (!sel->has_parent_ref() && !chroot) { - // create the objects to wrap parent selector reference - Compound_Selector_Obj head = SASS_MEMORY_NEW(Compound_Selector, pstate); - Parent_Selector* parent = SASS_MEMORY_NEW(Parent_Selector, pstate, false); - parent->media_block(last_media_block); - head->media_block(last_media_block); - // add simple selector - head->append(parent); - // selector may not have any head yet - if (!sel->head()) { sel->head(head); } - // otherwise we need to create a new complex selector and set the old one as its tail - else { - sel = SASS_MEMORY_NEW(Complex_Selector, pstate, Complex_Selector::ANCESTOR_OF, head, sel); - sel->media_block(last_media_block); - } - // peek for linefeed and remember result on head - // if (peek_newline()) head->has_line_break(true); - } - - sel->update_pstate(pstate); - // complex selector - return sel; - } - // EO parse_complex_selector - - // parse one compound selector, which is basically - // a list of simple selectors (directly adjacent) - // lex them exactly (without skipping white-space) - Compound_Selector_Obj Parser::parse_compound_selector() - { - // init an empty compound selector wrapper - Compound_Selector_Obj seq = SASS_MEMORY_NEW(Compound_Selector, pstate); - seq->media_block(last_media_block); - - // skip initial white-space - lex< css_whitespace >(); - - // parse list - while (true) - { - // remove all block comments (don't skip white-space) - lex< delimited_by< slash_star, star_slash, false > >(false); - // parse functional - if (match < re_pseudo_selector >()) - { - seq->append(parse_simple_selector()); - } - // parse parent selector - else if (lex< exactly<'&'> >(false)) - { - if (!allow_parent) error("Parent selectors aren't allowed here."); - // this produces a linefeed!? - seq->has_parent_reference(true); - seq->append(SASS_MEMORY_NEW(Parent_Selector, pstate)); - // parent selector only allowed at start - // upcoming Sass may allow also trailing - if (seq->length() > 1) { - ParserState state(pstate); - Simple_Selector_Obj cur = (*seq)[seq->length()-1]; - Simple_Selector_Obj prev = (*seq)[seq->length()-2]; - std::string sel(prev->to_string({ NESTED, 5 })); - std::string found(cur->to_string({ NESTED, 5 })); - if (lex < identifier >()) { found += std::string(lexed); } - error("Invalid CSS after \"" + sel + "\": expected \"{\", was \"" + found + "\"\n\n" - "\"" + found + "\" may only be used at the beginning of a compound selector.", state); - } - } - // parse type selector - else if (lex< re_type_selector >(false)) - { - seq->append(SASS_MEMORY_NEW(Type_Selector, pstate, lexed)); - } - // peek for abort conditions - else if (peek< spaces >()) break; - else if (peek< end_of_file >()) { break; } - else if (peek_css < class_char < selector_combinator_ops > >()) break; - else if (peek_css < class_char < complex_selector_delims > >()) break; - // otherwise parse another simple selector - else { - Simple_Selector_Obj sel = parse_simple_selector(); - if (!sel) return {}; - seq->append(sel); - } - } - - if (seq && !peek_css>>()) { - seq->has_line_break(peek_newline()); - } - - // EO while true - return seq; - - } - // EO parse_compound_selector - - Simple_Selector_Obj Parser::parse_simple_selector() + + SimpleSelectorObj Parser::parse_simple_selector() { lex < css_comments >(false); if (lex< class_name >()) { @@ -893,7 +686,7 @@ namespace Sass { return SASS_MEMORY_NEW(Type_Selector, pstate, lexed); } else if (peek< pseudo_not >()) { - return parse_negated_selector(); + return parse_negated_selector2(); } else if (peek< re_pseudo_selector >()) { return parse_pseudo_selector(); @@ -905,9 +698,7 @@ namespace Sass { return parse_attribute_selector(); } else if (lex< placeholder >()) { - Placeholder_Selector* sel = SASS_MEMORY_NEW(Placeholder_Selector, pstate, lexed); - sel->media_block(last_media_block); - return sel; + return SASS_MEMORY_NEW(Placeholder_Selector, pstate, lexed); } else { css_error("Invalid CSS", " after ", ": expected selector, was "); @@ -916,71 +707,91 @@ namespace Sass { return {}; } - Wrapped_Selector_Obj Parser::parse_negated_selector() + Pseudo_Selector_Obj Parser::parse_negated_selector2() { lex< pseudo_not >(); std::string name(lexed); ParserState nsource_position = pstate; - Selector_List_Obj negated = parse_selector_list(true); + SelectorListObj negated = parseSelectorList(true); if (!lex< exactly<')'> >()) { error("negated selector is missing ')'"); } name.erase(name.size() - 1); - return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, negated); + + Pseudo_Selector* sel = SASS_MEMORY_NEW(Pseudo_Selector, nsource_position, name.substr(1)); + sel->selector(negated); + return sel; } + // Helper to clean binominal string + bool BothAreSpaces(char lhs, char rhs) { return isspace(lhs) && isspace(rhs); } + // a pseudo selector often starts with one or two colons // it can contain more selectors inside parentheses - Simple_Selector_Obj Parser::parse_pseudo_selector() { - if (lex< sequence< - pseudo_prefix, - // we keep the space within the name, strange enough - // ToDo: refactor output to schedule the space for it - // or do we really want to keep the real white-space? - sequence< identifier, optional < block_comment >, exactly<'('> > - > >()) - { - - std::string name(lexed); - name.erase(name.size() - 1); - ParserState p = pstate; + SimpleSelectorObj Parser::parse_pseudo_selector() { + + // Lex one or two colon characters + if (lex()) { + std::string colons(lexed); + // Check if it is a pseudo element + bool element = colons.size() == 2; + + if (lex< sequence< + // we keep the space within the name, strange enough + // ToDo: refactor output to schedule the space for it + // or do we really want to keep the real white-space? + sequence< identifier, optional < block_comment >, exactly<'('> > + > >()) + { - // specially parse static stuff - // ToDo: really everything static? - if (peek_css < - sequence < - alternatives < - static_value, - binomial - >, - optional_css_whitespace, - exactly<')'> - > - >() - ) { - lex_css< alternatives < static_value, binomial > >(); - String_Constant_Obj expr = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - if (lex_css< exactly<')'> >()) { - expr->can_compress_whitespace(true); - return SASS_MEMORY_NEW(Pseudo_Selector, p, name, expr); + std::string name(lexed); + name.erase(name.size() - 1); + ParserState p = pstate; + + // specially parse nth-child pseudo selectors + if (lex_css < sequence < binomial, word_boundary >>()) { + std::string parsed(lexed); // always compacting binominals (as dart-sass) + parsed.erase(std::unique(parsed.begin(), parsed.end(), BothAreSpaces), parsed.end()); + String_Constant_Obj arg = SASS_MEMORY_NEW(String_Constant, pstate, parsed); + Pseudo_Selector* pseudo = SASS_MEMORY_NEW(Pseudo_Selector, p, name, element); + if (lex < sequence < css_whitespace, insensitive < of_kwd >>>(false)) { + pseudo->selector(parseSelectorList(true)); + } + pseudo->argument(arg); + if (lex_css< exactly<')'> >()) { + return pseudo; + } } - } - else if (Selector_List_Obj wrapped = parse_selector_list(true)) { - if (wrapped && lex_css< exactly<')'> >()) { - return SASS_MEMORY_NEW(Wrapped_Selector, p, name, wrapped); + else { + if (peek_css< exactly<')'>>() && Util::equalsLiteral("nth-", name.substr(0, 4))) { + css_error("Invalid CSS", " after ", ": expected An+B expression, was "); + } + if (SelectorListObj wrapped = parseSelectorList(true)) { + if (wrapped && lex_css< exactly<')'> >()) { + Pseudo_Selector* pseudo = SASS_MEMORY_NEW(Pseudo_Selector, p, name, element); + pseudo->selector(wrapped); + return pseudo; + } + } } + } + // EO if pseudo selector - } - // EO if pseudo selector + else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) { + return SASS_MEMORY_NEW(Pseudo_Selector, pstate, lexed, element); + } + else if (lex < pseudo_prefix >()) { + css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was "); + } - else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) { - return SASS_MEMORY_NEW(Pseudo_Selector, pstate, lexed); } - else if(lex < pseudo_prefix >()) { - css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was "); + else { + lex < identifier >(); // needed for error message? + css_error("Invalid CSS", " after ", ": expected selector, was "); } + css_error("Invalid CSS", " after ", ": expected \")\", was "); // unreachable statement @@ -1038,7 +849,7 @@ namespace Sass { } /* parse block comment and add to block */ - void Parser::parse_block_comments() + void Parser::parse_block_comments(bool store) { Block_Obj block = block_stack.back(); @@ -1046,7 +857,7 @@ namespace Sass { bool is_important = lexed.begin[2] == '!'; // flag on second param is to skip loosely over comments String_Obj contents = parse_interpolated_chunk(lexed, true, false); - block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); + if (store) block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); } } @@ -1104,23 +915,6 @@ namespace Sass { } } - // parse +/- and return false if negative - // this is never hit via spec tests - bool Parser::parse_number_prefix() - { - bool positive = true; - while(true) { - if (lex < block_comment >()) continue; - if (lex < number_prefix >()) continue; - if (lex < exactly < '-' > >()) { - positive = !positive; - continue; - } - break; - } - return positive; - } - Expression_Obj Parser::parse_map() { NESTING_GUARD(nestings); @@ -1530,13 +1324,6 @@ namespace Sass { if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); return ex; } - // this whole branch is never hit via spec tests - else if (peek < sequence < one_plus < alternatives < css_whitespace, exactly<'-'>, exactly<'+'> > >, number > >()) { - if (parse_number_prefix()) return parse_value(); // prefix is positive - Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_value()); - if (ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } else { return parse_value(); } @@ -2326,20 +2113,107 @@ namespace Sass { return call.detach(); } - // EO parse_while_directive - Media_Block_Obj Parser::parse_media_block() + + std::vector Parser::parseCssMediaQueries() { - stack.push_back(Scope::Media); - Media_Block_Obj media_block = SASS_MEMORY_NEW(Media_Block, pstate, {}, {}); + std::vector result; + do { + if (auto query = parseCssMediaQuery()) { + result.push_back(query); + } + } while (lex>()); + return result; + } + + std::string Parser::parseIdentifier() + { + if (lex < identifier >(false)) { + return std::string(lexed); + } + return std::string(); + } + + CssMediaQuery_Obj Parser::parseCssMediaQuery() + { + CssMediaQuery_Obj result = SASS_MEMORY_NEW(CssMediaQuery, pstate); + lex(false); + + // Check if any tokens are to parse + if (!peek_css>()) { + + std::string token1(parseIdentifier()); + lex(false); + + if (token1.empty()) { + return {}; + } + + std::string token2(parseIdentifier()); + lex(false); + + if (Util::equalsLiteral("and", token2)) { + result->type(token1); + } + else { + if (token2.empty()) { + result->type(token1); + } + else { + result->modifier(token1); + result->type(token2); + } + + if (lex < kwd_and >()) { + lex(false); + } + else { + return result; + } + + } + + } + + std::vector queries; + + do { + lex(false); + + if (lex>()) { + // In dart sass parser returns a pure string + if (lex < skip_over_scopes < exactly < '(' >, exactly < ')' > > >()) { + std::string decl("(" + std::string(lexed)); + queries.push_back(decl); + } + // Should be: parseDeclarationValue; + if (!lex>()) { + // Should we throw an error here? + } + } + } while (lex < kwd_and >()); + + result->features(queries); + + if (result->features().empty()) { + if (result->type().empty()) { + return {}; + } + } + + return result; + } - media_block->media_queries(parse_media_queries()); - Media_Block_Obj prev_media_block = last_media_block; - last_media_block = media_block; - media_block->block(parse_css_block()); - last_media_block = prev_media_block; + // EO parse_while_directive + MediaRule_Obj Parser::parseMediaRule() + { + MediaRule_Obj rule = SASS_MEMORY_NEW(MediaRule, pstate); + stack.push_back(Scope::Media); + rule->schema(parse_media_queries()); + parse_block_comments(false); + rule->block(parse_css_block()); stack.pop_back(); - return media_block.detach(); + return rule; } List_Obj Parser::parse_media_queries() @@ -2558,68 +2432,6 @@ namespace Sass { return cond; } - Directive_Obj Parser::parse_special_directive() - { - std::string kwd(lexed); - - if (lexed == "@else") error("Invalid CSS: @else must come after @if"); - - // this whole branch is never hit via spec tests - - Directive* at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); - Lookahead lookahead = lookahead_for_include(position); - if (lookahead.found && !lookahead.has_interpolants) { - at_rule->selector(parse_selector_list(false)); - } - - lex < css_comments >(false); - - if (lex < static_property >()) { - at_rule->value(parse_interpolated_chunk(Token(lexed))); - } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { - at_rule->value(parse_list()); - } - - lex < css_comments >(false); - - if (peek< exactly<'{'> >()) { - at_rule->block(parse_block()); - } - - return at_rule; - } - - // this whole branch is never hit via spec tests - Directive_Obj Parser::parse_prefixed_directive() - { - std::string kwd(lexed); - - if (lexed == "@else") error("Invalid CSS: @else must come after @if"); - - Directive_Obj at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); - Lookahead lookahead = lookahead_for_include(position); - if (lookahead.found && !lookahead.has_interpolants) { - at_rule->selector(parse_selector_list(false)); - } - - lex < css_comments >(false); - - if (lex < static_property >()) { - at_rule->value(parse_interpolated_chunk(Token(lexed))); - } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { - at_rule->value(parse_list()); - } - - lex < css_comments >(false); - - if (peek< exactly<'{'> >()) { - at_rule->block(parse_block()); - } - - return at_rule; - } - - Directive_Obj Parser::parse_directive() { Directive_Obj directive = SASS_MEMORY_NEW(Directive, pstate, lexed); @@ -2660,6 +2472,7 @@ namespace Sass { lex < one_plus < alternatives < + exactly <'>'>, sequence < exactly <'\\'>, any_char diff --git a/src/parser.hpp b/src/parser.hpp index 05f81d130..9235fc985 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -1,6 +1,8 @@ #ifndef SASS_PARSER_H #define SASS_PARSER_H +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" #include @@ -40,7 +42,6 @@ namespace Sass { Context& ctx; std::vector block_stack; std::vector stack; - Media_Block* last_media_block; const char* source; const char* position; const char* end; @@ -55,7 +56,7 @@ namespace Sass { Token lexed; Parser(Context& ctx, const ParserState& pstate, Backtraces traces, bool allow_parent = true) - : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), + : ParserState(pstate), ctx(ctx), block_stack(), stack(0), source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate(pstate), traces(traces), indentation(0), nestings(0), allow_parent(allow_parent) { @@ -67,7 +68,7 @@ namespace Sass { static Parser from_c_str(const char* beg, const char* end, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = nullptr, bool allow_parent = true); static Parser from_token(Token t, Context& ctx, Backtraces, ParserState pstate = ParserState("[TOKEN]"), const char* source = nullptr); // special static parsers to convert strings into certain selectors - static Selector_List_Obj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = nullptr, bool allow_parent = true); + static SelectorListObj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = nullptr, bool allow_parent = true); #ifdef __clang__ @@ -259,20 +260,20 @@ namespace Sass { Argument_Obj parse_argument(); Assignment_Obj parse_assignment(); Ruleset_Obj parse_ruleset(Lookahead lookahead); - Selector_List_Obj parse_selector_list(bool chroot); - Complex_Selector_Obj parse_complex_selector(bool chroot); + SelectorListObj parseSelectorList(bool chroot); + ComplexSelectorObj parseComplexSelector(bool chroot); Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot); - Compound_Selector_Obj parse_compound_selector(); - Simple_Selector_Obj parse_simple_selector(); - Wrapped_Selector_Obj parse_negated_selector(); - Simple_Selector_Obj parse_pseudo_selector(); + CompoundSelectorObj parseCompoundSelector(); + SimpleSelectorObj parse_simple_selector(); + Pseudo_Selector_Obj parse_negated_selector2(); + Expression* parse_binominal(); + SimpleSelectorObj parse_pseudo_selector(); Attribute_Selector_Obj parse_attribute_selector(); Block_Obj parse_block(bool is_root = false); Block_Obj parse_css_block(bool is_root = false); bool parse_block_nodes(bool is_root = false); bool parse_block_node(bool is_root = false); - bool parse_number_prefix(); Declaration_Obj parse_declaration(); Expression_Obj parse_map(); Expression_Obj parse_bracket_list(); @@ -303,10 +304,13 @@ namespace Sass { For_Obj parse_for_directive(); Each_Obj parse_each_directive(); While_Obj parse_while_directive(); + MediaRule_Obj parseMediaRule(); + std::vector parseCssMediaQueries(); + std::string parseIdentifier(); + CssMediaQuery_Obj parseCssMediaQuery(); Return_Obj parse_return_directive(); Content_Obj parse_content_directive(); void parse_charset_directive(); - Media_Block_Obj parse_media_block(); List_Obj parse_media_queries(); Media_Query_Obj parse_media_query(); Media_Query_Expression_Obj parse_media_expression(); @@ -320,8 +324,6 @@ namespace Sass { At_Root_Block_Obj parse_at_root_block(); At_Root_Query_Obj parse_at_root_query(); String_Schema_Obj parse_almost_any_value(); - Directive_Obj parse_special_directive(); - Directive_Obj parse_prefixed_directive(); Directive_Obj parse_directive(); Warning_Obj parse_warning(); Error_Obj parse_error(); @@ -340,7 +342,7 @@ namespace Sass { Token lex_variable(); Token lex_identifier(); - void parse_block_comments(); + void parse_block_comments(bool store = true); Lookahead lookahead_for_value(const char* start = 0); Lookahead lookahead_for_selector(const char* start = 0); diff --git a/src/parser_selectors.cpp b/src/parser_selectors.cpp new file mode 100644 index 000000000..d76dd2ca7 --- /dev/null +++ b/src/parser_selectors.cpp @@ -0,0 +1,187 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include "parser.hpp" + +namespace Sass { + + using namespace Prelexer; + using namespace Constants; + + ComplexSelectorObj Parser::parseComplexSelector(bool chroot) + { + + NESTING_GUARD(nestings); + + lex < block_comment >(); + advanceToNextToken(); + + ComplexSelectorObj sel = SASS_MEMORY_NEW(ComplexSelector, pstate); + + if (peek < end_of_file >()) return sel; + + while (true) { + + lex < block_comment >(); + advanceToNextToken(); + + // check for child (+) combinator + if (lex < exactly < selector_combinator_child > >()) { + sel->append(SASS_MEMORY_NEW(SelectorCombinator, pstate, SelectorCombinator::CHILD, peek_newline())); + } + // check for general sibling (~) combinator + else if (lex < exactly < selector_combinator_general > >()) { + sel->append(SASS_MEMORY_NEW(SelectorCombinator, pstate, SelectorCombinator::GENERAL, peek_newline())); + } + // check for adjecant sibling (+) combinator + else if (lex < exactly < selector_combinator_adjacent > >()) { + sel->append(SASS_MEMORY_NEW(SelectorCombinator, pstate, SelectorCombinator::ADJACENT, peek_newline())); + } + // check if we can parse a compound selector + else if (CompoundSelectorObj compound = parseCompoundSelector()) { + sel->append(compound); + } + else { + break; + } + } + + if (sel->empty()) return {}; + + // check if we parsed any parent references + sel->chroots(sel->has_real_parent_ref() || chroot); + + sel->update_pstate(pstate); + + return sel; + + } + + SelectorListObj Parser::parseSelectorList(bool chroot) + { + + bool reloop; + bool had_linefeed = false; + NESTING_GUARD(nestings); + SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate); + + if (peek_css< alternatives < end_of_file, exactly <'{'>, exactly <','> > >()) { + css_error("Invalid CSS", " after ", ": expected selector, was "); + } + + do { + reloop = false; + + had_linefeed = had_linefeed || peek_newline(); + + if (peek_css< alternatives < class_char < selector_list_delims > > >()) + break; // in case there are superfluous commas at the end + + // now parse the complex selector + ComplexSelectorObj complex = parseComplexSelector(chroot); + if (complex.isNull()) return list.detach(); + complex->hasPreLineFeed(had_linefeed); + + had_linefeed = false; + + while (peek_css< exactly<','> >()) + { + lex< css_comments >(false); + // consume everything up and including the comma separator + reloop = lex< exactly<','> >() != 0; + // remember line break (also between some commas) + had_linefeed = had_linefeed || peek_newline(); + // remember line break (also between some commas) + } + list->append(complex); + + } while (reloop); + + while (lex_css< kwd_optional >()) { + list->is_optional(true); + } + + // update for end position + list->update_pstate(pstate); + + return list.detach(); + } + + // parse one compound selector, which is basically + // a list of simple selectors (directly adjacent) + // lex them exactly (without skipping white-space) + CompoundSelectorObj Parser::parseCompoundSelector() + { + // init an empty compound selector wrapper + CompoundSelectorObj seq = SASS_MEMORY_NEW(CompoundSelector, pstate); + + // skip initial white-space + lex < block_comment >(); + advanceToNextToken(); + + if (lex< exactly<'&'> >(false)) + { + // ToDo: check the conditions and try to simplify flag passing + if (!allow_parent) error("Parent selectors aren't allowed here."); + // Create and append a new parent selector object + seq->hasRealParent(true); + } + + // parse list + while (true) + { + // remove all block comments + // leaves trailing white-space + lex < block_comment >(); + // parse parent selector + if (lex< exactly<'&'> >(false)) + { + // parent selector only allowed at start + // upcoming Sass may allow also trailing + ParserState state(pstate); + SimpleSelectorObj prev = (*seq)[seq->length()-1]; + std::string sel(prev->to_string({ NESTED, 5 })); + std::string found("&"); + if (lex < identifier >()) { found += std::string(lexed); } + // ToDo: parser should throw parser exceptions + error("Invalid CSS after \"" + sel + "\": expected \"{\", was \"" + found + "\"\n\n" + "\"" + found + "\" may only be used at the beginning of a compound selector.", state); + } + // parse functional + else if (match < re_functional >()) + { + seq->append(parse_simple_selector()); + } + + // parse type selector + else if (lex< re_type_selector >(false)) + { + seq->append(SASS_MEMORY_NEW(Type_Selector, pstate, lexed)); + } + // peek for abort conditions + else if (peek< spaces >()) break; + else if (peek< end_of_file >()) { break; } + else if (peek_css < class_char < selector_combinator_ops > >()) break; + else if (peek_css < class_char < complex_selector_delims > >()) break; + // otherwise parse another simple selector + else { + SimpleSelectorObj sel = parse_simple_selector(); + if (!sel) return {}; + seq->append(sel); + } + } + // EO while true + + if (seq && !peek_css>>()) { + seq->hasPostLineBreak(peek_newline()); + } + + // We may have set hasRealParent + if (seq && seq->empty() && !seq->hasRealParent()) return {}; + + return seq; + } + + +} diff --git a/src/paths.hpp b/src/paths.hpp deleted file mode 100644 index aabab94ae..000000000 --- a/src/paths.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef SASS_PATHS_H -#define SASS_PATHS_H - -#include -#include -#include - - -template -std::string vector_to_string(std::vector v) -{ - std::stringstream buffer; - buffer << "["; - - if (!v.empty()) - { buffer << v[0]; } - else - { buffer << "]"; } - - if (v.size() == 1) - { buffer << "]"; } - else - { - for (size_t i = 1, S = v.size(); i < S; ++i) buffer << ", " << v[i]; - buffer << "]"; - } - - return buffer.str(); -} - -namespace Sass { - - - template - std::vector > paths(std::vector > strata, size_t from_end = 0) - { - if (strata.empty()) { - return std::vector >(); - } - - size_t end = strata.size() - from_end; - if (end <= 1) { - std::vector > starting_points; - starting_points.reserve(strata[0].size()); - for (size_t i = 0, S = strata[0].size(); i < S; ++i) { - std::vector starting_point; - starting_point.push_back(strata[0][i]); - starting_points.push_back(starting_point); - } - return starting_points; - } - - std::vector > up_to_here = paths(strata, from_end + 1); - std::vector here = strata[end-1]; - - std::vector > branches; - branches.reserve(up_to_here.size() * here.size()); - for (size_t i = 0, S1 = up_to_here.size(); i < S1; ++i) { - for (size_t j = 0, S2 = here.size(); j < S2; ++j) { - std::vector branch = up_to_here[i]; - branch.push_back(here[j]); - branches.push_back(branch); - } - } - - return branches; - } - -} - -#endif diff --git a/src/permutate.hpp b/src/permutate.hpp new file mode 100644 index 000000000..2b83d02f5 --- /dev/null +++ b/src/permutate.hpp @@ -0,0 +1,136 @@ +#ifndef SASS_PATHS_H +#define SASS_PATHS_H + +#include + +namespace Sass { + + // Returns a list of all possible paths through the given lists. + // + // For example, given `[[1, 2], [3, 4], [5]]`, this returns: + // + // ``` + // [[1, 3, 5], + // [2, 3, 5], + // [1, 4, 5], + // [2, 4, 5]] + // ``` + // + // Note: called `paths` in dart-sass + template + std::vector> permutate( + const std::vector>& in) + { + + size_t L = in.size(); + size_t n = 0; + size_t* state = new size_t[L + 1]; + std::vector> out; + + // First initialize all states for every permutation group + for (size_t i = 0; i < L; i += 1) { + if (in[i].size() == 0) return {}; + state[i] = in[i].size() - 1; + } + while (true) { + std::vector perm; + // Create one permutation for state + for (size_t i = 0; i < L; i += 1) { + perm.push_back(in.at(i).at(in[i].size() - state[i] - 1)); + } + // Current group finished + if (state[n] == 0) { + // Find position of next decrement + while (n < L && state[++n] == 0) {} + + if (n == L) { + out.push_back(perm); + break; + } + + state[n] -= 1; + + for (size_t p = 0; p < n; p += 1) { + state[p] = in[p].size() - 1; + } + + // Restart from front + n = 0; + + } + else { + state[n] -= 1; + } + out.push_back(perm); + } + + delete[] state; + return out; + } + // EO permutate + + // ToDo: this variant is used in resolve_parent_refs + template + std::vector> + permutateAlt(const std::vector>& in) { + + size_t L = in.size(); + size_t n = in.size() - 1; + size_t* state = new size_t[L]; + std::vector< std::vector> out; + + // First initialize all states for every permutation group + for (size_t i = 0; i < L; i += 1) { + if (in[i].size() == 0) return {}; + state[i] = in[i].size() - 1; + } + + while (true) { + /* + // std::cerr << "PERM: "; + for (size_t p = 0; p < L; p++) + { // std::cerr << state[p] << " "; } + // std::cerr << "\n"; + */ + std::vector perm; + // Create one permutation for state + for (size_t i = 0; i < L; i += 1) { + perm.push_back(in.at(i).at(in[i].size() - state[i] - 1)); + } + // Current group finished + if (state[n] == 0) { + // Find position of next decrement + while (n > 0 && state[--n] == 0) + { + + } + // Check for end condition + if (state[n] != 0) { + // Decrease next on the left side + state[n] -= 1; + // Reset all counters to the right + for (size_t p = n + 1; p < L; p += 1) { + state[p] = in[p].size() - 1; + } + // Restart from end + n = L - 1; + } + else { + out.push_back(perm); + break; + } + } + else { + state[n] -= 1; + } + out.push_back(perm); + } + + delete[] state; + return out; + } + // EO permutateAlt + +} + +#endif diff --git a/src/position.hpp b/src/position.hpp index 923be3c59..3e3a1c203 100644 --- a/src/position.hpp +++ b/src/position.hpp @@ -86,7 +86,7 @@ namespace Sass { size_t length() const { return end - begin; } std::string ws_before() const { return std::string(prefix, begin); } - const std::string to_string() const { return std::string(begin, end); } + std::string to_string() const { return std::string(begin, end); } std::string time_wspace() const { std::string str(to_string()); std::string whitespaces(" \t\f\v\n\r"); diff --git a/src/remove_placeholders.cpp b/src/remove_placeholders.cpp index b9e57420f..6b2c57723 100644 --- a/src/remove_placeholders.cpp +++ b/src/remove_placeholders.cpp @@ -1,11 +1,9 @@ // sass.hpp must go before all system headers to get the // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" +#include "ast.hpp" #include "remove_placeholders.hpp" -#include "context.hpp" -#include "inspect.hpp" -#include namespace Sass { @@ -13,75 +11,76 @@ namespace Sass { { } void Remove_Placeholders::operator()(Block* b) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* st = b->at(i); - st->perform(this); - } + for (size_t i = 0, L = b->length(); i < L; ++i) { + if (b->get(i)) b->get(i)->perform(this); + } } - Selector_List* Remove_Placeholders::remove_placeholders(Selector_List* sl) + void Remove_Placeholders::remove_placeholders(SimpleSelector* simple) { - Selector_List* new_sl = SASS_MEMORY_NEW(Selector_List, sl->pstate()); - - for (size_t i = 0, L = sl->length(); i < L; ++i) { - if (!sl->at(i)->contains_placeholder()) { - new_sl->append(sl->at(i)); - } + if (Pseudo_Selector * pseudo = simple->getPseudoSelector()) { + if (pseudo->selector()) remove_placeholders(pseudo->selector()); } - - return new_sl; - } + void Remove_Placeholders::remove_placeholders(CompoundSelector* compound) + { + for (size_t i = 0, L = compound->length(); i < L; ++i) { + if (compound->get(i)) remove_placeholders(compound->get(i)); + } + listEraseItemIf(compound->elements(), listIsEmpty); + } - void Remove_Placeholders::operator()(Ruleset* r) { - // Create a new selector group without placeholders - Selector_List_Obj sl = Cast(r->selector()); - - if (sl) { - // Set the new placeholder selector list - r->selector(remove_placeholders(sl)); - // Remove placeholders in wrapped selectors - for (Complex_Selector_Obj cs : sl->elements()) { - while (cs) { - if (cs->head()) { - for (Simple_Selector_Obj& ss : cs->head()->elements()) { - if (Wrapped_Selector* ws = Cast(ss)) { - if (Selector_List* wsl = Cast(ws->selector())) { - Selector_List* clean = remove_placeholders(wsl); - // also clean superflous parent selectors - // probably not really the correct place - clean->remove_parent_selectors(); - ws->selector(clean); - } - } - } - } - cs = cs->tail(); - } + void Remove_Placeholders::remove_placeholders(ComplexSelector* complex) + { + if (complex->has_placeholder()) { + complex->clear(); // remove all + } + else { + for (size_t i = 0, L = complex->length(); i < L; ++i) { + if (CompoundSelector * compound = complex->get(i)->getCompound()) { + if (compound) remove_placeholders(compound); } } + listEraseItemIf(complex->elements(), listIsEmpty); + } + } - // Iterate into child blocks - Block_Obj b = r->block(); + SelectorList* Remove_Placeholders::remove_placeholders(SelectorList* sl) + { + for (size_t i = 0, L = sl->length(); i < L; ++i) { + if (sl->get(i)) remove_placeholders(sl->get(i)); + } + listEraseItemIf(sl->elements(), listIsEmpty); + return sl; + } - for (size_t i = 0, L = b->length(); i < L; ++i) { - if (b->at(i)) { - Statement_Obj st = b->at(i); - st->perform(this); - } - } + void Remove_Placeholders::operator()(CssMediaRule* rule) + { + if (rule->block()) operator()(rule->block()); } - void Remove_Placeholders::operator()(Media_Block* m) { - operator()(m->block()); + void Remove_Placeholders::operator()(Ruleset* r) + { + if (SelectorListObj sl = r->selector()) { + // Set the new placeholder selector list + r->selector((remove_placeholders(sl))); + } + // Iterate into child blocks + Block_Obj b = r->block(); + for (size_t i = 0, L = b->length(); i < L; ++i) { + if (b->get(i)) { b->get(i)->perform(this); } + } } - void Remove_Placeholders::operator()(Supports_Block* m) { - operator()(m->block()); + + void Remove_Placeholders::operator()(Supports_Block* m) + { + if (m->block()) operator()(m->block()); } - void Remove_Placeholders::operator()(Directive* a) { - if (a->block()) a->block()->perform(this); + void Remove_Placeholders::operator()(Directive* a) + { + if (a->block()) a->block()->perform(this); } } diff --git a/src/remove_placeholders.hpp b/src/remove_placeholders.hpp index 67f4cffcb..34654d3fa 100644 --- a/src/remove_placeholders.hpp +++ b/src/remove_placeholders.hpp @@ -1,33 +1,36 @@ #ifndef SASS_REMOVE_PLACEHOLDERS_H #define SASS_REMOVE_PLACEHOLDERS_H -#pragma once - -#include "ast.hpp" +#include "ast_fwd_decl.hpp" #include "operation.hpp" namespace Sass { + class Remove_Placeholders : public Operation_CRTP { + + public: + + SelectorList* remove_placeholders(SelectorList*); + void remove_placeholders(SimpleSelector* simple); + void remove_placeholders(CompoundSelector* complex); + void remove_placeholders(ComplexSelector* complex); - class Remove_Placeholders : public Operation_CRTP { - public: - Selector_List* remove_placeholders(Selector_List*); + public: + Remove_Placeholders(); + ~Remove_Placeholders() { } - public: - Remove_Placeholders(); - ~Remove_Placeholders() { } + void operator()(Block*); + void operator()(Ruleset*); + void operator()(CssMediaRule*); + void operator()(Supports_Block*); + void operator()(Directive*); - void operator()(Block*); - void operator()(Ruleset*); - void operator()(Media_Block*); - void operator()(Supports_Block*); - void operator()(Directive*); + // ignore missed types + template + void fallback(U x) {} - // ignore missed types - template - void fallback(U x) {} - }; + }; } diff --git a/src/sass.hpp b/src/sass.hpp index 014e4fa1a..1598d954a 100644 --- a/src/sass.hpp +++ b/src/sass.hpp @@ -65,6 +65,7 @@ namespace Sass { // only used internal to trigger ruby inspect behavior const static Sass_Output_Style INSPECT = SASS_STYLE_INSPECT; const static Sass_Output_Style TO_SASS = SASS_STYLE_TO_SASS; + const static Sass_Output_Style TO_CSS = SASS_STYLE_TO_CSS; // helper to aid dreaded MSVC debug mode // see implementation for more details diff --git a/src/sass_context.cpp b/src/sass_context.cpp index cca864f1c..518334ba1 100644 --- a/src/sass_context.cpp +++ b/src/sass_context.cpp @@ -1,23 +1,10 @@ // sass.hpp must go before all system headers to get the // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" - -#include -#include -#include -#include -#include - -#include "sass.h" #include "ast.hpp" -#include "file.hpp" -#include "json.hpp" -#include "util.hpp" -#include "context.hpp" -#include "sass_context.hpp" + #include "sass_functions.hpp" -#include "ast_fwd_decl.hpp" -#include "error_handling.hpp" +#include "json.hpp" #define LFEED "\n" @@ -31,6 +18,24 @@ namespace Sass { return json_mkstring(str.c_str()); } + static void handle_string_error(Sass_Context* c_ctx, const std::string& msg, int severety) + { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Internal Error: " << msg << std::endl; + json_append_member(json_err, "status", json_mknumber(severety)); + json_append_member(json_err, "message", json_mkstring(msg.c_str())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(msg.c_str()); + c_ctx->error_status = severety; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + static int handle_error(Sass_Context* c_ctx) { try { throw; @@ -110,7 +115,7 @@ namespace Sass { json_append_member(json_err, "message", json_mkstring(e.what())); json_append_member(json_err, "formatted", json_mkstream(msg_stream)); try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} + catch (...) {} // silently ignore this error? c_ctx->error_message = sass_copy_string(msg_stream.str()); c_ctx->error_text = sass_copy_c_string(e.what()); c_ctx->error_status = 1; @@ -124,82 +129,20 @@ namespace Sass { } catch (std::bad_alloc& ba) { std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Unable to allocate memory: " << ba.what() << std::endl; - json_append_member(json_err, "status", json_mknumber(2)); - json_append_member(json_err, "message", json_mkstring(ba.what())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(ba.what()); - c_ctx->error_status = 2; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); + msg_stream << "Unable to allocate memory: " << ba.what(); + handle_string_error(c_ctx, msg_stream.str(), 2); } catch (std::exception& e) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Internal Error: " << e.what() << std::endl; - json_append_member(json_err, "status", json_mknumber(3)); - json_append_member(json_err, "message", json_mkstring(e.what())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e.what()); - c_ctx->error_status = 3; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); + handle_string_error(c_ctx, e.what(), 3); } catch (std::string& e) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Internal Error: " << e << std::endl; - json_append_member(json_err, "status", json_mknumber(4)); - json_append_member(json_err, "message", json_mkstring(e.c_str())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e.c_str()); - c_ctx->error_status = 4; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); + handle_string_error(c_ctx, e, 4); } catch (const char* e) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Internal Error: " << e << std::endl; - json_append_member(json_err, "status", json_mknumber(4)); - json_append_member(json_err, "message", json_mkstring(e)); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e); - c_ctx->error_status = 4; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); + handle_string_error(c_ctx, e, 4); } catch (...) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Unknown error occurred" << std::endl; - json_append_member(json_err, "status", json_mknumber(5)); - json_append_member(json_err, "message", json_mkstring("unknown")); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string("unknown"); - c_ctx->error_status = 5; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); + handle_string_error(c_ctx, "unknown", 5); } return c_ctx->error_status; } @@ -399,7 +342,9 @@ extern "C" { Sass_File_Context* ADDCALL sass_make_file_context(const char* input_path) { - SharedObj::setTaint(true); // needed for static colors + #ifdef DEBUG_SHARED_PTR + SharedObj::setTaint(true); + #endif struct Sass_File_Context* ctx = (struct Sass_File_Context*) calloc(1, sizeof(struct Sass_File_Context)); if (ctx == 0) { std::cerr << "Error allocating memory for file context" << std::endl; return 0; } ctx->type = SASS_CONTEXT_FILE; @@ -416,6 +361,9 @@ extern "C" { Sass_Data_Context* ADDCALL sass_make_data_context(char* source_string) { + #ifdef DEBUG_SHARED_PTR + SharedObj::setTaint(true); + #endif struct Sass_Data_Context* ctx = (struct Sass_Data_Context*) calloc(1, sizeof(struct Sass_Data_Context)); if (ctx == 0) { std::cerr << "Error allocating memory for data context" << std::endl; return 0; } ctx->type = SASS_CONTEXT_DATA; diff --git a/src/sass_util.cpp b/src/sass_util.cpp deleted file mode 100644 index b60e64c57..000000000 --- a/src/sass_util.cpp +++ /dev/null @@ -1,152 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "node.hpp" - -namespace Sass { - - - /* - # This is the equivalent of ruby's Sass::Util.paths. - # - # Return an array of all possible paths through the given arrays. - # - # @param arrs [NodeCollection>] - # @return [NodeCollection>] - # - # @example - # paths([[1, 2], [3, 4], [5]]) #=> - # # [[1, 3, 5], - # # [2, 3, 5], - # # [1, 4, 5], - # # [2, 4, 5]] - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - def paths(arrs) - // I changed the inject and maps to an iterative approach to make it easier to implement in C++ - loopStart = [[]] - - for arr in arrs do - permutations = [] - for e in arr do - for path in loopStart do - permutations.push(path + [e]) - end - end - loopStart = permutations - end - end - */ - Node paths(const Node& arrs) { - - Node loopStart = Node::createCollection(); - loopStart.collection()->push_back(Node::createCollection()); - - for (NodeDeque::iterator arrsIter = arrs.collection()->begin(), arrsEndIter = arrs.collection()->end(); - arrsIter != arrsEndIter; ++arrsIter) { - - Node& arr = *arrsIter; - - Node permutations = Node::createCollection(); - - for (NodeDeque::iterator arrIter = arr.collection()->begin(), arrIterEnd = arr.collection()->end(); - arrIter != arrIterEnd; ++arrIter) { - - Node& e = *arrIter; - - for (NodeDeque::iterator loopStartIter = loopStart.collection()->begin(), loopStartIterEnd = loopStart.collection()->end(); - loopStartIter != loopStartIterEnd; ++loopStartIter) { - - Node& path = *loopStartIter; - - Node newPermutation = Node::createCollection(); - newPermutation.got_line_feed = arr.got_line_feed; - newPermutation.plus(path); - newPermutation.collection()->push_back(e); - - permutations.collection()->push_back(newPermutation); - } - } - - loopStart = permutations; - } - - return loopStart; - } - - - /* - This is the equivalent of ruby sass' Sass::Util.flatten and [].flatten. - Sass::Util.flatten requires the number of levels to flatten, while - [].flatten doesn't and will flatten the entire array. This function - supports both. - - # Flattens the first `n` nested arrays. If n == -1, all arrays will be flattened - # - # @param arr [NodeCollection] The array to flatten - # @param n [int] The number of levels to flatten - # @return [NodeCollection] The flattened array - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - def flatten(arr, n = -1) - if n != -1 and n == 0 then - return arr - end - - flattened = [] - - for e in arr do - if e.is_a?(Array) then - flattened.concat(flatten(e, n - 1)) - else - flattened << e - end - end - - return flattened - end - */ - Node flatten(Node& arr, int n) { - if (n != -1 && n == 0) { - return arr; - } - - Node flattened = Node::createCollection(); - if (arr.got_line_feed) flattened.got_line_feed = true; - - for (NodeDeque::iterator iter = arr.collection()->begin(), iterEnd = arr.collection()->end(); - iter != iterEnd; iter++) { - Node& e = *iter; - - // e has the lf set - if (e.isCollection()) { - - // e.collection().got_line_feed = e.got_line_feed; - Node recurseFlattened = flatten(e, n - 1); - - if(e.got_line_feed) { - flattened.got_line_feed = e.got_line_feed; - recurseFlattened.got_line_feed = e.got_line_feed; - } - - for(auto i : (*recurseFlattened.collection())) { - if (recurseFlattened.got_line_feed) { - - i.got_line_feed = true; - } - flattened.collection()->push_back(i); - } - - } else { - flattened.collection()->push_back(e); - } - } - - return flattened; - } -} diff --git a/src/sass_util.hpp b/src/sass_util.hpp deleted file mode 100644 index 816da5fd8..000000000 --- a/src/sass_util.hpp +++ /dev/null @@ -1,256 +0,0 @@ -#ifndef SASS_SASS_UTIL_H -#define SASS_SASS_UTIL_H - -#include "ast.hpp" -#include "node.hpp" -#include "debug.hpp" - -namespace Sass { - - - - - /* - This is for ports of functions in the Sass:Util module. - */ - - - /* - # Return a Node collection of all possible paths through the given Node collection of Node collections. - # - # @param arrs [NodeCollection>] - # @return [NodeCollection>] - # - # @example - # paths([[1, 2], [3, 4], [5]]) #=> - # # [[1, 3, 5], - # # [2, 3, 5], - # # [1, 4, 5], - # # [2, 4, 5]] - */ - Node paths(const Node& arrs); - - - /* - This class is a default implementation of a Node comparator that can be passed to the lcs function below. - It uses operator== for equality comparision. It then returns one if the Nodes are equal. - */ - class DefaultLcsComparator { - public: - bool operator()(const Node& one, const Node& two, Node& out) const { - // TODO: Is this the correct C++ interpretation? - // block ||= proc {|a, b| a == b && a} - if (one == two) { - out = one; - return true; - } - - return false; - } - }; - - - typedef std::vector > LCSTable; - - - /* - This is the equivalent of ruby's Sass::Util.lcs_backtrace. - - # Computes a single longest common subsequence for arrays x and y. - # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS - */ - template - Node lcs_backtrace(const LCSTable& c, const Node& x, const Node& y, int i, int j, const ComparatorType& comparator) { - DEBUG_PRINTLN(LCS, "LCSBACK: X=" << x << " Y=" << y << " I=" << i << " J=" << j) - - if (i == 0 || j == 0) { - DEBUG_PRINTLN(LCS, "RETURNING EMPTY") - return Node::createCollection(); - } - - NodeDeque& xChildren = *(x.collection()); - NodeDeque& yChildren = *(y.collection()); - - Node compareOut = Node::createNil(); - if (comparator(xChildren[i], yChildren[j], compareOut)) { - DEBUG_PRINTLN(LCS, "RETURNING AFTER ELEM COMPARE") - Node result = lcs_backtrace(c, x, y, i - 1, j - 1, comparator); - result.collection()->push_back(compareOut); - return result; - } - - if (c[i][j - 1] > c[i - 1][j]) { - DEBUG_PRINTLN(LCS, "RETURNING AFTER TABLE COMPARE") - return lcs_backtrace(c, x, y, i, j - 1, comparator); - } - - DEBUG_PRINTLN(LCS, "FINAL RETURN") - return lcs_backtrace(c, x, y, i - 1, j, comparator); - } - - - /* - This is the equivalent of ruby's Sass::Util.lcs_table. - - # Calculates the memoization table for the Least Common Subsequence algorithm. - # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS - */ - template - void lcs_table(const Node& x, const Node& y, const ComparatorType& comparator, LCSTable& out) { - DEBUG_PRINTLN(LCS, "LCSTABLE: X=" << x << " Y=" << y) - - NodeDeque& xChildren = *(x.collection()); - NodeDeque& yChildren = *(y.collection()); - - LCSTable c(xChildren.size(), std::vector(yChildren.size())); - - // These shouldn't be necessary since the vector will be initialized to 0 already. - // x.size.times {|i| c[i][0] = 0} - // y.size.times {|j| c[0][j] = 0} - - for (size_t i = 1; i < xChildren.size(); i++) { - for (size_t j = 1; j < yChildren.size(); j++) { - Node compareOut = Node::createNil(); - - if (comparator(xChildren[i], yChildren[j], compareOut)) { - c[i][j] = c[i - 1][j - 1] + 1; - } else { - c[i][j] = std::max(c[i][j - 1], c[i - 1][j]); - } - } - } - - out = c; - } - - - /* - This is the equivalent of ruby's Sass::Util.lcs. - - # Computes a single longest common subsequence for `x` and `y`. - # If there are more than one longest common subsequences, - # the one returned is that which starts first in `x`. - - # @param x [NodeCollection] - # @param y [NodeCollection] - # @comparator An equality check between elements of `x` and `y`. - # @return [NodeCollection] The LCS - - http://en.wikipedia.org/wiki/Longest_common_subsequence_problem - */ - template - Node lcs(Node& x, Node& y, const ComparatorType& comparator) { - DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y) - - Node newX = Node::createCollection(); - newX.collection()->push_back(Node::createNil()); - newX.plus(x); - - Node newY = Node::createCollection(); - newY.collection()->push_back(Node::createNil()); - newY.plus(y); - - LCSTable table; - lcs_table(newX, newY, comparator, table); - - return lcs_backtrace(table, newX, newY, static_cast(newX.collection()->size()) - 1, static_cast(newY.collection()->size()) - 1, comparator); - } - - - /* - This is the equivalent of ruby sass' Sass::Util.flatten and [].flatten. - Sass::Util.flatten requires the number of levels to flatten, while - [].flatten doesn't and will flatten the entire array. This function - supports both. - - # Flattens the first `n` nested arrays. If n == -1, all arrays will be flattened - # - # @param arr [NodeCollection] The array to flatten - # @param n [int] The number of levels to flatten - # @return [NodeCollection] The flattened array - */ - Node flatten(Node& arr, int n = -1); - - - /* - This is the equivalent of ruby's Sass::Util.group_by_to_a. - - # Performs the equivalent of `enum.group_by.to_a`, but with a guaranteed - # order. Unlike [#hash_to_a], the resulting order isn't sorted key order; - # instead, it's the same order as `#group_by` has under Ruby 1.9 (key - # appearance order). - # - # @param enum [Enumerable] - # @return [Array<[Object, Array]>] An array of pairs. - - TODO: update @param and @return once I know what those are. - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - def group_by_to_a(enum, &block) - order = {} - - arr = [] - - grouped = {} - - for e in enum do - key = block[e] - unless order.include?(key) - order[key] = order.size - end - - if not grouped.has_key?(key) then - grouped[key] = [e] - else - grouped[key].push(e) - end - end - - grouped.each do |key, vals| - arr[order[key]] = [key, vals] - end - - arr - end - - */ - template - void group_by_to_a(std::vector& enumeration, KeyFunctorType& keyFunc, std::vector > >& arr /*out*/) { - - std::map order; - - std::map > grouped; - - for (typename std::vector::iterator enumIter = enumeration.begin(), enumIterEnd = enumeration.end(); enumIter != enumIterEnd; enumIter++) { - EnumType& e = *enumIter; - - KeyType key = keyFunc(e); - - if (grouped.find(key->hash()) == grouped.end()) { - order.insert(std::make_pair((unsigned int)order.size(), key)); - - std::vector newCollection; - newCollection.push_back(e); - grouped.insert(std::make_pair(key->hash(), newCollection)); - } else { - std::vector& collection = grouped.at(key->hash()); - collection.push_back(e); - } - } - - for (unsigned int index = 0; index < order.size(); index++) { - KeyType& key = order.at(index); - std::vector& values = grouped.at(key->hash()); - - std::pair > grouping = std::make_pair(key, values); - - arr.push_back(grouping); - } - } - - -} - -#endif diff --git a/src/sass_values.cpp b/src/sass_values.cpp index 4e6bd2dd5..518087255 100644 --- a/src/sass_values.cpp +++ b/src/sass_values.cpp @@ -6,7 +6,6 @@ #include #include "util.hpp" #include "eval.hpp" -#include "values.hpp" #include "operators.hpp" #include "sass/values.h" #include "sass_values.hpp" diff --git a/src/stylesheet.cpp b/src/stylesheet.cpp new file mode 100644 index 000000000..e0e4345c2 --- /dev/null +++ b/src/stylesheet.cpp @@ -0,0 +1,22 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include "stylesheet.hpp" + +namespace Sass { + + // Constructor + Sass::StyleSheet::StyleSheet(const Resource& res, Block_Obj root) : + Resource(res), + root(root) + { + } + + StyleSheet::StyleSheet(const StyleSheet& sheet) : + Resource(sheet), + root(sheet.root) + { + } + +} diff --git a/src/stylesheet.hpp b/src/stylesheet.hpp new file mode 100644 index 000000000..37e85772a --- /dev/null +++ b/src/stylesheet.hpp @@ -0,0 +1,57 @@ +#ifndef SASS_STYLESHEET_H +#define SASS_STYLESHEET_H + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include "ast_fwd_decl.hpp" +#include "extender.hpp" +#include "file.hpp" + +namespace Sass { + + // parsed stylesheet from loaded resource + // this should be a `Module` for sass 4.0 + class StyleSheet : public Resource { + public: + + // The canonical URL for this module's source file. This may be `null` + // if the module was loaded from a string without a URL provided. + // Uri get url; + + // Modules that this module uses. + // List get upstream; + + // The module's variables. + // Map get variables; + + // The module's functions. Implementations must ensure + // that each [Callable] is stored under its own name. + // Map get functions; + + // The module's mixins. Implementations must ensure that + // each [Callable] is stored under its own name. + // Map get mixins; + + // The extensions defined in this module, which is also able to update + // [css]'s style rules in-place based on downstream extensions. + // Extender extender; + + // The module's CSS tree. + Block_Obj root; + + public: + + // default argument constructor + StyleSheet(const Resource& res, Block_Obj root); + + // Copy constructor + StyleSheet(const StyleSheet& res); + + }; + + +} + +#endif diff --git a/src/subset_map.cpp b/src/subset_map.cpp deleted file mode 100644 index 11586f30a..000000000 --- a/src/subset_map.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "ast.hpp" -#include "subset_map.hpp" - -namespace Sass { - - void Subset_Map::put(const Compound_Selector_Obj& sel, const SubSetMapPair& value) - { - if (sel->empty()) throw std::runtime_error("internal error: subset map keys may not be empty"); - size_t index = values_.size(); - values_.push_back(value); - for (size_t i = 0, S = sel->length(); i < S; ++i) - { - hash_[(*sel)[i]].push_back(std::make_pair(sel, index)); - } - } - - std::vector Subset_Map::get_kv(const Compound_Selector_Obj& sel) - { - SimpleSelectorDict dict(sel->begin(), sel->end()); // XXX Set - std::vector indices; - for (size_t i = 0, S = sel->length(); i < S; ++i) { - if (!hash_.count((*sel)[i])) { - continue; - } - const std::vector >& subsets = hash_[(*sel)[i]]; - for (const std::pair& item : subsets) { - bool include = true; - for (const Simple_Selector_Obj& it : item.first->elements()) { - auto found = dict.find(it); - if (found == dict.end()) { - include = false; - break; - } - } - if (include) indices.push_back(item.second); - } - } - sort(indices.begin(), indices.end()); - std::vector::iterator indices_end = unique(indices.begin(), indices.end()); - indices.resize(distance(indices.begin(), indices_end)); - - std::vector results; - for (size_t i = 0, S = indices.size(); i < S; ++i) { - results.push_back(values_[indices[i]]); - } - return results; - } - - std::vector Subset_Map::get_v(const Compound_Selector_Obj& sel) - { - return get_kv(sel); - } - -} diff --git a/src/subset_map.hpp b/src/subset_map.hpp deleted file mode 100644 index 5c091e685..000000000 --- a/src/subset_map.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef SASS_SUBSET_MAP_H -#define SASS_SUBSET_MAP_H - -#include -#include -#include -#include -#include - -#include "ast_fwd_decl.hpp" - - -// #include -// #include -// template -// std::string vector_to_string(std::vector v) -// { -// std::stringstream buffer; -// buffer << "["; - -// if (!v.empty()) -// { buffer << v[0]; } -// else -// { buffer << "]"; } - -// if (v.size() == 1) -// { buffer << "]"; } -// else -// { -// for (size_t i = 1, S = v.size(); i < S; ++i) buffer << ", " << v[i]; -// buffer << "]"; -// } - -// return buffer.str(); -// } - -// template -// std::string set_to_string(set v) -// { -// std::stringstream buffer; -// buffer << "["; -// typename std::set::iterator i = v.begin(); -// if (!v.empty()) -// { buffer << *i; } -// else -// { buffer << "]"; } - -// if (v.size() == 1) -// { buffer << "]"; } -// else -// { -// for (++i; i != v.end(); ++i) buffer << ", " << *i; -// buffer << "]"; -// } - -// return buffer.str(); -// } - -namespace Sass { - - class Subset_Map { - private: - std::vector values_; - std::map >, OrderNodes > hash_; - public: - void put(const Compound_Selector_Obj& sel, const SubSetMapPair& value); - std::vector get_kv(const Compound_Selector_Obj& s); - std::vector get_v(const Compound_Selector_Obj& s); - bool empty() { return values_.empty(); } - void clear() { values_.clear(); hash_.clear(); } - const std::vector values(void) { return values_; } - }; - -} - -#endif diff --git a/src/to_value.cpp b/src/to_value.cpp index e20f5e83f..fa2b174d9 100644 --- a/src/to_value.cpp +++ b/src/to_value.cpp @@ -95,8 +95,8 @@ namespace Sass { return arg->value()->perform(this); } - // Selector_List is converted to a string - Value* To_Value::operator()(Selector_List* s) + // SelectorList is converted to a string + Value* To_Value::operator()(SelectorList* s) { return SASS_MEMORY_NEW(String_Quoted, s->pstate(), diff --git a/src/to_value.hpp b/src/to_value.hpp index 32f33b1b1..736e17def 100644 --- a/src/to_value.hpp +++ b/src/to_value.hpp @@ -36,7 +36,7 @@ namespace Sass { Value* operator()(Function*); // convert to string via `To_String` - Value* operator()(Selector_List*); + Value* operator()(SelectorList*); Value* operator()(Binary_Expression*); }; diff --git a/src/units.cpp b/src/units.cpp index ed99f5b2c..ac4203a4e 100644 --- a/src/units.cpp +++ b/src/units.cpp @@ -1,5 +1,7 @@ #include "sass.hpp" +#include #include +#include #include "units.hpp" #include "error_handling.hpp" diff --git a/src/util.cpp b/src/util.cpp index 1ac9194f8..cf45a75db 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -26,8 +26,8 @@ namespace Sass { #endif // https://github.com/sass/sass/commit/4e3e1d5684cc29073a507578fc977434ff488c93 - if (fmod(val, 1) - 0.5 > - std::pow(0.1, precision + 1)) return std::ceil(val); - else if (fmod(val, 1) - 0.5 > std::pow(0.1, precision)) return std::floor(val); + if (std::fmod(val, 1) - 0.5 > - std::pow(0.1, precision + 1)) return std::ceil(val); + else if (std::fmod(val, 1) - 0.5 > std::pow(0.1, precision)) return std::floor(val); // work around some compiler issue // cygwin has it not defined in std using namespace std; @@ -535,7 +535,7 @@ namespace Sass { Block_Obj b = r->block(); - Selector_List* sl = Cast(r->selector()); + SelectorList* sl = r->selector(); bool hasSelectors = sl ? sl->length() > 0 : false; if (!hasSelectors) { @@ -625,11 +625,12 @@ namespace Sass { return false; } - bool isPrintable(Media_Block* m, Sass_Output_Style style) + bool isPrintable(CssMediaRule* m, Sass_Output_Style style) { if (m == nullptr) return false; Block_Obj b = m->block(); if (b == nullptr) return false; + if (m->empty()) return false; for (size_t i = 0, L = b->length(); i < L; ++i) { Statement_Obj stm = b->at(i); if (Cast(stm)) return true; @@ -649,7 +650,7 @@ namespace Sass { return true; } } - else if (Media_Block* mb = Cast(stm)) { + else if (CssMediaRule* mb = Cast(stm)) { if (isPrintable(mb, style)) { return true; } @@ -702,7 +703,7 @@ namespace Sass { return true; } } - else if (Media_Block* m = Cast(stm)) { + else if (CssMediaRule * m = Cast(stm)) { if (isPrintable(m, style)) { return true; } diff --git a/src/util.hpp b/src/util.hpp index 3ed563565..f7a3ed34b 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -8,6 +8,7 @@ #include "sass/base.h" #include "ast_fwd_decl.hpp" +#include #include #include #include @@ -93,7 +94,7 @@ namespace Sass { bool isPrintable(Ruleset* r, Sass_Output_Style style = NESTED); bool isPrintable(Supports_Block* r, Sass_Output_Style style = NESTED); - bool isPrintable(Media_Block* r, Sass_Output_Style style = NESTED); + bool isPrintable(CssMediaRule* r, Sass_Output_Style style = NESTED); bool isPrintable(Comment* b, Sass_Output_Style style = NESTED); bool isPrintable(Block_Obj b, Sass_Output_Style style = NESTED); bool isPrintable(String_Constant* s, Sass_Output_Style style = NESTED); diff --git a/src/util_string.cpp b/src/util_string.cpp index 8af7918e7..a92a2666c 100644 --- a/src/util_string.cpp +++ b/src/util_string.cpp @@ -1,75 +1,121 @@ #include "util_string.hpp" +#include #include namespace Sass { -namespace Util { + namespace Util { -std::string rtrim(const std::string &str) { - std::string trimmed = str; - size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r"); - if (pos_ws != std::string::npos) { - trimmed.erase(pos_ws + 1); - } else { - trimmed.clear(); - } - return trimmed; -} + // ########################################################################## + // Special case insensitive string matcher. We can optimize + // the more general compare case quite a bit by requiring + // consumers to obey some rules (lowercase and no space). + // - `literal` must only contain lower case ascii characters + // there is one edge case where this could give false positives + // test could contain a (non-ascii) char exactly 32 below literal + // ########################################################################## + bool equalsLiteral(const char* lit, const std::string& test) { + // Work directly on characters + const char* src = test.c_str(); + // There is a small chance that the search string + // Is longer than the rest of the string to look at + while (*lit && (*src == *lit || *src + 32 == *lit)) { + ++src, ++lit; + } + // True if literal is at end + // If not test was too long + return *lit == 0; + } + // EO equalsLiteral -std::string normalize_newlines(const std::string& str) { - std::string result; - result.reserve(str.size()); - std::size_t pos = 0; - while (true) { - const std::size_t newline = str.find_first_of("\n\f\r", pos); - if (newline == std::string::npos) break; - result.append(str, pos, newline - pos); - result += '\n'; - if (str[newline] == '\r' && str[newline + 1] == '\n') { - pos = newline + 2; - } else { - pos = newline + 1; + // ########################################################################### + // Returns [name] without a vendor prefix. + // If [name] has no vendor prefix, it's returned as-is. + // ########################################################################### + std::string unvendor(const std::string& name) + { + if (name.size() < 2) return name; + if (name[0] != '-') return name; + if (name[1] == '-') return name; + for (size_t i = 2; i < name.size(); i++) { + if (name[i] == '-') return name.substr(i + 1); + } + return name; } - } - result.append(str, pos, std::string::npos); - return result; -} + // EO unvendor -std::string normalize_underscores(const std::string& str) { - std::string normalized = str; - std::replace(normalized.begin(), normalized.end(), '_', '-'); - return normalized; -} + std::string rtrim(const std::string& str) { -std::string normalize_decimals(const std::string& str) { - std::string normalized; - if (!str.empty() && str[0] == '.') { - normalized.reserve(str.size() + 1); - normalized += '0'; - normalized += str; - } else { - normalized = str; - } - return normalized; -} + std::string trimmed = str; + size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r"); + if (pos_ws != std::string::npos) { + trimmed.erase(pos_ws + 1); + } + else { + trimmed.clear(); + } + return trimmed; + } -char opening_bracket_for(char closing_bracket) { - switch (closing_bracket) { - case ')': return '('; - case ']': return '['; - case '}': return '{'; - default: return '\0'; - } -} + std::string normalize_newlines(const std::string& str) { + std::string result; + result.reserve(str.size()); + std::size_t pos = 0; + while (true) { + const std::size_t newline = str.find_first_of("\n\f\r", pos); + if (newline == std::string::npos) break; + result.append(str, pos, newline - pos); + result += '\n'; + if (str[newline] == '\r' && str[newline + 1] == '\n') { + pos = newline + 2; + } + else { + pos = newline + 1; + } + } + result.append(str, pos, std::string::npos); + return result; + } + + std::string normalize_underscores(const std::string& str) { + std::string normalized = str; + std::replace(normalized.begin(), normalized.end(), '_', '-'); + return normalized; + } + + std::string normalize_decimals(const std::string& str) { + std::string normalized; + if (!str.empty() && str[0] == '.') { + normalized.reserve(str.size() + 1); + normalized += '0'; + normalized += str; + } + else { + normalized = str; + } + return normalized; + } + + char opening_bracket_for(char closing_bracket) { + switch (closing_bracket) { + case ')': return '('; + case ']': return '['; + case '}': return '{'; + default: return '\0'; + } + } + + char closing_bracket_for(char opening_bracket) { + switch (opening_bracket) { + case '(': return ')'; + case '[': return ']'; + case '{': return '}'; + default: return '\0'; + } + } -char closing_bracket_for(char opening_bracket) { - switch (opening_bracket) { - case '(': return ')'; - case '[': return ']'; - case '{': return '}'; - default: return '\0'; } -} + // namespace Util -} // namespace Sass -} // namespace Util +} +// namespace Sass diff --git a/src/util_string.hpp b/src/util_string.hpp index 9f3795c4b..72e3b9b1b 100644 --- a/src/util_string.hpp +++ b/src/util_string.hpp @@ -4,16 +4,34 @@ #include namespace Sass { -namespace Util { + namespace Util { -std::string rtrim(const std::string& str); + // ########################################################################## + // Special case insensitive string matcher. We can optimize + // the more general compare case quite a bit by requiring + // consumers to obey some rules (lowercase and no space). + // - `literal` must only contain lower case ascii characters + // there is one edge case where this could give false positives + // test could contain a (non-ascii) char exactly 32 below literal + // ########################################################################## + bool equalsLiteral(const char* lit, const std::string& test); -std::string normalize_newlines(const std::string& str); -std::string normalize_underscores(const std::string& str); -std::string normalize_decimals(const std::string& str); -char opening_bracket_for(char closing_bracket); -char closing_bracket_for(char opening_bracket); + // ########################################################################### + // Returns [name] without a vendor prefix. + // If [name] has no vendor prefix, it's returned as-is. + // ########################################################################### + std::string unvendor(const std::string& name); + + std::string rtrim(const std::string& str); + std::string normalize_newlines(const std::string& str); + std::string normalize_underscores(const std::string& str); + std::string normalize_decimals(const std::string& str); + char opening_bracket_for(char closing_bracket); + char closing_bracket_for(char opening_bracket); + + } + // namespace Util +} +// namespace Sass -} // namespace Sass -} // namespace Util #endif // SASS_UTIL_STRING_H diff --git a/test/test_shared_ptr.cpp b/test/test_shared_ptr.cpp index b7b1d3d68..265608827 100644 --- a/test/test_shared_ptr.cpp +++ b/test/test_shared_ptr.cpp @@ -15,7 +15,7 @@ class TestObj : public Sass::SharedObj { public: TestObj(bool *destroyed) : destroyed_(destroyed) {} ~TestObj() { *destroyed_ = true; } - const std::string to_string() const { + std::string to_string() const { std::ostringstream result; result << "refcount=" << refcount << " destroyed=" << *destroyed_; return result.str(); @@ -137,7 +137,7 @@ bool TestDetachNull() { class EmptyTestObj : public Sass::SharedObj { public: - const std::string to_string() const { return ""; } + std::string to_string() const { return ""; } }; bool TestComparisonWithSharedPtr() { diff --git a/test/test_util_string.cpp b/test/test_util_string.cpp index 5c73430f0..3f110af20 100644 --- a/test/test_util_string.cpp +++ b/test/test_util_string.cpp @@ -28,6 +28,17 @@ std::string escape_string(const std::string& str) { return out; } +#define ASSERT_TRUE(cond) \ + if (!cond) { \ + std::cerr << \ + "Expected condition to be true at " << __FILE__ << ":" << __LINE__ << \ + std::endl; \ + return false; \ + } \ + +#define ASSERT_FALSE(cond) \ + ASSERT_TRUE(!(cond)) \ + #define ASSERT_STR_EQ(a, b) \ if (a != b) { \ std::cerr << \ @@ -94,6 +105,43 @@ bool TestNormalizeDecimalsNoLeadingZero() { return true; } +bool testEqualsLiteral() { + ASSERT_TRUE(Sass::Util::equalsLiteral("moz", "moz")); + ASSERT_TRUE(Sass::Util::equalsLiteral(":moz", ":moz")); + ASSERT_FALSE(Sass::Util::equalsLiteral("moz", ":moz")); + ASSERT_FALSE(Sass::Util::equalsLiteral(":moz", "moz")); + ASSERT_TRUE(Sass::Util::equalsLiteral("moz-foo", "MOZ-foo")); + ASSERT_FALSE(Sass::Util::equalsLiteral("moz-foo", "moz_foo")); + ASSERT_TRUE(Sass::Util::equalsLiteral("moz-foo", "MOZ-FOOS")); + ASSERT_FALSE(Sass::Util::equalsLiteral("moz-foos", "moz-foo")); + ASSERT_FALSE(Sass::Util::equalsLiteral("-moz-foo", "moz-foo")); + return true; + +} + +bool TestUnvendor() { + // Generated by using dart sass + ASSERT_STR_EQ("moz", Sass::Util::unvendor("moz")); + ASSERT_STR_EQ(":moz", Sass::Util::unvendor(":moz")); + ASSERT_STR_EQ("-moz", Sass::Util::unvendor("-moz")); + ASSERT_STR_EQ("--moz", Sass::Util::unvendor("--moz")); + ASSERT_STR_EQ("moz-bar", Sass::Util::unvendor("moz-bar")); + ASSERT_STR_EQ("bar", Sass::Util::unvendor("-moz-bar")); + ASSERT_STR_EQ("bar-", Sass::Util::unvendor("-moz-bar-")); + ASSERT_STR_EQ("--moz-bar", Sass::Util::unvendor("--moz-bar")); + ASSERT_STR_EQ("-bar", Sass::Util::unvendor("-moz--bar")); + ASSERT_STR_EQ("any", Sass::Util::unvendor("-s-any")); + ASSERT_STR_EQ("any-more", Sass::Util::unvendor("-s-any-more")); + ASSERT_STR_EQ("any--more", Sass::Util::unvendor("-s-any--more")); + ASSERT_STR_EQ("--s-any--more", Sass::Util::unvendor("--s-any--more")); + ASSERT_STR_EQ("s-any--more", Sass::Util::unvendor("s-any--more")); + ASSERT_STR_EQ("_s_any_more", Sass::Util::unvendor("_s_any_more")); + ASSERT_STR_EQ("more", Sass::Util::unvendor("-s_any-more")); + ASSERT_STR_EQ("any_more", Sass::Util::unvendor("-s-any_more")); + ASSERT_STR_EQ("_s_any_more", Sass::Util::unvendor("_s_any_more")); + return true; +} + } // namespace #define TEST(fn) \ @@ -116,6 +164,8 @@ int main(int argc, char **argv) { TEST(TestNormalizeUnderscores); TEST(TestNormalizeDecimalsLeadingZero); TEST(TestNormalizeDecimalsNoLeadingZero); + TEST(testEqualsLiteral); + TEST(TestUnvendor); std::cerr << argv[0] << ": Passed: " << passed.size() << ", failed: " << failed.size() << "." << std::endl; diff --git a/win/libsass.targets b/win/libsass.targets index 0cf5eff0c..ba83de4c1 100644 --- a/win/libsass.targets +++ b/win/libsass.targets @@ -32,7 +32,8 @@ - + + @@ -48,7 +49,6 @@ - @@ -60,10 +60,9 @@ - - + @@ -82,7 +81,9 @@ + + @@ -99,8 +100,10 @@ + - + + @@ -114,9 +117,9 @@ - + @@ -126,11 +129,10 @@ - - + diff --git a/win/libsass.vcxproj.filters b/win/libsass.vcxproj.filters index e68e84ba4..2f8666869 100644 --- a/win/libsass.vcxproj.filters +++ b/win/libsass.vcxproj.filters @@ -43,6 +43,15 @@ Include Headers + + Sources + + + Sources + + + Sources + @@ -108,9 +117,6 @@ Headers - - Headers - Headers @@ -156,9 +162,6 @@ Headers - - Headers - Headers @@ -189,18 +192,12 @@ Headers - - Headers - Headers Headers - - Headers - Headers @@ -311,9 +308,6 @@ Sources - - Sources - Sources @@ -353,9 +347,6 @@ Sources - - Sources - Sources @@ -389,9 +380,6 @@ Sources - - Sources - Sources @@ -401,9 +389,6 @@ Sources - - Sources - Sources @@ -428,5 +413,26 @@ Sources + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources +