diff --git a/.gitignore b/.gitignore index f6fb025b5c..d36d048fd0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Miscellaneous stuff +VERSION .DS_Store .sass-cache *.gem diff --git a/Makefile b/Makefile index 9c24ab8da3..dea46dadd3 100644 --- a/Makefile +++ b/Makefile @@ -18,26 +18,30 @@ else ifneq (,$(findstring mingw32,$(MAKE))) UNAME := MinGW else - UNAME := $(shell uname -s) + ifneq (,$(findstring MINGW32,$(shell uname -s))) + UNAME = MinGW + else + UNAME := $(shell uname -s) + endif endif endif endif ifeq "$(LIBSASS_VERSION)" "" - ifneq "$(wildcard ./.git/ )" "" - LIBSASS_VERSION ?= $(shell git describe --abbrev=4 --dirty --always --tags) - endif + ifneq "$(wildcard ./.git/ )" "" + LIBSASS_VERSION ?= $(shell git describe --abbrev=4 --dirty --always --tags) + endif endif ifeq "$(LIBSASS_VERSION)" "" - ifneq ("$(wildcard VERSION)","") - LIBSASS_VERSION ?= $(shell $(CAT) VERSION) - endif + ifneq ("$(wildcard VERSION)","") + LIBSASS_VERSION ?= $(shell $(CAT) VERSION) + endif endif ifneq "$(LIBSASS_VERSION)" "" - CFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" - CXXFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" + CFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" + CXXFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" endif # enable mandatory flag @@ -50,18 +54,18 @@ else endif ifneq "$(SASS_LIBSASS_PATH)" "" - CFLAGS += -I $(SASS_LIBSASS_PATH) - CXXFLAGS += -I $(SASS_LIBSASS_PATH) + CFLAGS += -I $(SASS_LIBSASS_PATH) + CXXFLAGS += -I $(SASS_LIBSASS_PATH) endif ifneq "$(EXTRA_CFLAGS)" "" - CFLAGS += $(EXTRA_CFLAGS) + CFLAGS += $(EXTRA_CFLAGS) endif ifneq "$(EXTRA_CXXFLAGS)" "" - CXXFLAGS += $(EXTRA_CXXFLAGS) + CXXFLAGS += $(EXTRA_CXXFLAGS) endif ifneq "$(EXTRA_LDFLAGS)" "" - LDFLAGS += $(EXTRA_LDFLAGS) + LDFLAGS += $(EXTRA_LDFLAGS) endif LDLIBS = -lstdc++ -lm @@ -71,6 +75,11 @@ ifeq ($(UNAME),Darwin) LDFLAGS += -stdlib=libc++ endif +ifneq (MinGW,$(UNAME)) + LDFLAGS += -ldl + LDLIBS += -ldl +endif + ifneq ($(BUILD),shared) BUILD = static endif @@ -103,7 +112,6 @@ SOURCES = \ constants.cpp \ context.cpp \ contextualize.cpp \ - copy_c_str.cpp \ cssize.cpp \ error_handling.cpp \ eval.cpp \ @@ -117,6 +125,7 @@ SOURCES = \ emitter.cpp \ output.cpp \ parser.cpp \ + plugins.cpp \ position.cpp \ prelexer.cpp \ remove_placeholders.cpp \ diff --git a/Makefile.am b/Makefile.am index 9b8526e2ba..d4c56043bd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -10,6 +10,7 @@ else AM_CFLAGS += -fPIC AM_CXXFLAGS += -fPIC AM_CXXFLAGS += -std=c++0x + AM_LDFLAGS += -ldl endif AM_CFLAGS += -DLIBSASS_VERSION="\"$(VERSION)\"" @@ -54,7 +55,6 @@ libsass_la_SOURCES = \ constants.cpp constants.hpp \ context.cpp context.hpp \ contextualize.cpp contextualize.hpp \ - copy_c_str.cpp copy_c_str.hpp \ error_handling.cpp error_handling.hpp \ eval.cpp eval.hpp \ expand.cpp expand.hpp \ @@ -68,6 +68,7 @@ libsass_la_SOURCES = \ emitter.cpp emitter.hpp \ output.cpp output.hpp \ parser.cpp parser.hpp \ + plugins.cpp plugins.hpp \ prelexer.cpp prelexer.hpp \ remove_placeholders.cpp remove_placeholders.hpp \ sass.cpp sass.h \ diff --git a/Readme.md b/Readme.md index bd4b400d64..0e6ad773b5 100644 --- a/Readme.md +++ b/Readme.md @@ -89,7 +89,7 @@ Our MIT license is designed to be as simple, and liberal as possible. [@chriseppstein]: https://github.com/chriseppstein [@nex3]: https://github.com/nex3 -sass2scss was originally written by Marcel Greter [@mgreter] +sass2scss was originally written by [Marcel Greter](@mgreter) and he happily agreed to have it merged into the project. [sass_interface.h]: sass_interface.h diff --git a/appveyor.yml b/appveyor.yml index f534ca6d2d..487aa848f4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,24 +15,23 @@ environment: ruby_version: "21-x64" cache: - - x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z + - C:\mingw64 + - C:\Ruby%ruby_version%\lib\ruby\gems install: - git clone https://github.com/sass/sassc.git - git clone https://github.com/sass/sass-spec.git - set PATH=C:\Ruby%ruby_version%\bin;%PATH% - set SASS_LIBSASS_PATH=.. - - gem install minitest - ps: | - if ($env:Compiler -eq "mingw") { - if (-Not (Test-Path "x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z")) { - # Install MinGW. - $url = "http://sourceforge.net/projects/mingw-w64/files/" - $url += "Toolchains%20targetting%20Win64/Personal%20Builds/" - $url += "mingw-builds/4.9.2/threads-win32/seh/" - $url += "x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download" - Invoke-WebRequest -UserAgent wget -Uri $url -OutFile x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z - } + if(!(gem which minitest 2>$nul)) { gem install minitest } + if ($env:Compiler -eq "mingw" -AND -Not (Test-Path "C:\mingw64")) { + # Install MinGW. + $url = "http://sourceforge.net/projects/mingw-w64/files/" + $url += "Toolchains%20targetting%20Win64/Personal%20Builds/" + $url += "mingw-builds/4.9.2/threads-win32/seh/" + $url += "x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download" + Invoke-WebRequest -UserAgent wget -Uri $url -OutFile x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z &7z x -oC:\ x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z > $null } - set PATH=C:\mingw64\bin;%PATH% @@ -71,4 +70,3 @@ test_script: ruby sass-spec\sass-spec.rb -c win\bin\sassc.exe -s --ignore-todo sass-spec/spec } } - diff --git a/ast.cpp b/ast.cpp index 51801f8ba0..99f63a42be 100644 --- a/ast.cpp +++ b/ast.cpp @@ -414,7 +414,10 @@ namespace Sass { { if (!tail()) return 0; if (!head()) return tail()->context(ctx); - return new (ctx.mem) Complex_Selector(pstate(), combinator(), head(), tail()->context(ctx)); + Complex_Selector* cpy = new (ctx.mem) Complex_Selector(pstate(), combinator(), head(), tail()->context(ctx)); + cpy->media_block(media_block()); + cpy->last_block(last_block()); + return cpy; } Complex_Selector* Complex_Selector::innermost() diff --git a/ast.hpp b/ast.hpp index 7de7128fb6..80232b62cb 100644 --- a/ast.hpp +++ b/ast.hpp @@ -44,6 +44,7 @@ #include "ast_def_macros.hpp" #include "ast_fwd_decl.hpp" #include "to_string.hpp" +#include "source_map.hpp" #include "sass.h" #include "sass_values.h" @@ -86,7 +87,6 @@ namespace Sass { STRING, LIST, MAP, - SELECTOR, NULL_VAL, NUM_TYPES }; @@ -410,7 +410,13 @@ namespace Sass { At_Rule(ParserState pstate, string kwd, Selector* sel = 0, Block* b = 0) : Has_Block(pstate, b), keyword_(kwd), selector_(sel), value_(0) // set value manually if needed { statement_type(DIRECTIVE); } - bool bubbles() { return true; } + bool bubbles() { return is_keyframes() || is_media(); } + bool is_media() { + return keyword_.compare("@-webkit-media") == 0 || + keyword_.compare("@-moz-media") == 0 || + keyword_.compare("@-o-media") == 0 || + keyword_.compare("@media") == 0; + } bool is_keyframes() { return keyword_.compare("@-webkit-keyframes") == 0 || keyword_.compare("@-moz-keyframes") == 0 || @@ -1692,21 +1698,28 @@ namespace Sass { ///////////////////////////////////////// // Abstract base class for CSS selectors. ///////////////////////////////////////// - class Selector : public Expression { + 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); + // maybe we have optional flag + ADD_PROPERTY(bool, is_optional); + // parent block pointers + ADD_PROPERTY(Block*, last_block); + ADD_PROPERTY(Media_Block*, media_block); public: Selector(ParserState pstate, bool r = false, bool h = false) - : Expression(pstate), + : AST_Node(pstate), has_reference_(r), has_placeholder_(h), has_line_feed_(false), - has_line_break_(false) - { concrete_type(SELECTOR); } + has_line_break_(false), + is_optional_(false), + media_block_(0) + { } virtual ~Selector() = 0; // virtual Selector_Placeholder* find_placeholder(); virtual int specificity() { return Constants::SPECIFICITY_BASE; } @@ -1957,9 +1970,9 @@ namespace Sass { ADD_PROPERTY(Complex_Selector*, tail); public: Complex_Selector(ParserState pstate, - Combinator c, - Compound_Selector* h, - Complex_Selector* t) + Combinator c, + Compound_Selector* h, + Complex_Selector* t) : Selector(pstate), combinator_(c), head_(h), tail_(t) { if ((h && h->has_reference()) || (t && t->has_reference())) has_reference(true); diff --git a/backtrace.hpp b/backtrace.hpp index e59b5a9892..2c8eed2607 100644 --- a/backtrace.hpp +++ b/backtrace.hpp @@ -3,11 +3,8 @@ #include -#include "position.hpp" - -#ifndef SASS_FILE #include "file.hpp" -#endif +#include "position.hpp" namespace Sass { diff --git a/context.cpp b/context.cpp index b6d5c4edc6..77729a27c9 100644 --- a/context.cpp +++ b/context.cpp @@ -5,7 +5,10 @@ #endif #include "ast.hpp" +#include "util.hpp" +#include "sass.h" #include "context.hpp" +#include "plugins.hpp" #include "constants.hpp" #include "parser.hpp" #include "file.hpp" @@ -17,7 +20,6 @@ #include "cssize.hpp" #include "extend.hpp" #include "remove_placeholders.hpp" -#include "copy_c_str.hpp" #include "color_names.hpp" #include "functions.hpp" #include "backtrace.hpp" @@ -35,6 +37,7 @@ namespace Sass { using namespace Constants; using namespace File; + using namespace Sass; using std::cerr; using std::endl; @@ -51,6 +54,7 @@ namespace Sass { mem(Memory_Manager()), source_c_str (initializers.source_c_str()), sources (vector()), + plugin_paths (initializers.plugin_paths()), include_paths (initializers.include_paths()), queue (vector()), style_sheets (map()), @@ -63,6 +67,7 @@ namespace Sass { source_comments (initializers.source_comments()), output_style (initializers.output_style()), source_map_file (make_canonical_path(initializers.source_map_file())), + source_map_root (initializers.source_map_root()), // pass-through source_map_embed (initializers.source_map_embed()), source_map_contents (initializers.source_map_contents()), omit_source_map_url (initializers.omit_source_map_url()), @@ -71,8 +76,10 @@ namespace Sass { names_to_colors (map()), colors_to_names (map()), precision (initializers.precision()), + plugins(), subset_map (Subset_Map >()) { + cwd = get_cwd(); // enforce some safe defaults @@ -83,9 +90,19 @@ namespace Sass { include_paths.push_back(cwd); collect_include_paths(initializers.include_paths_c_str()); collect_include_paths(initializers.include_paths_array()); + collect_plugin_paths(initializers.plugin_paths_c_str()); + collect_plugin_paths(initializers.plugin_paths_array()); setup_color_map(); + for (size_t i = 0, S = plugin_paths.size(); i < S; ++i) { + plugins.load_plugins(plugin_paths[i]); + } + + for(auto fn : plugins.get_functions()) { + c_functions.push_back(fn); + } + string entry_point = initializers.entry_point(); if (!entry_point.empty()) { string result(add_file(entry_point)); @@ -155,7 +172,6 @@ namespace Sass { void Context::collect_include_paths(const char** paths_array) { - if (*include_paths.back().rbegin() != '/') include_paths.back() += '/'; if (paths_array) { for (size_t i = 0; paths_array[i]; i++) { collect_include_paths(paths_array[i]); @@ -163,6 +179,39 @@ namespace Sass { } } + void Context::collect_plugin_paths(const char* paths_str) + { + + if (paths_str) { + const char* beg = paths_str; + const char* end = Prelexer::find_first(beg); + + while (end) { + string path(beg, end - beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + plugin_paths.push_back(path); + } + beg = end + 1; + end = Prelexer::find_first(beg); + } + + string path(beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + plugin_paths.push_back(path); + } + } + } + + void Context::collect_plugin_paths(const char** paths_array) + { + if (paths_array) { + for (size_t i = 0; paths_array[i]; i++) { + collect_plugin_paths(paths_array[i]); + } + } + } void Context::add_source(string load_path, string abs_path, const char* contents) { sources.push_back(contents); @@ -230,11 +279,12 @@ namespace Sass { if (!root) return 0; root->perform(&emitter); emitter.finalize(); - string output = emitter.get_buffer(); + OutputBuffer emitted = emitter.get_buffer(); + string output = emitted.buffer; if (source_map_file != "" && !omit_source_map_url) { output += linefeed + format_source_mapping_url(source_map_file); } - return copy_c_str(output.c_str()); + return sass_strdup(output.c_str()); } Block* Context::parse_file() @@ -283,11 +333,12 @@ namespace Sass { if (!source_c_str) return 0; queue.clear(); if(is_indented_syntax_src) { - char * contents = sass2scss(source_c_str, SASS2SCSS_PRETTIFY_1); + char * contents = sass2scss(source_c_str, SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT); add_source(input_path, input_path, contents); + delete [] source_c_str; return parse_file(); } - add_source(input_path, input_path, copy_c_str(source_c_str)); + add_source(input_path, input_path, source_c_str); return parse_file(); } @@ -323,7 +374,7 @@ namespace Sass { if (source_map_file == "") return 0; char* result = 0; string map = emitter.generate_source_map(*this); - result = copy_c_str(map.c_str()); + result = sass_strdup(map.c_str()); return result; } diff --git a/context.hpp b/context.hpp index 98ee5c7cee..0f3ccc84e4 100644 --- a/context.hpp +++ b/context.hpp @@ -15,6 +15,7 @@ #include "source_map.hpp" #include "subset_map.hpp" #include "output.hpp" +#include "plugins.hpp" #include "sass_functions.h" struct Sass_C_Function_Descriptor; @@ -44,6 +45,7 @@ namespace Sass { vector include_links; // vectors above have same size + vector plugin_paths; // relative paths to load plugins vector include_paths; // lookup paths for includes vector queue; // queue of files to be parsed map style_sheets; // map of paths to ASTs @@ -58,6 +60,7 @@ namespace Sass { bool source_comments; // for inline debug comments in css output Output_Style output_style; // output style for the generated css code string source_map_file; // path to source map file (enables feature) + string source_map_root; // path for sourceRoot property (pass-through) bool source_map_embed; // embed in sourceMappingUrl (as data-url) bool source_map_contents; // insert included contents into source map bool omit_source_map_url; // disable source map comment in css output @@ -80,11 +83,15 @@ namespace Sass { KWD_ARG(Data, string, indent); KWD_ARG(Data, string, linefeed); KWD_ARG(Data, const char*, include_paths_c_str); + KWD_ARG(Data, const char*, plugin_paths_c_str); KWD_ARG(Data, const char**, include_paths_array); + KWD_ARG(Data, const char**, plugin_paths_array); KWD_ARG(Data, vector, include_paths); + KWD_ARG(Data, vector, plugin_paths); KWD_ARG(Data, bool, source_comments); KWD_ARG(Data, Output_Style, output_style); KWD_ARG(Data, string, source_map_file); + KWD_ARG(Data, string, source_map_root); KWD_ARG(Data, bool, omit_source_map_url); KWD_ARG(Data, bool, is_indented_syntax_src); KWD_ARG(Data, size_t, precision); @@ -113,11 +120,14 @@ namespace Sass { vector get_included_files(size_t skip = 0); private: + void collect_plugin_paths(const char* paths_str); + void collect_plugin_paths(const char** paths_array); void collect_include_paths(const char* paths_str); void collect_include_paths(const char** paths_array); string format_source_mapping_url(const string& file); string cwd; + Plugins plugins; // void register_built_in_functions(Env* env); // void register_function(Signature sig, Native_Function f, Env* env); diff --git a/contextualize.cpp b/contextualize.cpp index 9e7d8a4985..07c2a1a769 100644 --- a/contextualize.cpp +++ b/contextualize.cpp @@ -31,7 +31,10 @@ namespace Sass { 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(); + Parser p = Parser::from_c_str(result_str.c_str(), ctx, s->pstate()); + p.block_stack.push_back(s->last_block()); + p.last_media_block = s->media_block(); + Selector* result_sel = p.parse_selector_group(); return result_sel->perform(this); } @@ -64,6 +67,8 @@ namespace Sass { { To_String to_string(&ctx); Complex_Selector* ss = new (ctx.mem) Complex_Selector(*s); + // ss->last_block(s->last_block()); + // ss->media_block(s->media_block()); Compound_Selector* new_head = 0; Complex_Selector* new_tail = 0; if (ss->head()) { @@ -72,6 +77,8 @@ namespace Sass { } if (ss->tail()) { new_tail = static_cast(s->tail()->perform(this)); + // new_tail->last_block(s->last_block()); + // new_tail->media_block(s->media_block()); ss->tail(new_tail); } if ((new_head && new_head->has_placeholder()) || (new_tail && new_tail->has_placeholder())) { @@ -95,6 +102,8 @@ namespace Sass { return extender; } Compound_Selector* ss = new (ctx.mem) Compound_Selector(s->pstate(), s->length()); + ss->last_block(s->last_block()); + ss->media_block(s->media_block()); 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)); diff --git a/contrib/libsass.spec b/contrib/libsass.spec new file mode 100644 index 0000000000..a83d5f0cb5 --- /dev/null +++ b/contrib/libsass.spec @@ -0,0 +1,66 @@ +Name: libsass +Version: %{version} +Release: 1%{?dist} +Summary: A C/C++ implementation of a Sass compiler + +License: MIT +URL: http://libsass.org +Source0: %{name}-%{version}.tar.gz + +BuildRequires: gcc-c++ >= 4.7 +BuildRequires: autoconf +BuildRequires: automake +BuildRequires: libtool + + +%description +LibSass is a C/C++ port of the Sass engine. The point is to be simple, fast, and easy to integrate. + +%package devel +Summary: Development files for %{name} +Requires: %{name}%{?_isa} = %{version}-%{release} + + +%description devel +The %{name}-devel package contains libraries and header files for +developing applications that use %{name}. + + +%prep +%setup -q +autoreconf --force --install + + +%build +%configure --disable-static \ + --disable-tests \ + --enable-shared + +make %{?_smp_mflags} + + +%install +%make_install +find $RPM_BUILD_ROOT -name '*.la' -exec rm -f {} ';' + + +%post -p /sbin/ldconfig + +%postun -p /sbin/ldconfig + + +%files +%doc Readme.md LICENSE +%{_libdir}/*.so.* + +%files devel +%doc +%{_includedir}/* +%{_libdir}/*.so +%{_libdir}/pkgconfig/*.pc + + +%changelog +* Tue Feb 10 2015 Gawain Lynch - 3.1.0-1 +- Initial SPEC file + diff --git a/contrib/plugin.cpp b/contrib/plugin.cpp new file mode 100644 index 0000000000..8d154f1936 --- /dev/null +++ b/contrib/plugin.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include "sass_values.h" + +// gcc: g++ -shared plugin.cpp -o plugin.so -fPIC -Llib -lsass +// mingw: g++ -shared plugin.cpp -o plugin.dll -Llib -lsass + +union Sass_Value* call_fn_foo(const union Sass_Value* s_args, void* cookie) +{ + // we actually abuse the void* to store an "int" + return sass_make_number((intptr_t)cookie, "px"); +} + +extern "C" const char* ADDCALL libsass_get_version() { + return libsass_version(); +} + +extern "C" Sass_C_Function_List ADDCALL libsass_load_functions() +{ + // allocate a custom function caller + Sass_C_Function_Callback fn_foo = + sass_make_function("foo()", call_fn_foo, (void*)42); + // create list of all custom functions + Sass_C_Function_List fn_list = sass_make_function_list(1); + // put the only function in this plugin to the list + sass_function_set_list_entry(fn_list, 0, fn_foo); + // return the list + return fn_list; +} diff --git a/copy_c_str.cpp b/copy_c_str.cpp deleted file mode 100644 index b4d61cb90c..0000000000 --- a/copy_c_str.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include - -namespace Sass { - using namespace std; - - char* copy_c_str(const char* orig) - { - char* copy = (char*) malloc(sizeof(char) * strlen(orig) + 1); - strcpy(copy, orig); - return copy; - } -} diff --git a/copy_c_str.hpp b/copy_c_str.hpp deleted file mode 100644 index 7ee289ea1c..0000000000 --- a/copy_c_str.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef SASS_COPY_C_STR_H -#define SASS_COPY_C_STR_H - -namespace Sass { - - char* copy_c_str(const char*); - -} - -#endif diff --git a/cssize.cpp b/cssize.cpp index 444811d718..b00225a370 100644 --- a/cssize.cpp +++ b/cssize.cpp @@ -101,23 +101,18 @@ namespace Sass { // rr->tabs(r->block()->tabs()); p_stack.pop_back(); - Block* props = new Block(rr->block()->pstate()); - for (size_t i = 0, L = rr->block()->length(); i < L; i++) - { - Statement* s = (*rr->block())[i]; - if (!bubblable(s)) *props << s; - } - - Block* rules = new Block(rr->block()->pstate()); + Block* props = new (ctx.mem) Block(rr->block()->pstate()); + Block* rules = new (ctx.mem) Block(rr->block()->pstate()); for (size_t i = 0, L = rr->block()->length(); i < L; i++) { Statement* s = (*rr->block())[i]; if (bubblable(s)) *rules << s; + if (!bubblable(s)) *props << s; } if (props->length()) { - Block* bb = new Block(rr->block()->pstate()); + Block* bb = new (ctx.mem) Block(rr->block()->pstate()); *bb += props; rr->block(bb); diff --git a/debugger.hpp b/debugger.hpp index a2648d4e73..19a49e27a0 100644 --- a/debugger.hpp +++ b/debugger.hpp @@ -1,6 +1,7 @@ #ifndef SASS_DEBUGGER_H #define SASS_DEBUGGER_H +#include #include "ast_fwd_decl.hpp" using namespace std; @@ -37,7 +38,16 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) 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; + + cerr << ind << "Selector_List " << selector + << " [block:" << selector->last_block() << "]" + << (selector->last_block() && selector->last_block()->is_root() ? " [root]" : "") + << " [@media:" << selector->media_block() << "]" + << (selector->is_optional() ? " [is_optional]": " -") + << (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)) { @@ -46,7 +56,13 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) } 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]": " -") << " -> "; + cerr << ind << "Complex_Selector " << selector + << " [block:" << selector->last_block() << "]" + << (selector->last_block() && selector->last_block()->is_root() ? " [root]" : "") + << " [@media:" << selector->media_block() << "]" + << (selector->is_optional() ? " [is_optional]": " -") + << (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; @@ -58,7 +74,13 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) 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]": " -") << + cerr << ind << "Compound_Selector " << selector + << " [block:" << selector->last_block() << "]" + << (selector->last_block() && selector->last_block()->is_root() ? " [root]" : "") + << " [@media:" << selector->media_block() << "]" + << (selector->is_optional() ? " [is_optional]": " -") + << (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)) { @@ -85,8 +107,16 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) 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; + cerr << ind << "Selector_Placeholder [" << selector->name() << "] " << selector + << " [block:" << selector->last_block() << "]" + << " [@media:" << selector->media_block() << "]" + << (selector->is_optional() ? " [is_optional]": " -") + << (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; @@ -96,16 +126,46 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) } 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; + cerr << ind << "Selector_Schema " << selector + << " [block:" << selector->last_block() << "]" + << (selector->last_block() && selector->last_block()->is_root() ? " [root]" : "") + << " [@media:" << selector->media_block() << "]" + << (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; + cerr << ind << "Selector " << selector + << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << endl; + + } else if (dynamic_cast(node)) { + Media_Query_Expression* block = dynamic_cast(node); + cerr << ind << "Media_Query_Expression " << block + << (block->is_interpolated() ? " [is_interpolated]": " -") + << endl; + debug_ast(block->feature(), ind + " f) "); + debug_ast(block->value(), ind + " v) "); + + } else if (dynamic_cast(node)) { + Media_Query* block = dynamic_cast(node); + cerr << ind << "Media_Query " << block + << (block->is_negated() ? " [is_negated]": " -") + << (block->is_restricted() ? " [is_restricted]": " -") + << endl; + debug_ast(block->media_type(), ind + " "); + for(auto i : block->elements()) { debug_ast(i, ind + " ", env); } + } else if (dynamic_cast(node)) { Media_Block* block = dynamic_cast(node); cerr << ind << "Media_Block " << block << " " << block->tabs() << endl; + debug_ast(block->media_queries(), ind + " =@ "); + debug_ast(block->selector(), ind + " -@ "); 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); @@ -161,7 +221,7 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) 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; + cerr << ind << "At_Rule " << block << " [" << block->keyword() << "] " << 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); } diff --git a/emitter.cpp b/emitter.cpp index e2b030b2b6..91e30aecf5 100644 --- a/emitter.cpp +++ b/emitter.cpp @@ -15,6 +15,7 @@ namespace Sass { scheduled_linefeed(0), scheduled_delimiter(false), in_comment(false), + in_at_rule(false), in_media_block(false), in_declaration(false), in_declaration_list(false) @@ -84,6 +85,20 @@ namespace Sass { } } + // prepend some text or token to the buffer + void Emitter::prepend_output(const OutputBuffer& output) + { + wbuf.smap.prepend(output); + wbuf.buffer = output.buffer + wbuf.buffer; + } + + // prepend some text or token to the buffer + void Emitter::prepend_string(const string& text) + { + wbuf.smap.prepend(Offset(text)); + wbuf.buffer = text + wbuf.buffer; + } + // append some text or token to the buffer void Emitter::append_string(const string& text) { @@ -96,12 +111,12 @@ namespace Sass { // add to buffer wbuf.buffer += out; // account for data in source-maps - wbuf.smap.update_column(out); + wbuf.smap.append(Offset(out)); } else { // add to buffer wbuf.buffer += text; // account for data in source-maps - wbuf.smap.update_column(text); + wbuf.smap.append(Offset(text)); } } diff --git a/emitter.hpp b/emitter.hpp index 83855ce5ff..8dded8baf4 100644 --- a/emitter.hpp +++ b/emitter.hpp @@ -9,17 +9,6 @@ namespace Sass { class Context; using namespace std; - class OutputBuffer { - public: - OutputBuffer(void) - : buffer(""), - smap() - { } - public: - string buffer; - SourceMap smap; - }; - class Emitter { public: @@ -31,6 +20,7 @@ namespace Sass { public: const string buffer(void) { return wbuf.buffer; } const SourceMap smap(void) { return wbuf.smap; } + const OutputBuffer output(void) { return wbuf; } // proxy methods for source maps void add_source_index(size_t idx); void set_filename(const string& str); @@ -48,6 +38,7 @@ namespace Sass { public: bool in_comment; + bool in_at_rule; bool in_media_block; bool in_declaration; bool in_declaration_list; @@ -61,6 +52,9 @@ namespace Sass { void finalize(void); // flush scheduled space/linefeed void flush_schedules(void); + // prepend some text or token to the buffer + void prepend_string(const string& text); + void prepend_output(const OutputBuffer& out); // append some text or token to the buffer void append_string(const string& text); // append some white-space only text diff --git a/eval.cpp b/eval.cpp index 5aaf23451a..f3ddb317bc 100644 --- a/eval.cpp +++ b/eval.cpp @@ -604,7 +604,7 @@ namespace Sass { string name(v->name()); Expression* value = 0; if (env->has(name)) value = static_cast((*env)[name]); - else error("unbound variable " + v->name(), v->pstate()); + else error("Undefined variable: \"" + v->name() + "\".", v->pstate()); // cerr << "name: " << v->name() << "; type: " << typeid(*value).name() << "; value: " << value->perform(&to_string) << endl; if (typeid(*value) == typeid(Argument)) value = static_cast(value)->value(); @@ -658,19 +658,19 @@ namespace Sass { { case Textual::NUMBER: result = new (ctx.mem) Number(t->pstate(), - atof(num.c_str()), + sass_atof(num.c_str()), "", zero); break; case Textual::PERCENTAGE: result = new (ctx.mem) Number(t->pstate(), - atof(num.c_str()), + sass_atof(num.c_str()), "%", zero); break; case Textual::DIMENSION: result = new (ctx.mem) Number(t->pstate(), - atof(num.c_str()), + sass_atof(num.c_str()), Token(number(text.c_str()), t->pstate()), zero); break; @@ -770,7 +770,7 @@ namespace Sass { } else if (Variable* var = dynamic_cast(s)) { string name(var->name()); - if (!env->has(name)) return name; + if (!env->has(name)) error("Undefined variable: \"" + var->name() + "\".", var->pstate()); Expression* value = static_cast((*env)[name]); return evacuate_quotes(interpolation(value)); diff --git a/expand.cpp b/expand.cpp index 24fb3858ee..b2592d0c6b 100644 --- a/expand.cpp +++ b/expand.cpp @@ -70,22 +70,24 @@ namespace Sass { string str = isp.get_buffer(); str += ";"; - Parser p(ctx, ParserState("[REPARSE]", 0)); + Parser p(ctx, r->pstate()); + p.block_stack.push_back(r->selector() ? r->selector()->last_block() : 0); + p.last_media_block = r->selector() ? r->selector()->media_block() : 0; p.source = str.c_str(); 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.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.remap(pIter->pstate())); + // pIter->pstate(isp.remap(pIter->pstate())); if (pHead) { - pHead->pstate(isp.remap(pHead->pstate())); - (*pHead)[0]->pstate(isp.remap((*pHead)[0]->pstate())); + // pHead->pstate(isp.remap(pHead->pstate())); + // (*pHead)[0]->pstate(isp.remap((*pHead)[0]->pstate())); } pIter = pIter->tail(); } @@ -450,7 +452,8 @@ namespace Sass { 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))); + Selector_List* org_extendee = static_cast(e->selector()); + Selector_List* extendee = static_cast(org_extendee->perform(contextualize->with(0, env, backtrace))); if (extendee->length() != 1) { error("selector groups may not be extended", extendee->pstate(), backtrace); } @@ -459,7 +462,7 @@ namespace Sass { error("nested selectors may not be extended", c->pstate(), backtrace); } Compound_Selector* s = c->head(); - + s->is_optional(org_extendee->is_optional()); // // need to convert the compound selector into a by-value data structure // vector target_vec; // for (size_t i = 0, L = s->length(); i < L; ++i) diff --git a/extend.cpp b/extend.cpp index 812d5e4c60..0e1a7465a6 100644 --- a/extend.cpp +++ b/extend.cpp @@ -235,6 +235,7 @@ namespace Sass { return os; } #endif + static bool parentSuperselector(Complex_Selector* pOne, Complex_Selector* pTwo, Context& ctx) { // TODO: figure out a better way to create a Complex_Selector from scratch // TODO: There's got to be a better way. This got ugly quick... @@ -1674,13 +1675,58 @@ namespace Sass { if (pHead) { SubsetMapEntries entries = subsetMap.get_v(pHead->to_str_vec()); + for (ExtensionPair ext : entries) { + // check if both selectors have the same media block parent + if (ext.first->media_block() == pComplexSelector->media_block()) continue; + To_String to_string(&ctx); + if (ext.second->media_block() && ext.second->media_block()->media_queries() && + pComplexSelector->media_block() && pComplexSelector->media_block()->media_queries()) + { + string query_left(ext.second->media_block()->media_queries()->perform(&to_string)); + string query_right(pComplexSelector->media_block()->media_queries()->perform(&to_string)); + if (query_left == query_right) continue; + } - hasExtension = entries.size() > 0; + // fail if one goes across media block boundaries + stringstream err; + string cwd(Sass::File::get_cwd()); + ParserState pstate(ext.second->pstate()); + string rel_path(Sass::File::resolve_relative_path(pstate.path, cwd, cwd)); + err << "You may not @extend an outer selector from within @media.\n"; + err << "You may only @extend selectors within the same directive.\n"; + err << "From \"@extend " << ext.second->perform(&to_string) << "\""; + err << " on line " << pstate.line+1 << " of " << rel_path << "\n"; + error(err.str(), pComplexSelector->pstate()); + } + if (entries.size() > 0) hasExtension = true; } pIter = pIter->tail(); } + if (!hasExtension) { + /* ToDo: don't break stuff + stringstream err; + To_String to_string(&ctx); + string cwd(Sass::File::get_cwd()); + string sel1(pComplexSelector->perform(&to_string)); + Compound_Selector* pExtendSelector = 0; + for (auto i : subsetMap.values()) { + if (i.first == pComplexSelector) { + pExtendSelector = i.second; + break; + } + } + if (!pExtendSelector || !pExtendSelector->is_optional()) { + string sel2(pExtendSelector ? pExtendSelector->perform(&to_string) : "[unknown]"); + err << "\"" << sel1 << "\" failed to @extend \"" << sel2 << "\"\n"; + err << "The selector \"" << sel2 << "\" was not found.\n"; + err << "Use \"@extend " << sel2 << " !optional\" if the extend should be able to fail."; + error(err.str(), pExtendSelector ? pExtendSelector->pstate() : pComplexSelector->pstate()); + } + */ + } + return hasExtension; } @@ -1705,7 +1751,6 @@ namespace Sass { pComplexSelector->tail()->has_line_feed(pComplexSelector->has_line_feed()); Node complexSelector = complexSelectorToNode(pComplexSelector, ctx); - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector) Node extendedNotExpanded = Node::createCollection(); diff --git a/file.cpp b/file.cpp index 8fc6136815..a08f6f0d36 100644 --- a/file.cpp +++ b/file.cpp @@ -146,6 +146,12 @@ namespace Sass { string absolute_uri = make_absolute_path(uri, cwd); string absolute_base = make_absolute_path(base, cwd); + #ifdef _WIN32 + // absolute link must have a drive letter, and we know that we + // can only create relative links if both are on the same drive + if (absolute_base[0] != absolute_uri[0]) return absolute_uri; + #endif + string stripped_uri = ""; string stripped_base = ""; @@ -284,7 +290,7 @@ namespace Sass { for(size_t i=0; ikeyword(), at_rule); if (at_rule->selector()) { append_mandatory_space(); + in_at_rule = true; at_rule->selector()->perform(this); + in_at_rule = false; } if (at_rule->block()) { at_rule->block()->perform(this); @@ -836,7 +838,7 @@ namespace Sass { { if (g->empty()) return; for (size_t i = 0, L = g->length(); i < L; ++i) { - if (i == 0) append_indentation(); + if (!in_at_rule && i == 0) append_indentation(); (*g)[i]->perform(this); if (i < L - 1) { append_comma_separator(); diff --git a/output.cpp b/output.cpp index d0970b1e60..9310d74041 100644 --- a/output.cpp +++ b/output.cpp @@ -24,7 +24,7 @@ namespace Sass { top_imports.push_back(imp); } - string Output::get_buffer(void) + OutputBuffer Output::get_buffer(void) { Emitter emitter(ctx); @@ -44,17 +44,16 @@ namespace Sass { // flush scheduled outputs inspect.finalize(); - // create combined buffer string - string buffer = inspect.buffer() - + this->buffer(); + // prepend buffer on top + prepend_output(inspect.output()); // make sure we end with a linefeed - if (!ends_with(buffer, ctx->linefeed)) { + if (!ends_with(wbuf.buffer, ctx->linefeed)) { // if the output is not completely empty - if (!buffer.empty()) buffer += ctx->linefeed; + if (!wbuf.buffer.empty()) append_string(ctx->linefeed); } // search for unicode char - for(const char& chr : buffer) { + for(const char& chr : wbuf.buffer) { // skip all ascii chars if (chr >= 0) continue; // declare the charset @@ -67,7 +66,9 @@ namespace Sass { } // add charset as first line, before comments and imports - return (charset.empty() ? "" : charset) + buffer; + if (!charset.empty()) prepend_string(charset); + + return wbuf; } @@ -337,7 +338,9 @@ namespace Sass { append_token(kwd, a); if (s) { append_mandatory_space(); + in_at_rule = true; s->perform(this); + in_at_rule = false; } else if (v) { append_mandatory_space(); diff --git a/output.hpp b/output.hpp index f2cdd2c633..9417a05613 100644 --- a/output.hpp +++ b/output.hpp @@ -34,7 +34,7 @@ namespace Sass { vector top_comments; public: - string get_buffer(void); + OutputBuffer get_buffer(void); virtual void operator()(Ruleset*); // virtual void operator()(Propset*); diff --git a/parser.cpp b/parser.cpp index 7e0b0cdfff..b0f6defe1a 100644 --- a/parser.cpp +++ b/parser.cpp @@ -22,6 +22,9 @@ namespace Sass { p.source = str; p.position = p.source; p.end = str + strlen(str); + Block* root = new (ctx.mem) Block(pstate); + p.block_stack.push_back(root); + root->is_root(true); return p; } @@ -31,6 +34,9 @@ namespace Sass { p.source = beg; p.position = p.source; p.end = end; + Block* root = new (ctx.mem) Block(pstate); + p.block_stack.push_back(root); + root->is_root(true); return p; } @@ -45,12 +51,16 @@ namespace Sass { p.source = t.begin; p.position = p.source; p.end = t.end; + Block* root = new (ctx.mem) Block(pstate); + p.block_stack.push_back(root); + root->is_root(true); return p; } Block* Parser::parse() { Block* root = new (ctx.mem) Block(pstate); + block_stack.push_back(root); root->is_root(true); read_bom(); lex< optional_spaces >(); @@ -143,6 +153,7 @@ namespace Sass { } lex< optional_spaces >(); } + block_stack.pop_back(); return root; } @@ -178,6 +189,7 @@ namespace Sass { Import* imp = new (ctx.mem) Import(pstate); bool first = true; do { + while (lex< block_comment >()); if (lex< quoted_string >()) { string import_path(lexed); @@ -199,9 +211,15 @@ namespace Sass { while (*includes) { struct Sass_Import* include = *includes; const char *file = sass_import_get_path(include); - char *source = sass_import_take_source(include); + char* source = sass_import_take_source(include); + size_t line = sass_import_get_error_line(include); + size_t column = sass_import_get_error_column(include); + const char* message = sass_import_get_error_message(include); // char *srcmap = sass_import_take_srcmap(include); - if (source) { + if (message) { + if (line == string::npos && column == string::npos) error(message, pstate); + else error(message, ParserState(message, Position(line, column))); + } else if (source) { if (file) { ctx.add_source(file, inc_path, source); imp->files().push_back(file); @@ -273,8 +291,9 @@ namespace Sass { // if there's anything there at all if (!peek< exactly<')'> >()) { do (*params) << parse_parameter(); - while (lex< exactly<','> >()); + while (lex< alternatives < spaces,block_comment, exactly<','> > >()); } + while (lex< alternatives < spaces, block_comment > >()) {}; if (!lex< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, pstate); } return params; @@ -282,12 +301,15 @@ namespace Sass { Parameter* Parser::parse_parameter() { + while (lex< alternatives < spaces, block_comment > >()); lex< variable >(); string name(Util::normalize_underscores(lexed)); ParserState pos = pstate; Expression* val = 0; bool is_rest = false; + while (lex< alternatives < spaces, block_comment > >()); if (lex< exactly<':'> >()) { // there's a default value + while (lex< block_comment >()); val = parse_space_list(); val->is_delayed(false); } @@ -322,8 +344,9 @@ namespace Sass { // if there's anything there at all if (!peek< exactly<')'> >()) { do (*args) << parse_argument(); - while (lex< exactly<','> >()); + while (lex< alternatives < block_comment, exactly<','> > >()); } + while (lex< block_comment >()); if (!lex< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, pstate); } @@ -333,10 +356,12 @@ namespace Sass { Argument* Parser::parse_argument() { Argument* arg; - if (peek< sequence < variable, optional_spaces_and_comments, exactly<':'> > >()) { + while (lex< alternatives < spaces, block_comment > >()); + if (peek< sequence < variable, zero_plus < alternatives < spaces, line_comment, block_comment > >, exactly<':'> > >()) { lex< variable >(); string name(Util::normalize_underscores(lexed)); ParserState p = pstate; + while (lex< alternatives < spaces, block_comment > >()) {}; lex< exactly<':'> >(); Expression* val = parse_space_list(); val->is_delayed(false); @@ -386,7 +411,6 @@ namespace Sass { 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); @@ -443,7 +467,10 @@ namespace Sass { } } position = end_of_selector; - return new (ctx.mem) Selector_Schema(pstate, schema); + Selector_Schema* selector_schema = new (ctx.mem) Selector_Schema(pstate, schema); + selector_schema->media_block(last_media_block); + selector_schema->last_block(block_stack.back()); + return selector_schema; } Selector_List* Parser::parse_selector_group() @@ -452,6 +479,8 @@ namespace Sass { To_String to_string(&ctx); lex< optional_spaces_and_comments >(); Selector_List* group = new (ctx.mem) Selector_List(pstate); + group->media_block(last_media_block); + group->last_block(block_stack.back()); do { reloop = false; if (peek< exactly<'{'> >() || @@ -464,6 +493,8 @@ namespace Sass { ParserState sel_source_position = pstate; Selector_Reference* ref = new (ctx.mem) Selector_Reference(sel_source_position); Compound_Selector* ref_wrap = new (ctx.mem) Compound_Selector(sel_source_position); + ref_wrap->media_block(last_media_block); + ref_wrap->last_block(block_stack.back()); (*ref_wrap) << ref; if (!comb->head()) { comb->head(ref_wrap); @@ -471,6 +502,8 @@ namespace Sass { } else { comb = new (ctx.mem) Complex_Selector(sel_source_position, Complex_Selector::ANCESTOR_OF, ref_wrap, comb); + comb->media_block(last_media_block); + comb->last_block(block_stack.back()); comb->has_reference(true); } if (peek_newline()) ref_wrap->has_line_break(true); @@ -488,7 +521,9 @@ namespace Sass { (*group) << comb; } while (reloop); - while (lex< optional >()); // JMA - ignore optional flag if it follows the selector group + while (lex< optional >()) { + group->is_optional(true); + } return group; } @@ -531,7 +566,9 @@ namespace Sass { sel_source_position = before_token; } if (!sel_source_position.line) sel_source_position = before_token; - Complex_Selector* cpx = 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), cmb, lhs, rhs); + cpx->media_block(last_media_block); + cpx->last_block(block_stack.back()); if (cpx_lf) cpx->has_line_break(cpx_lf); return cpx; } @@ -539,9 +576,14 @@ namespace Sass { Compound_Selector* Parser::parse_simple_selector_sequence() { Compound_Selector* seq = new (ctx.mem) Compound_Selector(pstate); + seq->media_block(last_media_block); + seq->last_block(block_stack.back()); bool sawsomething = false; if (lex< exactly<'&'> >()) { - // if you see a & + // check if we have a parent selector on the root level block + if (block_stack.back() && block_stack.back()->is_root()) { + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", pstate); + } (*seq) << new (ctx.mem) Selector_Reference(pstate); sawsomething = true; // if you see a space after a &, then you're done @@ -594,7 +636,10 @@ namespace Sass { return parse_attribute_selector(); } else if (lex< placeholder >()) { - return new (ctx.mem) Selector_Placeholder(pstate, unquote(lexed)); + Selector_Placeholder* sel = new (ctx.mem) Selector_Placeholder(pstate, unquote(lexed)); + sel->media_block(last_media_block); + sel->last_block(block_stack.back()); + return sel; } else { error("invalid selector after " + lexed.to_string(), pstate); @@ -713,7 +758,8 @@ namespace Sass { bool semicolon = false; Selector_Lookahead lookahead_result; Block* block = new (ctx.mem) Block(pstate); - + block_stack.push_back(block); + lex< zero_plus < alternatives < space, line_comment > > >(); // JMA - ensure that a block containing only block_comments is parsed while (lex< block_comment >()) { bool is_important = lexed.begin[2] == '!'; @@ -760,6 +806,9 @@ namespace Sass { (*block) << parse_assignment(); semicolon = true; } + else if (lex< line_comment >()) { + // throw line comments away + } else if (peek< if_directive >()) { (*block) << parse_if_directive(); } @@ -881,6 +930,7 @@ namespace Sass { (*block) << comment; } } + block_stack.pop_back(); return block; } @@ -908,6 +958,22 @@ namespace Sass { } } + // parse +/- and return false if negative + bool Parser::parse_number_prefix() + { + bool positive = true; + while(true) { + if (lex < block_comment >()) continue; + if (lex < number_prefix >()) continue; + if (lex < exactly < '-' > >()) { + positive = !positive; + continue; + } + break; + } + return positive; + } + Expression* Parser::parse_map() { To_String to_string(&ctx); @@ -1110,6 +1176,7 @@ namespace Sass { peek< exactly<'%'> >(position))) { return fact1; } + while (lex< block_comment >()); vector operands; vector operators; while (lex< exactly<'*'> >() || lex< exactly<'/'> >() || lex< exactly<'%'> >()) { @@ -1169,6 +1236,10 @@ namespace Sass { else if (lex< sequence< not_op, spaces_and_comments > >()) { return new (ctx.mem) Unary_Expression(pstate, Unary_Expression::NOT, parse_factor()); } + else if (peek < sequence < one_plus < alternatives < spaces_and_comments, exactly<'-'>, exactly<'+'> > >, number > >()) { + if (parse_number_prefix()) return parse_value(); // prefix is positive + return new (ctx.mem) Unary_Expression(pstate, Unary_Expression::MINUS, parse_value()); + } else { return parse_value(); } @@ -1176,6 +1247,7 @@ namespace Sass { Expression* Parser::parse_value() { + while (lex< block_comment >()); if (lex< uri_prefix >()) { Arguments* args = new (ctx.mem) Arguments(pstate); Function_Call* result = new (ctx.mem) Function_Call(pstate, "url", args); @@ -1277,7 +1349,7 @@ namespace Sass { { const char* i = chunk.begin; // see if there any interpolants - const char* p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly > >(i, chunk.end); + const char* p = find_first_in_interval< exactly >(i, chunk.end); if (!p) { String_Quoted* str_quoted = new (ctx.mem) String_Quoted(pstate, string(i, chunk.end)); if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); @@ -1287,7 +1359,7 @@ namespace Sass { String_Schema* schema = new (ctx.mem) String_Schema(pstate); while (i < chunk.end) { - p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly > >(i, chunk.end); + p = find_first_in_interval< exactly >(i, chunk.end); if (p) { if (i < p) { // accumulate the preceding segment if it's nonempty @@ -1342,7 +1414,7 @@ namespace Sass { Token str(lexed); 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); + const char* p = find_first_in_interval< exactly >(str.begin, str.end); if (!p) { String_Constant* str_node = new (ctx.mem) String_Constant(pstate, normalize_wspace(string(str.begin, str.end))); str_node->is_delayed(true); @@ -1351,7 +1423,7 @@ namespace Sass { String_Schema* schema = new (ctx.mem) String_Schema(pstate); while (i < str.end) { - p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly > >(i, str.end); + p = find_first_in_interval< exactly >(i, str.end); if (p) { if (i < p) { (*schema) << new (ctx.mem) String_Constant(pstate, string(i, p)); // accumulate the preceding segment if it's nonempty @@ -1478,14 +1550,14 @@ namespace Sass { 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); + const char* p = find_first_in_interval< exactly >(id.begin, id.end); if (!p) { return new (ctx.mem) String_Quoted(pstate, string(id.begin, id.end)); } String_Schema* schema = new (ctx.mem) String_Schema(pstate); while (i < id.end) { - p = find_first_in_interval< sequence< negate< exactly<'\\'> >, exactly > >(i, id.end); + p = find_first_in_interval< exactly >(i, id.end); if (p) { if (i < p) { // accumulate the preceding segment if it's nonempty @@ -1641,9 +1713,13 @@ namespace Sass { if (!peek< exactly<'{'> >()) { error("expected '{' in media query", pstate); } - Block* block = parse_block(); + Media_Block* media_block = new (ctx.mem) Media_Block(media_source_position, media_queries, 0); + Media_Block* prev_media_block = last_media_block; + last_media_block = media_block; + media_block->block(parse_block()); + last_media_block = prev_media_block; - return new (ctx.mem) Media_Block(media_source_position, media_queries, block); + return media_block; } List* Parser::parse_media_queries() @@ -1663,11 +1739,18 @@ 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_Quoted(pstate, lexed)); + else if (lex< identifier >()) media_query->media_type(parse_interpolated_chunk(lexed)); else (*media_query) << parse_media_expression(); while (lex< exactly< and_kwd > >()) (*media_query) << parse_media_expression(); - + if (peek< identifier_schema >()) { + String_Schema* schema = new (ctx.mem) String_Schema(pstate); + *schema << media_query->media_type(); + *schema << new (ctx.mem) String_Constant(pstate, " "); + *schema << parse_identifier_schema(); + media_query->media_type(schema); + } + while (lex< exactly< and_kwd > >()) (*media_query) << parse_media_expression(); return media_query; } @@ -1908,6 +1991,7 @@ namespace Sass { (q = peek< exactly<'~'> >(p)) || (q = peek< exactly<'>'> >(p)) || (q = peek< exactly<','> >(p)) || + (saw_stuff && (q = peek< exactly<'-'> >(p))) || (q = peek< binomial >(p)) || (q = peek< sequence< optional, optional, @@ -1966,6 +2050,7 @@ namespace Sass { (q = peek< exactly<'~'> >(p)) || (q = peek< exactly<'>'> >(p)) || (q = peek< exactly<','> >(p)) || + (saw_stuff && (q = peek< exactly<'-'> >(p))) || (q = peek< binomial >(p)) || (q = peek< sequence< optional, optional, diff --git a/parser.hpp b/parser.hpp index fbefbb5c32..3dea229af7 100644 --- a/parser.hpp +++ b/parser.hpp @@ -31,7 +31,9 @@ namespace Sass { enum Syntactic_Context { nothing, mixin_def, function_def }; Context& ctx; + vector block_stack; vector stack; + Media_Block* last_media_block; const char* source; const char* position; const char* end; @@ -44,12 +46,12 @@ namespace Sass { Token lexed; bool in_at_root; - Parser(Context& ctx, ParserState pstate) - : ParserState(pstate), ctx(ctx), stack(vector()), + Parser(Context& ctx, const ParserState& pstate) + : ParserState(pstate), ctx(ctx), block_stack(0), stack(0), last_media_block(0), 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(const 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]")); @@ -146,6 +148,9 @@ namespace Sass { else if (mx == optional_spaces_and_comments) { it_before_token = position; } + else if (mx == spaces_and_comments) { + it_before_token = position; + } else if (mx == optional_spaces) { // ToDo: what are optiona_spaces ??? @@ -229,6 +234,7 @@ namespace Sass { Simple_Selector* parse_pseudo_selector(); Attribute_Selector* parse_attribute_selector(); Block* parse_block(); + bool parse_number_prefix(); Declaration* parse_declaration(); Expression* parse_map_value(); Expression* parse_map(); diff --git a/plugins.cpp b/plugins.cpp new file mode 100644 index 0000000000..a01f8650e1 --- /dev/null +++ b/plugins.cpp @@ -0,0 +1,155 @@ +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#endif + +#include +#include "output.hpp" +#include "plugins.hpp" + +#define npos string::npos + +namespace Sass { + + Plugins::Plugins(void) { } + Plugins::~Plugins(void) { } + + // check if plugin is compatible with this version + // plugins may be linked static against libsass + // we try to be compatible between major versions + inline bool compatibility(const char* their_version) + { +// const char* their_version = "3.1.2"; + // first check if anyone has an unknown version + const char* our_version = libsass_version(); + if (!strcmp(their_version, "[na]")) return false; + if (!strcmp(our_version, "[na]")) return false; + + // find the position of the second dot + size_t pos = string(our_version).find('.', 0); + if (pos != npos) pos = string(our_version).find('.', pos + 1); + + // if we do not have two dots we fallback to compare complete string + if (pos == npos) { return strcmp(their_version, our_version) ? 0 : 1; } + // otherwise only compare up to the second dot (major versions) + else { return strncmp(their_version, our_version, pos) ? 0 : 1; } + + } + + // load one specific plugin + bool Plugins::load_plugin (const string& path) + { + + typedef const char* (*__plugin_version__)(void); + typedef Sass_C_Function_List (*__plugin_load_fns__)(void); + + if (LOAD_LIB(plugin, path)) + { + // try to load initial function to query libsass version suppor + if (LOAD_LIB_FN(__plugin_version__, plugin_version, "libsass_get_version")) + { + // get the libsass version of the plugin + if (!compatibility(plugin_version())) return false; + // try to get import address for "libsass_load_functions" + if (LOAD_LIB_FN(__plugin_load_fns__, plugin_load_functions, "libsass_load_functions")) + { + Sass_C_Function_List fns = plugin_load_functions(); + while (fns && *fns) { functions.push_back(*fns); ++ fns; } + } + // success + return true; + } + else + { + // print debug message to stderr (should not happen) + cerr << "failed loading 'libsass_support' in <" << path << ">" << endl; + if (const char* dlsym_error = dlerror()) cerr << dlsym_error << endl; + CLOSE_LIB(plugin); + } + } + else + { + // print debug message to stderr (should not happen) + cerr << "failed loading plugin <" << path << ">" << endl; + if (const char* dlopen_error = dlerror()) cerr << dlopen_error << endl; + } + + return false; + + } + + size_t Plugins::load_plugins(const string& path) + { + + // count plugins + size_t loaded = 0; + + #ifdef _WIN32 + + try + { + + // use wchar (utf16) + WIN32_FIND_DATAW data; + // trailing slash is guaranteed + string globsrch(path + "*.dll"); + // convert to wide chars (utf16) for system call + wstring wglobsrch(UTF_8::convert_to_utf16(globsrch)); + HANDLE hFile = FindFirstFileW(wglobsrch.c_str(), &data); + // check if system called returned a result + // ToDo: maybe we should print a debug message + if (hFile == INVALID_HANDLE_VALUE) return -1; + + // read directory + while (true) + { + try + { + // the system will report the filenames with wide chars (utf16) + string entry = UTF_8::convert_from_utf16(data.cFileName); + // check if file ending matches exactly + if (!ends_with(entry, ".dll")) continue; + // load the plugin and increase counter + if (load_plugin(path + entry)) ++ loaded; + // check if there should be more entries + if (GetLastError() == ERROR_NO_MORE_FILES) break; + // load next entry (check for return type) + if (!FindNextFileW(hFile, &data)) break; + } + catch (...) + { + // report the error to the console (should not happen) + // seems like we got strange data from the system call? + cerr << "filename in plugin path has invalid utf8?" << endl; + } + } + } + catch (utf8::invalid_utf8) + { + // report the error to the console (should not happen) + // implementors should make sure to provide valid utf8 + cerr << "plugin path contains invalid utf8" << endl; + } + + #else + + DIR *dp; + struct dirent *dirp; + if((dp = opendir(path.c_str())) == NULL) return -1; + while ((dirp = readdir(dp)) != NULL) { + if (!ends_with(dirp->d_name, ".so")) continue; + if (load_plugin(path + dirp->d_name)) ++ loaded; + } + closedir(dp); + + #endif + return loaded; + + } + +} + diff --git a/plugins.hpp b/plugins.hpp new file mode 100644 index 0000000000..dee6a0e8d0 --- /dev/null +++ b/plugins.hpp @@ -0,0 +1,56 @@ +#ifndef SASS_PLUGINS_H +#define SASS_PLUGINS_H + +#include +#include +#include "utf8_string.hpp" +#include "sass_functions.h" + +#ifdef _WIN32 + + #define LOAD_LIB(var, path) HMODULE var = LoadLibraryW(UTF_8::convert_to_utf16(path).c_str()) + #define LOAD_LIB_WCHR(var, path_wide_str) HMODULE var = LoadLibraryW(path_wide_str.c_str()) + #define LOAD_LIB_FN(type, var, name) type var = (type) GetProcAddress(plugin, name) + #define CLOSE_LIB(var) FreeLibrary(var) + + #ifndef dlerror + #define dlerror() 0 + #endif + +#else + + #define LOAD_LIB(var, path) void* var = dlopen(path.c_str(), RTLD_LAZY) + #define LOAD_LIB_FN(type, var, name) type var = (type) dlsym(plugin, name) + #define CLOSE_LIB(var) dlclose(var) + +#endif + +namespace Sass { + + using namespace std; + + class Plugins { + + public: // c-tor + Plugins(void); + ~Plugins(void); + + public: // methods + // load one specific plugin + bool load_plugin(const string& path); + // load all plugins from a directory + size_t load_plugins(const string& path); + + public: // public accessors + // const vector get_importers(void) { return importers; }; + const vector get_functions(void) { return functions; }; + + private: // private vars + // vector importers; + vector functions; + + }; + +} + +#endif \ No newline at end of file diff --git a/position.cpp b/position.cpp index 0217faf4f6..3f7d3013e8 100644 --- a/position.cpp +++ b/position.cpp @@ -4,6 +4,18 @@ namespace Sass { using namespace std; + Offset::Offset(const char* string) + : line(0), column(0) + { + *this = inc(string, string + strlen(string)); + } + + Offset::Offset(const string& text) + : line(0), column(0) + { + *this = inc(text.c_str(), text.c_str() + text.size()); + } + Offset::Offset(const size_t line, const size_t column) : line(line), column(column) { } @@ -34,7 +46,12 @@ namespace Sass { return line != pos.line || column != pos.column; } - const Offset Offset::operator+ (const Offset &off) const + void Offset::operator+= (const Offset &off) + { + *this = Offset(line + off.line, off.line > 0 ? off.column : off.column + column); + } + + Offset Offset::operator+ (const Offset &off) const { return Offset(line + off.line, off.line > 0 ? off.column : off.column + column); } @@ -67,7 +84,7 @@ namespace Sass { Position Position::inc(const char* begin, const char* end) const { Offset offset(line, column); - offset.inc(begin, end); + offset = offset.inc(begin, end); return Position(file, offset); } @@ -81,6 +98,11 @@ namespace Sass { return file == pos.file || line != pos.line || column != pos.column; } + void Position::operator+= (const Offset &off) + { + *this = Position(file, line + off.line, off.line > 0 ? off.column : off.column + column); + } + const Position Position::operator+ (const Offset &off) const { return Position(file, line + off.line, off.line > 0 ? off.column : off.column + column); diff --git a/position.hpp b/position.hpp index b73424f414..55734119eb 100644 --- a/position.hpp +++ b/position.hpp @@ -14,15 +14,18 @@ namespace Sass { class Offset { public: // c-tor + Offset(const char* string); + Offset(const string& text); Offset(const size_t line, const size_t column); // return new position, incremented by the given string Offset inc(const char* begin, const char* end) const; public: // overload operators for position + void operator+= (const Offset &pos); bool operator== (const Offset &pos) const; bool operator!= (const Offset &pos) const; - const Offset operator+ (const Offset &off) const; + Offset operator+ (const Offset &off) const; public: // overload output stream operator // friend ostream& operator<<(ostream& strm, const Offset& off); @@ -45,6 +48,7 @@ namespace Sass { Position(const size_t file, const size_t line, const size_t column); public: // overload operators for position + void operator+= (const Offset &off); bool operator== (const Position &pos) const; bool operator!= (const Position &pos) const; const Position operator+ (const Offset &off) const; @@ -96,8 +100,8 @@ namespace Sass { 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); + ParserState(string path, Position position, Offset offset = Offset(0, 0)); + ParserState(string path, Token token, Position position, Offset offset = Offset(0, 0)); public: // down casts Offset off() { return *this; }; diff --git a/prelexer.cpp b/prelexer.cpp index d44c9e5fa2..dadf273f79 100644 --- a/prelexer.cpp +++ b/prelexer.cpp @@ -26,6 +26,8 @@ namespace Sass { // Match any single character. const char* any_char(const char* src) { return *src ? src+1 : src; } + // Match word boundary (look ahead) + const char* word_boundary(const char* src) { return !*src || isspace(*src) || ispunct(*src) || !Sass::Util::isAscii(*src) ? src : 0 ; } // Match a single character satisfying the ctype predicates. const char* space(const char* src) { return std::isspace(*src) ? src+1 : 0; } @@ -57,7 +59,7 @@ namespace Sass { } // Match either comment. const char* comment(const char* src) { - return alternatives(src); + return line_comment(src); } const char* wspaces(const char* src) { @@ -98,10 +100,10 @@ namespace Sass { const char* optional_spaces(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); + return zero_plus< alternatives >(src); } const char* spaces_and_comments(const char* src) { - return one_plus< alternatives >(src); + return one_plus< alternatives >(src); } const char* no_spaces(const char* src) { return negate< spaces >(src); @@ -145,6 +147,18 @@ namespace Sass { return sequence< exactly<'-'>, exactly<'-'>, identifier >(src); } + // Match number prefix ([\+\-]+) + const char* number_prefix(const char* src) { + return alternatives < + exactly < '+' >, + sequence < + exactly < '-' >, + optional_spaces_and_comments, + exactly< '-' > + > + >(src); + } + // Match interpolant schemas const char* identifier_schema(const char* src) { // follows this pattern: (x*ix*)+ ... well, not quite @@ -167,7 +181,7 @@ namespace Sass { zero_plus < alternatives < // skip all escaped chars first - sequence < exactly < '\\' >, any_char >, + backslash_something, // skip interpolants interpolant, // skip non delimiters @@ -186,7 +200,7 @@ namespace Sass { zero_plus < alternatives < // skip all escaped chars first - sequence < exactly < '\\' >, any_char >, + backslash_something, // skip interpolants interpolant, // skip non delimiters @@ -288,7 +302,7 @@ namespace Sass { } const char* extend(const char* src) { - return exactly(src); + return sequence < exactly, word_boundary >(src); } diff --git a/prelexer.hpp b/prelexer.hpp index 5f8bc0cdb8..3e6db55c36 100644 --- a/prelexer.hpp +++ b/prelexer.hpp @@ -172,6 +172,8 @@ namespace Sass { const char* any_char_except(const char* src) { return (*src && *src != c) ? src+1 : 0; } + // Match word boundary (look ahead) + const char* word_boundary(const char* src); // Matches zero characters (always succeeds without consuming input). // const char* epsilon(const char*); @@ -394,6 +396,8 @@ namespace Sass { const char* quoted_string(const char* src); // Match interpolants. const char* interpolant(const char* src); + // Match number prefix ([\+\-]+) + const char* number_prefix(const char* src); // Whitespace handling. const char* optional_spaces(const char* src); @@ -566,8 +570,11 @@ namespace Sass { } template const char* find_first_in_interval(const char* beg, const char* end) { + bool esc = false; while ((beg < end) && *beg) { - if (mx(beg)) return beg; + if (esc) esc = false; + else if (*beg == '\\') esc = true; + else if (mx(beg)) return beg; ++beg; } return 0; @@ -575,8 +582,11 @@ namespace Sass { template unsigned int count_interval(const char* beg, const char* end) { unsigned int counter = 0; + bool esc = false; while (beg < end && *beg) { - if (*beg == c) ++counter; + if (esc) esc = false; + else if (*beg == '\\') esc = true; + else if (*beg == c) ++counter; ++beg; } return counter; @@ -584,9 +594,16 @@ namespace Sass { template unsigned int count_interval(const char* beg, const char* end) { unsigned int counter = 0; + bool esc = false; while (beg < end && *beg) { const char* p; - if ((p = mx(beg))) { + if (esc) { + esc = false; + ++beg; + } else if (*beg == '\\') { + esc = true; + ++beg; + } else if ((p = mx(beg))) { ++counter; beg = p; } diff --git a/sass.h b/sass.h index 95ce1d877f..888f3c6198 100644 --- a/sass.h +++ b/sass.h @@ -4,6 +4,15 @@ #include #include +#ifdef __GNUC__ + #define DEPRECATED(func) func __attribute__ ((deprecated)) +#elif defined(_MSC_VER) + #define DEPRECATED(func) __declspec(deprecated) func +#else + #pragma message("WARNING: You need to implement DEPRECATED for this compiler") + #define DEPRECATED(func) func +#endif + #ifdef _WIN32 /* You should define ADD_EXPORTS *only* when building the DLL. */ @@ -52,7 +61,6 @@ ADDAPI char* ADDCALL sass_string_unquote (const char *str); // Get compiled libsass version ADDAPI const char* ADDCALL libsass_version(void); - #ifdef __cplusplus } // __cplusplus defined. #endif diff --git a/sass_context.cpp b/sass_context.cpp index 6717295c4a..b59012e63f 100644 --- a/sass_context.cpp +++ b/sass_context.cpp @@ -8,8 +8,9 @@ #include #include +#include "file.hpp" #include "json.hpp" -#include "copy_c_str.hpp" +#include "util.hpp" #include "context.hpp" #include "sass_values.h" #include "sass_context.h" @@ -82,15 +83,21 @@ extern "C" { // Semicolon-separated on Windows // Maybe use array interface instead? char* include_path; + char* plugin_path; - // Include path (linked string list) + // Include paths (linked string list) struct string_list* include_paths; + // Plugin paths (linked string list) + struct string_list* plugin_paths; // Path to source map file // Enables source map generation // Used to create sourceMappingUrl char* source_map_file; + // Directly inserted in source maps + char* source_map_root; + // Custom functions that can be called from sccs code Sass_C_Function_List c_functions; @@ -115,6 +122,7 @@ extern "C" { // error status int error_status; char* error_json; + char* error_text; char* error_message; // error position char* error_file; @@ -169,7 +177,7 @@ extern "C" { #define IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(type, option) \ type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return options->option; } \ void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) \ - { free(options->option); options->option = option ? copy_c_str(option) : 0; } + { free(options->option); options->option = option ? sass_strdup(option) : 0; } #define IMPLEMENT_SASS_CONTEXT_GETTER(type, option) \ type ADDCALL sass_context_get_##option (struct Sass_Context* ctx) { return ctx->option; } @@ -217,17 +225,35 @@ extern "C" { } catch (Sass_Error& e) { stringstream msg_stream; + string cwd(Sass::File::get_cwd()); JsonNode* json_err = json_mkobject(); json_append_member(json_err, "status", json_mknumber(1)); json_append_member(json_err, "file", json_mkstring(e.pstate.path.c_str())); json_append_member(json_err, "line", json_mknumber(e.pstate.line+1)); json_append_member(json_err, "column", json_mknumber(e.pstate.column+1)); json_append_member(json_err, "message", json_mkstring(e.message.c_str())); - msg_stream << e.pstate.path << ":" << e.pstate.line+1 << ": " << e.message << endl; + string rel_path(Sass::File::resolve_relative_path(e.pstate.path, cwd, cwd)); + + string msg_prefix("Error: "); + bool got_newline = false; + msg_stream << msg_prefix; + for (char chr : e.message) { + if (chr == '\n') { + got_newline = true; + } else if (got_newline) { + msg_stream << string(msg_prefix.size(), ' '); + got_newline = false; + } + msg_stream << chr; + } + if (!got_newline) msg_stream << "\n"; + msg_stream << string(msg_prefix.size(), ' '); + msg_stream << " on line " << e.pstate.line+1 << " of " << rel_path << "\n"; c_ctx->error_json = json_stringify(json_err, " ");; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); + c_ctx->error_text = strdup(e.message.c_str()); c_ctx->error_status = 1; - c_ctx->error_file = copy_c_str(e.pstate.path.c_str()); + c_ctx->error_file = sass_strdup(e.pstate.path.c_str()); c_ctx->error_line = e.pstate.line+1; c_ctx->error_column = e.pstate.column+1; c_ctx->output_string = 0; @@ -241,7 +267,8 @@ extern "C" { json_append_member(json_err, "status", json_mknumber(2)); json_append_member(json_err, "message", json_mkstring(ba.what())); c_ctx->error_json = json_stringify(json_err, " ");; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); + c_ctx->error_text = strdup(ba.what()); c_ctx->error_status = 2; c_ctx->output_string = 0; c_ctx->source_map_string = 0; @@ -254,7 +281,8 @@ extern "C" { json_append_member(json_err, "status", json_mknumber(3)); json_append_member(json_err, "message", json_mkstring(e.what())); c_ctx->error_json = json_stringify(json_err, " ");; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); + c_ctx->error_text = strdup(e.what()); c_ctx->error_status = 3; c_ctx->output_string = 0; c_ctx->source_map_string = 0; @@ -267,7 +295,8 @@ extern "C" { json_append_member(json_err, "status", json_mknumber(4)); json_append_member(json_err, "message", json_mkstring(e.c_str())); c_ctx->error_json = json_stringify(json_err, " ");; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); + c_ctx->error_text = strdup(e.c_str()); c_ctx->error_status = 4; c_ctx->output_string = 0; c_ctx->source_map_string = 0; @@ -280,7 +309,8 @@ extern "C" { json_append_member(json_err, "status", json_mknumber(5)); json_append_member(json_err, "message", json_mkstring("unknown")); c_ctx->error_json = json_stringify(json_err, " ");; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); + c_ctx->error_text = strdup("unknown"); c_ctx->error_status = 5; c_ctx->output_string = 0; c_ctx->source_map_string = 0; @@ -304,19 +334,35 @@ extern "C" { } // convert include path linked list to static array - struct string_list* cur = c_ctx->include_paths; + struct string_list* inc = c_ctx->include_paths; // very poor loop to get the length of the linked list - size_t length = 0; while (cur) { length ++; cur = cur->next; } + size_t inc_size = 0; while (inc) { inc_size ++; inc = inc->next; } // create char* array to hold all paths plus null terminator - const char** include_paths = (const char**) calloc(length + 1, sizeof(char*)); + const char** include_paths = (const char**) calloc(inc_size + 1, sizeof(char*)); if (include_paths == 0) throw(bad_alloc()); // reset iterator - cur = c_ctx->include_paths; + inc = c_ctx->include_paths; + // copy over the paths + for (size_t i = 0; inc; i++) { + include_paths[i] = inc->string; + inc = inc->next; + } + + // convert plugin path linked list to static array + struct string_list* imp = c_ctx->plugin_paths; + // very poor loop to get the length of the linked list + size_t imp_size = 0; while (imp) { imp_size ++; imp = imp->next; } + // create char* array to hold all paths plus null terminator + const char** plugin_paths = (const char**) calloc(imp_size + 1, sizeof(char*)); + if (plugin_paths == 0) throw(bad_alloc()); + // reset iterator + imp = c_ctx->plugin_paths; // copy over the paths - for (size_t i = 0; cur; i++) { - include_paths[i] = cur->string; - cur = cur->next; + for (size_t i = 0; imp; i++) { + plugin_paths[i] = imp->string; + imp = imp->next; } + // transfer the options to c++ cpp_opt.input_path(input_path) .output_path(output_path) @@ -324,13 +370,17 @@ extern "C" { .is_indented_syntax_src(c_ctx->is_indented_syntax_src) .source_comments(c_ctx->source_comments) .source_map_file(safe_str(c_ctx->source_map_file)) + .source_map_root(safe_str(c_ctx->source_map_root)) .source_map_embed(c_ctx->source_map_embed) .source_map_contents(c_ctx->source_map_contents) .omit_source_map_url(c_ctx->omit_source_map_url) .include_paths_c_str(c_ctx->include_path) - .importer(c_ctx->importer) + .plugin_paths_c_str(c_ctx->plugin_path) .include_paths_array(include_paths) + .plugin_paths_array(plugin_paths) .include_paths(vector()) + .plugin_paths(vector()) + .importer(c_ctx->importer) .precision(c_ctx->precision ? c_ctx->precision : 5) .linefeed(c_ctx->linefeed ? c_ctx->linefeed : LFEED) .indent(c_ctx->indent ? c_ctx->indent : " "); @@ -339,6 +389,7 @@ extern "C" { Context* cpp_ctx = new Context(cpp_opt); // free intermediate data free(include_paths); + free(plugin_paths); // register our custom functions if (c_ctx->c_functions) { @@ -351,6 +402,7 @@ extern "C" { // reset error status c_ctx->error_json = 0; + c_ctx->error_text = 0; c_ctx->error_message = 0; c_ctx->error_status = 0; // reset error position @@ -394,7 +446,7 @@ extern "C" { } // copy the included files on to the context (dont forget to free) - copy_strings(cpp_ctx->get_included_files(skip), &c_ctx->included_files, skip); + if (root) copy_strings(cpp_ctx->get_included_files(skip), &c_ctx->included_files, skip); // return parsed block return root; @@ -585,6 +637,18 @@ extern "C" { } } // Deallocate inc paths + if (options->plugin_paths) { + struct string_list* cur; + struct string_list* next; + cur = options->plugin_paths; + while (cur) { + next = cur->next; + free(cur->string); + free(cur); + cur = next; + } + } + // Deallocate inc paths if (options->include_paths) { struct string_list* cur; struct string_list* next; @@ -603,6 +667,7 @@ extern "C" { // Make it null terminated options->importer = 0; options->c_functions = 0; + options->plugin_paths = 0; options->include_paths = 0; } @@ -611,27 +676,31 @@ extern "C" { static void sass_clear_context (struct Sass_Context* ctx) { if (ctx == 0) return; - // release the allocated memory (mostly via copy_c_str) + // release the allocated memory (mostly via sass_strdup) if (ctx->output_string) free(ctx->output_string); if (ctx->source_map_string) free(ctx->source_map_string); if (ctx->error_message) free(ctx->error_message); + if (ctx->error_text) free(ctx->error_text); if (ctx->error_json) free(ctx->error_json); if (ctx->error_file) free(ctx->error_file); if (ctx->input_path) free(ctx->input_path); if (ctx->output_path) free(ctx->output_path); if (ctx->include_path) free(ctx->include_path); if (ctx->source_map_file) free(ctx->source_map_file); + if (ctx->source_map_root) free(ctx->source_map_root); free_string_array(ctx->included_files); // play safe and reset properties ctx->output_string = 0; ctx->source_map_string = 0; ctx->error_message = 0; + ctx->error_text = 0; ctx->error_json = 0; ctx->error_file = 0; ctx->input_path = 0; ctx->output_path = 0; ctx->include_path = 0; ctx->source_map_file = 0; + ctx->source_map_root = 0; ctx->included_files = 0; // now clear the options sass_clear_options(ctx); @@ -675,13 +744,16 @@ extern "C" { IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, linefeed); IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, input_path); IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, output_path); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, plugin_path); IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, include_path); IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_file); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_root); // Create getter and setters for context IMPLEMENT_SASS_CONTEXT_GETTER(int, error_status); IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_json); IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_message); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_text); IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_file); IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_line); IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_column); @@ -692,6 +764,7 @@ extern "C" { // Take ownership of memory (value on context is set to 0) IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_json); IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_message); + IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_text); IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_file); IMPLEMENT_SASS_CONTEXT_TAKER(char*, output_string); IMPLEMENT_SASS_CONTEXT_TAKER(char*, source_map_string); @@ -702,7 +775,7 @@ extern "C" { struct string_list* include_path = (struct string_list*) calloc(1, sizeof(struct string_list)); if (include_path == 0) return; - include_path->string = path ? copy_c_str(path) : 0; + include_path->string = path ? sass_strdup(path) : 0; struct string_list* last = options->include_paths; if (!options->include_paths) { options->include_paths = include_path; @@ -713,4 +786,23 @@ extern "C" { } } + + // Push function for plugin paths (no manipulation support for now) + void ADDCALL sass_option_push_plugin_path(struct Sass_Options* options, const char* path) + { + + struct string_list* plugin_path = (struct string_list*) calloc(1, sizeof(struct string_list)); + if (plugin_path == 0) return; + plugin_path->string = path ? sass_strdup(path) : 0; + struct string_list* last = options->plugin_paths; + if (!options->plugin_paths) { + options->plugin_paths = plugin_path; + } else { + while (last->next) + last = last->next; + last->next = plugin_path; + } + + } + } diff --git a/sass_context.h b/sass_context.h index 96c5be80b8..e5defaa70f 100644 --- a/sass_context.h +++ b/sass_context.h @@ -50,7 +50,7 @@ ADDAPI void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx); ADDAPI struct Sass_Context* ADDCALL sass_file_context_get_context (struct Sass_File_Context* file_ctx); ADDAPI struct Sass_Context* ADDCALL sass_data_context_get_context (struct Sass_Data_Context* data_ctx); -// Getters for context options from Sass_Context +// Getters for Context_Options from Sass_Context ADDAPI struct Sass_Options* ADDCALL sass_context_get_options (struct Sass_Context* ctx); ADDAPI struct Sass_Options* ADDCALL sass_file_context_get_options (struct Sass_File_Context* file_ctx); ADDAPI struct Sass_Options* ADDCALL sass_data_context_get_options (struct Sass_Data_Context* data_ctx); @@ -58,7 +58,7 @@ ADDAPI void ADDCALL sass_file_context_set_options (struct Sass_File_Context* fil ADDAPI void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); -// Getters for options +// Getters for Context_Option values ADDAPI int ADDCALL sass_option_get_precision (struct Sass_Options* options); ADDAPI enum Sass_Output_Style ADDCALL sass_option_get_output_style (struct Sass_Options* options); ADDAPI bool ADDCALL sass_option_get_source_comments (struct Sass_Options* options); @@ -70,12 +70,14 @@ ADDAPI const char* ADDCALL sass_option_get_indent (struct Sass_Options* options) ADDAPI const char* ADDCALL sass_option_get_linefeed (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_input_path (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_output_path (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_plugin_path (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_include_path (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_source_map_file (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_source_map_root (struct Sass_Options* options); ADDAPI Sass_C_Function_List ADDCALL sass_option_get_c_functions (struct Sass_Options* options); ADDAPI Sass_C_Import_Callback ADDCALL sass_option_get_importer (struct Sass_Options* options); -// Setters for options +// Setters for Context_Option values ADDAPI void ADDCALL sass_option_set_precision (struct Sass_Options* options, int precision); ADDAPI void ADDCALL sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); ADDAPI void ADDCALL sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); @@ -87,16 +89,19 @@ ADDAPI void ADDCALL sass_option_set_indent (struct Sass_Options* options, const ADDAPI void ADDCALL sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); ADDAPI void ADDCALL sass_option_set_input_path (struct Sass_Options* options, const char* input_path); ADDAPI void ADDCALL sass_option_set_output_path (struct Sass_Options* options, const char* output_path); +ADDAPI void ADDCALL sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); ADDAPI void ADDCALL sass_option_set_include_path (struct Sass_Options* options, const char* include_path); ADDAPI void ADDCALL sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); +ADDAPI void ADDCALL sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root); ADDAPI void ADDCALL sass_option_set_c_functions (struct Sass_Options* options, Sass_C_Function_List c_functions); ADDAPI void ADDCALL sass_option_set_importer (struct Sass_Options* options, Sass_C_Import_Callback importer); -// Getter for context +// Getters for Sass_Context values ADDAPI const char* ADDCALL sass_context_get_output_string (struct Sass_Context* ctx); ADDAPI int ADDCALL sass_context_get_error_status (struct Sass_Context* ctx); ADDAPI const char* ADDCALL sass_context_get_error_json (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_text (struct Sass_Context* ctx); ADDAPI const char* ADDCALL sass_context_get_error_message (struct Sass_Context* ctx); ADDAPI const char* ADDCALL sass_context_get_error_file (struct Sass_Context* ctx); ADDAPI size_t ADDCALL sass_context_get_error_line (struct Sass_Context* ctx); @@ -106,12 +111,14 @@ ADDAPI char** ADDCALL sass_context_get_included_files (struct Sass_Context* ctx) // Take ownership of memory (value on context is set to 0) ADDAPI char* ADDCALL sass_context_take_error_json (struct Sass_Context* ctx); +ADDAPI char* ADDCALL sass_context_take_error_text (struct Sass_Context* ctx); ADDAPI char* ADDCALL sass_context_take_error_message (struct Sass_Context* ctx); ADDAPI char* ADDCALL sass_context_take_error_file (struct Sass_Context* ctx); ADDAPI char* ADDCALL sass_context_take_output_string (struct Sass_Context* ctx); ADDAPI char* ADDCALL sass_context_take_source_map_string (struct Sass_Context* ctx); -// Push function for include paths (no manipulation support for now) +// Push function for paths (no manipulation support for now) +ADDAPI void ADDCALL sass_option_push_plugin_path (struct Sass_Options* options, const char* path); ADDAPI void ADDCALL sass_option_push_include_path (struct Sass_Options* options, const char* path); diff --git a/sass_functions.cpp b/sass_functions.cpp index 2d8503c713..e869f04d70 100644 --- a/sass_functions.cpp +++ b/sass_functions.cpp @@ -5,12 +5,13 @@ #endif #include -#include "copy_c_str.hpp" +#include "util.hpp" #include "context.hpp" #include "sass_functions.h" extern "C" { using namespace std; + using namespace Sass; // Struct to hold custom function callback struct Sass_C_Function_Descriptor { @@ -48,6 +49,10 @@ extern "C" { char* base; char* source; char* srcmap; + // error handling + char* error; + size_t line; + size_t column; }; // Struct to hold importer callback @@ -86,10 +91,13 @@ extern "C" { { Sass_Import* v = (Sass_Import*) calloc(1, sizeof(Sass_Import)); if (v == 0) return 0; - v->path = path ? Sass::copy_c_str(path) : 0; - v->base = base ? Sass::copy_c_str(base) : 0; + v->path = path ? sass_strdup(path) : 0; + v->base = base ? sass_strdup(base) : 0; v->source = source; v->srcmap = srcmap; + v->error = 0; + v->line = -1; + v->column = -1; return v; } @@ -99,6 +107,17 @@ extern "C" { return sass_make_import(path, path, source, srcmap); } + // Upgrade a normal import entry to throw an error (original path can be re-used by error reporting) + struct Sass_Import* ADDCALL sass_import_set_error(struct Sass_Import* import, const char* error, size_t line, size_t col) + { + if (import == 0) return 0; + if (import->error) free(import->error); + import->error = error ? strdup(error) : 0; + import->line = line ? line : -1; + import->column = col ? col : -1; + return import; + } + // Setters and getters for entries on the import list void ADDCALL sass_import_set_list_entry(struct Sass_Import** list, size_t idx, struct Sass_Import* entry) { list[idx] = entry; } struct Sass_Import* ADDCALL sass_import_get_list_entry(struct Sass_Import** list, size_t idx) { return list[idx]; } @@ -122,6 +141,7 @@ extern "C" { free(import->base); free(import->source); free(import->srcmap); + free(import->error); free(import); } @@ -131,6 +151,11 @@ extern "C" { const char* ADDCALL sass_import_get_source(struct Sass_Import* entry) { return entry->source; } const char* ADDCALL sass_import_get_srcmap(struct Sass_Import* entry) { return entry->srcmap; } + // Getter for import error entry + size_t ADDCALL sass_import_get_error_line(struct Sass_Import* entry) { return entry->line; } + size_t ADDCALL sass_import_get_error_column(struct Sass_Import* entry) { return entry->column; } + const char* ADDCALL sass_import_get_error_message(struct Sass_Import* entry) { return entry->error; } + // Explicit functions to take ownership of the memory // Resets our own property since we do not know if it is still alive char* ADDCALL sass_import_take_source(struct Sass_Import* entry) { char* ptr = entry->source; entry->source = 0; return ptr; } diff --git a/sass_functions.h b/sass_functions.h index 76cc7d6a08..65aa3e5107 100644 --- a/sass_functions.h +++ b/sass_functions.h @@ -37,6 +37,8 @@ ADDAPI struct Sass_Import** ADDCALL sass_make_import_list (size_t length); // Creator for a single import entry returned by the custom importer inside the list ADDAPI struct Sass_Import* ADDCALL sass_make_import_entry (const char* path, char* source, char* srcmap); ADDAPI struct Sass_Import* ADDCALL sass_make_import (const char* path, const char* base, char* source, char* srcmap); +// set error message to abort import and to print out a message (path from existing object is used in output) +ADDAPI struct Sass_Import* ADDCALL sass_import_set_error(struct Sass_Import* import, const char* message, size_t line, size_t col); // Setters to insert an entry into the import list (you may also use [] access directly) // Since we are dealing with pointers they should have a guaranteed and fixed size @@ -52,6 +54,10 @@ ADDAPI const char* ADDCALL sass_import_get_srcmap (struct Sass_Import*); // The property on our struct will be reset to NULL ADDAPI char* ADDCALL sass_import_take_source (struct Sass_Import*); ADDAPI char* ADDCALL sass_import_take_srcmap (struct Sass_Import*); +// Getters from import error entry +ADDAPI size_t ADDCALL sass_import_get_error_line (struct Sass_Import*); +ADDAPI size_t ADDCALL sass_import_get_error_column (struct Sass_Import*); +ADDAPI const char* ADDCALL sass_import_get_error_message (struct Sass_Import*); // Deallocator for associated memory (incl. entries) ADDAPI void ADDCALL sass_delete_import_list (struct Sass_Import**); diff --git a/sass_interface.cpp b/sass_interface.cpp index 216426c307..c79450324a 100644 --- a/sass_interface.cpp +++ b/sass_interface.cpp @@ -12,7 +12,7 @@ #include #include -#include "copy_c_str.hpp" +#include "util.hpp" #include "context.hpp" #include "inspect.hpp" #include "error_handling.hpp" @@ -115,12 +115,16 @@ extern "C" { .is_indented_syntax_src(c_ctx->options.is_indented_syntax_src) .source_comments(c_ctx->options.source_comments) .source_map_file(safe_str(c_ctx->options.source_map_file)) + .source_map_root(safe_str(c_ctx->options.source_map_root)) .source_map_embed(c_ctx->options.source_map_embed) .source_map_contents(c_ctx->options.source_map_contents) .omit_source_map_url(c_ctx->options.omit_source_map_url) .include_paths_c_str(c_ctx->options.include_paths) + .plugin_paths_c_str(c_ctx->options.plugin_paths) .include_paths_array(0) + .plugin_paths_array(0) .include_paths(vector()) + .plugin_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 : LFEED) @@ -143,7 +147,7 @@ extern "C" { catch (Sass_Error& e) { stringstream msg_stream; msg_stream << e.pstate.path << ":" << e.pstate.line << ": " << e.message << endl; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); c_ctx->error_status = 1; c_ctx->output_string = 0; c_ctx->source_map_string = 0; @@ -151,7 +155,7 @@ extern "C" { catch(bad_alloc& ba) { stringstream msg_stream; msg_stream << "Unable to allocate memory: " << ba.what() << endl; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); c_ctx->error_status = 1; c_ctx->output_string = 0; c_ctx->source_map_string = 0; @@ -159,7 +163,7 @@ extern "C" { catch (std::exception& e) { stringstream msg_stream; msg_stream << "Error: " << e.what() << endl; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); c_ctx->error_status = 1; c_ctx->output_string = 0; c_ctx->source_map_string = 0; @@ -167,7 +171,7 @@ extern "C" { catch (string& e) { stringstream msg_stream; msg_stream << "Error: " << e << endl; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); c_ctx->error_status = 1; c_ctx->output_string = 0; c_ctx->source_map_string = 0; @@ -176,7 +180,7 @@ extern "C" { // couldn't find the specified file in the include paths; report an error stringstream msg_stream; msg_stream << "Unknown error occurred" << endl; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); c_ctx->error_status = 1; c_ctx->output_string = 0; c_ctx->source_map_string = 0; @@ -204,12 +208,16 @@ extern "C" { .is_indented_syntax_src(c_ctx->options.is_indented_syntax_src) .source_comments(c_ctx->options.source_comments) .source_map_file(safe_str(c_ctx->options.source_map_file)) + .source_map_root(safe_str(c_ctx->options.source_map_root)) .source_map_embed(c_ctx->options.source_map_embed) .source_map_contents(c_ctx->options.source_map_contents) .omit_source_map_url(c_ctx->options.omit_source_map_url) .include_paths_c_str(c_ctx->options.include_paths) + .plugin_paths_c_str(c_ctx->options.plugin_paths) .include_paths_array(0) + .plugin_paths_array(0) .include_paths(vector()) + .plugin_paths(vector()) .precision(c_ctx->options.precision ? c_ctx->options.precision : 5) ); if (c_ctx->c_functions) { @@ -229,7 +237,7 @@ extern "C" { catch (Sass_Error& e) { stringstream msg_stream; msg_stream << e.path << ":" << e.pstate.line << ": " << e.message << endl; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); c_ctx->error_status = 1; c_ctx->output_string = 0; c_ctx->source_map_string = 0; @@ -237,7 +245,7 @@ extern "C" { catch(bad_alloc& ba) { stringstream msg_stream; msg_stream << "Unable to allocate memory: " << ba.what() << endl; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); c_ctx->error_status = 1; c_ctx->output_string = 0; c_ctx->source_map_string = 0; @@ -245,7 +253,7 @@ extern "C" { catch (std::exception& e) { stringstream msg_stream; msg_stream << "Error: " << e.what() << endl; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); c_ctx->error_status = 1; c_ctx->output_string = 0; c_ctx->source_map_string = 0; @@ -253,7 +261,7 @@ extern "C" { catch (string& e) { stringstream msg_stream; msg_stream << "Error: " << e << endl; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); c_ctx->error_status = 1; c_ctx->output_string = 0; c_ctx->source_map_string = 0; @@ -262,7 +270,7 @@ extern "C" { // couldn't find the specified file in the include paths; report an error stringstream msg_stream; msg_stream << "Unknown error occurred" << endl; - c_ctx->error_message = copy_c_str(msg_stream.str().c_str()); + c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); c_ctx->error_status = 1; c_ctx->output_string = 0; c_ctx->source_map_string = 0; diff --git a/sass_interface.h b/sass_interface.h index c8d9457e4a..16bc41c212 100644 --- a/sass_interface.h +++ b/sass_interface.h @@ -28,11 +28,14 @@ struct sass_options { bool source_map_embed; // embed include contents in maps bool source_map_contents; + // Pass-through as sourceRoot property + const char* source_map_root; // Treat source_string as sass (as opposed to scss) bool is_indented_syntax_src; // Colon-separated list of paths // Semicolon-separated on Windows const char* include_paths; + const char* plugin_paths; // String to be used for indentation const char* indent; // String to be used to for line feeds diff --git a/sass_values.cpp b/sass_values.cpp index 1ef88b70f9..4991bc53f7 100644 --- a/sass_values.cpp +++ b/sass_values.cpp @@ -6,11 +6,12 @@ #include #include -#include "copy_c_str.hpp" +#include "util.hpp" #include "sass_values.h" extern "C" { using namespace std; + using namespace Sass; struct Sass_Unknown { enum Sass_Tag tag; @@ -165,7 +166,7 @@ extern "C" { if (v == 0) return 0; v->number.tag = SASS_NUMBER; v->number.value = val; - v->number.unit = unit ? Sass::copy_c_str(unit) : 0; + v->number.unit = unit ? sass_strdup(unit) : 0; if (v->number.unit == 0) { free(v); return 0; } return v; } @@ -187,7 +188,7 @@ extern "C" { Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); if (v == 0) return 0; v->string.tag = SASS_STRING; - v->string.value = val ? Sass::copy_c_str(val) : 0; + v->string.value = val ? sass_strdup(val) : 0; if (v->string.value == 0) { free(v); return 0; } return v; } @@ -228,7 +229,7 @@ extern "C" { Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); if (v == 0) return 0; v->error.tag = SASS_ERROR; - v->error.message = msg ? Sass::copy_c_str(msg) : 0; + v->error.message = msg ? sass_strdup(msg) : 0; if (v->error.message == 0) { free(v); return 0; } return v; } @@ -238,7 +239,7 @@ extern "C" { Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); if (v == 0) return 0; v->warning.tag = SASS_WARNING; - v->warning.message = msg ? Sass::copy_c_str(msg) : 0; + v->warning.message = msg ? sass_strdup(msg) : 0; if (v->warning.message == 0) { free(v); return 0; } return v; } diff --git a/source_map.cpp b/source_map.cpp index ba72c730cb..2cfdc32065 100644 --- a/source_map.cpp +++ b/source_map.cpp @@ -21,10 +21,16 @@ namespace Sass { const vector includes = ctx.include_links; const vector sources = ctx.sources; - JsonNode *json_srcmap = json_mkobject(); + JsonNode* json_srcmap = json_mkobject(); json_append_member(json_srcmap, "version", json_mknumber(3)); + // pass-through sourceRoot option + if (!ctx.source_map_root.empty()) { + JsonNode* root = json_mkstring(ctx.source_map_root.c_str()); + json_append_member(json_srcmap, "sourceRoot", root); + } + const char *include = file.c_str(); JsonNode *json_include = json_mkstring(include); json_append_member(json_srcmap, "file", json_include); @@ -106,15 +112,51 @@ namespace Sass { return result; } - void SourceMap::update_column(const string& str) + void SourceMap::prepend(const OutputBuffer& out) + { + Offset size(out.smap.current_position); + for (Mapping mapping : out.smap.mappings) { + if (mapping.generated_position.line > size.line) { + throw(runtime_error("prepend sourcemap has illegal line")); + } + if (mapping.generated_position.line == size.line) { + if (mapping.generated_position.column > size.column) { + throw(runtime_error("prepend sourcemap has illegal column")); + } + } + } + // will adjust the offset + prepend(Offset(out.buffer)); + // now add the new mappings + VECTOR_UNSHIFT(mappings, out.smap.mappings); + } + + void SourceMap::append(const OutputBuffer& out) { - const ptrdiff_t new_line_count = std::count(str.begin(), str.end(), '\n'); - current_position.line += new_line_count; - if (new_line_count > 0) { - current_position.column = str.size() - str.find_last_of('\n') - 1; - } else { - current_position.column += str.size(); + append(Offset(out.buffer)); + } + + void SourceMap::prepend(const Offset& offset) + { + if (offset.line != 0 || offset.column != 0) { + for (Mapping& mapping : mappings) { + // move stuff on the first old line + if (mapping.generated_position.line == 0) { + mapping.generated_position.column += offset.column; + } + // make place for the new lines + mapping.generated_position.line += offset.line; + } } + if (current_position.line == 0) { + current_position.column += offset.column; + } + current_position.line += offset.line; + } + + void SourceMap::append(const Offset& offset) + { + current_position += offset; } void SourceMap::add_open_mapping(AST_Node* node) diff --git a/source_map.hpp b/source_map.hpp index 0830f9fd82..cdb2792b38 100644 --- a/source_map.hpp +++ b/source_map.hpp @@ -5,12 +5,17 @@ #include "ast_fwd_decl.hpp" #include "base64vlq.hpp" +#include "position.hpp" #include "mapping.hpp" +#define VECTOR_PUSH(vec, ins) vec.insert(vec.end(), ins.begin(), ins.end()) +#define VECTOR_UNSHIFT(vec, ins) vec.insert(vec.begin(), ins.begin(), ins.end()) + namespace Sass { using std::vector; class Context; + class OutputBuffer; class SourceMap { @@ -22,7 +27,10 @@ namespace Sass { void setFile(const string& str) { file = str; } - void update_column(const string& str); + void append(const Offset& offset); + void prepend(const Offset& offset); + void append(const OutputBuffer& out); + void prepend(const OutputBuffer& out); void add_open_mapping(AST_Node* node); void add_close_mapping(AST_Node* node); @@ -41,6 +49,17 @@ namespace Sass { Base64VLQ base64vlq; }; + class OutputBuffer { + public: + OutputBuffer(void) + : buffer(""), + smap() + { } + public: + string buffer; + SourceMap smap; + }; + } #endif diff --git a/subset_map.hpp b/subset_map.hpp index b9ce01a23b..580c0cc145 100644 --- a/subset_map.hpp +++ b/subset_map.hpp @@ -82,6 +82,7 @@ namespace Sass { vector get_v(const vector& s); bool empty() { return values_.empty(); } void clear() { values_.clear(); hash_.clear(); } + const vector values(void) { return values_; } }; template diff --git a/utf8_string.hpp b/utf8_string.hpp index 1ab2416f9a..c2d7356a9d 100644 --- a/utf8_string.hpp +++ b/utf8_string.hpp @@ -2,6 +2,7 @@ #define SASS_UTF8_STRING_H #include +#include "utf8.h" namespace Sass { namespace UTF_8 { diff --git a/util.cpp b/util.cpp index 12e007c56b..eedf11f310 100644 --- a/util.cpp +++ b/util.cpp @@ -6,6 +6,44 @@ namespace Sass { + #define out_of_memory() do { \ + fprintf(stderr, "Out of memory.\n"); \ + exit(EXIT_FAILURE); \ + } while (0) + + /* Sadly, sass_strdup is not portable. */ + char *sass_strdup(const char *str) + { + char *ret = (char*) malloc(strlen(str) + 1); + if (ret == NULL) + out_of_memory(); + strcpy(ret, str); + return ret; + } + + /* Locale unspecific atof function. */ + double sass_atof(const char *str) + { + char separator = *(localeconv()->decimal_point); + if(separator != '.'){ + // The current locale specifies another + // separator. convert the separator to the + // one understood by the locale if needed + const char *found = strchr(str, '.'); + if(found != NULL){ + // substitution is required. perform the substitution on a copy + // of the string. This is slower but it is thread safe. + char *copy = sass_strdup(str); + *(copy + (found - str)) = separator; + double res = atof(copy); + free(copy); + return res; + } + } + + return atof(str); + } + // double escape every escape sequences // escape unescaped quotes and backslashes string string_escape(const string& str) @@ -91,7 +129,10 @@ namespace Sass { out += i; } } - if (esc) out += 'Z'; + // happens when parsing does not correctly skip + // over escaped sequences for ie. interpolations + // one example: foo\#{interpolate} + // if (esc) out += '\\'; return out; } @@ -138,7 +179,8 @@ namespace Sass { else return text; } - string normalize_wspace(const string& str) { + string normalize_wspace(const string& str) + { bool ws = false; bool esc = false; char inside_str = 0; diff --git a/util.hpp b/util.hpp index cf47691d49..5d01045cc6 100644 --- a/util.hpp +++ b/util.hpp @@ -8,6 +8,8 @@ namespace Sass { using namespace std; + char* sass_strdup(const char* str); + double sass_atof(const char* str); string string_escape(const string& str); string string_unescape(const string& str); string evacuate_quotes(const string& str); diff --git a/win/libsass.filters b/win/libsass.filters index a165dd0433..fb566b2a09 100644 --- a/win/libsass.filters +++ b/win/libsass.filters @@ -39,9 +39,6 @@ Source Files - - Source Files - Source Files @@ -78,6 +75,9 @@ Source Files + + Source Files + Source Files @@ -176,9 +176,6 @@ Header Files - - Header Files - Header Files @@ -233,6 +230,9 @@ Header Files + + Header Files + Header Files diff --git a/win/libsass.vcxproj b/win/libsass.vcxproj index 434f80767d..dac9b18c5e 100644 --- a/win/libsass.vcxproj +++ b/win/libsass.vcxproj @@ -164,7 +164,6 @@ - @@ -178,6 +177,7 @@ + @@ -208,7 +208,6 @@ - @@ -228,6 +227,7 @@ +