diff --git a/.gitignore b/.gitignore index 6b61812fdf..f6fb025b5c 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,11 @@ libsass/* *.a a.out libsass.js +tester +tester.exe +build/ +config.h.in* +lib/pkgconfig/ bin/* .deps/ diff --git a/Makefile b/Makefile index d8e70d112c..9c24ab8da3 100644 --- a/Makefile +++ b/Makefile @@ -114,8 +114,8 @@ SOURCES = \ inspect.cpp \ node.cpp \ json.cpp \ - output_compressed.cpp \ - output_nested.cpp \ + emitter.cpp \ + output.cpp \ parser.cpp \ position.cpp \ prelexer.cpp \ diff --git a/Makefile.am b/Makefile.am index 4c294924ce..9b8526e2ba 100644 --- a/Makefile.am +++ b/Makefile.am @@ -65,8 +65,8 @@ libsass_la_SOURCES = \ inspect.cpp inspect.hpp \ node.cpp node.hpp \ json.cpp json.hpp \ - output_compressed.cpp output_compressed.hpp \ - output_nested.cpp output_nested.hpp \ + emitter.cpp emitter.hpp \ + output.cpp output.hpp \ parser.cpp parser.hpp \ prelexer.cpp prelexer.hpp \ remove_placeholders.cpp remove_placeholders.hpp \ diff --git a/ast.cpp b/ast.cpp index 0879e8a895..51801f8ba0 100644 --- a/ast.cpp +++ b/ast.cpp @@ -89,7 +89,7 @@ namespace Sass { Compound_Selector* Simple_Selector::unify_with(Compound_Selector* rhs, Context& ctx) { - To_String to_string; + To_String to_string(&ctx); for (size_t i = 0, L = rhs->length(); i < L; ++i) { if (perform(&to_string) == (*rhs)[i]->perform(&to_string)) return rhs; } @@ -183,6 +183,7 @@ namespace Sass { return 0; } } + rhs->has_line_break(has_line_break()); return Simple_Selector::unify_with(rhs, ctx); } @@ -451,7 +452,7 @@ namespace Sass { { Complex_Selector* cpy = new (ctx.mem) Complex_Selector(*this); - if (head()) { + if (head()) { cpy->head(head()->clone(ctx)); } @@ -470,10 +471,11 @@ namespace Sass { + /* not used anymore - remove? Selector_Placeholder* Selector::find_placeholder() { return 0; - } + }*/ void Selector_List::adjust_after_pushing(Complex_Selector* c) { @@ -486,6 +488,7 @@ namespace Sass { #endif } + /* not used anymore - remove? Selector_Placeholder* Selector_List::find_placeholder() { if (has_placeholder()) { @@ -494,8 +497,9 @@ namespace Sass { } } return 0; - } + }*/ + /* not used anymore - remove? Selector_Placeholder* Complex_Selector::find_placeholder() { if (has_placeholder()) { @@ -503,8 +507,9 @@ namespace Sass { else if (tail() && tail()->has_placeholder()) return tail()->find_placeholder(); } return 0; - } + }*/ + /* not used anymore - remove? Selector_Placeholder* Compound_Selector::find_placeholder() { if (has_placeholder()) { @@ -514,12 +519,13 @@ namespace Sass { // return this; } return 0; - } + }*/ + /* not used anymore - remove? Selector_Placeholder* Selector_Placeholder::find_placeholder() { return this; - } + }*/ vector Compound_Selector::to_str_vec() { @@ -533,7 +539,7 @@ namespace Sass { Compound_Selector* Compound_Selector::minus(Compound_Selector* rhs, Context& ctx) { - To_String to_string; + To_String to_string(&ctx); Compound_Selector* result = new (ctx.mem) Compound_Selector(pstate()); // not very efficient because it needs to preserve order @@ -562,6 +568,7 @@ namespace Sass { } } + /* not used anymore - remove? vector Complex_Selector::to_vector() { vector result; @@ -575,7 +582,7 @@ namespace Sass { if (h) result.push_back(h); } return result; - } + }*/ } diff --git a/ast.hpp b/ast.hpp index 7281b384a9..c69b32c16d 100644 --- a/ast.hpp +++ b/ast.hpp @@ -30,8 +30,10 @@ #endif +#include "util.hpp" #include "units.hpp" -#include "token.hpp" +#include "context.hpp" +#include "position.hpp" #include "constants.hpp" #include "operation.hpp" #include "position.hpp" @@ -40,7 +42,8 @@ #include "environment.hpp" #include "error_handling.hpp" #include "ast_def_macros.hpp" - #include "to_string.hpp" +#include "ast_fwd_decl.hpp" +#include "to_string.hpp" #include "sass.h" #include "sass_values.h" @@ -52,14 +55,12 @@ namespace Sass { ////////////////////////////////////////////////////////// // Abstract base class for all abstract syntax tree nodes. ////////////////////////////////////////////////////////// - class Block; - class Statement; - class Expression; - class Selector; class AST_Node { ADD_PROPERTY(ParserState, pstate); public: - AST_Node(ParserState pstate) : pstate_(pstate) { } + AST_Node(ParserState pstate) + : pstate_(pstate) + { } virtual ~AST_Node() = 0; // virtual Block* block() { return 0; } public: @@ -98,7 +99,10 @@ namespace Sass { Expression(ParserState pstate, bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE) : AST_Node(pstate), - is_delayed_(d), is_expanded_(d), is_interpolant_(i), concrete_type_(ct) + is_delayed_(d), + is_expanded_(d), + is_interpolant_(i), + concrete_type_(ct) { } virtual operator bool() { return true; } virtual ~Expression() { }; @@ -184,7 +188,7 @@ namespace Sass { inline Vectorized::~Vectorized() { } ///////////////////////////////////////////////////////////////////////////// - // Mixin class for AST nodes that should behave like ahash table. Uses an + // Mixin class for AST nodes that should behave like a hash table. Uses an // extra internally to maintain insertion order for interation. ///////////////////////////////////////////////////////////////////////////// class Hashed { @@ -316,7 +320,6 @@ namespace Sass { // Rulesets (i.e., sets of styles headed by a selector and containing a block // of style declarations. ///////////////////////////////////////////////////////////////////////////// - class Selector; class Ruleset : public Has_Block { ADD_PROPERTY(Selector*, selector); public: @@ -332,7 +335,6 @@ namespace Sass { ///////////////////////////////////////////////////////// // Nested declaration sets (i.e., namespaced properties). ///////////////////////////////////////////////////////// - class String; class Propset : public Has_Block { ADD_PROPERTY(String*, property_fragment); public: @@ -359,7 +361,6 @@ namespace Sass { ///////////////// // Media queries. ///////////////// - class List; class Media_Block : public Has_Block { ADD_PROPERTY(List*, media_queries); ADD_PROPERTY(Selector*, selector); @@ -448,8 +449,6 @@ namespace Sass { ///////////////////////////////////// // Assignments -- variable and value. ///////////////////////////////////// - class Variable; - class Expression; class Assignment : public Statement { ADD_PROPERTY(string, variable); ADD_PROPERTY(Expression*, value); @@ -471,7 +470,7 @@ namespace Sass { //////////////////////////////////////////////////////////////////////////// class Import : public Statement { vector files_; - vector urls_; + vector urls_; public: Import(ParserState pstate) : Statement(pstate), @@ -532,9 +531,10 @@ namespace Sass { /////////////////////////////////////////// class Comment : public Statement { ADD_PROPERTY(String*, text); + ADD_PROPERTY(bool, is_important); public: - Comment(ParserState pstate, String* txt) - : Statement(pstate), text_(txt) + Comment(ParserState pstate, String* txt, bool is_important) + : Statement(pstate), text_(txt), is_important_(is_important) { } ATTACH_OPERATIONS(); }; @@ -623,9 +623,7 @@ namespace Sass { // Definitions for both mixins and functions. The two cases are distinguished // by a type tag. ///////////////////////////////////////////////////////////////////////////// - struct Context; struct Backtrace; - class Parameters; typedef Environment Env; typedef const char* Signature; typedef Expression* (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtrace*); @@ -701,7 +699,6 @@ namespace Sass { ////////////////////////////////////// // Mixin calls (i.e., `@include ...`). ////////////////////////////////////// - class Arguments; class Mixin_Call : public Has_Block { ADD_PROPERTY(string, name); ADD_PROPERTY(Arguments*, arguments); @@ -780,7 +777,6 @@ namespace Sass { /////////////////////////////////////////////////////////////////////// // Key value paris. /////////////////////////////////////////////////////////////////////// - class Map : public Expression, public Hashed { void adjust_after_pushing(std::pair p) { is_expanded(false); } public: @@ -823,8 +819,6 @@ namespace Sass { ATTACH_OPERATIONS(); }; - - ////////////////////////////////////////////////////////////////////////// // Binary expressions. Represents logical, relational, and arithmetic // operations. Templatized to avoid large switch statements and repetitive @@ -1326,10 +1320,10 @@ namespace Sass { // "flat" strings. //////////////////////////////////////////////////////////////////////// class String : public Expression { - ADD_PROPERTY(bool, needs_unquoting); + ADD_PROPERTY(bool, sass_fix_1291); public: - String(ParserState pstate, bool unq = false, bool delayed = false) - : Expression(pstate, delayed), needs_unquoting_(unq) + String(ParserState pstate, bool delayed = false, bool sass_fix_1291 = false) + : Expression(pstate, delayed), sass_fix_1291_(sass_fix_1291) { concrete_type(STRING); } static string type_name() { return "string"; } virtual ~String() = 0; @@ -1342,12 +1336,11 @@ namespace Sass { // evaluation phase. /////////////////////////////////////////////////////////////////////// class String_Schema : public String, public Vectorized { - ADD_PROPERTY(char, quote_mark); ADD_PROPERTY(bool, has_interpolants); size_t hash_; public: - String_Schema(ParserState pstate, size_t size = 0, bool unq = false, char qm = '\0', bool i = false) - : String(pstate, unq), Vectorized(size), quote_mark_(qm), has_interpolants_(i), hash_(0) + String_Schema(ParserState pstate, size_t size = 0, bool has_interpolants = false) + : String(pstate), Vectorized(size), has_interpolants_(has_interpolants), hash_(0) { } string type() { return "string"; } static string type_name() { return "string"; } @@ -1386,22 +1379,23 @@ namespace Sass { // Flat strings -- the lowest level of raw textual data. //////////////////////////////////////////////////////// class String_Constant : public String { + ADD_PROPERTY(char, quote_mark); ADD_PROPERTY(string, value); - string unquoted_; + protected: size_t hash_; public: - String_Constant(ParserState pstate, string val, bool unq = false) - : String(pstate, unq, true), value_(val), hash_(0) - { unquoted_ = unquote(value_); } - String_Constant(ParserState pstate, const char* beg, bool unq = false) - : String(pstate, unq, true), value_(string(beg)), hash_(0) - { unquoted_ = unquote(value_); } - String_Constant(ParserState pstate, const char* beg, const char* end, bool unq = false) - : String(pstate, unq, true), value_(string(beg, end-beg)), hash_(0) - { unquoted_ = unquote(value_); } - String_Constant(ParserState pstate, const Token& tok, bool unq = false) - : String(pstate, unq, true), value_(string(tok.begin, tok.end)), hash_(0) - { unquoted_ = unquote(value_); } + String_Constant(ParserState pstate, string val) + : String(pstate), quote_mark_(0), value_(val), hash_(0) + { } + String_Constant(ParserState pstate, const char* beg) + : String(pstate), quote_mark_(0), value_(string(beg)), hash_(0) + { } + String_Constant(ParserState pstate, const char* beg, const char* end) + : String(pstate), quote_mark_(0), value_(string(beg, end-beg)), hash_(0) + { } + String_Constant(ParserState pstate, const Token& tok) + : String(pstate), quote_mark_(0), value_(string(tok.begin, tok.end)), hash_(0) + { } string type() { return "string"; } static string type_name() { return "string"; } @@ -1410,7 +1404,7 @@ namespace Sass { try { String_Constant& e = dynamic_cast(rhs); - return e && unquoted_ == e.unquoted_; + return e && value_ == e.value_; } catch (std::bad_cast&) { @@ -1421,15 +1415,27 @@ namespace Sass { virtual size_t hash() { - if (hash_ == 0) hash_ = std::hash()(unquoted_); + if (hash_ == 0) hash_ = std::hash()(value_); return hash_; } - static char single_quote() { return '\''; } + // static char auto_quote() { return '*'; } static char double_quote() { return '"'; } + static char single_quote() { return '\''; } + + ATTACH_OPERATIONS(); + }; - bool is_quoted() { return value_.length() && (value_[0] == '"' || value_[0] == '\''); } - char quote_mark() { return is_quoted() ? value_[0] : '\0'; } + //////////////////////////////////////////////////////// + // Possibly quoted string (unquote on instantiation) + //////////////////////////////////////////////////////// + class String_Quoted : public String_Constant { + public: + String_Quoted(ParserState pstate, string val) + : String_Constant(pstate, val) + { + value_ = unquote(value_, "e_mark_); + } ATTACH_OPERATIONS(); }; @@ -1688,12 +1694,20 @@ namespace Sass { class Selector : public AST_Node { ADD_PROPERTY(bool, has_reference); ADD_PROPERTY(bool, has_placeholder); + // line break before list separator + ADD_PROPERTY(bool, has_line_feed); + // line break after list separator + ADD_PROPERTY(bool, has_line_break); public: Selector(ParserState pstate, bool r = false, bool h = false) - : AST_Node(pstate), has_reference_(r), has_placeholder_(h) + : AST_Node(pstate), + has_reference_(r), + has_placeholder_(h), + has_line_feed_(false), + has_line_break_(false) { } virtual ~Selector() = 0; - virtual Selector_Placeholder* find_placeholder(); + // virtual Selector_Placeholder* find_placeholder(); virtual int specificity() { return Constants::SPECIFICITY_BASE; } }; inline Selector::~Selector() { } @@ -1756,7 +1770,7 @@ namespace Sass { Selector_Placeholder(ParserState pstate, string n) : Simple_Selector(pstate), name_(n) { has_placeholder(true); } - virtual Selector_Placeholder* find_placeholder(); + // virtual Selector_Placeholder* find_placeholder(); ATTACH_OPERATIONS(); }; @@ -1886,7 +1900,7 @@ namespace Sass { { } Compound_Selector* unify_with(Compound_Selector* rhs, Context& ctx); - virtual Selector_Placeholder* find_placeholder(); + // virtual Selector_Placeholder* find_placeholder(); Simple_Selector* base() { // Implement non-const in terms of const. Safe to const_cast since this method is non-const @@ -1933,7 +1947,6 @@ namespace Sass { // CSS selector combinators (">", "+", "~", and whitespace). Essentially a // linked list. //////////////////////////////////////////////////////////////////////////// - struct Context; class Complex_Selector : public Selector { public: enum Combinator { ANCESTOR_OF, PARENT_OF, PRECEDES, ADJACENT_TO }; @@ -1957,7 +1970,7 @@ namespace Sass { size_t length(); bool is_superselector_of(Compound_Selector*); bool is_superselector_of(Complex_Selector*); - virtual Selector_Placeholder* find_placeholder(); + // virtual Selector_Placeholder* find_placeholder(); Combinator clear_innermost(); void set_innermost(Complex_Selector*, Combinator); virtual int specificity() const @@ -2020,7 +2033,7 @@ namespace Sass { } Complex_Selector* clone(Context&) const; // does not clone Compound_Selector*s Complex_Selector* cloneFully(Context&) const; // clones Compound_Selector*s - vector to_vector(); + // vector to_vector(); ATTACH_OPERATIONS(); }; @@ -2029,18 +2042,18 @@ namespace Sass { /////////////////////////////////// // Comma-separated selector groups. /////////////////////////////////// - class Selector_List - : public Selector, public Vectorized { + class Selector_List : public Selector, public Vectorized { #ifdef DEBUG ADD_PROPERTY(string, mCachedSelector); #endif + ADD_PROPERTY(vector, wspace); protected: void adjust_after_pushing(Complex_Selector* c); public: Selector_List(ParserState pstate, size_t s = 0) - : Selector(pstate), Vectorized(s) + : Selector(pstate), Vectorized(s), wspace_(0) { } - virtual Selector_Placeholder* find_placeholder(); + // virtual Selector_Placeholder* find_placeholder(); virtual int specificity() { int sum = 0; diff --git a/ast_fwd_decl.hpp b/ast_fwd_decl.hpp index 20ae667632..83b46d0fc0 100644 --- a/ast_fwd_decl.hpp +++ b/ast_fwd_decl.hpp @@ -6,6 +6,8 @@ ///////////////////////////////////////////// namespace Sass { + enum Output_Style { NESTED, EXPANDED, COMPACT, COMPRESSED, FORMATTED }; + class AST_Node; // statements class Statement; @@ -51,6 +53,7 @@ namespace Sass { class String_Schema; class String; class String_Constant; + class String_Quoted; class Media_Query; class Media_Query_Expression; class Feature_Query; diff --git a/bind.cpp b/bind.cpp index aeba077d5e..d0cba9e719 100644 --- a/bind.cpp +++ b/bind.cpp @@ -145,7 +145,7 @@ namespace Sass { // That's only okay if they have default values, or were already bound by // named arguments, or if it's a single rest-param. for (size_t i = ip; i < LP; ++i) { - To_String to_string; + To_String to_string(&ctx); Parameter* leftover = (*ps)[i]; // cerr << "env for default params:" << endl; // env->print(); diff --git a/context.cpp b/context.cpp index 664898f9fb..00d978b5c1 100644 --- a/context.cpp +++ b/context.cpp @@ -10,8 +10,7 @@ #include "parser.hpp" #include "file.hpp" #include "inspect.hpp" -#include "output_nested.hpp" -#include "output_compressed.hpp" +#include "output.hpp" #include "expand.hpp" #include "eval.hpp" #include "contextualize.hpp" @@ -24,11 +23,14 @@ #include "backtrace.hpp" #include "sass2scss.h" #include "prelexer.hpp" +#include "emitter.hpp" -#include -#include +#include +#include #include +#include #include +#include namespace Sass { using namespace Constants; @@ -45,13 +47,14 @@ namespace Sass { Context::Context(Context::Data initializers) - : mem(Memory_Manager()), + : // Output(this), + mem(Memory_Manager()), source_c_str (initializers.source_c_str()), sources (vector()), include_paths (initializers.include_paths()), queue (vector()), style_sheets (map()), - source_map (resolve_relative_path(initializers.output_path(), initializers.source_map_file(), get_cwd())), + emitter (this), c_functions (vector()), indent (initializers.indent()), linefeed (initializers.linefeed()), @@ -68,7 +71,6 @@ namespace Sass { names_to_colors (map()), colors_to_names (map()), precision (initializers.precision()), - _skip_source_map_update (initializers._skip_source_map_update()), subset_map (Subset_Map >()) { cwd = get_cwd(); @@ -91,6 +93,9 @@ namespace Sass { throw "File to read not found or unreadable: " + entry_point; } } + + emitter.set_filename(output_path); + } Context::~Context() @@ -163,7 +168,7 @@ namespace Sass { sources.push_back(contents); included_files.push_back(abs_path); queue.push_back(Sass_Queued(load_path, abs_path, contents)); - source_map.source_index.push_back(sources.size() - 1); + emitter.add_source_index(sources.size() - 1); include_links.push_back(resolve_relative_path(abs_path, source_map_file, cwd)); } @@ -222,31 +227,14 @@ namespace Sass { char* Context::compile_block(Block* root) { - char* result = 0; if (!root) return 0; - switch (output_style) { - case COMPRESSED: { - Output_Compressed output_compressed(this); - root->perform(&output_compressed); - string output = output_compressed.get_buffer(); - if (source_map_file != "" && !omit_source_map_url) { - output += format_source_mapping_url(source_map_file); - } - result = copy_c_str(output.c_str()); - } break; - - default: { - Output_Nested output_nested(source_comments, this); - root->perform(&output_nested); - string output = output_nested.get_buffer(); - if (source_map_file != "" && !omit_source_map_url) { - output += linefeed + format_source_mapping_url(source_map_file); - } - result = copy_c_str(output.c_str()); - - } break; + root->perform(&emitter); + emitter.finalize(); + string output = emitter.get_buffer(); + if (source_map_file != "" && !omit_source_map_url) { + output += linefeed + format_source_mapping_url(source_map_file); } - return result; + return copy_c_str(output.c_str()); } Block* Context::parse_file() @@ -277,9 +265,6 @@ namespace Sass { Contextualize contextualize(*this, &eval, &tge, &backtrace); Expand expand(*this, &eval, &contextualize, &tge, &backtrace); Cssize cssize(*this, &tge, &backtrace); - // Inspect inspect(this); - // Output_Nested output_nested(*this); - root = root->perform(&expand)->block(); root = root->perform(&cssize)->block(); if (!subset_map.empty()) { @@ -322,7 +307,7 @@ namespace Sass { { string url = resolve_relative_path(file, output_path, cwd); if (source_map_embed) { - string map = source_map.generate_source_map(*this); + string map = emitter.generate_source_map(*this); istringstream is( map ); ostringstream buffer; base64::encoder E; @@ -337,7 +322,7 @@ namespace Sass { { if (source_map_file == "") return 0; char* result = 0; - string map = source_map.generate_source_map(*this); + string map = emitter.generate_source_map(*this); result = copy_c_str(map.c_str()); return result; } diff --git a/context.hpp b/context.hpp index 8ff8427cb5..98ee5c7cee 100644 --- a/context.hpp +++ b/context.hpp @@ -8,29 +8,19 @@ #define BUFFERSIZE 255 #include "b64/encode.h" +#include "ast_fwd_decl.hpp" #include "kwd_arg_macros.hpp" #include "memory_manager.hpp" #include "environment.hpp" #include "source_map.hpp" #include "subset_map.hpp" +#include "output.hpp" #include "sass_functions.h" struct Sass_C_Function_Descriptor; namespace Sass { using namespace std; - class AST_Node; - class Block; - class Expression; - class Color; - struct Backtrace; - // typedef const char* Signature; - // struct Context; - // typedef Environment Env; - // typedef Expression* (*Native_Function)(Env&, Context&, Signature, string, size_t); - - enum Output_Style { NESTED, EXPANDED, COMPACT, COMPRESSED, FORMATTED }; - struct Sass_Queued { string abs_path; string load_path; @@ -39,7 +29,8 @@ namespace Sass { Sass_Queued(const string& load_path, const string& abs_path, const char* source); }; - struct Context { + class Context { + public: Memory_Manager mem; const char* source_c_str; @@ -56,7 +47,8 @@ namespace Sass { vector include_paths; // lookup paths for includes vector queue; // queue of files to be parsed map style_sheets; // map of paths to ASTs - SourceMap source_map; + // SourceMap source_map; + Output emitter; vector c_functions; string indent; // String to be used for indentation @@ -79,7 +71,6 @@ namespace Sass { map colors_to_names; size_t precision; // precision for outputting fractional numbers - bool _skip_source_map_update; // status flag to skip source map updates KWD_ARG_SET(Data) { KWD_ARG(Data, const char*, source_c_str); @@ -97,7 +88,6 @@ namespace Sass { KWD_ARG(Data, bool, omit_source_map_url); KWD_ARG(Data, bool, is_indented_syntax_src); KWD_ARG(Data, size_t, precision); - KWD_ARG(Data, bool, _skip_source_map_update); KWD_ARG(Data, bool, source_map_embed); KWD_ARG(Data, bool, source_map_contents); KWD_ARG(Data, Sass_C_Import_Callback, importer); diff --git a/contextualize.cpp b/contextualize.cpp index 9a762b1e3e..9e7d8a4985 100644 --- a/contextualize.cpp +++ b/contextualize.cpp @@ -28,7 +28,7 @@ namespace Sass { Selector* Contextualize::operator()(Selector_Schema* s) { - To_String to_string; + To_String to_string(&ctx); string result_str(s->contents()->perform(eval->with(env, backtrace))->perform(&to_string)); result_str += '{'; // the parser looks for a brace to end the selector Selector* result_sel = Parser::from_c_str(result_str.c_str(), ctx, s->pstate()).parse_selector_group(); @@ -45,6 +45,7 @@ namespace Sass { for (size_t j = 0, L = s->length(); j < L; ++j) { parent = (*p)[i]; Complex_Selector* comb = static_cast((*s)[j]->perform(this)); + if (parent->has_line_feed()) comb->has_line_feed(true); if (comb) *ss << comb; } } @@ -61,7 +62,7 @@ namespace Sass { Selector* Contextualize::operator()(Complex_Selector* s) { - To_String to_string; + To_String to_string(&ctx); Complex_Selector* ss = new (ctx.mem) Complex_Selector(*s); Compound_Selector* new_head = 0; Complex_Selector* new_tail = 0; @@ -89,11 +90,12 @@ namespace Sass { Selector* Contextualize::operator()(Compound_Selector* s) { - To_String to_string; + To_String to_string(&ctx); if (placeholder && extender && s->perform(&to_string) == placeholder->perform(&to_string)) { return extender; } Compound_Selector* ss = new (ctx.mem) Compound_Selector(s->pstate(), s->length()); + ss->has_line_break(s->has_line_break()); for (size_t i = 0, L = s->length(); i < L; ++i) { Simple_Selector* simp = static_cast((*s)[i]->perform(this)); if (simp) *ss << simp; @@ -135,7 +137,7 @@ namespace Sass { Selector* Contextualize::operator()(Selector_Placeholder* p) { - To_String to_string; + To_String to_string(&ctx); if (placeholder && extender && p->perform(&to_string) == placeholder->perform(&to_string)) { return extender; } diff --git a/contextualize.hpp b/contextualize.hpp index 2a814d06e7..49d27e6da6 100644 --- a/contextualize.hpp +++ b/contextualize.hpp @@ -1,26 +1,13 @@ #ifndef SASS_CONTEXTUALIZE_H #define SASS_CONTEXTUALIZE_H +#include "eval.hpp" +#include "context.hpp" #include "operation.hpp" #include "environment.hpp" +#include "ast_fwd_decl.hpp" namespace Sass { - class AST_Node; - class Selector; - class Selector_Schema; - class Selector_List; - class Complex_Selector; - class Compound_Selector; - class Wrapped_Selector; - class Pseudo_Selector; - class Attribute_Selector; - class Selector_Qualifier; - class Type_Selector; - class Selector_Placeholder; - class Selector_Reference; - class Simple_Selector; - struct Context; - class Eval; struct Backtrace; typedef Environment Env; diff --git a/cssize.cpp b/cssize.cpp index fd8c437507..444811d718 100644 --- a/cssize.cpp +++ b/cssize.cpp @@ -27,6 +27,7 @@ namespace Sass { new_env.link(*env); env = &new_env; Block* bb = new (ctx.mem) Block(b->pstate(), b->length(), b->is_root()); + // bb->tabs(b->tabs()); block_stack.push_back(bb); append_block(b); block_stack.pop_back(); @@ -97,6 +98,7 @@ namespace Sass { Ruleset* rr = new (ctx.mem) Ruleset(r->pstate(), r->selector(), r->block()->perform(this)->block()); + // rr->tabs(r->block()->tabs()); p_stack.pop_back(); Block* props = new Block(rr->block()->pstate()); @@ -191,6 +193,7 @@ namespace Sass { { Block* bb = m->block()->perform(this)->block(); for (size_t i = 0, L = bb->length(); i < L; ++i) { + // (bb->elements())[i]->tabs(m->tabs()); if (bubblable((*bb)[i])) (*bb)[i]->tabs((*bb)[i]->tabs() + m->tabs()); } if (bb->length() && bubblable(bb->last())) bb->last()->group_end(m->group_end()); @@ -295,6 +298,8 @@ namespace Sass { wrapper_block, m->selector()); + mm->tabs(m->tabs()); + Bubble* bubble = new (ctx.mem) Bubble(mm->pstate(), mm); return bubble; @@ -496,7 +501,7 @@ namespace Sass { Media_Query* Cssize::merge_media_query(Media_Query* mq1, Media_Query* mq2) { - To_String to_string; + To_String to_string(&ctx); string type; string mod; diff --git a/cssize.hpp b/cssize.hpp index c505680299..1389965393 100644 --- a/cssize.hpp +++ b/cssize.hpp @@ -5,13 +5,13 @@ #include #include "ast.hpp" +#include "context.hpp" #include "operation.hpp" #include "environment.hpp" namespace Sass { using namespace std; - struct Context; typedef Environment Env; struct Backtrace; diff --git a/debugger.hpp b/debugger.hpp new file mode 100644 index 0000000000..a2648d4e73 --- /dev/null +++ b/debugger.hpp @@ -0,0 +1,298 @@ +#ifndef SASS_DEBUGGER_H +#define SASS_DEBUGGER_H + +#include "ast_fwd_decl.hpp" + +using namespace std; +using namespace Sass; + +inline string str_replace(std::string str, const std::string& oldStr, const std::string& newStr) +{ + size_t pos = 0; + while((pos = str.find(oldStr, pos)) != std::string::npos) + { + str.replace(pos, oldStr.length(), newStr); + pos += newStr.length(); + } + return str; +} + +inline string prettyprint(const string& str) { + string clean = str_replace(str, "\n", "\\n"); + clean = str_replace(clean, " ", "\\t"); + clean = str_replace(clean, "\r", "\\r"); + return clean; +} + +inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) +{ + + if (ind == "") cerr << "####################################################################\n"; + if (dynamic_cast(node)) { + Bubble* bubble = dynamic_cast(node); + cerr << ind << "Bubble " << bubble << " " << bubble->tabs() << endl; + } else if (dynamic_cast(node)) { + At_Root_Block* root_block = dynamic_cast(node); + cerr << ind << "At_Root_Block " << root_block << " " << root_block->tabs() << endl; + if (root_block->block()) for(auto i : root_block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Selector_List* selector = dynamic_cast(node); + cerr << ind << "Selector_List " << selector << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; + for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } + +// } else if (dynamic_cast(node)) { +// Expression* expression = dynamic_cast(node); +// cerr << ind << "Expression " << expression << " " << expression->concrete_type() << endl; + + } else if (dynamic_cast(node)) { + Complex_Selector* selector = dynamic_cast(node); + cerr << ind << "Complex_Selector " << selector << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << " -> "; + switch (selector->combinator()) { + case Complex_Selector::PARENT_OF: cerr << "{>}"; break; + case Complex_Selector::PRECEDES: cerr << "{~}"; break; + case Complex_Selector::ADJACENT_TO: cerr << "{+}"; break; + case Complex_Selector::ANCESTOR_OF: cerr << "{ }"; break; + } + cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << "> X <" << prettyprint(selector->pstate().token.ws_after()) << ">" << endl; + debug_ast(selector->head(), ind + " ", env); + debug_ast(selector->tail(), ind + "-", env); + } else if (dynamic_cast(node)) { + Compound_Selector* selector = dynamic_cast(node); + cerr << ind << "Compound_Selector " << selector << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << + " <" << prettyprint(selector->pstate().token.ws_before()) << "> X <" << prettyprint(selector->pstate().token.ws_after()) << ">" << endl; + for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Propset* selector = dynamic_cast(node); + cerr << ind << "Propset " << selector << " " << selector->tabs() << endl; + if (selector->block()) for(auto i : selector->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Wrapped_Selector* selector = dynamic_cast(node); + cerr << ind << "Wrapped_Selector " << selector << " <<" << selector->name() << ">>" << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; + debug_ast(selector->selector(), ind + " () ", env); + } else if (dynamic_cast(node)) { + Pseudo_Selector* selector = dynamic_cast(node); + cerr << ind << "Pseudo_Selector " << selector << " <<" << selector->name() << ">>" << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; + debug_ast(selector->expression(), ind + " <= ", env); + } else if (dynamic_cast(node)) { + Attribute_Selector* selector = dynamic_cast(node); + cerr << ind << "Attribute_Selector " << selector << " <<" << selector->name() << ">>" << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; + debug_ast(selector->value(), ind + "[" + selector->matcher() + "] ", env); + } else if (dynamic_cast(node)) { + Selector_Qualifier* selector = dynamic_cast(node); + cerr << ind << "Selector_Qualifier " << selector << " <<" << selector->name() << ">>" << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; + } else if (dynamic_cast(node)) { + Type_Selector* selector = dynamic_cast(node); + cerr << ind << "Type_Selector " << selector << " <<" << selector->name() << ">>" << (selector->has_line_break() ? " [line-break]": " -") << + " <" << prettyprint(selector->pstate().token.ws_before()) << "> X <" << prettyprint(selector->pstate().token.ws_after()) << ">" << endl; + } else if (dynamic_cast(node)) { + Selector_Placeholder* selector = dynamic_cast(node); + cerr << ind << "Selector_Placeholder " << selector << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; + } else if (dynamic_cast(node)) { + Selector_Reference* selector = dynamic_cast(node); + cerr << ind << "Selector_Reference " << selector << " @ref " << selector->selector() << endl; + } else if (dynamic_cast(node)) { + Simple_Selector* selector = dynamic_cast(node); + cerr << ind << "Simple_Selector " << selector << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; + + } else if (dynamic_cast(node)) { + Selector_Schema* selector = dynamic_cast(node); + cerr << ind << "Selector_Schema " << selector << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; + debug_ast(selector->contents(), ind + " "); + // for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } + + } else if (dynamic_cast(node)) { + Selector* selector = dynamic_cast(node); + cerr << ind << "Selector " << selector << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; + } else if (dynamic_cast(node)) { + Media_Block* block = dynamic_cast(node); + cerr << ind << "Media_Block " << block << " " << block->tabs() << endl; + if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Feature_Block* block = dynamic_cast(node); + cerr << ind << "Feature_Block " << block << " " << block->tabs() << endl; + if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Block* root_block = dynamic_cast(node); + cerr << ind << "Block " << root_block << " " << root_block->tabs() << endl; + if (root_block->block()) for(auto i : root_block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Warning* block = dynamic_cast(node); + cerr << ind << "Warning " << block << " " << block->tabs() << endl; + } else if (dynamic_cast(node)) { + Error* block = dynamic_cast(node); + cerr << ind << "Error " << block << " " << block->tabs() << endl; + } else if (dynamic_cast(node)) { + Debug* block = dynamic_cast(node); + cerr << ind << "Debug " << block << " " << block->tabs() << endl; + } else if (dynamic_cast(node)) { + Comment* block = dynamic_cast(node); + cerr << ind << "Comment " << block << " " << block->tabs() << + " <" << prettyprint(block->pstate().token.ws_before()) << "> X <" << prettyprint(block->pstate().token.ws_after()) << ">" << endl; + debug_ast(block->text(), ind + "// ", env); + } else if (dynamic_cast(node)) { + If* block = dynamic_cast(node); + cerr << ind << "If " << block << " " << block->tabs() << endl; + } else if (dynamic_cast(node)) { + Return* block = dynamic_cast(node); + cerr << ind << "Return " << block << " " << block->tabs() << endl; + } else if (dynamic_cast(node)) { + Extension* block = dynamic_cast(node); + cerr << ind << "Extension " << block << " " << block->tabs() << endl; + debug_ast(block->selector(), ind + "-> ", env); + } else if (dynamic_cast(node)) { + Content* block = dynamic_cast(node); + cerr << ind << "Content " << block << " " << block->tabs() << endl; + } else if (dynamic_cast(node)) { + Import_Stub* block = dynamic_cast(node); + cerr << ind << "Import_Stub " << block << " " << block->tabs() << endl; + } else if (dynamic_cast(node)) { + Import* block = dynamic_cast(node); + cerr << ind << "Import " << block << " " << block->tabs() << endl; + // vector files_; + for (auto imp : block->urls()) debug_ast(imp, "@ ", env); + } else if (dynamic_cast(node)) { + Assignment* block = dynamic_cast(node); + cerr << ind << "Assignment " << block << " <<" << block->variable() << ">> " << block->tabs() << endl; + debug_ast(block->value(), ind + "=", env); + } else if (dynamic_cast(node)) { + Declaration* block = dynamic_cast(node); + cerr << ind << "Declaration " << block << " " << block->tabs() << endl; + debug_ast(block->property(), ind + " prop: ", env); + debug_ast(block->value(), ind + " value: ", env); + } else if (dynamic_cast(node)) { + At_Rule* block = dynamic_cast(node); + cerr << ind << "At_Rule " << block << " " << block->tabs() << endl; + debug_ast(block->value(), ind + "+", env); + debug_ast(block->selector(), ind + "~", env); + if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Each* block = dynamic_cast(node); + cerr << ind << "Each " << block << " " << block->tabs() << endl; + if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + For* block = dynamic_cast(node); + cerr << ind << "For " << block << " " << block->tabs() << endl; + if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + While* block = dynamic_cast(node); + cerr << ind << "While " << block << " " << block->tabs() << endl; + if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Definition* block = dynamic_cast(node); + cerr << ind << "Definition " << block << " " << block->tabs() << endl; + if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Mixin_Call* block = dynamic_cast(node); + cerr << ind << "Mixin_Call " << block << " " << block->tabs() << endl; + if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Ruleset* ruleset = dynamic_cast(node); + cerr << ind << "Ruleset " << ruleset << " " << ruleset->tabs() << endl; + debug_ast(ruleset->selector(), ind + " "); + if (ruleset->block()) for(auto i : ruleset->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Block* block = dynamic_cast(node); + cerr << ind << "Block " << block << " " << block->tabs() << endl; + for(auto i : block->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Textual* expression = dynamic_cast(node); + cerr << ind << "Textual "; + if (expression->type() == Textual::NUMBER) cerr << " [NUMBER]"; + else if (expression->type() == Textual::PERCENTAGE) cerr << " [PERCENTAGE]"; + else if (expression->type() == Textual::DIMENSION) cerr << " [DIMENSION]"; + else if (expression->type() == Textual::HEX) cerr << " [HEX]"; + cerr << expression << " [" << expression->value() << "]" << endl; + } else if (dynamic_cast(node)) { + Variable* expression = dynamic_cast(node); + cerr << ind << "Variable " << expression << " [" << expression->name() << "]" << endl; + string name(expression->name()); + if (env && env->has(name)) debug_ast(static_cast((*env)[name]), ind + " -> ", env); + } else if (dynamic_cast(node)) { + Function_Call_Schema* expression = dynamic_cast(node); + cerr << ind << "Function_Call_Schema " << expression << "]" << endl; + debug_ast(expression->name(), ind + "name: ", env); + debug_ast(expression->arguments(), ind + " args: ", env); + } else if (dynamic_cast(node)) { + Function_Call* expression = dynamic_cast(node); + cerr << ind << "Function_Call " << expression << " [" << expression->name() << "]" << endl; + debug_ast(expression->arguments(), ind + " args: ", env); + } else if (dynamic_cast(node)) { + Arguments* expression = dynamic_cast(node); + cerr << ind << "Arguments " << expression << "]" << endl; + for(auto i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Argument* expression = dynamic_cast(node); + cerr << ind << "Argument " << expression << " [" << expression->value() << "]" << endl; + debug_ast(expression->value(), ind + " value: ", env); + } else if (dynamic_cast(node)) { + Unary_Expression* expression = dynamic_cast(node); + cerr << ind << "Unary_Expression " << expression << " [" << expression->type() << "]" << endl; + debug_ast(expression->operand(), ind + " operand: ", env); + } else if (dynamic_cast(node)) { + Binary_Expression* expression = dynamic_cast(node); + cerr << ind << "Binary_Expression " << expression << " [" << expression->type() << "]" << endl; + debug_ast(expression->left(), ind + " left: ", env); + debug_ast(expression->right(), ind + " right: ", env); + } else if (dynamic_cast(node)) { + Map* expression = dynamic_cast(node); + cerr << ind << "Map " << expression << " [Hashed]" << endl; + } else if (dynamic_cast(node)) { + List* expression = dynamic_cast(node); + cerr << ind << "List " << expression << + (expression->separator() == Sass::List::Separator::COMMA ? "Comma " : "Space ") << + " [delayed: " << expression->is_delayed() << "] " << + " [interpolant: " << expression->is_interpolant() << "] " << + endl; + for(auto i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Content* expression = dynamic_cast(node); + cerr << ind << "Content " << expression << " [Statement]" << endl; + } else if (dynamic_cast(node)) { + Boolean* expression = dynamic_cast(node); + cerr << ind << "Boolean " << expression << " [" << expression->value() << "]" << endl; + } else if (dynamic_cast(node)) { + Color* expression = dynamic_cast(node); + cerr << ind << "Color " << expression << " [" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << endl; + } else if (dynamic_cast(node)) { + Number* expression = dynamic_cast(node); + cerr << ind << "Number " << expression << " [" << expression->value() << expression->unit() << "]" << endl; + } else if (dynamic_cast(node)) { + String_Quoted* expression = dynamic_cast(node); + cerr << ind << "String_Quoted : " << expression << " [" << prettyprint(expression->value()) << "]" << + (expression->is_delayed() ? " {delayed}" : "") << + (expression->sass_fix_1291() ? " {sass_fix_1291}" : "") << + (expression->quote_mark() != 0 ? " {qm:" + string(1, expression->quote_mark()) + "}" : "") << + " <" << prettyprint(expression->pstate().token.ws_before()) << "> X <" << prettyprint(expression->pstate().token.ws_after()) << ">" << endl; + } else if (dynamic_cast(node)) { + String_Constant* expression = dynamic_cast(node); + cerr << ind << "String_Constant : " << expression << " [" << prettyprint(expression->value()) << "]" << + (expression->is_delayed() ? " {delayed}" : "") << + (expression->sass_fix_1291() ? " {sass_fix_1291}" : "") << + " <" << prettyprint(expression->pstate().token.ws_before()) << "> X <" << prettyprint(expression->pstate().token.ws_after()) << ">" << endl; + } else if (dynamic_cast(node)) { + String_Schema* expression = dynamic_cast(node); + cerr << ind << "String_Schema " << expression << " " << expression->concrete_type() << + (expression->has_interpolants() ? " {has_interpolants}" : "") << + endl; + for(auto i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + String* expression = dynamic_cast(node); + cerr << ind << "String " << expression << expression->concrete_type() << + " " << (expression->sass_fix_1291() ? "{sass_fix_1291}" : "") << + endl; + } else if (dynamic_cast(node)) { + Expression* expression = dynamic_cast(node); + cerr << ind << "Expression " << expression << " " << expression->concrete_type() << endl; + } else if (dynamic_cast(node)) { + Has_Block* has_block = dynamic_cast(node); + cerr << ind << "Has_Block " << has_block << " " << has_block->tabs() << endl; + if (has_block->block()) for(auto i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { + Statement* statement = dynamic_cast(node); + cerr << ind << "Statement " << statement << " " << statement->tabs() << endl; + } + + if (ind == "") cerr << "####################################################################\n"; +} + +#endif // SASS_DEBUGGER diff --git a/emitter.cpp b/emitter.cpp new file mode 100644 index 0000000000..e2b030b2b6 --- /dev/null +++ b/emitter.cpp @@ -0,0 +1,240 @@ +#include "util.hpp" +#include "context.hpp" +#include "output.hpp" +#include "emitter.hpp" +#include "utf8_string.hpp" + +namespace Sass { + using namespace std; + + Emitter::Emitter(Context* ctx) + : wbuf(), + ctx(ctx), + indentation(0), + scheduled_space(0), + scheduled_linefeed(0), + scheduled_delimiter(false), + in_comment(false), + in_media_block(false), + in_declaration(false), + in_declaration_list(false) + { } + + // return buffer as string + string Emitter::get_buffer(void) + { + return wbuf.buffer; + } + + Output_Style Emitter::output_style(void) + { + return ctx ? ctx->output_style : COMPRESSED; + } + + // PROXY METHODS FOR SOURCE MAPS + + void Emitter::add_source_index(size_t idx) + { wbuf.smap.source_index.push_back(idx); } + + string Emitter::generate_source_map(Context &ctx) + { return wbuf.smap.generate_source_map(ctx); } + + void Emitter::set_filename(const string& str) + { wbuf.smap.file = str; } + + void Emitter::add_open_mapping(AST_Node* node) + { wbuf.smap.add_open_mapping(node); } + void Emitter::add_close_mapping(AST_Node* node) + { wbuf.smap.add_close_mapping(node); } + ParserState Emitter::remap(const ParserState& pstate) + { return wbuf.smap.remap(pstate); } + + // MAIN BUFFER MANIPULATION + + // add outstanding delimiter + void Emitter::finalize(void) + { + scheduled_space = 0; + if (scheduled_linefeed) + scheduled_linefeed = 1; + flush_schedules(); + } + + // flush scheduled space/linefeed + void Emitter::flush_schedules(void) + { + // check the schedule + if (scheduled_linefeed) { + string linefeeds = ""; + + for (size_t i = 0; i < scheduled_linefeed; i++) + linefeeds += ctx ? ctx->linefeed : "\n"; + scheduled_space = 0; + scheduled_linefeed = 0; + append_string(linefeeds); + + } else if (scheduled_space) { + string spaces(scheduled_space, ' '); + scheduled_space = 0; + append_string(spaces); + } + if (scheduled_delimiter) { + scheduled_delimiter = false; + append_string(";"); + } + } + + // append some text or token to the buffer + void Emitter::append_string(const string& text) + { + // write space/lf + flush_schedules(); + + if (in_comment && output_style() == COMPACT) { + // unescape comment nodes + string out = comment_to_string(text); + // add to buffer + wbuf.buffer += out; + // account for data in source-maps + wbuf.smap.update_column(out); + } else { + // add to buffer + wbuf.buffer += text; + // account for data in source-maps + wbuf.smap.update_column(text); + } + } + + // append some white-space only text + void Emitter::append_wspace(const string& text) + { + if (text.empty()) return; + if (peek_linefeed(text.c_str())) { + scheduled_space = 0; + append_mandatory_linefeed(); + } + } + + // append some text or token to the buffer + // this adds source-mappings for node start and end + void Emitter::append_token(const string& text, AST_Node* node) + { + flush_schedules(); + add_open_mapping(node); + append_string(text); + add_close_mapping(node); + } + + // HELPER METHODS + + void Emitter::append_indentation() + { + if (output_style() == COMPRESSED) return; + if (output_style() == COMPACT) return; + if (scheduled_linefeed && indentation) + scheduled_linefeed = 1; + string indent = ""; + for (size_t i = 0; i < indentation; i++) + indent += ctx ? ctx->indent : " "; + append_string(indent); + } + + void Emitter::append_delimiter() + { + scheduled_delimiter = true; + if (output_style() == COMPACT) { + if (indentation == 0) { + append_mandatory_linefeed(); + } else { + append_mandatory_space(); + } + } else if (output_style() != COMPRESSED) { + append_optional_linefeed(); + } + } + + void Emitter::append_comma_separator() + { + scheduled_space = 0; + append_string(","); + append_optional_space(); + } + + void Emitter::append_colon_separator() + { + scheduled_space = 0; + append_string(":"); + append_optional_space(); + } + + void Emitter::append_mandatory_space() + { + scheduled_space = 1; + } + + void Emitter::append_optional_space() + { + if (output_style() != COMPRESSED && buffer().size()) { + char lst = buffer().at(buffer().length() - 1); + if (!isspace(lst)) append_mandatory_space(); + } + } + + void Emitter::append_special_linefeed() + { + if (output_style() == COMPACT) { + append_mandatory_linefeed(); + for (size_t p = 0; p < indentation; p++) + append_string(ctx ? ctx->indent : " "); + } + } + + void Emitter::append_optional_linefeed() + { + if (output_style() == COMPACT) { + append_mandatory_space(); + } else { + append_mandatory_linefeed(); + } + } + + void Emitter::append_mandatory_linefeed() + { + if (output_style() != COMPRESSED) { + scheduled_linefeed = 1; + scheduled_space = 0; + // flush_schedules(); + } + } + + void Emitter::append_scope_opener(AST_Node* node) + { + append_optional_space(); + flush_schedules(); + if (node) add_open_mapping(node); + append_string("{"); + append_optional_linefeed(); + // append_optional_space(); + ++ indentation; + } + void Emitter::append_scope_closer(AST_Node* node) + { + -- indentation; + scheduled_linefeed = 0; + if (output_style() == COMPRESSED) + scheduled_delimiter = false; + if (output_style() == EXPANDED) { + append_optional_linefeed(); + append_indentation(); + } else { + append_optional_space(); + } + append_string("}"); + if (node) add_close_mapping(node); + append_optional_linefeed(); + if (indentation != 0) return; + if (output_style() != COMPRESSED) + scheduled_linefeed = 2; + } + +} diff --git a/emitter.hpp b/emitter.hpp new file mode 100644 index 0000000000..83855ce5ff --- /dev/null +++ b/emitter.hpp @@ -0,0 +1,89 @@ +#ifndef SASS_EMITTER_H +#define SASS_EMITTER_H + +#include +#include "source_map.hpp" +#include "ast_fwd_decl.hpp" + +namespace Sass { + class Context; + using namespace std; + + class OutputBuffer { + public: + OutputBuffer(void) + : buffer(""), + smap() + { } + public: + string buffer; + SourceMap smap; + }; + + class Emitter { + + public: + Emitter(Context* ctx); + virtual ~Emitter() { }; + + protected: + OutputBuffer wbuf; + public: + const string buffer(void) { return wbuf.buffer; } + const SourceMap smap(void) { return wbuf.smap; } + // proxy methods for source maps + void add_source_index(size_t idx); + void set_filename(const string& str); + void add_open_mapping(AST_Node* node); + void add_close_mapping(AST_Node* node); + string generate_source_map(Context &ctx); + ParserState remap(const ParserState& pstate); + + public: + Context* ctx; + size_t indentation; + size_t scheduled_space; + size_t scheduled_linefeed; + bool scheduled_delimiter; + + public: + bool in_comment; + bool in_media_block; + bool in_declaration; + bool in_declaration_list; + + public: + // return buffer as string + string get_buffer(void); + // flush scheduled space/linefeed + Output_Style output_style(void); + // add outstanding linefeed + void finalize(void); + // flush scheduled space/linefeed + void flush_schedules(void); + // append some text or token to the buffer + void append_string(const string& text); + // append some white-space only text + void append_wspace(const string& text); + // append some text or token to the buffer + // this adds source-mappings for node start and end + void append_token(const string& text, AST_Node* node); + + public: // syntax sugar + void append_indentation(); + void append_optional_space(void); + void append_mandatory_space(void); + void append_special_linefeed(void); + void append_optional_linefeed(void); + void append_mandatory_linefeed(void); + void append_scope_opener(AST_Node* node = 0); + void append_scope_closer(AST_Node* node = 0); + void append_comma_separator(void); + void append_colon_separator(void); + void append_delimiter(void); + + }; + +} + +#endif diff --git a/eval.cpp b/eval.cpp index cf4979c749..44e6c1e2f8 100644 --- a/eval.cpp +++ b/eval.cpp @@ -204,7 +204,7 @@ namespace Sass { Expression* Eval::operator()(Warning* w) { Expression* message = w->message()->perform(this); - To_String to_string; + To_String to_string(&ctx); // try to use generic function if (env->has("@warn[f]")) { @@ -235,7 +235,7 @@ namespace Sass { Expression* Eval::operator()(Error* e) { Expression* message = e->message()->perform(this); - To_String to_string; + To_String to_string(&ctx); // try to use generic function if (env->has("@error[f]")) { @@ -266,7 +266,7 @@ namespace Sass { Expression* Eval::operator()(Debug* d) { Expression* message = d->value()->perform(this); - To_String to_string; + To_String to_string(&ctx); // try to use generic function if (env->has("@debug[f]")) { @@ -384,7 +384,18 @@ namespace Sass { if (l_type == Expression::COLOR && r_type == Expression::COLOR) { return op_colors(ctx, op_type, lhs, rhs); } - return op_strings(ctx, op_type, lhs, rhs); + + Expression* ex = op_strings(ctx, op_type, lhs, rhs); + if (String_Constant* str = (String_Constant*) ex) + { + if (str->concrete_type() != Expression::STRING) return ex; + String_Constant* lstr = dynamic_cast(lhs); + String_Constant* rstr = dynamic_cast(rhs); + if (String_Constant* org = lstr ? lstr : rstr) + { str->quote_mark(org->quote_mark()); } + } + return ex; + } Expression* Eval::operator()(Unary_Expression* u) @@ -403,7 +414,7 @@ namespace Sass { return result; } else { - To_String to_string; + To_String to_string(&ctx); // Special cases: +/- variables which evaluate to null ouput just +/-, // but +/- null itself outputs the string if (operand->concrete_type() == Expression::NULL_VAL && typeid(*(u->operand())) == typeid(Variable)) { @@ -438,7 +449,7 @@ namespace Sass { Function_Call* lit = new (ctx.mem) Function_Call(c->pstate(), c->name(), args); - To_String to_string; + To_String to_string(&ctx); return new (ctx.mem) String_Constant(c->pstate(), lit->perform(&to_string)); } @@ -589,7 +600,7 @@ namespace Sass { Expression* Eval::operator()(Variable* v) { - To_String to_string; + To_String to_string(&ctx); string name(v->name()); Expression* value = 0; if (env->has(name)) value = static_cast((*env)[name]); @@ -603,7 +614,11 @@ namespace Sass { static_cast(value)->zero(true); } else if (value->concrete_type() == Expression::STRING) { - value = new (ctx.mem) String_Constant(*static_cast(value)); + if (auto str = dynamic_cast(value)) { + value = new (ctx.mem) String_Quoted(*str); + } else if (auto str = dynamic_cast(value)) { + value = new (ctx.mem) String_Constant(*str); + } } else if (value->concrete_type() == Expression::LIST) { value = new (ctx.mem) List(*static_cast(value)); @@ -711,28 +726,91 @@ namespace Sass { } } + string Eval::interpolation(Expression* s) { + + if (String_Quoted* str_quoted = dynamic_cast(s)) { + + if (str_quoted->quote_mark()) { + return string_escape(str_quoted->value()); + } else { + return evacuate_escapes(str_quoted->value()); + } + + } else if (String_Constant* str_constant = dynamic_cast(s)) { + + return evacuate_escapes(str_constant->value()); + + } else if (String_Schema* str_schema = dynamic_cast(s)) { + + string res = ""; + for(auto i : str_schema->elements()) + res += (interpolation(i)); + //ToDo: do this in one step + auto esc = evacuate_escapes(res); + auto unq = unquote(esc); + if (unq == esc) { + return string_to_output(res); + } else { + return evacuate_quotes(unq); + } + + } else if (List* list = dynamic_cast(s)) { + + string acc = ""; // ToDo: different output styles + string sep = list->separator() == List::Separator::COMMA ? "," : " "; + if (ctx.output_style != COMPRESSED && sep == ",") sep += " "; + bool initial = false; + for(auto item : list->elements()) { + if (initial) acc += sep; + acc += interpolation(item); + initial = true; + } + return evacuate_quotes(acc); + + } else if (Variable* var = dynamic_cast(s)) { + + string name(var->name()); + if (!env->has(name)) return name; + Expression* value = static_cast((*env)[name]); + return evacuate_quotes(interpolation(value)); + + } else if (Binary_Expression* var = dynamic_cast(s)) { + + Expression* ex = operator()(var); + return evacuate_quotes(interpolation(ex)); + + } else if (Function_Call* var = dynamic_cast(s)) { + + Expression* ex = operator()(var); + return evacuate_quotes(interpolation(ex)); + + } else { + + To_String to_string(&ctx); + // to_string.in_decl_list = true; + return evacuate_quotes(s->perform(&to_string)); + + } + } + Expression* Eval::operator()(String_Schema* s) { string acc; - // ctx._skip_source_map_update = true; - To_String to_string(&ctx); - // ctx._skip_source_map_update = false; for (size_t i = 0, L = s->length(); i < L; ++i) { - string chunk((*s)[i]->perform(this)->perform(&to_string)); - if (((s->quote_mark() && is_quoted(chunk)) || !s->quote_mark()) && (*s)[i]->is_interpolant()) { // some redundancy in that test - acc += unquote(chunk); - } - else { - acc += chunk; - } + acc += interpolation((*s)[i]); + } + String_Quoted* str = new (ctx.mem) String_Quoted(s->pstate(), acc); + if (!str->quote_mark()) { + str->value(string_unescape(str->value())); + } else if (str->quote_mark()) { + str->quote_mark('*'); } - return new (ctx.mem) String_Constant(s->pstate(), - acc); + return str; } Expression* Eval::operator()(String_Constant* s) { - if (!s->is_delayed() && ctx.names_to_colors.count(s->value())) { + if (!s->quote_mark() && !s->is_delayed() && ctx.names_to_colors.count(s->value())) { Color* c = new (ctx.mem) Color(*ctx.names_to_colors[s->value()]); c->pstate(s->pstate()); c->disp(s->value()); @@ -783,7 +861,7 @@ namespace Sass { Expression* Eval::operator()(Media_Query* q) { - To_String to_string; + To_String to_string(&ctx); String* t = q->media_type(); t = static_cast(t ? t->perform(this) : 0); Media_Query* qq = new (ctx.mem) Media_Query(q->pstate(), @@ -1019,8 +1097,9 @@ namespace Sass { case Binary_Expression::SUB: case Binary_Expression::DIV: { string sep(op == Binary_Expression::SUB ? "-" : "/"); - To_String to_string; - string color(r->sixtuplet() ? r->perform(&to_string) : + To_String to_string(&ctx); + string color(r->sixtuplet() && (ctx.output_style != COMPRESSED) ? + r->perform(&to_string) : Util::normalize_sixtuplet(r->perform(&to_string))); return new (ctx.mem) String_Constant(l->pstate(), l->perform(&to_string) @@ -1069,20 +1148,18 @@ namespace Sass { Expression* op_strings(Context& ctx, Binary_Expression::Type op, Expression* lhs, Expression*rhs) { - To_String to_string; + To_String to_string(&ctx); Expression::Concrete_Type ltype = lhs->concrete_type(); Expression::Concrete_Type rtype = rhs->concrete_type(); string lstr(lhs->perform(&to_string)); string rstr(rhs->perform(&to_string)); - bool l_str_quoted = ((Sass::String*)lhs) && ((Sass::String*)lhs)->needs_unquoting(); - bool r_str_quoted = ((Sass::String*)rhs) && ((Sass::String*)rhs)->needs_unquoting(); + bool l_str_quoted = ((Sass::String*)lhs) && ((Sass::String*)lhs)->sass_fix_1291(); + bool r_str_quoted = ((Sass::String*)rhs) && ((Sass::String*)rhs)->sass_fix_1291(); bool l_str_color = ltype == Expression::STRING && ctx.names_to_colors.count(lstr) && !l_str_quoted; bool r_str_color = rtype == Expression::STRING && ctx.names_to_colors.count(rstr) && !r_str_quoted; - bool unquoted = false; - if (ltype == Expression::STRING && lstr[0] != '"' && lstr[0] != '\'') unquoted = true; if (l_str_color && r_str_color) { return op_colors(ctx, op, ctx.names_to_colors[lstr], ctx.names_to_colors[rstr]); } @@ -1108,12 +1185,10 @@ namespace Sass { } if (ltype == Expression::NULL_VAL) error("invalid null operation: \"null plus "+quote(unquote(rstr), '"')+"\".", lhs->pstate()); if (rtype == Expression::NULL_VAL) error("invalid null operation: \""+quote(unquote(lstr), '"')+" plus null\".", lhs->pstate()); - char q = '\0'; - if (lstr[0] == '"' || lstr[0] == '\'') q = lstr[0]; - else if (rstr[0] == '"' || rstr[0] == '\'') q = rstr[0]; - string result(unquote(lstr) + sep + unquote(rstr)); - return new (ctx.mem) String_Constant(lhs->pstate(), - unquoted ? result : quote(result, q)); + string result((lstr) + sep + (rstr)); + String_Quoted* str = new (ctx.mem) String_Quoted(lhs->pstate(), result); + str->quote_mark(0); + return str; } Expression* cval_to_astnode(Sass_Value* v, Context& ctx, Backtrace* backtrace, ParserState pstate) diff --git a/eval.hpp b/eval.hpp index e3a2fed59c..c902687de5 100644 --- a/eval.hpp +++ b/eval.hpp @@ -3,6 +3,7 @@ #include +#include "context.hpp" #include "position.hpp" #include "operation.hpp" #include "environment.hpp" @@ -11,7 +12,6 @@ namespace Sass { using namespace std; - struct Context; typedef Environment Env; struct Backtrace; @@ -65,6 +65,10 @@ namespace Sass { template Expression* fallback(U x) { return fallback_impl(x); } + + private: + string interpolation(Expression* s); + }; Expression* cval_to_astnode(Sass_Value* v, Context& ctx, Backtrace* backtrace, ParserState pstate = ParserState("[AST]")); diff --git a/expand.cpp b/expand.cpp index da485539e4..a8a30de67e 100644 --- a/expand.cpp +++ b/expand.cpp @@ -62,8 +62,10 @@ namespace Sass { contextual = contextualize->with(at_root_selector_stack.back(), env, backtrace); Selector* sel_ctx = r->selector()->perform(contextual); + if (sel_ctx == 0) throw "Cannot expand null selector"; - Inspect isp(0); + Emitter emitter(&ctx); + Inspect isp(emitter); sel_ctx->perform(&isp); string str = isp.get_buffer(); str += ";"; @@ -73,17 +75,17 @@ namespace Sass { p.position = str.c_str(); p.end = str.c_str() + strlen(str.c_str()); Selector_List* sel_lst = p.parse_selector_group(); - sel_lst->pstate(isp.source_map.remap(sel_lst->pstate())); + sel_lst->pstate(isp.remap(sel_lst->pstate())); for(size_t i = 0; i < sel_lst->length(); i++) { Complex_Selector* pIter = (*sel_lst)[i]; while (pIter) { Compound_Selector* pHead = pIter->head(); - pIter->pstate(isp.source_map.remap(pIter->pstate())); + pIter->pstate(isp.remap(pIter->pstate())); if (pHead) { - pHead->pstate(isp.source_map.remap(pHead->pstate())); - (*pHead)[0]->pstate(isp.source_map.remap((*pHead)[0]->pstate())); + pHead->pstate(isp.remap(pHead->pstate())); + (*pHead)[0]->pstate(isp.remap((*pHead)[0]->pstate())); } pIter = pIter->tail(); } @@ -91,9 +93,11 @@ namespace Sass { sel_ctx = sel_lst; selector_stack.push_back(sel_ctx); + Block* blk = r->block()->perform(this)->block(); Ruleset* rr = new (ctx.mem) Ruleset(r->pstate(), sel_ctx, - r->block()->perform(this)->block()); + blk); + rr->tabs(r->tabs()); selector_stack.pop_back(); in_at_root = old_in_at_root; old_in_at_root = false; @@ -147,13 +151,14 @@ namespace Sass { Statement* Expand::operator()(Media_Block* m) { - To_String to_string; + To_String to_string(&ctx); Expression* mq = m->media_queries()->perform(eval->with(env, backtrace)); mq = Parser::from_c_str(mq->perform(&to_string).c_str(), ctx, mq->pstate()).parse_media_queries(); Media_Block* mm = new (ctx.mem) Media_Block(m->pstate(), static_cast(mq), m->block()->perform(this)->block(), selector_stack.back()); + mm->tabs(m->tabs()); return mm; } @@ -201,10 +206,12 @@ namespace Sass { if (value->is_invisible() && !d->is_important()) return 0; - return new (ctx.mem) Declaration(d->pstate(), - new_p, - value, - d->is_important()); + Declaration* decl = new (ctx.mem) Declaration(d->pstate(), + new_p, + value, + d->is_important()); + decl->tabs(d->tabs()); + return decl; } Statement* Expand::operator()(Assignment* a) @@ -259,7 +266,7 @@ namespace Sass { Statement* Expand::operator()(Comment* c) { // TODO: eval the text, once we're parsing/storing it as a String_Schema - return new (ctx.mem) Comment(c->pstate(), static_cast(c->text()->perform(eval->with(env, backtrace)))); + return new (ctx.mem) Comment(c->pstate(), static_cast(c->text()->perform(eval->with(env, backtrace))), c->is_important()); } Statement* Expand::operator()(If* i) @@ -393,7 +400,7 @@ namespace Sass { Statement* Expand::operator()(Extension* e) { - To_String to_string; + To_String to_string(&ctx); Selector_List* extender = static_cast(selector_stack.back()); if (!extender) return 0; Selector_List* extendee = static_cast(e->selector()->perform(contextualize->with(0, env, backtrace))); diff --git a/expand.hpp b/expand.hpp index e6afa35331..e6962f7a84 100644 --- a/expand.hpp +++ b/expand.hpp @@ -6,15 +6,14 @@ #include #include "ast.hpp" +#include "eval.hpp" #include "operation.hpp" #include "environment.hpp" +#include "contextualize.hpp" namespace Sass { using namespace std; - struct Context; - class Eval; - class Contextualize; typedef Environment Env; struct Backtrace; diff --git a/extend.cpp b/extend.cpp index 566568ddb3..812d5e4c60 100644 --- a/extend.cpp +++ b/extend.cpp @@ -1594,6 +1594,7 @@ namespace Sass { #endif + if (pSelector && pSelector->has_line_feed()) pNewSelector->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, ctx)) @@ -1701,6 +1702,8 @@ namespace Sass { ExtensionSubsetMap& subsetMap, set seen) { + pComplexSelector->tail()->has_line_feed(pComplexSelector->has_line_feed()); + Node complexSelector = complexSelectorToNode(pComplexSelector, ctx); DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector) @@ -1806,7 +1809,7 @@ namespace Sass { */ static Selector_List* extendSelectorList(Selector_List* pSelectorList, Context& ctx, ExtensionSubsetMap& subsetMap, bool& extendedSomething) { - To_String to_string; + To_String to_string(&ctx); Selector_List* pNewSelectors = new (ctx.mem) Selector_List(pSelectorList->pstate(), pSelectorList->length()); @@ -1881,7 +1884,7 @@ namespace Sass { // Extend a ruleset by extending the selectors and updating them on the ruleset. The block's rules don't need to change. template static void extendObjectWithSelectorAndBlock(ObjectType* pObject, Context& ctx, ExtensionSubsetMap& subsetMap) { - To_String to_string; + To_String to_string(&ctx); DEBUG_PRINTLN(EXTEND_OBJECT, "FOUND SELECTOR: " << static_cast(pObject->selector())->perform(&to_string)) diff --git a/functions.cpp b/functions.cpp index 63f75e8498..dc33586601 100644 --- a/functions.cpp +++ b/functions.cpp @@ -754,21 +754,21 @@ namespace Sass { Signature unquote_sig = "unquote($string)"; BUILT_IN(sass_unquote) { - To_String to_string; + To_String to_string(&ctx); AST_Node* arg = env["$string"]; - string org(arg->perform(&to_string)); - string str(unquote(org)); - String_Constant* result = new (ctx.mem) String_Constant(pstate, str); - // remember if the string was quoted (color tokens) - if (org[0] != str[0]) result->needs_unquoting(true); - result->is_delayed(true); - return result; + if (String_Quoted* string_quoted = dynamic_cast(arg)) { + String_Constant* result = new (ctx.mem) String_Constant(pstate, string_quoted->value()); + // remember if the string was quoted (color tokens) + result->sass_fix_1291(string_quoted->quote_mark()); + return result; + } + return new (ctx.mem) String_Constant(pstate, string(arg->perform(&to_string))); } Signature quote_sig = "quote($string)"; BUILT_IN(sass_quote) { - To_String to_string; + To_String to_string(&ctx); AST_Node* arg = env["$string"]; string str(quote(arg->perform(&to_string), String_Constant::double_quote())); String_Constant* result = new (ctx.mem) String_Constant(pstate, str); @@ -783,16 +783,7 @@ namespace Sass { size_t len = string::npos; try { String_Constant* s = ARG("$string", String_Constant); - string str = s->value(); - size_t length_of_s = str.size(); - size_t i = 0; - - if (s->is_quoted()) { - ++i; - --length_of_s; - } - - len = UTF_8::code_point_count(str, i, length_of_s); + len = UTF_8::code_point_count(s->value(), 0, s->value().size()); } catch (utf8::invalid_code_point) { @@ -819,7 +810,6 @@ namespace Sass { try { String_Constant* s = ARG("$string", String_Constant); str = s->value(); - char quotemark = s->quote_mark(); str = unquote(str); String_Constant* i = ARG("$insert", String_Constant); string ins = i->value(); @@ -849,8 +839,8 @@ namespace Sass { str = ins + str; } - if (quotemark) { - str = quote(str, String_Constant::double_quote()); + if (String_Quoted* ss = dynamic_cast(s)) { + if (ss->quote_mark()) str = quote(str); } } catch (utf8::invalid_code_point) { @@ -913,9 +903,7 @@ namespace Sass { Number* n = ARG("$start-at", Number); Number* m = ARG("$end-at", Number); - string str = s->value(); - char quotemark = s->quote_mark(); - str = unquote(str); + string str = unquote(s->value()); // normalize into 0-based indices size_t start = UTF_8::offset_at_position(str, UTF_8::normalize_index(n->value(), UTF_8::code_point_count(str))); @@ -924,15 +912,19 @@ namespace Sass { // `str-slice` should always return an empty string when $end-at == 0 // `normalize_index` normalizes 1 -> 0 so we need to check the original value if(m->value() == 0) { - if(!quotemark) return new (ctx.mem) Null(pstate); + if (String_Quoted* ss = dynamic_cast(s)) { + if(!ss->quote_mark()) return new (ctx.mem) Null(pstate); + } else { + return new (ctx.mem) Null(pstate); + } newstr = ""; } else if(start == end && m->value() != 0) { newstr = str.substr(start, 1); } else if(end > start) { newstr = str.substr(start, end - start + UTF_8::code_point_size_at_offset(str, end)); } - if(quotemark) { - newstr = quote(newstr, String_Constant::double_quote()); + if (String_Quoted* ss = dynamic_cast(s)) { + if(ss->quote_mark()) newstr = quote(newstr); } } catch (utf8::invalid_code_point) { @@ -963,6 +955,9 @@ namespace Sass { } } + if (String_Quoted* ss = dynamic_cast(s)) { + str = ss->quote_mark() ? quote(str) : str; + } return new (ctx.mem) String_Constant(pstate, str); } @@ -978,6 +973,9 @@ namespace Sass { } } + if (String_Quoted* ss = dynamic_cast(s)) { + str = ss->quote_mark() ? quote(str, '"') : str; + } return new (ctx.mem) String_Constant(pstate, str); } @@ -1362,7 +1360,7 @@ namespace Sass { { Expression* v = ARG("$value", Expression); if (v->concrete_type() == Expression::STRING) { - To_String to_string; + To_String to_string(&ctx); string str(v->perform(&to_string)); if (ctx.names_to_colors.count(str)) { return new (ctx.mem) String_Constant(pstate, "color"); @@ -1373,7 +1371,7 @@ namespace Sass { Signature unit_sig = "unit($number)"; BUILT_IN(unit) - { return new (ctx.mem) String_Constant(pstate, quote(ARG("$number", Number)->unit(), '"')); } + { return new (ctx.mem) String_Quoted(pstate, quote(ARG("$number", Number)->unit(), '"')); } Signature unitless_sig = "unitless($number)"; BUILT_IN(unitless) @@ -1520,8 +1518,20 @@ namespace Sass { return new (ctx.mem) String_Constant(pstate, "null"); } else if (v->concrete_type() == Expression::BOOLEAN && *v == 0) { return new (ctx.mem) String_Constant(pstate, "false"); + } else if (v->concrete_type() == Expression::STRING) { + return v; + } else { + Output_Style old_style; + old_style = ctx.output_style; + ctx.output_style = NESTED; + To_String to_string(&ctx); + string inspect = v->perform(&to_string); + ctx.output_style = old_style; + return new (ctx.mem) String_Constant(pstate, inspect); + + } - return v; + // return v; } Signature unique_id_sig = "unique-id()"; diff --git a/inspect.cpp b/inspect.cpp index d58dd3ce74..2fd3d236b3 100644 --- a/inspect.cpp +++ b/inspect.cpp @@ -6,16 +6,14 @@ #include #include #include +#include +#include namespace Sass { using namespace std; - Inspect::Inspect(Context* ctx) - : buffer(""), - indentation(0), - ctx(ctx), - in_declaration(false), - in_declaration_list(false) + Inspect::Inspect(Emitter emi) + : Emitter(emi) { } Inspect::~Inspect() { } @@ -23,30 +21,19 @@ namespace Sass { void Inspect::operator()(Block* block) { if (!block->is_root()) { - append_to_buffer(" {" + ctx->linefeed); - ++ indentation; + add_open_mapping(block); + append_scope_opener(); } + if (output_style() == NESTED) indentation += block->tabs(); for (size_t i = 0, L = block->length(); i < L; ++i) { - append_indent_to_buffer(); (*block)[i]->perform(this); - // extra newline at the end of top-level statements - if (block->is_root()) append_to_buffer(ctx->linefeed); - append_to_buffer(ctx->linefeed); } + if (output_style() == NESTED) indentation -= block->tabs(); if (!block->is_root()) { - -- indentation; - append_indent_to_buffer(); - append_to_buffer("}"); - } - // remove extra newline that gets added after the last top-level block - if (block->is_root()) { - size_t l = buffer.length(); - if (l > 2 && buffer[l-1] == '\n' && buffer[l-2] == '\n') { - buffer.erase(l-1); - if (ctx) ctx->source_map.remove_line(); - source_map.remove_line(); - } + append_scope_closer(); + add_close_mapping(block); } + } void Inspect::operator()(Ruleset* ruleset) @@ -57,6 +44,7 @@ namespace Sass { void Inspect::operator()(Keyframe_Rule* rule) { + append_indentation(); if (rule->rules()) rule->rules()->perform(this); rule->block()->perform(this); } @@ -64,50 +52,65 @@ namespace Sass { void Inspect::operator()(Propset* propset) { propset->property_fragment()->perform(this); - append_to_buffer(": "); + append_colon_separator(); propset->block()->perform(this); } void Inspect::operator()(Bubble* bubble) { - append_to_buffer("Bubble ( "); + append_indentation(); + append_token("Bubble", bubble); + append_optional_space(); + append_string("("); + append_optional_space(); bubble->node()->perform(this); - append_to_buffer(" )"); + append_optional_space(); + append_string(")"); + append_optional_space(); } void Inspect::operator()(Media_Block* media_block) { - append_to_buffer("@media", media_block, " "); + append_indentation(); + append_token("@media", media_block); + append_mandatory_space(); + in_media_block = true; media_block->media_queries()->perform(this); + in_media_block = false; media_block->block()->perform(this); } void Inspect::operator()(Feature_Block* feature_block) { - append_to_buffer("@supports", feature_block, " "); + append_indentation(); + append_token("@supports", feature_block); + append_mandatory_space(); feature_block->feature_queries()->perform(this); feature_block->block()->perform(this); } void Inspect::operator()(At_Root_Block* at_root_block) { - append_to_buffer("@at-root ", at_root_block, " "); + append_indentation(); + append_token("@at-root ", at_root_block); + append_mandatory_space(); if(at_root_block->expression()) at_root_block->expression()->perform(this); at_root_block->block()->perform(this); } void Inspect::operator()(At_Rule* at_rule) { - append_to_buffer(at_rule->keyword()); + append_indentation(); + append_token(at_rule->keyword(), at_rule); if (at_rule->selector()) { - append_to_buffer(" "); + append_mandatory_space(); at_rule->selector()->perform(this); } if (at_rule->block()) { at_rule->block()->perform(this); } else { - append_to_buffer(";"); + append_delimiter(); } } @@ -115,166 +118,210 @@ namespace Sass { { if (dec->value()->concrete_type() == Expression::NULL_VAL) return; in_declaration = true; - if (ctx) ctx->source_map.add_open_mapping(dec->property()); - source_map.add_open_mapping(dec->property()); + if (output_style() == NESTED) + indentation += dec->tabs(); + append_indentation(); dec->property()->perform(this); - if (ctx) ctx->source_map.add_close_mapping(dec->property()); - source_map.add_close_mapping(dec->property()); - append_to_buffer(": "); - if (ctx) ctx->source_map.add_open_mapping(dec->value()); - source_map.add_open_mapping(dec->value()); + append_colon_separator(); dec->value()->perform(this); - if (dec->is_important()) append_to_buffer(" !important"); - if (ctx) ctx->source_map.add_close_mapping(dec->value()); - source_map.add_close_mapping(dec->value()); - append_to_buffer(";"); + if (dec->is_important()) { + append_optional_space(); + append_string("!important"); + } + append_delimiter(); + if (output_style() == NESTED) + indentation -= dec->tabs(); in_declaration = false; } void Inspect::operator()(Assignment* assn) { - append_to_buffer(assn->variable()); - append_to_buffer(": "); + append_token(assn->variable(), assn); + append_colon_separator(); assn->value()->perform(this); - if (assn->is_guarded()) append_to_buffer(" !default"); - append_to_buffer(";"); + if (assn->is_guarded()) { + append_optional_space(); + append_string("!default"); + } + append_delimiter(); } void Inspect::operator()(Import* import) { if (!import->urls().empty()) { - append_to_buffer("@import", import, " "); + append_token("@import", import); + append_mandatory_space(); + + if (String_Quoted* strq = dynamic_cast(import->urls().front())) { + strq->is_delayed(false); + } + import->urls().front()->perform(this); - append_to_buffer(";"); + append_delimiter(); for (size_t i = 1, S = import->urls().size(); i < S; ++i) { - append_to_buffer(ctx->linefeed); - append_to_buffer("@import", import, " "); + append_mandatory_linefeed(); + append_token("@import", import); + append_mandatory_space(); + + if (String_Quoted* strq = dynamic_cast(import->urls()[i])) { + strq->is_delayed(false); + } + import->urls()[i]->perform(this); - append_to_buffer(";"); + append_delimiter(); } } } void Inspect::operator()(Import_Stub* import) { - append_to_buffer("@import", import, " "); - append_to_buffer(import->file_name()); - append_to_buffer(";"); + append_indentation(); + append_token("@import", import); + append_mandatory_space(); + append_string(import->file_name()); + append_delimiter(); } void Inspect::operator()(Warning* warning) { - append_to_buffer("@warn", warning, " "); + append_indentation(); + append_token("@warn", warning); + append_mandatory_space(); warning->message()->perform(this); - append_to_buffer(";"); + append_delimiter(); } void Inspect::operator()(Error* error) { - append_to_buffer("@error", error, " "); + append_indentation(); + append_token("@error", error); + append_mandatory_space(); error->message()->perform(this); - append_to_buffer(";"); + append_delimiter(); } void Inspect::operator()(Debug* debug) { - append_to_buffer("@debug", debug, " "); + append_indentation(); + append_token("@debug", debug); + append_mandatory_space(); debug->value()->perform(this); - append_to_buffer(";"); + append_delimiter(); } void Inspect::operator()(Comment* comment) { + in_comment = true; comment->text()->perform(this); + in_comment = false; } void Inspect::operator()(If* cond) { - append_to_buffer("@if", cond, " "); + append_indentation(); + append_token("@if", cond); + append_mandatory_space(); cond->predicate()->perform(this); cond->consequent()->perform(this); if (cond->alternative()) { - append_to_buffer(ctx->linefeed); - append_indent_to_buffer(); - append_to_buffer("else"); + append_optional_linefeed(); + append_indentation(); + append_string("else"); cond->alternative()->perform(this); } } void Inspect::operator()(For* loop) { - append_to_buffer("@for", loop, " "); - append_to_buffer(loop->variable()); - append_to_buffer(" from "); + append_indentation(); + append_token("@for", loop); + append_mandatory_space(); + append_string(loop->variable()); + append_string(" from "); loop->lower_bound()->perform(this); - append_to_buffer((loop->is_inclusive() ? " through " : " to ")); + append_string(loop->is_inclusive() ? " through " : " to "); loop->upper_bound()->perform(this); loop->block()->perform(this); } void Inspect::operator()(Each* loop) { - append_to_buffer("@each", loop, " "); - append_to_buffer(loop->variables()[0]); + append_indentation(); + append_token("@each", loop); + append_mandatory_space(); + append_string(loop->variables()[0]); for (size_t i = 1, L = loop->variables().size(); i < L; ++i) { - append_to_buffer(", "); - append_to_buffer(loop->variables()[i]); + append_comma_separator(); + append_string(loop->variables()[i]); } - append_to_buffer(" in "); + append_string(" in "); loop->list()->perform(this); loop->block()->perform(this); } void Inspect::operator()(While* loop) { - append_to_buffer("@while", loop, " "); + append_indentation(); + append_token("@while", loop); + append_mandatory_space(); loop->predicate()->perform(this); loop->block()->perform(this); } void Inspect::operator()(Return* ret) { - append_to_buffer("@return", ret, " "); + append_indentation(); + append_token("@return", ret); + append_mandatory_space(); ret->value()->perform(this); - append_to_buffer(";"); + append_delimiter(); } void Inspect::operator()(Extension* extend) { - append_to_buffer("@extend", extend, " "); + append_indentation(); + append_token("@extend", extend); + append_mandatory_space(); extend->selector()->perform(this); - append_to_buffer(";"); + append_delimiter(); } void Inspect::operator()(Definition* def) { + append_indentation(); if (def->type() == Definition::MIXIN) { - append_to_buffer("@mixin", def, " "); + append_token("@mixin", def); + append_mandatory_space(); } else { - append_to_buffer("@function", def, " "); + append_token("@function", def); + append_mandatory_space(); } - append_to_buffer(def->name()); + append_string(def->name()); def->parameters()->perform(this); def->block()->perform(this); } void Inspect::operator()(Mixin_Call* call) { - append_to_buffer("@include", call, " "); - append_to_buffer(call->name()); + append_indentation(); + append_token("@include", call); + append_mandatory_space(); + append_string(call->name()); if (call->arguments()) { call->arguments()->perform(this); } if (call->block()) { - append_to_buffer(" "); + append_optional_space(); call->block()->perform(this); } - if (!call->block()) append_to_buffer(";"); + if (!call->block()) append_delimiter(); } void Inspect::operator()(Content* content) { - append_to_buffer("@content", content, ";"); + append_indentation(); + append_token("@content", content); + append_delimiter(); } void Inspect::operator()(Map* map) @@ -282,22 +329,24 @@ namespace Sass { if (map->empty()) return; if (map->is_invisible()) return; bool items_output = false; - append_to_buffer("("); + append_string("("); for (auto key : map->keys()) { if (key->is_invisible()) continue; if (map->at(key)->is_invisible()) continue; - if (items_output) append_to_buffer(", "); + if (items_output) append_comma_separator(); key->perform(this); - append_to_buffer(": "); + append_colon_separator(); map->at(key)->perform(this); items_output = true; } - append_to_buffer(")"); + append_string(")"); } void Inspect::operator()(List* list) { - string sep(list->separator() == List::SPACE ? " " : ", "); + string sep(list->separator() == List::SPACE ? " " : ","); + if (output_style() != COMPRESSED && sep == ",") sep += " "; + else if (in_media_block && sep != " ") sep += " "; // verified if (list->empty()) return; bool items_output = false; in_declaration_list = in_declaration; @@ -306,7 +355,11 @@ namespace Sass { if (list_item->is_invisible()) { continue; } - if (items_output) append_to_buffer(sep); + if (items_output) { + append_string(sep); + } + if (items_output && sep != " ") + append_optional_space(); list_item->perform(this); items_output = true; } @@ -317,19 +370,19 @@ namespace Sass { { expr->left()->perform(this); switch (expr->type()) { - case Binary_Expression::AND: append_to_buffer(" and "); break; - case Binary_Expression::OR: append_to_buffer(" or "); break; - case Binary_Expression::EQ: append_to_buffer(" == "); break; - case Binary_Expression::NEQ: append_to_buffer(" != "); break; - case Binary_Expression::GT: append_to_buffer(" > "); break; - case Binary_Expression::GTE: append_to_buffer(" >= "); break; - case Binary_Expression::LT: append_to_buffer(" < "); break; - case Binary_Expression::LTE: append_to_buffer(" <= "); break; - case Binary_Expression::ADD: append_to_buffer(" + "); break; - case Binary_Expression::SUB: append_to_buffer(" - "); break; - case Binary_Expression::MUL: append_to_buffer(" * "); break; - case Binary_Expression::DIV: append_to_buffer("/"); break; - case Binary_Expression::MOD: append_to_buffer(" % "); break; + case Binary_Expression::AND: append_string(" and "); break; + case Binary_Expression::OR: append_string(" or "); break; + case Binary_Expression::EQ: append_string(" == "); break; + case Binary_Expression::NEQ: append_string(" != "); break; + case Binary_Expression::GT: append_string(" > "); break; + case Binary_Expression::GTE: append_string(" >= "); break; + case Binary_Expression::LT: append_string(" < "); break; + case Binary_Expression::LTE: append_string(" <= "); break; + case Binary_Expression::ADD: append_string(" + "); break; + case Binary_Expression::SUB: append_string(" - "); break; + case Binary_Expression::MUL: append_string(" * "); break; + case Binary_Expression::DIV: append_string("/"); break; + case Binary_Expression::MOD: append_string(" % "); break; default: break; // shouldn't get here } expr->right()->perform(this); @@ -337,14 +390,14 @@ namespace Sass { void Inspect::operator()(Unary_Expression* expr) { - if (expr->type() == Unary_Expression::PLUS) append_to_buffer("+"); - else append_to_buffer("-"); + if (expr->type() == Unary_Expression::PLUS) append_string("+"); + else append_string("-"); expr->operand()->perform(this); } void Inspect::operator()(Function_Call* call) { - append_to_buffer(call->name()); + append_token(call->name(), call); call->arguments()->perform(this); } @@ -356,34 +409,13 @@ namespace Sass { void Inspect::operator()(Variable* var) { - append_to_buffer(var->name()); + append_token(var->name(), var); } void Inspect::operator()(Textual* txt) { - append_to_buffer(txt->value()); - } - - // helper functions for serializing numbers - // string frac_to_string(double f, size_t p) { - // stringstream ss; - // ss.setf(ios::fixed, ios::floatfield); - // ss.precision(p); - // ss << f; - // string result(ss.str().substr(f < 0 ? 2 : 1)); - // size_t i = result.size() - 1; - // while (result[i] == '0') --i; - // result = result.substr(0, i+1); - // return result; - // } - // string double_to_string(double d, size_t p) { - // stringstream ss; - // double ipart; - // double fpart = std::modf(d, &ipart); - // ss << ipart; - // if (fpart != 0) ss << frac_to_string(fpart, 5); - // return ss.str(); - // } + append_token(txt->value(), txt); + } void Inspect::operator()(Number* n) { @@ -416,8 +448,7 @@ namespace Sass { // a value before it got truncated if (d == "0" && nonzero) d = "0.0"; // append number and unit - append_to_buffer(d); - append_to_buffer(n->unit()); + append_token(d + n->unit(), n); } // helper function for serializing colors @@ -431,47 +462,85 @@ namespace Sass { void Inspect::operator()(Color* c) { stringstream ss; + + // check if we prefer short hex colors + bool want_short = output_style() == COMPRESSED; + + // original color name + // maybe an unknown token + string name = c->disp(); + + // resolved color + string res_name = name; + double r = round(cap_channel<0xff>(c->r())); double g = round(cap_channel<0xff>(c->g())); double b = round(cap_channel<0xff>(c->b())); double a = cap_channel<1> (c->a()); + // get color from given name (if one was given at all) + if (name != "" && ctx && ctx->names_to_colors.count(name)) { + Color* n = ctx->names_to_colors[name]; + r = round(cap_channel<0xff>(n->r())); + g = round(cap_channel<0xff>(n->g())); + b = round(cap_channel<0xff>(n->b())); + a = cap_channel<1> (n->a()); + } + // otherwise get the possible resolved color name + else { + int numval = r * 0x10000 + g * 0x100 + b; + if (ctx && ctx->colors_to_names.count(numval)) + res_name = ctx->colors_to_names[numval]; + } + + stringstream hexlet; + hexlet << '#' << setw(1) << setfill('0'); + // create a short color hexlet if there is any need for it + if (want_short && is_color_doublet(r, g, b) && a == 1) { + hexlet << hex << setw(1) << (static_cast(r) >> 4); + hexlet << hex << setw(1) << (static_cast(g) >> 4); + hexlet << hex << setw(1) << (static_cast(b) >> 4); + } else { + hexlet << hex << setw(2) << static_cast(r); + hexlet << hex << setw(2) << static_cast(g); + hexlet << hex << setw(2) << static_cast(b); + } + // retain the originally specified color definition if unchanged - if (!c->disp().empty()) { - ss << c->disp(); + if (name != "") { + ss << name; } else if (r == 0 && g == 0 && b == 0 && a == 0) { ss << "transparent"; } else if (a >= 1) { - // see if it's a named color - int numval = r * 0x10000; - numval += g * 0x100; - numval += b; - if (ctx && ctx->colors_to_names.count(numval)) { - ss << ctx->colors_to_names[numval]; + if (res_name != "") { + if (want_short && hexlet.str().size() < res_name.size()) { + ss << hexlet.str(); + } else { + ss << res_name; + } } else { - // otherwise output the hex triplet - ss << '#' << setw(2) << setfill('0'); - ss << hex << setw(2) << static_cast(r); - ss << hex << setw(2) << static_cast(g); - ss << hex << setw(2) << static_cast(b); + ss << hexlet.str(); } } else { ss << "rgba("; - ss << static_cast(r) << ", "; - ss << static_cast(g) << ", "; - ss << static_cast(b) << ", "; + ss << static_cast(r) << ","; + if (output_style() != COMPRESSED) ss << " "; + ss << static_cast(g) << ","; + if (output_style() != COMPRESSED) ss << " "; + ss << static_cast(b) << ","; + if (output_style() != COMPRESSED) ss << " "; ss << a << ')'; } - append_to_buffer(ss.str()); + append_token(ss.str(), c); } void Inspect::operator()(Boolean* b) { - append_to_buffer(b->value() ? "true" : "false"); + append_token(b->value() ? "true" : "false", b); } void Inspect::operator()(String_Schema* ss) @@ -479,15 +548,27 @@ namespace Sass { // Evaluation should turn these into String_Constants, so this method is // only for inspection purposes. for (size_t i = 0, L = ss->length(); i < L; ++i) { - if ((*ss)[i]->is_interpolant()) append_to_buffer("#{"); + if ((*ss)[i]->is_interpolant()) append_string("#{"); (*ss)[i]->perform(this); - if ((*ss)[i]->is_interpolant()) append_to_buffer("}"); + if ((*ss)[i]->is_interpolant()) append_string("}"); } } void Inspect::operator()(String_Constant* s) { - append_to_buffer(s->needs_unquoting() ? unquote(s->value()) : s->value(), s); + if (String_Quoted* quoted = dynamic_cast(s)) { + return Inspect::operator()(quoted); + } + append_token(s->value(), s); + } + + void Inspect::operator()(String_Quoted* s) + { + if (s->quote_mark()) { + append_token(quote(s->value(), s->quote_mark()), s); + } else { + append_token(s->value(), s); + } } void Inspect::operator()(Feature_Query* fq) @@ -501,40 +582,46 @@ namespace Sass { void Inspect::operator()(Feature_Query_Condition* fqc) { - if (fqc->operand() == Feature_Query_Condition::AND) - append_to_buffer(" and "); - else if (fqc->operand() == Feature_Query_Condition::OR) - append_to_buffer(" or "); - else if (fqc->operand() == Feature_Query_Condition::NOT) - append_to_buffer(" not "); + if (fqc->operand() == Feature_Query_Condition::AND) { + append_mandatory_space(); + append_token("and", fqc); + append_mandatory_space(); + } else if (fqc->operand() == Feature_Query_Condition::OR) { + append_mandatory_space(); + append_token("or", fqc); + append_mandatory_space(); + } else if (fqc->operand() == Feature_Query_Condition::NOT) { + append_mandatory_space(); + append_token("not", fqc); + append_mandatory_space(); + } - if (!fqc->is_root()) append_to_buffer("("); + if (!fqc->is_root()) append_string("("); if (!fqc->length()) { fqc->feature()->perform(this); - append_to_buffer(": "); + append_string(": "); // verified fqc->value()->perform(this); } - // else for (size_t i = 0, L = fqc->length(); i < L; ++i) (*fqc)[i]->perform(this); - if (!fqc->is_root()) append_to_buffer(")"); + if (!fqc->is_root()) append_string(")"); } void Inspect::operator()(Media_Query* mq) { size_t i = 0; if (mq->media_type()) { - if (mq->is_negated()) append_to_buffer("not "); - else if (mq->is_restricted()) append_to_buffer("only "); + if (mq->is_negated()) append_string("not "); + else if (mq->is_restricted()) append_string("only "); mq->media_type()->perform(this); } else { (*mq)[i++]->perform(this); } for (size_t L = mq->length(); i < L; ++i) { - append_to_buffer(" and "); + append_string(" and "); (*mq)[i]->perform(this); } } @@ -542,28 +629,16 @@ namespace Sass { void Inspect::operator()(Media_Query_Expression* mqe) { if (mqe->is_interpolated()) { - if (ctx) ctx->source_map.add_open_mapping(mqe->feature()); - source_map.add_open_mapping(mqe->feature()); mqe->feature()->perform(this); - if (ctx) ctx->source_map.add_close_mapping(mqe->feature()); - source_map.add_close_mapping(mqe->feature()); } else { - append_to_buffer("("); - if (ctx) ctx->source_map.add_open_mapping(mqe->feature()); - source_map.add_open_mapping(mqe->feature()); + append_string("("); mqe->feature()->perform(this); - if (ctx) ctx->source_map.add_close_mapping(mqe->feature()); - source_map.add_close_mapping(mqe->feature()); if (mqe->value()) { - append_to_buffer(": "); - if (ctx) ctx->source_map.add_open_mapping(mqe->value()); - source_map.add_open_mapping(mqe->value()); + append_string(": "); // verified mqe->value()->perform(this); - if (ctx) ctx->source_map.add_close_mapping(mqe->value()); - source_map.add_close_mapping(mqe->value()); } - append_to_buffer(")"); + append_string(")"); } } @@ -573,52 +648,52 @@ namespace Sass { ae->feature()->perform(this); } else { - append_to_buffer("("); + append_string("("); ae->feature()->perform(this); if (ae->value()) { - append_to_buffer(": "); + append_colon_separator(); ae->value()->perform(this); } - append_to_buffer(")"); + append_string(")"); } } void Inspect::operator()(Null* n) { - append_to_buffer("null"); + append_token("null", n); } // parameters and arguments void Inspect::operator()(Parameter* p) { - append_to_buffer(p->name()); + append_token(p->name(), p); if (p->default_value()) { - append_to_buffer(": "); + append_colon_separator(); p->default_value()->perform(this); } else if (p->is_rest_parameter()) { - append_to_buffer("..."); + append_string("..."); } } void Inspect::operator()(Parameters* p) { - append_to_buffer("("); + append_string("("); if (!p->empty()) { (*p)[0]->perform(this); for (size_t i = 1, L = p->length(); i < L; ++i) { - append_to_buffer(", "); + append_comma_separator(); (*p)[i]->perform(this); } } - append_to_buffer(")"); + append_string(")"); } void Inspect::operator()(Argument* a) { if (!a->name().empty()) { - append_to_buffer(a->name()); - append_to_buffer(": "); + append_token(a->name(), a); + append_colon_separator(); } // Special case: argument nulls can be ignored if (a->value()->concrete_type() == Expression::NULL_VAL) { @@ -626,28 +701,27 @@ namespace Sass { } if (a->value()->concrete_type() == Expression::STRING) { String_Constant* s = static_cast(a->value()); - if (s->is_quoted()) s->value(quote(unquote(s->value()), String_Constant::double_quote())); s->perform(this); } else a->value()->perform(this); if (a->is_rest_argument()) { - append_to_buffer("..."); + append_string("..."); } } void Inspect::operator()(Arguments* a) { - append_to_buffer("("); + append_string("("); if (!a->empty()) { (*a)[0]->perform(this); for (size_t i = 1, L = a->length(); i < L; ++i) { - append_to_buffer(", "); + append_string(", "); // verified + // Sass Bug? append_comma_separator(); (*a)[i]->perform(this); } } - append_to_buffer(")"); + append_string(")"); } - // selectors void Inspect::operator()(Selector_Schema* s) { s->contents()->perform(this); @@ -656,56 +730,58 @@ namespace Sass { void Inspect::operator()(Selector_Reference* ref) { if (ref->selector()) ref->selector()->perform(this); - else append_to_buffer("&"); + else append_string("&"); } void Inspect::operator()(Selector_Placeholder* s) { - append_to_buffer(s->name(), s); + append_token(s->name(), s); + if (s->has_line_break()) append_optional_linefeed(); + if (s->has_line_break()) append_indentation(); + } void Inspect::operator()(Type_Selector* s) { - append_to_buffer(s->name(), s); + append_token(s->name(), s); } void Inspect::operator()(Selector_Qualifier* s) { - append_to_buffer(s->name(), s); + append_token(s->name(), s); + if (s->has_line_break()) append_optional_linefeed(); + if (s->has_line_break()) append_indentation(); } void Inspect::operator()(Attribute_Selector* s) { - append_to_buffer("["); - if (ctx) ctx->source_map.add_open_mapping(s); - source_map.add_open_mapping(s); - append_to_buffer(s->name()); + append_string("["); + add_open_mapping(s); + append_token(s->name(), s); if (!s->matcher().empty()) { - append_to_buffer(s->matcher()); + append_string(s->matcher()); if (s->value()) { s->value()->perform(this); } - // append_to_buffer(s->value()); } - if (ctx) ctx->source_map.add_close_mapping(s); - source_map.add_close_mapping(s); - append_to_buffer("]"); + add_close_mapping(s); + append_string("]"); } void Inspect::operator()(Pseudo_Selector* s) { - append_to_buffer(s->name(), s); + append_token(s->name(), s); if (s->expression()) { s->expression()->perform(this); - append_to_buffer(")"); + append_string(")"); } } void Inspect::operator()(Wrapped_Selector* s) { - append_to_buffer(s->name(), s); + append_token(s->name(), s); s->selector()->perform(this); - append_to_buffer(")"); + append_string(")"); } void Inspect::operator()(Compound_Selector* s) @@ -713,6 +789,9 @@ namespace Sass { for (size_t i = 0, L = s->length(); i < L; ++i) { (*s)[i]->perform(this); } + if (s->has_line_break()) { + append_optional_linefeed(); + } } void Inspect::operator()(Complex_Selector* c) @@ -721,15 +800,34 @@ namespace Sass { Complex_Selector* tail = c->tail(); Complex_Selector::Combinator comb = c->combinator(); if (head && !head->is_empty_reference()) head->perform(this); - if (head && !head->is_empty_reference() && tail) append_to_buffer(" "); + bool is_empty = head && 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: break; - case Complex_Selector::PARENT_OF: append_to_buffer(">"); break; - case Complex_Selector::PRECEDES: append_to_buffer("~"); break; - case Complex_Selector::ADJACENT_TO: append_to_buffer("+"); break; + 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::PRECEDES: + if (is_empty) append_optional_space(); + else append_mandatory_space(); + append_string("~"); + if (tail) append_mandatory_space(); + else append_optional_space(); + break; } if (tail && comb != Complex_Selector::ANCESTOR_OF) { - append_to_buffer(" "); + if (c->has_line_break()) append_optional_linefeed(); } if (tail) tail->perform(this); } @@ -737,130 +835,21 @@ namespace Sass { void Inspect::operator()(Selector_List* g) { if (g->empty()) return; - if (ctx) ctx->source_map.add_open_mapping((*g)[0]); - source_map.add_open_mapping((*g)[0]); - (*g)[0]->perform(this); - if (ctx) ctx->source_map.add_close_mapping((*g)[0]); - source_map.add_close_mapping((*g)[0]); - for (size_t i = 1, L = g->length(); i < L; ++i) { - append_to_buffer(", "); - if (ctx) ctx->source_map.add_open_mapping((*g)[i]); - source_map.add_open_mapping((*g)[i]); + for (size_t i = 0, L = g->length(); i < L; ++i) { + if (i == 0) append_indentation(); (*g)[i]->perform(this); - if (ctx) ctx->source_map.add_close_mapping((*g)[i]); - source_map.add_close_mapping((*g)[i]); - } - } - - inline void Inspect::fallback_impl(AST_Node* n) - { } - - void Inspect::append_indent_to_buffer() - { - string indent = ""; - for (size_t i = 0; i < indentation; i++) - indent += ctx->indent; - append_to_buffer(indent); - } - - string unquote(const string& s) - { - if (s.empty()) return ""; - if (s.length() == 1) { - if (s[0] == '"' || s[0] == '\'') return ""; - } - // char q; - if (*s.begin() == '"' && *s.rbegin() == '"') {} // q = '"'; - else if (*s.begin() == '\'' && *s.rbegin() == '\'') {} // q = '\''; - else return s; - string t; - t.reserve(s.length()-2); - - for (size_t i = 1, L = s.length()-1; i < L; ++i) { - - // implement the same strange ruby sass behavior - // an escape sequence can also mean a unicode char - if (s[i] == '\\') { - - // skip it - ++ i; - - // escape length - size_t len = 0; - - // parse as many sequence chars as possible - // ToDo: Check if ruby aborts after possible max - while (s[i + len] && isxdigit(s[i + len])) ++ len; - - // hex string? - if (len == 0) { - - // add next char - t.push_back(s[i]); - - } else { - - // convert the extracted hex string to code point value - // ToDo: Maybe we could do this without creating a substring - uint32_t cp = strtol(s.substr (i, len).c_str(), nullptr, 16); - - // use a very simple approach to convert via utf8 lib - // maybe there is a more elegant way; maybe we shoud - // convert the whole output from string to a stream!? - // allocate memory for utf8 char and convert to utf8 - unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u); - for(size_t m = 0; u[m] && m < 5; m++) t.push_back(u[m]); - - // skip some more chars? - if (len > 1) i += len - 1; - + if (i < L - 1) { + append_comma_separator(); + if ((*g)[i]->has_line_feed()) { + append_optional_linefeed(); + append_indentation(); } - // EO if hex - - } else { - // add single char - t.push_back(s[i]); } } - - return t; - } - - string quote(const string& s, char q) - { - if (s.empty()) return string(2, q); - if (!q || s[0] == '"' || s[0] == '\'') return s; - string t; - t.reserve(s.length()+2); - t.push_back(q); - for (size_t i = 0, L = s.length(); i < L; ++i) { - if (s[i] == q) t.push_back('\\'); - t.push_back(s[i]); - } - t.push_back(q); - return t; - } - - void Inspect::append_to_buffer(const string& text) - { - buffer += text; - if (ctx) ctx->source_map.update_column(text); - source_map.update_column(text); - } - - void Inspect::append_to_buffer(const string& text, AST_Node* node) - { - if (ctx) ctx->source_map.add_open_mapping(node); - source_map.add_open_mapping(node); - append_to_buffer(text); - if (ctx) ctx->source_map.add_close_mapping(node); - source_map.add_close_mapping(node); } - void Inspect::append_to_buffer(const string& text, AST_Node* node, const string& tail) + void Inspect::fallback_impl(AST_Node* n) { - append_to_buffer(text, node); - append_to_buffer(tail); } } diff --git a/inspect.hpp b/inspect.hpp index e408322c91..702d562fd3 100644 --- a/inspect.hpp +++ b/inspect.hpp @@ -5,39 +5,24 @@ #include "position.hpp" #include "operation.hpp" -#include "source_map.hpp" +#include "emitter.hpp" namespace Sass { + class Context; using namespace std; - struct Context; - class Inspect : public Operation_CRTP { + class Inspect : public Operation_CRTP, public Emitter { + protected: // import all the class-specific methods and override as desired using Operation_CRTP::operator(); - // To_String* to_string; - string buffer; - size_t indentation; - Context* ctx; - bool in_declaration; - bool in_declaration_list; - void fallback_impl(AST_Node* n); - public: - void append_indent_to_buffer(); - void append_to_buffer(const string& text); - void append_to_buffer(const string& text, AST_Node* node); - void append_to_buffer(const string& text, AST_Node* node, const string& tail); - public: - SourceMap source_map; - Inspect(Context* ctx = 0); + Inspect(Emitter emi); virtual ~Inspect(); - string get_buffer() { return buffer; } - // statements virtual void operator()(Block*); virtual void operator()(Ruleset*); @@ -79,6 +64,7 @@ namespace Sass { virtual void operator()(Boolean*); virtual void operator()(String_Schema*); virtual void operator()(String_Constant*); + virtual void operator()(String_Quoted*); virtual void operator()(Feature_Query*); virtual void operator()(Feature_Query_Condition*); virtual void operator()(Media_Query*); @@ -103,12 +89,9 @@ namespace Sass { virtual void operator()(Complex_Selector*); virtual void operator()(Selector_List*); - template - void fallback(U x) { fallback_impl(reinterpret_cast(x)); } + // template + // void fallback(U x) { fallback_impl(reinterpret_cast(x)); } }; - string unquote(const string&); - string quote(const string&, char); - } #endif diff --git a/node.cpp b/node.cpp index 18bf4d9b7c..5fea9cec9b 100644 --- a/node.cpp +++ b/node.cpp @@ -41,11 +41,9 @@ namespace Sass { } - Node::Node(const TYPE& type, Complex_Selector::Combinator combinator, Complex_Selector* pSelector, NodeDequePtr& pCollection) : - mType(type), - mCombinator(combinator), - mpSelector(pSelector), - mpCollection(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::clone(Context& ctx) const { @@ -131,6 +129,7 @@ namespace Sass { } + /* not used anymore - remove? ostream& operator<<(ostream& os, const Node& node) { if (node.isCombinator()) { @@ -169,7 +168,7 @@ namespace Sass { return os; - } + }*/ Node complexSelectorToNode(Complex_Selector* pToConvert, Context& ctx) { @@ -177,7 +176,7 @@ namespace Sass { return Node::createNil(); } - Node node = Node::createCollection(); + Node node = Node::createCollection(); while (pToConvert) { @@ -243,6 +242,7 @@ namespace Sass { Selector_Reference* selectorRef = new (ctx.mem) Selector_Reference(ParserState("[NODE]"), NULL); fakeHead->elements().push_back(selectorRef); pFirst->head(fakeHead); + pFirst->has_line_feed(pFirst->has_line_feed() || pFirst->tail()->has_line_feed() || toConvert.got_line_feed); return pFirst; } diff --git a/node.hpp b/node.hpp index 097e2f8d16..d42d645801 100644 --- a/node.hpp +++ b/node.hpp @@ -50,6 +50,7 @@ namespace Sass { 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; } @@ -107,14 +108,10 @@ namespace Sass { NodeDequePtr mpCollection; }; - - ostream& operator<<(ostream& os, const Node& node); - - + // ostream& operator<<(ostream& os, const Node& node); Node complexSelectorToNode(Complex_Selector* pToConvert, Context& ctx); Complex_Selector* nodeToComplexSelector(const Node& toConvert, Context& ctx); - bool nodesEqual(const Node& one, const Node& two, bool simpleSelectorOrderDependent); } diff --git a/output.cpp b/output.cpp new file mode 100644 index 0000000000..d0970b1e60 --- /dev/null +++ b/output.cpp @@ -0,0 +1,398 @@ +#include "ast.hpp" +#include "output.hpp" +#include "to_string.hpp" + +namespace Sass { + using namespace std; + + Output::Output(Context* ctx) + : Inspect(Emitter(ctx)), + charset(""), + top_imports(0), + top_comments(0) + {} + + Output::~Output() { } + + void Output::fallback_impl(AST_Node* n) + { + return n->perform(this); + } + + void Output::operator()(Import* imp) + { + top_imports.push_back(imp); + } + + string Output::get_buffer(void) + { + + Emitter emitter(ctx); + Inspect inspect(emitter); + + size_t size_com = top_comments.size(); + for (size_t i = 0; i < size_com; i++) { + top_comments[i]->perform(&inspect); + inspect.append_mandatory_linefeed(); + } + + size_t size_imp = top_imports.size(); + for (size_t i = 0; i < size_imp; i++) { + top_imports[i]->perform(&inspect); + inspect.append_mandatory_linefeed(); + } + + // flush scheduled outputs + inspect.finalize(); + // create combined buffer string + string buffer = inspect.buffer() + + this->buffer(); + // make sure we end with a linefeed + if (!ends_with(buffer, ctx->linefeed)) { + // if the output is not completely empty + if (!buffer.empty()) buffer += ctx->linefeed; + } + + // search for unicode char + for(const char& chr : buffer) { + // skip all ascii chars + if (chr >= 0) continue; + // declare the charset + if (output_style() != COMPRESSED) + charset = "@charset \"UTF-8\";" + + ctx->linefeed; + else charset = "\xEF\xBB\xBF"; + // abort search + break; + } + + // add charset as first line, before comments and imports + return (charset.empty() ? "" : charset) + buffer; + + } + + void Output::operator()(Comment* c) + { + To_String to_string(ctx); + string txt = c->text()->perform(&to_string); + // if (indentation && txt == "/**/") return; + bool important = c->is_important(); + if (output_style() != COMPRESSED || important) { + if (buffer().size() + top_imports.size() == 0) { + top_comments.push_back(c); + } else { + in_comment = true; + append_indentation(); + c->text()->perform(this); + in_comment = false; + if (indentation == 0) { + append_mandatory_linefeed(); + } else { + append_optional_linefeed(); + } + } + } + } + + void Output::operator()(Ruleset* r) + { + Selector* s = r->selector(); + Block* b = r->block(); + bool decls = false; + + // 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) { + Statement* stm = (*b)[i]; + if (dynamic_cast(stm)) { + stm->perform(this); + } + } + return; + } + + if (b->has_non_hoistable()) { + decls = true; + if (output_style() == NESTED) indentation += r->tabs(); + if (ctx && ctx->source_comments) { + stringstream ss; + append_indentation(); + ss << "/* line " << r->pstate().line+1 << ", " << r->pstate().path << " */"; + append_string(ss.str()); + append_optional_linefeed(); + } + s->perform(this); + append_scope_opener(b); + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement* stm = (*b)[i]; + bool bPrintExpression = true; + // Check print conditions + if (typeid(*stm) == typeid(Declaration)) { + Declaration* dec = static_cast(stm); + if (dec->value()->concrete_type() == Expression::STRING) { + String_Constant* valConst = static_cast(dec->value()); + string val(valConst->value()); + if (dynamic_cast(valConst)) { + if (val.empty()) { + bPrintExpression = false; + } + } + } + else if (dec->value()->concrete_type() == Expression::LIST) { + List* list = static_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)[list_i]; + if (!item->is_invisible()) all_invisible = false; + } + if (all_invisible) bPrintExpression = false; + } + } + // Print if OK + if (!stm->is_hoistable() && bPrintExpression) { + stm->perform(this); + } + } + if (output_style() == NESTED) indentation -= r->tabs(); + append_scope_closer(b); + } + + if (b->has_hoistable()) { + if (decls) ++indentation; + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement* stm = (*b)[i]; + if (stm->is_hoistable()) { + stm->perform(this); + } + } + if (decls) --indentation; + } + } + + void Output::operator()(Keyframe_Rule* r) + { + String* v = r->rules(); + Block* b = r->block(); + + if (v) { + append_indentation(); + v->perform(this); + } + + if (!b) { + append_colon_separator(); + return; + } + + append_scope_opener(); + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement* stm = (*b)[i]; + if (!stm->is_hoistable()) { + stm->perform(this); + if (i < L - 1) append_special_linefeed(); + } + } + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement* stm = (*b)[i]; + if (stm->is_hoistable()) { + stm->perform(this); + } + } + + append_scope_closer(); + } + + void Output::operator()(Feature_Block* f) + { + if (f->is_invisible()) return; + + Feature_Query* q = f->feature_queries(); + Block* b = f->block(); + + // 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* stm = (*b)[i]; + if (dynamic_cast(stm)) { + stm->perform(this); + } + } + return; + } + + if (output_style() == NESTED) indentation += f->tabs(); + append_indentation(); + append_token("@supports", f); + append_mandatory_space(); + q->perform(this); + append_scope_opener(); + + Selector* e = f->selector(); + if (e && b->has_non_hoistable()) { + // JMA - hoisted, output the non-hoistable in a nested block, followed by the hoistable + e->perform(this); + append_scope_opener(); + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement* stm = (*b)[i]; + if (!stm->is_hoistable()) { + stm->perform(this); + } + } + + append_scope_closer(); + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement* stm = (*b)[i]; + if (stm->is_hoistable()) { + stm->perform(this); + } + } + } + else { + // JMA - not hoisted, just output in order + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement* stm = (*b)[i]; + stm->perform(this); + if (i < L - 1) append_special_linefeed(); + } + } + + if (output_style() == NESTED) indentation -= f->tabs(); + + append_scope_closer(); + + } + + void Output::operator()(Media_Block* m) + { + if (m->is_invisible()) return; + + List* q = m->media_queries(); + Block* 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* stm = (*b)[i]; + if (dynamic_cast(stm)) { + stm->perform(this); + } + } + return; + } + if (output_style() == NESTED) indentation += m->tabs(); + append_indentation(); + append_token("@media", m); + append_mandatory_space(); + in_media_block = true; + q->perform(this); + in_media_block = false; + append_scope_opener(); + + Selector* e = m->selector(); + if (e && b->has_non_hoistable()) { + // JMA - hoisted, output the non-hoistable in a nested block, followed by the hoistable + e->perform(this); + append_scope_opener(); + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement* stm = (*b)[i]; + if (!stm->is_hoistable()) { + stm->perform(this); + } + } + + append_scope_closer(); + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement* stm = (*b)[i]; + if (stm->is_hoistable()) { + stm->perform(this); + } + } + } + else { + // JMA - not hoisted, just output in order + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement* stm = (*b)[i]; + stm->perform(this); + if (i < L - 1) append_special_linefeed(); + } + } + + if (output_style() == NESTED) indentation -= m->tabs(); + append_scope_closer(); + } + + void Output::operator()(At_Rule* a) + { + string kwd = a->keyword(); + Selector* s = a->selector(); + Expression* v = a->value(); + Block* b = a->block(); + + append_indentation(); + append_token(kwd, a); + if (s) { + append_mandatory_space(); + s->perform(this); + } + else if (v) { + append_mandatory_space(); + v->perform(this); + } + if (!b) { + append_delimiter(); + return; + } + + if (b->is_invisible() || b->length() == 0) { + return append_string(" {}"); + } + + append_scope_opener(); + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement* stm = (*b)[i]; + if (!stm->is_hoistable()) { + stm->perform(this); + if (i < L - 1) append_special_linefeed(); + } + } + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement* stm = (*b)[i]; + if (stm->is_hoistable()) { + stm->perform(this); + if (i < L - 1) append_special_linefeed(); + } + } + + append_scope_closer(); + } + + void Output::operator()(String_Quoted* s) + { + if (s->quote_mark()) { + append_token(quote((s->value()), s->quote_mark()), s); + } else if (!in_comment) { + append_token(string_to_output(s->value()), s); + } else { + append_token(s->value(), s); + } + } + + void Output::operator()(String_Constant* s) + { + if (String_Quoted* quoted = dynamic_cast(s)) { + return Output::operator()(quoted); + } else if (!in_comment) { + append_token(string_to_output(s->value()), s); + } else { + append_token(s->value(), s); + } + } + +} diff --git a/output.hpp b/output.hpp index 246c10ab54..f2cdd2c633 100644 --- a/output.hpp +++ b/output.hpp @@ -5,88 +5,49 @@ #include #include "util.hpp" -#include "context.hpp" +#include "inspect.hpp" #include "operation.hpp" namespace Sass { + class Context; using namespace std; - struct Context; - template - class Output : public Operation_CRTP { - // import class-specific methods and override as desired - using Operation_CRTP::operator(); + // Refactor to make it generic to find linefeed (look behind) + inline bool ends_with(std::string const & value, std::string const & ending) + { + if (ending.size() > value.size()) return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); + } + class Output : public Inspect { protected: - Context* ctx; - string buffer; - vector top_imports; - vector top_comments; - virtual void fallback_impl(AST_Node* n) = 0; + using Inspect::operator(); public: - Output(Context* ctx = 0) - : ctx(ctx), - buffer(""), - top_imports(0), - top_comments(0) - { } - virtual ~Output() { }; - - string get_buffer(void) - { - string charset(""); - - Inspect comments(ctx); - size_t size_com = top_comments.size(); - for (size_t i = 0; i < size_com; i++) { - top_comments[i]->perform(&comments); - comments.append_to_buffer(ctx->linefeed); - } - - Inspect imports(ctx); - size_t size_imp = top_imports.size(); - for (size_t i = 0; i < size_imp; i++) { - top_imports[i]->perform(&imports); - imports.append_to_buffer(ctx->linefeed); - } - - // create combined buffer string - string buffer = comments.get_buffer() - + imports.get_buffer() - + this->buffer; + // change to Emitter + Output(Context* ctx); + virtual ~Output(); - // search for unicode char - for(const char& chr : buffer) { - // skip all ascii chars - if (chr >= 0) continue; - // declare the charset - charset = "@charset \"UTF-8\";"; - // abort search - break; - } - - // add charset as the very first line, before top comments and imports - return (charset.empty() ? "" : charset + ctx->linefeed) + buffer; - } - - // append some text or token to the buffer - void append_to_buffer(const string& data) - { - // add to buffer - buffer += data; - // account for data in source-maps - ctx->source_map.update_column(data); - } + protected: + string charset; + vector top_imports; + vector top_comments; - // append some text or token to the buffer - // this adds source-mappings for node start and end - void append_to_buffer(const string& data, AST_Node* node) - { - ctx->source_map.add_open_mapping(node); - append_to_buffer(data); - ctx->source_map.add_close_mapping(node); - } + public: + string get_buffer(void); + + virtual void operator()(Ruleset*); + // virtual void operator()(Propset*); + virtual void operator()(Feature_Block*); + virtual void operator()(Media_Block*); + virtual void operator()(At_Rule*); + virtual void operator()(Keyframe_Rule*); + virtual void operator()(Import*); + virtual void operator()(Comment*); + virtual void operator()(String_Quoted*); + virtual void operator()(String_Constant*); + + void fallback_impl(AST_Node* n); }; diff --git a/output_compressed.cpp b/output_compressed.cpp deleted file mode 100644 index 3c97c5b10d..0000000000 --- a/output_compressed.cpp +++ /dev/null @@ -1,410 +0,0 @@ -#include -#include - -#include "ast.hpp" -#include "util.hpp" -#include "context.hpp" -#include "inspect.hpp" -#include "to_string.hpp" -#include "output_compressed.hpp" - -namespace Sass { - using namespace std; - - Output_Compressed::Output_Compressed(Context* ctx) : Output(ctx), seen_utf8(false) { } - Output_Compressed::~Output_Compressed() { } - - inline void Output_Compressed::fallback_impl(AST_Node* n) - { - Inspect i(ctx); - n->perform(&i); - append_to_buffer(i.get_buffer()); - } - - void Output_Compressed::operator()(Import* imp) - { - // Inspect insp(ctx); - // imp->perform(&insp); - // rendered_imports += insp.get_buffer(); - top_imports.push_back(imp); - } - - void Output_Compressed::operator()(Block* b) - { - if (!b->is_root()) return; - for (size_t i = 0, L = b->length(); i < L; ++i) { - (*b)[i]->perform(this); - } - } - - void Output_Compressed::operator()(Ruleset* r) - { - Selector* s = r->selector(); - Block* b = r->block(); - - // Filter out rulesets that aren't printable (process its children though) - if (!Util::isPrintable(r)) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (dynamic_cast(stm)) { - stm->perform(this); - } - } - return; - } - - if (b->has_non_hoistable()) { - s->perform(this); - append_singleline_part_to_buffer("{"); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (!stm->is_hoistable()) { - stm->perform(this); - } - } - size_t l = buffer.length(); - if (l > 0 && buffer.at(l - 1) == ';') buffer.erase(l - 1); - append_singleline_part_to_buffer("}"); - } - - if (b->has_hoistable()) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (stm->is_hoistable()) { - stm->perform(this); - } - } - } - } - - void Output_Compressed::operator()(Media_Block* m) - { - if (m->is_invisible()) return; - - List* q = m->media_queries(); - Block* b = m->block(); - - // Filter out media blocks that aren't printable (process its children though) - if (!Util::isPrintable(m)) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (dynamic_cast(stm)) { - stm->perform(this); - } - } - return; - } - - ctx->source_map.add_open_mapping(m); - append_singleline_part_to_buffer("@media "); - ctx->source_map.add_close_mapping(m); - q->perform(this); - append_singleline_part_to_buffer("{"); - - Selector* e = m->selector(); - if (e && b->has_non_hoistable()) { - // JMA - hoisted, output the non-hoistable in a nested block, followed by the hoistable - e->perform(this); - append_singleline_part_to_buffer("{"); - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (!stm->is_hoistable()) { - stm->perform(this); - } - } - - append_singleline_part_to_buffer("}"); - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (stm->is_hoistable()) { - stm->perform(this); - } - } - } - else { - // JMA - not hoisted, just output in order - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - stm->perform(this); - } - } - - append_singleline_part_to_buffer("}"); - } - - void Output_Compressed::operator()(Keyframe_Rule* r) - { - String* v = r->rules(); - Block* b = r->block(); - - if (v) { - append_singleline_part_to_buffer(" "); - v->perform(this); - } - - if (!b) { - append_singleline_part_to_buffer(";"); - return; - } - - append_singleline_part_to_buffer("{"); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (!stm->is_hoistable()) { - stm->perform(this); - } - } - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (stm->is_hoistable()) { - stm->perform(this); - } - } - - append_singleline_part_to_buffer("}"); - } - - void Output_Compressed::operator()(At_Rule* a) - { - string kwd = a->keyword(); - Selector* s = a->selector(); - Expression* v = a->value(); - Block* b = a->block(); - - append_singleline_part_to_buffer(kwd); - if (s) { - append_singleline_part_to_buffer(" "); - s->perform(this); - } - else if (v) { - append_singleline_part_to_buffer(" "); - v->perform(this); - } - - if (!b) { - append_singleline_part_to_buffer(";"); - return; - } - - append_singleline_part_to_buffer("{"); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (!stm->is_hoistable()) { - stm->perform(this); - } - } - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (stm->is_hoistable()) { - stm->perform(this); - } - } - - append_singleline_part_to_buffer("}"); - } - - void Output_Compressed::operator()(Declaration* d) - { - bool bPrintExpression = true; - // Check print conditions - if (d->value()->concrete_type() == Expression::NULL_VAL) { - bPrintExpression = false; - } - if (d->value()->concrete_type() == Expression::STRING) { - String_Constant* valConst = static_cast(d->value()); - string val(valConst->value()); - if (val.empty()) { - bPrintExpression = false; - } - } - // Print if OK - if(bPrintExpression) { - if (ctx) ctx->source_map.add_open_mapping(d->property()); - d->property()->perform(this); - append_singleline_part_to_buffer(":"); - if (ctx) ctx->source_map.add_open_mapping(d->value()); - d->value()->perform(this); - if (d->is_important()) append_singleline_part_to_buffer("!important"); - append_singleline_part_to_buffer(";"); - } - } - - void Output_Compressed::operator()(Comment* c) - { - To_String to_string; - string txt = c->text()->perform(&to_string); - if(txt[2] != '!') { - return; - } - else { - Inspect i(ctx); - c->perform(&i); - append_to_buffer(i.get_buffer()); - } - } - - void Output_Compressed::operator()(List* list) - { - string sep(list->separator() == List::SPACE ? " " : ","); - if (list->empty()) return; - Expression* first = (*list)[0]; - bool first_invisible = first->is_invisible(); - if (!first_invisible) first->perform(this); - for (size_t i = 1, L = list->length(); i < L; ++i) { - Expression* next = (*list)[i]; - bool next_invisible = next->is_invisible(); - if (i == 1 && !first_invisible && !next_invisible) append_singleline_part_to_buffer(sep); - else if (!next_invisible) append_singleline_part_to_buffer(sep); - next->perform(this); - } - } - - // helper function for serializing colors - template - static double cap_channel(double c) { - if (c > range) return range; - else if (c < 0) return 0; - else return c; - } - - void Output_Compressed::operator()(Color* c) - { - stringstream ss; - double r = round(cap_channel<0xff>(c->r())); - double g = round(cap_channel<0xff>(c->g())); - double b = round(cap_channel<0xff>(c->b())); - double a = cap_channel<1> (c->a()); - - // retain the originally specified color definition if unchanged - if (!c->disp().empty()) { - ss << c->disp(); - } - else if (r == 0 && g == 0 && b == 0 && a == 0) { - ss << "transparent"; - } - else if (a >= 1) { - // see if it's a named color - int numval = r * 0x10000; - numval += g * 0x100; - numval += b; - if (ctx && ctx->colors_to_names.count(numval)) { - ss << ctx->colors_to_names[numval]; - } - else { - // otherwise output the hex triplet - ss << '#' << setw(2) << setfill('0'); - ss << hex << setw(2) << static_cast(r); - ss << hex << setw(2) << static_cast(g); - ss << hex << setw(2) << static_cast(b); - } - } - else { - ss << "rgba("; - ss << static_cast(r) << ","; - ss << static_cast(g) << ","; - ss << static_cast(b) << ","; - ss << a << ')'; - } - append_singleline_part_to_buffer(ss.str()); - } - - void Output_Compressed::operator()(Media_Query_Expression* mqe) - { - if (mqe->is_interpolated()) { - mqe->feature()->perform(this); - } - else { - append_singleline_part_to_buffer("("); - mqe->feature()->perform(this); - if (mqe->value()) { - append_singleline_part_to_buffer(":"); - mqe->value()->perform(this); - } - append_singleline_part_to_buffer(")"); - } - } - - void Output_Compressed::operator()(Null* n) - { - // noop - } - - void Output_Compressed::operator()(Argument* a) - { - if (!a->name().empty()) { - append_singleline_part_to_buffer(a->name()); - append_singleline_part_to_buffer(":"); - } - a->value()->perform(this); - if (a->is_rest_argument()) { - append_singleline_part_to_buffer("..."); - } - } - - void Output_Compressed::operator()(Arguments* a) - { - append_singleline_part_to_buffer("("); - if (!a->empty()) { - (*a)[0]->perform(this); - for (size_t i = 1, L = a->length(); i < L; ++i) { - append_singleline_part_to_buffer(","); - (*a)[i]->perform(this); - } - } - append_singleline_part_to_buffer(")"); - } - - void Output_Compressed::operator()(Complex_Selector* c) - { - Compound_Selector* head = c->head(); - Complex_Selector* tail = c->tail(); - Complex_Selector::Combinator comb = c->combinator(); - if (head && head->is_empty_reference() && tail) - { - tail->perform(this); - return; - } - if (head && !head->is_empty_reference()) head->perform(this); - switch (comb) { - case Complex_Selector::ANCESTOR_OF: - if (tail) append_singleline_part_to_buffer(" "); - break; - case Complex_Selector::PARENT_OF: - append_singleline_part_to_buffer(">"); - break; - case Complex_Selector::PRECEDES: - // Apparently need to preserve spaces around this combinator? - if (head && !head->is_empty_reference()) append_singleline_part_to_buffer(" "); - append_singleline_part_to_buffer("~"); - if (tail) append_singleline_part_to_buffer(" "); - break; - case Complex_Selector::ADJACENT_TO: - append_singleline_part_to_buffer("+"); - break; - } - if (tail) tail->perform(this); - } - - void Output_Compressed::operator()(Selector_List* g) - { - if (g->empty()) return; - (*g)[0]->perform(this); - for (size_t i = 1, L = g->length(); i < L; ++i) { - append_singleline_part_to_buffer(","); - (*g)[i]->perform(this); - } - } - - void Output_Compressed::append_singleline_part_to_buffer(const string& text) - { - append_to_buffer(text); - } - - // compile output implementation - template class Output; - -} diff --git a/output_compressed.hpp b/output_compressed.hpp deleted file mode 100644 index d7374c2d69..0000000000 --- a/output_compressed.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef SASS_OUTPUT_COMPRESSED_H -#define SASS_OUTPUT_COMPRESSED_H - -#include -#include "output.hpp" - -namespace Sass { - using namespace std; - struct Context; - - class Output_Compressed : public Output { - // import all the class-specific methods and override as desired - // using Operation_CRTP::operator(); - - // string buffer; - // string rendered_imports; - // Context* ctx; - bool seen_utf8; - - void fallback_impl(AST_Node* n); - - void append_singleline_part_to_buffer(const string& text); - - public: - Output_Compressed(Context* ctx = 0); - virtual ~Output_Compressed(); - - // statements - virtual void operator()(Block*); - virtual void operator()(Ruleset*); - // virtual void operator()(Propset*); - virtual void operator()(Media_Block*); - virtual void operator()(At_Rule*); - virtual void operator()(Keyframe_Rule*); - virtual void operator()(Declaration*); - // virtual void operator()(Assignment*); - virtual void operator()(Import*); - // virtual void operator()(Import_Stub*); - // virtual void operator()(Warning*); - // virtual void operator()(Error*); - // virtual void operator()(Debug*); - virtual void operator()(Comment*); - // virtual void operator()(If*); - // virtual void operator()(For*); - // virtual void operator()(Each*); - // virtual void operator()(While*); - // virtual void operator()(Return*); - // virtual void operator()(Extension*); - // virtual void operator()(Definition*); - // virtual void operator()(Mixin_Call*); - // virtual void operator()(Content*); - // // expressions - virtual void operator()(List*); - // virtual void operator()(Binary_Expression*); - // virtual void operator()(Unary_Expression*); - // virtual void operator()(Function_Call*); - // virtual void operator()(Function_Call_Schema*); - // virtual void operator()(Variable*); - // virtual void operator()(Textual*); - // virtual void operator()(Number*); - virtual void operator()(Color*); - // virtual void operator()(Boolean*); - // virtual void operator()(String_Schema*); - // virtual void operator()(String_Constant* x); - // virtual void operator()(Media_Query*); - virtual void operator()(Media_Query_Expression*); - virtual void operator()(Null*); - // // parameters and arguments - // virtual void operator()(Parameter*); - // virtual void operator()(Parameters*); - virtual void operator()(Argument*); - virtual void operator()(Arguments*); - // // selectors - // virtual void operator()(Selector_Schema*); - // virtual void operator()(Selector_Reference*); - // virtual void operator()(Selector_Placeholder*); - // virtual void operator()(Type_Selector*); - // virtual void operator()(Selector_Qualifier*); - // 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*); - - template - void fallback(U x) { fallback_impl(x); } - }; - -} - -#endif diff --git a/output_nested.cpp b/output_nested.cpp deleted file mode 100644 index fb2fcb6c33..0000000000 --- a/output_nested.cpp +++ /dev/null @@ -1,471 +0,0 @@ -#include -#include -#include - -#include "ast.hpp" -#include "util.hpp" -#include "context.hpp" -#include "inspect.hpp" -#include "to_string.hpp" -#include "output_nested.hpp" - -namespace Sass { - using namespace std; - - Output_Nested::Output_Nested(bool source_comments, Context* ctx) - : Output(ctx), - indentation(0), - source_comments(source_comments), - in_directive(false), - in_keyframes(false) - { } - Output_Nested::~Output_Nested() { } - - inline void Output_Nested::fallback_impl(AST_Node* n) - { - Inspect i(ctx); - n->perform(&i); - append_to_buffer(i.get_buffer()); - } - - void Output_Nested::operator()(Import* imp) - { - // Inspect insp(ctx); - // imp->perform(&insp); - // insp.get_buffer(); - top_imports.push_back(imp); - } - - void Output_Nested::operator()(Block* b) - { - if (!b->is_root()) return; - for (size_t i = 0, L = b->length(); i < L; ++i) { - size_t old_len = buffer.length(); - (*b)[i]->perform(this); - if (i < L-1 && old_len < buffer.length()) append_to_buffer(ctx->linefeed); - } - } - - void Output_Nested::operator()(Comment* c) - { - To_String to_string; - string txt = c->text()->perform(&to_string); - if (buffer.size() + top_imports.size() == 0) { - top_comments.push_back(c); - } else { - Inspect i(ctx); - c->perform(&i); - append_to_buffer(i.get_buffer()); - } - } - - void Output_Nested::operator()(Ruleset* r) - { - Selector* s = r->selector(); - Block* b = r->block(); - bool decls = false; - - // disabled to avoid clang warning [-Wunused-function] - // Selector_List* sl = static_cast(s); - - // Filter out rulesets that aren't printable (process its children though) - if (!Util::isPrintable(r)) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (dynamic_cast(stm)) { - stm->perform(this); - } - } - return; - } - - if (b->has_non_hoistable()) { - decls = true; - indentation += r->tabs(); - indent(); - if (source_comments) { - stringstream ss; - ss << "/* line " << r->pstate().line+1 << ", " << r->pstate().path << " */" << endl; - append_to_buffer(ss.str()); - indent(); - } - s->perform(this); - append_to_buffer(" {" + ctx->linefeed); - ++indentation; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - bool bPrintExpression = true; - // Check print conditions - if (typeid(*stm) == typeid(Declaration)) { - Declaration* dec = static_cast(stm); - if (dec->value()->concrete_type() == Expression::STRING) { - String_Constant* valConst = static_cast(dec->value()); - string val(valConst->value()); - if (val.empty()) { - bPrintExpression = false; - } - } - else if (dec->value()->concrete_type() == Expression::LIST) { - List* list = static_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)[list_i]; - if (!item->is_invisible()) all_invisible = false; - } - if (all_invisible) bPrintExpression = false; - } - } - // Print if OK - if (!stm->is_hoistable() && bPrintExpression) { - if (!stm->block()) indent(); - stm->perform(this); - if (i < L-1) append_to_buffer(ctx->linefeed); - } - } - --indentation; - indentation -= r->tabs(); - - while (buffer.substr(buffer.length()-ctx->linefeed.length()) == ctx->linefeed) { - buffer.erase(buffer.length()-1); - if (ctx) ctx->source_map.remove_line(); - } - - append_to_buffer(" }"); - if (r->group_end()) append_to_buffer(ctx->linefeed); - // Match Sass 3.4.9 behaviour - if (in_directive && !in_keyframes) append_to_buffer(ctx->linefeed); - } - - if (b->has_hoistable()) { - if (decls) ++indentation; - // indent(); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (stm->is_hoistable()) { - stm->perform(this); - } - } - if (decls) --indentation; - } - } - - void Output_Nested::operator()(Keyframe_Rule* r) - { - String* v = r->rules(); - Block* b = r->block(); - bool decls = false; - - // indent(); - if (v) { - append_to_buffer(" "); - v->perform(this); - } - - if (!b) { - append_to_buffer(";"); - return; - } - - append_to_buffer(" {" + ctx->linefeed); - - bool old_in_directive = in_directive; - in_directive = true; - - ++indentation; - decls = true; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (!stm->is_hoistable()) { - if (!stm->block()) indent(); - stm->perform(this); - append_to_buffer(ctx->linefeed); - } - } - --indentation; - - if (decls) ++indentation; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (stm->is_hoistable()) { - stm->perform(this); - // append_to_buffer(ctx->linefeed); - } - } - if (decls) --indentation; - - while (buffer.substr(buffer.length()-ctx->linefeed.length()) == ctx->linefeed) { - buffer.erase(buffer.length()-1); - if (ctx) ctx->source_map.remove_line(); - } - - in_directive = old_in_directive; - - append_to_buffer(" }"); - } - - void Output_Nested::operator()(Feature_Block* f) - { - if (f->is_invisible()) return; - - Feature_Query* q = f->feature_queries(); - Block* b = f->block(); - - // Filter out feature blocks that aren't printable (process its children though) - if (!Util::isPrintable(f)) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (dynamic_cast(stm)) { - stm->perform(this); - } - } - return; - } - - indentation += f->tabs(); - indent(); - append_to_buffer("@supports", f); - append_to_buffer(" "); - q->perform(this); - append_to_buffer(" "); - append_to_buffer("{"); - append_to_buffer(ctx->linefeed); - - bool old_in_directive = in_directive; - in_directive = true; - - Selector* e = f->selector(); - if (e && b->has_non_hoistable()) { - // JMA - hoisted, output the non-hoistable in a nested block, followed by the hoistable - ++indentation; - indent(); - e->perform(this); - append_to_buffer(" {" + ctx->linefeed); - - ++indentation; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (!stm->is_hoistable()) { - if (!stm->block()) indent(); - stm->perform(this); - if (i < L-1) append_to_buffer(ctx->linefeed); - } - } - --indentation; - - in_directive = old_in_directive; - - // buffer.erase(buffer.length()-1); - // if (ctx) ctx->source_map.remove_line(); - append_to_buffer(" }" + ctx->linefeed); - --indentation; - - ++indentation; - ++indentation; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (stm->is_hoistable()) { - stm->perform(this); - } - } - --indentation; - --indentation; - } - else { - // JMA - not hoisted, just output in order - ++indentation; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (!stm->is_hoistable()) { - if (!stm->block()) indent(); - } - stm->perform(this); - if (!stm->is_hoistable()) append_to_buffer(ctx->linefeed); - } - --indentation; - } - - while (buffer.substr(buffer.length()-ctx->linefeed.length()) == ctx->linefeed) { - buffer.erase(buffer.length()-1); - if (ctx) ctx->source_map.remove_line(); - } - - in_directive = old_in_directive; - - append_to_buffer(" }"); - if (f->group_end() || in_directive) append_to_buffer(ctx->linefeed); - - indentation -= f->tabs(); - } - - void Output_Nested::operator()(Media_Block* m) - { - if (m->is_invisible()) return; - - List* q = m->media_queries(); - Block* b = m->block(); - - // Filter out media blocks that aren't printable (process its children though) - if (!Util::isPrintable(m)) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (dynamic_cast(stm)) { - stm->perform(this); - } - } - return; - } - - indentation += m->tabs(); - indent(); - append_to_buffer("@media", m); - append_to_buffer(" "); - q->perform(this); - append_to_buffer(" "); - append_to_buffer("{"); - append_to_buffer(ctx->linefeed); - - bool old_in_directive = in_directive; - in_directive = true; - - Selector* e = m->selector(); - if (e && b->has_non_hoistable()) { - // JMA - hoisted, output the non-hoistable in a nested block, followed by the hoistable - ++indentation; - indent(); - e->perform(this); - append_to_buffer(" {" + ctx->linefeed); - - ++indentation; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (!stm->is_hoistable()) { - if (!stm->block()) indent(); - stm->perform(this); - if (i < L-1) append_to_buffer(ctx->linefeed); - } - } - --indentation; - - in_directive = old_in_directive; - - // buffer.erase(buffer.length()-1); - // if (ctx) ctx->source_map.remove_line(); - append_to_buffer(" }" + ctx->linefeed); - --indentation; - - ++indentation; - ++indentation; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (stm->is_hoistable()) { - stm->perform(this); - } - } - --indentation; - --indentation; - } - else { - // JMA - not hoisted, just output in order - ++indentation; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (!stm->is_hoistable()) { - if (!stm->block()) indent(); - } - stm->perform(this); - if (!stm->is_hoistable()) append_to_buffer(ctx->linefeed); - } - --indentation; - } - - while (buffer.substr(buffer.length()-ctx->linefeed.length()) == ctx->linefeed) { - buffer.erase(buffer.length()-1); - if (ctx) ctx->source_map.remove_line(); - } - - in_directive = old_in_directive; - - append_to_buffer(" }"); - if (m->group_end() || in_directive) append_to_buffer(ctx->linefeed); - - indentation -= m->tabs(); - } - - void Output_Nested::operator()(At_Rule* a) - { - string kwd = a->keyword(); - Selector* s = a->selector(); - Expression* v = a->value(); - Block* b = a->block(); - bool decls = false; - - // indent(); - append_to_buffer(kwd); - if (s) { - append_to_buffer(" "); - s->perform(this); - } - else if (v) { - append_to_buffer(" "); - v->perform(this); - } - - if (!b) { - append_to_buffer(";"); - return; - } - - append_to_buffer(" {" + ctx->linefeed); - - bool old_in_directive = in_directive; - in_directive = true; - in_keyframes = kwd.compare("@keyframes") == 0; - - ++indentation; - decls = true; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (!stm->is_hoistable()) { - if (!stm->block()) indent(); - stm->perform(this); - append_to_buffer(ctx->linefeed); - } - } - --indentation; - - if (decls) ++indentation; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = (*b)[i]; - if (stm->is_hoistable()) { - stm->perform(this); - // append_to_buffer(ctx->linefeed); - } - } - if (decls) --indentation; - - while (buffer.substr(buffer.length()-ctx->linefeed.length()) == ctx->linefeed) { - buffer.erase(buffer.length()-1); - if (ctx) ctx->source_map.remove_line(); - } - - in_directive = old_in_directive; - in_keyframes = false; - - append_to_buffer(" }"); - - // Match Sass 3.4.9 behaviour - if (kwd.compare("@font-face") != 0 && kwd.compare("@keyframes") != 0) append_to_buffer(ctx->linefeed); - } - - void Output_Nested::indent() - { - string indent = ""; - for (size_t i = 0; i < indentation; i++) - indent += ctx->indent; - append_to_buffer(indent); - } - - // compile output implementation - template class Output; - -} diff --git a/output_nested.hpp b/output_nested.hpp deleted file mode 100644 index eaa13e367b..0000000000 --- a/output_nested.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef SASS_OUTPUT_NESTED_H -#define SASS_OUTPUT_NESTED_H - -#include -#include "output.hpp" - -namespace Sass { - using namespace std; - struct Context; - - class Output_Nested : public Output { - - size_t indentation; - bool source_comments; - bool in_directive; - bool in_keyframes; - void indent(); - - void fallback_impl(AST_Node* n); - - public: - - Output_Nested(bool source_comments = false, Context* ctx = 0); - virtual ~Output_Nested(); - - string get_buffer() - { - // call parent method for result - return Output::get_buffer(); - } - - // statements - virtual void operator()(Block*); - virtual void operator()(Ruleset*); - // virtual void operator()(Propset*); - virtual void operator()(Feature_Block*); - virtual void operator()(Media_Block*); - virtual void operator()(At_Rule*); - virtual void operator()(Keyframe_Rule*); - // virtual void operator()(Declaration*); - // virtual void operator()(Assignment*); - virtual void operator()(Import*); - // virtual void operator()(Import_Stub*); - // virtual void operator()(Warning*); - // virtual void operator()(Error*); - // virtual void operator()(Debug*); - virtual void operator()(Comment*); - // virtual void operator()(If*); - // virtual void operator()(For*); - // virtual void operator()(Each*); - // virtual void operator()(While*); - // virtual void operator()(Return*); - // virtual void operator()(Extension*); - // virtual void operator()(Definition*); - // virtual void operator()(Mixin_Call*); - // virtual void operator()(Content*); - // // expressions - // virtual void operator()(List*); - // virtual void operator()(Binary_Expression*); - // virtual void operator()(Unary_Expression*); - // virtual void operator()(Function_Call*); - // virtual void operator()(Function_Call_Schema*); - // virtual void operator()(Variable*); - // virtual void operator()(Textual*); - // virtual void operator()(Number*); - // virtual void operator()(Color*); - // virtual void operator()(Boolean*); - // virtual void operator()(String_Schema*); - // virtual void operator()(String_Constant* x); - // virtual void operator()(Media_Query*); - // virtual void operator()(Media_Query_Expression*); - // // parameters and arguments - // virtual void operator()(Parameter*); - // virtual void operator()(Parameters*); - // virtual void operator()(Argument*); - // virtual void operator()(Arguments*); - // // selectors - // virtual void operator()(Selector_Schema*); - // virtual void operator()(Selector_Reference*); - // virtual void operator()(Selector_Placeholder*); - // virtual void operator()(Type_Selector*); - // virtual void operator()(Selector_Qualifier*); - // 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*); - - template - void fallback(U x) { fallback_impl(x); } - }; - - string unquote(const string&); - string quote(const string&, char); - -} - -#endif diff --git a/parser.cpp b/parser.cpp index 7d632e6548..39b15879c0 100644 --- a/parser.cpp +++ b/parser.cpp @@ -25,13 +25,26 @@ namespace Sass { return p; } + Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate) + { + Parser p(ctx, pstate); + p.source = beg; + p.position = p.source; + p.end = end; + return p; + } + + bool Parser::peek_newline(const char* start) + { + return peek_linefeed(start ? start : position); + } + Parser Parser::from_token(Token t, Context& ctx, ParserState pstate) { Parser p(ctx, pstate); p.source = t.begin; p.position = p.source; p.end = t.end; - p.dequote = true; return p; } @@ -44,8 +57,9 @@ namespace Sass { Selector_Lookahead lookahead_result; while (position < end) { if (lex< block_comment >()) { + bool is_important = lexed.begin[2] == '!'; String* contents = parse_interpolated_chunk(lexed); - Comment* comment = new (ctx.mem) Comment(pstate, contents); + Comment* comment = new (ctx.mem) Comment(pstate, contents, is_important); (*root) << comment; } else if (peek< import >()) { @@ -65,9 +79,9 @@ namespace Sass { (*root) << parse_assignment(); if (!lex< one_plus< exactly<';'> > >()) error("top-level variable binding must be terminated by ';'", pstate); } - else if (peek< sequence< optional< exactly<'*'> >, alternatives< identifier_schema, identifier >, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) { + /*else if (peek< sequence< optional< exactly<'*'> >, alternatives< identifier_schema, identifier >, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) { (*root) << parse_propset(); - } + }*/ else if (peek< include >() /* || peek< exactly<'+'> >() */) { Mixin_Call* mixin_call = parse_mixin_call(); (*root) << mixin_call; @@ -142,7 +156,7 @@ namespace Sass { } if (extension == ".css") { - String_Constant* loc = new (ctx.mem) String_Constant(pstate, import_path, true); + String_Constant* loc = new (ctx.mem) String_Constant(pstate, unquote(import_path)); Argument* loc_arg = new (ctx.mem) Argument(pstate, loc); Arguments* loc_args = new (ctx.mem) Arguments(pstate); (*loc_args) << loc_arg; @@ -211,7 +225,7 @@ namespace Sass { !unquote(import_path).substr(0, 8).compare("https://") || !unquote(import_path).substr(0, 2).compare("//")) { - imp->urls().push_back(new (ctx.mem) String_Constant(pstate, import_path)); + imp->urls().push_back(new (ctx.mem) String_Quoted(pstate, import_path)); } else { add_single_file(imp, import_path); @@ -360,6 +374,7 @@ namespace Sass { return var; } + /* not used anymore - remove? Propset* Parser::parse_propset() { String* property_segment; @@ -368,17 +383,20 @@ namespace Sass { } else { lex< sequence< optional< exactly<'*'> >, identifier > >(); - property_segment = new (ctx.mem) String_Constant(pstate, lexed); + property_segment = new (ctx.mem) String_Quoted(pstate, lexed); } Propset* propset = new (ctx.mem) Propset(pstate, property_segment); + // debug_ast(property_segment); lex< exactly<':'> >(); if (!peek< exactly<'{'> >()) error("expected a '{' after namespaced property", pstate); propset->block(parse_block()); + propset->tabs(indentation); + return propset; - } + } */ Ruleset* Parser::parse_ruleset(Selector_Lookahead lookahead) { @@ -404,23 +422,23 @@ namespace Sass { { lex< optional_spaces >(); const char* i = position; - const char* p; String_Schema* schema = new (ctx.mem) String_Schema(pstate); - while (i < end_of_selector) { - p = find_first_in_interval< exactly >(i, end_of_selector); - if (p) { - // accumulate the preceding segment if there is one - if (i < p) (*schema) << new (ctx.mem) String_Constant(pstate, Token(i, p, Position(0, 0))); - // find the end of the interpolant and parse it - const char* j = find_first_in_interval< exactly >(p, end_of_selector); - Expression* interp_node = Parser::from_token(Token(p+2, j, Position(0, 0)), ctx, pstate).parse_list(); - interp_node->is_interpolant(true); - (*schema) << interp_node; - i = j + 1; - } - else { // no interpolants left; add the last segment if there is one - if (i < end_of_selector) (*schema) << new (ctx.mem) String_Constant(pstate, Token(i, end_of_selector, Position(0, 0))); + // try to parse mutliple interpolants + if (const char* p = find_first_in_interval< exactly >(i, end_of_selector)) { + // accumulate the preceding segment if the position has advanced + if (i < p) (*schema) << new (ctx.mem) String_Quoted(pstate, string(i, p)); + // skip to the delimiter by skipping occurences in quoted strings + const char* j = skip_over_scopes< exactly, exactly >(p + 2, end_of_selector); + Expression* interpolant = Parser::from_c_str(p+2, j, ctx, pstate).parse_list(); + interpolant->is_interpolant(true); + (*schema) << interpolant; + i = j; + } + // no more interpolants have been found + // add the last segment if there is one + else { + if (i < end_of_selector) (*schema) << new (ctx.mem) String_Quoted(pstate, string(i, end_of_selector)); break; } } @@ -430,10 +448,12 @@ namespace Sass { Selector_List* Parser::parse_selector_group() { - To_String to_string; + bool reloop = true; + To_String to_string(&ctx); lex< optional_spaces_and_comments >(); Selector_List* group = new (ctx.mem) Selector_List(pstate); do { + reloop = false; if (peek< exactly<'{'> >() || peek< exactly<'}'> >() || peek< exactly<')'> >() || @@ -453,17 +473,28 @@ namespace Sass { comb = new (ctx.mem) Complex_Selector(sel_source_position, Complex_Selector::ANCESTOR_OF, ref_wrap, comb); comb->has_reference(true); } + if (peek_newline()) ref_wrap->has_line_break(true); + } + while (peek< sequence< optional_spaces_and_comments, exactly<','> > >()) + { + // consume everything up and including the comma speparator + reloop = lex< sequence< optional_spaces_and_comments, exactly<','> > >(); + // remember line break (also between some commas) + if (peek_newline()) comb->has_line_feed(true); + if (comb->tail() && peek_newline()) comb->tail()->has_line_feed(true); + if (comb->tail() && comb->tail()->head() && peek_newline()) comb->tail()->head()->has_line_feed(true); + // remember line break (also between some commas) } (*group) << comb; } - while (lex< one_plus< sequence< optional_spaces_and_comments, exactly<','> > > >()); + while (reloop); while (lex< optional >()); // JMA - ignore optional flag if it follows the selector group return group; } Complex_Selector* Parser::parse_selector_combination() { - lex< optional_spaces_and_comments >(); + // lex< optional_spaces_and_comments >(); Position sel_source_position(-1); Compound_Selector* lhs; if (peek< exactly<'+'> >() || @@ -475,6 +506,7 @@ namespace Sass { else { lhs = parse_simple_selector_sequence(); sel_source_position = before_token; + lhs->has_line_break(peek_newline()); } Complex_Selector::Combinator cmb; @@ -482,6 +514,7 @@ namespace Sass { else if (lex< exactly<'~'> >()) cmb = Complex_Selector::PRECEDES; else if (lex< exactly<'>'> >()) cmb = Complex_Selector::PARENT_OF; else cmb = Complex_Selector::ANCESTOR_OF; + bool cpx_lf = peek_newline(); Complex_Selector* rhs; if (peek< exactly<','> >() || @@ -498,7 +531,9 @@ namespace Sass { sel_source_position = before_token; } if (!sel_source_position.line) sel_source_position = before_token; - return new (ctx.mem) Complex_Selector(ParserState(path, sel_source_position, Offset(0, 0)), cmb, lhs, rhs); + Complex_Selector* cpx = new (ctx.mem) Complex_Selector(ParserState(path, sel_source_position, Offset(0, 0)), cmb, lhs, rhs); + if (cpx_lf) cpx->has_line_break(cpx_lf); + return cpx; } Compound_Selector* Parser::parse_simple_selector_sequence() @@ -510,13 +545,13 @@ namespace Sass { (*seq) << new (ctx.mem) Selector_Reference(pstate); sawsomething = true; // if you see a space after a &, then you're done - if(lex< spaces >()) { + if(peek< spaces >()) { return seq; } } if (sawsomething && lex< sequence< negate< functional >, alternatives< identifier_fragment, universal, quoted_string, dimension, percentage, number > > >()) { // saw an ampersand, then allow type selectors with arbitrary number of hyphens at the beginning - (*seq) << new (ctx.mem) Type_Selector(pstate, lexed); + (*seq) << new (ctx.mem) Type_Selector(pstate, unquote(lexed)); } else if (lex< sequence< negate< functional >, alternatives< type_selector, universal, quoted_string, dimension, percentage, number > > >()) { // if you see a type selector (*seq) << new (ctx.mem) Type_Selector(pstate, lexed); @@ -544,10 +579,10 @@ namespace Sass { Simple_Selector* Parser::parse_simple_selector() { if (lex< id_name >() || lex< class_name >()) { - return new (ctx.mem) Selector_Qualifier(pstate, lexed); + return new (ctx.mem) Selector_Qualifier(pstate, unquote(lexed)); } else if (lex< quoted_string >() || lex< number >()) { - return new (ctx.mem) Type_Selector(pstate, lexed); + return new (ctx.mem) Type_Selector(pstate, unquote(lexed)); } else if (peek< pseudo_not >()) { return parse_negated_selector(); @@ -559,7 +594,7 @@ namespace Sass { return parse_attribute_selector(); } else if (lex< placeholder >()) { - return new (ctx.mem) Selector_Placeholder(pstate, lexed); + return new (ctx.mem) Selector_Placeholder(pstate, unquote(lexed)); } else { error("invalid selector after " + lexed.to_string(), pstate); @@ -587,16 +622,16 @@ namespace Sass { ParserState p = pstate; Selector* wrapped = 0; if (lex< alternatives< even, odd > >()) { - expr = new (ctx.mem) String_Constant(p, lexed); + expr = new (ctx.mem) String_Quoted(p, lexed); } else if (peek< binomial >(position)) { lex< sequence< optional< coefficient >, exactly<'n'> > >(); - String_Constant* var_coef = new (ctx.mem) String_Constant(p, lexed); + String_Constant* var_coef = new (ctx.mem) String_Quoted(p, lexed); lex< sign >(); - String_Constant* op = new (ctx.mem) String_Constant(p, lexed); + String_Constant* op = new (ctx.mem) String_Quoted(p, lexed); // Binary_Expression::Type op = (lexed == "+" ? Binary_Expression::ADD : Binary_Expression::SUB); lex< digits >(); - String_Constant* constant = new (ctx.mem) String_Constant(p, lexed); + String_Constant* constant = new (ctx.mem) String_Quoted(p, lexed); // expr = new (ctx.mem) Binary_Expression(p, op, var_coef, constant); String_Schema* schema = new (ctx.mem) String_Schema(p, 3); *schema << var_coef << op << constant; @@ -610,17 +645,17 @@ namespace Sass { lex< sequence< optional, optional, exactly<'n'> > >(); - expr = new (ctx.mem) String_Constant(p, lexed); + expr = new (ctx.mem) String_Quoted(p, lexed); } else if (lex< sequence< optional, digits > >()) { - expr = new (ctx.mem) String_Constant(p, lexed); + expr = new (ctx.mem) String_Quoted(p, lexed); } else if (peek< sequence< identifier, optional_spaces_and_comments, exactly<')'> > >()) { lex< identifier >(); - expr = new (ctx.mem) String_Constant(p, lexed); + expr = new (ctx.mem) String_Quoted(p, lexed); } else if (lex< quoted_string >()) { - expr = new (ctx.mem) String_Constant(p, lexed); + expr = new (ctx.mem) String_Quoted(p, lexed); } else if (peek< exactly<')'> >()) { expr = new (ctx.mem) String_Constant(p, ""); @@ -635,7 +670,7 @@ namespace Sass { return new (ctx.mem) Pseudo_Selector(p, name, expr); } else if (lex < sequence< pseudo_prefix, identifier > >()) { - return new (ctx.mem) Pseudo_Selector(pstate, lexed); + return new (ctx.mem) Pseudo_Selector(pstate, unquote(lexed)); } else { error("unrecognized pseudo-class or pseudo-element", pstate); @@ -659,10 +694,10 @@ namespace Sass { String* value = 0; if (lex< identifier >()) { - value = new (ctx.mem) String_Constant(p, lexed, true); + value = new (ctx.mem) String_Constant(p, lexed); } else if (lex< quoted_string >()) { - value = parse_interpolated_chunk(lexed); + value = parse_interpolated_chunk(lexed, true); // needed! } else { error("expected a string constant or identifier in attribute selector for " + name, pstate); @@ -681,8 +716,9 @@ namespace Sass { // JMA - ensure that a block containing only block_comments is parsed while (lex< block_comment >()) { + bool is_important = lexed.begin[2] == '!'; String* contents = parse_interpolated_chunk(lexed); - Comment* comment = new (ctx.mem) Comment(pstate, contents); + Comment* comment = new (ctx.mem) Comment(pstate, contents, is_important); (*block) << comment; } @@ -693,15 +729,17 @@ namespace Sass { } semicolon = false; while (lex< block_comment >()) { + bool is_important = lexed.begin[2] == '!'; String* contents = parse_interpolated_chunk(lexed); - Comment* comment = new (ctx.mem) Comment(pstate, contents); + Comment* comment = new (ctx.mem) Comment(pstate, contents, is_important); (*block) << comment; } if (lex< sequence< exactly<'}'>, zero_plus< exactly<';'> > > >()) break; } if (lex< block_comment >()) { + bool is_important = lexed.begin[2] == '!'; String* contents = parse_interpolated_chunk(lexed); - Comment* comment = new (ctx.mem) Comment(pstate, contents); + Comment* comment = new (ctx.mem) Comment(pstate, contents, is_important); (*block) << comment; } else if (peek< import >(position)) { @@ -805,23 +843,28 @@ namespace Sass { } else if ((lookahead_result = lookahead_for_selector(position)).found) { (*block) << parse_ruleset(lookahead_result); - } + }/* not used anymore - remove? else if (peek< sequence< optional< exactly<'*'> >, alternatives< identifier_schema, identifier >, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) { (*block) << parse_propset(); - } + }*/ else if (!peek< exactly<';'> >()) { + bool indent = ! peek< sequence< optional< exactly<'*'> >, alternatives< identifier_schema, identifier >, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position); + /* not used anymore - remove? if (peek< sequence< optional< exactly<'*'> >, identifier_schema, exactly<':'>, exactly<'{'> > >()) { (*block) << parse_propset(); } else if (peek< sequence< optional< exactly<'*'> >, identifier, exactly<':'>, exactly<'{'> > >()) { (*block) << parse_propset(); } - else { + else */ { Declaration* decl = parse_declaration(); + decl->tabs(indentation); (*block) << decl; if (peek< exactly<'{'> >()) { // parse a propset that rides on the declaration's property + if (indent) indentation++; Propset* ps = new (ctx.mem) Propset(pstate, decl->property(), parse_block()); + if (indent) indentation--; (*block) << ps; } else { @@ -832,8 +875,9 @@ namespace Sass { } else lex< one_plus< exactly<';'> > >(); while (lex< block_comment >()) { + bool is_important = lexed.begin[2] == '!'; String* contents = parse_interpolated_chunk(lexed); - Comment* comment = new (ctx.mem) Comment(pstate, contents); + Comment* comment = new (ctx.mem) Comment(pstate, contents, is_important); (*block) << comment; } } @@ -846,10 +890,10 @@ namespace Sass { prop = parse_identifier_schema(); } else if (lex< sequence< optional< exactly<'*'> >, identifier > >()) { - prop = new (ctx.mem) String_Constant(pstate, lexed); + prop = new (ctx.mem) String_Quoted(pstate, lexed); } else if (lex< custom_property_name >()) { - prop = new (ctx.mem) String_Constant(pstate, lexed); + prop = new (ctx.mem) String_Quoted(pstate, lexed); } else { error("invalid property name", pstate); @@ -866,7 +910,7 @@ namespace Sass { Expression* Parser::parse_map() { - To_String to_string; + To_String to_string(&ctx); Expression* key = parse_list(); // it's not a map so return the lexed value as a list value @@ -1173,8 +1217,9 @@ namespace Sass { if (lex< important >()) { return new (ctx.mem) String_Constant(pstate, "!important"); } - if (lex< value_schema >()) - { return Parser::from_token(lexed, ctx, pstate).parse_value_schema(); } + const char* stop; + if ((stop = peek< value_schema >())) + { return parse_value_schema(stop); } if (lex< sequence< true_val, negate< identifier > > >()) { return new (ctx.mem) Boolean(pstate, true); } @@ -1186,9 +1231,9 @@ namespace Sass { { return new (ctx.mem) Null(pstate); } if (lex< identifier >()) { - String_Constant* str = new (ctx.mem) String_Constant(pstate, lexed); + String_Constant* str = new (ctx.mem) String_Quoted(pstate, lexed); // Dont' delay this string if it is a name color. Fixes #652. - str->is_delayed(ctx.names_to_colors.count(lexed) == 0); + str->is_delayed(ctx.names_to_colors.count(unquote(lexed)) == 0); return str; } @@ -1214,7 +1259,7 @@ namespace Sass { // Special case handling for `%` proceeding an interpolant. if (lex< sequence< exactly<'%'>, optional< percentage > > >()) - { return new (ctx.mem) String_Constant(pstate, lexed); } + { return new (ctx.mem) String_Quoted(pstate, lexed); } error("error reading values after " + lexed.to_string(), pstate); @@ -1222,32 +1267,37 @@ namespace Sass { return 0; } - String* Parser::parse_interpolated_chunk(Token chunk) + // this parses interpolation inside other strings + // means the result should later be quoted again + String* Parser::parse_interpolated_chunk(Token chunk, bool constant) { const char* i = chunk.begin; // see if there any interpolants - const char* p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly > >(chunk.begin, chunk.end); + const char* p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly > >(i, chunk.end); if (!p) { - String_Constant* str_node = new (ctx.mem) String_Constant(pstate, chunk, dequote); - str_node->is_delayed(true); - return str_node; + String_Quoted* str_quoted = new (ctx.mem) String_Quoted(pstate, string(i, chunk.end)); + if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); + str_quoted->is_delayed(true); + return str_quoted; } String_Schema* schema = new (ctx.mem) String_Schema(pstate); - schema->quote_mark(*chunk.begin); while (i < chunk.end) { p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly > >(i, chunk.end); if (p) { if (i < p) { - (*schema) << new (ctx.mem) String_Constant(pstate, Token(i, p, before_token)); // accumulate the preceding segment if it's nonempty + // accumulate the preceding segment if it's nonempty + (*schema) << new (ctx.mem) String_Quoted(pstate, string(i, p)); } - const char* j = find_first_in_interval< exactly >(p, chunk.end); // find the closing brace - if (j) { + // we need to skip anything inside strings + // create a new target in parser/prelexer + const char* j = skip_over_scopes< exactly, exactly >(p + 2, chunk.end); // find the closing brace + if (j) { --j; // parse the interpolant and accumulate it Expression* interp_node = Parser::from_token(Token(p+2, j, before_token), ctx, pstate).parse_list(); interp_node->is_interpolant(true); (*schema) << interp_node; - i = j+1; + i = j; } else { // throw an error if the interpolant is unterminated @@ -1255,9 +1305,11 @@ namespace Sass { } } else { // no interpolants left; add the last segment if nonempty - if (i < chunk.end) (*schema) << new (ctx.mem) String_Constant(pstate, Token(i, chunk.end, before_token)); + // check if we need quotes here (was not sure after merge) + if (i < chunk.end) (*schema) << new (ctx.mem) String_Quoted(pstate, string(i, chunk.end)); break; } + ++ i; } return schema; } @@ -1269,51 +1321,15 @@ namespace Sass { --str.end; --position; String_Constant* str_node = new (ctx.mem) String_Constant(pstate, str); - str_node->is_delayed(true); + // str_node->is_delayed(true); return str_node; } String* Parser::parse_string() { lex< quoted_string >(); - Token str(lexed); - return parse_interpolated_chunk(str); - // const char* i = str.begin; - // // see if there any interpolants - // const char* p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly > >(str.begin, str.end); - // if (!p) { - // String_Constant* str_node = new (ctx.mem) String_Constant(pstate, str); - // str_node->is_delayed(true); - // return str_node; - // } - - // String_Schema* schema = new (ctx.mem) String_Schema(pstate); - // schema->quote_mark(*str.begin); - // while (i < str.end) { - // p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly > >(i, str.end); - // if (p) { - // if (i < p) { - // (*schema) << new (ctx.mem) String_Constant(pstate, Token(i, p)); // accumulate the preceding segment if it's nonempty - // } - // const char* j = find_first_in_interval< exactly >(p, str.end); // find the closing brace - // if (j) { - // // parse the interpolant and accumulate it - // Expression* interp_node = Parser::from_token(Token(p+2, j), ctx, pstate).parse_list(); - // interp_node->is_interpolant(true); - // (*schema) << interp_node; - // i = j+1; - // } - // else { - // // throw an error if the interpolant is unterminated - // error("unterminated interpolant inside string constant " + str.to_string(), pstate); - // } - // } - // else { // no interpolants left; add the last segment if nonempty - // if (i < str.end) (*schema) << new (ctx.mem) String_Constant(pstate, Token(i, str.end)); - // break; - // } - // } - // return schema; + Token token(lexed); + return parse_interpolated_chunk(token); } String* Parser::parse_ie_property() @@ -1324,7 +1340,7 @@ namespace Sass { // see if there any interpolants const char* p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly > >(str.begin, str.end); if (!p) { - String_Constant* str_node = new (ctx.mem) String_Constant(pstate, str); + String_Constant* str_node = new (ctx.mem) String_Constant(pstate, normalize_wspace(string(str.begin, str.end))); str_node->is_delayed(true); return str_node; } @@ -1334,15 +1350,15 @@ namespace Sass { p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly > >(i, str.end); if (p) { if (i < p) { - (*schema) << new (ctx.mem) String_Constant(pstate, Token(i, p, before_token)); // accumulate the preceding segment if it's nonempty + (*schema) << new (ctx.mem) String_Constant(pstate, string(i, p)); // accumulate the preceding segment if it's nonempty } - const char* j = find_first_in_interval< exactly >(p, str.end); // find the closing brace + const char* j = skip_over_scopes< exactly, exactly >(p+2, str.end); // find the closing brace if (j) { // parse the interpolant and accumulate it Expression* interp_node = Parser::from_token(Token(p+2, j, before_token), ctx, pstate).parse_list(); interp_node->is_interpolant(true); (*schema) << interp_node; - i = j+1; + i = j; } else { // throw an error if the interpolant is unterminated @@ -1350,7 +1366,7 @@ namespace Sass { } } else { // no interpolants left; add the last segment if nonempty - if (i < str.end) (*schema) << new (ctx.mem) String_Constant(pstate, Token(i, str.end, before_token)); + if (i < str.end) (*schema) << new (ctx.mem) String_Constant(pstate, string(i, str.end)); break; } } @@ -1360,27 +1376,27 @@ namespace Sass { String* Parser::parse_ie_keyword_arg() { String_Schema* kwd_arg = new (ctx.mem) String_Schema(pstate, 3); - if (lex< variable >()) *kwd_arg << new (ctx.mem) Variable(pstate, Util::normalize_underscores(lexed)); - else { + if (lex< variable >()) { + *kwd_arg << new (ctx.mem) Variable(pstate, Util::normalize_underscores(lexed)); + } else { lex< alternatives< identifier_schema, identifier > >(); - *kwd_arg << new (ctx.mem) String_Constant(pstate, lexed); + *kwd_arg << new (ctx.mem) String_Quoted(pstate, lexed); } lex< exactly<'='> >(); - *kwd_arg << new (ctx.mem) String_Constant(pstate, lexed); + *kwd_arg << new (ctx.mem) String_Quoted(pstate, lexed); if (peek< variable >()) *kwd_arg << parse_list(); else if (lex< number >()) *kwd_arg << new (ctx.mem) Textual(pstate, Textual::NUMBER, Util::normalize_decimals(lexed)); - else { - lex< alternatives< identifier_schema, identifier, number, hex > >(); - *kwd_arg << new (ctx.mem) String_Constant(pstate, lexed); + else if (lex< alternatives< identifier_schema, identifier, number, hexa, hex > >()) { + *kwd_arg << new (ctx.mem) String_Quoted(pstate, lexed); } return kwd_arg; } - String_Schema* Parser::parse_value_schema() + String_Schema* Parser::parse_value_schema(const char* stop) { String_Schema* schema = new (ctx.mem) String_Schema(pstate); size_t num_items = 0; - while (position < end) { + while (position < stop) { if (lex< interpolant >()) { Token insides(Token(lexed.begin + 2, lexed.end - 1, before_token)); Expression* interp_node = Parser::from_token(insides, ctx, pstate).parse_list(); @@ -1391,7 +1407,7 @@ namespace Sass { (*schema) << new (ctx.mem) String_Constant(pstate, lexed); } else if (lex< identifier >()) { - (*schema) << new (ctx.mem) String_Constant(pstate, lexed); + (*schema) << new (ctx.mem) String_Quoted(pstate, lexed); } else if (lex< percentage >()) { (*schema) << new (ctx.mem) Textual(pstate, Textual::PERCENTAGE, lexed); @@ -1403,11 +1419,10 @@ namespace Sass { (*schema) << new (ctx.mem) Textual(pstate, Textual::NUMBER, lexed); } else if (lex< hex >()) { - (*schema) << new (ctx.mem) Textual(pstate, Textual::HEX, lexed); + (*schema) << new (ctx.mem) Textual(pstate, Textual::HEX, unquote(lexed)); } else if (lex< quoted_string >()) { (*schema) << new (ctx.mem) String_Constant(pstate, lexed); - if (!num_items) schema->quote_mark(*lexed.begin); } else if (lex< variable >()) { (*schema) << new (ctx.mem) Variable(pstate, Util::normalize_underscores(lexed)); @@ -1420,6 +1435,7 @@ namespace Sass { return schema; } + /* not used anymore - remove? String_Schema* Parser::parse_url_schema() { String_Schema* schema = new (ctx.mem) String_Schema(pstate); @@ -1427,7 +1443,7 @@ namespace Sass { while (position < end) { if (position[0] == '/') { lexed = Token(position, position+1, before_token); - (*schema) << new (ctx.mem) String_Constant(pstate, lexed); + (*schema) << new (ctx.mem) String_Quoted(pstate, lexed); ++position; } else if (lex< interpolant >()) { @@ -1437,27 +1453,30 @@ namespace Sass { (*schema) << interp_node; } else if (lex< sequence< identifier, exactly<':'> > >()) { - (*schema) << new (ctx.mem) String_Constant(pstate, lexed); + (*schema) << new (ctx.mem) String_Quoted(pstate, lexed); } else if (lex< filename >()) { - (*schema) << new (ctx.mem) String_Constant(pstate, lexed); + (*schema) << new (ctx.mem) String_Quoted(pstate, lexed); } else { error("error parsing interpolated url", pstate); } } return schema; - } + } */ + // this parses interpolation outside other strings + // means the result must not be quoted again later String* Parser::parse_identifier_schema() { + // first lex away whatever we have found lex< sequence< optional< exactly<'*'> >, identifier_schema > >(); Token id(lexed); const char* i = id.begin; // see if there any interpolants const char* p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly > >(id.begin, id.end); if (!p) { - return new (ctx.mem) String_Constant(pstate, id); + return new (ctx.mem) String_Quoted(pstate, string(id.begin, id.end)); } String_Schema* schema = new (ctx.mem) String_Schema(pstate); @@ -1465,16 +1484,19 @@ namespace Sass { p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly > >(i, id.end); if (p) { if (i < p) { - (*schema) << new (ctx.mem) String_Constant(pstate, Token(i, p, before_token)); // accumulate the preceding segment if it's nonempty + // accumulate the preceding segment if it's nonempty + (*schema) << new (ctx.mem) String_Constant(pstate, string(i, p)); } - const char* j = find_first_in_interval< exactly >(p, id.end); // find the closing brace + // we need to skip anything inside strings + // create a new target in parser/prelexer + const char* j = skip_over_scopes< exactly, exactly >(p+2, id.end); // find the closing brace if (j) { // parse the interpolant and accumulate it Expression* interp_node = Parser::from_token(Token(p+2, j, before_token), ctx, pstate).parse_list(); interp_node->is_interpolant(true); (*schema) << interp_node; schema->has_interpolants(true); - i = j+1; + i = j; } else { // throw an error if the interpolant is unterminated @@ -1482,7 +1504,7 @@ namespace Sass { } } else { // no interpolants left; add the last segment if nonempty - if (i < id.end) (*schema) << new (ctx.mem) String_Constant(pstate, Token(i, id.end, before_token)); + if (i < end) (*schema) << new (ctx.mem) String_Quoted(pstate, string(i, id.end)); break; } } @@ -1637,7 +1659,7 @@ namespace Sass { else if (lex< exactly< only_kwd > >()) media_query->is_restricted(true); if (peek< identifier_schema >()) media_query->media_type(parse_identifier_schema()); - else if (lex< identifier >()) media_query->media_type(new (ctx.mem) String_Constant(pstate, lexed)); + else if (lex< identifier >()) media_query->media_type(new (ctx.mem) String_Quoted(pstate, lexed)); else (*media_query) << parse_media_expression(); while (lex< exactly< and_kwd > >()) (*media_query) << parse_media_expression(); @@ -1792,7 +1814,7 @@ namespace Sass { if (!peek< alternatives< with_directive, without_directive > >()) { const char* i = position; - const char* p = peek< until_closing_paren >(i); + const char* p = peek< until<')'> >(i); Token* t = new Token(i, p, Position(0, 0)); error("Invalid CSS after \"(\": expected \"with\" or \"without\", was \""+t->to_string()+"\"", pstate); } diff --git a/parser.hpp b/parser.hpp index ab3c714ed3..fbefbb5c32 100644 --- a/parser.hpp +++ b/parser.hpp @@ -6,7 +6,7 @@ #include #include "ast.hpp" -#include "token.hpp" +#include "position.hpp" #include "context.hpp" #include "position.hpp" #include "prelexer.hpp" @@ -38,19 +38,20 @@ namespace Sass { Position before_token; Position after_token; ParserState pstate; + int indentation; Token lexed; - bool dequote; bool in_at_root; Parser(Context& ctx, ParserState pstate) : ParserState(pstate), ctx(ctx), stack(vector()), - source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate("[NULL]") - { dequote = false; in_at_root = false; stack.push_back(nothing); } + source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate("[NULL]"), indentation(0) + { in_at_root = false; stack.push_back(nothing); } - static Parser from_string(string src, Context& ctx, ParserState pstate = ParserState("[STRING]")); + static Parser from_string(const string& src, Context& ctx, ParserState pstate = ParserState("[STRING]")); static Parser from_c_str(const char* src, Context& ctx, ParserState pstate = ParserState("[CSTRING]")); + static Parser from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate = ParserState("[CSTRING]")); static Parser from_token(Token t, Context& ctx, ParserState pstate = ParserState("[TOKEN]")); #ifdef __clang__ @@ -65,6 +66,9 @@ namespace Sass { #endif + + bool peek_newline(const char* start = 0); + template const char* peek(const char* start = 0) { @@ -77,7 +81,7 @@ namespace Sass { else if (/*mx == ancestor_of ||*/ mx == no_spaces) { it_before_token = position; } - else if (mx == spaces || mx == ancestor_of) { + else if (mx == spaces) { it_before_token = mx(start); if (it_before_token) { return it_before_token; @@ -111,6 +115,9 @@ namespace Sass { const char* lex() { + // remeber interesting position + const char* wspace_start = position; + // advance position for next call before_token = after_token; @@ -121,7 +128,7 @@ namespace Sass { // a block comment can be preceded by spaces and/or line comments it_before_token = zero_plus< alternatives >(position); } - else if (mx == url || mx == ancestor_of || mx == no_spaces) { + else if (mx == url || mx == no_spaces) { // parse everything literally it_before_token = position; } @@ -136,6 +143,10 @@ namespace Sass { } } + else if (mx == optional_spaces_and_comments) { + it_before_token = position; + } + else if (mx == optional_spaces) { // ToDo: what are optiona_spaces ??? it_before_token = optional_spaces(position); @@ -180,9 +191,9 @@ namespace Sass { after_token = after_token + size; // create parsed token string (public member) - lexed = Token(it_before_token, it_after_token, before_token); - - pstate = ParserState(path, Position(before_token.file, before_token.line, before_token.column), size); + lexed = Token(wspace_start, it_before_token, it_after_token, optional_spaces_and_comments(it_after_token) ? optional_spaces_and_comments(it_after_token) : it_after_token, before_token); + Position pos(before_token.file, before_token.line, before_token.column); + pstate = ParserState(path, lexed, pos, size); // advance internal char iterator return position = it_after_token; @@ -207,7 +218,7 @@ namespace Sass { Arguments* parse_arguments(); Argument* parse_argument(); Assignment* parse_assignment(); - Propset* parse_propset(); + // Propset* parse_propset(); Ruleset* parse_ruleset(Selector_Lookahead lookahead); Selector_Schema* parse_selector_schema(const char* end_of_selector); Selector_List* parse_selector_group(); @@ -234,14 +245,14 @@ namespace Sass { Function_Call* parse_calc_function(); Function_Call* parse_function_call(); Function_Call_Schema* parse_function_call_schema(); - String* parse_interpolated_chunk(Token); + String* parse_interpolated_chunk(Token, bool constant = false); String* parse_string(); String_Constant* parse_static_value(); String* parse_ie_property(); String* parse_ie_keyword_arg(); - String_Schema* parse_value_schema(); + String_Schema* parse_value_schema(const char* stop); String* parse_identifier_schema(); - String_Schema* parse_url_schema(); + // String_Schema* parse_url_schema(); If* parse_if_directive(bool else_if = false); For* parse_for_directive(); Each* parse_each_directive(); diff --git a/position.cpp b/position.cpp index 0a7a358797..0217faf4f6 100644 --- a/position.cpp +++ b/position.cpp @@ -42,6 +42,9 @@ namespace Sass { Position::Position(const size_t file) : Offset(0, 0), file(file) { } + Position::Position(const size_t file, const Offset& offset) + : Offset(offset), file(file) { } + Position::Position(const size_t line, const size_t column) : Offset(line, column), file(-1) { } @@ -50,28 +53,22 @@ namespace Sass { ParserState::ParserState(string path) - : Position(-1, 0, 0), path(path), offset(0, 0) { } + : Position(-1, 0, 0), path(path), offset(0, 0), token() { } ParserState::ParserState(string path, const size_t file) - : Position(file, 0, 0), path(path), offset(0, 0) { } + : Position(file, 0, 0), path(path), offset(0, 0), token() { } ParserState::ParserState(string path, Position position, Offset offset) - : Position(position), path(path), offset(offset) { } + : Position(position), path(path), offset(offset), token() { } + ParserState::ParserState(string path, Token token, Position position, Offset offset) + : Position(position), path(path), offset(offset), token(token) { } Position Position::inc(const char* begin, const char* end) const { - Position pos(file, line, column); - while (begin < end && *begin) { - if (*begin == '\n') { - ++ pos.line; - pos.column = 0; - } else { - ++ pos.column; - } - ++begin; - } - return pos; + Offset offset(line, column); + offset.inc(begin, end); + return Position(file, offset); } bool Position::operator== (const Position &pos) const @@ -89,19 +86,21 @@ namespace Sass { return Position(file, line + off.line, off.line > 0 ? off.column : off.column + column); } + /* not used anymore - remove? std::ostream& operator<<(std::ostream& strm, const Offset& off) { if (off.line == string::npos) strm << "-1:"; else strm << off.line << ":"; if (off.column == string::npos) strm << "-1"; else strm << off.column; return strm; - } + } */ + /* not used anymore - remove? std::ostream& operator<<(std::ostream& strm, const Position& pos) { if (pos.file != string::npos) strm << pos.file << ":"; if (pos.line == string::npos) strm << "-1:"; else strm << pos.line << ":"; if (pos.column == string::npos) strm << "-1"; else strm << pos.column; return strm; - } + } */ } \ No newline at end of file diff --git a/position.hpp b/position.hpp index 07866f3275..b73424f414 100644 --- a/position.hpp +++ b/position.hpp @@ -2,7 +2,9 @@ #define SASS_POSITION_H #include +#include #include +#include #include namespace Sass { @@ -23,7 +25,7 @@ namespace Sass { const Offset operator+ (const Offset &off) const; public: // overload output stream operator - friend ostream& operator<<(ostream& strm, const Offset& off); + // friend ostream& operator<<(ostream& strm, const Offset& off); public: Offset off() { return *this; }; @@ -38,6 +40,7 @@ namespace Sass { public: // c-tor Position(const size_t file); // line(0), column(0) + Position(const size_t file, const Offset& offset); Position(const size_t line, const size_t column); // file(-1) Position(const size_t file, const size_t line, const size_t column); @@ -49,27 +52,61 @@ namespace Sass { Position inc(const char* begin, const char* end) const; public: // overload output stream operator - friend ostream& operator<<(ostream& strm, const Position& pos); + // friend ostream& operator<<(ostream& strm, const Position& pos); public: size_t file; }; - class ParserState : public Position{ + // Token type for representing lexed chunks of text + class Token { + public: + const char* prefix; + const char* begin; + const char* end; + const char* suffix; + Position start; + Position stop; + + Token() + : prefix(0), begin(0), end(0), suffix(0), start(0), stop(0) { } + Token(const char* b, const char* e, const Position pos) + : prefix(b), begin(b), end(e), suffix(e), start(pos), stop(pos.inc(b, e)) { } + Token(const char* s, const Position pos) + : prefix(s), begin(s), end(s + strlen(s)), suffix(end), start(pos), stop(pos.inc(s, s + strlen(s))) { } + Token(const char* p, const char* b, const char* e, const char* s, const Position pos) + : prefix(p), begin(b), end(e), suffix(s), start(pos), stop(pos.inc(b, e)) { } + + size_t length() const { return end - begin; } + string ws_before() const { return string(prefix, begin); } + string to_string() const { return string(begin, end); } + string ws_after() const { return string(end, suffix); } + + // string unquote() const; + + operator bool() { return begin && end && begin >= end; } + operator string() { return to_string(); } + + bool operator==(Token t) { return to_string() == t.to_string(); } + }; - public: + class ParserState : public Position { + + public: // c-tor ParserState(string path); ParserState(string path, const size_t file); ParserState(string path, Position position, Offset offset); + ParserState(string path, Token token, Position position, Offset offset); - public: + public: // down casts Offset off() { return *this; }; Position pos() { return *this; }; public: string path; Offset offset; + Token token; }; diff --git a/prelexer.cpp b/prelexer.cpp index 900f981fd6..f3de77f3e7 100644 --- a/prelexer.cpp +++ b/prelexer.cpp @@ -2,9 +2,10 @@ #include #include #include -#include "constants.hpp" -#include "prelexer.hpp" #include "util.hpp" +#include "position.hpp" +#include "prelexer.hpp" +#include "constants.hpp" namespace Sass { @@ -13,13 +14,15 @@ namespace Sass { namespace Prelexer { using std::ptrdiff_t; // Matches zero characters (always succeeds without consuming input). + /* not used anymore - remove? const char* epsilon(char *src) { return src; - } + }*/ // Matches the empty string. + /* not used anymore - remove? const char* empty(char *src) { return *src ? 0 : src; - } + }*/ // Match any single character. const char* any_char(const char* src) { return *src ? src+1 : src; } @@ -57,6 +60,15 @@ namespace Sass { return alternatives(src); } + const char* wspaces(const char* src) { + return + alternatives< + exactly<' '>, + exactly<'\t'> + >(src); + } + + /* not used anymore - remove? const char* newline(const char* src) { return alternatives< @@ -65,29 +77,26 @@ namespace Sass { exactly<'\r'>, exactly<'\f'> >(src); - } + }*/ + /* not used anymore - remove? const char* whitespace(const char* src) { - return - alternatives< - newline, - exactly<' '>, - exactly<'\t'> - >(src); - } + return spaces(src); + }*/ + /* not used anymore - remove? const char* escape(const char* src) { return sequence< exactly<'\\'>, any_char >(src); - } + }*/ // Whitespace handling. const char* optional_spaces(const char* src) { return optional(src); } - const char* optional_comment(const char* src) { return optional(src); } + // const char* optional_comment(const char* src) { return optional(src); } const char* optional_spaces_and_comments(const char* src) { return zero_plus< alternatives >(src); } @@ -120,6 +129,7 @@ namespace Sass { } // Match CSS selectors. + /* not used anymore - remove? const char* sel_ident(const char* src) { return sequence< optional< alternatives< exactly<'-'>, exactly<'|'> > >, alternatives< alpha, exactly<'_'>, backslash_something, exactly<'|'> >, @@ -128,7 +138,7 @@ namespace Sass { exactly<'_'>, exactly<'|'>, backslash_something > > >(src); - } + }*/ // Match CSS css variables. const char* custom_property_name(const char* src) { @@ -146,7 +156,7 @@ namespace Sass { // interpolants can be recursive/nested const char* interpolant(const char* src) { - return smartdel_by(src); + return recursive_scopes< exactly, exactly >(src); } // $re_squote = /'(?:$re_itplnt|\\.|[^'])*'/ @@ -202,11 +212,12 @@ namespace Sass { interpolant, zero_plus< alternatives< identifier, percentage, dimension, hex, number, quoted_string, exactly<'%'> > > > >(src); } + /* not used anymore - remove? const char* filename_schema(const char* src) { return one_plus< sequence< zero_plus< alternatives< identifier, number, exactly<'.'>, exactly<'/'> > >, interpolant, zero_plus< alternatives< identifier, number, exactly<'.'>, exactly<'/'> > > > >(src); - } + }*/ const char* filename(const char* src) { return one_plus< alternatives< identifier, number, exactly<'.'> > >(src); @@ -233,10 +244,6 @@ namespace Sass { return exactly(src); } - const char* until_closing_paren(const char* src) { - return until<')'>(src); - } - const char* media(const char* src) { return exactly(src); } @@ -245,17 +252,20 @@ namespace Sass { return exactly(src); } + /* not used anymore - remove? const char* keyframes(const char* src) { return sequence< exactly<'@'>, optional< vendor_prefix >, exactly< keyframes_kwd > >(src); - } + } */ + /* not used anymore - remove? const char* vendor_prefix(const char* src) { return alternatives< exactly< vendor_opera_kwd >, exactly< vendor_webkit_kwd >, exactly< vendor_mozilla_kwd >, exactly< vendor_ms_kwd >, exactly< vendor_khtml_kwd > >(src); - } + } */ + /* not used anymore - remove? const char* keyf(const char* src) { return one_plus< alternatives< to, from, percentage > >(src); - } + } */ const char* mixin(const char* src) { return exactly(src); @@ -342,9 +352,10 @@ namespace Sass { return exactly(src); } + /* not used anymore - remove? const char* directive(const char* src) { return sequence< exactly<'@'>, identifier >(src); - } + } */ const char* null(const char* src) { return exactly(src); @@ -413,9 +424,10 @@ namespace Sass { return sequence< number, exactly<'%'> >(src); } + /* not used anymore - remove? const char* em(const char* src) { return sequence< number, exactly >(src); - } + } */ const char* dimension(const char* src) { return sequence >(src); } @@ -435,31 +447,35 @@ namespace Sass { return (len != 5 && len != 8) ? 0 : p; } + /* no longer used - remove? const char* rgb_prefix(const char* src) { return exactly(src); - } + }*/ // Match CSS uri specifiers. const char* uri_prefix(const char* src) { return exactly(src); } // TODO: rename the following two functions + /* no longer used - remove? const char* uri(const char* src) { return sequence< exactly, optional, quoted_string, optional, exactly<')'> >(src); - } + }*/ + /* no longer used - remove? const char* url_value(const char* src) { return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol one_plus< sequence< zero_plus< exactly<'/'> >, filename > >, // one or more folders and/or trailing filename optional< exactly<'/'> > >(src); - } + }*/ + /* no longer used - remove? const char* url_schema(const char* src) { return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol filename_schema >(src); // optional trailing slash - } + }*/ // Match CSS "!important" keyword. const char* important(const char* src) { return sequence< exactly<'!'>, @@ -514,6 +530,7 @@ namespace Sass { const char* suffix_match(const char* src) { return exactly(src); } const char* substring_match(const char* src) { return exactly(src); } // Match CSS combinators. + /* not used anymore - remove? const char* adjacent_to(const char* src) { return sequence< optional_spaces, exactly<'+'> >(src); } @@ -525,7 +542,7 @@ namespace Sass { } const char* ancestor_of(const char* src) { return sequence< spaces, negate< exactly<'{'> > >(src); - } + }*/ // Match SCSS variable names. const char* variable(const char* src) { @@ -627,13 +644,14 @@ namespace Sass { } // Path matching functions. + /* not used anymore - remove? const char* folder(const char* src) { return sequence< zero_plus< any_char_except<'/'> >, exactly<'/'> >(src); } const char* folders(const char* src) { return zero_plus< folder >(src); - } + }*/ const char* chunk(const char* src) { char inside_str = 0; @@ -663,26 +681,30 @@ namespace Sass { } // follow the CSS spec more closely and see if this helps us scan URLs correctly + /* not used anymore - remove? const char* NL(const char* src) { return alternatives< exactly<'\n'>, sequence< exactly<'\r'>, exactly<'\n'> >, exactly<'\r'>, exactly<'\f'> >(src); - } + }*/ + /* not used anymore - remove? const char* H(const char* src) { return std::isxdigit(*src) ? src+1 : 0; - } + }*/ + /* not used anymore - remove? const char* unicode(const char* src) { return sequence< exactly<'\\'>, between, optional< class_char > >(src); - } + }*/ + /* not used anymore - remove? const char* ESCAPE(const char* src) { return alternatives< unicode, class_char >(src); - } + }*/ const char* url(const char* src) { return chunk(src); diff --git a/prelexer.hpp b/prelexer.hpp index 2c33242c99..e0950b5061 100644 --- a/prelexer.hpp +++ b/prelexer.hpp @@ -88,26 +88,25 @@ namespace Sass { } } - // Match a sequence of characters delimited by the supplied chars. - template - const char* smartdel_by(const char* src) { + // skip to delimiter (mx) inside given range + // this will savely skip over all quoted strings + // recursive skip stuff delimited by start/stop + // first start/opener must be consumed already! + template + const char* skip_over_scopes(const char* src, const char* end = 0) { size_t level = 0; bool in_squote = false; bool in_dquote = false; // bool in_braces = false; - src = exactly(src); - - if (!src) return 0; - - while (1) { + while (*src) { - // end of string? - if (!*src) return 0; + // check for abort condition + if (end && src >= end) break; // has escaped sequence? - if (!esc && *src == '\\') { + if (*src == '\\') { ++ src; // skip this (and next) } else if (*src == '"') { @@ -121,23 +120,35 @@ namespace Sass { } // find another opener inside? - else if (exactly(src)) { + else if (start(src)) { ++ level; // increase counter } // look for the closer (maybe final, maybe not) - else if (const char* stop = exactly(src)) { + else if (const char* final = stop(src)) { // only close one level? if (level > 0) -- level; // return position at end of stop // delimiter may be multiple chars - else return stop; + else return final; } // next ++ src; - } + + return 0; + } + + // Match a sequence of characters delimited by the supplied chars. + template + const char* recursive_scopes(const char* src) { + // parse opener + src = start(src); + // abort if not found + if (!src) return 0; + // parse the rest until final closer + return skip_over_scopes(src); } // Match a sequence of characters delimited by the supplied strings. @@ -154,58 +165,6 @@ namespace Sass { } } - // Match a sequence of characters delimited by the supplied strings. - template - const char* smartdel_by(const char* src) { - - size_t level = 0; - bool in_squote = false; - bool in_dquote = false; - // bool in_braces = false; - - src = exactly(src); - - if (!src) return 0; - - while (1) { - - // end of string? - if (!*src) return 0; - - // has escaped sequence? - if (!esc && *src == '\\') { - ++ src; // skip this (and next) - } - else if (*src == '"') { - in_dquote = ! in_dquote; - } - else if (*src == '\'') { - in_squote = ! in_squote; - } - else if (in_dquote || in_squote) { - // take everything literally - } - - // find another opener inside? - else if (exactly(src)) { - ++ level; // increase counter - } - - // look for the closer (maybe final, maybe not) - else if (const char* stop = exactly(src)) { - // only close one level? - if (level > 0) -- level; - // return position at end of stop - // delimiter may be multiple chars - else return stop; - } - - // next - ++ src; - - } - } - // Match any single character. const char* any_char(const char* src); // Match any single character except the supplied one. @@ -215,10 +174,10 @@ namespace Sass { } // Matches zero characters (always succeeds without consuming input). - const char* epsilon(const char*); + // const char* epsilon(const char*); // Matches the empty string. - const char* empty(const char*); + // const char* empty(const char*); // Succeeds of the supplied matcher fails, and vice versa. template @@ -415,6 +374,10 @@ namespace Sass { const char* xdigits(const char* src); const char* alnums(const char* src); const char* puncts(const char* src); + // Match certain white-space charactes. + const char* wspaces(const char* src); + // const char* newline(const char* src); + // const char* whitespace(const char* src); // Match a line comment. const char* line_comment(const char* src); @@ -434,7 +397,7 @@ namespace Sass { // Whitespace handling. const char* optional_spaces(const char* src); - const char* optional_comment(const char* src); + // const char* optional_comment(const char* src); const char* optional_spaces_and_comments(const char* src); const char* spaces_and_comments(const char* src); const char* no_spaces(const char* src); @@ -447,15 +410,14 @@ namespace Sass { const char* identifier(const char* src); const char* identifier_fragment(const char* src); // Match selector names. - const char* sel_ident(const char* src); - const char* until_closing_paren(const char* src); + // const char* sel_ident(const char* src); // Match interpolant schemas const char* identifier_schema(const char* src); const char* value_schema(const char* src); const char* filename(const char* src); - const char* filename_schema(const char* src); - const char* url_schema(const char* src); - const char* url_value(const char* src); + // const char* filename_schema(const char* src); + // const char* url_schema(const char* src); + // const char* url_value(const char* src); const char* vendor_prefix(const char* src); // Match CSS '@' keywords. const char* at_keyword(const char* src); @@ -465,8 +427,8 @@ namespace Sass { const char* without_directive(const char* src); const char* media(const char* src); const char* supports(const char* src); - const char* keyframes(const char* src); - const char* keyf(const char* src); + // const char* keyframes(const char* src); + // const char* keyf(const char* src); const char* mixin(const char* src); const char* function(const char* src); const char* return_directive(const char* src); @@ -492,7 +454,7 @@ namespace Sass { const char* err(const char* src); const char* dbg(const char* src); - const char* directive(const char* src); + // const char* directive(const char* src); const char* at_keyword(const char* src); const char* null(const char* src); @@ -522,10 +484,10 @@ namespace Sass { const char* hex(const char* src); const char* hexa(const char* src); const char* hex0(const char* src); - const char* rgb_prefix(const char* src); + // const char* rgb_prefix(const char* src); // Match CSS uri specifiers. const char* uri_prefix(const char* src); - const char* uri(const char* src); + // const char* uri(const char* src); const char* url(const char* src); // Match CSS "!important" keyword. const char* important(const char* src); @@ -551,10 +513,10 @@ namespace Sass { const char* suffix_match(const char* src); const char* substring_match(const char* src); // Match CSS combinators. - const char* adjacent_to(const char* src); - const char* precedes(const char* src); - const char* parent_of(const char* src); - const char* ancestor_of(const char* src); + // const char* adjacent_to(const char* src); + // const char* precedes(const char* src); + // const char* parent_of(const char* src); + // const char* ancestor_of(const char* src); // Match SCSS variable names. const char* variable(const char* src); @@ -582,8 +544,8 @@ namespace Sass { const char* url(const char* src); // Path matching functions. - const char* folder(const char* src); - const char* folders(const char* src); + // const char* folder(const char* src); + // const char* folders(const char* src); const char* static_string(const char* src); diff --git a/sass.cpp b/sass.cpp index b58dd32ea5..dbc24021d2 100644 --- a/sass.cpp +++ b/sass.cpp @@ -4,14 +4,14 @@ #include #include "sass.h" -#include "inspect.hpp" +#include "util.hpp" extern "C" { using namespace std; // caller must free the returned memory - char* ADDCALL sass_string_quote (const char *str, const char quotemark) { - string quoted = Sass::quote(str, quotemark); + char* ADDCALL sass_string_quote (const char *str, const char quote_mark) { + string quoted = Sass::quote(str, quote_mark); char *cstr = (char*) malloc(quoted.length() + 1); std::strcpy(cstr, quoted.c_str()); return cstr; diff --git a/sass.h b/sass.h index f169e05fd8..95ce1d877f 100644 --- a/sass.h +++ b/sass.h @@ -46,7 +46,7 @@ enum Sass_Output_Style { }; // Some convenient string helper function -ADDAPI char* ADDCALL sass_string_quote (const char *str, const char quotemark); +ADDAPI char* ADDCALL sass_string_quote (const char *str, const char quote_mark); ADDAPI char* ADDCALL sass_string_unquote (const char *str); // Get compiled libsass version diff --git a/sass_context.cpp b/sass_context.cpp index af1faa84e3..bc2dabc0ea 100644 --- a/sass_context.cpp +++ b/sass_context.cpp @@ -1,7 +1,9 @@ #ifdef _WIN32 #include +#define LFEED "\n" #else #include +#define LFEED "\n" #endif #include @@ -329,7 +331,7 @@ extern "C" { .include_paths_array(include_paths) .include_paths(vector()) .precision(c_ctx->precision ? c_ctx->precision : 5) - .linefeed(c_ctx->linefeed ? c_ctx->linefeed : "\n") + .linefeed(c_ctx->linefeed ? c_ctx->linefeed : LFEED) .indent(c_ctx->indent ? c_ctx->indent : " "); // create new c++ Context diff --git a/sass_interface.cpp b/sass_interface.cpp index ceb0d1de6a..46d9ec15cb 100644 --- a/sass_interface.cpp +++ b/sass_interface.cpp @@ -1,7 +1,9 @@ #ifdef _WIN32 #include +#define LFEED "\n" #else #include +#define LFEED "\n" #endif #include @@ -120,7 +122,7 @@ extern "C" { .include_paths(vector()) .precision(c_ctx->options.precision ? c_ctx->options.precision : 5) .indent(c_ctx->options.indent ? c_ctx->options.indent : " ") - .linefeed(c_ctx->options.linefeed ? c_ctx->options.linefeed : "\n") + .linefeed(c_ctx->options.linefeed ? c_ctx->options.linefeed : LFEED) .importer(0) ); if (c_ctx->c_functions) { diff --git a/sass_util.cpp b/sass_util.cpp index ddeaeb3ea0..7510e718b2 100644 --- a/sass_util.cpp +++ b/sass_util.cpp @@ -38,7 +38,7 @@ namespace Sass { end */ Node paths(const Node& arrs, Context& ctx) { - To_String to_string; + To_String to_string(&ctx); Node loopStart = Node::createCollection(); loopStart.collection()->push_back(Node::createCollection()); diff --git a/source_map.cpp b/source_map.cpp index 88f8247ec6..ba72c730cb 100644 --- a/source_map.cpp +++ b/source_map.cpp @@ -106,15 +106,6 @@ namespace Sass { return result; } - void SourceMap::remove_line() - { - // prevent removing non existing lines - if (current_position.line > 1) { - current_position.line -= 1; - current_position.column = 1; - } - } - void SourceMap::update_column(const string& str) { const ptrdiff_t new_line_count = std::count(str.begin(), str.end(), '\n'); diff --git a/source_map.hpp b/source_map.hpp index 9178708e41..386105ff43 100644 --- a/source_map.hpp +++ b/source_map.hpp @@ -19,7 +19,9 @@ namespace Sass { SourceMap(); SourceMap(const string& file); - void remove_line(); + void setFile(const string& str) { + file = str; + } void update_column(const string& str); void add_open_mapping(AST_Node* node); void add_close_mapping(AST_Node* node); @@ -33,7 +35,9 @@ namespace Sass { vector mappings; Position current_position; +public: string file; +private: Base64VLQ base64vlq; }; diff --git a/subset_map.hpp b/subset_map.hpp index 09bbd1f793..b9ce01a23b 100644 --- a/subset_map.hpp +++ b/subset_map.hpp @@ -107,9 +107,7 @@ namespace Sass { sort(sorted.begin(), sorted.end()); vector > > indices; for (size_t i = 0, S = s.size(); i < S; ++i) { - // cerr << "looking for " << s[i] << endl; if (!hash_.count(s[i])) { - // cerr << "didn't find " << s[i] << endl; continue; } vector, set, size_t> > subsets = hash_[s[i]]; diff --git a/to_string.cpp b/to_string.cpp index 9cfa2cfa9c..b61c235b0d 100644 --- a/to_string.cpp +++ b/to_string.cpp @@ -11,16 +11,24 @@ namespace Sass { using namespace std; - To_String::To_String(Context* ctx) : ctx(ctx) { } + To_String::To_String(Context* ctx) + : ctx(ctx) { } To_String::~To_String() { } inline string To_String::fallback_impl(AST_Node* n) { - Inspect i(ctx); + Emitter emitter(ctx); + Inspect i(emitter); + i.in_declaration_list = true; n->perform(&i); return i.get_buffer(); } + inline string To_String::operator()(String_Constant* s) + { + return s->value(); + } + inline string To_String::operator()(Null* n) { return ""; } } diff --git a/to_string.hpp b/to_string.hpp index 8476163ca4..937ddd079f 100644 --- a/to_string.hpp +++ b/to_string.hpp @@ -24,6 +24,7 @@ namespace Sass { virtual ~To_String(); string operator()(Null* n); + string operator()(String_Constant*); template string fallback(U n) { return fallback_impl(n); } diff --git a/token.hpp b/token.hpp deleted file mode 100644 index df248c527b..0000000000 --- a/token.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef SASS_TOKEN_H -#define SASS_TOKEN_H - -#include -#include -#include - -#include "position.hpp" - -namespace Sass { - using namespace std; - - // Token type for representing lexed chunks of text - class Token { - public: - const char* begin; - const char* end; - Position before; - Position after; - - Token() - : begin(0), end(0), before(0), after(0) { } - Token(const char* b, const char* e, const Position pos) - : begin(b), end(e), before(pos), after(pos.inc(b, e)) { } - Token(const char* s, const Position pos) - : begin(s), end(s + strlen(s)), before(pos), after(pos.inc(s, s + strlen(s))) { } - - size_t length() const { return end - begin; } - string to_string() const { return string(begin, end - begin); } - - string unquote() const; - void unquote_to_stream(stringstream& buf) const; - - operator bool() { return begin && end && begin >= end; } - operator string() { return to_string(); } - - bool operator==(Token t) { return to_string() == t.to_string(); } - }; - -} - -#endif diff --git a/units.cpp b/units.cpp index 8cd5adaf20..d9b78bcd0d 100644 --- a/units.cpp +++ b/units.cpp @@ -45,10 +45,11 @@ namespace Sass { return factor; } + /* not used anymore - remove? double convert(double n, const string& from, const string& to) { double factor = conversion_factor(from, to); return factor ? factor * n : n; - } + } */ } diff --git a/units.hpp b/units.hpp index bdb780ffd1..d58c1be583 100644 --- a/units.hpp +++ b/units.hpp @@ -9,7 +9,7 @@ namespace Sass { extern double conversion_factors[10][10]; Unit string_to_unit(const string&); double conversion_factor(const string&, const string&); - double convert(double, const string&, const string&); + // double convert(double, const string&, const string&); } #endif diff --git a/util.cpp b/util.cpp index 106d571999..12e007c56b 100644 --- a/util.cpp +++ b/util.cpp @@ -1,6 +1,349 @@ +#include +#include "ast.hpp" #include "util.hpp" +#include "prelexer.hpp" +#include "utf8/checked.h" namespace Sass { + + // double escape every escape sequences + // escape unescaped quotes and backslashes + string string_escape(const string& str) + { + string out(""); + for (auto i : str) { + // escape some characters + if (i == '"') out += '\\'; + if (i == '\'') out += '\\'; + if (i == '\\') out += '\\'; + out += i; + } + return out; + } + + // unescape every escape sequence + // only removes unescaped backslashes + string string_unescape(const string& str) + { + string out(""); + bool esc = false; + for (auto i : str) { + if (esc || i != '\\') { + esc = false; + out += i; + } else { + esc = true; + } + } + // open escape sequence at end + // maybe it should thow an error + if (esc) { out += '\\'; } + return out; + } + + // evacuate unescaped quoted + // leave everything else untouched + string evacuate_quotes(const string& str) + { + string out(""); + bool esc = false; + for (auto i : str) { + if (!esc) { + // ignore next character + if (i == '\\') esc = true; + // evacuate unescaped quotes + else if (i == '"') out += '\\'; + else if (i == '\'') out += '\\'; + } + // get escaped char now + else { esc = false; } + // remove nothing + out += i; + } + return out; + } + + // double escape all escape sequences + // keep unescaped quotes and backslashes + string evacuate_escapes(const string& str) + { + string out(""); + bool esc = false; + for (auto i : str) { + if (i == '\\' && !esc) { + out += '\\'; + out += '\\'; + esc = true; + } else if (esc && i == '"') { + out += '\\'; + out += i; + esc = false; + } else if (esc && i == '\'') { + out += '\\'; + out += i; + esc = false; + } else if (esc && i == '\\') { + out += '\\'; + out += i; + esc = false; + } else { + esc = false; + out += i; + } + } + if (esc) out += 'Z'; + return out; + } + + // bell character is replaces with space + string string_to_output(const string& str) + { + string out(""); + for (auto i : str) { + if (i == 10) { + out += ' '; + } else { + out += i; + } + } + return out; + } + + string comment_to_string(const string& text) + { + string str = ""; + size_t has = 0; + bool clean = false; + for (auto i : text) { + if (clean) { + if (i == '\n') { has = 0; } + else if (i == '\r') { has = 0; } + else if (i == '\t') { ++ has; } + else if (i == ' ') { ++ has; } + else if (i == '*') {} + else { + clean = false; + str += ' '; + str += i; + } + } else if (i == '\n') { + clean = true; + } else if (i == '\r') { + clean = true; + } else { + str += i; + } + } + if (has) return str; + else return text; + } + + string normalize_wspace(const string& str) { + bool ws = false; + bool esc = false; + char inside_str = 0; + string text = ""; + for(auto i : str) { + if (!esc && i == '\\') { + esc = true; + text += i; + } else if (esc) { + esc = false; + text += i; + } else if (!inside_str && (i == '"' || i == '\'')) { + inside_str = i; + text += i; + } else if (inside_str) { + if (i == inside_str) + inside_str = false; + text += i; + } else if ( + i == ' ' || + i == '\r' || + i == '\n' || + i == ' ' + ) { + // only add one space + if (!ws) text += ' '; + ws = true; + } else { + ws = false; + text += i; + } + } + if (esc) text += '\\'; + return text; + } + + // find best quote_mark by detecting if the string contains any single + // or double quotes. When a single quote is found, we not we want a double + // quote as quote_mark. Otherwise we check if the string cotains any double + // quotes, which will trigger the use of single quotes as best quote_mark. + char detect_best_quotemark(const char* s, char qm) + { + // ensure valid fallback quote_mark + char quote_mark = qm && qm != '*' ? qm : '"'; + while (*s) { + // force double quotes as soon + // as one single quote is found + if (*s == '\'') { return '"'; } + // a single does not force quote_mark + // maybe we see a double quote later + else if (*s == '"') { quote_mark = '\''; } + ++ s; + } + return quote_mark; + } + + string unquote(const string& s, char* qd) + { + + // not enough room for quotes + // no possibility to unquote + if (s.length() < 2) return s; + + char q; + bool skipped = false; + + // this is no guarantee that the unquoting will work + // what about whitespace before/after the quote_mark? + if (*s.begin() == '"' && *s.rbegin() == '"') q = '"'; + else if (*s.begin() == '\'' && *s.rbegin() == '\'') q = '\''; + else return s; + + string unq; + unq.reserve(s.length()-2); + + for (size_t i = 1, L = s.length() - 1; i < L; ++i) { + + // implement the same strange ruby sass behavior + // an escape sequence can also mean a unicode char + if (s[i] == '\\' && !skipped) { + // remember + skipped = true; + + // skip it + // ++ i; + + // if (i == L) break; + + // escape length + size_t len = 1; + + // parse as many sequence chars as possible + // ToDo: Check if ruby aborts after possible max + while (i + len < L && s[i + len] && isxdigit(s[i + len])) ++ len; + + // hex string? + if (len > 1) { + + // convert the extracted hex string to code point value + // ToDo: Maybe we could do this without creating a substring + uint32_t cp = strtol(s.substr (i + 1, len - 1).c_str(), nullptr, 16); + + // assert invalid code points + if (cp == 0) cp = 0xFFFD; + // replace bell character + // if (cp == 10) cp = 32; + + // use a very simple approach to convert via utf8 lib + // maybe there is a more elegant way; maybe we shoud + // convert the whole output from string to a stream!? + // allocate memory for utf8 char and convert to utf8 + unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u); + for(size_t m = 0; u[m] && m < 5; m++) unq.push_back(u[m]); + + // skip some more chars? + i += len - 1; skipped = false; + + } + + + } + // check for unexpected delimiter + // be strict and throw error back + else if (!skipped && q == s[i]) { + // don't be that strict + return s; + // this basically always means an internal error and not users fault + error("Unescaped delimiter in string to unquote found. [" + s + "]", ParserState("[UNQUOTE]", -1)); + } + else { + skipped = false; + unq.push_back(s[i]); + } + + } + if (skipped) { return s; } + if (qd) *qd = q; + return unq; + + } + + string quote(const string& s, char q) + { + + // return an empty quoted string + if (s.empty()) return string(2, q ? q : '"'); + + // autodetect with fallback to given quote + q = detect_best_quotemark(s.c_str(), q); + + string quoted; + quoted.reserve(s.length()+2); + quoted.push_back(q); + + const char* it = s.c_str(); + const char* end = it + strlen(it) + 1; + while (*it && it < end) { + const char* now = it; + + if (*it == q) { + quoted.push_back('\\'); + } else if (*it == '\\') { + quoted.push_back('\\'); + } + + int cp = utf8::next(it, end); + + if (cp == 10) { + quoted.push_back('\\'); + quoted.push_back('a'); + } else if (cp < 127) { + quoted.push_back((char) cp); + } else { + while (now < it) { + quoted.push_back(*now); + ++ now; + } + } + } + + quoted.push_back(q); + return quoted; + } + + bool is_hex_doublet(double n) + { + return n == 0x00 || n == 0x11 || n == 0x22 || n == 0x33 || + n == 0x44 || n == 0x55 || n == 0x66 || n == 0x77 || + n == 0x88 || n == 0x99 || n == 0xAA || n == 0xBB || + n == 0xCC || n == 0xDD || n == 0xEE || n == 0xFF ; + } + + bool is_color_doublet(double r, double g, double b) + { + return is_hex_doublet(r) && is_hex_doublet(g) && is_hex_doublet(b); + } + + bool peek_linefeed(const char* start) + { + if(*start == '\n' || *start == '\r') return true;; + const char* linefeed = Prelexer::wspaces(start); + if (linefeed == 0) return false; + return *linefeed == '\n' || *linefeed == '\r'; + } + namespace Util { using std::string; @@ -37,7 +380,7 @@ namespace Sass { } } - bool isPrintable(Ruleset* r) { + bool isPrintable(Ruleset* r, Output_Style style) { if (r == NULL) { return false; } @@ -56,9 +399,15 @@ namespace Sass { Statement* stm = (*b)[i]; if (dynamic_cast(stm)) { Block* pChildBlock = ((Has_Block*)stm)->block(); - if (isPrintable(pChildBlock)) { + if (isPrintable(pChildBlock, style)) { hasPrintableChildBlocks = true; } + } else if (Comment* c = dynamic_cast(stm)) { + if (style == COMPRESSED) { + hasDeclarations = c->is_important(); + } else { + hasDeclarations = true; + } } else { hasDeclarations = true; } @@ -71,7 +420,7 @@ namespace Sass { return false; } - bool isPrintable(Feature_Block* f) { + bool isPrintable(Feature_Block* f, Output_Style style) { if (f == NULL) { return false; } @@ -95,7 +444,7 @@ namespace Sass { } else if (dynamic_cast(stm)) { Block* pChildBlock = ((Has_Block*)stm)->block(); - if (isPrintable(pChildBlock)) { + if (isPrintable(pChildBlock, style)) { hasPrintableChildBlocks = true; } } @@ -108,7 +457,7 @@ namespace Sass { return false; } - bool isPrintable(Media_Block* m) { + bool isPrintable(Media_Block* m, Output_Style style) { if (m == NULL) { return false; } @@ -132,7 +481,7 @@ namespace Sass { } else if (dynamic_cast(stm)) { Block* pChildBlock = ((Has_Block*)stm)->block(); - if (isPrintable(pChildBlock)) { + if (isPrintable(pChildBlock, style)) { hasPrintableChildBlocks = true; } } @@ -145,7 +494,7 @@ namespace Sass { return false; } - bool isPrintable(Block* b) { + bool isPrintable(Block* b, Output_Style style) { if (b == NULL) { return false; } @@ -154,26 +503,29 @@ namespace Sass { Statement* stm = (*b)[i]; if (typeid(*stm) == typeid(Declaration) || typeid(*stm) == typeid(At_Rule)) { return true; + } + else if (typeid(*stm) == typeid(Comment)) { + } else if (typeid(*stm) == typeid(Ruleset)) { Ruleset* r = (Ruleset*) stm; - if (isPrintable(r)) { + if (isPrintable(r, style)) { return true; } } else if (typeid(*stm) == typeid(Feature_Block)) { Feature_Block* f = (Feature_Block*) stm; - if (isPrintable(f)) { + if (isPrintable(f, style)) { return true; } } else if (typeid(*stm) == typeid(Media_Block)) { Media_Block* m = (Media_Block*) stm; - if (isPrintable(m)) { + if (isPrintable(m, style)) { return true; } } - else if (dynamic_cast(stm) && isPrintable(((Has_Block*)stm)->block())) { + else if (dynamic_cast(stm) && isPrintable(((Has_Block*)stm)->block(), style)) { return true; } } diff --git a/util.hpp b/util.hpp index d3df0d2ef9..cf47691d49 100644 --- a/util.hpp +++ b/util.hpp @@ -1,12 +1,31 @@ #ifndef SASS_UTIL_H #define SASS_UTIL_H -#include "ast.hpp" - +#include #include +#include "ast_fwd_decl.hpp" + namespace Sass { + using namespace std; + + string string_escape(const string& str); + string string_unescape(const string& str); + string evacuate_quotes(const string& str); + string evacuate_escapes(const string& str); + string string_to_output(const string& str); + string comment_to_string(const string& text); + string normalize_wspace(const string& str); + + string quote(const string&, char q = 0); + string unquote(const string&, char* q = 0); + char detect_best_quotemark(const char* s, char qm = '"'); + + bool is_hex_doublet(double n); + bool is_color_doublet(double r, double g, double b); + + bool peek_linefeed(const char* start); + namespace Util { - using namespace std; string normalize_underscores(const string& str); string normalize_decimals(const string& str); @@ -15,10 +34,10 @@ namespace Sass { string vecJoin(const vector& vec, const string& sep); bool containsAnyPrintableStatements(Block* b); - bool isPrintable(Ruleset* r); - bool isPrintable(Feature_Block* r); - bool isPrintable(Media_Block* r); - bool isPrintable(Block* b); + bool isPrintable(Ruleset* r, Output_Style style = NESTED); + bool isPrintable(Feature_Block* r, Output_Style style = NESTED); + bool isPrintable(Media_Block* r, Output_Style style = NESTED); + bool isPrintable(Block* b, Output_Style style = NESTED); bool isAscii(int ch); } diff --git a/win/libsass.filters b/win/libsass.filters index 6d53a53bfa..a165dd0433 100644 --- a/win/libsass.filters +++ b/win/libsass.filters @@ -69,10 +69,10 @@ Source Files - + Source Files - + Source Files @@ -224,10 +224,10 @@ Header Files - + Header Files - + Header Files diff --git a/win/libsass.vcxproj b/win/libsass.vcxproj index b119a9d5d2..434f80767d 100644 --- a/win/libsass.vcxproj +++ b/win/libsass.vcxproj @@ -175,8 +175,8 @@ - - + + @@ -225,8 +225,8 @@ - - + +