From bf981279c951923e9663fd71b149b7200b6400ce Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 17 Dec 2014 09:53:41 +1100 Subject: [PATCH 1/4] Inital super basic implementation of @media bubbling --- Makefile | 7 +- Makefile.am | 1 + ast.hpp | 39 ++++++++++- ast_fwd_decl.hpp | 1 + context.cpp | 3 + cssize.cpp | 165 ++++++++++++++++++++++++++++++++++++++++++++ cssize.hpp | 77 +++++++++++++++++++++ expand.cpp | 4 +- inspect.cpp | 7 ++ inspect.hpp | 1 + operation.hpp | 2 + win/libsass.filters | 8 ++- win/libsass.vcxproj | 6 +- 13 files changed, 310 insertions(+), 11 deletions(-) create mode 100644 cssize.cpp create mode 100644 cssize.hpp diff --git a/Makefile b/Makefile index ab6df63cb9..1a0e7688a8 100644 --- a/Makefile +++ b/Makefile @@ -97,6 +97,7 @@ SOURCES = \ context.cpp \ contextualize.cpp \ copy_c_str.cpp \ + cssize.cpp \ error_handling.cpp \ eval.cpp \ expand.cpp \ @@ -127,14 +128,14 @@ SOURCES = \ CSOURCES = cencode.c -RESOURCES = +RESOURCES = LIBRARIES = lib/libsass.so ifeq (MinGW,$(UNAME)) ifeq (shared,$(BUILD)) - CFLAGS += -D ADD_EXPORTS - CXXFLAGS += -D ADD_EXPORTS + CFLAGS += -D ADD_EXPORTS + CXXFLAGS += -D ADD_EXPORTS LIBRARIES += lib/libsass.dll RESOURCES += res/resource.rc endif diff --git a/Makefile.am b/Makefile.am index 8ab2f38935..d61563173f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -53,6 +53,7 @@ libsass_la_SOURCES = \ eval.cpp eval.hpp \ expand.cpp expand.hpp \ extend.cpp extend.hpp \ + cssize.cpp cssize.hpp \ file.cpp file.hpp \ functions.cpp functions.hpp \ inspect.cpp inspect.hpp \ diff --git a/ast.hpp b/ast.hpp index 697b1313d0..deefa9f785 100644 --- a/ast.hpp +++ b/ast.hpp @@ -258,7 +258,23 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////// class Statement : public AST_Node { public: - Statement(string path, Position position) : AST_Node(path, position) { } + enum Statement_Type { + NONE, + RULESET, + MEDIA, + DIRECTIVE, + KEYFRAME, + FEATURE, + BUBBLE + }; + private: + ADD_PROPERTY(Block*, block); + ADD_PROPERTY(Statement_Type, statement_type); + public: + Statement(string path, Position position, Statement_Type st = NONE) + : AST_Node(path, position), + statement_type_(st) + { } virtual ~Statement() = 0; // needed for rearranging nested rulesets during CSS emission virtual bool is_hoistable() { return false; } @@ -314,7 +330,7 @@ namespace Sass { public: Ruleset(string path, Position position, Selector* s, Block* b) : Has_Block(path, position, b), selector_(s) - { } + { statement_type(RULESET); } bool is_invisible(); // nested rulesets need to be hoisted out of their enclosing blocks bool is_hoistable() { return true; } @@ -334,6 +350,20 @@ namespace Sass { ATTACH_OPERATIONS(); }; + ///////////////// + // Bubble. + ///////////////// + class Bubble : public Statement { + ADD_PROPERTY(Statement*, node); + ADD_PROPERTY(Statement*, group_end); + ADD_PROPERTY(size_t, tabs); + public: + Bubble(string path, Position position, Statement* n, Statement* g = 0, size_t t = 0) + : Statement(path, position), node_(n), group_end_(g), tabs_(t) + { statement_type(BUBBLE); } + ATTACH_OPERATIONS(); + }; + ///////////////// // Media queries. ///////////////// @@ -344,7 +374,10 @@ namespace Sass { public: Media_Block(string path, Position position, List* mqs, Block* b) : Has_Block(path, position, b), media_queries_(mqs), selector_(0) - { } + { statement_type(MEDIA); } + Media_Block(string path, Position position, List* mqs, Block* b, Selector* s) + : Has_Block(path, position, b), media_queries_(mqs), selector_(s) + { statement_type(MEDIA); } bool is_hoistable() { return true; } bool is_invisible() { bool is_invisible = true; diff --git a/ast_fwd_decl.hpp b/ast_fwd_decl.hpp index e732298c81..410cd3aacf 100644 --- a/ast_fwd_decl.hpp +++ b/ast_fwd_decl.hpp @@ -9,6 +9,7 @@ namespace Sass { class Block; class Ruleset; class Propset; + class Bubble; class Media_Block; class Feature_Block; class At_Rule; diff --git a/context.cpp b/context.cpp index c6aa49074c..7db82dc933 100644 --- a/context.cpp +++ b/context.cpp @@ -18,6 +18,7 @@ #include "expand.hpp" #include "eval.hpp" #include "contextualize.hpp" +#include "cssize.hpp" #include "extend.hpp" #include "remove_placeholders.hpp" #include "copy_c_str.hpp" @@ -282,10 +283,12 @@ namespace Sass { Eval eval(*this, &tge, &backtrace); Contextualize contextualize(*this, &eval, &tge, &backtrace); Expand expand(*this, &eval, &contextualize, &tge, &backtrace); + Cssize cssize(*this, &tge); // Inspect inspect(this); // Output_Nested output_nested(*this); root = root->perform(&expand)->block(); + root = root->perform(&cssize)->block(); if (!subset_map.empty()) { Extend extend(*this, subset_map); root->perform(&extend); diff --git a/cssize.cpp b/cssize.cpp new file mode 100644 index 0000000000..339e911c04 --- /dev/null +++ b/cssize.cpp @@ -0,0 +1,165 @@ +#include "cssize.hpp" +#include "to_string.hpp" + +#include +#include + +#ifndef SASS_CONTEXT +#include "context.hpp" +#endif + +namespace Sass { + + Cssize::Cssize(Context& ctx, Env* env) + : ctx(ctx), + env(env), + block_stack(vector()), + p_stack(vector()) + { } + + Statement* Cssize::parent() + { + return p_stack.size() ? p_stack.back() : block_stack.front(); + } + + Statement* Cssize::operator()(Block* b) + { + Env new_env; + new_env.link(*env); + env = &new_env; + Block* bb = new (ctx.mem) Block(b->path(), b->position(), b->length(), b->is_root()); + block_stack.push_back(bb); + append_block(b); + block_stack.pop_back(); + env = env->parent(); + return bb; + } + + Statement* Cssize::operator()(Media_Block* m) + { + if (parent()->statement_type() == Statement::MEDIA) + { return new (ctx.mem) Bubble(m->path(), m->position(), m); } + + p_stack.push_back(m); + + Media_Block* mm = new (ctx.mem) Media_Block(m->path(), + m->position(), + m->media_queries(), + m->block()->perform(this)->block()); + p_stack.pop_back(); + + return debubble(mm->block(), mm)->block(); + } + + Statement* Cssize::flatten(Statement* s) + { + Block* bb = s->block(); + Block* result = new (ctx.mem) Block(bb->path(), bb->position(), 0, bb->is_root()); + for (size_t i = 0, L = bb->length(); i < L; ++i) { + Statement* ss = (*bb)[i]; + if (ss->block()) { + ss = flatten(ss); + for (size_t j = 0, K = ss->block()->length(); j < K; ++j) { + *result << (*ss->block())[j]; + } + } + else { + *result << ss; + } + } + return result; + } + + vector> Cssize::slice_by_bubble(Statement* b) + { + vector> results; + for (size_t i = 0, L = b->block()->length(); i < L; ++i) { + Statement* value = (*b->block())[i]; + bool key = value->statement_type() == Statement::BUBBLE; + + if (!results.empty() && results.back().first == key) + { + Block* wrapper_block = results.back().second; + *wrapper_block << value; + } + else + { + Block* wrapper_block = new (ctx.mem) Block(value->path(), value->position()); + *wrapper_block << value; + results.push_back(make_pair(key, wrapper_block)); + } + } + return results; + } + + Statement* Cssize::debubble(Block* children, Statement* parent) + { + Has_Block* previous_parent = 0; + vector> baz = slice_by_bubble(children); + Block* result = new (ctx.mem) Block(parent->path(), parent->position()); + + for (size_t i = 0, L = baz.size(); i < L; ++i) { + bool is_bubble = baz[i].first; + Block* slice = baz[i].second; + + if (!is_bubble) { + if (!previous_parent) { + previous_parent = static_cast(parent); + + Has_Block* new_parent = static_cast(parent); + new_parent->block(slice); + + *result << new_parent; + } + continue; + } + + Block* wrapper_block = new (ctx.mem) Block(parent->block()->path(), + parent->block()->position(), + parent->block()->length(), + parent->block()->is_root()); + + for (size_t j = 0, K = slice->length(); j < K; ++j) + { + Statement* ss = 0; + if ((*slice)[j]->statement_type() == Statement::BUBBLE) { + ss = static_cast((*slice)[j])->node(); + } + + if (!ss) continue; + + Statement* ssss = ss->perform(this); + Statement* wrapper = flatten(ssss); + *wrapper_block << wrapper; + } + + if (wrapper_block) { + *result << flatten(wrapper_block); + } + } + + return flatten(result); + } + + Statement* Cssize::fallback_impl(AST_Node* n) + { + return static_cast(n); + } + + void Cssize::append_block(Block* b) + { + Block* current_block = block_stack.back(); + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement* ith = (*b)[i]->perform(this); + if (ith && ith->block()) { + for (size_t j = 0, K = ith->block()->length(); j < K; ++j) { + *current_block << (*ith->block())[j]; + } + } + else if (ith) { + *current_block << ith; + } + } + } +} diff --git a/cssize.hpp b/cssize.hpp new file mode 100644 index 0000000000..b3a0132776 --- /dev/null +++ b/cssize.hpp @@ -0,0 +1,77 @@ +#ifndef SASS_CSSIZE +#define SASS_CSSIZE + +#include +#include + +#ifndef SASS_AST +#include "ast.hpp" +#endif + +#ifndef SASS_OPERATION +#include "operation.hpp" +#endif + +#ifndef SASS_ENVIRONMENT +#include "environment.hpp" +#endif + +namespace Sass { + using namespace std; + + struct Context; + typedef Environment Env; + + class Cssize : public Operation_CRTP { + + Context& ctx; + Env* env; + vector block_stack; + vector p_stack; + + Statement* fallback_impl(AST_Node* n); + + public: + Cssize(Context&, Env*); + virtual ~Cssize() { } + + using Operation::operator(); + + Statement* operator()(Block*); + // Statement* operator()(Ruleset*); + // Statement* operator()(Propset*); + // Statement* operator()(Bubble*); + Statement* operator()(Media_Block*); + // Statement* operator()(Feature_Block*); + // Statement* operator()(At_Rule*); + // Statement* operator()(Declaration*); + // Statement* operator()(Assignment*); + // Statement* operator()(Import*); + // Statement* operator()(Import_Stub*); + // Statement* operator()(Warning*); + // Statement* operator()(Error*); + // Statement* operator()(Comment*); + // Statement* operator()(If*); + // Statement* operator()(For*); + // Statement* operator()(Each*); + // Statement* operator()(While*); + // Statement* operator()(Return*); + // Statement* operator()(Extension*); + // Statement* operator()(Definition*); + // Statement* operator()(Mixin_Call*); + // Statement* operator()(Content*); + + Statement* parent(); + vector> slice_by_bubble(Statement*); + Statement* debubble(Block*, Statement*); + Statement* flatten(Statement*); + + template + Statement* fallback(U x) { return fallback_impl(x); } + + void append_block(Block*); + }; + +} + +#endif diff --git a/expand.cpp b/expand.cpp index 832ca08a51..620e52ac9b 100644 --- a/expand.cpp +++ b/expand.cpp @@ -105,8 +105,8 @@ namespace Sass { Media_Block* mm = new (ctx.mem) Media_Block(m->path(), m->position(), static_cast(media_queries), - m->block()->perform(this)->block()); - mm->selector(selector_stack.back()); + m->block()->perform(this)->block(), + selector_stack.back()); return mm; } diff --git a/inspect.cpp b/inspect.cpp index eaedbc006d..0fd36e2c2b 100644 --- a/inspect.cpp +++ b/inspect.cpp @@ -53,6 +53,13 @@ namespace Sass { propset->block()->perform(this); } + void Inspect::operator()(Bubble* bubble) + { + append_to_buffer("Bubble ( "); + bubble->node()->perform(this); + append_to_buffer(" )"); + } + void Inspect::operator()(Media_Block* media_block) { if (ctx) ctx->source_map.add_mapping(media_block); diff --git a/inspect.hpp b/inspect.hpp index b25f358671..dc5d04ad0a 100644 --- a/inspect.hpp +++ b/inspect.hpp @@ -40,6 +40,7 @@ namespace Sass { virtual void operator()(Block*); virtual void operator()(Ruleset*); virtual void operator()(Propset*); + virtual void operator()(Bubble*); virtual void operator()(Feature_Block*); virtual void operator()(Media_Block*); virtual void operator()(At_Rule*); diff --git a/operation.hpp b/operation.hpp index b95e237cd4..7e6d370d5c 100644 --- a/operation.hpp +++ b/operation.hpp @@ -17,6 +17,7 @@ namespace Sass { virtual T operator()(Block* x) = 0; virtual T operator()(Ruleset* x) = 0; virtual T operator()(Propset* x) = 0; + virtual T operator()(Bubble* x) = 0; virtual T operator()(Feature_Block* x) = 0; virtual T operator()(Media_Block* x) = 0; virtual T operator()(At_Rule* x) = 0; @@ -87,6 +88,7 @@ namespace Sass { virtual T operator()(Block* x) { return static_cast(this)->fallback(x); } virtual T operator()(Ruleset* x) { return static_cast(this)->fallback(x); } virtual T operator()(Propset* x) { return static_cast(this)->fallback(x); } + virtual T operator()(Bubble* x) { return static_cast(this)->fallback(x); } virtual T operator()(Feature_Block* x) { return static_cast(this)->fallback(x); } virtual T operator()(Media_Block* x) { return static_cast(this)->fallback(x); } virtual T operator()(At_Rule* x) { return static_cast(this)->fallback(x); } diff --git a/win/libsass.filters b/win/libsass.filters index 1ad9528ed0..62f0d7e97b 100644 --- a/win/libsass.filters +++ b/win/libsass.filters @@ -51,6 +51,9 @@ Source Files + + Source Files + Source Files @@ -188,6 +191,9 @@ Header Files + + Header Files + Header Files @@ -288,4 +294,4 @@ Header Files - \ No newline at end of file + diff --git a/win/libsass.vcxproj b/win/libsass.vcxproj index 6520e7ce6e..714deb8222 100644 --- a/win/libsass.vcxproj +++ b/win/libsass.vcxproj @@ -168,6 +168,7 @@ + @@ -184,7 +185,7 @@ - + @@ -212,6 +213,7 @@ + @@ -252,4 +254,4 @@ - \ No newline at end of file + From ca883502b8ff996a88a0bcb878229944f2d21349 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Tue, 30 Dec 2014 21:58:24 +1100 Subject: [PATCH 2/4] Implement media query merging when bubbling --- ast.hpp | 3 ++ cssize.cpp | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++-- cssize.hpp | 8 +++- 3 files changed, 115 insertions(+), 5 deletions(-) diff --git a/ast.hpp b/ast.hpp index deefa9f785..11174e9251 100644 --- a/ast.hpp +++ b/ast.hpp @@ -279,6 +279,7 @@ namespace Sass { // needed for rearranging nested rulesets during CSS emission virtual bool is_hoistable() { return false; } virtual bool is_invisible() { return false; } + virtual bool bubbles() { return false; } virtual Block* block() { return 0; } }; inline Statement::~Statement() { } @@ -361,6 +362,7 @@ namespace Sass { Bubble(string path, Position position, Statement* n, Statement* g = 0, size_t t = 0) : Statement(path, position), node_(n), group_end_(g), tabs_(t) { statement_type(BUBBLE); } + bool bubbles() { return true; } ATTACH_OPERATIONS(); }; @@ -378,6 +380,7 @@ namespace Sass { Media_Block(string path, Position position, List* mqs, Block* b, Selector* s) : Has_Block(path, position, b), media_queries_(mqs), selector_(s) { statement_type(MEDIA); } + bool bubbles() { return true; } bool is_hoistable() { return true; } bool is_invisible() { bool is_invisible = true; diff --git a/cssize.cpp b/cssize.cpp index 339e911c04..fc240dbd47 100644 --- a/cssize.cpp +++ b/cssize.cpp @@ -35,6 +35,18 @@ namespace Sass { return bb; } + Statement* Cssize::operator()(Ruleset* r) + { + p_stack.push_back(r); + Ruleset* rr = new (ctx.mem) Ruleset(r->path(), + r->position(), + r->selector(), + r->block()->perform(this)->block()); + p_stack.pop_back(); + + return rr; + } + Statement* Cssize::operator()(Media_Block* m) { if (parent()->statement_type() == Statement::MEDIA) @@ -51,6 +63,11 @@ namespace Sass { return debubble(mm->block(), mm)->block(); } + bool Cssize::bubblable(Statement* s) + { + return s->statement_type() == Statement::RULESET || s->bubbles(); + } + Statement* Cssize::flatten(Statement* s) { Block* bb = s->block(); @@ -103,7 +120,10 @@ namespace Sass { Block* slice = baz[i].second; if (!is_bubble) { - if (!previous_parent) { + if (!parent) { + *result << slice; + } + else if (!previous_parent) { previous_parent = static_cast(parent); Has_Block* new_parent = static_cast(parent); @@ -122,8 +142,20 @@ namespace Sass { for (size_t j = 0, K = slice->length(); j < K; ++j) { Statement* ss = 0; - if ((*slice)[j]->statement_type() == Statement::BUBBLE) { - ss = static_cast((*slice)[j])->node(); + Bubble* b = static_cast((*slice)[j]); + + if (!parent || + b->node()->statement_type() != Statement::MEDIA || + static_cast(b->node())->media_queries() == static_cast(parent)->media_queries()) + { + ss = b->node(); + } + else + { + List* mq = merge_media_queries(static_cast(b->node()), static_cast(parent)); + if (!mq->length()) continue; + static_cast(b->node())->media_queries(mq); + ss = b->node(); } if (!ss) continue; @@ -162,4 +194,75 @@ namespace Sass { } } } + + List* Cssize::merge_media_queries(Media_Block* m1, Media_Block* m2) + { + List* qq = new (ctx.mem) List(m1->media_queries()->path(), + m1->media_queries()->position(), + m1->media_queries()->length()); + + 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++) { + Media_Query* mq1 = static_cast((*m1->media_queries())[i]); + Media_Query* mq2 = static_cast((*m2->media_queries())[j]); + Media_Query* mq = merge_media_query(mq1, mq2); + + if (mq) *qq << mq; + } + } + + return qq; + } + + + Media_Query* Cssize::merge_media_query(Media_Query* mq1, Media_Query* mq2) + { + To_String to_string; + + string type; + string mod; + + string m1 = string(mq1->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); + string t1 = mq1->media_type() ? mq1->media_type()->perform(&to_string) : ""; + string m2 = string(mq2->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); + string t2 = mq2->media_type() ? mq2->media_type()->perform(&to_string) : ""; + + + 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 = new (ctx.mem) Media_Query( + mq1->path(), mq1->position(), 0, + mq1->length() + mq2->length(), mod == "not", mod == "only" + ); + + if (!type.empty()) { + mm->media_type(new (ctx.mem) String_Constant(mq1->path(), mq1->position(), type)); + } + + *mm += mq2; + *mm += mq1; + return mm; + } } diff --git a/cssize.hpp b/cssize.hpp index b3a0132776..5ca97a0361 100644 --- a/cssize.hpp +++ b/cssize.hpp @@ -38,7 +38,7 @@ namespace Sass { using Operation::operator(); Statement* operator()(Block*); - // Statement* operator()(Ruleset*); + Statement* operator()(Ruleset*); // Statement* operator()(Propset*); // Statement* operator()(Bubble*); Statement* operator()(Media_Block*); @@ -63,8 +63,12 @@ namespace Sass { Statement* parent(); vector> slice_by_bubble(Statement*); - Statement* debubble(Block*, Statement*); + Statement* debubble(Block* children, Statement* parent = 0); Statement* flatten(Statement*); + bool bubblable(Statement*); + + List* merge_media_queries(Media_Block*, Media_Block*); + Media_Query* merge_media_query(Media_Query*, Media_Query*); template Statement* fallback(U x) { return fallback_impl(x); } From 1204e9daa2726c0250ab40b945cbcf2b9bd3a3d9 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Wed, 31 Dec 2014 09:32:46 +1100 Subject: [PATCH 3/4] Implement bubbling of ruleset nodes --- ast.hpp | 5 ++++ cssize.cpp | 85 +++++++++++++++++++++++++++++++++++++++++++++++------- cssize.hpp | 1 + eval.cpp | 4 ++- 4 files changed, 84 insertions(+), 11 deletions(-) diff --git a/ast.hpp b/ast.hpp index 11174e9251..a6225ca651 100644 --- a/ast.hpp +++ b/ast.hpp @@ -187,6 +187,11 @@ namespace Sass { for (size_t i = 0, L = v->length(); i < L; ++i) *this << (*v)[i]; return *this; } + Vectorized& unshift(T element) + { + elements_.insert(elements_.begin(), element); + return *this; + } vector& elements() { return elements_; } const vector& elements() const { return elements_; } vector& elements(vector& e) { elements_ = e; return elements_; } diff --git a/cssize.cpp b/cssize.cpp index fc240dbd47..777c3fdc12 100644 --- a/cssize.cpp +++ b/cssize.cpp @@ -44,11 +44,38 @@ namespace Sass { r->block()->perform(this)->block()); p_stack.pop_back(); - return rr; + Block* props = new Block(rr->block()->path(), rr->block()->position()); + for (size_t i = 0, L = rr->block()->length(); i < L; i++) + { + Statement* s = (*rr->block())[i]; + if (!bubblable(s)) *props << s; + } + + Block* rules = new Block(rr->block()->path(), rr->block()->position()); + for (size_t i = 0, L = rr->block()->length(); i < L; i++) + { + Statement* s = (*rr->block())[i]; + if (bubblable(s)) *rules << s; + } + + if (props->length()) + { + Block* bb = new Block(rr->block()->path(), rr->block()->position()); + *bb += props; + rr->block(bb); + rules->unshift(rr); + } + + rules = debubble(rules)->block(); + + return rules; } Statement* Cssize::operator()(Media_Block* m) { + if (parent()->statement_type() == Statement::RULESET) + { return bubble(m); } + if (parent()->statement_type() == Statement::MEDIA) { return new (ctx.mem) Bubble(m->path(), m->position(), m); } @@ -63,6 +90,33 @@ namespace Sass { return debubble(mm->block(), mm)->block(); } + Statement* Cssize::bubble(Media_Block* m) + { + Ruleset* parent = static_cast(this->parent()); + + Block* bb = new (ctx.mem) Block(parent->block()->path(), parent->block()->position()); + Ruleset* new_rule = new (ctx.mem) Ruleset(parent->path(), + parent->position(), + parent->selector(), + bb); + + for (size_t i = 0, L = m->block()->length(); i < L; ++i) { + *new_rule->block() << (*m->block())[i]; + } + + Block* wrapper_block = new (ctx.mem) Block(m->block()->path(), m->block()->position()); + *wrapper_block << new_rule; + Media_Block* mm = new (ctx.mem) Media_Block(m->path(), + m->position(), + m->media_queries(), + wrapper_block, + m->selector()); + + Bubble* bubble = new (ctx.mem) Bubble(mm->path(), mm->position(), mm); + + return bubble; + } + bool Cssize::bubblable(Statement* s) { return s->statement_type() == Statement::RULESET || s->bubbles(); @@ -113,7 +167,7 @@ namespace Sass { { Has_Block* previous_parent = 0; vector> baz = slice_by_bubble(children); - Block* result = new (ctx.mem) Block(parent->path(), parent->position()); + Block* result = new (ctx.mem) Block(children->path(), children->position()); for (size_t i = 0, L = baz.size(); i < L; ++i) { bool is_bubble = baz[i].first; @@ -123,7 +177,10 @@ namespace Sass { if (!parent) { *result << slice; } - else if (!previous_parent) { + else if (previous_parent) { + *previous_parent->block() += slice; + } + else { previous_parent = static_cast(parent); Has_Block* new_parent = static_cast(parent); @@ -134,10 +191,10 @@ namespace Sass { continue; } - Block* wrapper_block = new (ctx.mem) Block(parent->block()->path(), - parent->block()->position(), - parent->block()->length(), - parent->block()->is_root()); + Block* wrapper_block = new (ctx.mem) Block(children->block()->path(), + children->block()->position(), + children->block()->length(), + children->block()->is_root()); for (size_t j = 0, K = slice->length(); j < K; ++j) { @@ -145,6 +202,7 @@ namespace Sass { Bubble* b = static_cast((*slice)[j]); if (!parent || + parent->statement_type() != Statement::MEDIA || b->node()->statement_type() != Statement::MEDIA || static_cast(b->node())->media_queries() == static_cast(parent)->media_queries()) { @@ -153,16 +211,23 @@ namespace Sass { else { List* mq = merge_media_queries(static_cast(b->node()), static_cast(parent)); - if (!mq->length()) continue; static_cast(b->node())->media_queries(mq); ss = b->node(); } if (!ss) continue; - Statement* ssss = ss->perform(this); - Statement* wrapper = flatten(ssss); + Block* bb = new (ctx.mem) Block(children->block()->path(), + children->block()->position(), + children->block()->length(), + children->block()->is_root()); + *bb << ss->perform(this); + Statement* wrapper = flatten(bb); *wrapper_block << wrapper; + + if (wrapper->block()->length()) { + previous_parent = 0; + } } if (wrapper_block) { diff --git a/cssize.hpp b/cssize.hpp index 5ca97a0361..4f72f48102 100644 --- a/cssize.hpp +++ b/cssize.hpp @@ -63,6 +63,7 @@ namespace Sass { Statement* parent(); vector> slice_by_bubble(Statement*); + Statement* bubble(Media_Block*); Statement* debubble(Block* children, Statement* parent = 0); Statement* flatten(Statement*); bool bubblable(Statement*); diff --git a/eval.cpp b/eval.cpp index 996a4f82f0..41be505e4d 100644 --- a/eval.cpp +++ b/eval.cpp @@ -9,6 +9,7 @@ #include "context.hpp" #include "backtrace.hpp" #include "prelexer.hpp" +#include "parser.hpp" #include #include @@ -767,6 +768,7 @@ namespace Sass { Expression* Eval::operator()(Media_Query* q) { + To_String to_string; String* t = q->media_type(); t = static_cast(t ? t->perform(this) : 0); Media_Query* qq = new (ctx.mem) Media_Query(q->path(), @@ -778,7 +780,7 @@ namespace Sass { for (size_t i = 0, L = q->length(); i < L; ++i) { *qq << static_cast((*q)[i]->perform(this)); } - return qq; + return Parser::from_c_str(qq->perform(&to_string).c_str(), ctx, qq->path(), qq->position()).parse_media_query();; } Expression* Eval::operator()(Media_Query_Expression* e) From c79ef9f89af4bece290943323c92ded5d536253d Mon Sep 17 00:00:00 2001 From: xzyfer Date: Thu, 1 Jan 2015 14:46:30 +1100 Subject: [PATCH 4/4] Preserve the nested style output for bubbled media queries --- ast.hpp | 12 +++++++----- cssize.cpp | 21 +++++++++++++++++++++ output_nested.cpp | 4 +++- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/ast.hpp b/ast.hpp index a6225ca651..d40fcfe413 100644 --- a/ast.hpp +++ b/ast.hpp @@ -173,6 +173,7 @@ namespace Sass { virtual ~Vectorized() = 0; size_t length() const { return elements_.size(); } bool empty() const { return elements_.empty(); } + T last() { return elements_.back(); } T& operator[](size_t i) { return elements_[i]; } const T& operator[](size_t i) const { return elements_[i]; } Vectorized& operator<<(T element) @@ -275,10 +276,12 @@ namespace Sass { private: ADD_PROPERTY(Block*, block); ADD_PROPERTY(Statement_Type, statement_type); + ADD_PROPERTY(size_t, tabs); + ADD_PROPERTY(bool, group_end); public: - Statement(string path, Position position, Statement_Type st = NONE) + Statement(string path, Position position, Statement_Type st = NONE, size_t t = 0) : AST_Node(path, position), - statement_type_(st) + statement_type_(st), tabs_(t), group_end_(false) { } virtual ~Statement() = 0; // needed for rearranging nested rulesets during CSS emission @@ -362,11 +365,10 @@ namespace Sass { class Bubble : public Statement { ADD_PROPERTY(Statement*, node); ADD_PROPERTY(Statement*, group_end); - ADD_PROPERTY(size_t, tabs); public: Bubble(string path, Position position, Statement* n, Statement* g = 0, size_t t = 0) - : Statement(path, position), node_(n), group_end_(g), tabs_(t) - { statement_type(BUBBLE); } + : Statement(path, position, Statement::BUBBLE, t), node_(n), group_end_(g) + { } bool bubbles() { return true; } ATTACH_OPERATIONS(); }; diff --git a/cssize.cpp b/cssize.cpp index 777c3fdc12..1b32487ba4 100644 --- a/cssize.cpp +++ b/cssize.cpp @@ -63,11 +63,24 @@ namespace Sass { Block* bb = new Block(rr->block()->path(), rr->block()->position()); *bb += props; rr->block(bb); + + for (size_t i = 0, L = rules->length(); i < L; i++) + { + (*rules)[i]->tabs((*rules)[i]->tabs() + 1); + } + rules->unshift(rr); } rules = debubble(rules)->block(); + if (!(!rules->length() || + !bubblable(rules->last()) || + parent()->statement_type() == Statement::RULESET)) + { + rules->last()->group_end(true); + } + return rules; } @@ -85,6 +98,8 @@ namespace Sass { m->position(), m->media_queries(), m->block()->perform(this)->block()); + mm->tabs(m->tabs()); + p_stack.pop_back(); return debubble(mm->block(), mm)->block(); @@ -99,6 +114,7 @@ namespace Sass { parent->position(), parent->selector(), bb); + new_rule->tabs(parent->tabs()); for (size_t i = 0, L = m->block()->length(); i < L; ++i) { *new_rule->block() << (*m->block())[i]; @@ -182,9 +198,11 @@ namespace Sass { } else { previous_parent = static_cast(parent); + previous_parent->tabs(parent->tabs()); Has_Block* new_parent = static_cast(parent); new_parent->block(slice); + new_parent->tabs(parent->tabs()); *result << new_parent; } @@ -215,6 +233,9 @@ namespace Sass { ss = b->node(); } + ss->tabs(ss->tabs() + b->tabs()); + ss->group_end(b->group_end()); + if (!ss) continue; Block* bb = new (ctx.mem) Block(children->block()->path(), diff --git a/output_nested.cpp b/output_nested.cpp index 4b458f75ad..d50aa6c877 100644 --- a/output_nested.cpp +++ b/output_nested.cpp @@ -229,6 +229,7 @@ namespace Sass { return; } + indentation += m->tabs(); indent(); ctx->source_map.add_mapping(m); append_to_buffer("@media "); @@ -286,7 +287,8 @@ namespace Sass { buffer.erase(buffer.length()-1); if (ctx) ctx->source_map.remove_line(); - append_to_buffer(" }" + ctx->linefeed); + append_to_buffer(" }"); + if (m->group_end()) append_to_buffer(ctx->linefeed); } void Output_Nested::operator()(At_Rule* a)