diff --git a/.gitignore b/.gitignore index ef3ca02205..5eb2d2b939 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,10 @@ libsass/* *.lo *.so *.dll +*.h.gch +*.h.pch +*.hpp.gch +*.hpp.pch *.a *.suo *.sdf diff --git a/.travis.yml b/.travis.yml index d2a0ca0f45..32bd964b1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,10 +61,6 @@ matrix: script: - ./script/ci-build-libsass - - ./script/ci-build-plugin math - - ./script/ci-build-plugin glob - - ./script/ci-build-plugin digest - - ./script/ci-build-plugin tests before_install: ./script/ci-install-deps install: ./script/ci-install-compiler after_success: ./script/ci-report-coverage diff --git a/GNUmakefile.am b/GNUmakefile.am index 06a1d0c1e6..5a58c3eb4c 100644 --- a/GNUmakefile.am +++ b/GNUmakefile.am @@ -29,7 +29,7 @@ if ENABLE_TESTS SASS_SASSC_PATH ?= $(top_srcdir)/sassc SASS_SPEC_PATH ?= $(top_srcdir)/sass-spec -LIBSASS_SPEC_PATH ?= $(top_srcdir)/libsass-spec +SASS_SPEC_ROOT ?= $(top_srcdir)/sass-spec noinst_PROGRAMS = tester tester_LDADD = src/libsass.la @@ -50,41 +50,25 @@ AM_RB_LOG_FLAGS = $(RUBY) SASS_TEST_FLAGS = --impl libsass SASS_TEST_FLAGS += -r $(SASS_SPEC_PATH)/spec SASS_TEST_FLAGS += -c $(top_srcdir)/tester$(EXEEXT) -LIBSASS_TEST_FLAGS = --impl libsass -LIBSASS_TEST_FLAGS += -r $(LIBSASS_SPEC_PATH)/spec -LIBSASS_TEST_FLAGS += -c $(top_srcdir)/tester$(EXEEXT) -COMPRESSED_TEST_FLAGS = --impl libsass -COMPRESSED_TEST_FLAGS += -r $(LIBSASS_SPEC_PATH)/styles/compressed -COMPRESSED_TEST_FLAGS += -c $(top_srcdir)/tester$(EXEEXT) -COMPRESSED_TEST_FLAGS += --cmd-args="-t compressed" +SASS_TEST_FLAGS += --cmd-args "-I $(SASS_SPEC_ROOT)/spec" AM_TESTS_ENVIRONMENT = TEST_FLAGS='$(SASS_TEST_FLAGS)' SASS_TESTER = $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb test: $(SASS_TESTER) $(SASS_TEST_FLAGS) - $(SASS_TESTER) $(LIBSASS_TEST_FLAGS) - $(SASS_TESTER) $(COMPRESSED_TEST_FLAGS) test_build: $(SASS_TESTER) $(SASS_TEST_FLAGS) - $(SASS_TESTER) $(LIBSASS_TEST_FLAGS) - $(SASS_TESTER) $(COMPRESSED_TEST_FLAGS) test_full: $(SASS_TESTER) --run-todo $(SASS_TEST_FLAGS) - $(SASS_TESTER) --run-todo $(LIBSASS_TEST_FLAGS) - $(SASS_TESTER) --run-todo $(COMPRESSED_TEST_FLAGS) test_probe: $(SASS_TESTER) --probe-todo $(SASS_TEST_FLAGS) - $(SASS_TESTER) --probe-todo $(LIBSASS_TEST_FLAGS) - $(SASS_TESTER) --probe-todo $(COMPRESSED_TEST_FLAGS) test_interactive: $(SASS_TESTER) --interactive $(SASS_TEST_FLAGS) - $(SASS_TESTER) --interactive $(LIBSASS_TEST_FLAGS) - $(SASS_TESTER) --interactive $(COMPRESSED_TEST_FLAGS) .PHONY: test test_build test_full test_probe diff --git a/Makefile b/Makefile index 30023bab0e..aba515e5fb 100644 --- a/Makefile +++ b/Makefile @@ -16,14 +16,24 @@ CFLAGS ?= -Wall CXXFLAGS ?= -Wall LDFLAGS ?= -Wall ifndef COVERAGE - CFLAGS += -O2 - CXXFLAGS += -O2 - LDFLAGS += -O2 + CFLAGS += -O3 -pipe -DNDEBUG -fomit-frame-pointer + CXXFLAGS += -O3 -pipe -DNDEBUG -fomit-frame-pointer + LDFLAGS += -O3 -pipe -DNDEBUG -fomit-frame-pointer else CFLAGS += -O1 -fno-omit-frame-pointer CXXFLAGS += -O1 -fno-omit-frame-pointer LDFLAGS += -O1 -fno-omit-frame-pointer endif +ifeq "$(LIBSASS_GPO)" "generate" + CFLAGS += -fprofile-generate + CXXFLAGS += -fprofile-generate + LDFLAGS += -fprofile-generate -Wl,-fprofile-instr-generate +endif +ifeq "$(LIBSASS_GPO)" "use" + CFLAGS += -fprofile-use + CXXFLAGS += -fprofile-use + LDFLAGS += -fprofile-use -Wl,-fprofile-instr-use +endif CAT ?= $(if $(filter $(OS),Windows_NT),type,cat) ifneq (,$(findstring /cygdrive/,$(PATH))) @@ -84,6 +94,9 @@ else CXXFLAGS += -I include endif +CFLAGS += -I $(SASS_LIBSASS_PATH)/src +CXXFLAGS += -I $(SASS_LIBSASS_PATH)/src + CFLAGS += $(EXTRA_CFLAGS) CXXFLAGS += $(EXTRA_CXXFLAGS) LDFLAGS += $(EXTRA_LDFLAGS) @@ -144,7 +157,7 @@ SASS_SASSC_PATH ?= sassc SASS_SPEC_PATH ?= sass-spec SASS_SPEC_SPEC_DIR ?= spec LIBSASS_SPEC_PATH ?= libsass-spec -LIBSASS_SPEC_SPEC_DIR ?= spec +LIBSASS_SPEC_SPEC_DIR ?= suites SASSC_BIN = $(SASS_SASSC_PATH)/bin/sassc RUBY_BIN = ruby @@ -177,6 +190,7 @@ endif include Makefile.conf OBJECTS = $(addprefix src/,$(SOURCES:.cpp=.o)) COBJECTS = $(addprefix src/,$(CSOURCES:.c=.o)) +HEADOBJS = $(addprefix src/,$(HPPFILES:.hpp=.hpp.gch)) RCOBJECTS = $(RESOURCES:.rc=.o) DEBUG_LVL ?= NONE @@ -185,6 +199,7 @@ CLEANUPS ?= CLEANUPS += $(RCOBJECTS) CLEANUPS += $(COBJECTS) CLEANUPS += $(OBJECTS) +CLEANUPS += $(HEADOBJS) CLEANUPS += $(LIBSASS_LIB) all: $(BUILD) @@ -218,15 +233,18 @@ lib/libsass.dll: $(COBJECTS) $(OBJECTS) $(RCOBJECTS) | lib $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) \ -s -Wl,--subsystem,windows,--out-implib,lib/libsass.a -%.o: %.c - $(CC) $(CFLAGS) -c -o $@ $< - -%.o: %.rc +$(RCOBJECTS): %.o: %.rc $(WINDRES) -i $< -o $@ -%.o: %.cpp +$(OBJECTS): %.o: %.cpp $(CXX) $(CXXFLAGS) -c -o $@ $< +$(COBJECTS): %.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +$(HEADOBJS): %.hpp.gch: %.hpp + $(CXX) $(CXXFLAGS) -x c++-header -c -o $@ $< + %: %.o static $(CXX) $(CXXFLAGS) -o $@ $+ $(LDFLAGS) $(LDLIBS) @@ -252,7 +270,6 @@ $(DESTDIR)$(PREFIX)/include/%.h: include/%.h \ $(INSTALL) -v -m0644 "$<" "$@" install-headers: $(DESTDIR)$(PREFIX)/include/sass.h \ - $(DESTDIR)$(PREFIX)/include/sass2scss.h \ $(DESTDIR)$(PREFIX)/include/sass/base.h \ $(DESTDIR)$(PREFIX)/include/sass/version.h \ $(DESTDIR)$(PREFIX)/include/sass/values.h \ @@ -302,13 +319,13 @@ test_build: $(SASSC_BIN) $(SASS_SPEC_PATH) $(LIBSASS_SPEC_PATH) --cmd-args "-I $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)" \ $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/compressed $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/nested test_full: $(SASSC_BIN) $(SASS_SPEC_PATH) $(LIBSASS_SPEC_PATH) @@ -316,13 +333,13 @@ test_full: $(SASSC_BIN) $(SASS_SPEC_PATH) $(LIBSASS_SPEC_PATH) --cmd-args "-I $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)" \ --run-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ --run-todo $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ --run-todo $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/compressed $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ --run-todo $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/nested test_probe: $(SASSC_BIN) $(SASS_SPEC_PATH) $(LIBSASS_SPEC_PATH) @@ -330,13 +347,13 @@ test_probe: $(SASSC_BIN) $(SASS_SPEC_PATH) $(LIBSASS_SPEC_PATH) --cmd-args "-I $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)" \ --probe-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ --probe-todo $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ --probe-todo $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/compressed $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ --probe-todo $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/nested test_interactive: $(SASSC_BIN) $(SASS_SPEC_PATH) $(LIBSASS_SPEC_PATH) @@ -344,13 +361,13 @@ test_interactive: $(SASSC_BIN) $(SASS_SPEC_PATH) $(LIBSASS_SPEC_PATH) --cmd-args "-I $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)" \ --interactive $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ --interactive $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ --interactive $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/compressed $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ --interactive $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/nested clean-objects: | lib diff --git a/Makefile.conf b/Makefile.conf index 679e36ccfa..f088bec305 100644 --- a/Makefile.conf +++ b/Makefile.conf @@ -4,95 +4,125 @@ # in parallel. But we also want to mix them a little too avoid # heavy RAM usage peaks. Other than that the order is arbitrary. - INCFILES = \ sass.h \ - sass2scss.h \ sass/base.h \ - sass/context.h \ - sass/functions.h \ + sass/fwdecl.h \ + sass/enums.h \ sass/values.h \ - sass/version.h + sass/version.h \ + sass/context.h \ + sass/function.h HPPFILES = \ ast.hpp \ - ast2c.hpp \ + ast_css.hpp \ + ast_nodes.hpp \ ast_def_macros.hpp \ ast_fwd_decl.hpp \ ast_helpers.hpp \ ast_selectors.hpp \ + ast_containers.hpp \ ast_supports.hpp \ + ast_callables.hpp \ + ast_statements.hpp \ + ast_expressions.hpp \ ast_values.hpp \ backtrace.hpp \ base64vlq.hpp \ - bind.hpp \ - c2ast.hpp \ - check_nesting.hpp \ + character.hpp \ + charcode.hpp \ color_maps.hpp \ constants.hpp \ context.hpp \ cssize.hpp \ dart_helpers.hpp \ - debug.hpp \ debugger.hpp \ emitter.hpp \ environment.hpp \ - error_handling.hpp \ + exceptions.hpp \ + memory.hpp \ + memory/config.hpp \ + memory/allocator.hpp \ + memory/shared_ptr.hpp \ + memory/memory_pool.hpp \ + MurmurHash2.hpp \ eval.hpp \ - expand.hpp \ extender.hpp \ extension.hpp \ file.hpp \ + compiler.hpp \ fn_colors.hpp \ fn_lists.hpp \ fn_maps.hpp \ - fn_miscs.hpp \ + fn_meta.hpp \ fn_numbers.hpp \ fn_selectors.hpp \ - fn_strings.hpp \ + fn_texts.hpp \ fn_utils.hpp \ + strings.hpp \ inspect.hpp \ + terminal.hpp \ + interpolation.hpp \ + logger.hpp \ json.hpp \ kwd_arg_macros.hpp \ - lexer.hpp \ - listize.hpp \ + randomize.hpp \ mapping.hpp \ - memory.hpp \ - MurmurHash2.hpp \ - operation.hpp \ - operators.hpp \ ordered_map.hpp \ + source.hpp \ output.hpp \ parser.hpp \ + parser_base.hpp \ + parser_css.hpp \ + parser_expression.hpp \ + parser_media_query.hpp \ + parser_at_root_query.hpp \ + parser_keyframe_selector.hpp \ + parser_sass.hpp \ + parser_scss.hpp \ + parser_selector.hpp \ + parser_stylesheet.hpp \ permutate.hpp \ plugins.hpp \ - position.hpp \ - prelexer.hpp \ + offset.hpp \ remove_placeholders.hpp \ - sass.hpp \ - sass_context.hpp \ - sass_functions.hpp \ - sass_values.hpp \ - settings.hpp \ - source.hpp \ - source_data.hpp \ + capi_sass.hpp \ + capi_base.hpp \ + capi_list.hpp \ + capi_error.hpp \ + capi_context.hpp \ + capi_compiler.hpp \ + capi_functions.hpp \ + capi_values.hpp \ + scanner_string.hpp \ source_map.hpp \ + source_state.hpp \ + source_span.hpp \ stylesheet.hpp \ - to_value.hpp \ units.hpp \ - utf8_string.hpp \ - util.hpp \ + unicode.hpp \ util_string.hpp \ - values.hpp \ - memory/allocator.hpp \ - memory/config.hpp \ - memory/memory_pool.hpp \ - memory/shared_ptr.hpp + visitor_css.hpp \ + visitor_expression.hpp \ + visitor_selector.hpp \ + visitor_statement.hpp \ + visitor_value.hpp \ + string_utils.hpp \ + callstack.hpp \ + environment_cnt.hpp \ + environment_key.hpp \ + environment_stack.hpp SOURCES = \ ast.cpp \ + ast_css.cpp \ + ast_nodes.cpp \ ast_values.cpp \ ast_supports.cpp \ + ast_callables.cpp \ + ast_statements.cpp \ + ast_expressions.cpp \ ast_sel_cmp.cpp \ ast_sel_unify.cpp \ ast_sel_super.cpp \ @@ -100,60 +130,69 @@ SOURCES = \ ast_selectors.cpp \ context.cpp \ constants.cpp \ - fn_utils.cpp \ - fn_miscs.cpp \ + compiler.cpp \ fn_maps.cpp \ fn_lists.cpp \ fn_colors.cpp \ fn_numbers.cpp \ - fn_strings.cpp \ + fn_texts.cpp \ fn_selectors.cpp \ + fn_meta.cpp \ color_maps.cpp \ environment.cpp \ ast_fwd_decl.cpp \ - bind.cpp \ file.cpp \ - util.cpp \ util_string.cpp \ + string_utils.cpp \ + logger.cpp \ + strings.cpp \ json.cpp \ units.cpp \ - values.cpp \ plugins.cpp \ source.cpp \ - position.cpp \ - lexer.cpp \ - parser.cpp \ - parser_selectors.cpp \ - prelexer.cpp \ + offset.cpp \ eval.cpp \ - eval_selectors.cpp \ - expand.cpp \ - listize.cpp \ + randomize.cpp \ cssize.cpp \ extender.cpp \ extension.cpp \ stylesheet.cpp \ + interpolation.cpp \ + parser.cpp \ + parser_css.cpp \ + parser_base.cpp \ + parser_scss.cpp \ + parser_sass.cpp \ + parser_selector.cpp \ + parser_stylesheet.cpp \ + parser_expression.cpp \ + parser_media_query.cpp \ + parser_at_root_query.cpp \ + parser_keyframe_selector.cpp \ output.cpp \ inspect.cpp \ + terminal.cpp \ emitter.cpp \ - check_nesting.cpp \ + scanner_string.cpp \ remove_placeholders.cpp \ - sass.cpp \ - sass_values.cpp \ - sass_context.cpp \ - sass_functions.cpp \ - sass2scss.cpp \ - backtrace.cpp \ - operators.cpp \ - ast2c.cpp \ - c2ast.cpp \ - to_value.cpp \ + capi_sass.cpp \ + capi_base.cpp \ + capi_lists.cpp \ + capi_error.cpp \ + capi_values.cpp \ + capi_context.cpp \ + capi_compiler.cpp \ + capi_functions.cpp \ + character.cpp \ + environment_stack.cpp \ source_map.cpp \ - error_handling.cpp \ + source_state.cpp \ + source_span.cpp \ + exceptions.cpp \ memory/allocator.cpp \ memory/shared_ptr.cpp \ - utf8_string.cpp \ - base64vlq.cpp + LUrlParser/LUrlParser.cpp \ + unicode.cpp \ + cencode.cpp -CSOURCES = \ - cencode.c +CSOURCES = diff --git a/appveyor.yml b/appveyor.yml index f462207844..ac38a87fb3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -os: Visual Studio 2013 +os: Visual Studio 2015 environment: CTEST_OUTPUT_ON_FAILURE: 1 @@ -33,7 +33,7 @@ cache: install: - git clone https://github.com/sass/sassc.git - - git clone https://github.com/sass/sass-spec.git + - git clone https://github.com/mgreter/sass-spec.git --branch feature/libsass-parser-backport - set PATH=C:\Ruby%ruby_version%\bin;%PATH% - ps: | if(!(gem which minitest 2>$nul)) { gem install minitest --no-ri --no-rdoc } @@ -66,17 +66,6 @@ build_script: test_script: - ps: | $PRNR = $env:APPVEYOR_PULL_REQUEST_NUMBER - if ($PRNR) { - echo "Fetching info for PR $PRNR" - wget https://api.github.com/repos/sass/libsass/pulls/$PRNR -OutFile pr.json - $json = cat pr.json -Raw - $SPEC_PR = [regex]::match($json,'sass\/sass-spec(#|\/pull\/)([0-9]+)').Groups[2].Value - if ($SPEC_PR) { - echo "Checkout sass spec PR $SPEC_PR" - git -C sass-spec fetch -q -u origin pull/$SPEC_PR/head:ci-spec-pr-$SPEC_PR - git -C sass-spec checkout -q --force ci-spec-pr-$SPEC_PR - } - } $env:TargetPath = Join-Path $pwd.Path $env:TargetPath If (Test-Path "$env:TargetPath") { ruby sass-spec/sass-spec.rb --probe-todo --impl libsass -c $env:TargetPath -s sass-spec/spec diff --git a/configure.ac b/configure.ac index b5a943217a..78899d642e 100644 --- a/configure.ac +++ b/configure.ac @@ -88,18 +88,23 @@ the --with-sass-spec-dir= argument. case $sass_spec_dir in /*) SASS_SPEC_PATH=`$RUBY -e "require 'pathname'; puts Pathname.new('$sass_spec_dir').relative_path_from(Pathname.new('$PWD')).to_s"` + SASS_SPEC_ROOT="$sass_spec_dir" ;; *) SASS_SPEC_PATH="$sass_spec_dir" + SASS_SPEC_ROOT="$sass_spec_dir" ;; esac AC_SUBST(SASS_SPEC_PATH) + AC_SUBST(SASS_SPEC_ROOT) else # we do not really need these paths for non test build # but automake may error if we do not define them here SASS_SPEC_PATH=sass-spec + SASS_SPEC_ROOT=sass-spec SASS_SASSC_PATH=sassc AC_SUBST(SASS_SPEC_PATH) + AC_SUBST(SASS_SPEC_ROOT) AC_SUBST(SASS_SASSC_PATH) fi diff --git a/docs/build-shared-library.md b/docs/build-shared-library.md index 3c143b46ae..409ab3cebb 100644 --- a/docs/build-shared-library.md +++ b/docs/build-shared-library.md @@ -28,7 +28,6 @@ This should install these files /usr/lib/libsass.so.0.0.9 # $ ls -la /usr/include/sass* /usr/include/sass.h -/usr/include/sass2scss.h /usr/include/sass/context.h /usr/include/sass/functions.h /usr/include/sass/values.h diff --git a/include/sass.h b/include/sass.h index 1dd8b06dca..41c90fa435 100644 --- a/include/sass.h +++ b/include/sass.h @@ -3,13 +3,28 @@ // #define DEBUG 1 +// Note: we can't forward declare with inheritance +// https://stackoverflow.com/a/10145303/1550314 + // include API headers #include +#include #include +#include #include +#include +#include +#include #include #include -#include +#include -#endif +typedef struct SassImport* Sass_Import_Entry; +typedef struct SassImporter* Sass_Importer_Entry; +typedef struct SassFunction* Sass_Function_Entry; +typedef struct SassImportList* Sass_Import_List; +typedef struct SassImporterList* Sass_Importer_List; +typedef struct SassFunctionList* Sass_Function_List; + +#endif diff --git a/include/sass/base.h b/include/sass/base.h index 132da693ab..b73c4023dc 100644 --- a/include/sass/base.h +++ b/include/sass/base.h @@ -1,8 +1,5 @@ -#ifndef SASS_BASE_H -#define SASS_BASE_H - -// #define DEBUG -// #define DEBUG_SHARED_PTR +#ifndef SASS_C_BASE_H +#define SASS_C_BASE_H #ifdef _MSC_VER #pragma warning(disable : 4503) @@ -23,9 +20,17 @@ #define noexcept throw( ) #endif +// Load some POD types #include +#include #include +// Include forward declarations +#include + +// Include enumerations +#include + #ifdef __GNUC__ #define DEPRECATED(func) func __attribute__ ((deprecated)) #elif defined(_MSC_VER) @@ -54,41 +59,50 @@ #endif -/* Make sure functions are exported with C linkage under C++ compilers. */ #ifdef __cplusplus extern "C" { #endif - -// Different render styles -enum Sass_Output_Style { - SASS_STYLE_NESTED, - SASS_STYLE_EXPANDED, - SASS_STYLE_COMPACT, - SASS_STYLE_COMPRESSED, - // only used internaly - SASS_STYLE_INSPECT, - SASS_STYLE_TO_SASS, - SASS_STYLE_TO_CSS -}; - -// to allocate buffer to be filled -ADDAPI void* ADDCALL sass_alloc_memory(size_t size); -// to allocate a buffer from existing string -ADDAPI char* ADDCALL sass_copy_c_string(const char* str); -// to free overtaken memory when done -ADDAPI void ADDCALL sass_free_memory(void* ptr); - -// Some convenient string helper function -ADDAPI char* ADDCALL sass_string_quote (const char* str, const char quote_mark); -ADDAPI char* ADDCALL sass_string_unquote (const char* str); - -// Implemented sass language version -// Hardcoded version 3.4 for time being -ADDAPI const char* ADDCALL libsass_version(void); - -// Get compiled libsass language -ADDAPI const char* ADDCALL libsass_language_version(void); + // Change the virtual current working directory + // You should probably not really use this function! + ADDAPI void ADDCALL sass_chdir(const char* path); + + // Prints message to stderr with color for windows + ADDAPI void ADDCALL sass_print_stdout(const char* message); + ADDAPI void ADDCALL sass_print_stderr(const char* message); + + // Allocate a memory block on the heap of (at least) [size]. + // Make sure to release to acquired memory at some later point via + // `sass_free_memory`. You need to go through my utility function in + // case your code and my main program don't use the same memory manager. + ADDAPI void* ADDCALL sass_alloc_memory(size_t size); + + // Allocate a memory block on the heap and copy [string] into it. + // Make sure to release to acquired memory at some later point via + // `sass_free_memory`. You need to go through my utility function in + // case your code and my main program don't use the same memory manager. + ADDAPI char* ADDCALL sass_copy_c_string(const char* str); + + // Deallocate libsass heap memory + ADDAPI void ADDCALL sass_free_memory(void* ptr); + ADDAPI void ADDCALL sass_free_c_string(char* ptr); + + // Resolve a file via the given include paths in the sass option struct + // find_file looks for the exact file name while find_include does a regular sass include + // ADDAPI char* ADDCALL sass_find_file (const char* path, struct SassOptionsCpp* opt); + // ADDAPI char* ADDCALL sass_find_include (const char* path, struct SassOptionsCpp* opt); + + // Resolve a file relative to last import or include paths in the sass option struct + // find_file looks for the exact file name while find_include does a regular sass include + // ADDAPI char* ADDCALL sass_compiler_find_file(const char* path, struct SassCompiler* compiler); + // ADDAPI char* ADDCALL sass_compiler_find_include(const char* path, struct SassCompiler* compiler); + + // Return implemented sass language version + ADDAPI const char* ADDCALL libsass_version(void); + + // Return the compiled libsass language (hard-coded) + // This is hard-coded with the library on compilation! + ADDAPI const char* ADDCALL libsass_language_version(void); #ifdef __cplusplus } // __cplusplus defined. diff --git a/include/sass/compiler.h b/include/sass/compiler.h new file mode 100644 index 0000000000..8fc1642bda --- /dev/null +++ b/include/sass/compiler.h @@ -0,0 +1,91 @@ +#ifndef SASS_C_COMPILER_H +#define SASS_C_COMPILER_H + +#include +#ifdef __cplusplus +extern "C" { +#endif + + // Create a new compiler from the libsass context and the given entry point + ADDAPI struct SassCompiler* ADDCALL sass_make_compiler(); + + // Release all memory allocated with the structures + ADDAPI void ADDCALL sass_delete_compiler(struct SassCompiler* compiler); + + // Parse the entry point and potentially all imports within + ADDAPI void ADDCALL sass_compiler_parse(struct SassCompiler* compiler); + + // Evaluate the parsed entry point and store resulting ast-tree + ADDAPI void ADDCALL sass_compiler_compile(struct SassCompiler* compiler); + + // Render the evaluated ast-tree to get the final output string + ADDAPI void ADDCALL sass_compiler_render(struct SassCompiler* compiler); + + // Push function for paths (no manipulation support for now) + ADDAPI void ADDCALL sass_compiler_load_plugins(struct SassCompiler* compiler, const char* paths); + ADDAPI void ADDCALL sass_compiler_add_include_paths(struct SassCompiler* compiler, const char* paths); + ADDAPI void ADDCALL sass_compiler_add_custom_header(struct SassCompiler* compiler, struct SassImporter* header); + ADDAPI void ADDCALL sass_compiler_add_custom_importer(struct SassCompiler* compiler, struct SassImporter* importer); + ADDAPI void ADDCALL sass_compiler_add_custom_function(struct SassCompiler* compiler, struct SassFunction* function); + + // Setters for output and console logging styles + ADDAPI void ADDCALL sass_compiler_set_output_style(struct SassCompiler* compiler, enum SassOutputStyle output_style); + ADDAPI void ADDCALL sass_compiler_set_logger_style(struct SassCompiler* compiler, enum SassLoggerStyle log_style); + + // Getters for compiler option values + ADDAPI int ADDCALL sass_compiler_get_precision(struct SassCompiler* compiler); + ADDAPI const char* ADDCALL sass_compiler_get_output_path(struct SassCompiler* compiler); + ADDAPI struct SassImport* ADDCALL sass_compiler_get_entry_point(struct SassCompiler* compiler); + + // Setters for compiler option values + ADDAPI void ADDCALL sass_compiler_set_precision(struct SassCompiler* compiler, int precision); + ADDAPI void ADDCALL sass_compiler_set_output_path(struct SassCompiler* compiler, const char* output_path); + ADDAPI void ADDCALL sass_compiler_set_entry_point(struct SassCompiler* compiler, struct SassImport* import); + + // Getter for sass compiler results + ADDAPI const char* ADDCALL sass_compiler_get_warn_string(struct SassCompiler* compiler); + ADDAPI const char* ADDCALL sass_compiler_get_output_string(struct SassCompiler* compiler); + ADDAPI const char* ADDCALL sass_compiler_get_footer_string(struct SassCompiler* compiler); + ADDAPI const char* ADDCALL sass_compiler_get_srcmap_string(struct SassCompiler* compiler); + + // Setters for source-map options + ADDAPI void ADDCALL sass_compiler_set_srcmap_path(struct SassCompiler* compiler, const char* path); + ADDAPI void ADDCALL sass_compiler_set_srcmap_root(struct SassCompiler* compiler, const char* root); + ADDAPI void ADDCALL sass_compiler_set_srcmap_mode(struct SassCompiler* compiler, enum SassSrcMapMode mode); + ADDAPI void ADDCALL sass_compiler_set_srcmap_file_urls(struct SassCompiler* compiler, bool enable); + ADDAPI void ADDCALL sass_compiler_set_srcmap_embed_contents(struct SassCompiler* compiler, bool enable); + + ADDAPI size_t ADDCALL sass_compiler_get_included_files_count(struct SassCompiler* compiler); + ADDAPI const char* ADDCALL sass_compiler_get_included_file_path(struct SassCompiler* compiler, size_t n); + + ADDAPI struct SassImport* ADDCALL sass_compiler_get_last_import(struct SassCompiler* compiler); + +#ifdef __cplusplus +} // EO extern "C". +#endif + +#ifdef __cplusplus +namespace Sass { +extern "C" { +#endif + + // Returns pointer to error object associated with compiler. + // Will be valid until the associated compiler is destroyed. + ADDAPI const struct SassError* ADDCALL sass_compiler_get_error(struct SassCompiler* compiler); + ADDAPI const char* ADDCALL sass_compiler_get_stderr(struct SassCompiler* compiler); + + + + + // ADDAPI void ADDCALL sass_compiler_set_input_path(struct SassCompiler* compiler, const char* input_path); + // ADDAPI void ADDCALL sass_compiler_set_source_map_file(struct SassCompiler* compiler, const char* source_map_file); + // ADDAPI void ADDCALL sass_compiler_set_source_map_root(struct SassCompiler* compiler, const char* source_map_root); + + + +#ifdef __cplusplus +} // EO extern "C". +} // EO namespace Sass +#endif + +#endif diff --git a/include/sass/context.h b/include/sass/context.h index 7eb181e259..d3041fa703 100644 --- a/include/sass/context.h +++ b/include/sass/context.h @@ -1,171 +1,22 @@ #ifndef SASS_C_CONTEXT_H #define SASS_C_CONTEXT_H -#include -#include #include -#include -#include #ifdef __cplusplus extern "C" { #endif -// Forward declaration -struct Sass_Compiler; - -// Forward declaration -struct Sass_Options; // base struct -struct Sass_Context; // : Sass_Options -struct Sass_File_Context; // : Sass_Context -struct Sass_Data_Context; // : Sass_Context - -// Compiler states -enum Sass_Compiler_State { - SASS_COMPILER_CREATED, - SASS_COMPILER_PARSED, - SASS_COMPILER_EXECUTED -}; - -// Create and initialize an option struct -ADDAPI struct Sass_Options* ADDCALL sass_make_options (void); -// Create and initialize a specific context -ADDAPI struct Sass_File_Context* ADDCALL sass_make_file_context (const char* input_path); -ADDAPI struct Sass_Data_Context* ADDCALL sass_make_data_context (char* source_string); - -// Call the compilation step for the specific context -ADDAPI int ADDCALL sass_compile_file_context (struct Sass_File_Context* ctx); -ADDAPI int ADDCALL sass_compile_data_context (struct Sass_Data_Context* ctx); - -// Create a sass compiler instance for more control -ADDAPI struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx); -ADDAPI struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx); - -// Execute the different compilation steps individually -// Useful if you only want to query the included files -ADDAPI int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler); -ADDAPI int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler); - -// Release all memory allocated with the compiler -// This does _not_ include any contexts or options -ADDAPI void ADDCALL sass_delete_compiler(struct Sass_Compiler* compiler); -ADDAPI void ADDCALL sass_delete_options(struct Sass_Options* options); - -// Release all memory allocated and also ourself -ADDAPI void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx); -ADDAPI void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx); - -// Getters for context from specific implementation -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 -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); -ADDAPI void ADDCALL sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); -ADDAPI void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); - - -// 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); -ADDAPI bool ADDCALL sass_option_get_source_map_embed (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_source_map_contents (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_source_map_file_urls (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_omit_source_map_url (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_is_indented_syntax_src (struct Sass_Options* options); -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_source_map_file (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_source_map_root (struct Sass_Options* options); -ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_headers (struct Sass_Options* options); -ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_importers (struct Sass_Options* options); -ADDAPI Sass_Function_List ADDCALL sass_option_get_c_functions (struct Sass_Options* 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); -ADDAPI void ADDCALL sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); -ADDAPI void ADDCALL sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); -ADDAPI void ADDCALL sass_option_set_source_map_file_urls (struct Sass_Options* options, bool source_map_file_urls); -ADDAPI void ADDCALL sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); -ADDAPI void ADDCALL sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); -ADDAPI void ADDCALL sass_option_set_indent (struct Sass_Options* options, const char* indent); -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_headers (struct Sass_Options* options, Sass_Importer_List c_headers); -ADDAPI void ADDCALL sass_option_set_c_importers (struct Sass_Options* options, Sass_Importer_List c_importers); -ADDAPI void ADDCALL sass_option_set_c_functions (struct Sass_Options* options, Sass_Function_List c_functions); - - -// 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 const char* ADDCALL sass_context_get_error_src (struct Sass_Context* ctx); -ADDAPI size_t ADDCALL sass_context_get_error_line (struct Sass_Context* ctx); -ADDAPI size_t ADDCALL sass_context_get_error_column (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_source_map_string (struct Sass_Context* ctx); -ADDAPI char** ADDCALL sass_context_get_included_files (struct Sass_Context* ctx); - -// Getters for options include path array -ADDAPI size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i); -// Plugin paths to load dynamic libraries work the same -ADDAPI size_t ADDCALL sass_option_get_plugin_path_size(struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_plugin_path(struct Sass_Options* options, size_t i); - -// Calculate the size of the stored null terminated array -ADDAPI size_t ADDCALL sass_context_get_included_files_size (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_error_src (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); -ADDAPI char** ADDCALL sass_context_take_included_files (struct Sass_Context* ctx); - -// Getters for Sass_Compiler options -ADDAPI enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler); -ADDAPI struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler); -ADDAPI struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler); -ADDAPI size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); -ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler); -ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); -ADDAPI size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler); -ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler); -ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); - -// 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); - // Resolve a file via the given include paths in the sass option struct // find_file looks for the exact file name while find_include does a regular sass include -ADDAPI char* ADDCALL sass_find_file (const char* path, struct Sass_Options* opt); -ADDAPI char* ADDCALL sass_find_include (const char* path, struct Sass_Options* opt); +// ADDAPI char* ADDCALL sass_find_file (const char* path, struct SassOptionsCpp* opt); +// ADDAPI char* ADDCALL sass_find_include (const char* path, struct SassOptionsCpp* opt); // Resolve a file relative to last import or include paths in the sass option struct // find_file looks for the exact file name while find_include does a regular sass include -ADDAPI char* ADDCALL sass_compiler_find_file (const char* path, struct Sass_Compiler* compiler); -ADDAPI char* ADDCALL sass_compiler_find_include (const char* path, struct Sass_Compiler* compiler); +ADDAPI char* ADDCALL sass_compiler_find_file (const char* path, struct SassCompiler* compiler); +ADDAPI char* ADDCALL sass_compiler_find_include (const char* path, struct SassCompiler* compiler); #ifdef __cplusplus } // __cplusplus defined. diff --git a/include/sass/enums.h b/include/sass/enums.h new file mode 100644 index 0000000000..1bcfc58b95 --- /dev/null +++ b/include/sass/enums.h @@ -0,0 +1,60 @@ +#ifndef SASS_C_ENUMS_H +#define SASS_C_ENUMS_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Different render styles +enum SassOutputStyle { + SASS_STYLE_NESTED, + SASS_STYLE_EXPANDED, + SASS_STYLE_COMPACT, + SASS_STYLE_COMPRESSED, + // only used internally, but + // should be flag like inspect + SASS_STYLE_TO_CSS +}; + +// Type of parser to use +enum SassImportFormat { + SASS_IMPORT_AUTO, + SASS_IMPORT_SCSS, + SASS_IMPORT_SASS, + SASS_IMPORT_CSS, +}; + +enum SassSrcMapMode { + SASS_SRCMAP_NONE, + SASS_SRCMAP_CREATE, + SASS_SRCMAP_EMBED_LINK, + SASS_SRCMAP_EMBED_JSON, +}; + +enum SassCompilerState { + SASS_COMPILER_CREATED, + SASS_COMPILER_PARSED, + SASS_COMPILER_COMPILED, + SASS_COMPILER_RENDERED, + SASS_COMPILER_DESTROYED +}; + +#define SASS_LOGGER_MONO 1 +#define SASS_LOGGER_COLOR 2 +#define SASS_LOGGER_ASCII 4 +#define SASS_LOGGER_UNICODE 8 + +// Logging style +enum SassLoggerStyle { + SASS_LOGGER_AUTO = 0, + SASS_LOGGER_ASCII_MONO = SASS_LOGGER_ASCII | SASS_LOGGER_MONO, + SASS_LOGGER_ASCII_COLOR = SASS_LOGGER_ASCII | SASS_LOGGER_COLOR, + SASS_LOGGER_UNICODE_MONO = SASS_LOGGER_UNICODE | SASS_LOGGER_MONO, + SASS_LOGGER_UNICODE_COLOR = SASS_LOGGER_UNICODE | SASS_LOGGER_COLOR, +}; + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/error.h b/include/sass/error.h new file mode 100644 index 0000000000..7ebb04cc94 --- /dev/null +++ b/include/sass/error.h @@ -0,0 +1,40 @@ +#ifndef SASS_C_ERROR_H +#define SASS_C_ERROR_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + // Error related getters (use after compiler was rendered) + ADDAPI int ADDCALL sass_error_get_status(const struct SassError* error); + ADDAPI char* ADDCALL sass_error_get_json(const struct SassError* error); + ADDAPI const char* ADDCALL sass_error_get_what(const struct SassError* error); + // ADDAPI const char* ADDCALL sass_error_get_messages(struct SassError* error); + // ADDAPI const char* ADDCALL sass_error_get_warnings(struct SassError* error); + ADDAPI const char* ADDCALL sass_error_get_formatted(const struct SassError* error); + + // These are here for convenience, could get them also indirectly + ADDAPI size_t ADDCALL sass_error_get_line(const struct SassError* error); + ADDAPI size_t ADDCALL sass_error_get_column(const struct SassError* error); + ADDAPI const char* ADDCALL sass_error_get_path(const struct SassError* error); + ADDAPI const char* ADDCALL sass_error_get_content(const struct SassError* error); + + // ADDAPI size_t ADDCALL sass_traces_get_size(struct SassTraces* traces); + // ADDAPI struct SassTrace* ADDCALL sass_traces_get_last(struct SassTraces* traces); + // ADDAPI struct SassTrace* ADDCALL sass_traces_get_trace(struct SassTraces* traces, size_t i); + + ADDAPI size_t ADDCALL sass_error_count_traces(const struct SassError* error); + ADDAPI const struct SassTrace* ADDCALL sass_error_last_trace(const struct SassError* error); + ADDAPI const struct SassTrace* ADDCALL sass_error_get_trace(const struct SassError* error, size_t i); + + ADDAPI size_t ADDCALL sass_compiler_count_traces(struct SassCompiler* compiler); + ADDAPI const struct SassTrace* ADDCALL sass_compiler_last_trace(struct SassCompiler* compiler); + ADDAPI const struct SassTrace* ADDCALL sass_compiler_get_trace(struct SassCompiler* compiler, size_t i); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/functions.h b/include/sass/functions.h index ac47e8ede1..82e9f89c0e 100644 --- a/include/sass/functions.h +++ b/include/sass/functions.h @@ -1,135 +1,83 @@ #ifndef SASS_C_FUNCTIONS_H #define SASS_C_FUNCTIONS_H -#include -#include #include #ifdef __cplusplus extern "C" { #endif - -// Forward declaration -struct Sass_Env; -struct Sass_Callee; -struct Sass_Import; -struct Sass_Options; -struct Sass_Compiler; -struct Sass_Importer; -struct Sass_Function; - -// Typedef helpers for callee lists -typedef struct Sass_Env (*Sass_Env_Frame); -// Typedef helpers for callee lists -typedef struct Sass_Callee (*Sass_Callee_Entry); -// Typedef helpers for import lists -typedef struct Sass_Import (*Sass_Import_Entry); -typedef struct Sass_Import* (*Sass_Import_List); -// Typedef helpers for custom importer lists -typedef struct Sass_Importer (*Sass_Importer_Entry); -typedef struct Sass_Importer* (*Sass_Importer_List); -// Typedef defining importer signature and return type -typedef Sass_Import_List (*Sass_Importer_Fn) - (const char* url, Sass_Importer_Entry cb, struct Sass_Compiler* compiler); - -// Typedef helpers for custom functions lists -typedef struct Sass_Function (*Sass_Function_Entry); -typedef struct Sass_Function* (*Sass_Function_List); -// Typedef defining function signature and return type -typedef union Sass_Value* (*Sass_Function_Fn) - (const union Sass_Value*, Sass_Function_Entry cb, struct Sass_Compiler* compiler); - -// Type of function calls -enum Sass_Callee_Type { - SASS_CALLEE_MIXIN, - SASS_CALLEE_FUNCTION, - SASS_CALLEE_C_FUNCTION, -}; - // Creator for sass custom importer return argument list -ADDAPI Sass_Importer_List ADDCALL sass_make_importer_list (size_t length); -ADDAPI Sass_Importer_Entry ADDCALL sass_importer_get_list_entry (Sass_Importer_List list, size_t idx); -ADDAPI void ADDCALL sass_importer_set_list_entry (Sass_Importer_List list, size_t idx, Sass_Importer_Entry entry); -ADDAPI void ADDCALL sass_delete_importer_list (Sass_Importer_List list); +// ADDAPI struct SassImportList* ADDCALL sass_make_import_list(); +// ADDAPI void ADDCALL sass_delete_import_list(struct SassImportList* list); +// ADDAPI size_t ADDCALL sass_import_list_size(struct SassImportList* list); +// ADDAPI struct SassImport* ADDCALL sass_import_list_shift(struct SassImportList* list); +// ADDAPI void ADDCALL sass_import_list_push(struct SassImportList* list, struct SassImport*); +// Creator for sass custom importer return argument list +// ADDAPI struct SassImporterList* ADDCALL sass_make_importer_list (); +// ADDAPI void ADDCALL sass_delete_importer_list(struct SassImporterList* list); +// ADDAPI size_t ADDCALL sass_importer_list_size(struct SassImporterList* list); +// ADDAPI struct SassImporter* ADDCALL sass_importer_list_shift(struct SassImporterList* list); +// ADDAPI void ADDCALL sass_importer_list_push(struct SassImporterList* list, struct SassImporter*); // Creators for custom importer callback (with some additional pointer) // The pointer is mostly used to store the callback into the actual binding -ADDAPI Sass_Importer_Entry ADDCALL sass_make_importer (Sass_Importer_Fn importer, double priority, void* cookie); + ADDAPI struct SassImporter* ADDCALL sass_make_importer(SassImporterLambda importer, double priority, void* cookie); -// Getters for import function descriptors -ADDAPI Sass_Importer_Fn ADDCALL sass_importer_get_function (Sass_Importer_Entry cb); -ADDAPI double ADDCALL sass_importer_get_priority (Sass_Importer_Entry cb); -ADDAPI void* ADDCALL sass_importer_get_cookie (Sass_Importer_Entry cb); + // Getters for import function descriptors + ADDAPI SassImporterLambda ADDCALL sass_importer_get_callback(struct SassImporter* cb); + ADDAPI double ADDCALL sass_importer_get_priority(struct SassImporter* cb); + ADDAPI void* ADDCALL sass_importer_get_cookie(struct SassImporter* cb); -// Deallocator for associated memory -ADDAPI void ADDCALL sass_delete_importer (Sass_Importer_Entry cb); + // Deallocator for associated memory + ADDAPI void ADDCALL sass_delete_importer(struct SassImporter* cb); -// Creator for sass custom importer return argument list -ADDAPI Sass_Import_List ADDCALL sass_make_import_list (size_t length); -// Creator for a single import entry returned by the custom importer inside the list -ADDAPI Sass_Import_Entry ADDCALL sass_make_import_entry (const char* path, char* source, char* srcmap); -ADDAPI Sass_Import_Entry ADDCALL sass_make_import (const char* imp_path, const char* abs_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 Sass_Import_Entry ADDCALL sass_import_set_error(Sass_Import_Entry 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 -ADDAPI void ADDCALL sass_import_set_list_entry (Sass_Import_List list, size_t idx, Sass_Import_Entry entry); -ADDAPI Sass_Import_Entry ADDCALL sass_import_get_list_entry (Sass_Import_List list, size_t idx); - -// Getters for callee entry -ADDAPI const char* ADDCALL sass_callee_get_name (Sass_Callee_Entry); -ADDAPI const char* ADDCALL sass_callee_get_path (Sass_Callee_Entry); -ADDAPI size_t ADDCALL sass_callee_get_line (Sass_Callee_Entry); -ADDAPI size_t ADDCALL sass_callee_get_column (Sass_Callee_Entry); -ADDAPI enum Sass_Callee_Type ADDCALL sass_callee_get_type (Sass_Callee_Entry); -ADDAPI Sass_Env_Frame ADDCALL sass_callee_get_env (Sass_Callee_Entry); + // Creator for a single import entry returned by the custom importer inside the list + ADDAPI struct SassImport* ADDCALL sass_make_import(const char* imp_path, const char* abs_base, char* source, char* srcmap, enum SassImportFormat format); + // ADDAPI struct SassImport* ADDCALL sass_make_import_error(const char* error); + + // set error message to abort import and to print out a message (path from existing object is used in output) + ADDAPI void ADDCALL sass_import_set_error_msg(struct SassImport* import, const char* message, uint32_t line, uint32_t col); + + // Getters for callee entry +// ADDAPI const char* ADDCALL sass_callee_get_name (struct SassCallee*); +// ADDAPI const char* ADDCALL sass_callee_get_path (struct SassCallee*); +// ADDAPI uint32_t ADDCALL sass_callee_get_line (struct SassCallee*); +// ADDAPI uint32_t ADDCALL sass_callee_get_column (struct SassCallee*); +// ADDAPI enum Sass_Callee_Type ADDCALL sass_callee_get_type (struct SassCallee*); // Getters and Setters for environments (lexical, local and global) -ADDAPI union Sass_Value* ADDCALL sass_env_get_lexical (Sass_Env_Frame, const char*); -ADDAPI void ADDCALL sass_env_set_lexical (Sass_Env_Frame, const char*, union Sass_Value*); -ADDAPI union Sass_Value* ADDCALL sass_env_get_local (Sass_Env_Frame, const char*); -ADDAPI void ADDCALL sass_env_set_local (Sass_Env_Frame, const char*, union Sass_Value*); -ADDAPI union Sass_Value* ADDCALL sass_env_get_global (Sass_Env_Frame, const char*); -ADDAPI void ADDCALL sass_env_set_global (Sass_Env_Frame, const char*, union Sass_Value*); +// ADDAPI struct SassValue* ADDCALL sass_env_get_lexical (struct SassCompiler*, const char*); +ADDAPI void ADDCALL sass_env_set_lexical (struct SassCompiler*, const char*, struct SassValue*); +// ADDAPI struct SassValue* ADDCALL sass_env_get_local (struct SassCompiler*, const char*); +// ADDAPI void ADDCALL sass_env_set_local (struct SassCompiler*, const char*, struct SassValue*); +// ADDAPI struct SassValue* ADDCALL sass_env_get_global (struct SassCompiler*, const char*); +ADDAPI void ADDCALL sass_env_set_global (struct SassCompiler*, const char*, struct SassValue*); // Getters for import entry -ADDAPI const char* ADDCALL sass_import_get_imp_path (Sass_Import_Entry); -ADDAPI const char* ADDCALL sass_import_get_abs_path (Sass_Import_Entry); -ADDAPI const char* ADDCALL sass_import_get_source (Sass_Import_Entry); -ADDAPI const char* ADDCALL sass_import_get_srcmap (Sass_Import_Entry); -// Explicit functions to take ownership of these items -// The property on our struct will be reset to NULL -ADDAPI char* ADDCALL sass_import_take_source (Sass_Import_Entry); -ADDAPI char* ADDCALL sass_import_take_srcmap (Sass_Import_Entry); +ADDAPI const char* ADDCALL sass_import_get_imp_path (struct SassImport*); +ADDAPI const char* ADDCALL sass_import_get_abs_path (struct SassImport*); +// ADDAPI const char* ADDCALL sass_import_get_source (struct SassImport*); +// ADDAPI const char* ADDCALL sass_import_get_srcmap (struct SassImport*); +ADDAPI enum SassImportFormat ADDCALL sass_import_get_type(struct SassImport*); + // Getters from import error entry -ADDAPI size_t ADDCALL sass_import_get_error_line (Sass_Import_Entry); -ADDAPI size_t ADDCALL sass_import_get_error_column (Sass_Import_Entry); -ADDAPI const char* ADDCALL sass_import_get_error_message (Sass_Import_Entry); +// ADDAPI uint32_t ADDCALL sass_import_get_error_line (struct SassImport*); +// ADDAPI uint32_t ADDCALL sass_import_get_error_column (struct SassImport*); +ADDAPI const char* ADDCALL sass_import_get_error_message (struct SassImport*); -// Deallocator for associated memory (incl. entries) -ADDAPI void ADDCALL sass_delete_import_list (Sass_Import_List); // Just in case we have some stray import structs -ADDAPI void ADDCALL sass_delete_import (Sass_Import_Entry); - - +ADDAPI void ADDCALL sass_delete_import(struct SassImport*); -// Creators for sass function list and function descriptors -ADDAPI Sass_Function_List ADDCALL sass_make_function_list (size_t length); -ADDAPI Sass_Function_Entry ADDCALL sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie); -ADDAPI void ADDCALL sass_delete_function (Sass_Function_Entry entry); -ADDAPI void ADDCALL sass_delete_function_list (Sass_Function_List list); -// Setters and getters for callbacks on function lists -ADDAPI Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos); -ADDAPI void ADDCALL sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb); +ADDAPI struct SassFunction* ADDCALL sass_make_function (const char* signature, SassFunctionLambda cb, void* cookie); +ADDAPI void ADDCALL sass_delete_function (struct SassFunction* entry); // Getters for custom function descriptors -ADDAPI const char* ADDCALL sass_function_get_signature (Sass_Function_Entry cb); -ADDAPI Sass_Function_Fn ADDCALL sass_function_get_function (Sass_Function_Entry cb); -ADDAPI void* ADDCALL sass_function_get_cookie (Sass_Function_Entry cb); +ADDAPI const char* ADDCALL sass_function_get_signature (struct SassFunction* cb); +ADDAPI SassFunctionLambda ADDCALL sass_function_get_function (struct SassFunction* cb); +ADDAPI void* ADDCALL sass_function_get_cookie (struct SassFunction* cb); #ifdef __cplusplus diff --git a/include/sass/fwdecl.h b/include/sass/fwdecl.h new file mode 100644 index 0000000000..b3d7ac9eaf --- /dev/null +++ b/include/sass/fwdecl.h @@ -0,0 +1,32 @@ +#ifndef SASS_C_FWDECL_H +#define SASS_C_FWDECL_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Forward declare structs +struct SassTrace; +struct SassError; +struct SassCompiler; +struct SassFunction; + +struct SassSource; +struct SassSrcSpan; + +struct SassImport; +struct SassImporter; + +struct SassImportList; +struct SassImporterList; +struct SassFunctionList; + +// Typedef defining importer/function callback signature and return type +typedef struct SassImportList* (*SassImporterLambda)(const char* url, struct SassImporter* cb, struct SassCompiler* compiler); +typedef struct SassValue* (*SassFunctionLambda)(struct SassValue*, struct SassFunction* cb, struct SassCompiler* compiler); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/import.h b/include/sass/import.h new file mode 100644 index 0000000000..882ee450f9 --- /dev/null +++ b/include/sass/import.h @@ -0,0 +1,37 @@ +#ifndef SASS_C_IMPORT_H +#define SASS_C_IMPORT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + // Create an import entry by reading from `stdin` + ADDAPI struct SassImport* ADDCALL sass_make_stdin_import(const char* imp_path); + + // Crate import entry for input path (returns nullptr if not found or unreadable) + // Will read the file without LibSass internal path resolving (relative to CWD)! + // ADDAPI struct SassImport* ADDCALL sass_make_file_import(struct SassCompiler* compiler, const char* imp_path); + + // Crate an import entry for the passed data content and optional import path. + // The import path is optional and defaults to `sass://data` if `null` is passed. + // ADDAPI struct SassImport* ADDCALL sass_make_data_import(char* content, const char* imp_path); + + // Note: compiler ones do error reporting, since file loading my error ... + // One solution would be to postpone the file loading into the parse stage + + // Crate an import entry for the passed input path + ADDAPI struct SassImport* ADDCALL sass_make_file_import(const char* imp_path); + + // Crate an import entry for the passed input path + ADDAPI struct SassImport* ADDCALL sass_make_content_import(char* content, const char* imp_path); + + // Set specific import format for the given import + ADDAPI void ADDCALL sass_import_set_format(struct SassImport* import, enum SassImportFormat format); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/lists.h b/include/sass/lists.h new file mode 100644 index 0000000000..c23ea254e8 --- /dev/null +++ b/include/sass/lists.h @@ -0,0 +1,39 @@ +#ifndef SASS_C_LISTS_H +#define SASS_C_LISTS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + struct SassImportList; + struct SassImporterList; + + // Creator for sass custom importer return argument list + ADDAPI struct SassImportList* ADDCALL sass_make_import_list(); + ADDAPI void ADDCALL sass_delete_import_list(struct SassImportList* list); + ADDAPI size_t ADDCALL sass_import_list_size(struct SassImportList* list); + + ADDAPI struct SassImport* ADDCALL sass_import_list_shift(struct SassImportList* list); + ADDAPI void ADDCALL sass_import_list_push(struct SassImportList* list, struct SassImport*); + + // Creator for sass plugin loading return arguments + ADDAPI struct SassImporterList* ADDCALL sass_make_importer_list(); + ADDAPI void ADDCALL sass_delete_importer_list(struct SassImporterList* list); + ADDAPI size_t ADDCALL sass_importer_list_size(struct SassImporterList* list); + ADDAPI struct SassImporter* ADDCALL sass_importer_list_shift(struct SassImporterList* list); + ADDAPI void ADDCALL sass_importer_list_push(struct SassImporterList* list, struct SassImporter*); + + // Creators for sass function list and function descriptors + ADDAPI struct SassFunctionList* ADDCALL sass_make_function_list(); + ADDAPI void ADDCALL sass_delete_function_list(struct SassFunctionList* list); + ADDAPI size_t ADDCALL sass_function_list_size(struct SassFunctionList* list); + ADDAPI struct SassFunction* ADDCALL sass_function_list_shift(struct SassFunctionList* list); + ADDAPI void ADDCALL sass_function_list_push(struct SassFunctionList* list, struct SassFunction*); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/traces.h b/include/sass/traces.h new file mode 100644 index 0000000000..0ca8dd917a --- /dev/null +++ b/include/sass/traces.h @@ -0,0 +1,50 @@ +#ifndef SASS_C_TRACES_H +#define SASS_C_TRACES_H + +#include + +struct SassTrace; +struct SassTraces; +struct SassSrcSpan; +struct SassSource; + +#ifdef __cplusplus +extern "C" { +#endif + + // Traces must be got directly from the underlying object. + // We expose traces during eval (BackTraces) and on when handling + // error (StackTraces). We can't convert a vector to + // vector, because they are not covariant. Easiest to add + // two implementation to fetch a trace and make the cast there. + + // ADDAPI size_t ADDCALL sass_traces_get_size(struct SassTraces* traces); + // ADDAPI struct SassTrace* ADDCALL sass_traces_get_last(struct SassTraces* traces); + // ADDAPI struct SassTrace* ADDCALL sass_traces_get_trace(struct SassTraces* traces, size_t i); + + // ADDAPI size_t ADDCALL sass_traces_get_size(struct SassTraces* traces); + + ADDAPI const char* ADDCALL sass_trace_get_name(struct SassTrace* trace); + ADDAPI bool ADDCALL sass_trace_get_was_fncall(struct SassTrace* trace); + ADDAPI const struct SassSrcSpan* ADDCALL sass_trace_get_srcspan(struct SassTrace* trace); + + ADDAPI size_t ADDCALL sass_srcspan_get_src_ln(struct SassSrcSpan* pstate); + ADDAPI size_t ADDCALL sass_srcspan_get_src_col(struct SassSrcSpan* pstate); + ADDAPI size_t ADDCALL sass_srcspan_get_span_ln(struct SassSrcSpan* pstate); + ADDAPI size_t ADDCALL sass_srcspan_get_span_col(struct SassSrcSpan* pstate); + ADDAPI size_t ADDCALL sass_srcspan_get_src_line(struct SassSrcSpan* pstate); + ADDAPI size_t ADDCALL sass_srcspan_get_src_column(struct SassSrcSpan* pstate); + + ADDAPI struct SassSource* ADDCALL sass_srcspan_get_source(struct SassSrcSpan* pstate); + + ADDAPI const char* ADDCALL sass_source_get_abs_path(struct SassSource* source); + ADDAPI const char* ADDCALL sass_source_get_imp_path(struct SassSource* source); + ADDAPI const char* ADDCALL sass_source_get_content(struct SassSource* source); + ADDAPI const char* ADDCALL sass_source_get_srcmap(struct SassSource* source); + + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/values.h b/include/sass/values.h index 9832038b71..ee6ec1c913 100644 --- a/include/sass/values.h +++ b/include/sass/values.h @@ -1,20 +1,30 @@ #ifndef SASS_C_VALUES_H #define SASS_C_VALUES_H -#include -#include #include +// Implementation Notes: While I was refactoring for LibSass 4.0, I figured we should get +// rid of all intermediate structs that we created when converting back and forth from C +// to CPP. I researched several approaches and will document my findings here. C only knows +// about structs, therefore we can export any struct-ptr from C++ directly to C. This would +// have been the most desirable approach, since any class in C++ can be turned into a struct +// but there are limitations. Mainly we cannot export a name-spaced struct, so we would need to +// define all our "classes" on the root namespace. The main benefit would be that we could +// inspect objects during debugging if linking statically. Another approach would be to wrap +// a ValueObj inside a struct. My final conclusion was to simply create an "anonymous" struct +// on the C-API side, which has no implementation at all. In the actual implementation we +// just trust the pointer to be of the type it should be, or you get undefined behavior. +// Since underlying the pointer is a SharedObj, we know how to handle the reference count +// for memory management when destruction is requested from C-API. Whenever the created value +// is a e.g. added to container, the actual destruction of the original is skipped. + #ifdef __cplusplus extern "C" { #endif -// Forward declaration -union Sass_Value; - // Type for Sass values -enum Sass_Tag { +enum SassValueType { SASS_BOOLEAN, SASS_NUMBER, SASS_COLOR, @@ -23,120 +33,154 @@ enum Sass_Tag { SASS_MAP, SASS_NULL, SASS_ERROR, - SASS_WARNING + SASS_WARNING, + SASS_FUNCTION, + SASS_PARENT, }; -// Tags for denoting Sass list separators -enum Sass_Separator { +// List Separators +enum SassSeparator { SASS_COMMA, SASS_SPACE, - // only used internally to represent a hash map before evaluation - // otherwise we would be too early to check for duplicate keys - SASS_HASH + // A separator that hasn't yet been determined. + // Singleton lists and empty lists don't have separators defined. This means + // that list functions will prefer other lists' separators if possible. + SASS_UNDEF, }; // Value Operators -enum Sass_OP { - AND, OR, // logical connectives +enum SassOperator { + OR, AND, // logical connectives EQ, NEQ, GT, GTE, LT, LTE, // arithmetic relations ADD, SUB, MUL, DIV, MOD, // arithmetic functions - NUM_OPS // so we know how big to make the op table + IESEQ // special IE single equal }; // Creator functions for all value types -ADDAPI union Sass_Value* ADDCALL sass_make_null (void); -ADDAPI union Sass_Value* ADDCALL sass_make_boolean (bool val); -ADDAPI union Sass_Value* ADDCALL sass_make_string (const char* val); -ADDAPI union Sass_Value* ADDCALL sass_make_qstring (const char* val); -ADDAPI union Sass_Value* ADDCALL sass_make_number (double val, const char* unit); -ADDAPI union Sass_Value* ADDCALL sass_make_color (double r, double g, double b, double a); -ADDAPI union Sass_Value* ADDCALL sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); -ADDAPI union Sass_Value* ADDCALL sass_make_map (size_t len); -ADDAPI union Sass_Value* ADDCALL sass_make_error (const char* msg); -ADDAPI union Sass_Value* ADDCALL sass_make_warning (const char* msg); +ADDAPI struct SassValue* ADDCALL sass_make_null (void); +ADDAPI struct SassValue* ADDCALL sass_make_boolean (bool val); +ADDAPI struct SassValue* ADDCALL sass_make_string (const char* val, bool is_quoted); +ADDAPI struct SassValue* ADDCALL sass_make_number (double val, const char* unit); + +ADDAPI struct SassValue* ADDCALL sass_make_number2(double val, const char* unit); + + +ADDAPI struct SassValue* ADDCALL sass_make_color (double r, double g, double b, double a); +ADDAPI struct SassValue* ADDCALL sass_make_list (enum SassSeparator sep, bool is_bracketed); +ADDAPI struct SassValue* ADDCALL sass_make_map (); +ADDAPI struct SassValue* ADDCALL sass_make_error (const char* msg); +ADDAPI struct SassValue* ADDCALL sass_make_warning (const char* msg); // Generic destructor function for all types // Will release memory of all associated Sass_Values // Means we will delete recursively for lists and maps -ADDAPI void ADDCALL sass_delete_value (union Sass_Value* val); +ADDAPI void ADDCALL sass_delete_value(struct SassValue* val); // Make a deep cloned copy of the given sass value -ADDAPI union Sass_Value* ADDCALL sass_clone_value (const union Sass_Value* val); +ADDAPI struct SassValue* ADDCALL sass_clone_value (struct SassValue* val); // Execute an operation for two Sass_Values and return the result as a Sass_Value too -ADDAPI union Sass_Value* ADDCALL sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b); +ADDAPI struct SassValue* ADDCALL sass_value_op (enum SassOperator op, struct SassValue* a, struct SassValue* b); // Stringify a Sass_Values and also return the result as a Sass_Value (of type STRING) -ADDAPI union Sass_Value* ADDCALL sass_value_stringify (const union Sass_Value* a, bool compressed, int precision); +ADDAPI struct SassValue* ADDCALL sass_value_stringify (struct SassValue* a, bool compressed, int precision); // Return the sass tag for a generic sass value // Check is needed before accessing specific values! -ADDAPI enum Sass_Tag ADDCALL sass_value_get_tag (const union Sass_Value* v); +ADDAPI enum SassValueType ADDCALL sass_value_get_tag (struct SassValue* v); // Check value to be of a specific type // Can also be used before accessing properties! -ADDAPI bool ADDCALL sass_value_is_null (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_number (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_string (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_boolean (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_color (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_list (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_map (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_error (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_warning (const union Sass_Value* v); +ADDAPI bool ADDCALL sass_value_is_null (struct SassValue* v); +ADDAPI bool ADDCALL sass_value_is_number (struct SassValue* v); +ADDAPI bool ADDCALL sass_value_is_string (struct SassValue* v); +ADDAPI bool ADDCALL sass_value_is_boolean (struct SassValue* v); +ADDAPI bool ADDCALL sass_value_is_color (struct SassValue* v); +ADDAPI bool ADDCALL sass_value_is_list (struct SassValue* v); +ADDAPI bool ADDCALL sass_value_is_map (struct SassValue* v); +ADDAPI bool ADDCALL sass_value_is_error (struct SassValue* v); +ADDAPI bool ADDCALL sass_value_is_warning (struct SassValue* v); // Getters and setters for Sass_Number -ADDAPI double ADDCALL sass_number_get_value (const union Sass_Value* v); -ADDAPI void ADDCALL sass_number_set_value (union Sass_Value* v, double value); -ADDAPI const char* ADDCALL sass_number_get_unit (const union Sass_Value* v); -ADDAPI void ADDCALL sass_number_set_unit (union Sass_Value* v, char* unit); +ADDAPI double ADDCALL sass_number_get_value (struct SassValue* v); +ADDAPI void ADDCALL sass_number_set_value (struct SassValue* v, double value); +ADDAPI const char* ADDCALL sass_number_get_unit (struct SassValue* v); +ADDAPI void ADDCALL sass_number_set_unit (struct SassValue* v, const char* unit); +ADDAPI void ADDCALL sass_number_normalize(struct SassValue* v); // What does it do? +ADDAPI void ADDCALL sass_number_reduce(struct SassValue* v); // Getters and setters for Sass_String -ADDAPI const char* ADDCALL sass_string_get_value (const union Sass_Value* v); -ADDAPI void ADDCALL sass_string_set_value (union Sass_Value* v, char* value); -ADDAPI bool ADDCALL sass_string_is_quoted(const union Sass_Value* v); -ADDAPI void ADDCALL sass_string_set_quoted(union Sass_Value* v, bool quoted); +ADDAPI const char* ADDCALL sass_string_get_value (struct SassValue* v); +ADDAPI void ADDCALL sass_string_set_value (struct SassValue* v, char* value); +ADDAPI bool ADDCALL sass_string_is_quoted(struct SassValue* v); +ADDAPI void ADDCALL sass_string_set_quoted(struct SassValue* v, bool quoted); // Getters and setters for Sass_Boolean -ADDAPI bool ADDCALL sass_boolean_get_value (const union Sass_Value* v); -ADDAPI void ADDCALL sass_boolean_set_value (union Sass_Value* v, bool value); +ADDAPI bool ADDCALL sass_boolean_get_value (struct SassValue* v); +ADDAPI void ADDCALL sass_boolean_set_value (struct SassValue* v, bool value); // Getters and setters for Sass_Color -ADDAPI double ADDCALL sass_color_get_r (const union Sass_Value* v); -ADDAPI void ADDCALL sass_color_set_r (union Sass_Value* v, double r); -ADDAPI double ADDCALL sass_color_get_g (const union Sass_Value* v); -ADDAPI void ADDCALL sass_color_set_g (union Sass_Value* v, double g); -ADDAPI double ADDCALL sass_color_get_b (const union Sass_Value* v); -ADDAPI void ADDCALL sass_color_set_b (union Sass_Value* v, double b); -ADDAPI double ADDCALL sass_color_get_a (const union Sass_Value* v); -ADDAPI void ADDCALL sass_color_set_a (union Sass_Value* v, double a); - -// Getter for the number of items in list -ADDAPI size_t ADDCALL sass_list_get_length (const union Sass_Value* v); +ADDAPI double ADDCALL sass_color_get_r (struct SassValue* v); +ADDAPI void ADDCALL sass_color_set_r (struct SassValue* v, double r); +ADDAPI double ADDCALL sass_color_get_g (struct SassValue* v); +ADDAPI void ADDCALL sass_color_set_g (struct SassValue* v, double g); +ADDAPI double ADDCALL sass_color_get_b (struct SassValue* v); +ADDAPI void ADDCALL sass_color_set_b (struct SassValue* v, double b); +ADDAPI double ADDCALL sass_color_get_a (struct SassValue* v); +ADDAPI void ADDCALL sass_color_set_a (struct SassValue* v, double a); + +ADDAPI size_t ADDCALL sass_list_get_size(struct SassValue* list); +ADDAPI void ADDCALL sass_list_push(struct SassValue* list, struct SassValue* value); +ADDAPI struct SassValue* ADDCALL sass_list_at(struct SassValue* list, size_t i); +ADDAPI struct SassValue* ADDCALL sass_list_pop(struct SassValue* list, struct SassValue* value); +ADDAPI struct SassValue* ADDCALL sass_list_shift(struct SassValue* list, struct SassValue* value); + + + // Getters and setters for Sass_List -ADDAPI enum Sass_Separator ADDCALL sass_list_get_separator (const union Sass_Value* v); -ADDAPI void ADDCALL sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value); -ADDAPI bool ADDCALL sass_list_get_is_bracketed (const union Sass_Value* v); -ADDAPI void ADDCALL sass_list_set_is_bracketed (union Sass_Value* v, bool value); +ADDAPI enum SassSeparator ADDCALL sass_list_get_separator (struct SassValue* v); +ADDAPI void ADDCALL sass_list_set_separator (struct SassValue* v, enum SassSeparator value); +ADDAPI bool ADDCALL sass_list_get_is_bracketed (struct SassValue* v); +ADDAPI void ADDCALL sass_list_set_is_bracketed (struct SassValue* v, bool value); // Getters and setters for Sass_List values -ADDAPI union Sass_Value* ADDCALL sass_list_get_value (const union Sass_Value* v, size_t i); -ADDAPI void ADDCALL sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); +ADDAPI struct SassValue* ADDCALL sass_list_get_value (struct SassValue* v, size_t i); +ADDAPI void ADDCALL sass_list_set_value (struct SassValue* v, size_t i, struct SassValue* value); // Getter for the number of items in map -ADDAPI size_t ADDCALL sass_map_get_length (const union Sass_Value* v); +// ADDAPI size_t ADDCALL sass_map_get_size (struct SassValue* v); + +ADDAPI void ADDCALL sass_map_set(struct SassValue* m, struct SassValue* k, struct SassValue* v); + +struct SassMapIterator; + +ADDAPI struct SassMapIterator* ADDCALL sass_map_make_iterator(struct SassValue* map); +ADDAPI void ADDCALL sass_map_delete_iterator(struct SassMapIterator* it); +ADDAPI bool ADDCALL sass_map_iterator_exhausted(struct SassMapIterator* it); +ADDAPI struct SassValue* ADDCALL sass_map_iterator_get_key(struct SassMapIterator* it); +ADDAPI struct SassValue* ADDCALL sass_map_iterator_get_value(struct SassMapIterator* it); +ADDAPI void ADDCALL sass_map_iterator_next(struct SassMapIterator* it); + +// sass_map_get_iterator(); +// sass_map_iterator_next(it); + + +ADDAPI void ADDCALL sass_map_set(struct SassValue* m, struct SassValue* k, struct SassValue* v); +ADDAPI struct SassValue* ADDCALL sass_map_get(struct SassValue* m, struct SassValue* k); + + // Getters and setters for Sass_Map keys and values -ADDAPI union Sass_Value* ADDCALL sass_map_get_key (const union Sass_Value* v, size_t i); -ADDAPI void ADDCALL sass_map_set_key (union Sass_Value* v, size_t i, union Sass_Value*); -ADDAPI union Sass_Value* ADDCALL sass_map_get_value (const union Sass_Value* v, size_t i); -ADDAPI void ADDCALL sass_map_set_value (union Sass_Value* v, size_t i, union Sass_Value*); +//ADDAPI struct SassValue* ADDCALL sass_map_get_key (struct SassValue* v, size_t i); +//ADDAPI void ADDCALL sass_map_set_key (struct SassValue* v, size_t i, struct SassValue*); +//ADDAPI struct SassValue* ADDCALL sass_map_get_value (struct SassValue* v, size_t i); +//ADDAPI void ADDCALL sass_map_set_value (struct SassValue* v, size_t i, struct SassValue*); // Getters and setters for Sass_Error -ADDAPI char* ADDCALL sass_error_get_message (const union Sass_Value* v); -ADDAPI void ADDCALL sass_error_set_message (union Sass_Value* v, char* msg); +ADDAPI const char* ADDCALL sass_error_get_message (struct SassValue* v); +ADDAPI void ADDCALL sass_error_set_message (struct SassValue* v, const char* msg); // Getters and setters for Sass_Warning -ADDAPI char* ADDCALL sass_warning_get_message (const union Sass_Value* v); -ADDAPI void ADDCALL sass_warning_set_message (union Sass_Value* v, char* msg); +ADDAPI const char* ADDCALL sass_warning_get_message (struct SassValue* v); +ADDAPI void ADDCALL sass_warning_set_message (struct SassValue* v, const char* msg); #ifdef __cplusplus } // __cplusplus defined. diff --git a/include/sass/version.h b/include/sass/version.h index 56ea016a25..e3cd91ca68 100644 --- a/include/sass/version.h +++ b/include/sass/version.h @@ -1,12 +1,12 @@ -#ifndef SASS_VERSION_H -#define SASS_VERSION_H +#ifndef SASS_C_VERSION_H +#define SASS_C_VERSION_H #ifndef LIBSASS_VERSION #define LIBSASS_VERSION "[NA]" #endif #ifndef LIBSASS_LANGUAGE_VERSION -#define LIBSASS_LANGUAGE_VERSION "3.5" +#define LIBSASS_LANGUAGE_VERSION "3.9" #endif #endif diff --git a/include/sass/version.h.in b/include/sass/version.h.in index b8d4072d4d..8d3f35b04d 100644 --- a/include/sass/version.h.in +++ b/include/sass/version.h.in @@ -1,12 +1,12 @@ -#ifndef SASS_VERSION_H -#define SASS_VERSION_H +#ifndef SASS_C_VERSION_H +#define SASS_C_VERSION_H #ifndef LIBSASS_VERSION #define LIBSASS_VERSION "@PACKAGE_VERSION@" #endif #ifndef LIBSASS_LANGUAGE_VERSION -#define LIBSASS_LANGUAGE_VERSION "3.5" +#define LIBSASS_LANGUAGE_VERSION "3.7" #endif #endif diff --git a/include/sass2scss.h b/include/sass2scss.h deleted file mode 100644 index 8736b2cb9d..0000000000 --- a/include/sass2scss.h +++ /dev/null @@ -1,120 +0,0 @@ -/** - * sass2scss - * Licensed under the MIT License - * Copyright (c) Marcel Greter - */ - -#ifndef SASS2SCSS_H -#define SASS2SCSS_H - -#ifdef _WIN32 - - /* You should define ADD_EXPORTS *only* when building the DLL. */ - #ifdef ADD_EXPORTS - #define ADDAPI __declspec(dllexport) - #define ADDCALL __cdecl - #else - #define ADDAPI - #define ADDCALL - #endif - -#else /* _WIN32 not defined. */ - - /* Define with no value on non-Windows OSes. */ - #define ADDAPI - #define ADDCALL - -#endif - -#ifdef __cplusplus - -#include -#include -#include -#include -#include - -#ifndef SASS2SCSS_VERSION -// Hardcode once the file is copied from -// https://github.com/mgreter/sass2scss -#define SASS2SCSS_VERSION "1.1.1" -#endif - -// add namespace for c++ -namespace Sass -{ - - // pretty print options - const int SASS2SCSS_PRETTIFY_0 = 0; - const int SASS2SCSS_PRETTIFY_1 = 1; - const int SASS2SCSS_PRETTIFY_2 = 2; - const int SASS2SCSS_PRETTIFY_3 = 3; - - // remove one-line comment - const int SASS2SCSS_KEEP_COMMENT = 32; - // remove multi-line comments - const int SASS2SCSS_STRIP_COMMENT = 64; - // convert one-line to multi-line - const int SASS2SCSS_CONVERT_COMMENT = 128; - - // String for finding something interesting - const std::string SASS2SCSS_FIND_WHITESPACE = " \t\n\v\f\r"; - - // converter struct - // holding all states - struct converter - { - // bit options - int options; - // is selector - bool selector; - // concat lists - bool comma; - // has property - bool property; - // has semicolon - bool semicolon; - // comment context - std::string comment; - // flag end of file - bool end_of_file; - // whitespace buffer - std::string whitespace; - // context/block stack - std::stack indents; - }; - - // function only available in c++ code - char* sass2scss (const std::string& sass, const int options); - -} -// EO namespace - -// declare for c -extern "C" { -#endif - - // prettyfy print options - #define SASS2SCSS_PRETTIFY_0 0 - #define SASS2SCSS_PRETTIFY_1 1 - #define SASS2SCSS_PRETTIFY_2 2 - #define SASS2SCSS_PRETTIFY_3 3 - - // keep one-line comments - #define SASS2SCSS_KEEP_COMMENT 32 - // remove multi-line comments - #define SASS2SCSS_STRIP_COMMENT 64 - // convert one-line to multi-line - #define SASS2SCSS_CONVERT_COMMENT 128 - - // available to c and c++ code - ADDAPI char* ADDCALL sass2scss (const char* sass, const int options); - - // Get compiled sass2scss version - ADDAPI const char* ADDCALL sass2scss_version(void); - -#ifdef __cplusplus -} // __cplusplus defined. -#endif - -#endif \ No newline at end of file diff --git a/script/bootstrap b/script/bootstrap index b0df8b2367..cb814484a3 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -7,11 +7,11 @@ script/branding : ${SASS_SASSC_PATH:="sassc" } if [ ! -d $LIBSASS_SPEC_PATH ]; then - git clone https://github.com/mgreter/libsass-spec.git $LIBSASS_SPEC_PATH + git clone https://github.com/mgreter/libsass-spec.git --branch refactor/libsass-4-alpha $LIBSASS_SPEC_PATH fi if [ ! -d $SASS_SPEC_PATH ]; then - git clone https://github.com/sass/sass-spec.git $SASS_SPEC_PATH + git clone https://github.com/mgreter/sass-spec.git --branch refactor/libsass-4-alpha $SASS_SPEC_PATH fi if [ ! -d $SASS_SASSC_PATH ]; then - git clone https://github.com/sass/sassc.git $SASS_SASSC_PATH + git clone https://github.com/mgreter/sassc.git --branch refactor/libsass-4-alpha $SASS_SASSC_PATH fi diff --git a/script/ci-build-libsass b/script/ci-build-libsass index d4ade4eb73..c26191388f 100755 --- a/script/ci-build-libsass +++ b/script/ci-build-libsass @@ -58,9 +58,9 @@ fi if [ "x$CC" == "xclang" ]; then if [ "x$COVERAGE" != "xyes" ]; then if [ "$TRAVIS_OS_NAME" == "linux" ]; then - export EXTRA_CFLAGS="$EXTRA_CFLAGS -fsanitize=address" - export EXTRA_CXXFLAGS="$EXTRA_CXXFLAGS -fsanitize=address" - export EXTRA_LDFLAGS="$EXTRA_LDFLAGS -fsanitize=address" + export EXTRA_CFLAGS="$EXTRA_CFLAGS -g -fsanitize=address" + export EXTRA_CXXFLAGS="$EXTRA_CXXFLAGS -g -fsanitize=address" + export EXTRA_LDFLAGS="$EXTRA_LDFLAGS -g -fsanitize=address" fi fi fi @@ -90,8 +90,8 @@ else make $MAKE_OPTS clean # Run C++ unit tests - make -C test clean - make -C test test + # make -C test clean + # make -C test test fi @@ -107,28 +107,42 @@ if [ "$CONTINUOUS_INTEGRATION" == "true" ] && [ "$TRAVIS_PULL_REQUEST" != "false ([ "$TRAVIS_OS_NAME" == "linux" ] || [ "$TRAVIS_OS_NAME" == "osx" ] || [ "$TRAVIS_OS_NAME" == "cygwin" ]); then - echo "Fetching PR $TRAVIS_PULL_REQUEST" + if [ "x$TRAVIS_PULL_REQUEST" != "2918" ]; then - JSON=$(curl -L -sS https://api.github.com/repos/sass/libsass/pulls/$TRAVIS_PULL_REQUEST) + echo "Checking out refactoring branch" + cd sass-spec + if ! git config remote.mgreter.url > /dev/null; then + git remote add mgreter https://github.com/mgreter/sass-spec.git -f + git checkout -b refactoring mgreter/feature/libsass-parser-backport + fi + cd .. + make $MAKE_OPTS test_probe - if [[ $JSON =~ "API rate limit exceeded" ]]; - then - echo "Travis rate limit on github exceeded" - echo "Retrying via 'special purpose proxy'" - JSON=$(curl -L -sS https://github-api-reverse-proxy.herokuapp.com/repos/sass/libsass/pulls/$TRAVIS_PULL_REQUEST) - fi + else - RE_SPEC_PR="sass\/sass-spec(#|\/pull\/)([0-9]+)" + echo "Fetching PR $TRAVIS_PULL_REQUEST" - if [[ $JSON =~ $RE_SPEC_PR ]]; - then - SPEC_PR="${BASH_REMATCH[2]}" - echo "Fetching Sass Spec PR $SPEC_PR" - git -C sass-spec fetch -u origin pull/$SPEC_PR/head:ci-spec-pr-$SPEC_PR - git -C sass-spec checkout --force ci-spec-pr-$SPEC_PR - make $MAKE_OPTS test_probe - else - make $MAKE_OPTS test_probe + JSON=$(curl -L -sS https://api.github.com/repos/sass/libsass/pulls/$TRAVIS_PULL_REQUEST) + + if [[ $JSON =~ "API rate limit exceeded" ]]; + then + echo "Travis rate limit on github exceeded" + echo "Retrying via 'special purpose proxy'" + JSON=$(curl -L -sS https://github-api-reverse-proxy.herokuapp.com/repos/sass/libsass/pulls/$TRAVIS_PULL_REQUEST) + fi + + RE_SPEC_PR="sass\/sass-spec(#|\/pull\/)([0-9]+)" + + if [[ $JSON =~ $RE_SPEC_PR ]]; + then + SPEC_PR="${BASH_REMATCH[2]}" + echo "Fetching Sass Spec PR $SPEC_PR" + git -C sass-spec fetch -u origin pull/$SPEC_PR/head:ci-spec-pr-$SPEC_PR + git -C sass-spec checkout --force ci-spec-pr-$SPEC_PR + make $MAKE_OPTS test_probe + else + make $MAKE_OPTS test_probe + fi fi else make $MAKE_OPTS test_probe diff --git a/script/ci-build-plugin b/script/ci-build-plugin index 533a3f512e..0fc4a9ea4e 100755 --- a/script/ci-build-plugin +++ b/script/ci-build-plugin @@ -34,8 +34,10 @@ fi mkdir -p plugins if [ ! -d plugins/libsass-${PLUGIN} ] ; then - if [ "$PLUGIN" == "tests" ]; then - git clone https://github.com/mgreter/libsass-${PLUGIN} plugins/libsass-${PLUGIN} --branch master + if [ "$PLUGIN" == "math" ]; then + git clone https://github.com/mgreter/libsass-${PLUGIN} plugins/libsass-${PLUGIN} --branch feature/libsass-3.7 + elif [ "$PLUGIN" == "tests" ]; then + git clone https://github.com/mgreter/libsass-${PLUGIN} plugins/libsass-${PLUGIN} --branch feature/libsass-4.0 else git clone https://github.com/mgreter/libsass-${PLUGIN} plugins/libsass-${PLUGIN} fi diff --git a/script/ci-report-coverage b/script/ci-report-coverage index 495cb05cbd..a5a42ec192 100755 --- a/script/ci-report-coverage +++ b/script/ci-report-coverage @@ -19,11 +19,9 @@ if [ "x$COVERAGE" = "xyes" ]; then --exclude src/cencode.c --exclude src/b64 --exclude src/utf8 - --exclude src/utf8_string.hpp + --exclude src/unicode.hpp --exclude src/utf8.h - --exclude src/utf8_string.cpp - --exclude src/sass2scss.h - --exclude src/sass2scss.cpp + --exclude src/unicode.cpp --exclude src/test --exclude src/posix --exclude src/debugger.hpp" diff --git a/src/GNUmakefile.am b/src/GNUmakefile.am index 9b0e6a99b3..71a834de3a 100644 --- a/src/GNUmakefile.am +++ b/src/GNUmakefile.am @@ -38,8 +38,7 @@ nodist_EXTRA_libsass_la_SOURCES = non-existent-file-to-force-CXX-linking.cxx endif endif -include_HEADERS = $(top_srcdir)/include/sass.h \ - $(top_srcdir)/include/sass2scss.h +include_HEADERS = $(top_srcdir)/include/sass.h sass_includedir = $(includedir)/sass diff --git a/src/LUrlParser/LUrlParser.cpp b/src/LUrlParser/LUrlParser.cpp new file mode 100644 index 0000000000..9884a908fb --- /dev/null +++ b/src/LUrlParser/LUrlParser.cpp @@ -0,0 +1,265 @@ +/* + * Lightweight URL & URI parser (RFC 1738, RFC 3986) + * https://github.com/corporateshark/LUrlParser + * + * The MIT License (MIT) + * + * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "LUrlParser.hpp" + +#include +#include +#include + +// check if the scheme name is valid +static bool IsSchemeValid( const std::string& SchemeName ) +{ + for ( auto c : SchemeName ) + { + if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false; + } + + return true; +} + +bool LUrlParser::clParseURL::GetPort( int* OutPort ) const +{ + if ( !IsValid() ) { return false; } + + int Port = atoi( m_Port.c_str() ); + + if ( Port <= 0 || Port > 65535 ) { return false; } + + if ( OutPort ) { *OutPort = Port; } + + return true; +} + +// based on RFC 1738 and RFC 3986 +LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL ) +{ + LUrlParser::clParseURL Result; + + const char* CurrentString = URL.c_str(); + + /* + * : + * := [a-z\+\-\.]+ + * For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names + */ + + // try to read scheme + { + const char* LocalString = strchr( CurrentString, ':' ); + + if ( !LocalString ) + { + return clParseURL(LUrlParserError::NoUrlCharacter ); + } + + // save the scheme name + Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString ); + + if ( !IsSchemeValid( Result.m_Scheme ) ) + { + return clParseURL( LUrlParserError::InvalidSchemeName ); + } + + // scheme should be lowercase + std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower ); + + // skip ':' + CurrentString = LocalString+1; + } + + /* + * //:@:/ + * any ":", "@" and "/" must be normalized + */ + + // skip "//" + if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError::NoDoubleSlash ); + if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError::NoDoubleSlash ); + + // check if the user name and password are specified + bool bHasUserName = false; + + const char* LocalString = CurrentString; + + while ( *LocalString ) + { + if ( *LocalString == '@' ) + { + // user name and password are specified + bHasUserName = true; + break; + } + else if ( *LocalString == '/' ) + { + // end of : specification + bHasUserName = false; + break; + } + + LocalString++; + } + + // user name and password + LocalString = CurrentString; + + if ( bHasUserName ) + { + // read user name + while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++; + + Result.m_UserName = std::string( CurrentString, LocalString - CurrentString ); + + // proceed with the current pointer + CurrentString = LocalString; + + if ( *CurrentString == ':' ) + { + // skip ':' + CurrentString++; + + // read password + LocalString = CurrentString; + + while ( *LocalString && *LocalString != '@' ) LocalString++; + + Result.m_Password = std::string( CurrentString, LocalString - CurrentString ); + + CurrentString = LocalString; + } + + // skip '@' + if ( *CurrentString != '@' ) + { + return clParseURL( LUrlParserError::NoAtSign ); + } + + CurrentString++; + } + + bool bHasBracket = ( *CurrentString == '[' ); + + // go ahead, read the host name + LocalString = CurrentString; + + while ( *LocalString ) + { + if ( bHasBracket && *LocalString == ']' ) + { + // end of IPv6 address + LocalString++; + break; + } + else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) ) + { + // port number is specified + break; + } + + LocalString++; + } + + Result.m_Host = std::string( CurrentString, LocalString - CurrentString ); + + CurrentString = LocalString; + + // is port number specified? + if ( *CurrentString == ':' ) + { + CurrentString++; + + // read port number + LocalString = CurrentString; + + while ( *LocalString && *LocalString != '/' ) LocalString++; + + Result.m_Port = std::string( CurrentString, LocalString - CurrentString ); + + CurrentString = LocalString; + } + + // end of string + if ( !*CurrentString ) + { + Result.m_ErrorCode = LUrlParserError::Ok; + + return Result; + } + + // skip '/' + if ( *CurrentString != '/' ) + { + return clParseURL( LUrlParserError::NoSlash ); + } + + CurrentString++; + + // parse the path + LocalString = CurrentString; + + while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++; + + Result.m_Path = std::string( CurrentString, LocalString - CurrentString ); + + CurrentString = LocalString; + + // check for query + if ( *CurrentString == '?' ) + { + // skip '?' + CurrentString++; + + // read query + LocalString = CurrentString; + + while ( *LocalString && *LocalString != '#' ) LocalString++; + + Result.m_Query = std::string( CurrentString, LocalString - CurrentString ); + + CurrentString = LocalString; + } + + // check for fragment + if ( *CurrentString == '#' ) + { + // skip '#' + CurrentString++; + + // read fragment + LocalString = CurrentString; + + while ( *LocalString ) LocalString++; + + Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString ); + + CurrentString = LocalString; + } + + Result.m_ErrorCode = LUrlParserError::Ok; + + return Result; +} diff --git a/src/LUrlParser/LUrlParser.hpp b/src/LUrlParser/LUrlParser.hpp new file mode 100644 index 0000000000..a4b5d759d8 --- /dev/null +++ b/src/LUrlParser/LUrlParser.hpp @@ -0,0 +1,80 @@ +/* + * Lightweight URL & URI parser (RFC 1738, RFC 3986) + * https://github.com/corporateshark/LUrlParser + * + * The MIT License (MIT) + * + * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include + +namespace LUrlParser +{ + + // LibSass change: made enum scoped + enum class LUrlParserError + { + Ok = 0, + Uninitialized = 1, + NoUrlCharacter = 2, + InvalidSchemeName = 3, + NoDoubleSlash = 4, + NoAtSign = 5, + UnexpectedEndOfLine = 6, + NoSlash = 7, + }; + + class clParseURL + { + public: + LUrlParserError m_ErrorCode; + std::string m_Scheme; + std::string m_Host; + std::string m_Port; + std::string m_Path; + std::string m_Query; + std::string m_Fragment; + std::string m_UserName; + std::string m_Password; + + clParseURL() + : m_ErrorCode(LUrlParserError::Uninitialized ) + {} + + /// return 'true' if the parsing was successful + bool IsValid() const { return m_ErrorCode == LUrlParserError::Ok; } + + /// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range) + bool GetPort( int* OutPort ) const; + + /// parse the URL + static clParseURL ParseURL( const std::string& URL ); + + private: + explicit clParseURL( LUrlParserError ErrorCode ) + : m_ErrorCode( ErrorCode ) + {} + }; + +} // namespace LUrlParser diff --git a/src/MurmurHash2.hpp b/src/MurmurHash2.hpp index ab9b1634c6..3a58265a57 100644 --- a/src/MurmurHash2.hpp +++ b/src/MurmurHash2.hpp @@ -1,15 +1,18 @@ -//----------------------------------------------------------------------------- -// MurmurHash2 was written by Austin Appleby, and is placed in the public -// domain. The author hereby disclaims copyright to this source code. -//----------------------------------------------------------------------------- -// LibSass only needs MurmurHash2, so we made this header only -//----------------------------------------------------------------------------- +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* MurmurHash2 was written by Austin Appleby, and is placed in the public */ +/* domain. The author hereby disclaims copyright to this source code. */ +/*****************************************************************************/ +/* LibSass only needs MurmurHash2, so we made this header only */ +/*****************************************************************************/ #ifndef _MURMURHASH2_H_ #define _MURMURHASH2_H_ -//----------------------------------------------------------------------------- +///////////////////////////////////////////////////////////////////////// // Platform-specific functions and macros +///////////////////////////////////////////////////////////////////////// // Microsoft Visual Studio @@ -23,11 +26,12 @@ typedef unsigned __int64 uint64_t; #else // defined(_MSC_VER) -#include +#include #endif // !defined(_MSC_VER) -//----------------------------------------------------------------------------- +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// inline uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ) { @@ -85,7 +89,8 @@ inline uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ) return h; } -//----------------------------------------------------------------------------- +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// #endif // _MURMURHASH2_H_ diff --git a/src/ast.cpp b/src/ast.cpp index 2c0dd64c94..43d2894305 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -1,953 +1,131 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "ast.hpp" -namespace Sass { - - static Null sass_null(SourceSpan("null")); - - const char* sass_op_to_name(enum Sass_OP op) { - switch (op) { - case AND: return "and"; - case OR: return "or"; - case EQ: return "eq"; - case NEQ: return "neq"; - case GT: return "gt"; - case GTE: return "gte"; - case LT: return "lt"; - case LTE: return "lte"; - case ADD: return "plus"; - case SUB: return "minus"; - case MUL: return "times"; - case DIV: return "div"; - case MOD: return "mod"; - // this is only used internally! - case NUM_OPS: return "[OPS]"; - default: return "invalid"; - } - } - - const char* sass_op_separator(enum Sass_OP op) { - switch (op) { - case AND: return "&&"; - case OR: return "||"; - case EQ: return "=="; - case NEQ: return "!="; - case GT: return ">"; - case GTE: return ">="; - case LT: return "<"; - case LTE: return "<="; - case ADD: return "+"; - case SUB: return "-"; - case MUL: return "*"; - case DIV: return "/"; - case MOD: return "%"; - // this is only used internally! - case NUM_OPS: return "[OPS]"; - default: return "invalid"; - } - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - void AST_Node::update_pstate(const SourceSpan& pstate) - { - pstate_.offset += pstate.position - pstate_.position + pstate.offset; - } - - sass::string AST_Node::to_string(Sass_Inspect_Options opt) const - { - Sass_Output_Options out(opt); - Emitter emitter(out); - Inspect i(emitter); - i.in_declaration = true; - // ToDo: inspect should be const - const_cast(this)->perform(&i); - return i.get_buffer(); - } - - sass::string AST_Node::to_css(Sass_Inspect_Options opt) const - { - opt.output_style = TO_CSS; - Sass_Output_Options out(opt); - Emitter emitter(out); - Inspect i(emitter); - i.in_declaration = true; - // ToDo: inspect should be const - const_cast(this)->perform(&i); - return i.get_buffer(); - } - - sass::string AST_Node::to_string() const - { - return to_string({ NESTED, 5 }); - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Statement::Statement(SourceSpan pstate, Type st, size_t t) - : AST_Node(pstate), statement_type_(st), tabs_(t), group_end_(false) - { } - Statement::Statement(const Statement* ptr) - : AST_Node(ptr), - statement_type_(ptr->statement_type_), - tabs_(ptr->tabs_), - group_end_(ptr->group_end_) - { } - - bool Statement::bubbles() - { - return false; - } - - bool Statement::has_content() - { - return statement_type_ == CONTENT; - } - - bool Statement::is_invisible() const - { - return false; - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Block::Block(SourceSpan pstate, size_t s, bool r) - : Statement(pstate), - Vectorized(s), - is_root_(r) - { } - Block::Block(const Block* ptr) - : Statement(ptr), - Vectorized(*ptr), - is_root_(ptr->is_root_) - { } - - bool Block::isInvisible() const - { - for (auto& item : elements()) { - if (!item->is_invisible()) return false; - } - return true; - } - - bool Block::has_content() - { - for (size_t i = 0, L = elements().size(); i < L; ++i) { - if (elements()[i]->has_content()) return true; - } - return Statement::has_content(); - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - ParentStatement::ParentStatement(SourceSpan pstate, Block_Obj b) - : Statement(pstate), block_(b) - { } - ParentStatement::ParentStatement(const ParentStatement* ptr) - : Statement(ptr), block_(ptr->block_) - { } - - bool ParentStatement::has_content() - { - return (block_ && block_->has_content()) || Statement::has_content(); - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - StyleRule::StyleRule(SourceSpan pstate, SelectorListObj s, Block_Obj b) - : ParentStatement(pstate, b), selector_(s), schema_(), is_root_(false) - { statement_type(RULESET); } - StyleRule::StyleRule(const StyleRule* ptr) - : ParentStatement(ptr), - selector_(ptr->selector_), - schema_(ptr->schema_), - is_root_(ptr->is_root_) - { statement_type(RULESET); } - - bool StyleRule::is_invisible() const { - if (const SelectorList * sl = Cast(selector())) { - for (size_t i = 0, L = sl->length(); i < L; i += 1) - if (!(*sl)[i]->isInvisible()) return false; - } - return true; - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Bubble::Bubble(SourceSpan pstate, Statement_Obj n, Statement_Obj g, size_t t) - : Statement(pstate, Statement::BUBBLE, t), node_(n), group_end_(g == nullptr) - { } - Bubble::Bubble(const Bubble* ptr) - : Statement(ptr), - node_(ptr->node_), - group_end_(ptr->group_end_) - { } - - bool Bubble::bubbles() - { - return true; - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Trace::Trace(SourceSpan pstate, sass::string n, Block_Obj b, char type) - : ParentStatement(pstate, b), type_(type), name_(n) - { } - Trace::Trace(const Trace* ptr) - : ParentStatement(ptr), - type_(ptr->type_), - name_(ptr->name_) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - AtRule::AtRule(SourceSpan pstate, sass::string kwd, SelectorListObj sel, Block_Obj b, ExpressionObj val) - : ParentStatement(pstate, b), keyword_(kwd), selector_(sel), value_(val) // set value manually if needed - { statement_type(DIRECTIVE); } - AtRule::AtRule(const AtRule* ptr) - : ParentStatement(ptr), - keyword_(ptr->keyword_), - selector_(ptr->selector_), - value_(ptr->value_) // set value manually if needed - { statement_type(DIRECTIVE); } - - bool AtRule::bubbles() { return is_keyframes() || is_media(); } - - bool AtRule::is_media() { - return keyword_.compare("@-webkit-media") == 0 || - keyword_.compare("@-moz-media") == 0 || - keyword_.compare("@-o-media") == 0 || - keyword_.compare("@media") == 0; - } - bool AtRule::is_keyframes() { - return keyword_.compare("@-webkit-keyframes") == 0 || - keyword_.compare("@-moz-keyframes") == 0 || - keyword_.compare("@-o-keyframes") == 0 || - keyword_.compare("@keyframes") == 0; - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Keyframe_Rule::Keyframe_Rule(SourceSpan pstate, Block_Obj b) - : ParentStatement(pstate, b), name_() - { statement_type(KEYFRAMERULE); } - Keyframe_Rule::Keyframe_Rule(const Keyframe_Rule* ptr) - : ParentStatement(ptr), name_(ptr->name_) - { statement_type(KEYFRAMERULE); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Declaration::Declaration(SourceSpan pstate, String_Obj prop, ExpressionObj val, bool i, bool c, Block_Obj b) - : ParentStatement(pstate, b), property_(prop), value_(val), is_important_(i), is_custom_property_(c), is_indented_(false) - { statement_type(DECLARATION); } - Declaration::Declaration(const Declaration* ptr) - : ParentStatement(ptr), - property_(ptr->property_), - value_(ptr->value_), - is_important_(ptr->is_important_), - is_custom_property_(ptr->is_custom_property_), - is_indented_(ptr->is_indented_) - { statement_type(DECLARATION); } - - bool Declaration::is_invisible() const - { - if (is_custom_property()) return false; - return !(value_ && !Cast(value_)); - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Assignment::Assignment(SourceSpan pstate, sass::string var, ExpressionObj val, bool is_default, bool is_global) - : Statement(pstate), variable_(var), value_(val), is_default_(is_default), is_global_(is_global) - { statement_type(ASSIGNMENT); } - Assignment::Assignment(const Assignment* ptr) - : Statement(ptr), - variable_(ptr->variable_), - value_(ptr->value_), - is_default_(ptr->is_default_), - is_global_(ptr->is_global_) - { statement_type(ASSIGNMENT); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Import::Import(SourceSpan pstate) - : Statement(pstate), - urls_(sass::vector()), - incs_(sass::vector()), - import_queries_() - { statement_type(IMPORT); } - Import::Import(const Import* ptr) - : Statement(ptr), - urls_(ptr->urls_), - incs_(ptr->incs_), - import_queries_(ptr->import_queries_) - { statement_type(IMPORT); } - - sass::vector& Import::incs() { return incs_; } - sass::vector& Import::urls() { return urls_; } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Import_Stub::Import_Stub(SourceSpan pstate, Include res) - : Statement(pstate), resource_(res) - { statement_type(IMPORT_STUB); } - Import_Stub::Import_Stub(const Import_Stub* ptr) - : Statement(ptr), resource_(ptr->resource_) - { statement_type(IMPORT_STUB); } - Include Import_Stub::resource() { return resource_; }; - sass::string Import_Stub::imp_path() { return resource_.imp_path; }; - sass::string Import_Stub::abs_path() { return resource_.abs_path; }; - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - WarningRule::WarningRule(SourceSpan pstate, ExpressionObj msg) - : Statement(pstate), message_(msg) - { statement_type(WARNING); } - WarningRule::WarningRule(const WarningRule* ptr) - : Statement(ptr), message_(ptr->message_) - { statement_type(WARNING); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - ErrorRule::ErrorRule(SourceSpan pstate, ExpressionObj msg) - : Statement(pstate), message_(msg) - { statement_type(ERROR); } - ErrorRule::ErrorRule(const ErrorRule* ptr) - : Statement(ptr), message_(ptr->message_) - { statement_type(ERROR); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// +#include "cssize.hpp" +#include "inspect.hpp" +#include "dart_helpers.hpp" - DebugRule::DebugRule(SourceSpan pstate, ExpressionObj val) - : Statement(pstate), value_(val) - { statement_type(DEBUGSTMT); } - DebugRule::DebugRule(const DebugRule* ptr) - : Statement(ptr), value_(ptr->value_) - { statement_type(DEBUGSTMT); } +namespace Sass { ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Comment::Comment(SourceSpan pstate, String_Obj txt, bool is_important) - : Statement(pstate), text_(txt), is_important_(is_important) - { statement_type(COMMENT); } - Comment::Comment(const Comment* ptr) - : Statement(ptr), - text_(ptr->text_), - is_important_(ptr->is_important_) - { statement_type(COMMENT); } - - bool Comment::is_invisible() const - { - return false; - } + // Needs to be in sync with SassOp enum + uint8_t SassOpPresedence[15] = { + 1, 2, 3, 3, 4, 4, 4, 4, + 5, 5, 6, 6, 6, 9, 255 + }; - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// + // Needs to be in sync with SassOp enum + const char* SassOpName[15] = { + "or", "and", "eq", "neq", "gt", "gte", "lt", "lte", + "plus", "minus", "times", "div", "mod", "seq", "invalid" + }; - If::If(SourceSpan pstate, ExpressionObj pred, Block_Obj con, Block_Obj alt) - : ParentStatement(pstate, con), predicate_(pred), alternative_(alt) - { statement_type(IF); } - If::If(const If* ptr) - : ParentStatement(ptr), - predicate_(ptr->predicate_), - alternative_(ptr->alternative_) - { statement_type(IF); } + // Needs to be in sync with SassOp enum + const char* SassOpOperator[15] = { + "||", "&&", "==", "!=", ">", ">=", "<", "<=", + "+", "-", "*", "/", "%", "=", "invalid" + }; - bool If::has_content() + // Precedence is used to decide order + // in ExpressionParser::addOperator. + uint8_t sass_op_to_precedence(enum SassOperator op) { - return ParentStatement::has_content() || (alternative_ && alternative_->has_content()); + return SassOpPresedence[op]; } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - ForRule::ForRule(SourceSpan pstate, - sass::string var, ExpressionObj lo, ExpressionObj hi, Block_Obj b, bool inc) - : ParentStatement(pstate, b), - variable_(var), lower_bound_(lo), upper_bound_(hi), is_inclusive_(inc) - { statement_type(FOR); } - ForRule::ForRule(const ForRule* ptr) - : ParentStatement(ptr), - variable_(ptr->variable_), - lower_bound_(ptr->lower_bound_), - upper_bound_(ptr->upper_bound_), - is_inclusive_(ptr->is_inclusive_) - { statement_type(FOR); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - EachRule::EachRule(SourceSpan pstate, sass::vector vars, ExpressionObj lst, Block_Obj b) - : ParentStatement(pstate, b), variables_(vars), list_(lst) - { statement_type(EACH); } - EachRule::EachRule(const EachRule* ptr) - : ParentStatement(ptr), variables_(ptr->variables_), list_(ptr->list_) - { statement_type(EACH); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - WhileRule::WhileRule(SourceSpan pstate, ExpressionObj pred, Block_Obj b) - : ParentStatement(pstate, b), predicate_(pred) - { statement_type(WHILE); } - WhileRule::WhileRule(const WhileRule* ptr) - : ParentStatement(ptr), predicate_(ptr->predicate_) - { statement_type(WHILE); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Return::Return(SourceSpan pstate, ExpressionObj val) - : Statement(pstate), value_(val) - { statement_type(RETURN); } - Return::Return(const Return* ptr) - : Statement(ptr), value_(ptr->value_) - { statement_type(RETURN); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - ExtendRule::ExtendRule(SourceSpan pstate, SelectorListObj s) - : Statement(pstate), isOptional_(false), selector_(s), schema_() - { statement_type(EXTEND); } - ExtendRule::ExtendRule(SourceSpan pstate, Selector_Schema_Obj s) - : Statement(pstate), isOptional_(false), selector_(), schema_(s) + // Get readable name for error messages + const char* sass_op_to_name(enum SassOperator op) { - statement_type(EXTEND); + return SassOpName[op]; } - ExtendRule::ExtendRule(const ExtendRule* ptr) - : Statement(ptr), - isOptional_(ptr->isOptional_), - selector_(ptr->selector_), - schema_(ptr->schema_) - { statement_type(EXTEND); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Definition::Definition(const Definition* ptr) - : ParentStatement(ptr), - name_(ptr->name_), - parameters_(ptr->parameters_), - environment_(ptr->environment_), - type_(ptr->type_), - native_function_(ptr->native_function_), - c_function_(ptr->c_function_), - cookie_(ptr->cookie_), - is_overload_stub_(ptr->is_overload_stub_), - signature_(ptr->signature_) - { } - - Definition::Definition(SourceSpan pstate, - sass::string n, - Parameters_Obj params, - Block_Obj b, - Type t) - : ParentStatement(pstate, b), - name_(n), - parameters_(params), - environment_(0), - type_(t), - native_function_(0), - c_function_(0), - cookie_(0), - is_overload_stub_(false), - signature_(0) - { } - - Definition::Definition(SourceSpan pstate, - Signature sig, - sass::string n, - Parameters_Obj params, - Native_Function func_ptr, - bool overload_stub) - : ParentStatement(pstate, {}), - name_(n), - parameters_(params), - environment_(0), - type_(FUNCTION), - native_function_(func_ptr), - c_function_(0), - cookie_(0), - is_overload_stub_(overload_stub), - signature_(sig) - { } - Definition::Definition(SourceSpan pstate, - Signature sig, - sass::string n, - Parameters_Obj params, - Sass_Function_Entry c_func) - : ParentStatement(pstate, {}), - name_(n), - parameters_(params), - environment_(0), - type_(FUNCTION), - native_function_(0), - c_function_(c_func), - cookie_(sass_function_get_cookie(c_func)), - is_overload_stub_(false), - signature_(sig) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Mixin_Call::Mixin_Call(SourceSpan pstate, sass::string n, Arguments_Obj args, Parameters_Obj b_params, Block_Obj b) - : ParentStatement(pstate, b), name_(n), arguments_(args), block_parameters_(b_params) - { } - Mixin_Call::Mixin_Call(const Mixin_Call* ptr) - : ParentStatement(ptr), - name_(ptr->name_), - arguments_(ptr->arguments_), - block_parameters_(ptr->block_parameters_) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Content::Content(SourceSpan pstate, Arguments_Obj args) - : Statement(pstate), - arguments_(args) - { statement_type(CONTENT); } - Content::Content(const Content* ptr) - : Statement(ptr), - arguments_(ptr->arguments_) - { statement_type(CONTENT); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Expression::Expression(SourceSpan pstate, bool d, bool e, bool i, Type ct) - : AST_Node(pstate), - is_delayed_(d), - is_expanded_(e), - is_interpolant_(i), - concrete_type_(ct) - { } - - Expression::Expression(const Expression* ptr) - : AST_Node(ptr), - is_delayed_(ptr->is_delayed_), - is_expanded_(ptr->is_expanded_), - is_interpolant_(ptr->is_interpolant_), - concrete_type_(ptr->concrete_type_) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Unary_Expression::Unary_Expression(SourceSpan pstate, Type t, ExpressionObj o) - : Expression(pstate), optype_(t), operand_(o), hash_(0) - { } - Unary_Expression::Unary_Expression(const Unary_Expression* ptr) - : Expression(ptr), - optype_(ptr->optype_), - operand_(ptr->operand_), - hash_(ptr->hash_) - { } - const sass::string Unary_Expression::type_name() { - switch (optype_) { - case PLUS: return "plus"; - case MINUS: return "minus"; - case SLASH: return "slash"; - case NOT: return "not"; - default: return "invalid"; - } - } - bool Unary_Expression::operator==(const Expression& rhs) const - { - try - { - const Unary_Expression* m = Cast(&rhs); - if (m == 0) return false; - return type() == m->type() && - *operand() == *m->operand(); - } - catch (std::bad_cast&) - { - return false; - } - catch (...) { throw; } - } - size_t Unary_Expression::hash() const + // Get readable name for operator (e.g. `==`) + const char* sass_op_separator(enum SassOperator op) { - if (hash_ == 0) { - hash_ = std::hash()(optype_); - hash_combine(hash_, operand()->hash()); - }; - return hash_; + return SassOpOperator[op]; } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Argument::Argument(SourceSpan pstate, ExpressionObj val, sass::string n, bool rest, bool keyword) - : Expression(pstate), value_(val), name_(n), is_rest_argument_(rest), is_keyword_argument_(keyword), hash_(0) + sass::string Selector::inspect(int precision) const { - if (!name_.empty() && is_rest_argument_) { - coreError("variable-length argument may not be passed by name", pstate_); - } - } - Argument::Argument(const Argument* ptr) - : Expression(ptr), - value_(ptr->value_), - name_(ptr->name_), - is_rest_argument_(ptr->is_rest_argument_), - is_keyword_argument_(ptr->is_keyword_argument_), - hash_(ptr->hash_) - { - if (!name_.empty() && is_rest_argument_) { - coreError("variable-length argument may not be passed by name", pstate_); - } - } - - void Argument::set_delayed(bool delayed) - { - if (value_) value_->set_delayed(delayed); - is_delayed(delayed); + SassOutputOptionsCpp out({ + SASS_STYLE_NESTED, + precision }); + Inspect i(out, false); + i.inspect = true; + // Inspect must be const, accept isn't + const_cast(this)->accept(&i); + return i.get_buffer(); } - bool Argument::operator==(const Expression& rhs) const + sass::string Value::inspect(int precision, bool quotes) const { - try - { - const Argument* m = Cast(&rhs); - if (!(m && name() == m->name())) return false; - return *value() == *m->value(); - } - catch (std::bad_cast&) - { - return false; - } - catch (...) { throw; } + SassOutputOptionsCpp out({ + SASS_STYLE_NESTED, + precision }); + Inspect i(out, false); + i.inspect = true; + i.quotes = quotes; + // Inspect must be const, accept isn't + const_cast(this)->accept(&i); + return i.get_buffer(); } - size_t Argument::hash() const + sass::string Value::toCss(Logger& logger, bool quote) const { - if (hash_ == 0) { - hash_ = std::hash()(name()); - hash_combine(hash_, value()->hash()); - } - return hash_; + SassOutputOptionsCpp out({ + SASS_STYLE_TO_CSS, + SassDefaultPrecision }); + Cssize i(out, false); + i.quotes = quote; + // Inspect must be const, accept isn't + const_cast(this)->accept(&i); + return i.get_buffer(); } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Arguments::Arguments(SourceSpan pstate) - : Expression(pstate), - Vectorized(), - has_named_arguments_(false), - has_rest_argument_(false), - has_keyword_argument_(false) - { } - Arguments::Arguments(const Arguments* ptr) - : Expression(ptr), - Vectorized(*ptr), - has_named_arguments_(ptr->has_named_arguments_), - has_rest_argument_(ptr->has_rest_argument_), - has_keyword_argument_(ptr->has_keyword_argument_) - { } - - void Arguments::set_delayed(bool delayed) - { - for (Argument_Obj arg : elements()) { - if (arg) arg->set_delayed(delayed); - } - is_delayed(delayed); - } + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// - Argument_Obj Arguments::get_rest_argument() + // Only used for nth sass function + // Single values act like lists with 1 item + // Doesn't allow overflow of index (throw error) + // Allows negative index but no overflow either + Value* Value::getValueAt(Value* index, Logger& logger) { - if (this->has_rest_argument()) { - for (Argument_Obj arg : this->elements()) { - if (arg->is_rest_argument()) { - return arg; - } - } - } - return {}; + // Check out of boundary access + sassIndexToListIndex(index, logger, "n"); + // Return single value + return this; } - Argument_Obj Arguments::get_keyword_argument() + // Only used for nth sass function + // Doesn't allow overflow of index (throw error) + // Allows negative index but no overflow either + Value* Map::getValueAt(Value* index, Logger& logger) { - if (this->has_keyword_argument()) { - for (Argument_Obj arg : this->elements()) { - if (arg->is_keyword_argument()) { - return arg; - } - } - } - return {}; + return getPairAsList(sassIndexToListIndex(index, logger, "n")); } - void Arguments::adjust_after_pushing(Argument_Obj a) - { - if (!a->name().empty()) { - if (has_keyword_argument()) { - coreError("named arguments must precede variable-length argument", a->pstate()); - } - has_named_arguments(true); - } - else if (a->is_rest_argument()) { - if (has_rest_argument()) { - coreError("functions and mixins may only be called with one variable-length argument", a->pstate()); - } - if (has_keyword_argument_) { - coreError("only keyword arguments may follow variable arguments", a->pstate()); - } - has_rest_argument(true); - } - else if (a->is_keyword_argument()) { - if (has_keyword_argument()) { - coreError("functions and mixins may only be called with one keyword argument", a->pstate()); - } - has_keyword_argument(true); - } - else { - if (has_rest_argument()) { - coreError("ordinal arguments must precede variable-length arguments", a->pstate()); - } - if (has_named_arguments()) { - coreError("ordinal arguments must precede named arguments", a->pstate()); - } - } + // Search the position of the given value + size_t List::indexOf(Value* value) { + return Sass::indexOf(elements(), value); } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Media_Query::Media_Query(SourceSpan pstate, String_Obj t, size_t s, bool n, bool r) - : Expression(pstate), Vectorized(s), - media_type_(t), is_negated_(n), is_restricted_(r) - { } - Media_Query::Media_Query(const Media_Query* ptr) - : Expression(ptr), - Vectorized(*ptr), - media_type_(ptr->media_type_), - is_negated_(ptr->is_negated_), - is_restricted_(ptr->is_restricted_) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Media_Query_Expression::Media_Query_Expression(SourceSpan pstate, - ExpressionObj f, ExpressionObj v, bool i) - : Expression(pstate), feature_(f), value_(v), is_interpolated_(i) - { } - Media_Query_Expression::Media_Query_Expression(const Media_Query_Expression* ptr) - : Expression(ptr), - feature_(ptr->feature_), - value_(ptr->value_), - is_interpolated_(ptr->is_interpolated_) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - At_Root_Query::At_Root_Query(SourceSpan pstate, ExpressionObj f, ExpressionObj v, bool i) - : Expression(pstate), feature_(f), value_(v) - { } - At_Root_Query::At_Root_Query(const At_Root_Query* ptr) - : Expression(ptr), - feature_(ptr->feature_), - value_(ptr->value_) - { } - - bool At_Root_Query::exclude(sass::string str) + // Only used for nth sass function + // Doesn't allow overflow of index (throw error) + // Allows negative index but no overflow either + Value* List::getValueAt(Value* index, Logger& logger) { - bool with = feature() && unquote(feature()->to_string()).compare("with") == 0; - List* l = static_cast(value().ptr()); - sass::string v; - - if (with) - { - if (!l || l->length() == 0) return str.compare("rule") != 0; - for (size_t i = 0, L = l->length(); i < L; ++i) - { - v = unquote((*l)[i]->to_string()); - if (v.compare("all") == 0 || v == str) return false; - } - return true; - } - else - { - if (!l || !l->length()) return str.compare("rule") == 0; - for (size_t i = 0, L = l->length(); i < L; ++i) - { - v = unquote((*l)[i]->to_string()); - if (v.compare("all") == 0 || v == str) return true; - } - return false; - } + return get(sassIndexToListIndex(index, logger, "n")); } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - AtRootRule::AtRootRule(SourceSpan pstate, Block_Obj b, At_Root_Query_Obj e) - : ParentStatement(pstate, b), expression_(e) - { statement_type(ATROOT); } - AtRootRule::AtRootRule(const AtRootRule* ptr) - : ParentStatement(ptr), expression_(ptr->expression_) - { statement_type(ATROOT); } - - bool AtRootRule::bubbles() { - return true; - } - - bool AtRootRule::exclude_node(Statement_Obj s) { - if (expression() == nullptr) - { - return s->statement_type() == Statement::RULESET; - } - - if (s->statement_type() == Statement::DIRECTIVE) - { - if (AtRuleObj dir = Cast(s)) - { - sass::string keyword(dir->keyword()); - if (keyword.length() > 0) keyword.erase(0, 1); - return expression()->exclude(keyword); - } - } - if (s->statement_type() == Statement::MEDIA) - { - return expression()->exclude("media"); - } - if (s->statement_type() == Statement::RULESET) - { - return expression()->exclude("rule"); - } - if (s->statement_type() == Statement::SUPPORTS) - { - return expression()->exclude("supports"); - } - if (AtRuleObj dir = Cast(s)) - { - if (dir->is_keyframes()) return expression()->exclude("keyframes"); - } - return false; - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Parameter::Parameter(SourceSpan pstate, sass::string n, ExpressionObj def, bool rest) - : AST_Node(pstate), name_(n), default_value_(def), is_rest_parameter_(rest) - { } - Parameter::Parameter(const Parameter* ptr) - : AST_Node(ptr), - name_(ptr->name_), - default_value_(ptr->default_value_), - is_rest_parameter_(ptr->is_rest_parameter_) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Parameters::Parameters(SourceSpan pstate) - : AST_Node(pstate), - Vectorized(), - has_optional_parameters_(false), - has_rest_parameter_(false) - { } - Parameters::Parameters(const Parameters* ptr) - : AST_Node(ptr), - Vectorized(*ptr), - has_optional_parameters_(ptr->has_optional_parameters_), - has_rest_parameter_(ptr->has_rest_parameter_) - { } - - void Parameters::adjust_after_pushing(Parameter_Obj p) - { - if (p->default_value()) { - if (has_rest_parameter()) { - coreError("optional parameters may not be combined with variable-length parameters", p->pstate()); - } - has_optional_parameters(true); - } - else if (p->is_rest_parameter()) { - if (has_rest_parameter()) { - coreError("functions and mixins cannot have more than one variable-length parameter", p->pstate()); - } - has_rest_parameter(true); - } - else { - if (has_rest_parameter()) { - coreError("required parameters must precede variable-length parameters", p->pstate()); - } - if (has_optional_parameters()) { - coreError("required parameters must precede optional parameters", p->pstate()); - } - } - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - // If you forget to add a class here you will get - // undefined reference to `vtable for Sass::Class' - - IMPLEMENT_AST_OPERATORS(StyleRule); - IMPLEMENT_AST_OPERATORS(MediaRule); - IMPLEMENT_AST_OPERATORS(CssMediaRule); - IMPLEMENT_AST_OPERATORS(CssMediaQuery); - IMPLEMENT_AST_OPERATORS(Import); - IMPLEMENT_AST_OPERATORS(Import_Stub); - IMPLEMENT_AST_OPERATORS(AtRule); - IMPLEMENT_AST_OPERATORS(AtRootRule); - IMPLEMENT_AST_OPERATORS(WhileRule); - IMPLEMENT_AST_OPERATORS(EachRule); - IMPLEMENT_AST_OPERATORS(ForRule); - IMPLEMENT_AST_OPERATORS(If); - IMPLEMENT_AST_OPERATORS(Mixin_Call); - IMPLEMENT_AST_OPERATORS(ExtendRule); - IMPLEMENT_AST_OPERATORS(Media_Query); - IMPLEMENT_AST_OPERATORS(Media_Query_Expression); - IMPLEMENT_AST_OPERATORS(DebugRule); - IMPLEMENT_AST_OPERATORS(ErrorRule); - IMPLEMENT_AST_OPERATORS(WarningRule); - IMPLEMENT_AST_OPERATORS(Assignment); - IMPLEMENT_AST_OPERATORS(Return); - IMPLEMENT_AST_OPERATORS(At_Root_Query); - IMPLEMENT_AST_OPERATORS(Comment); - IMPLEMENT_AST_OPERATORS(Parameters); - IMPLEMENT_AST_OPERATORS(Parameter); - IMPLEMENT_AST_OPERATORS(Arguments); - IMPLEMENT_AST_OPERATORS(Argument); - IMPLEMENT_AST_OPERATORS(Unary_Expression); - IMPLEMENT_AST_OPERATORS(Block); - IMPLEMENT_AST_OPERATORS(Content); - IMPLEMENT_AST_OPERATORS(Trace); - IMPLEMENT_AST_OPERATORS(Keyframe_Rule); - IMPLEMENT_AST_OPERATORS(Bubble); - IMPLEMENT_AST_OPERATORS(Definition); - IMPLEMENT_AST_OPERATORS(Declaration); - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// } diff --git a/src/ast.hpp b/src/ast.hpp index e7dcaf657d..7cef4a1eee 100644 --- a/src/ast.hpp +++ b/src/ast.hpp @@ -1,1064 +1,24 @@ -#ifndef SASS_AST_H -#define SASS_AST_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include - -#include "sass/base.h" -#include "ast_helpers.hpp" -#include "ast_fwd_decl.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_HPP +#define SASS_AST_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_css.hpp" +#include "ast_callables.hpp" +#include "ast_containers.hpp" +#include "ast_expressions.hpp" #include "ast_def_macros.hpp" - -#include "file.hpp" -#include "position.hpp" -#include "operation.hpp" -#include "environment.hpp" -#include "fn_utils.hpp" - -namespace Sass { - - // ToDo: where does this fit best? - // We don't share this with C-API? - class Operand { - public: - Operand(Sass_OP operand, bool ws_before = false, bool ws_after = false) - : operand(operand), ws_before(ws_before), ws_after(ws_after) - { } - public: - enum Sass_OP operand; - bool ws_before; - bool ws_after; - }; - - ////////////////////////////////////////////////////////// - // `hash_combine` comes from boost (functional/hash): - // http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html - // Boost Software License - Version 1.0 - // http://www.boost.org/users/license.html - template - void hash_combine (std::size_t& seed, const T& val) - { - seed ^= std::hash()(val) + 0x9e3779b9 - + (seed<<6) + (seed>>2); - } - ////////////////////////////////////////////////////////// - - const char* sass_op_to_name(enum Sass_OP op); - - const char* sass_op_separator(enum Sass_OP op); - - ////////////////////////////////////////////////////////// - // Abstract base class for all abstract syntax tree nodes. - ////////////////////////////////////////////////////////// - class AST_Node : public SharedObj { - ADD_PROPERTY(SourceSpan, pstate) - public: - AST_Node(SourceSpan pstate) - : pstate_(pstate) - { } - AST_Node(const AST_Node* ptr) - : pstate_(ptr->pstate_) - { } - - // allow implicit conversion to string - // needed for by SharedPtr implementation - operator sass::string() { - return to_string(); - } - - // AST_Node(AST_Node& ptr) = delete; - - virtual ~AST_Node() = 0; - virtual size_t hash() const { return 0; } - virtual sass::string inspect() const { return to_string({ INSPECT, 5 }); } - virtual sass::string to_sass() const { return to_string({ TO_SASS, 5 }); } - virtual sass::string to_string(Sass_Inspect_Options opt) const; - virtual sass::string to_css(Sass_Inspect_Options opt) const; - virtual sass::string to_string() const; - virtual void cloneChildren() {}; - // generic find function (not fully implemented yet) - // ToDo: add specific implementations to all children - virtual bool find ( bool (*f)(AST_Node_Obj) ) { return f(this); }; - void update_pstate(const SourceSpan& pstate); - - // Some objects are not meant to be compared - // ToDo: maybe fall-back to pointer comparison? - virtual bool operator== (const AST_Node& rhs) const { - throw std::runtime_error("operator== not implemented"); - } - - // We can give some reasonable implementations by using - // invert operators on the specialized implementations - virtual bool operator!= (const AST_Node& rhs) const { - // Unequal if not equal - return !(*this == rhs); - } - - ATTACH_ABSTRACT_AST_OPERATIONS(AST_Node); - ATTACH_ABSTRACT_CRTP_PERFORM_METHODS() - }; - inline AST_Node::~AST_Node() { } - - ////////////////////////////////////////////////////////////////////// - // define cast template now (need complete type) - ////////////////////////////////////////////////////////////////////// - - template - T* Cast(AST_Node* ptr) { - return ptr && typeid(T) == typeid(*ptr) ? - static_cast(ptr) : NULL; - }; - - template - const T* Cast(const AST_Node* ptr) { - return ptr && typeid(T) == typeid(*ptr) ? - static_cast(ptr) : NULL; - }; - - ////////////////////////////////////////////////////////////////////// - // Abstract base class for expressions. This side of the AST hierarchy - // represents elements in value contexts, which exist primarily to be - // evaluated and returned. - ////////////////////////////////////////////////////////////////////// - class Expression : public AST_Node { - public: - enum Type { - NONE, - BOOLEAN, - NUMBER, - COLOR, - STRING, - LIST, - MAP, - SELECTOR, - NULL_VAL, - FUNCTION_VAL, - C_WARNING, - C_ERROR, - FUNCTION, - VARIABLE, - PARENT, - NUM_TYPES - }; - private: - // expressions in some contexts shouldn't be evaluated - ADD_PROPERTY(bool, is_delayed) - ADD_PROPERTY(bool, is_expanded) - ADD_PROPERTY(bool, is_interpolant) - ADD_PROPERTY(Type, concrete_type) - public: - Expression(SourceSpan pstate, bool d = false, bool e = false, bool i = false, Type ct = NONE); - virtual operator bool() { return true; } - virtual ~Expression() { } - virtual bool is_invisible() const { return false; } - - virtual sass::string type() const { return ""; } - static sass::string type_name() { return ""; } - - virtual bool is_false() { return false; } - // virtual bool is_true() { return !is_false(); } - virtual bool operator< (const Expression& rhs) const { return false; } - virtual bool operator== (const Expression& rhs) const { return false; } - inline bool operator>(const Expression& rhs) const { return rhs < *this; } - inline bool operator!=(const Expression& rhs) const { return !(rhs == *this); } - virtual bool eq(const Expression& rhs) const { return *this == rhs; }; - virtual void set_delayed(bool delayed) { is_delayed(delayed); } - virtual bool has_interpolant() const { return is_interpolant(); } - virtual bool is_left_interpolant() const { return is_interpolant(); } - virtual bool is_right_interpolant() const { return is_interpolant(); } - ATTACH_VIRTUAL_AST_OPERATIONS(Expression); - size_t hash() const override { return 0; } - }; - -} - -///////////////////////////////////////////////////////////////////////////////////// -// Hash method specializations for std::unordered_map to work with Sass::Expression -///////////////////////////////////////////////////////////////////////////////////// - -namespace std { - template<> - struct hash - { - size_t operator()(Sass::ExpressionObj s) const - { - return s->hash(); - } - }; - template<> - struct equal_to - { - bool operator()( Sass::ExpressionObj lhs, Sass::ExpressionObj rhs) const - { - return lhs->hash() == rhs->hash(); - } - }; -} - -namespace Sass { - - ///////////////////////////////////////////////////////////////////////////// - // Mixin class for AST nodes that should behave like vectors. Uses the - // "Template Method" design pattern to allow subclasses to adjust their flags - // when certain objects are pushed. - ///////////////////////////////////////////////////////////////////////////// - template - class Vectorized { - sass::vector elements_; - protected: - mutable size_t hash_; - void reset_hash() { hash_ = 0; } - virtual void adjust_after_pushing(T element) { } - public: - Vectorized(size_t s = 0) : hash_(0) - { elements_.reserve(s); } - Vectorized(sass::vector vec) : - elements_(std::move(vec)), - hash_(0) - {} - virtual ~Vectorized() = 0; - size_t length() const { return elements_.size(); } - bool empty() const { return elements_.empty(); } - void clear() { return elements_.clear(); } - T& last() { return elements_.back(); } - T& first() { return elements_.front(); } - const T& last() const { return elements_.back(); } - const T& first() const { return elements_.front(); } - - bool operator== (const Vectorized& rhs) const { - // Abort early if sizes do not match - if (length() != rhs.length()) return false; - // Otherwise test each node for object equalicy in order - return std::equal(begin(), end(), rhs.begin(), ObjEqualityFn); - } - - bool operator!= (const Vectorized& rhs) const { - return !(*this == rhs); - } - - T& operator[](size_t i) { return elements_[i]; } - virtual const T& at(size_t i) const { return elements_.at(i); } - virtual T& at(size_t i) { return elements_.at(i); } - const T& get(size_t i) const { return elements_[i]; } - const T& operator[](size_t i) const { return elements_[i]; } - - // Implicitly get the sass::vector from our object - // Makes the Vector directly assignable to sass::vector - // You are responsible to make a copy if needed - // Note: since this returns the real object, we can't - // Note: guarantee that the hash will not get out of sync - operator sass::vector&() { return elements_; } - operator const sass::vector&() const { return elements_; } - - // Explicitly request all elements as a real sass::vector - // You are responsible to make a copy if needed - // Note: since this returns the real object, we can't - // Note: guarantee that the hash will not get out of sync - sass::vector& elements() { return elements_; } - const sass::vector& elements() const { return elements_; } - - // Insert all items from compatible vector - void concat(const sass::vector& v) - { - if (!v.empty()) reset_hash(); - elements().insert(end(), v.begin(), v.end()); - } - - // Syntatic sugar for pointers - void concat(const Vectorized* v) - { - if (v != nullptr) { - return concat(*v); - } - } - - // Insert one item on the front - void unshift(T element) - { - reset_hash(); - elements_.insert(begin(), element); - } - - // Remove and return item on the front - // ToDo: handle empty vectors - T shift() { - reset_hash(); - T first = get(0); - elements_.erase(begin()); - return first; - } - - // Insert one item on the back - // ToDo: rename this to push - void append(T element) - { - reset_hash(); - elements_.insert(end(), element); - // ToDo: Mostly used by parameters and arguments - // ToDo: Find a more elegant way to support this - adjust_after_pushing(element); - } - - // Check if an item already exists - // Uses underlying object `operator==` - // E.g. compares the actual objects - bool contains(const T& el) const { - for (const T& rhs : elements_) { - // Test the underlying objects for equality - // A std::find checks for pointer equality - if (ObjEqualityFn(el, rhs)) { - return true; - } - } - return false; - } - - // This might be better implemented as `operator=`? - void elements(sass::vector e) { - reset_hash(); - elements_ = std::move(e); - } - - virtual size_t hash() const - { - if (hash_ == 0) { - for (const T& el : elements_) { - hash_combine(hash_, el->hash()); - } - } - return hash_; - } - - template - typename sass::vector::iterator insert(P position, const V& val) { - reset_hash(); - return elements_.insert(position, val); - } - - typename sass::vector::iterator end() { return elements_.end(); } - typename sass::vector::iterator begin() { return elements_.begin(); } - typename sass::vector::const_iterator end() const { return elements_.end(); } - typename sass::vector::const_iterator begin() const { return elements_.begin(); } - typename sass::vector::iterator erase(typename sass::vector::iterator el) { reset_hash(); return elements_.erase(el); } - typename sass::vector::const_iterator erase(typename sass::vector::const_iterator el) { reset_hash(); return elements_.erase(el); } - - }; - template - inline Vectorized::~Vectorized() { } - - ///////////////////////////////////////////////////////////////////////////// - // Mixin class for AST nodes that should behave like a hash table. Uses an - // extra internally to maintain insertion order for interation. - ///////////////////////////////////////////////////////////////////////////// - template - class Hashed { - private: - std::unordered_map< - K, T, ObjHash, ObjHashEquality - > elements_; - - sass::vector _keys; - sass::vector _values; - protected: - mutable size_t hash_; - K duplicate_key_; - void reset_hash() { hash_ = 0; } - void reset_duplicate_key() { duplicate_key_ = {}; } - virtual void adjust_after_pushing(std::pair p) { } - public: - Hashed(size_t s = 0) - : elements_(), - _keys(), - _values(), - hash_(0), duplicate_key_({}) - { - _keys.reserve(s); - _values.reserve(s); - elements_.reserve(s); - } - virtual ~Hashed(); - size_t length() const { return _keys.size(); } - bool empty() const { return _keys.empty(); } - bool has(K k) const { - return elements_.find(k) != elements_.end(); - } - T at(K k) const { - if (elements_.count(k)) - { - return elements_.at(k); - } - else { return {}; } - } - bool has_duplicate_key() const { return duplicate_key_ != nullptr; } - K get_duplicate_key() const { return duplicate_key_; } - const std::unordered_map< - K, T, ObjHash, ObjHashEquality - >& elements() { return elements_; } - Hashed& operator<<(std::pair p) - { - reset_hash(); - - if (!has(p.first)) { - _keys.push_back(p.first); - _values.push_back(p.second); - } - else if (!duplicate_key_) { - duplicate_key_ = p.first; - } - - elements_[p.first] = p.second; - - adjust_after_pushing(p); - return *this; - } - Hashed& operator+=(Hashed* h) - { - if (length() == 0) { - this->elements_ = h->elements_; - this->_values = h->_values; - this->_keys = h->_keys; - return *this; - } - - for (auto key : h->keys()) { - *this << std::make_pair(key, h->at(key)); - } - - reset_duplicate_key(); - return *this; - } - const std::unordered_map< - K, T, ObjHash, ObjHashEquality - >& pairs() const { return elements_; } - - const sass::vector& keys() const { return _keys; } - const sass::vector& values() const { return _values; } - -// std::unordered_map::iterator end() { return elements_.end(); } -// std::unordered_map::iterator begin() { return elements_.begin(); } -// std::unordered_map::const_iterator end() const { return elements_.end(); } -// std::unordered_map::const_iterator begin() const { return elements_.begin(); } - - }; - template - inline Hashed::~Hashed() { } - - ///////////////////////////////////////////////////////////////////////// - // Abstract base class for statements. This side of the AST hierarchy - // represents elements in expansion contexts, which exist primarily to be - // rewritten and macro-expanded. - ///////////////////////////////////////////////////////////////////////// - class Statement : public AST_Node { - public: - enum Type { - NONE, - RULESET, - MEDIA, - DIRECTIVE, - SUPPORTS, - ATROOT, - BUBBLE, - CONTENT, - KEYFRAMERULE, - DECLARATION, - ASSIGNMENT, - IMPORT_STUB, - IMPORT, - COMMENT, - WARNING, - RETURN, - EXTEND, - ERROR, - DEBUGSTMT, - WHILE, - EACH, - FOR, - IF - }; - private: - ADD_PROPERTY(Type, statement_type) - ADD_PROPERTY(size_t, tabs) - ADD_PROPERTY(bool, group_end) - public: - Statement(SourceSpan pstate, Type st = NONE, size_t t = 0); - virtual ~Statement() = 0; // virtual destructor - // needed for rearranging nested rulesets during CSS emission - virtual bool bubbles(); - virtual bool has_content(); - virtual bool is_invisible() const; - ATTACH_VIRTUAL_AST_OPERATIONS(Statement) - }; - inline Statement::~Statement() { } - - //////////////////////// - // Blocks of statements. - //////////////////////// - class Block final : public Statement, public Vectorized { - ADD_PROPERTY(bool, is_root) - // needed for properly formatted CSS emission - protected: - void adjust_after_pushing(Statement_Obj s) override {} - public: - Block(SourceSpan pstate, size_t s = 0, bool r = false); - bool isInvisible() const; - bool has_content() override; - ATTACH_AST_OPERATIONS(Block) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////////////////////////// - // Abstract base class for statements that contain blocks of statements. - //////////////////////////////////////////////////////////////////////// - class ParentStatement : public Statement { - ADD_PROPERTY(Block_Obj, block) - public: - ParentStatement(SourceSpan pstate, Block_Obj b); - ParentStatement(const ParentStatement* ptr); // copy constructor - virtual ~ParentStatement() = 0; // virtual destructor - virtual bool has_content() override; - }; - inline ParentStatement::~ParentStatement() { } - - ///////////////////////////////////////////////////////////////////////////// - // Rulesets (i.e., sets of styles headed by a selector and containing a block - // of style declarations. - ///////////////////////////////////////////////////////////////////////////// - class StyleRule final : public ParentStatement { - ADD_PROPERTY(SelectorListObj, selector) - ADD_PROPERTY(Selector_Schema_Obj, schema) - ADD_PROPERTY(bool, is_root); - public: - StyleRule(SourceSpan pstate, SelectorListObj s = {}, Block_Obj b = {}); - bool is_invisible() const override; - ATTACH_AST_OPERATIONS(StyleRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////// - // Bubble. - ///////////////// - class Bubble final : public Statement { - ADD_PROPERTY(Statement_Obj, node) - ADD_PROPERTY(bool, group_end) - public: - Bubble(SourceSpan pstate, Statement_Obj n, Statement_Obj g = {}, size_t t = 0); - bool bubbles() override; - ATTACH_AST_OPERATIONS(Bubble) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////// - // Trace. - ///////////////// - class Trace final : public ParentStatement { - ADD_CONSTREF(char, type) - ADD_CONSTREF(sass::string, name) - public: - Trace(SourceSpan pstate, sass::string n, Block_Obj b = {}, char type = 'm'); - ATTACH_AST_OPERATIONS(Trace) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////////////////////////////////////////////////////////////////// - // At-rules -- arbitrary directives beginning with "@" that may have an - // optional statement block. - /////////////////////////////////////////////////////////////////////// - class AtRule final : public ParentStatement { - ADD_CONSTREF(sass::string, keyword) - ADD_PROPERTY(SelectorListObj, selector) - ADD_PROPERTY(ExpressionObj, value) - public: - AtRule(SourceSpan pstate, sass::string kwd, SelectorListObj sel = {}, Block_Obj b = {}, ExpressionObj val = {}); - bool bubbles() override; - bool is_media(); - bool is_keyframes(); - ATTACH_AST_OPERATIONS(AtRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////////////////////////////////////////////////////////////////// - // Keyframe-rules -- the child blocks of "@keyframes" nodes. - /////////////////////////////////////////////////////////////////////// - class Keyframe_Rule final : public ParentStatement { - // according to css spec, this should be - // = | - ADD_PROPERTY(SelectorListObj, name) - public: - Keyframe_Rule(SourceSpan pstate, Block_Obj b); - ATTACH_AST_OPERATIONS(Keyframe_Rule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////////////////////////// - // Declarations -- style rules consisting of a property name and values. - //////////////////////////////////////////////////////////////////////// - class Declaration final : public ParentStatement { - ADD_PROPERTY(String_Obj, property) - ADD_PROPERTY(ExpressionObj, value) - ADD_PROPERTY(bool, is_important) - ADD_PROPERTY(bool, is_custom_property) - ADD_PROPERTY(bool, is_indented) - public: - Declaration(SourceSpan pstate, String_Obj prop, ExpressionObj val, bool i = false, bool c = false, Block_Obj b = {}); - bool is_invisible() const override; - ATTACH_AST_OPERATIONS(Declaration) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////////////////////////// - // Assignments -- variable and value. - ///////////////////////////////////// - class Assignment final : public Statement { - ADD_CONSTREF(sass::string, variable) - ADD_PROPERTY(ExpressionObj, value) - ADD_PROPERTY(bool, is_default) - ADD_PROPERTY(bool, is_global) - public: - Assignment(SourceSpan pstate, sass::string var, ExpressionObj val, bool is_default = false, bool is_global = false); - ATTACH_AST_OPERATIONS(Assignment) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////////////////////////////// - // Import directives. CSS and Sass import lists can be intermingled, so it's - // necessary to store a list of each in an Import node. - //////////////////////////////////////////////////////////////////////////// - class Import final : public Statement { - sass::vector urls_; - sass::vector incs_; - ADD_PROPERTY(List_Obj, import_queries); - public: - Import(SourceSpan pstate); - sass::vector& incs(); - sass::vector& urls(); - ATTACH_AST_OPERATIONS(Import) - ATTACH_CRTP_PERFORM_METHODS() - }; - - // not yet resolved single import - // so far we only know requested name - class Import_Stub final : public Statement { - Include resource_; - public: - Import_Stub(SourceSpan pstate, Include res); - Include resource(); - sass::string imp_path(); - sass::string abs_path(); - ATTACH_AST_OPERATIONS(Import_Stub) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ////////////////////////////// - // The Sass `@warn` directive. - ////////////////////////////// - class WarningRule final : public Statement { - ADD_PROPERTY(ExpressionObj, message) - public: - WarningRule(SourceSpan pstate, ExpressionObj msg); - ATTACH_AST_OPERATIONS(WarningRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////////////////////////// - // The Sass `@error` directive. - /////////////////////////////// - class ErrorRule final : public Statement { - ADD_PROPERTY(ExpressionObj, message) - public: - ErrorRule(SourceSpan pstate, ExpressionObj msg); - ATTACH_AST_OPERATIONS(ErrorRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////////////////////////// - // The Sass `@debug` directive. - /////////////////////////////// - class DebugRule final : public Statement { - ADD_PROPERTY(ExpressionObj, value) - public: - DebugRule(SourceSpan pstate, ExpressionObj val); - ATTACH_AST_OPERATIONS(DebugRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////////////////////////////////////// - // CSS comments. These may be interpolated. - /////////////////////////////////////////// - class Comment final : public Statement { - ADD_PROPERTY(String_Obj, text) - ADD_PROPERTY(bool, is_important) - public: - Comment(SourceSpan pstate, String_Obj txt, bool is_important); - virtual bool is_invisible() const override; - ATTACH_AST_OPERATIONS(Comment) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////// - // The Sass `@if` control directive. - //////////////////////////////////// - class If final : public ParentStatement { - ADD_PROPERTY(ExpressionObj, predicate) - ADD_PROPERTY(Block_Obj, alternative) - public: - If(SourceSpan pstate, ExpressionObj pred, Block_Obj con, Block_Obj alt = {}); - virtual bool has_content() override; - ATTACH_AST_OPERATIONS(If) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////////////////////////// - // The Sass `@for` control directive. - ///////////////////////////////////// - class ForRule final : public ParentStatement { - ADD_CONSTREF(sass::string, variable) - ADD_PROPERTY(ExpressionObj, lower_bound) - ADD_PROPERTY(ExpressionObj, upper_bound) - ADD_PROPERTY(bool, is_inclusive) - public: - ForRule(SourceSpan pstate, sass::string var, ExpressionObj lo, ExpressionObj hi, Block_Obj b, bool inc); - ATTACH_AST_OPERATIONS(ForRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ////////////////////////////////////// - // The Sass `@each` control directive. - ////////////////////////////////////// - class EachRule final : public ParentStatement { - ADD_PROPERTY(sass::vector, variables) - ADD_PROPERTY(ExpressionObj, list) - public: - EachRule(SourceSpan pstate, sass::vector vars, ExpressionObj lst, Block_Obj b); - ATTACH_AST_OPERATIONS(EachRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////////////////////////////////// - // The Sass `@while` control directive. - /////////////////////////////////////// - class WhileRule final : public ParentStatement { - ADD_PROPERTY(ExpressionObj, predicate) - public: - WhileRule(SourceSpan pstate, ExpressionObj pred, Block_Obj b); - ATTACH_AST_OPERATIONS(WhileRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////////////////////////////////////////////////// - // The @return directive for use inside SassScript functions. - ///////////////////////////////////////////////////////////// - class Return final : public Statement { - ADD_PROPERTY(ExpressionObj, value) - public: - Return(SourceSpan pstate, ExpressionObj val); - ATTACH_AST_OPERATIONS(Return) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////////////////////////////////////////////////////////////////// - // Definitions for both mixins and functions. The two cases are distinguished - // by a type tag. - ///////////////////////////////////////////////////////////////////////////// - class Definition final : public ParentStatement { - public: - enum Type { MIXIN, FUNCTION }; - ADD_CONSTREF(sass::string, name) - ADD_PROPERTY(Parameters_Obj, parameters) - ADD_PROPERTY(Env*, environment) - ADD_PROPERTY(Type, type) - ADD_PROPERTY(Native_Function, native_function) - ADD_PROPERTY(Sass_Function_Entry, c_function) - ADD_PROPERTY(void*, cookie) - ADD_PROPERTY(bool, is_overload_stub) - ADD_PROPERTY(Signature, signature) - public: - Definition(SourceSpan pstate, - sass::string n, - Parameters_Obj params, - Block_Obj b, - Type t); - Definition(SourceSpan pstate, - Signature sig, - sass::string n, - Parameters_Obj params, - Native_Function func_ptr, - bool overload_stub = false); - Definition(SourceSpan pstate, - Signature sig, - sass::string n, - Parameters_Obj params, - Sass_Function_Entry c_func); - ATTACH_AST_OPERATIONS(Definition) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ////////////////////////////////////// - // Mixin calls (i.e., `@include ...`). - ////////////////////////////////////// - class Mixin_Call final : public ParentStatement { - ADD_CONSTREF(sass::string, name) - ADD_PROPERTY(Arguments_Obj, arguments) - ADD_PROPERTY(Parameters_Obj, block_parameters) - public: - Mixin_Call(SourceSpan pstate, sass::string n, Arguments_Obj args, Parameters_Obj b_params = {}, Block_Obj b = {}); - ATTACH_AST_OPERATIONS(Mixin_Call) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////////////////////////////////////////////// - // The @content directive for mixin content blocks. - /////////////////////////////////////////////////// - class Content final : public Statement { - ADD_PROPERTY(Arguments_Obj, arguments) - public: - Content(SourceSpan pstate, Arguments_Obj args); - ATTACH_AST_OPERATIONS(Content) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////////////////////////////// - // Arithmetic negation (logical negation is just an ordinary function call). - //////////////////////////////////////////////////////////////////////////// - class Unary_Expression final : public Expression { - public: - enum Type { PLUS, MINUS, NOT, SLASH }; - private: - HASH_PROPERTY(Type, optype) - HASH_PROPERTY(ExpressionObj, operand) - mutable size_t hash_; - public: - Unary_Expression(SourceSpan pstate, Type t, ExpressionObj o); - const sass::string type_name(); - virtual bool operator==(const Expression& rhs) const override; - size_t hash() const override; - ATTACH_AST_OPERATIONS(Unary_Expression) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////////////// - // Individual argument objects for mixin and function calls. - //////////////////////////////////////////////////////////// - class Argument final : public Expression { - HASH_PROPERTY(ExpressionObj, value) - HASH_CONSTREF(sass::string, name) - ADD_PROPERTY(bool, is_rest_argument) - ADD_PROPERTY(bool, is_keyword_argument) - mutable size_t hash_; - public: - Argument(SourceSpan pstate, ExpressionObj val, sass::string n = "", bool rest = false, bool keyword = false); - void set_delayed(bool delayed) override; - bool operator==(const Expression& rhs) const override; - size_t hash() const override; - ATTACH_AST_OPERATIONS(Argument) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////////////////////////// - // Argument lists -- in their own class to facilitate context-sensitive - // error checking (e.g., ensuring that all ordinal arguments precede all - // named arguments). - //////////////////////////////////////////////////////////////////////// - class Arguments final : public Expression, public Vectorized { - ADD_PROPERTY(bool, has_named_arguments) - ADD_PROPERTY(bool, has_rest_argument) - ADD_PROPERTY(bool, has_keyword_argument) - protected: - void adjust_after_pushing(Argument_Obj a) override; - public: - Arguments(SourceSpan pstate); - void set_delayed(bool delayed) override; - Argument_Obj get_rest_argument(); - Argument_Obj get_keyword_argument(); - ATTACH_AST_OPERATIONS(Arguments) - ATTACH_CRTP_PERFORM_METHODS() - }; - - - // A Media StyleRule before it has been evaluated - // Could be already final or an interpolation - class MediaRule final : public ParentStatement { - ADD_PROPERTY(List_Obj, schema) - public: - MediaRule(SourceSpan pstate, Block_Obj block = {}); - - bool bubbles() override { return true; }; - bool is_invisible() const override { return false; }; - ATTACH_AST_OPERATIONS(MediaRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - // A Media StyleRule after it has been evaluated - // Representing the static or resulting css - class CssMediaRule final : public ParentStatement, - public Vectorized { - public: - CssMediaRule(SourceSpan pstate, Block_Obj b); - bool bubbles() override { return true; }; - bool isInvisible() const { return empty(); } - bool is_invisible() const override { return false; }; - - public: - // Hash and equality implemtation from vector - size_t hash() const override { return Vectorized::hash(); } - // Check if two instances are considered equal - bool operator== (const CssMediaRule& rhs) const { - return Vectorized::operator== (rhs); - } - bool operator!=(const CssMediaRule& rhs) const { - // Invert from equality - return !(*this == rhs); - } - - ATTACH_AST_OPERATIONS(CssMediaRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - // Media Queries after they have been evaluated - // Representing the static or resulting css - class CssMediaQuery final : public AST_Node { - - // The modifier, probably either "not" or "only". - // This may be `null` if no modifier is in use. - ADD_PROPERTY(sass::string, modifier); - - // The media type, for example "screen" or "print". - // This may be `null`. If so, [features] will not be empty. - ADD_PROPERTY(sass::string, type); - - // Feature queries, including parentheses. - ADD_PROPERTY(sass::vector, features); - - public: - CssMediaQuery(SourceSpan pstate); - - // Check if two instances are considered equal - bool operator== (const CssMediaQuery& rhs) const; - bool operator!=(const CssMediaQuery& rhs) const { - // Invert from equality - return !(*this == rhs); - } - - // Returns true if this query is empty - // Meaning it has no type and features - bool empty() const { - return type_.empty() - && modifier_.empty() - && features_.empty(); - } - - // Whether this media query matches all media types. - bool matchesAllTypes() const { - return type_.empty() || Util::equalsLiteral("all", type_); - } - - // Merges this with [other] and adds a query that matches the intersection - // of both inputs to [result]. Returns false if the result is unrepresentable - CssMediaQuery_Obj merge(CssMediaQuery_Obj& other); - - ATTACH_AST_OPERATIONS(CssMediaQuery) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////// - // Media queries (replaced by MediaRule at al). - // ToDo: only used for interpolation case - //////////////////////////////////////////////////// - class Media_Query final : public Expression, - public Vectorized { - ADD_PROPERTY(String_Obj, media_type) - ADD_PROPERTY(bool, is_negated) - ADD_PROPERTY(bool, is_restricted) - public: - Media_Query(SourceSpan pstate, String_Obj t = {}, size_t s = 0, bool n = false, bool r = false); - ATTACH_AST_OPERATIONS(Media_Query) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////// - // Media expressions (for use inside media queries). - // ToDo: only used for interpolation case - //////////////////////////////////////////////////// - class Media_Query_Expression final : public Expression { - ADD_PROPERTY(ExpressionObj, feature) - ADD_PROPERTY(ExpressionObj, value) - ADD_PROPERTY(bool, is_interpolated) - public: - Media_Query_Expression(SourceSpan pstate, ExpressionObj f, ExpressionObj v, bool i = false); - ATTACH_AST_OPERATIONS(Media_Query_Expression) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////////////////////////////////////// - // At root expressions (for use inside @at-root). - ///////////////////////////////////////////////// - class At_Root_Query final : public Expression { - private: - ADD_PROPERTY(ExpressionObj, feature) - ADD_PROPERTY(ExpressionObj, value) - public: - At_Root_Query(SourceSpan pstate, ExpressionObj f = {}, ExpressionObj v = {}, bool i = false); - bool exclude(sass::string str); - ATTACH_AST_OPERATIONS(At_Root_Query) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////// - // At-root. - /////////// - class AtRootRule final : public ParentStatement { - ADD_PROPERTY(At_Root_Query_Obj, expression) - public: - AtRootRule(SourceSpan pstate, Block_Obj b = {}, At_Root_Query_Obj e = {}); - bool bubbles() override; - bool exclude_node(Statement_Obj s); - ATTACH_AST_OPERATIONS(AtRootRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////////////////////////////////////////////// - // Individual parameter objects for mixins and functions. - ///////////////////////////////////////////////////////// - class Parameter final : public AST_Node { - ADD_CONSTREF(sass::string, name) - ADD_PROPERTY(ExpressionObj, default_value) - ADD_PROPERTY(bool, is_rest_parameter) - public: - Parameter(SourceSpan pstate, sass::string n, ExpressionObj def = {}, bool rest = false); - ATTACH_AST_OPERATIONS(Parameter) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////////////////////////////////////////////////////////////// - // Parameter lists -- in their own class to facilitate context-sensitive - // error checking (e.g., ensuring that all optional parameters follow all - // required parameters). - ///////////////////////////////////////////////////////////////////////// - class Parameters final : public AST_Node, public Vectorized { - ADD_PROPERTY(bool, has_optional_parameters) - ADD_PROPERTY(bool, has_rest_parameter) - protected: - void adjust_after_pushing(Parameter_Obj p) override; - public: - Parameters(SourceSpan pstate); - ATTACH_AST_OPERATIONS(Parameters) - ATTACH_CRTP_PERFORM_METHODS() - }; - -} - -#include "ast_values.hpp" -#include "ast_supports.hpp" +#include "ast_fwd_decl.hpp" +#include "ast_helpers.hpp" +#include "ast_nodes.hpp" #include "ast_selectors.hpp" - -#ifdef __clang__ - -// #pragma clang diagnostic pop -// #pragma clang diagnostic push - -#endif +#include "ast_statements.hpp" +#include "ast_supports.hpp" +#include "ast_values.hpp" #endif diff --git a/src/ast2.hpp b/src/ast2.hpp new file mode 100644 index 0000000000..2d6ef1bef5 --- /dev/null +++ b/src/ast2.hpp @@ -0,0 +1,1560 @@ +#ifndef SASS_AST_H +#define SASS_AST_H + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include +#include +#include + +#include "sass/base.h" +#include "ast_helpers.hpp" +#include "ast_fwd_decl.hpp" +#include "ast_def_macros.hpp" +#include "ordered_map.hpp" + +#include "file.hpp" +#include "memory.hpp" +#include "mapping.hpp" +#include "position.hpp" +#include "operation.hpp" +#include "environment.hpp" +#include "fn_utils.hpp" +#include "environment_stack.hpp" + +#include "ordered-map/ordered_map.h" + +namespace Sass { + + uint8_t sass_op_to_precedence(enum Sass_OP op); + + const char* sass_op_to_name(enum Sass_OP op); + + const char* sass_op_separator(enum Sass_OP op); + + ////////////////////////////////////////////////////////// + // Abstract base class for all abstract syntax tree nodes. + ////////////////////////////////////////////////////////// + class AST_Node : public SharedObj { + ADD_PROPERTY(SourceSpan, pstate); + public: + AST_Node(const SourceSpan& pstate) + : SharedObj(), pstate_(pstate) + { } + AST_Node(SourceSpan&& pstate) + : SharedObj(), pstate_(std::move(pstate)) + { } + AST_Node(const AST_Node* ptr) + : pstate_(ptr->pstate_) + { } + + // void* operator new(size_t nbytes) { + // return ::operator new(nbytes); + // } + // + // void operator delete(void* ptr) { + // return ::operator delete(ptr); + // } + + // allow implicit conversion to string + // needed for by SharedPtr implementation + operator sass::string() { + return to_string(); + } + + // AST_Node(AST_Node& ptr) = delete; + + virtual ~AST_Node() = 0; + virtual size_t hash() const { return 0; } + virtual sass::string inspect() const { return to_string({ INSPECT, 5 }); } + virtual sass::string to_sass() const { return to_string({ TO_SASS, 5 }); } + virtual sass::string to_string(Sass_Inspect_Options opt) const; + virtual sass::string to_css(Sass_Inspect_Options opt, bool quotes = false) const; + virtual sass::string to_css(Sass_Inspect_Options opt, sass::vector& mappings, bool quotes = false) const; + virtual sass::string to_string() const; + virtual sass::string to_css(sass::vector& mappings, bool quotes = false) const; + virtual sass::string to_css(bool quotes = false) const; + virtual void cloneChildren() {}; + // generic find function (not fully implemented yet) + // ToDo: add specific implementions to all children + virtual bool find ( bool (*f)(AST_Node_Obj) ) { return f(this); }; + // Offset off() { return pstate(); } + // Position pos() { return pstate(); } + + // Subclasses should only override these methods + // The full set is emulated by calling only those + // Make sure the left side is resonably upcasted! + virtual bool operator< (const AST_Node& rhs) const { + throw std::runtime_error("operator< not implemented"); + } + virtual bool operator== (const AST_Node& rhs) const { + throw std::runtime_error("operator== not implemented"); + } + + + // We can give some reasonable implementations by using + // inverst operators on the specialized implementations + virtual bool operator>(const AST_Node& rhs) const { return rhs < *this; }; + virtual bool operator<=(const AST_Node& rhs) const { return !(rhs < *this); }; + virtual bool operator>=(const AST_Node& rhs) const { return !(*this < rhs); }; + virtual bool operator!=(const AST_Node& rhs) const { return !(*this == rhs); } + + ATTACH_ABSTRACT_COPY_OPERATIONS(AST_Node); + ATTACH_ABSTRACT_CRTP_PERFORM_METHODS() + }; + inline AST_Node::~AST_Node() { } + + ////////////////////////////////////////////////////////////////////// + // define cast template now (need complete type) + ////////////////////////////////////////////////////////////////////// + + template + T* Cast(AST_Node* ptr) { + return ptr && typeid(T) == typeid(*ptr) ? + static_cast(ptr) : NULL; + }; + + template + const T* Cast(const AST_Node* ptr) { + return ptr && typeid(T) == typeid(*ptr) ? + static_cast(ptr) : NULL; + }; + + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// + + class SassNode : public AST_Node { + public: + SassNode(const SourceSpan& pstate) : + AST_Node(pstate) {}; + SassNode(SourceSpan&& pstate) : + AST_Node(std::move(pstate)) {}; + ATTACH_VIRTUAL_COPY_OPERATIONS(SassNode); + ATTACH_CRTP_PERFORM_METHODS(); + }; + + class CallableInvocation { + // The arguments passed to the callable. + ADD_PROPERTY(ArgumentInvocationObj, arguments); + public: + CallableInvocation( + ArgumentInvocation* arguments) : + arguments_(arguments) {} + }; + + + class ArgumentDeclaration : public SassNode { + + // The arguments that are taken. + ADD_CONSTREF(sass::vector, arguments); + + // The name of the rest argument (as in `$args...`), + // or `null` if none was declared. + ADD_CONSTREF(sass::string, restArg); + + public: + + ArgumentDeclaration( + const SourceSpan& pstate, + const sass::vector& arguments, + const sass::string& restArg = ""); + + bool isEmpty() const { + return arguments_.empty() + && restArg_.empty(); + } + + static ArgumentDeclaration* parse( + Context& context, + const sass::string& contents); + + void verify( + size_t positional, + const KeywordMap& names, + const SourceSpan& pstate, + const Backtraces& traces); + + bool matches( + size_t positional, + const KeywordMap& names); + + sass::string toString2() const; + + }; + + + /// The result of evaluating arguments to a function or mixin. + class ArgumentResults2 { + + // Arguments passed by position. + ADD_REF(sass::vector, positional); + + // The [AstNode]s that hold the spans for each [positional] + // argument, or `null` if source span tracking is disabled. This + // stores [AstNode]s rather than [FileSpan]s so it can avoid + // calling [AstNode.span] if the span isn't required, since + // some nodes need to do real work to manufacture a source span. + // sass::vector positionalNodes; + + // Arguments passed by name. + // A list implementation might be more efficient + // I dont expect any function to have many arguments + // Normally the tradeoff is around 8 items in the list + ADD_REF(KeywordMap, named); + + // The [AstNode]s that hold the spans for each [named] argument, + // or `null` if source span tracking is disabled. This stores + // [AstNode]s rather than [FileSpan]s so it can avoid calling + // [AstNode.span] if the span isn't required, since some nodes + // need to do real work to manufacture a source span. + // KeywordMap namedNodes; + + // The separator used for the rest argument list, if any. + ADD_REF(Sass_Separator, separator); + + public: + + ArgumentResults2() {}; + + ArgumentResults2( + const ArgumentResults2& other); + + ArgumentResults2( + ArgumentResults2&& other); + + ArgumentResults2& operator=( + ArgumentResults2&& other); + + ArgumentResults2( + const sass::vector& positional, + const KeywordMap& named, + Sass_Separator separator); + + ArgumentResults2( + sass::vector && positional, + KeywordMap&& named, + Sass_Separator separator); + + }; + + + class ArgumentInvocation : public SassNode { + + // The arguments passed by position. + ADD_REF(sass::vector, positional); + public: + // + ArgumentResults2 evaluated; + + // The arguments passed by name. + ADD_CONSTREF(KeywordMap, named); + + // The first rest argument (as in `$args...`). + ADD_PROPERTY(ExpressionObj, restArg); + + // The second rest argument, which is expected to only contain a keyword map. + ADD_PROPERTY(ExpressionObj, kwdRest); + + public: + + ArgumentInvocation(const SourceSpan& pstate, + const sass::vector& positional, + const KeywordMap& named, + Expression* restArgs = nullptr, + Expression* kwdRest = nullptr); + + // Returns whether this invocation passes no arguments. + bool isEmpty() const; + + sass::string toString() const; + + ATTACH_CRTP_PERFORM_METHODS(); + + }; + + ////////////////////////////////////////////////////////////////////// + // Abstract base class for expressions. This side of the AST hierarchy + // represents elements in value contexts, which exist primarily to be + // evaluated and returned. + ////////////////////////////////////////////////////////////////////// + class Expression : public SassNode { + public: + enum Type { + NONE, + BOOLEAN, + NUMBER, + COLOR, + STRING, + LIST, + MAP, + NULL_VAL, + FUNCTION_VAL, + C_WARNING, + C_ERROR, + FUNCTION, + VARIABLE, + PARENT, + NUM_TYPES + }; + private: + // expressions in some contexts shouldn't be evaluated + ADD_PROPERTY(Type, concrete_type) + public: + Expression(SourceSpan&& pstate, bool d = false, bool e = false, bool i = false, Type ct = NONE); + Expression(const SourceSpan& pstate, bool d = false, bool e = false, bool i = false, Type ct = NONE); + virtual operator bool() { return true; } + virtual ~Expression() { } + virtual bool is_invisible() const { return false; } + + virtual const sass::string& type() const { + throw std::runtime_error("Invalid reflection"); + } + + virtual Expression* withoutSlash() { + return this; + } + + virtual Expression* removeSlash() { + return this; + } + + virtual bool is_false() { return false; } + // virtual bool is_true() { return !is_false(); } + virtual bool operator== (const Expression& rhs) const { return false; } + inline bool operator!=(const Expression& rhs) const { return !(rhs == *this); } + ATTACH_VIRTUAL_COPY_OPERATIONS(Expression); + size_t hash() const override { return 0; } + }; + +} + +///////////////////////////////////////////////////////////////////////////////////// +// Hash method specializations for std::unordered_map to work with Sass::Expression +///////////////////////////////////////////////////////////////////////////////////// + +namespace std { + template<> + struct hash + { + size_t operator()(Sass::Expression_Obj s) const + { + return s->hash(); + } + }; + template<> + struct equal_to + { + bool operator()( Sass::Expression_Obj lhs, Sass::Expression_Obj rhs) const + { + return lhs->hash() == rhs->hash(); + } + }; +} + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////////// + // Mixin class for AST nodes that should behave like vectors. Uses the + // "Template Method" design pattern to allow subclasses to adjust their flags + // when certain objects are pushed. + ///////////////////////////////////////////////////////////////////////////// + template + class Vectorized { + sass::vector elements_; + protected: + mutable size_t hash_; + void reset_hash() { hash_ = 0; } + public: + Vectorized(size_t s = 0) : hash_(0) + { elements_.reserve(s); } + Vectorized(const Vectorized* vec) : + elements_(vec->elements_), + hash_(0) + {} + Vectorized(sass::vector vec) : + elements_(std::move(vec)), + hash_(0) + {} + ~Vectorized() {}; + size_t length() const { return elements_.size(); } + bool empty() const { return elements_.empty(); } + void clear() { return elements_.clear(); } + T& last() { return elements_.back(); } + T& first() { return elements_.front(); } + const T& last() const { return elements_.back(); } + const T& first() const { return elements_.front(); } + + bool operator== (const Vectorized& rhs) const { + // Abort early if sizes do not match + if (length() != rhs.length()) return false; + // Otherwise test each node for object equalicy in order + return std::equal(begin(), end(), rhs.begin(), ObjEqualityFn); + } + + bool operator!= (const Vectorized& rhs) const { + return !(*this == rhs); + } + + T& operator[](size_t i) { return elements_[i]; } + virtual const T& at(size_t i) const { return elements_.at(i); } + virtual T& at(size_t i) { return elements_.at(i); } + const T& get(size_t i) const { return elements_[i]; } + // ToDo: might insert am item (update ordered list) + const T& operator[](size_t i) const { return elements_[i]; } + + // Implicitly get the std::vector from our object + // Makes the Vector directly assignable to std::vector + // You are responsible to make a copy if needed + // Note: since this returns the real object, we can't + // Note: guarantee that the hash will not get out of sync + operator sass::vector&() { return elements_; } + operator const sass::vector&() const { return elements_; } + + // Explicitly request all elements as a real std::vector + // You are responsible to make a copy if needed + // Note: since this returns the real object, we can't + // Note: guarantee that the hash will not get out of sync + sass::vector& elements() { return elements_; } + const sass::vector& elements() const { return elements_; } + + // Insert all items from compatible vector + void concat(const sass::vector& v) + { + if (!v.empty()) reset_hash(); + elements().insert(end(), v.begin(), v.end()); + } + + // Insert all items from compatible vector + void concat(sass::vector&& v) + { + if (!v.empty()) reset_hash(); + std::move(v.begin(), v.end(), + std::back_inserter(elements_)); + // elements().insert(end(), v.begin(), v.end()); + } + + + // Syntatic sugar for pointers + void concat(const Vectorized* v) + { + if (v != nullptr) { + return concat(*v); + } + } + + // Insert one item on the front + void unshift(T element) + { + reset_hash(); + elements_.insert(begin(), element); + } + + // Remove and return item on the front + // ToDo: handle empty vectors + T shift() { + reset_hash(); + T first = get(0); + elements_.erase(begin()); + return first; + } + + // Insert one item on the back + // ToDo: rename this to push + void append(T element) + { + if (!element) { + std::cerr << "APPEND NULL PTR\n"; + } + reset_hash(); + elements_.emplace_back(element); + } + + // Check if an item already exists + // Uses underlying object `operator==` + // E.g. compares the actual objects + bool contains(const T& el) const { + for (const T& rhs : elements_) { + // Test the underlying objects for equality + // A std::find checks for pointer equality + if (ObjEqualityFn(el, rhs)) { + return true; + } + } + return false; + } + + // This might be better implemented as `operator=`? + void elements(sass::vector e) { + reset_hash(); + elements_ = std::move(e); + } + + virtual size_t hash() const + { + if (hash_ == 0) { + for (const T& el : elements_) { + hash_combine(hash_, el->hash()); + } + } + return hash_; + } + + template + typename sass::vector::iterator insert(P position, const V& val) { + reset_hash(); + return elements_.insert(position, val); + } + + typename sass::vector::iterator end() { return elements_.end(); } + typename sass::vector::iterator begin() { return elements_.begin(); } + typename sass::vector::const_iterator end() const { return elements_.end(); } + typename sass::vector::const_iterator begin() const { return elements_.begin(); } + typename sass::vector::iterator erase(typename sass::vector::iterator el) { reset_hash(); return elements_.erase(el); } + typename sass::vector::const_iterator erase(typename sass::vector::const_iterator el) { reset_hash(); return elements_.erase(el); } + + }; + + ///////////////////////////////////////////////////////////////////////////// + // Mixin class for AST nodes that should behave like a hash table. Uses an + // extra internally to maintain insertion order for interation. + ///////////////////////////////////////////////////////////////////////////// + template + class Hashed { + + + using ordered_map_type = typename tsl::ordered_map< + K, T, ObjHash, ObjEquality, + SassAllocator>, + sass::vector> + >; + + protected: + + ordered_map_type elements_; + + mutable size_t hash_; + + void reset_hash() { hash_ = 0; } + + public: + + Hashed() + : elements_(), + hash_(0) + { + // elements_.reserve(s); + } + + // Copy constructor + Hashed(const Hashed& copy) : + elements_(), hash_(0) + { + // this seems to expensive!? + // elements_.reserve(copy.size()); + elements_ = copy.elements_; + }; + + // Move constructor + Hashed(Hashed&& move) : + elements_(std::move(move.elements_)), + hash_(move.hash_) {}; + + virtual ~Hashed(); + + size_t size() const { return elements_.size(); } + bool empty() const { return elements_.empty(); } + + bool has(K k) const { + return elements_.count(k) == 1; + } + + void reserve(size_t size) + { + elements_.reserve(size); + } + + T at(K k) const { + auto it = elements_.find(k); + if (it == elements_.end()) return {}; + else return it->second; + } + + bool erase(K key) + { + reset_hash(); + return elements_.erase(key); + } + + void set(std::pair& kv) + { + reset_hash(); + elements_[kv.first] = kv.second; + } + + void insert(K key, T val) + { + reset_hash(); + elements_[key] = val; + } + + void concat(Hashed arr) + { + reset_hash(); + for (const auto& kv : arr) { + elements_[kv.first] = kv.second; + } + // elements_.append(arr.elements()); + } + + // Return unmodifiable reference + const ordered_map_type& elements() const { + return elements_; + } + + const sass::vector keys() const { + sass::vector list; + for (auto kv : elements_) { + list.emplace_back(kv.first); + } + return list; + } + const sass::vector values() const { + sass::vector list; + for (auto kv : elements_) { + list.emplace_back(kv.second); + } + return list; + } + + typename ordered_map_type::iterator end() { return elements_.end(); } + typename ordered_map_type::iterator begin() { return elements_.begin(); } + typename ordered_map_type::const_iterator end() const { return elements_.end(); } + typename ordered_map_type::const_iterator begin() const { return elements_.begin(); } + + }; + + template + inline Hashed::~Hashed() { } + + ///////////////////////////////////////////////////////////////////////// + // Abstract base class for statements. This side of the AST hierarchy + // represents elements in expansion contexts, which exist primarily to be + // rewritten and macro-expanded. + ///////////////////////////////////////////////////////////////////////// + class Statement : public AST_Node { + private: + ADD_PROPERTY(size_t, tabs) + ADD_PROPERTY(bool, group_end) + public: + Statement(SourceSpan&& pstate, size_t t = 0); + Statement(const SourceSpan& pstate, size_t t = 0); + virtual ~Statement() = 0; // virtual destructor + // needed for rearranging nested rulesets during CSS emission + virtual bool bubbles() const; + virtual bool has_content(); + virtual bool is_invisible() const; + ATTACH_VIRTUAL_COPY_OPERATIONS(Statement) + }; + inline Statement::~Statement() { } + + //////////////////////// + // Blocks of statements. + //////////////////////// + class Block final : public Statement, public Vectorized { + ADD_POINTER(IDXS*, idxs); + ADD_PROPERTY(bool, is_root); + // needed for properly formatted CSS emission + public: + Block(const SourceSpan& pstate, size_t s = 0, bool r = false); + Block(const SourceSpan& pstate, const sass::vector& vec, bool r = false); + Block(const SourceSpan& pstate, sass::vector&& vec, bool r = false); + bool isInvisible() const; + bool is_invisible() const override { + return isInvisible(); + } + bool has_content() override; + // ATTACH_CLONE_OPERATIONS(Block) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////////////////////////////// + // Abstract base class for statements that contain blocks of statements. + //////////////////////////////////////////////////////////////////////// + + // [X] AtRootRule + // [X] AtRule + // [X] CallableDeclaration + // [X] Declaration + // [X] EachRule + // [X] ForRule + // [X] MediaRule + // [X] StyleRule + // [ ] Stylesheet + // [X] SupportsRule + // [X] WhileRule + class ParentStatement : public Statement { + ADD_PROPERTY(Block_Obj, block) + public: + void concat(const sass::vector& vec) { + if (block_.ptr() == nullptr) { + block_ = SASS_MEMORY_NEW(Block, pstate_); + } + block_->concat(vec); + } + void concat(sass::vector&& vec) { + if (block_.ptr() == nullptr) { + block_ = SASS_MEMORY_NEW(Block, pstate_); + } + block_->concat(std::move(vec)); + } + ParentStatement(SourceSpan&& pstate, Block_Obj b); + ParentStatement(const SourceSpan& pstate, Block_Obj b); + ParentStatement(const ParentStatement* ptr); // copy constructor + virtual ~ParentStatement() = 0; // virtual destructor + virtual bool has_content() override; + }; + inline ParentStatement::~ParentStatement() { } + + ///////////////////////////////////////////////////////////////////////////// + // A style rule. This applies style declarations to elements + // that match a given selector. Formerly known as `Ruleset`. + ///////////////////////////////////////////////////////////////////////////// + class StyleRule final : public ParentStatement { + // The selector to which the declaration will be applied. + // This is only parsed after the interpolation has been resolved. + ADD_PROPERTY(InterpolationObj, interpolation); + ADD_POINTER(IDXS*, idxs); + public: + StyleRule(SourceSpan&& pstate, Interpolation* s, Block_Obj b = {}); + bool empty() const { return block().isNull() || block()->empty(); } + // ATTACH_CLONE_OPERATIONS(StyleRule) + ATTACH_CRTP_PERFORM_METHODS() + }; + + // ToDo: ParentStatement + /////////////////////////////////////////////////////////////////////// + // At-rules -- arbitrary directives beginning with "@" that may have an + // optional statement block. + /////////////////////////////////////////////////////////////////////// + class AtRule final : public ParentStatement { + ADD_PROPERTY(InterpolationObj, name); + ADD_PROPERTY(InterpolationObj, value); + public: + AtRule(const SourceSpan& pstate, + InterpolationObj name, + ExpressionObj value, + Block_Obj b = {}); + ATTACH_CLONE_OPERATIONS(AtRule); + ATTACH_CRTP_PERFORM_METHODS(); + }; + + ///////////////// + // Bubble. + ///////////////// + class Bubble final : public Statement { + ADD_PROPERTY(Statement_Obj, node) + ADD_PROPERTY(bool, group_end) + public: + Bubble(const SourceSpan& pstate, Statement_Obj n, Statement_Obj g = {}, size_t t = 0); + bool bubbles() const override final; + // ATTACH_CLONE_OPERATIONS(Bubble) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ///////////////// + // Trace. + ///////////////// + class Trace final : public ParentStatement { + ADD_CONSTREF(char, type) + ADD_CONSTREF(sass::string, name) + public: + Trace(const SourceSpan& pstate, const sass::string& name, Block_Obj b = {}, char type = 'm'); + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + // An expression that directly embeds a [Value]. This is never + // constructed by the parser. It's only used when ASTs are + // constructed dynamically, as for the `call()` function. + class ValueExpression : public Expression { + ADD_PROPERTY(ValueObj, value); + public: + ValueExpression( + const SourceSpan& pstate, + ValueObj value); + ATTACH_CRTP_PERFORM_METHODS(); + }; + + class ListExpression : public Expression { + ADD_CONSTREF(sass::vector, contents); + ADD_PROPERTY(Sass_Separator, separator); + ADD_PROPERTY(bool, hasBrackets); + public: + ListExpression(const SourceSpan& pstate, Sass_Separator separator = SASS_UNDEF); + void concat(sass::vector& expressions) { + std::copy( + expressions.begin(), expressions.end(), + std::back_inserter(contents_) + ); + } + size_t size() const { + return contents_.size(); + } + Expression* get(size_t i) { + return contents_[i]; + } + void append(Expression* expression) { + contents_.emplace_back(expression); + } + sass::string toString() { + // var buffer = StringBuffer(); + // if (hasBrackets) buffer.writeCharCode($lbracket); + // buffer.write(contents + // .map((element) = > + // _elementNeedsParens(element) ? "($element)" : element.toString()) + // .join(separator == ListSeparator.comma ? ", " : " ")); + // if (hasBrackets) buffer.writeCharCode($rbracket); + // return buffer.toString(); + return "ListExpression"; + } + // Returns whether [expression], contained in [this], + // needs parentheses when printed as Sass source. + bool _elementNeedsParens(Expression* expression) { + /* + if (expression is ListExpression) { + if (expression.contents.length < 2) return false; + if (expression.hasBrackets) return false; + return separator == ListSeparator.comma + ? separator == ListSeparator.comma + : separator != ListSeparator.undecided; + } + + if (separator != ListSeparator.space) return false; + + if (expression is UnaryOperationExpression) { + return expression.operator == UnaryOperator.plus || + expression.operator == UnaryOperator.minus; + } + + */ + return false; + } + ATTACH_CRTP_PERFORM_METHODS(); + }; + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + class MapExpression final : public Expression { + ADD_CONSTREF(sass::vector, kvlist); + public: + void append(Expression* kv) { + kvlist_.emplace_back(kv); + } + size_t size() const { + return kvlist_.size(); + } + Expression* get(size_t i) { + return kvlist_[i]; + } + MapExpression(const SourceSpan& pstate); + ATTACH_CRTP_PERFORM_METHODS(); + }; + + + //////////////////////////////////////////////////////////////////////// + // Declarations -- style rules consisting of a property name and values. + //////////////////////////////////////////////////////////////////////// + class Declaration final : public ParentStatement { + ADD_PROPERTY(InterpolationObj, name); + ADD_PROPERTY(ExpressionObj, value); + ADD_PROPERTY(bool, is_custom_property); + public: + Declaration(const SourceSpan& pstate, InterpolationObj name, ExpressionObj value = {}, bool c = false, Block_Obj b = {}); + bool is_invisible() const override; + // ATTACH_CLONE_OPERATIONS(Declaration) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ///////////////////////////////////// + // Assignments -- variable and value. + ///////////////////////////////////// + class Assignment final : public Statement { + ADD_CONSTREF(EnvString, variable); + ADD_PROPERTY(ExpressionObj, value); + ADD_PROPERTY(IdxRef, vidx); + ADD_PROPERTY(bool, is_default); + ADD_PROPERTY(bool, is_global); + public: + Assignment(const SourceSpan& pstate, const sass::string& var, IdxRef vidx, Expression_Obj val, bool is_default = false, bool is_global = false); + // ATTACH_CLONE_OPERATIONS(Assignment) + ATTACH_CRTP_PERFORM_METHODS() + }; + + class ImportBase : public Statement { + public: + ImportBase(const SourceSpan& pstate); + ATTACH_VIRTUAL_COPY_OPERATIONS(ImportBase); + }; + + class StaticImport final : public ImportBase { + ADD_PROPERTY(InterpolationObj, url); + ADD_PROPERTY(CssStringObj, url2); + ADD_PROPERTY(SupportsCondition_Obj, supports); + ADD_PROPERTY(InterpolationObj, media); + ADD_PROPERTY(bool, outOfOrder); + public: + StaticImport(const SourceSpan& pstate, InterpolationObj url, SupportsCondition_Obj supports = {}, InterpolationObj media = {}); + // ATTACH_CLONE_OPERATIONS(StaticImport); + ATTACH_CRTP_PERFORM_METHODS(); + }; + + class DynamicImport final : public ImportBase { + ADD_CONSTREF(sass::string, url); + public: + DynamicImport(const SourceSpan& pstate, const sass::string& url); + // ATTACH_CLONE_OPERATIONS(DynamicImport); + ATTACH_CRTP_PERFORM_METHODS(); + }; + + + class ImportRule final : public Statement, public Vectorized { + public: + ImportRule(const SourceSpan& pstate); + // ATTACH_CLONE_OPERATIONS(ImportRule); + ATTACH_CRTP_PERFORM_METHODS(); + }; + + //////////////////////////////////////////////////////////////////////////// + // Import directives. CSS and Sass import lists can be intermingled, so it's + // necessary to store a list of each in an Import node. + //////////////////////////////////////////////////////////////////////////// + class Import final : public ImportBase { + sass::vector urls_; + sass::vector incs_; + // sass::vector imports_; + ADD_CONSTREF(sass::vector, import_queries); + ADD_CONSTREF(sass::vector, queries); + public: + Import(const SourceSpan& pstate); + sass::vector& incs(); + sass::vector& urls(); + // sass::vector& imports(); + sass::vector& queries2(); + bool is_invisible() const override; + // ATTACH_CLONE_OPERATIONS(Import) + ATTACH_CRTP_PERFORM_METHODS() + }; + + // not yet resolved single import + // so far we only know requested name + class Import_Stub final : public ImportBase { + Include resource_; + // Sass_Import_Entry import_; + public: + Import_Stub(const SourceSpan& pstate, Include res/*, + Sass_Import_Entry import*/); + Include resource(); + // Sass_Import_Entry import(); + sass::string imp_path(); + sass::string abs_path(); + // ATTACH_CLONE_OPERATIONS(Import_Stub) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ////////////////////////////// + // The Sass `@warn` directive. + ////////////////////////////// + class WarnRule final : public Statement { + ADD_PROPERTY(ExpressionObj, expression); + public: + WarnRule(const SourceSpan& pstate, + ExpressionObj expression); + // String toString() => "@warn $expression;"; + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////////////// + // The Sass `@error` directive. + /////////////////////////////// + class ErrorRule final : public Statement { + ADD_PROPERTY(ExpressionObj, expression); + public: + ErrorRule(const SourceSpan& pstate, + ExpressionObj expression); + // String toString() => "@error $expression;"; + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////////////// + // The Sass `@debug` directive. + /////////////////////////////// + class DebugRule final : public Statement { + ADD_PROPERTY(ExpressionObj, expression); + public: + DebugRule(const SourceSpan& pstate, + ExpressionObj expression); + // String toString() => "@debug $expression;"; + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////////////////////////// + // CSS comments. These may be interpolated. + /////////////////////////////////////////// + class LoudComment final : public Statement { + // The interpolated text of this comment, including comment characters. + ADD_PROPERTY(InterpolationObj, text) + public: + LoudComment(const SourceSpan& pstate, InterpolationObj itpl); + // ATTACH_CLONE_OPERATIONS(LoudComment) + ATTACH_CRTP_PERFORM_METHODS() + }; + + class SilentComment final : public Statement { + // The text of this comment, including comment characters. + ADD_CONSTREF(sass::string, text) + public: + SilentComment(const SourceSpan& pstate, const sass::string& text); + // not used in dart sass beside tests!? + // sass::string getDocComment() const; + // ATTACH_CLONE_OPERATIONS(SilentComment) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////// + // The Sass `@if` control directive. + //////////////////////////////////// + class If final : public ParentStatement { + ADD_POINTER(IDXS*, idxs); + ADD_PROPERTY(Expression_Obj, predicate); + ADD_PROPERTY(Block_Obj, alternative); + public: + If(const SourceSpan& pstate, Expression_Obj pred, Block_Obj con, Block_Obj alt = {}); + virtual bool has_content() override; + // ATTACH_CLONE_OPERATIONS(If) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ///////////////////////////////////// + // The Sass `@for` control directive. + ///////////////////////////////////// + class For final : public ParentStatement { + ADD_CONSTREF(EnvString, variable); + ADD_PROPERTY(Expression_Obj, lower_bound); + ADD_PROPERTY(Expression_Obj, upper_bound); + ADD_POINTER(IDXS*, idxs); + ADD_PROPERTY(bool, is_inclusive); + public: + For(const SourceSpan& pstate, const EnvString& var, Expression_Obj lo, Expression_Obj hi, bool inc = false, Block_Obj b = {}); + // ATTACH_CLONE_OPERATIONS(For) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ////////////////////////////////////// + // The Sass `@each` control directive. + ////////////////////////////////////// + class Each final : public ParentStatement { + ADD_CONSTREF(sass::vector, variables); + ADD_POINTER(IDXS*, idxs); + ADD_PROPERTY(Expression_Obj, list); + public: + Each(const SourceSpan& pstate, const sass::vector& vars, Expression_Obj lst, Block_Obj b = {}); + // ATTACH_CLONE_OPERATIONS(Each) + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////////////////////// + // The Sass `@while` control directive. + /////////////////////////////////////// + class WhileRule final : public ParentStatement { + ADD_PROPERTY(ExpressionObj, condition); + ADD_POINTER(IDXS*, idxs); + public: + WhileRule(const SourceSpan& pstate, + ExpressionObj condition, + Block_Obj b = {}); + // String toString() = > "@while $condition {${children.join(" ")}}"; + ATTACH_CRTP_PERFORM_METHODS() + }; + + ///////////////////////////////////////////////////////////// + // The @return directive for use inside SassScript functions. + ///////////////////////////////////////////////////////////// + class Return final : public Statement { + ADD_PROPERTY(Expression_Obj, value); + public: + Return(const SourceSpan& pstate, Expression_Obj val); + // ATTACH_CLONE_OPERATIONS(Return) + ATTACH_CRTP_PERFORM_METHODS() + }; + + class InvocationExpression : + public Expression, + public CallableInvocation { + public: + InvocationExpression(const SourceSpan& pstate, + ArgumentInvocation* arguments) : + Expression(pstate), + CallableInvocation(arguments) + { + } + }; + + class InvocationStatement : + public Statement, + public CallableInvocation { + public: + InvocationStatement(const SourceSpan& pstate, + ArgumentInvocation* arguments) : + Statement(pstate), + CallableInvocation(arguments) + { + } + }; + + /// A function invocation. + /// + /// This may be a plain CSS function or a Sass function. + class IfExpression : public InvocationExpression { + + public: + IfExpression(const SourceSpan& pstate, + ArgumentInvocation* arguments) : + InvocationExpression(pstate, arguments) + { + } + + sass::string toString() const { + return "if" + arguments_->toString(); + } + + ATTACH_CRTP_PERFORM_METHODS(); + }; + + /// A function invocation. + /// + /// This may be a plain CSS function or a Sass function. + class FunctionExpression : public InvocationExpression { + + // The namespace of the function being invoked, + // or `null` if it's invoked without a namespace. + ADD_CONSTREF(sass::string, ns); + + ADD_PROPERTY(IdxRef, fidx); + + // The name of the function being invoked. If this is + // interpolated, the function will be interpreted as plain + // CSS, even if it has the same name as a Sass function. + ADD_PROPERTY(InterpolationObj, name); + + public: + FunctionExpression(const SourceSpan& pstate, + Interpolation* name, + ArgumentInvocation* arguments, + const sass::string& ns = "") : + InvocationExpression(pstate, arguments), + ns_(ns), name_(name) + { + + } + + ATTACH_CRTP_PERFORM_METHODS(); + }; + + ///////////////////////////////////////////////////////////////////////////// + // Definitions for both mixins and functions. The two cases are distinguished + // by a type tag. + ///////////////////////////////////////////////////////////////////////////// + class CallableDeclaration : public ParentStatement { + // The name of this callable. + // This may be empty for callables without names. + ADD_CONSTREF(EnvString, name); + ADD_POINTER(IDXS*, idxs); + ADD_PROPERTY(IdxRef, fidx); + ADD_PROPERTY(IdxRef, cidx); + + // The comment immediately preceding this declaration. + ADD_PROPERTY(SilentCommentObj, comment); + // The declared arguments this callable accepts. + ADD_PROPERTY(ArgumentDeclarationObj, arguments); + public: + CallableDeclaration( + const SourceSpan& pstate, + const EnvString& name, + ArgumentDeclaration* arguments, + SilentComment* comment = nullptr, + Block* block = nullptr); + + // Stringify declarations etc. (dart) + virtual sass::string toString1() const = 0; + + ATTACH_ABSTRACT_CRTP_PERFORM_METHODS(); + }; + + class ContentBlock : + public CallableDeclaration { + public: + ContentBlock( + const SourceSpan& pstate, + ArgumentDeclaration* arguments = nullptr, + const sass::vector& children = {}); + sass::string toString1() const override final; + ATTACH_CRTP_PERFORM_METHODS(); + }; + + class FunctionRule final : + public CallableDeclaration { + public: + FunctionRule( + const SourceSpan& pstate, + const EnvString& name, + ArgumentDeclaration* arguments, + SilentComment* comment = nullptr, + Block* block = nullptr); + sass::string toString1() const override final; + ATTACH_CRTP_PERFORM_METHODS(); + }; + + class MixinRule final : + public CallableDeclaration { + ADD_CONSTREF(IdxRef, cidx); + public: + MixinRule( + const SourceSpan& pstate, + const sass::string& name, + ArgumentDeclaration* arguments, + SilentComment* comment = nullptr, + Block* block = nullptr); + sass::string toString1() const override final; + ATTACH_CRTP_PERFORM_METHODS(); + }; + + class IncludeRule final : public InvocationStatement { + + // The namespace of the mixin being invoked, or + // `null` if it's invoked without a namespace. + ADD_CONSTREF(sass::string, ns); + + // The name of the mixin being invoked. + ADD_CONSTREF(EnvString, name); + + // The block that will be invoked for [ContentRule]s in the mixin + // being invoked, or `null` if this doesn't pass a content block. + ADD_PROPERTY(ContentBlockObj, content); + + ADD_CONSTREF(IdxRef, midx); + + public: + + IncludeRule( + const SourceSpan& pstate, + const EnvString& name, + ArgumentInvocation* arguments, + const sass::string& ns = "", + ContentBlock* content = nullptr, + Block* block = nullptr); + + bool has_content() override final; + + ATTACH_CRTP_PERFORM_METHODS(); + }; + + /////////////////////////////////////////////////// + // The @content directive for mixin content blocks. + /////////////////////////////////////////////////// + class ContentRule final : public Statement { + ADD_PROPERTY(ArgumentInvocationObj, arguments); + public: + ContentRule(const SourceSpan& pstate, + ArgumentInvocation* arguments); + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////////////////////////////////// + class ParenthesizedExpression final : public Expression { + ADD_PROPERTY(ExpressionObj, expression) + public: + ParenthesizedExpression(const SourceSpan& pstate, Expression* expression); + // ATTACH_CLONE_OPERATIONS(ParenthesizedExpression); + ATTACH_CRTP_PERFORM_METHODS(); + }; + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + // Arithmetic negation (logical negation is just an ordinary function call). + //////////////////////////////////////////////////////////////////////////// + class Unary_Expression final : public Expression { + public: + enum Type { PLUS, MINUS, NOT, SLASH }; + private: + ADD_PROPERTY(Type, optype) + ADD_PROPERTY(Expression_Obj, operand) + public: + Unary_Expression(const SourceSpan& pstate, Type t, Expression_Obj o); + // ATTACH_CLONE_OPERATIONS(Unary_Expression) + ATTACH_CRTP_PERFORM_METHODS() + }; + + // A Media Ruleset before it has been evaluated + // Could be already final or an interpolation + class MediaRule final : public ParentStatement { + ADD_PROPERTY(InterpolationObj, query) + public: + // The query that determines on which platforms the styles will be in effect. + // This is only parsed after the interpolation has been resolved. + MediaRule(const SourceSpan& pstate, InterpolationObj query, Block_Obj block = {}); + + bool bubbles() const override final { return true; }; + bool is_invisible() const override { return false; }; + // ATTACH_CLONE_OPERATIONS(MediaRule) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ///////////////////////////////////////////////// + // A query for the `@at-root` rule. + ///////////////////////////////////////////////// + class AtRootQuery final : public AST_Node { + private: + // The names of the rules included or excluded by this query. There are + // two special names. "all" indicates that all rules are included or + // excluded, and "rule" indicates style rules are included or excluded. + ADD_PROPERTY(StringSet, names); + // Whether the query includes or excludes rules with the specified names. + ADD_PROPERTY(bool, include); + + public: + + AtRootQuery( + const SourceSpan& pstate, + const StringSet& names, + bool include); + + // Whether this includes or excludes *all* rules. + bool all() const; + + // Whether this includes or excludes style rules. + bool rule() const; + + // Whether this includes or excludes media rules. + bool media() const; + + // Returns the at-rule name for [node], or `null` if it's not an at-rule. + sass::string _nameFor(Statement* node) const; + + // Returns whether [this] excludes a node with the given [name]. + bool excludesName(const sass::string& name) const; + + // Returns whether [this] excludes [node]. + bool excludes(Statement* node) const; + + // Whether this excludes `@media` rules. + // Note that this takes [include] into account. + bool excludesMedia() const; + + // Whether this excludes style rules. + // Note that this takes [include] into account. + bool excludesStyleRules() const; + + // Parses an at-root query from [contents]. If passed, [url] + // is the name of the file from which [contents] comes. + // Throws a [SassFormatException] if parsing fails. + static AtRootQuery* parse( + const sass::string& contents, Context& ctx); + + // The default at-root query, which excludes only style rules. + // ToDo: check out how to make this static + static AtRootQuery* defaultQuery(const SourceSpan& pstate); + + // Only for debug purposes + sass::string toString() const; + + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////// + // At-root. + /////////// + class AtRootRule final : public ParentStatement { + ADD_PROPERTY(InterpolationObj, query); + ADD_POINTER(IDXS*, idxs); + public: + AtRootRule(SourceSpan&& pstate, InterpolationObj query = {}, Block_Obj b = {}); + // ATTACH_CLONE_OPERATIONS(CssAtRootRule) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////////////////// + // Individual argument objects for mixin and function calls. + //////////////////////////////////////////////////////////// + class Argument final : public Expression { + HASH_PROPERTY(Expression_Obj, value); + HASH_CONSTREF(EnvString, name); + ADD_PROPERTY(bool, is_rest_argument); + ADD_PROPERTY(bool, is_keyword_argument); + mutable size_t hash_; + public: + Argument(const SourceSpan& pstate, Expression_Obj val, + const EnvString& n, bool rest = false, bool keyword = false); + size_t hash() const override; + ATTACH_CLONE_OPERATIONS(Argument) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ///////////////////////////////////////////////////////////////////////// + // Parameter lists -- in their own class to facilitate context-sensitive + // error checking (e.g., ensuring that all optional parameters follow all + // required parameters). + ///////////////////////////////////////////////////////////////////////// + typedef Value* (*SassFnSig)(FN_PROTOTYPE2); + typedef std::pair SassFnPair; + typedef sass::vector SassFnPairs; + + class Callable : public SassNode { + public: + Callable(const SourceSpan& pstate); + virtual Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate) = 0; + virtual bool operator== (const Callable& rhs) const = 0; + ATTACH_CRTP_PERFORM_METHODS() + }; + + class UserDefinedCallable : public Callable { + // Name of this callable (used for reporting) + ADD_CONSTREF(EnvString, name); + // The declaration (parameters this function takes). + ADD_PROPERTY(CallableDeclarationObj, declaration); + // The environment in which this callable was declared. + ADD_POINTER(EnvSnapshot*, snapshot); + public: + UserDefinedCallable( + const SourceSpan& pstate, + const EnvString& name, + CallableDeclarationObj declaration, + EnvSnapshot* snapshot); + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate) override final; + bool operator== (const Callable& rhs) const override final; + ATTACH_CRTP_PERFORM_METHODS() + }; + + class PlainCssCallable : public Callable { + ADD_CONSTREF(sass::string, name); + public: + PlainCssCallable(const SourceSpan& pstate, const sass::string& name); + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate) override final; + bool operator== (const Callable& rhs) const override final; + ATTACH_CRTP_PERFORM_METHODS() + }; + + class BuiltInCallable : public Callable { + + // The function name + ADD_CONSTREF(EnvString, name); + + ADD_PROPERTY(ArgumentDeclarationObj, parameters); + + ADD_CONSTREF(SassFnPair, function); + + public: + + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate) override final; + + // Value* execute(ArgumentInvocation* arguments) { + // // return callback(arguments); + // return nullptr; + // } + + // Creates a callable with a single [arguments] declaration + // and a single [callback]. The argument declaration is parsed + // from [arguments], which should not include parentheses. + // Throws a [SassFormatException] if parsing fails. + BuiltInCallable( + const EnvString& name, + ArgumentDeclaration* parameters, + const SassFnSig& callback); + + virtual const SassFnPair& callbackFor( + size_t positional, + const KeywordMap& names); + + bool operator== (const Callable& rhs) const override final; + + ATTACH_CRTP_PERFORM_METHODS() + }; + + class BuiltInCallables : public Callable { + + // The function name + ADD_CONSTREF(EnvString, name); + + // The overloads declared for this callable. + ADD_PROPERTY(SassFnPairs, overloads); + + public: + + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate) override final; + + // Value* execute(ArgumentInvocation* arguments) { + // // return callback(arguments); + // return nullptr; + // } + + // Creates a callable with multiple implementations. Each + // key/value pair in [overloads] defines the argument declaration + // for the overload (which should not include parentheses), and + // the callback to execute if that argument declaration matches. + // Throws a [SassFormatException] if parsing fails. + BuiltInCallables( + const EnvString& name, + const SassFnPairs& overloads); + + const SassFnPair& callbackFor( + size_t positional, + const KeywordMap& names); // override final; + + bool operator== (const Callable& rhs) const override final; + + ATTACH_CRTP_PERFORM_METHODS() + }; + + class ExternalCallable : public Callable { + + // The function name + ADD_CONSTREF(sass::string, name); + + ADD_PROPERTY(ArgumentDeclarationObj, declaration); + + ADD_PROPERTY(Sass_Function_Entry, function); + + ADD_POINTER(IDXS*, idxs); + + public: + + ExternalCallable( + const sass::string& name, + ArgumentDeclaration* parameters, + Sass_Function_Entry function); + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate) override final; + bool operator== (const Callable& rhs) const override final; + + ATTACH_CRTP_PERFORM_METHODS() + }; + + +} + +#include "ast_css.hpp" +#include "ast_values.hpp" +#include "ast_supports.hpp" +#include "ast_selectors.hpp" + +#ifdef __clang__ + +// #pragma clang diagnostic pop +// #pragma clang diagnostic push + +#endif + +#endif diff --git a/src/ast2c.cpp b/src/ast2c.cpp deleted file mode 100644 index f167b7ea77..0000000000 --- a/src/ast2c.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "ast2c.hpp" -#include "ast.hpp" - -namespace Sass { - - union Sass_Value* AST2C::operator()(Boolean* b) - { return sass_make_boolean(b->value()); } - - union Sass_Value* AST2C::operator()(Number* n) - { return sass_make_number(n->value(), n->unit().c_str()); } - - union Sass_Value* AST2C::operator()(Custom_Warning* w) - { return sass_make_warning(w->message().c_str()); } - - union Sass_Value* AST2C::operator()(Custom_Error* e) - { return sass_make_error(e->message().c_str()); } - - union Sass_Value* AST2C::operator()(Color_RGBA* c) - { return sass_make_color(c->r(), c->g(), c->b(), c->a()); } - - union Sass_Value* AST2C::operator()(Color_HSLA* c) - { - Color_RGBA_Obj rgba = c->copyAsRGBA(); - return operator()(rgba.ptr()); - } - - union Sass_Value* AST2C::operator()(String_Constant* s) - { - if (s->quote_mark()) { - return sass_make_qstring(s->value().c_str()); - } else { - return sass_make_string(s->value().c_str()); - } - } - - union Sass_Value* AST2C::operator()(String_Quoted* s) - { return sass_make_qstring(s->value().c_str()); } - - union Sass_Value* AST2C::operator()(List* l) - { - union Sass_Value* v = sass_make_list(l->length(), l->separator(), l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - sass_list_set_value(v, i, (*l)[i]->perform(this)); - } - return v; - } - - union Sass_Value* AST2C::operator()(Map* m) - { - union Sass_Value* v = sass_make_map(m->length()); - int i = 0; - for (auto key : m->keys()) { - sass_map_set_key(v, i, key->perform(this)); - sass_map_set_value(v, i, m->at(key)->perform(this)); - i++; - } - return v; - } - - union Sass_Value* AST2C::operator()(Arguments* a) - { - union Sass_Value* v = sass_make_list(a->length(), SASS_COMMA, false); - for (size_t i = 0, L = a->length(); i < L; ++i) { - sass_list_set_value(v, i, (*a)[i]->perform(this)); - } - return v; - } - - union Sass_Value* AST2C::operator()(Argument* a) - { return a->value()->perform(this); } - - // not strictly necessary because of the fallback - union Sass_Value* AST2C::operator()(Null* n) - { return sass_make_null(); } - -}; diff --git a/src/ast2c.hpp b/src/ast2c.hpp deleted file mode 100644 index cd99a17c31..0000000000 --- a/src/ast2c.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef SASS_AST2C_H -#define SASS_AST2C_H - -#include "ast_fwd_decl.hpp" -#include "operation.hpp" -#include "sass/values.h" - -namespace Sass { - - class AST2C : public Operation_CRTP { - - public: - - AST2C() { } - ~AST2C() { } - - union Sass_Value* operator()(Boolean*); - union Sass_Value* operator()(Number*); - union Sass_Value* operator()(Color_RGBA*); - union Sass_Value* operator()(Color_HSLA*); - union Sass_Value* operator()(String_Constant*); - union Sass_Value* operator()(String_Quoted*); - union Sass_Value* operator()(Custom_Warning*); - union Sass_Value* operator()(Custom_Error*); - union Sass_Value* operator()(List*); - union Sass_Value* operator()(Map*); - union Sass_Value* operator()(Null*); - union Sass_Value* operator()(Arguments*); - union Sass_Value* operator()(Argument*); - - // return sass error if type is not supported - union Sass_Value* fallback(AST_Node* x) - { return sass_make_error("unknown type for C-API"); } - - }; - -} - -#endif diff --git a/src/ast_callables.cpp b/src/ast_callables.cpp new file mode 100644 index 0000000000..9386b24c4a --- /dev/null +++ b/src/ast_callables.cpp @@ -0,0 +1,381 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "ast_callables.hpp" + +#include "exceptions.hpp" +#include "parser_scss.hpp" +#include "eval.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Callable::Callable( + const SourceSpan& pstate) : + AstNode(pstate) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + BuiltInCallable::BuiltInCallable( + const EnvKey& envkey, + ArgumentDeclaration* parameters, + const SassFnSig& callback) : + Callable(SourceSpan::tmp("[BUILTIN]")), + envkey_(envkey), + // Create a single entry in overloaded function + function_(SassFnPair{ parameters, callback }) + {} + + // Return callback with matching signature + const SassFnPair& BuiltInCallable::callbackFor( + const ArgumentResults& evaluated) + { + return function_; + } + + // Equality comparator (needed for `get-function` value) + bool BuiltInCallable::operator==(const Callable& rhs) const + { + if (const BuiltInCallable* builtin = rhs.isaBuiltInCallable()) { + return envkey_ == builtin->envkey_ && + function_.first == builtin->function_.first && + function_.second == builtin->function_.second; + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + BuiltInCallables::BuiltInCallables( + const EnvKey& envkey, + const SassFnPairs& overloads) : + Callable(SourceSpan::tmp("[BUILTINS]")), + envkey_(envkey), + overloads_(overloads) + { + size_t size = 0; + for (auto fn : overloads) { + size = std::max(size, + fn.first->maxArgs()); + } + for (auto fn : overloads) { + fn.first->maxArgs(size); + } + } + + // Return callback with matching signature + const SassFnPair& BuiltInCallables::callbackFor( + const ArgumentResults& evaluated) + { + for (SassFnPair& pair : overloads_) { + if (pair.first->matches(evaluated)) { + return pair; + } + } + return overloads_.back(); + } + + // Equality comparator (needed for `get-function` value) + bool BuiltInCallables::operator==(const Callable& rhs) const + { + if (const BuiltInCallables* builtin = rhs.isaBuiltInCallables()) { + if (!(envkey_ == builtin->envkey_)) return false; + return overloads_ == builtin->overloads_; + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + PlainCssCallable::PlainCssCallable( + const SourceSpan& pstate, + const sass::string& fname) : + Callable(pstate), + fname_(fname) + {} + + // Equality comparator (needed for `get-function` value) + bool PlainCssCallable::operator==(const Callable& rhs) const + { + if (const PlainCssCallable* builtin = rhs.isaPlainCssCallable()) { + return fname_ == builtin->fname_; + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + UserDefinedCallable::UserDefinedCallable( + const SourceSpan& pstate, + const EnvKey& envkey, + CallableDeclarationObj declaration, + UserDefinedCallable* content) : + Callable(pstate), + envkey_(envkey), + declaration_(declaration), + content_(content) + {} + + // Equality comparator (needed for `get-function` value) + bool UserDefinedCallable::operator==(const Callable& rhs) const + { + if (const UserDefinedCallable* builtin = rhs.isaUserDefinedCallable()) { + return envkey_ == builtin->envkey_ && + declaration_ == builtin->declaration_; + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ExternalCallable::ExternalCallable( + const EnvKey& fname, + ArgumentDeclaration* parameters, + struct SassFunction* function) : + Callable(SourceSpan::tmp("[EXTERNAL]")), + envkey_(fname), + declaration_(parameters), + function_(function) + {} + + // Equality comparator (needed for `get-function` value) + bool ExternalCallable::operator==(const Callable& rhs) const + { + if (const ExternalCallable* builtin = rhs.isaExternalCallable()) { + return envkey_ == builtin->envkey_ && + function_ == builtin->function_; + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Argument::Argument(const SourceSpan& pstate, + const EnvKey& name, + ExpressionObj defval, + bool is_rest_argument, + bool is_keyword_argument) : + AstNode(pstate), + name_(name), + defval_(defval), + is_rest_argument_(is_rest_argument), + is_keyword_argument_(is_keyword_argument) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ArgumentDeclaration::ArgumentDeclaration( + SourceSpan&& pstate, + sass::vector&& arguments, + EnvKey&& restArg) : + AstNode(std::move(pstate)), + arguments_(std::move(arguments)), + restArg_(std::move(restArg)), + maxArgs_(arguments_.size()) + { + if (!restArg_.empty()) { + maxArgs_ += 1; + } + } + + // Parse source into arguments + ArgumentDeclaration* ArgumentDeclaration::parse( + Compiler& context, SourceData* source) + { + ScssParser parser(context, source); + return parser.parseArgumentDeclaration(); + } + + // Throws a [SassScriptException] if [positional] and + // [names] aren't valid for this argument declaration. + void ArgumentDeclaration::verify( + size_t positional, + const ValueFlatMap& names, + const SourceSpan& pstate, + const BackTraces& traces) const + { + + size_t i = 0; + size_t namedUsed = 0; + size_t iL = arguments_.size(); + while (i < std::min(positional, iL)) { + if (names.count(arguments_[i]->name()) == 1) { + throw Exception::RuntimeException(traces, + "Argument $" + arguments_[i]->name().orig() + + " name was passed both by position and by name."); + } + i++; + } + while (i < iL) { + if (names.count(arguments_[i]->name()) == 1) { + namedUsed++; + } + else if (arguments_[i]->defval() == nullptr) { + throw Exception::RuntimeException(traces, + "Missing argument $" + arguments_[i]->name().orig() + "."); + } + i++; + } + + if (!restArg_.empty()) return; + + if (positional > arguments_.size()) { + sass::sstream strm; + strm << "Only " << arguments_.size() << " "; // " positional "; + strm << pluralize("argument", arguments_.size()); + strm << " allowed, but " << positional << " "; + strm << pluralize("was", positional, "were"); + strm << " passed."; + throw Exception::RuntimeException( + traces, strm.str()); + } + + if (namedUsed < names.size()) { + ValueFlatMap unknownNames(names); + for (Argument* arg : arguments_) { + unknownNames.erase(arg->name()); + } + throw Exception::RuntimeException( + traces, "No argument named $" + + toSentence(getKeyVector(unknownNames), "or") + "."); + } + + } + // EO verify + + // Returns whether [positional] and [names] + // are valid for this argument declaration. + bool ArgumentDeclaration::matches( + const ArgumentResults& evaluated) const + { + size_t namedUsed = 0; Argument* argument; + for (size_t i = 0, iL = arguments_.size(); i < iL; i++) { + argument = arguments_[i]; + if (i < evaluated.positional().size()) { + if (evaluated.named().count(argument->name()) == 1) { + return false; + } + } + else if (evaluated.named().count(argument->name()) == 1) { + namedUsed++; + } + else if (argument->defval().isNull()) { + return false; + } + } + if (!restArg_.empty()) return true; + if (evaluated.positional().size() > arguments_.size()) return false; + if (namedUsed < evaluated.named().size()) return false; + return true; + } + // EO matches + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ArgumentInvocation::ArgumentInvocation( + const SourceSpan& pstate, + ExpressionVector&& positional, + ExpressionFlatMap&& named, + Expression* restArg, + Expression* kwdRest) : + AstNode(pstate), + positional_(std::move(positional)), + named_(std::move(named)), + restArg_(restArg), + kwdRest_(kwdRest) + {} + + ArgumentInvocation::ArgumentInvocation( + SourceSpan&& pstate, + ExpressionVector&& positional, + ExpressionFlatMap&& named, + Expression* restArg, + Expression* kwdRest) : + AstNode(std::move(pstate)), + positional_(std::move(positional)), + named_(std::move(named)), + restArg_(restArg), + kwdRest_(kwdRest) + {} + + // Returns whether this invocation passes no arguments. + bool ArgumentInvocation::isEmpty() const + { + return positional_.empty() + && named_.empty() + && restArg_.isNull(); + } + // EO isEmpty + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ArgumentResults::ArgumentResults( + ValueVector&& positional, + ValueFlatMap&& named, + SassSeparator separator) : + positional_(std::move(positional)), + named_(std::move(named)), + separator_(separator) + {} + + ArgumentResults::ArgumentResults( + ArgumentResults&& other) noexcept : + positional_(std::move(other.positional_)), + named_(std::move(other.named_)), + separator_(other.separator_) + {} + + ArgumentResults& ArgumentResults::operator=( + ArgumentResults&& other) noexcept + { + positional_ = std::move(other.positional_); + named_ = std::move(other.named_); + separator_ = other.separator_; + return *this; + } + + ///////////////////////////////////////////////////////////////////////// + // Implement the execute dispatch to evaluator + ///////////////////////////////////////////////////////////////////////// + + Value* BuiltInCallable::execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate, bool selfAssign) + { + return eval.execute(this, arguments, pstate, selfAssign); + } + + Value* BuiltInCallables::execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate, bool selfAssign) + { + return eval.execute(this, arguments, pstate, selfAssign); + } + + Value* PlainCssCallable::execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate, bool selfAssign) + { + return eval.execute(this, arguments, pstate, selfAssign); + } + + Value* UserDefinedCallable::execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate, bool selfAssign) + { + return eval.execute(this, arguments, pstate, selfAssign); + } + + Value* ExternalCallable::execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate, bool selfAssign) + { + return eval.execute(this, arguments, pstate, selfAssign); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/ast_callables.hpp b/src/ast_callables.hpp new file mode 100644 index 0000000000..3369f3bc2a --- /dev/null +++ b/src/ast_callables.hpp @@ -0,0 +1,416 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_CALLABLES_HPP +#define SASS_AST_CALLABLES_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_nodes.hpp" +#include "environment_key.hpp" +#include "environment_stack.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + typedef Value* (*SassFnSig)(FN_PROTOTYPE2); + typedef std::pair SassFnPair; + typedef sass::vector SassFnPairs; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class Callable : public AstNode + { + public: + + // Value constructor + Callable(const SourceSpan& pstate); + + // The main entry point to execute the function (implemented in each specialization) + virtual Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate, bool selfAssign) = 0; + + // Return the function name + virtual const sass::string& name() const = 0; + + // Equality comparator (needed for `get-function` value) + virtual bool operator==(const Callable& rhs) const = 0; + + // Declare up-casting methods + DECLARE_ISA_CASTER(BuiltInCallable); + DECLARE_ISA_CASTER(BuiltInCallables); + DECLARE_ISA_CASTER(PlainCssCallable); + DECLARE_ISA_CASTER(UserDefinedCallable); + DECLARE_ISA_CASTER(ExternalCallable); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class BuiltInCallable final : public Callable { + + // The function name + ADD_CONSTREF(EnvKey, envkey); + + ADD_CONSTREF(ArgumentDeclarationObj, parameters); + + ADD_REF(SassFnPair, function); + + public: + + // Creates a callable with a single [arguments] declaration + // and a single [callback]. The argument declaration is parsed + // from [arguments], which should not include parentheses. + // Throws a [SassFormatException] if parsing fails. + BuiltInCallable( + const EnvKey& fname, + ArgumentDeclaration* parameters, + const SassFnSig& callback); + + // Return callback with matching signature + const SassFnPair& callbackFor( + const ArgumentResults& evaluated); + + // The main entry point to execute the function (implemented in each specialization) + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate, bool selfAssign) override final; + + // Return the function name + const sass::string& name() const override final { return envkey_.norm(); } + + // Equality comparator (needed for `get-function` value) + bool operator==(const Callable& rhs) const override final; + + IMPLEMENT_ISA_CASTER(BuiltInCallable); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class BuiltInCallables final : public Callable { + + // The function name + ADD_CONSTREF(EnvKey, envkey); + + // The overloads declared for this callable. + ADD_CONSTREF(SassFnPairs, overloads); + + public: + + // Creates a callable with multiple implementations. Each + // key/value pair in [overloads] defines the argument declaration + // for the overload (which should not include parentheses), and + // the callback to execute if that argument declaration matches. + // Throws a [SassFormatException] if parsing fails. + BuiltInCallables( + const EnvKey& envkey, + const SassFnPairs& overloads); + + // Return callback with matching signature + const SassFnPair& callbackFor( + const ArgumentResults& evaluated); + + // The main entry point to execute the function (implemented in each specialization) + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate, bool selfAssign) override final; + + // Return the function name + const sass::string& name() const override final { return envkey_.norm(); } + + // Equality comparator (needed for `get-function` value) + bool operator==(const Callable& rhs) const override final; + + IMPLEMENT_ISA_CASTER(BuiltInCallables); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class PlainCssCallable final : public Callable + { + private: + + ADD_CONSTREF(sass::string, fname); + + public: + + // Value constructor + PlainCssCallable( + const SourceSpan& pstate, + const sass::string& fname); + + // The main entry point to execute the function (implemented in each specialization) + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate, bool selfAssign) override final; + + // Return the function name + const sass::string& name() const override final { return fname(); } + + // Equality comparator (needed for `get-function` value) + bool operator==(const Callable& rhs) const override final; + + IMPLEMENT_ISA_CASTER(PlainCssCallable); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class UserDefinedCallable final : public Callable + { + private: + + // Name of this callable (used for reporting) + ADD_CONSTREF(EnvKey, envkey); + // The declaration (parameters this function takes). + ADD_CONSTREF(CallableDeclarationObj, declaration); + // The environment in which this callable was declared. + ADD_PROPERTY(UserDefinedCallable*, content); + + public: + + // Value constructor + UserDefinedCallable( + const SourceSpan& pstate, + const EnvKey& fname, + CallableDeclarationObj declaration, + UserDefinedCallable* content); + + // The main entry point to execute the function (implemented in each specialization) + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate, bool selfAssign) override final; + + // Return the function name + const sass::string& name() const override final { return envkey_.norm(); } + + // Equality comparator (needed for `get-function` value) + bool operator==(const Callable& rhs) const override final; + + IMPLEMENT_ISA_CASTER(UserDefinedCallable); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class ExternalCallable final : public Callable + { + private: + + // Name of this callable (used for reporting) + ADD_CONSTREF(EnvKey, envkey); + // The declaration (parameters this function takes). + ADD_CONSTREF(ArgumentDeclarationObj, declaration); + // The attached external callback reference + ADD_PROPERTY(struct SassFunction*, function); + + public: + + // Value constructor + ExternalCallable( + const EnvKey& fname, + ArgumentDeclaration* parameters, + struct SassFunction* function); + + // Destructor + ~ExternalCallable() override final { + sass_delete_function(function_); + } + + // The main entry point to execute the function (implemented in each specialization) + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate, bool selfAssign) override final; + + // Return the function name + const sass::string& name() const override final { return envkey_.norm(); } + + // Equality comparator (needed for `get-function` value) + bool operator==(const Callable& rhs) const override final; + + IMPLEMENT_ISA_CASTER(ExternalCallable); + }; + + ///////////////////////////////////////////////////////////////////////// + // Individual argument objects for mixin and function calls. + ///////////////////////////////////////////////////////////////////////// + class Argument final : public AstNode + { + private: + + ADD_CONSTREF(EnvKey, name); + ADD_CONSTREF(ExpressionObj, defval); + ADD_CONSTREF(bool, is_rest_argument); + ADD_CONSTREF(bool, is_keyword_argument); + + public: + + // Value constructor + Argument(const SourceSpan& pstate, + const EnvKey& name, + ExpressionObj defval, + bool is_rest_argument = false, + bool is_keyword_argument = false); + + }; + + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// + + class ArgumentDeclaration final : public AstNode + { + private: + + // The arguments that are taken. + ADD_REF(sass::vector, arguments); + + // The name of the rest argument (as in `$args...`), + // or `null` if none was declared. + ADD_CONSTREF(EnvKey, restArg); + + // This is only used for debugging + ADD_CONSTREF(size_t, maxArgs); + + public: + + // Value constructor + ArgumentDeclaration(SourceSpan&& pstate, + sass::vector&& arguments = {}, + EnvKey&& restArg = {}); + + // Check if signature is void + bool isEmpty() const { + return arguments_.empty() + && restArg_.empty(); + } + + // Parse source into arguments + static ArgumentDeclaration* parse( + Compiler& context, SourceData* source); + + // Throws a [SassScriptException] if [positional] and + // [names] aren't valid for this argument declaration. + void verify( + size_t positional, + const ValueFlatMap& names, + const SourceSpan& pstate, + const BackTraces& traces) const; + + // Returns whether [positional] and [names] + // are valid for this argument declaration. + bool matches(const ArgumentResults& evaluated) const; + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class ArgumentInvocation final : public AstNode + { + private: + + // The arguments passed by position. + ADD_REF(ExpressionVector, positional); + + // The argument expressions passed by name. + ADD_REF(ExpressionFlatMap, named); + + // The first rest argument (as in `$args...`). + ADD_CONSTREF(ExpressionObj, restArg); + + // The second rest argument, which is expected to only contain a keyword map. + // This can be an already evaluated Map (via call) or a MapExpression. + // So we must guarantee that this evaluates to a real Map value. + ADD_CONSTREF(ExpressionObj, kwdRest); + + public: + + // Value constructor + ArgumentInvocation(const SourceSpan& pstate, + ExpressionVector&& positional, + ExpressionFlatMap&& named, + Expression* restArgs = nullptr, + Expression* kwdRest = nullptr); + + // Value constructor + ArgumentInvocation(SourceSpan&& pstate, + ExpressionVector&& positional, + ExpressionFlatMap&& named, + Expression* restArgs = nullptr, + Expression* kwdRest = nullptr); + + // Returns whether this invocation passes no arguments. + bool isEmpty() const; + + }; + + ///////////////////////////////////////////////////////////////////////// + // The result of evaluating arguments to a function or mixin. + ///////////////////////////////////////////////////////////////////////// + + class ArgumentResults final { + + // Arguments passed by position. + ADD_REF(ValueVector, positional); + + // Arguments passed by name. + // A list implementation is often more efficient + // I don't expect any function to have many arguments + // Normally the trade-off is around 8 items in the list + ADD_REF(ValueFlatMap, named); + + // The separator used for the rest argument list, if any. + ADD_REF(SassSeparator, separator); + + public: + + // Value constructor + ArgumentResults() : + separator_(SASS_UNDEF) + {}; + + // Value move constructor + ArgumentResults( + ValueVector&& positional, + ValueFlatMap&& named, + SassSeparator separator); + + // Move constructor + ArgumentResults( + ArgumentResults&& other) noexcept; + + // Move assignment operator + ArgumentResults& operator=( + ArgumentResults&& other) noexcept; + + // Clear results + void clear() { + named_.clear(); + positional_.clear(); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CallableInvocation + { + + private: + + // The arguments passed to the callable. + ADD_CONSTREF(ArgumentInvocationObj, arguments); + + public: + + // Value constructor + CallableInvocation( + ArgumentInvocation* arguments) : + arguments_(arguments) + {} + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/ast_containers.hpp b/src/ast_containers.hpp new file mode 100644 index 0000000000..6722a1b17a --- /dev/null +++ b/src/ast_containers.hpp @@ -0,0 +1,404 @@ +#ifndef SASS_AST_CONTAINERS_H +#define SASS_AST_CONTAINERS_H + +#include "ast_helpers.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + // Base class/container for AST nodes that should behave like vectors. + ///////////////////////////////////////////////////////////////////////// + template + class Vectorized { + + protected: + + // The main underlying container + typedef SharedImpl T; + typedef Vectorized Klass; + sass::vector elements_; + + // Hash is only calculated once and afterwards the value + // must not be mutated, which is the case with how sass + // works, although we must be a bit careful not to alter + // any value that has already been added to a set or map. + // Must create a copy if you need to alter such an object. + mutable size_t hash_ = 0; + + public: + + // Reserve constructor + Vectorized(size_t s = 0) + { + elements_.reserve(s); + } + + // Copy constructor from other Vectorized + Vectorized(const Vectorized* vec, bool childless = false) + { + if (!childless) elements_ = vec->elements_; + } + + // Copy constructor from other base vector + Vectorized(const sass::vector& vec, bool childless = false) + { + if (!childless) elements_ = vec; + } + + // Move constructor from other base vector + Vectorized(sass::vector&& vec, bool childless = false) + { + if (!childless) elements_ = std::move(vec); + } + + // Allow destructor overloading + // virtual ~Vectorized() {}; + + // Some simple method delegations + T& last() { return elements_.back(); } + T& first() { return elements_.front(); } + const T& last() const { return elements_.back(); } + const T& first() const { return elements_.front(); } + bool empty() const { return elements_.empty(); } + void clear() { return elements_.clear(); } + size_t size() const { return elements_.size(); } + + // Check underlying containers for equality + // Note: maybe we could gain some speed by checking + // the computed hash first, before doing full test? + bool operator== (const Vectorized& rhs) const + { + // Abort early if sizes do not match + if (size() != rhs.size()) return false; + // Otherwise test each node for object equality in order + return std::equal(begin(), end(), rhs.begin(), ObjEqualityFn); + } + + // Derive unequal operator from equality check + bool operator!= (const Vectorized& rhs) const + { + return !(*this == rhs); + } + + T& at(size_t i) { return elements_.at(i); } + T& get(size_t i) { return elements_[i]; } + T& operator[](size_t i) { return elements_[i]; } + + const T& at(size_t i) const { return elements_.at(i); } + const T& get(size_t i) const { return elements_[i]; } + // ToDo: might insert am item? (update ordered list) + const T& operator[](size_t i) const { return elements_[i]; } + + // Implicitly get the sass::vector from our object + // Makes the Vector directly assignable to sass::vector + // You are responsible to make a copy if needed + // Note: since this returns the real object, we can't + // Note: guarantee that the hash will not get out of sync + operator sass::vector& () { return elements_; } + operator const sass::vector& () const { return elements_; } + + // Explicitly request all elements as a real sass::vector + // You are responsible to make a copy if needed + // Note: since this returns the real object, we can't + // Note: guarantee that the hash will not get out of sync + sass::vector& elements() { return elements_; } + const sass::vector& elements() const { return elements_; } + + // Insert all items from compatible vector + void concat(const sass::vector& v) + { + if (v.empty()) return; + elements_.insert(end(), + v.begin(), v.end()); + } + + // Insert all items from compatible vector + void concat(sass::vector&& v) + { + if (v.empty()) return; + elements_.insert(elements_.end(), + std::make_move_iterator(v.begin()), + std::make_move_iterator(v.end())); + } + + // Syntactic sugar for pointers + void concat(const Vectorized* v) + { + if (v != nullptr) { + return concat(*v); + } + } + + // Insert one item on the front + void unshift(const T& element) + { + elements_.insert(begin(), + std::copy(element)); + } + + // Insert one item on the front + void unshift(T&& element) + { + elements_.insert(begin(), + std::move(element)); + } + + // Remove and return item on the front + T shift() { + T head = first(); + elements_.erase(begin()); + return head; + } + + // Remove and return item on the back + T pop() { + T tail = last(); + elements_.pop_back(); + return tail; + } + + // Insert one item on the back + // ToDo: rename this to push? + void append(const T& element) + { + elements_.emplace_back(element); + } + + // Insert one item on the back + // ToDo: rename this to push? + void append(T&& element) + { + elements_.emplace_back(std::move(element)); + } + + // Check if an item already exists + // Uses underlying object `operator==` + // E.g. compares the actual objects + bool contains(const T& el) const + { + for (const T& rhs : elements_) { + // Test the underlying objects for equality + // A std::find checks for pointer equality + if (ObjEqualityFn(el, rhs)) { + return true; + } + } + return false; + } + + // Check if an item already exists + // Uses underlying object `operator==` + // E.g. compares the actual objects + bool contains(const V* el) const + { + for (const T& rhs : elements_) { + // Test the underlying objects for equality + // A std::find checks for pointer equality + if (PtrObjEqualityFn(el, rhs.ptr())) { + return true; + } + } + return false; + } + + // This might be better implemented as `operator=`? + void elementsM(const sass::vector& e) + { + elements_ = e; + } + + // This might be better implemented as `operator=`? + void elementsM(sass::vector&& e) + { + elements_ = std::move(e); + } + + template + typename sass::vector::iterator insert(P position, const T& val) { + return elements_.insert(position, val); + } + + template + typename sass::vector::iterator insert(P position, T&& val) { + return elements_.insert(position, std::move(val)); + } + + size_t hash() const + { + if (hash_ == 0) { + hash_start(hash_, typeid(Vectorized).hash_code()); + for (auto child : elements_) { + hash_combine(hash_, child->hash()); + } + } + return hash_; + } + + typename sass::vector::iterator end() { return elements_.end(); } + typename sass::vector::iterator begin() { return elements_.begin(); } + typename sass::vector::const_iterator end() const { return elements_.end(); } + typename sass::vector::const_iterator begin() const { return elements_.begin(); } + typename sass::vector::iterator erase(typename sass::vector::iterator el) { return elements_.erase(el); } + typename sass::vector::const_iterator erase(typename sass::vector::const_iterator el) { return elements_.erase(el); } + + }; + + ///////////////////////////////////////////////////////////////////////// + // Mixin class for AST nodes that should behave like a hash table. Uses an + // extra internally to maintain insertion order for iteration. + ///////////////////////////////////////////////////////////////////////// + template + class Hashed { + + public: + + using ordered_map_type = typename OrderedMap< + K, T, ObjHash, ObjEquality, + Sass::Allocator>, + sass::vector> + >; + + protected: + + ordered_map_type elements_; + + // Hash is only calculated once and afterwards the value + // must not be mutated, which is the case with how sass + // works, although we must be a bit careful not to alter + // any value that has already been added to a set or map. + // Must create a copy if you need to alter such an object. + mutable size_t hash_; + + public: + + Hashed() + : elements_(), + hash_(0) + { + // elements_.reserve(s); + } + + // Copy constructor + Hashed(const Hashed& copy) : + elements_(), hash_(0) + { + // this seems to expensive!? + // elements_.reserve(copy.size()); + elements_ = copy.elements_; + }; + + // Move constructor + Hashed(Hashed&& move) noexcept : + elements_(std::move(move.elements_)), + hash_(move.hash_) {}; + + // Move constructor + Hashed(ordered_map_type&& values) : + elements_(std::move(values)), + hash_(0) {}; + + // virtual ~Hashed() {} + + size_t size() const { return elements_.size(); } + bool empty() const { return elements_.empty(); } + + bool has(const K& k) const { + return elements_.count(k) == 1; + } + + T at(const K& k) const { + auto it = elements_.find(k); + if (it == elements_.end()) return {}; + else return it->second; + } + + bool erase(const K& key) + { + return elements_.erase(key) != 0; + } + + typename ordered_map_type::const_iterator find(const K& key) const + { + return elements_.find(key); + } + + void insert(std::pair&& kv) + { + elements_.insert(kv); + } + + void insert(const std::pair& kv) + { + elements_.insert(kv); + } + + void insert(const K& key, const T& val) + { + insert(std::make_pair(key, val)); + } + + void insertOrSet(std::pair& kv) + { + auto exists = elements_.find(kv.first); + if (exists == elements_.end()) { + // Insert a new entry + elements_.insert(kv); + } + else { + // Update existing entry + exists.value() = kv.second; + } + } + + void insertOrSet(const K& key, const T& val) + { + elements_[key] = val; + } + + void insertOrSet(const K& key, T&& val) + { + elements_[key] = std::move(val); + } + + // Return unmodifiable reference + const ordered_map_type& elements() const { + return elements_; + } + + const sass::vector keys() const { + sass::vector list; + for (auto kv : elements_) { + list.emplace_back(kv.first); + } + return list; + } + const sass::vector values() const { + sass::vector list; + for (auto kv : elements_) { + list.emplace_back(kv.second); + } + return list; + } + + size_t hash() const + { + if (hash_ == 0) { + hash_start(hash_, typeid(this).hash_code()); + for (auto kv : elements_) { + hash_combine(hash_, kv.first->hash()); + hash_combine(hash_, kv.second->hash()); + } + } + return hash_; + } + + typename ordered_map_type::iterator end() { return elements_.end(); } + typename ordered_map_type::iterator begin() { return elements_.begin(); } + typename ordered_map_type::const_iterator end() const { return elements_.end(); } + typename ordered_map_type::const_iterator begin() const { return elements_.begin(); } + + }; + +}; + +#endif diff --git a/src/ast_css.cpp b/src/ast_css.cpp new file mode 100644 index 0000000000..b18697c75f --- /dev/null +++ b/src/ast_css.cpp @@ -0,0 +1,491 @@ +#include "ast_css.hpp" + +#include "ast_selectors.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssNode::CssNode( + const SourceSpan& pstate) : + AstNode(pstate) + {} + + CssNode::CssNode(const CssNode* ptr) : + AstNode(ptr) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssParentNode::CssParentNode( + const SourceSpan& pstate, + CssParentNode* parent, + CssNodeVector&& children) : + CssNode(pstate), + Vectorized(std::move(children)), + parent_(parent) + {} + + CssParentNode::CssParentNode( + const CssParentNode* ptr, + bool childless) : + CssNode(ptr), + Vectorized(ptr, childless), + parent_(ptr->parent_) + {} + + // Adds [node] as a child of the given [parent]. The parent + // is copied unless it's the latter most child of its parent. + void CssParentNode::addChildAt(CssParentNode* child, bool outOfOrder) + { + // Check if we have a valid parent + if (outOfOrder && parent() != nullptr) { + // Check that parent is visible css + // if (!parent()->isInvisibleCss()) { + // Skip if we are last item in parent + if (parent()->last() != this) { + // Get iterator to following siblings + auto it = parent()->begin(); + // Search ourself inside parent + while (it != parent()->end()) { + if (it->ptr() == this) break; + it += 1; + } + // Search for first visible sibling + while (++it != parent()->end()) { + // Special context for invisibility! + // dart calls this out to the parent + const CssNode* sibling = *it; + if (!sibling->isInvisibleCss()) { + // Retain and append copy of parent + auto copy = SASS_MEMORY_RESECT(this); + parent()->addChildAt(copy, false); + copy->elements_.push_back(child); + child->parent(copy); + return; + } + } + } + // } + } + // Add child to parent + child->parent(this); + append(child); + } + // EO addChildAt + + bool CssParentNode::isInvisibleCss() const + { + for (auto child : elements()) { + if (!child->isInvisibleCss()) { + return false; + } + } + return true; + } + + void CssParentNode::addNode(CssNode* child) + { + elements_.push_back(child); + } + + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssRoot::CssRoot( + const SourceSpan& pstate, + CssNodeVector&& children) : + CssParentNode( + pstate, nullptr, + std::move(children)) + {} + + CssRoot::CssRoot( + const CssRoot* ptr, + bool childless) : + CssParentNode( + ptr, childless) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssString::CssString( + const SourceSpan& pstate, + const sass::string& text) : + AstNode(pstate), + text_(text) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssStringList::CssStringList( + const SourceSpan& pstate, + StringVector&& texts) : + AstNode(pstate), + texts_(std::move(texts)) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssAtRule::CssAtRule( + const SourceSpan& pstate, + CssParentNode* parent, + CssString* name, + CssString* value, + bool isChildless, + CssNodeVector&& children) : + CssParentNode( + pstate, parent, + std::move(children)), + name_(name), + value_(value), + isChildless_(isChildless) + {} + + CssAtRule::CssAtRule( + const CssAtRule* ptr, + bool childless) : + CssParentNode( + ptr, childless), + name_(ptr->name_), + value_(ptr->value_), + isChildless_(ptr->isChildless_) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssComment::CssComment( + const SourceSpan& pstate, + const sass::string& text, + bool preserve) : + CssNode(pstate), + text_(text), + isPreserved_(preserve) + {} + + CssComment::CssComment( + const CssComment* ptr) : + CssNode(ptr), + text_(ptr->text_), + isPreserved_(ptr->isPreserved_) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssDeclaration::CssDeclaration( + const SourceSpan& pstate, + CssString* name, + Value* value, + bool is_custom_property) : + CssNode(pstate), + name_(name), + value_(value), + is_custom_property_(is_custom_property) + {} + + CssDeclaration::CssDeclaration( + const CssDeclaration* ptr) : + CssNode(ptr), + name_(ptr->name_), + value_(ptr->value_), + is_custom_property_(ptr->is_custom_property_) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + CssImport::CssImport( + const SourceSpan& pstate, + CssString* url, + CssString* supports, + CssMediaQueryVector media) : + CssNode(pstate), + url_(url), + supports_(supports), + media_(media), + outOfOrder_(false) + {} + + // Copy constructor + CssImport::CssImport( + const CssImport* ptr) : + CssNode(ptr), + url_(ptr->url_), + supports_(ptr->supports_), + media_(ptr->media_), + outOfOrder_(ptr->outOfOrder_) + {} + + ///////////////////////////////////////////////////////////////////////// + // A block within a `@keyframes` rule. + // For example, `10% {opacity: 0.5}`. + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + CssKeyframeBlock::CssKeyframeBlock( + const SourceSpan& pstate, + CssParentNode* parent, + CssStringList* selector, + CssNodeVector&& children) : + CssParentNode( + pstate, parent, + std::move(children)), + selector_(selector) + {} + + // Copy constructor + CssKeyframeBlock::CssKeyframeBlock( + const CssKeyframeBlock* ptr, + bool childless) : + CssParentNode( + ptr, childless), + selector_(ptr->selector_) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssStyleRule::CssStyleRule( + const SourceSpan& pstate, + CssParentNode* parent, + SelectorList* selector, + CssNodeVector&& children) : + CssParentNode( + pstate, parent, + std::move(children)), + selector_(selector) + {} + + CssStyleRule::CssStyleRule( + const CssStyleRule* ptr, + bool childless) : + CssParentNode( + ptr, childless), + selector_(ptr->selector_) + {} + + ///////////////////////////////////////////////////////////////////////// + + bool CssStyleRule::isInvisibleCss() const + { + bool sel_invisible = true; + if (const SelectorList* sl = selector()) { + for (auto selector : sl->elements()) { + if (!selector->hasInvisible()) { + sel_invisible = false; + break; + } + } + } + if (sel_invisible) return true; + for (const CssNode* item : elements()) { + if (!item->isInvisibleCss()) { + return false; + } + } + return true; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssSupportsRule::CssSupportsRule( + const SourceSpan& pstate, + CssParentNode* parent, + ValueObj condition, + CssNodeVector&& children) : + CssParentNode( + pstate, parent, + std::move(children)), + condition_(condition) + {} + + CssSupportsRule::CssSupportsRule( + const CssSupportsRule* ptr, + bool childless) : + CssParentNode( + ptr, childless), + condition_(ptr->condition_) + {} + + ///////////////////////////////////////////////////////////////////////// + // A plain CSS `@media` rule after it has been evaluated. + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + CssMediaRule::CssMediaRule( + const SourceSpan& pstate, + CssParentNode* parent, + const CssMediaQueryVector& queries, + CssNodeVector&& children) : + CssParentNode( + pstate, parent, + std::move(children)), + queries_(queries) + {} + + // Copy constructor + CssMediaRule::CssMediaRule( + const CssMediaRule* ptr, + bool childless) : + CssParentNode( + ptr, childless), + queries_(ptr->queries_) + {} + + // Used by Extension::assertCompatibleMediaContext + bool CssMediaRule::operator== (const CssMediaRule& rhs) const { + return queries_ == rhs.queries_; + } + // EO operator== + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssMediaQuery::CssMediaQuery( + const SourceSpan& pstate, + const sass::string& type, + const sass::string& modifier, + const StringVector& features) : + AstNode(pstate), + type_(type), + modifier_(modifier), + features_(features) + {} + + CssMediaQuery::CssMediaQuery( + const SourceSpan& pstate, + sass::string&& type, + sass::string&& modifier, + StringVector&& features) : + AstNode(pstate), + type_(std::move(type)), + modifier_(std::move(modifier)), + features_(std::move(features)) + {} + + // Used by Extension::assertCompatibleMediaContext + bool CssMediaQuery::operator==(const CssMediaQuery& rhs) const + { + return type_ == rhs.type_ + && modifier_ == rhs.modifier_ + && features_ == rhs.features_; + } + // EO operator== + + // Implemented after dart-sass (maybe move to other class?) + CssMediaQuery* CssMediaQuery::merge(CssMediaQuery* other) + { + + // Import namespace locally + using namespace StringUtils; + + // Get a few references from both objects + const sass::string& thisType(this->type()); + const sass::string& otherType(other->type()); + const sass::string& thisModifier(this->modifier()); + const sass::string& otherModifier(other->modifier()); + const StringVector& thisFeatures(this->features()); + const StringVector& otherFeatures(other->features()); + + // Check for the most simplistic case first + if (thisType.empty() && otherType.empty()) { + StringVector features; + features.reserve(thisFeatures.size() + otherFeatures.size()); + features.insert(features.end(), thisFeatures.begin(), thisFeatures.end()); + features.insert(features.end(), otherFeatures.begin(), otherFeatures.end()); + return SASS_MEMORY_NEW(CssMediaQuery, pstate(), + "", "", std::move(features)); + } + + // bool typesAreEqual(equalsIgnoreCase(thisType, otherType)); + bool thisMatchesAll(this->matchesAllTypes()); + bool otherMatchesAll(other->matchesAllTypes()); + bool thisModifierIsNot(equalsIgnoreCase(thisModifier, "not", 3)); + bool otherModifierIsNot(equalsIgnoreCase(otherModifier, "not", 3)); + + // The queries have different "not" modifier + if (thisModifierIsNot != otherModifierIsNot) { + // If types are equal, we can merge some cases + if (equalsIgnoreCase(thisType, otherType)) { + // Sort arrays into shorter and bigger one + const StringVector& negativeFeatures = thisModifierIsNot ? thisFeatures : otherFeatures; + const StringVector& positiveFeatures = thisModifierIsNot ? otherFeatures : thisFeatures; + // If the negative features are a subset of the positive features, the + // query is empty. For example, `not screen and (color)` has no + // intersection with `screen and (color) and (grid)`. + // However, `not screen and (color)` *does* intersect with `screen and + // (grid)`, because it means `not (screen and (color))` and so it allows + // a screen with no color but with a grid. + if (listIsSubsetOrEqual(negativeFeatures, positiveFeatures)) { + return SASS_MEMORY_NEW(CssMediaQuery, pstate(), ""); + } + // Otherwise we can't merge them + return nullptr; + } + // We established that types differ + // Check if one matches everything + if (thisMatchesAll) return nullptr; + if (otherMatchesAll) return nullptr; + // Check which modifier was "not" + return thisModifierIsNot ? other : this; + } + // Our modifier equals "not" + else if (thisModifierIsNot) { + // CSS has no way of representing "neither screen nor print". + if (!equalsIgnoreCase(thisType, otherType)) return nullptr; + // Sort arrays into shorter and bigger one + bool thisIsBigger(thisFeatures.size() > otherFeatures.size()); + const StringVector& moreFeatures = thisIsBigger ? thisFeatures : otherFeatures; + const StringVector& fewerFeatures = thisIsBigger ? otherFeatures : thisFeatures; + // If one set of features is a superset of the other, + // use those features because they're strictly narrower. + if (listIsSubsetOrEqual(fewerFeatures, moreFeatures)) { + // Ignore the lesser features (included in other) + return SASS_MEMORY_NEW(CssMediaQuery, pstate(), + thisType, thisModifier, moreFeatures); + } + // Otherwise, there's no way to + // represent the intersection. + return nullptr; + } + + // Check if types are not the same + if (!equalsIgnoreCase(thisType, otherType)) { + // Check that nothing has an "all" modifier + if (!thisMatchesAll && !otherMatchesAll) { + return SASS_MEMORY_NEW(CssMediaQuery, pstate(), ""); + } + } + + // Concatenate both features + StringVector features; + features.reserve(thisFeatures.size() + otherFeatures.size()); + features.insert(features.end(), thisFeatures.begin(), thisFeatures.end()); + features.insert(features.end(), otherFeatures.begin(), otherFeatures.end()); + + // Check if we should return other query + if (this->matchesAllTypes() && !(otherMatchesAll && thisType.empty())) { + return SASS_MEMORY_NEW(CssMediaQuery, pstate(), + otherType, otherModifier, std::move(features)); + } + + // Return same query with concatenated features + return SASS_MEMORY_NEW(CssMediaQuery, pstate(), + thisType, thisModifier, std::move(features)); + } + // EO merge + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/ast_css.hpp b/src/ast_css.hpp new file mode 100644 index 0000000000..b2056ec60f --- /dev/null +++ b/src/ast_css.hpp @@ -0,0 +1,547 @@ +#ifndef SASS_AST_CSS_HPP +#define SASS_AST_CSS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_nodes.hpp" +#include "visitor_css.hpp" +// #include "ast_def_macros.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssNode : public AstNode, + public CssVisitable + { + + private: + + // Whether this was generated from the last node in a + // nested Sass tree that got flattened during evaluation. + // ADD_CONSTREF(bool, isGroupEnd); + + public: + + // Value constructor + CssNode(const SourceSpan& pstate); + + // Copy constructor + CssNode(const CssNode* ptr); + + // Virtual destructor + // virtual ~CssNode() {} + + // Needed here to avoid ambiguity from base-classes!?? + virtual void accept(CssVisitor* visitor) override = 0; + + virtual bool isInvisibleCss() const { return false; } + + // Returns the at-rule name for [node], or `null` if it's not an at-rule. + virtual const sass::string& getAtRuleName() const { return Strings::empty; } + + // size_t tabs() const { return 0; } + // void tabs(size_t tabs) const { } + + // Declare up-casting methods + DECLARE_ISA_CASTER(CssMediaRule); + DECLARE_ISA_CASTER(CssStyleRule); + DECLARE_ISA_CASTER(CssSupportsRule); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssParentNode : public CssNode, + public Vectorized + { + private: + + // This must be a pointer to avoid circular references + // Means it has a possibility of being a dangling pointer + ADD_PROPERTY(CssParentNode*, parent); + + public: + + // Value constructor + CssParentNode( + const SourceSpan& pstate, + CssParentNode* parent, + CssNodeVector&& children = {}); + + // Copy constructor + CssParentNode( + const CssParentNode* ptr, + bool childless = false); + + void addChildAt(CssParentNode* node, + bool doStrangeStuff = false); + + void addNode(CssNode* element); + + bool isInvisibleCss() const override; + + + // Must be implemented in derived classes + virtual CssParentNode* copy(SASS_MEMORY_ARGS bool childless) const = 0; + + // + + CssParentNode* bubbleThrough(bool stopAtMediaRule = false) + { + return parent_ && bubbles(stopAtMediaRule) ? + parent_->bubbleThrough(stopAtMediaRule) : this; + } + + // Media rules are sometimes transparent, sometimes not + virtual bool bubbles(bool stopAtMediaRule = false) const { + return false; + } + + // Declare up-casting methods + DECLARE_ISA_CASTER(CssAtRule); + }; + + ///////////////////////////////////////////////////////////////////////// + // A plain CSS string + ///////////////////////////////////////////////////////////////////////// + + class CssString final : public AstNode + { + private: + + ADD_CONSTREF(sass::string, text); + + public: + + // Value constructor + CssString( + const SourceSpan& pstate, + const sass::string& text); + + bool empty() const { return text_.empty(); } + + }; + + ///////////////////////////////////////////////////////////////////////// + // A plain list of CSS strings + ///////////////////////////////////////////////////////////////////////// + + class CssStringList final : public AstNode + { + private: + + ADD_CONSTREF(StringVector, texts); + + public: + + // Value constructor + CssStringList( + const SourceSpan& pstate, + StringVector&& texts); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssComment final : public CssNode { + ADD_CONSTREF(sass::string, text); + ADD_CONSTREF(bool, isPreserved); + public: + CssComment(const SourceSpan& pstate, const sass::string& text, bool preserve = false); + CssComment(const CssComment* ptr); + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssComment(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssDeclaration final : public CssNode { + // The name of this declaration. + ADD_CONSTREF(CssStringObj, name); + // The value of this declaration. + ADD_CONSTREF(ValueObj, value); + ADD_CONSTREF(bool, is_custom_property); + public: + CssDeclaration(const CssDeclaration* ptr); + CssDeclaration(const SourceSpan& pstate, CssString* name, Value* value, + bool is_custom_property = false); + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssDeclaration(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // A css import is static in nature and + // can only have one single import url. + class CssImport final : public CssNode { + + // The url including quotes. + ADD_CONSTREF(CssStringObj, url); + + // The supports condition attached to this import. + ADD_CONSTREF(CssStringObj, supports); + + // The media query attached to this import. + ADD_CONSTREF(CssMediaQueryVector, media); + + // Flag to hoist import to the top. + ADD_CONSTREF(bool, outOfOrder); + + public: + + // Standard value constructor + CssImport( + const SourceSpan& pstate, + CssString* url = nullptr, + CssString* supports = nullptr, + CssMediaQueryVector media = {}); + + // Copy constructor + CssImport(const CssImport* ptr); + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssImport(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssRoot final : public CssParentNode + { + public: + + // Value constructor + CssRoot( + const SourceSpan& pstate, + CssNodeVector&& children = {}); + + // Copy constructor + CssRoot( + const CssRoot* ptr, + bool childless = false); + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssRoot(this); + } + + CssRoot* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CssRoot, this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssAtRule final : public CssParentNode + { + private: + + ADD_CONSTREF(CssStringObj, name); + + ADD_CONSTREF(CssStringObj, value); + + // Whether the rule has no children and should be emitted + // without curly braces. This implies `children.isEmpty`, + // but the reverse is not true - for a rule like `@foo {}`, + // [children] is empty but [isChildless] is `false`. + // It means we didn't see any `{` when parsed. + ADD_CONSTREF(bool, isChildless); + + public: + + // Value constructor + CssAtRule( + const SourceSpan& pstate, + CssParentNode* parent, + CssString* name, + CssString* value, + bool isChildless = false, + CssNodeVector&& children = {}); + + // Copy constructor + CssAtRule( + const CssAtRule* ptr, + bool childless = false); + + bool isInvisibleCss() const override final { + return false; + } + + // Returns the at-rule name for [node], or `null` if it's not an at-rule. + const sass::string& getAtRuleName() const override final { + return name()->text(); + } + + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssAtRule(this); + } + + CssAtRule* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CssAtRule, this); + } + + IMPLEMENT_ISA_CASTER(CssAtRule); + }; + + ///////////////////////////////////////////////////////////////////////// + // A block within a `@keyframes` rule. + // For example, `10% {opacity: 0.5}`. + ///////////////////////////////////////////////////////////////////////// + class CssKeyframeBlock final : public CssParentNode + { + private: + + // The selector for this block. + ADD_CONSTREF(CssStringListObj, selector); + + public: + + // Value constructor + CssKeyframeBlock( + const SourceSpan& pstate, + CssParentNode* parent, + CssStringList* selector, + CssNodeVector&& children = {}); + + // Copy constructor + CssKeyframeBlock( + const CssKeyframeBlock* ptr, + bool childless = false); + + // Return a copy with empty children + // CssKeyframeBlock* copyWithoutChildren(); + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssKeyframeBlock(this); + } + + CssKeyframeBlock* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CssKeyframeBlock, this); + } + + }; + // EO CssKeyframeBlock + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssStyleRule final : public CssParentNode + { + private: + + ADD_CONSTREF(SelectorListObj, selector); + + public: + + // Value constructor + CssStyleRule( + const SourceSpan& pstate, + CssParentNode* parent, + SelectorList* selector, + CssNodeVector&& children = {}); + + // Copy constructor + CssStyleRule( + const CssStyleRule* ptr, + bool childless = false); + + // Selector and one child must be visible + bool isInvisibleCss() const override final; + + // Media rules are sometimes transparent, sometimes not + bool bubbles(bool stopAtMediaRule) const override final { return true; } + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssStyleRule(this); + } + + // Declare via macro to allow line/col debugging + CssStyleRule* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CssStyleRule, this, childless); + } + + IMPLEMENT_ISA_CASTER(CssStyleRule); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssSupportsRule final : public CssParentNode + { + private: + + ADD_CONSTREF(ValueObj, condition); + + public: + + // Value constructor + CssSupportsRule( + const SourceSpan& pstate, + CssParentNode* parent, + ValueObj condition, + CssNodeVector&& children = {}); + + // Copy constructor + CssSupportsRule( + const CssSupportsRule* ptr, + bool childless = false); + + // Returns the at-rule name for [node], or `null` if it's not an at-rule. + const sass::string& getAtRuleName() const override final { return Strings::supports; } + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssSupportsRule(this); + } + + // Declare via macro to allow line/col debugging + CssSupportsRule* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CssSupportsRule, this, childless); + } + + IMPLEMENT_ISA_CASTER(CssSupportsRule); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Media Queries after they have been evaluated + // Representing the static or resulting css + class CssMediaQuery final : public AstNode { + + // The media type, for example "screen" or "print". + // This may be `null`. If so, [features] will not be empty. + ADD_CONSTREF(sass::string, type); + + // The modifier, probably either "not" or "only". + // This may be `null` if no modifier is in use. + ADD_CONSTREF(sass::string, modifier); + + // Feature queries, including parentheses. + ADD_REF(StringVector, features); + + public: + + // Value copy constructor + CssMediaQuery( + const SourceSpan& pstate, + const sass::string& type, + const sass::string& modifier, + const StringVector& features); + + // Value move constructor + CssMediaQuery( + const SourceSpan& pstate, + sass::string&& type, + sass::string&& modifier = "", + StringVector&& features = {}); + + // Returns true if this query is empty + // Meaning it has no type and features + bool empty() const { + return type_.empty() + && modifier_.empty() + && features_.empty(); + } + + // Whether this media query matches all media types. + bool matchesAllTypes() const { + return type_.empty() || StringUtils::equalsIgnoreCase(type_, "all", 3); + } + + // Check if two instances are considered equal + bool operator== (const CssMediaQuery& rhs) const; + + // Merges this with [other] and adds a query that matches the intersection + // of both inputs to [result]. Returns false if the result is unrepresentable + CssMediaQuery* merge(CssMediaQuery* other); + + }; + + ///////////////////////////////////////////////////////////////////////// + // A plain CSS `@media` rule after it has been evaluated. + ///////////////////////////////////////////////////////////////////////// + class CssMediaRule final : public CssParentNode + { + private: + + // The queries for this rule (this is never empty). + ADD_CONSTREF(Vectorized, queries); + + public: + + // Value constructor + CssMediaRule(const SourceSpan& pstate, + CssParentNode* parent, + const CssMediaQueryVector& queries, + CssNodeVector&& children = {}); + + // Copy constructor + CssMediaRule( + const CssMediaRule* ptr, + bool childless = false); + + // Check if we or any children are invisible + bool isInvisibleCss() const override final { + return queries_.empty() || + CssParentNode::isInvisibleCss(); + } + + // Media rules are sometimes transparent, sometimes not + bool bubbles(bool stopAtMediaRule) const override final { + return stopAtMediaRule == false; + } + + // Returns the at-rule name for [node], or `null` if it's not an at-rule. + const sass::string& getAtRuleName() const override final { return Strings::media; } + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssMediaRule(this); + } + + // Check if two instances are considered equal + // Used by Extension::assertCompatibleMediaContext + bool operator== (const CssMediaRule& rhs) const; + + // Declare via macro to allow line/col debugging + CssMediaRule* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CssMediaRule, this, childless); + } + + IMPLEMENT_ISA_CASTER(CssMediaRule); + }; + // EO CssMediaRule + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + + +#endif diff --git a/src/ast_def_macros.hpp b/src/ast_def_macros.hpp index 8a889cd9cc..0f0b4b3d9c 100644 --- a/src/ast_def_macros.hpp +++ b/src/ast_def_macros.hpp @@ -1,140 +1,224 @@ #ifndef SASS_AST_DEF_MACROS_H #define SASS_AST_DEF_MACROS_H +#include "memory/allocator.hpp" + +#ifndef MAX_NESTING +// Note that this limit is not an exact science +// it depends on various factors, which some are +// not under our control (compile time or even OS +// dependent settings on the available stack size) +// It should fix most common segfault cases though. +#define MAX_NESTING 512 +#endif + // Helper class to switch a flag and revert once we go out of scope template -class LocalOption { +class LocalOption final { private: T* var; // pointer to original variable T orig; // copy of the original option public: - LocalOption(T& var) - { - this->var = &var; - this->orig = var; - } - LocalOption(T& var, T orig) + LocalOption(T& var, T current) : + var(&var), orig(var) { - this->var = &var; - this->orig = var; - *(this->var) = orig; - } - void reset() - { - *(this->var) = this->orig; + *(this->var) = current; } ~LocalOption() { *(this->var) = this->orig; } }; + // Helper class to put something on a vector + // and revert once we go out of scope. + template + class LocalStack final { + private: + sass::vector& cnt; // container + public: + LocalStack(sass::vector& cnt, T push) : + cnt(cnt) + { + cnt.emplace_back(push); + } + ~LocalStack() { + cnt.pop_back(); + } + }; + + // typedef LocalOptions + +// Macros to help create and maintain local and recursive flag states #define LOCAL_FLAG(name,opt) LocalOption flag_##name(name, opt) -#define LOCAL_COUNT(name,opt) LocalOption cnt_##name(name, opt) +#define LOCAL_PTR(var,name,opt) LocalOption flag_##name(name, opt) +#define LOCAL_SELECTOR(name,opt) LocalStack stack_##name(name, opt) #define NESTING_GUARD(name) \ LocalOption cnt_##name(name, name + 1); \ - if (name > MAX_NESTING) throw Exception::NestingLimitError(pstate, traces); \ + if (name > MAX_NESTING) throw Exception::RecursionLimitError(); \ -#define ADD_PROPERTY(type, name)\ -protected:\ - type name##_;\ -public:\ - type name() const { return name##_; }\ - type name(type name##__) { return name##_ = name##__; }\ -private: +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// -#define HASH_PROPERTY(type, name)\ -protected:\ - type name##_;\ -public:\ - type name() const { return name##_; }\ - type name(type name##__) { hash_ = 0; return name##_ = name##__; }\ -private: - -#define ADD_CONSTREF(type, name) \ +#define ADD_REF(type, name) \ protected: \ type name##_; \ public: \ + type& name() { return name##_; } \ const type& name() const { return name##_; } \ - void name(type name##__) { name##_ = name##__; } \ + void name(type&& name##__) { name##_ = std::move(name##__); } \ + void name(const type& name##__) { name##_ = name##__; } \ private: -#define HASH_CONSTREF(type, name) \ -protected: \ - type name##_; \ -public: \ +#define ADD_CONSTREF(type, name)\ +protected:\ + type name##_;\ +public:\ const type& name() const { return name##_; } \ - void name(type name##__) { hash_ = 0; name##_ = name##__; } \ + void name(type&& name##__) { name##_ = std::move(name##__); } \ + void name(const type& name##__) { name##_ = name##__; } \ private: -#ifdef DEBUG_SHARED_PTR - -#define ATTACH_ABSTRACT_AST_OPERATIONS(klass) \ - virtual klass* copy(sass::string, size_t) const = 0; \ - virtual klass* clone(sass::string, size_t) const = 0; \ - -#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \ - klass(const klass* ptr); \ - virtual klass* copy(sass::string, size_t) const override = 0; \ - virtual klass* clone(sass::string, size_t) const override = 0; \ - -#define ATTACH_AST_OPERATIONS(klass) \ - klass(const klass* ptr); \ - virtual klass* copy(sass::string, size_t) const override; \ - virtual klass* clone(sass::string, size_t) const override; \ - -#else - -#define ATTACH_ABSTRACT_AST_OPERATIONS(klass) \ - virtual klass* copy() const = 0; \ - virtual klass* clone() const = 0; \ - -#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \ - klass(const klass* ptr); \ - virtual klass* copy() const override = 0; \ - virtual klass* clone() const override = 0; \ - -#define ATTACH_AST_OPERATIONS(klass) \ - klass(const klass* ptr); \ - virtual klass* copy() const override; \ - virtual klass* clone() const override; \ - -#endif - -#define ATTACH_VIRTUAL_CMP_OPERATIONS(klass) \ - virtual bool operator==(const klass& rhs) const = 0; \ - virtual bool operator!=(const klass& rhs) const { return !(*this == rhs); }; \ - -#define ATTACH_CMP_OPERATIONS(klass) \ - virtual bool operator==(const klass& rhs) const; \ - virtual bool operator!=(const klass& rhs) const { return !(*this == rhs); }; \ - -#ifdef DEBUG_SHARED_PTR +#define ADD_PROPERTY(type, name)\ +protected:\ + type name##_;\ +public:\ + type name() const { return name##_; }\ + void name(type name##__) { name##_ = name##__; }\ +private: - #define IMPLEMENT_AST_OPERATORS(klass) \ - klass* klass::copy(sass::string file, size_t line) const { \ - klass* cpy = SASS_MEMORY_NEW(klass, this); \ - cpy->trace(file, line); \ - return cpy; \ - } \ - klass* klass::clone(sass::string file, size_t line) const { \ - klass* cpy = copy(file, line); \ - cpy->cloneChildren(); \ - return cpy; \ + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + #ifdef DEBUG_SHARED_PTR + + #define SASS_MEMORY_PARAMS file, line, + #define SASS_MEMORY_PARAMS_VOID file, line + #define SASS_MEMORY_POS __FILE__, __LINE__ + #define SASS_MEMORY_POS_VOID __FILE__, __LINE__ + #define SASS_MEMORY_ARGS sass::string file, size_t line, + #define SASS_MEMORY_ARGS_VOID sass::string file, size_t line + + #else + + #define SASS_MEMORY_PARAMS + #define SASS_MEMORY_PARAMS_VOID + #define SASS_MEMORY_POS + #define SASS_MEMORY_POS_VOID + #define SASS_MEMORY_ARGS + #define SASS_MEMORY_ARGS_VOID + + #endif + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Inverting these functions is a bit tricky as there are consequences + // Easiest would be to just switch out the operand, but that would mean + // that the right hand side would determine the implementation. It would + // also invert the arguments given, e.g. for error reporting. Another way + // is to make both comparisons in order to keep left and right arguments. + // #ifdef SASS_OPTIMIZE_CMP_OPS + // bool operator>(const AstNode& rhs) const { return rhs < *this; } + // bool operator<=(const AstNode& rhs) const { return !(rhs < *this); } + // #else + // bool operator>(const AstNode& rhs) const { return !(*this < rhs || *this == rhs); } + // bool operator<=(const AstNode& rhs) const { return *this < rhs || *this == rhs; } + // #endif + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + #define DECLARE_ISA_CASTER(klass) \ + public: virtual klass* isa##klass() { return nullptr; } \ + public: virtual const klass* isa##klass() const { return nullptr; } \ + + #define IMPLEMENT_ISA_CASTER(klass) \ + public: klass* isa##klass() final override { return this; } \ + public: const klass* isa##klass() const final override { return this; } \ + + #define IMPLEMENT_ACCEPT(type, visitor, klass) \ + public: type accept(visitor##Visitor* visitor) override final { \ + return visitor->visit##klass(this); \ } \ -#else + #define IMPLEMENT_EQ_OPERATOR(subklass, klass) \ + public: bool operator==(const subklass& rhs) const override final { \ + auto sel = rhs.isa##klass(); \ + return sel ? *this == *sel : false; \ + } \ + public: bool operator==(const klass& rhs) const; \ - #define IMPLEMENT_AST_OPERATORS(klass) \ - klass* klass::copy() const { \ - return SASS_MEMORY_NEW(klass, this); \ + // Childless argument is passed to ctor + #define IMPLEMENT_SEL_COPY_CHILDREN(klass) \ + public: klass* copy(SASS_MEMORY_ARGS bool childless) const override final { \ + return SASS_MEMORY_NEW_DBG(klass, this, childless); \ } \ - klass* klass::clone() const { \ - klass* cpy = copy(); \ - cpy->cloneChildren(); \ - return cpy; \ + + // Childless argument is ignored on ctor + #define IMPLEMENT_SEL_COPY_IGNORE(klass) \ + public: klass* copy(SASS_MEMORY_ARGS bool childless) const override final { \ + return SASS_MEMORY_NEW_DBG(klass, this); \ } \ -#endif + ///////////////////////////////////////////////////////////////////////// + /* Wrap c++ pointers for C-API to anon-structs */ + ///////////////////////////////////////////////////////////////////////// + #define CAPI_WRAPPER(klass, strukt) \ + struct strukt* wrap() \ + { \ + /* This is a compile time cast and doesn't cost anything */ \ + return reinterpret_cast(this); \ + }; \ + /* Wrap the pointer for C-API */ \ + const struct strukt* wrap() const \ + { \ + /* This is a compile time cast and doesn't cost anything */ \ + return reinterpret_cast(this); \ + }; \ + /* Wrap the pointer for C-API */ \ + static struct strukt* wrap(klass* unwrapped) \ + { \ + /* Ensure we at least catch the most obvious stuff */ \ + if (unwrapped == nullptr) throw std::runtime_error( \ + "Null-Pointer passed to " #klass "::unwrap"); \ + /* Just delegate to wrap */ \ + return unwrapped->wrap(); \ + }; \ + /* Wrap the pointer for C-API */ \ + static const struct strukt* wrap(const klass* unwrapped) \ + { \ + /* Ensure we at least catch the most obvious stuff */ \ + if (unwrapped == nullptr) throw std::runtime_error( \ + "Null-Pointer passed to " #klass "::unwrap"); \ + /* Just delegate to wrap */ \ + return unwrapped->wrap(); \ + }; \ + /* Unwrap the pointer for C-API (potentially unsafe). */ \ + /* You must pass in a pointer you've got via wrap API. */ \ + /* Passing anything else will result in undefined behavior! */ \ + static klass& unwrap(struct strukt* wrapped) \ + { \ + /* Ensure we at least catch the most obvious stuff */ \ + if (wrapped == nullptr) throw std::runtime_error( \ + "Null-Pointer passed to " #klass "::unwrap"); \ + /* This is a compile time cast and doesn't cost anything */ \ + return *reinterpret_cast(wrapped); \ + }; \ + /* Unwrap the pointer for C-API (potentially unsafe). */ \ + /* You must pass in a pointer you've got via wrap API. */ \ + /* Passing anything else will result in undefined behavior! */ \ + static const klass& unwrap(const struct strukt* wrapped) \ + { \ + /* Ensure we at least catch the most obvious stuff */ \ + if (wrapped == nullptr) throw std::runtime_error( \ + "Null-Pointer passed to " #klass "::unwrap"); \ + /* This is a compile time cast and doesn't cost anything */ \ + return *reinterpret_cast(wrapped); \ + }; \ + + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// #endif diff --git a/src/ast_expressions.cpp b/src/ast_expressions.cpp new file mode 100644 index 0000000000..11e3d3ad66 --- /dev/null +++ b/src/ast_expressions.cpp @@ -0,0 +1,268 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "ast_expressions.hpp" +#include "interpolation.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ParentExpression::ParentExpression( + SourceSpan&& pstate) : + Expression(std::move(pstate)) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ValueExpression::ValueExpression( + SourceSpan pstate, + ValueObj value) : + Expression(std::move(pstate)), + value_(value) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + NullExpression::NullExpression( + SourceSpan pstate, + Null* value) : + Expression(std::move(pstate)), + value_(value) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ColorExpression::ColorExpression( + SourceSpan pstate, + Color* value) : + Expression(std::move(pstate)), + value_(value) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + NumberExpression::NumberExpression( + SourceSpan pstate, + Number* value) : + Expression(std::move(pstate)), + value_(value) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + BooleanExpression::BooleanExpression( + SourceSpan pstate, + Boolean* value) : + Expression(std::move(pstate)), + value_(value) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + StringExpression::StringExpression( + SourceSpan pstate, + InterpolationObj text, + bool hasQuotes) : + Expression(std::move(pstate)), + text_(text), + hasQuotes_(hasQuotes) + {} + + // Crutch instead of LiteralExpression + // Note: really not used very often + // ToDo: check for performance impact + StringExpression::StringExpression( + SourceSpan&& pstate, + sass::string&& text, + bool hasQuotes) : + Expression(std::move(pstate)), + text_(SASS_MEMORY_NEW(Interpolation, pstate, + SASS_MEMORY_NEW(String, pstate, std::move(text)))), + hasQuotes_(hasQuotes) + {} + + StringExpression* Interpolation::wrapInStringExpression() { + return SASS_MEMORY_NEW(StringExpression, pstate(), this); + } + + // find best quote_mark by detecting if the string contains any single + // or double quotes. When a single quote is found, we not we want a double + // quote as quote_mark. Otherwise we check if the string contains any double + // quotes, which will trigger the use of single quotes as best quote_mark. + uint8_t StringExpression::findBestQuote() + { + using namespace Character; + bool containsDoubleQuote = false; + for (auto item : text_->elements()) { + if (auto str = item->isaString()) { // Ex + auto& value = str->value(); + for (size_t i = 0; i < value.size(); i++) { + if (value[i] == $single_quote) return $double_quote; + if (value[i] == $double_quote) containsDoubleQuote = true; + } + } + } + return containsDoubleQuote ? $single_quote : $double_quote; + } + + // Interpolation that, when evaluated, produces the syntax of this string. + // Unlike [text], his doesn't resolve escapes and does include quotes for + // quoted strings. If [static] is true, this escapes any `#{` sequences in + // the string. If [quote] is passed, it uses that character as the quote mark; + // otherwise, it determines the best quote to add by looking at the string. + // Note: [static] is not yet implemented in LibSass (find where we need it) + InterpolationObj StringExpression::getAsInterpolation(bool escape, uint8_t quote) + { + + using namespace Character; + + if (!hasQuotes()) return text_; + + if (!quote && hasQuotes()) quote = findBestQuote(); + + InterpolationBuffer buffer(pstate()); + + if (quote != 0) { + buffer.write(quote); + } + + for (auto value : text_->elements()) { + if (ItplString* str = value->isaItplString()) { + sass::string value(str->text()); + for (size_t i = 0; i < value.size(); i++) { + uint8_t codeUnit = value[i]; + if (isNewline(codeUnit)) { + buffer.write($backslash); + buffer.write($a); + if (i != value.size() - 1) { + uint8_t next = value[i + 1]; + if (isWhitespace(next) || isHex(next)) { + buffer.write($space); + } + } + } + else { + if (codeUnit == quote || + codeUnit == $backslash || + (escape && + codeUnit == $hash && + i < value.size() - 1 && + value[i + 1] == $lbrace)) { + buffer.write($backslash); + } + buffer.write(codeUnit); + } + + } + } + else { + buffer.add(value); + } + } + + if (quote != 0) { + buffer.write(quote); + } + + return buffer.getInterpolation(pstate()); + + } + // EO getAsInterpolation + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + MapExpression::MapExpression( + SourceSpan&& pstate) : + Expression(std::move(pstate)), + kvlist_() + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ListExpression::ListExpression( + SourceSpan&& pstate, + SassSeparator separator) : + Expression(std::move(pstate)), + separator_(separator), + hasBrackets_(false) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + BinaryOpExpression::BinaryOpExpression( + SourceSpan&& pstate, + SassOperator operand, + Expression* lhs, + Expression* rhs, + bool allowSlash) : + Expression(std::move(pstate)), + operand_(operand), + left_(lhs), + right_(rhs), + allowsSlash_(allowSlash) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + VariableExpression::VariableExpression( + SourceSpan&& pstate, + const EnvKey& name) : + Expression(std::move(pstate)), + name_(name) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ParenthesizedExpression::ParenthesizedExpression( + SourceSpan&& pstate, + Expression* expression) : + Expression(std::move(pstate)), + expression_(expression) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + UnaryOpExpression::UnaryOpExpression( + SourceSpan&& pstate, + UnaryOpType optype, + ExpressionObj operand) : + Expression(std::move(pstate)), + optype_(optype), + operand_(operand) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + FunctionExpression::FunctionExpression( + SourceSpan pstate, + Interpolation* name, + ArgumentInvocation* arguments, + const sass::string& ns) : + InvocationExpression( + std::move(pstate), + arguments), + ns_(ns), + name_(name), + selfAssign_(false) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/ast_expressions.hpp b/src/ast_expressions.hpp new file mode 100644 index 0000000000..65760419ae --- /dev/null +++ b/src/ast_expressions.hpp @@ -0,0 +1,385 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_EXPRESSIONS_HPP +#define SASS_AST_EXPRESSIONS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_callables.hpp" +#include "environment_stack.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Enum for UnaryOpExpression (value prefix) + enum UnaryOpType { PLUS, MINUS, NOT, SLASH }; + + ///////////////////////////////////////////////////////////////////////// + // The Parent Reference Expression. + ///////////////////////////////////////////////////////////////////////// + class ParentExpression final : public Expression + { + public: + // Value constructor + ParentExpression( + SourceSpan&& pstate); + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitParentExpression(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // An expression that directly embeds a [Value]. This is never + // constructed by the parser. It's only used when ASTs are + // constructed dynamically, as for the `call()` function. + ///////////////////////////////////////////////////////////////////////// + class ValueExpression final : public Expression + { + ADD_CONSTREF(ValueObj, value); + public: + // Value constructor + ValueExpression( + SourceSpan pstate, + ValueObj value); + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitValueExpression(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // The Null Expression. + ///////////////////////////////////////////////////////////////////////// + class NullExpression final : public Expression + { + ADD_CONSTREF(NullObj, value); + public: + // Value constructor + NullExpression( + SourceSpan pstate, + Null* value); + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitNullExpression(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // The Color Expression. + ///////////////////////////////////////////////////////////////////////// + class ColorExpression final : public Expression + { + ADD_CONSTREF(ColorObj, value); + public: + // Value constructor + ColorExpression( + SourceSpan pstate, + Color* color); + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitColorExpression(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // The Number Expression. + ///////////////////////////////////////////////////////////////////////// + class NumberExpression final : public Expression + { + ADD_CONSTREF(NumberObj, value); + public: + // Value constructor + NumberExpression( + SourceSpan pstate, + Number* value); + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitNumberExpression(this); + } + IMPLEMENT_ISA_CASTER(NumberExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // The Boolean Expression. + ///////////////////////////////////////////////////////////////////////// + class BooleanExpression final : public Expression + { + ADD_CONSTREF(BooleanObj, value); + public: + // Value constructor + BooleanExpression( + SourceSpan pstate, + Boolean* value); + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitBooleanExpression(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // String expression holding an optionally quoted interpolation + ///////////////////////////////////////////////////////////////////////// + class StringExpression final : public Expression + { + ADD_CONSTREF(InterpolationObj, text); + ADD_CONSTREF(bool, hasQuotes); + public: + // Value constructor + StringExpression( + SourceSpan pstate, + InterpolationObj text, + bool hasQuotes = false); + // Crutch instead of LiteralExpression + // Note: really not used very often + // ToDo: check for performance impact + StringExpression( + SourceSpan&& pstate, + sass::string&& text, + bool hasQuotes = false); + // Interpolation that, when evaluated, produces the syntax of this string. + // Unlike [text], his doesn't resolve escapes and does include quotes for + // quoted strings. If [static] is true, this escapes any `#{` sequences in + // the string. If [quote] is passed, it uses that character as the quote mark; + // otherwise, it determines the best quote to add by looking at the string. + InterpolationObj getAsInterpolation( + bool escape = false, + uint8_t quote = 0); + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitStringExpression(this); + } + private: + + // find best quote_mark by detecting if the string contains any single + // or double quotes. When a single quote is found, we not we want a double + // quote as quote_mark. Otherwise we check if the string contains any double + // quotes, which will trigger the use of single quotes as best quote_mark. + uint8_t findBestQuote(); + + }; + + ///////////////////////////////////////////////////////////////////////// + // Map expression hold an even list of key and value expressions. + ///////////////////////////////////////////////////////////////////////// + class MapExpression final : public Expression + { + ADD_CONSTREF(ExpressionVector, kvlist); + public: + // Value constructor + MapExpression( + SourceSpan&& pstate); + // Append key or value + void append(Expression* expression) { + kvlist_.emplace_back(expression); + } + // Return number of items + size_t size() const { + return kvlist_.size(); + } + // Return expression at position + Expression* get(size_t position) { + return kvlist_[position]; + } + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitMapExpression(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + class ListExpression final : public Expression + { + ADD_CONSTREF(ExpressionVector, contents); + ADD_CONSTREF(SassSeparator, separator); + ADD_CONSTREF(bool, hasBrackets); + public: + // Value constructor + ListExpression( + SourceSpan&& pstate, + SassSeparator separator = SASS_UNDEF); + // Append a single expression + void append(Expression* expression) { + contents_.emplace_back(expression); + } + // Move items into our vector (append) + void concat(ExpressionVector&& expressions) { + contents_.insert(contents_.end(), + std::make_move_iterator(expressions.begin()), + std::make_move_iterator(expressions.end())); + } + // Return number of items + size_t size() const { + return contents_.size(); + } + // Return expression at position + Expression* get(size_t position) { + return contents_[position]; + } + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitListExpression(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // Arithmetic negation (logical negation is just an ordinary function call). + ///////////////////////////////////////////////////////////////////////// + + class UnaryOpExpression final : public Expression + { + ADD_CONSTREF(UnaryOpType, optype); + ADD_CONSTREF(ExpressionObj, operand); + public: + // Value constructor + UnaryOpExpression( + SourceSpan&& pstate, + UnaryOpType optype, + ExpressionObj operand); + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitUnaryOpExpression(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // Binary expressions. Represents logical, relational, and arithmetic ops. + // Templatized to avoid large switch statements and repetitive sub-classing. + ///////////////////////////////////////////////////////////////////////// + class BinaryOpExpression final : public Expression + { + ADD_CONSTREF(SassOperator, operand); + ADD_CONSTREF(ExpressionObj, left); + ADD_CONSTREF(ExpressionObj, right); + // ADD_CONSTREF(bool, ws_before); + // ADD_CONSTREF(bool, ws_after); + ADD_CONSTREF(bool, allowsSlash); + public: + // Value constructor + BinaryOpExpression( + SourceSpan&& pstate, + SassOperator operand, + Expression* lhs, + Expression* rhs, + bool allowSlash = false); + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitBinaryOpExpression(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // VariableExpression references. + ///////////////////////////////////////////////////////////////////////// + class VariableExpression final : public Expression + { + ADD_CONSTREF(EnvKey, name); + ADD_REF(sass::vector, vidxs); + public: + // Value constructor + VariableExpression( + SourceSpan&& pstate, + const EnvKey& name); + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitVariableExpression(this); + } + IMPLEMENT_ISA_CASTER(VariableExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + class ParenthesizedExpression final : public Expression + { + ADD_CONSTREF(ExpressionObj, expression) + public: + ParenthesizedExpression( + SourceSpan&& pstate, + Expression* expression); + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitParenthesizedExpression(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // Base class for `IfExpression` and `FunctionExpression` + ///////////////////////////////////////////////////////////////////////// + + class InvocationExpression : + public CallableInvocation, + public Expression + { + public: + InvocationExpression(SourceSpan&& pstate, + ArgumentInvocation* arguments) : + CallableInvocation(arguments), + Expression(std::move(pstate)) + {} + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class IfExpression final : public InvocationExpression + { + public: + static ArgumentDeclarationObj prototype; + IfExpression(SourceSpan pstate, + ArgumentInvocation* arguments) : + InvocationExpression(std::move(pstate), arguments) + { + } + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitIfExpression(this); + } + + }; + + // This may be a plain CSS function or a Sass function. + class FunctionExpression final : public InvocationExpression + { + + // The namespace of the function being invoked, + // or `null` if it's invoked without a namespace. + ADD_CONSTREF(sass::string, ns); + + // The name of the function being invoked. If this is + // interpolated, the function will be interpreted as plain + // CSS, even if it has the same name as a Sass function. + ADD_CONSTREF(InterpolationObj, name); + + // The frame offset for the function + ADD_CONSTREF(VarRef, fidx); + + // Internal optimization flag + ADD_CONSTREF(bool, selfAssign); + + public: + FunctionExpression(SourceSpan pstate, + Interpolation* name, + ArgumentInvocation* arguments, + const sass::string& ns = ""); + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitFunctionExpression(this); + } + + IMPLEMENT_ISA_CASTER(FunctionExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/ast_fwd_decl.cpp b/src/ast_fwd_decl.cpp index 90936bfd36..85c8b0b500 100644 --- a/src/ast_fwd_decl.cpp +++ b/src/ast_fwd_decl.cpp @@ -1,31 +1,42 @@ -#include "ast.hpp" +#include "ast_fwd_decl.hpp" + +#include "ast_css.hpp" +#include "ast_nodes.hpp" +#include "ast_values.hpp" +#include "ast_statements.hpp" +#include "ast_supports.hpp" +#include "ast_selectors.hpp" namespace Sass { #define IMPLEMENT_BASE_CAST(T) \ template<> \ - T* Cast(AST_Node* ptr) { \ + T* Cast(AstNode* ptr) { \ return dynamic_cast(ptr); \ }; \ \ template<> \ - const T* Cast(const AST_Node* ptr) { \ + const T* Cast(const AstNode* ptr) { \ return dynamic_cast(ptr); \ }; \ - IMPLEMENT_BASE_CAST(AST_Node) - IMPLEMENT_BASE_CAST(Expression) - IMPLEMENT_BASE_CAST(Statement) - IMPLEMENT_BASE_CAST(ParentStatement) - IMPLEMENT_BASE_CAST(PreValue) - IMPLEMENT_BASE_CAST(Value) - IMPLEMENT_BASE_CAST(Color) - IMPLEMENT_BASE_CAST(List) - IMPLEMENT_BASE_CAST(String) - IMPLEMENT_BASE_CAST(String_Constant) - IMPLEMENT_BASE_CAST(SupportsCondition) - IMPLEMENT_BASE_CAST(Selector) - IMPLEMENT_BASE_CAST(SelectorComponent) - IMPLEMENT_BASE_CAST(SimpleSelector) + IMPLEMENT_BASE_CAST(Expression); + IMPLEMENT_BASE_CAST(Statement); + IMPLEMENT_BASE_CAST(ParentStatement); + IMPLEMENT_BASE_CAST(CssParentNode); + IMPLEMENT_BASE_CAST(CallableInvocation); + IMPLEMENT_BASE_CAST(CallableDeclaration); + IMPLEMENT_BASE_CAST(Value); + IMPLEMENT_BASE_CAST(Color); + IMPLEMENT_BASE_CAST(List); + IMPLEMENT_BASE_CAST(Callable); + IMPLEMENT_BASE_CAST(String); + IMPLEMENT_BASE_CAST(SupportsCondition); + IMPLEMENT_BASE_CAST(Selector); + IMPLEMENT_BASE_CAST(SelectorComponent); + IMPLEMENT_BASE_CAST(SimpleSelector); + IMPLEMENT_BASE_CAST(NameSpaceSelector); + IMPLEMENT_BASE_CAST(CssNode); + } diff --git a/src/ast_fwd_decl.hpp b/src/ast_fwd_decl.hpp index 0d5b7975f7..9ef1fca5f3 100644 --- a/src/ast_fwd_decl.hpp +++ b/src/ast_fwd_decl.hpp @@ -1,109 +1,162 @@ -#ifndef SASS_AST_FWD_DECL_H -#define SASS_AST_FWD_DECL_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "memory.hpp" +#ifndef SASS_AST_FWD_DECL_HPP +#define SASS_AST_FWD_DECL_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include +#include +#include +#include +#include +#include "sass/values.h" #include "sass/functions.h" +#include "memory/shared_ptr.hpp" ///////////////////////////////////////////// // Forward declarations for the AST visitors. ///////////////////////////////////////////// namespace Sass { + class Logger; + class Compiler; + class EnvFrame; + + class StyleSheet; + class SourceData; + class Import; class SourceFile; - class SynthFile; - class ItplFile; - - class AST_Node; + class SourceString; + class SourceWithPath; + class SourceItpl; + + class AstNode; + class Root; + + class Callable; + class UserDefinedCallable; + class PlainCssCallable; + class ExternalCallable; + class BuiltInCallables; + class BuiltInCallable; + + class ArgumentResults; + class CallableInvocation; + class ArgumentInvocation; + class ArgumentDeclaration; + class CallableDeclaration; + class FunctionRule; + class IncludeRule; + class ContentBlock; + class MixinRule; class ParentStatement; + class CssParentNode; class SimpleSelector; + class NameSpaceSelector; - class Parent_Reference; + class ParentExpression; + class BooleanExpression; + class ColorExpression; + class NumberExpression; + class NullExpression; - class PreValue; - class Block; + class Interpolant; class Expression; class Statement; class Value; class Declaration; class StyleRule; - class Bubble; - class Trace; + + class MapExpression; + class ListExpression; + class ValueExpression; class MediaRule; + + class CssRoot; + class CssNode; + class CssString; + class CssStringList; + // class CssSelectors; class CssMediaRule; class CssMediaQuery; + class CssAtRule; + class CssComment; + class CssDeclaration; + class CssImport; + class CssKeyframeBlock; + class CssStyleRule; + class CssSupportsRule; class SupportsRule; class AtRule; - class Keyframe_Rule; class AtRootRule; - class Assignment; + class AssignRule; - class Import; - class Import_Stub; - class WarningRule; + class WarnRule; + + class ImportRule; + class ImportBase; + class StaticImport; + class IncludeImport; class ErrorRule; class DebugRule; - class Comment; + class LoudComment; + class SilentComment; - class If; + class IfRule; class ForRule; class EachRule; class WhileRule; - class Return; - class Content; + class ReturnRule; + class ContentRule; class ExtendRule; - class Definition; class List; + class ArgumentList; class Map; class Function; - class Mixin_Call; - class Binary_Expression; - class Unary_Expression; - class Function_Call; - class Custom_Warning; - class Custom_Error; + class ParenthesizedExpression; + class BinaryOpExpression; + class UnaryOpExpression; + class FunctionExpression; + class IfExpression; + class CustomWarning; + class CustomError; - class Variable; + class VariableExpression; class Number; class Color; - class Color_RGBA; - class Color_HSLA; + class ColorRgba; + class ColorHsla; + class ColorHwba; class Boolean; - class String; class Null; - class String_Schema; - class String_Constant; - class String_Quoted; + class Interpolation; + class ItplString; + class StringExpression; + + class String; - class Media_Query; - class Media_Query_Expression; class SupportsCondition; class SupportsOperation; class SupportsNegation; class SupportsDeclaration; - class Supports_Interpolation; + class SupportsInterpolation; - class At_Root_Query; - class Parameter; - class Parameters; + class AtRootQuery; class Argument; - class Arguments; class Selector; - class Selector_Schema; class PlaceholderSelector; class TypeSelector; class ClassSelector; @@ -120,92 +173,128 @@ namespace Sass { // common classes class Context; - class Expand; class Eval; class Extension; // declare classes that are instances of memory nodes - // Note: also add a mapping without underscore - // ToDo: move to camelCase vars in the future #define IMPL_MEM_OBJ(type) \ - typedef SharedImpl type##Obj; \ - typedef SharedImpl type##_Obj; \ + typedef SharedImpl type##Obj; + + IMPL_MEM_OBJ(StyleSheet); IMPL_MEM_OBJ(SourceData); + IMPL_MEM_OBJ(Import); IMPL_MEM_OBJ(SourceFile); - IMPL_MEM_OBJ(SynthFile); - IMPL_MEM_OBJ(ItplFile); + IMPL_MEM_OBJ(SourceString); + IMPL_MEM_OBJ(SourceWithPath); + IMPL_MEM_OBJ(SourceItpl); - IMPL_MEM_OBJ(AST_Node); + IMPL_MEM_OBJ(AstNode); IMPL_MEM_OBJ(Statement); - IMPL_MEM_OBJ(Block); + IMPL_MEM_OBJ(Root); IMPL_MEM_OBJ(StyleRule); - IMPL_MEM_OBJ(Bubble); - IMPL_MEM_OBJ(Trace); IMPL_MEM_OBJ(MediaRule); + + IMPL_MEM_OBJ(MapExpression); + IMPL_MEM_OBJ(ListExpression); + IMPL_MEM_OBJ(ValueExpression); + + IMPL_MEM_OBJ(CssRoot); + IMPL_MEM_OBJ(CssNode); + IMPL_MEM_OBJ(CssStringList); + IMPL_MEM_OBJ(CssString); + // IMPL_MEM_OBJ(CssSelectors); IMPL_MEM_OBJ(CssMediaRule); IMPL_MEM_OBJ(CssMediaQuery); + // IMPLEMENT_AST_OPERATORS(CssNode); + IMPL_MEM_OBJ(CssAtRule); + IMPL_MEM_OBJ(CssComment); + IMPL_MEM_OBJ(CssDeclaration); + IMPL_MEM_OBJ(CssImport); + IMPL_MEM_OBJ(CssKeyframeBlock); + IMPL_MEM_OBJ(CssStyleRule); + IMPL_MEM_OBJ(CssSupportsRule); + IMPL_MEM_OBJ(Callable); + IMPL_MEM_OBJ(UserDefinedCallable); + IMPL_MEM_OBJ(PlainCssCallable); + IMPL_MEM_OBJ(ExternalCallable); + IMPL_MEM_OBJ(BuiltInCallable); + IMPL_MEM_OBJ(BuiltInCallables); IMPL_MEM_OBJ(SupportsRule); + IMPL_MEM_OBJ(CallableDeclaration); + IMPL_MEM_OBJ(FunctionRule); + IMPL_MEM_OBJ(IncludeRule); + IMPL_MEM_OBJ(ContentBlock); + IMPL_MEM_OBJ(MixinRule); IMPL_MEM_OBJ(AtRule); - IMPL_MEM_OBJ(Keyframe_Rule); IMPL_MEM_OBJ(AtRootRule); IMPL_MEM_OBJ(Declaration); - IMPL_MEM_OBJ(Assignment); - IMPL_MEM_OBJ(Import); - IMPL_MEM_OBJ(Import_Stub); - IMPL_MEM_OBJ(WarningRule); + IMPL_MEM_OBJ(AssignRule); + IMPL_MEM_OBJ(ImportRule); + IMPL_MEM_OBJ(ImportBase); + IMPL_MEM_OBJ(StaticImport); + IMPL_MEM_OBJ(IncludeImport); + IMPL_MEM_OBJ(WarnRule); IMPL_MEM_OBJ(ErrorRule); IMPL_MEM_OBJ(DebugRule); - IMPL_MEM_OBJ(Comment); - IMPL_MEM_OBJ(PreValue); + IMPL_MEM_OBJ(LoudComment); + IMPL_MEM_OBJ(SilentComment); IMPL_MEM_OBJ(ParentStatement); - IMPL_MEM_OBJ(If); + IMPL_MEM_OBJ(CssParentNode); + IMPL_MEM_OBJ(CallableInvocation); + IMPL_MEM_OBJ(ArgumentInvocation); + IMPL_MEM_OBJ(ArgumentDeclaration); + IMPL_MEM_OBJ(IfRule); IMPL_MEM_OBJ(ForRule); IMPL_MEM_OBJ(EachRule); IMPL_MEM_OBJ(WhileRule); - IMPL_MEM_OBJ(Return); - IMPL_MEM_OBJ(Content); + IMPL_MEM_OBJ(ReturnRule); + IMPL_MEM_OBJ(ContentRule); IMPL_MEM_OBJ(ExtendRule); - IMPL_MEM_OBJ(Definition); - IMPL_MEM_OBJ(Mixin_Call); IMPL_MEM_OBJ(Value); + IMPL_MEM_OBJ(Interpolant); IMPL_MEM_OBJ(Expression); IMPL_MEM_OBJ(List); + IMPL_MEM_OBJ(ArgumentList); IMPL_MEM_OBJ(Map); IMPL_MEM_OBJ(Function); - IMPL_MEM_OBJ(Binary_Expression); - IMPL_MEM_OBJ(Unary_Expression); - IMPL_MEM_OBJ(Function_Call); - IMPL_MEM_OBJ(Custom_Warning); - IMPL_MEM_OBJ(Custom_Error); - IMPL_MEM_OBJ(Variable); + IMPL_MEM_OBJ(ParenthesizedExpression); + IMPL_MEM_OBJ(BinaryOpExpression); + IMPL_MEM_OBJ(UnaryOpExpression); + IMPL_MEM_OBJ(FunctionExpression); + IMPL_MEM_OBJ(IfExpression); + IMPL_MEM_OBJ(CustomWarning); + IMPL_MEM_OBJ(CustomError); + IMPL_MEM_OBJ(VariableExpression); IMPL_MEM_OBJ(Number); IMPL_MEM_OBJ(Color); - IMPL_MEM_OBJ(Color_RGBA); - IMPL_MEM_OBJ(Color_HSLA); + IMPL_MEM_OBJ(ColorRgba); + IMPL_MEM_OBJ(ColorHsla); + IMPL_MEM_OBJ(ColorHwba); IMPL_MEM_OBJ(Boolean); - IMPL_MEM_OBJ(String_Schema); IMPL_MEM_OBJ(String); - IMPL_MEM_OBJ(String_Constant); - IMPL_MEM_OBJ(String_Quoted); - IMPL_MEM_OBJ(Media_Query); - IMPL_MEM_OBJ(Media_Query_Expression); + IMPL_MEM_OBJ(Interpolation); + IMPL_MEM_OBJ(ItplString); + IMPL_MEM_OBJ(StringExpression); IMPL_MEM_OBJ(SupportsCondition); IMPL_MEM_OBJ(SupportsOperation); IMPL_MEM_OBJ(SupportsNegation); IMPL_MEM_OBJ(SupportsDeclaration); - IMPL_MEM_OBJ(Supports_Interpolation); - IMPL_MEM_OBJ(At_Root_Query); + IMPL_MEM_OBJ(SupportsInterpolation); + IMPL_MEM_OBJ(AtRootQuery); IMPL_MEM_OBJ(Null); - IMPL_MEM_OBJ(Parent_Reference); - IMPL_MEM_OBJ(Parameter); - IMPL_MEM_OBJ(Parameters); + + IMPL_MEM_OBJ(ParentExpression); + IMPL_MEM_OBJ(BooleanExpression); + IMPL_MEM_OBJ(ColorExpression); + IMPL_MEM_OBJ(NumberExpression); + IMPL_MEM_OBJ(NullExpression); + IMPL_MEM_OBJ(Argument); - IMPL_MEM_OBJ(Arguments); IMPL_MEM_OBJ(Selector); - IMPL_MEM_OBJ(Selector_Schema); IMPL_MEM_OBJ(SimpleSelector); + IMPL_MEM_OBJ(NameSpaceSelector); IMPL_MEM_OBJ(PlaceholderSelector); IMPL_MEM_OBJ(TypeSelector); IMPL_MEM_OBJ(ClassSelector); @@ -219,55 +308,73 @@ namespace Sass { IMPL_MEM_OBJ(ComplexSelector); IMPL_MEM_OBJ(SelectorList); - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // some often used typedefs - // ########################################################################### - - typedef sass::vector BlockStack; - typedef sass::vector CalleeStack; - typedef sass::vector CallStack; - typedef sass::vector MediaStack; - typedef sass::vector SelectorStack; - typedef sass::vector ImporterStack; - - // only to switch implementations for testing - #define environment_map std::map - - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# + + typedef sass::vector StringVector; + typedef sass::vector SelectorComponentVector; + typedef sass::vector ValueVector; + typedef sass::vector CssNodeVector; + typedef sass::vector CssParentVector; + typedef sass::vector CssMediaQueryVector; + typedef sass::vector CssMediaVector; + typedef sass::vector SelectorLists; + typedef sass::vector StatementVector; + typedef sass::vector ExpressionVector; + typedef std::unordered_set StringSet; + + /////////////////////////////////////////////////////////////////////////# // explicit type conversion functions - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# template - T* Cast(AST_Node* ptr); + T* Cast(AstNode* ptr); template - const T* Cast(const AST_Node* ptr); + const T* Cast(const AstNode* ptr); // sometimes you know the class you want to cast to is final // in this case a simple typeid check is faster and safe to use #define DECLARE_BASE_CAST(T) \ - template<> T* Cast(AST_Node* ptr); \ - template<> const T* Cast(const AST_Node* ptr); \ + template<> T* Cast(AstNode* ptr); \ + template<> const T* Cast(const AstNode* ptr); \ - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // implement specialization for final classes - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# - DECLARE_BASE_CAST(AST_Node) + DECLARE_BASE_CAST(AstNode) DECLARE_BASE_CAST(Expression) DECLARE_BASE_CAST(Statement) DECLARE_BASE_CAST(ParentStatement) - DECLARE_BASE_CAST(PreValue) + DECLARE_BASE_CAST(CssParentNode) + DECLARE_BASE_CAST(CallableInvocation) DECLARE_BASE_CAST(Value) - DECLARE_BASE_CAST(List) + DECLARE_BASE_CAST(Callable) DECLARE_BASE_CAST(Color) + DECLARE_BASE_CAST(List) DECLARE_BASE_CAST(String) - DECLARE_BASE_CAST(String_Constant) DECLARE_BASE_CAST(SupportsCondition) DECLARE_BASE_CAST(Selector) DECLARE_BASE_CAST(SimpleSelector) + DECLARE_BASE_CAST(NameSpaceSelector); DECLARE_BASE_CAST(SelectorComponent) + DECLARE_BASE_CAST(ImportBase); + DECLARE_BASE_CAST(CssNode); + + class Eval; + class Logger; + class Compiler; + class SourceSpan; + + #define FN_PROTOTYPE2 \ + const SourceSpan& pstate, \ + const ValueVector& arguments, \ + Compiler& compiler, \ + Eval& eval, \ + bool selfAssign \ } diff --git a/src/ast_helpers.hpp b/src/ast_helpers.hpp index fa7ae942a5..15b553cd56 100644 --- a/src/ast_helpers.hpp +++ b/src/ast_helpers.hpp @@ -1,41 +1,44 @@ #ifndef SASS_AST_HELPERS_H #define SASS_AST_HELPERS_H -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include #include #include "util_string.hpp" +#include "string_utils.hpp" namespace Sass { - // ########################################################################### - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# + /////////////////////////////////////////////////////////////////////////# // easier to search with name const bool DELAYED = true; - // ToDo: should this really be hardcoded + // ToDo: should this really be hard-coded // Note: most methods follow precision option const double NUMBER_EPSILON = 1e-12; // macro to test if numbers are equal within a small error margin #define NEAR_EQUAL(lhs, rhs) std::fabs(lhs - rhs) < NUMBER_EPSILON - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // We define various functions and functors here. // Functions satisfy the BinaryPredicate requirement // Functors are structs used for e.g. unordered_map - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // Implement compare and hashing operations for raw pointers - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# + + const std::hash hasher; template size_t PtrHashFn(const T* ptr) { - return std::hash()((size_t)ptr); + return ((size_t)ptr) >> 3; } struct PtrHash { @@ -57,11 +60,11 @@ namespace Sass { } }; - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // Implement compare and hashing operations for AST Nodes - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# - // TODO: get rid of funtions and use ObjEquality + // TODO: get rid of functions and use ObjEquality template // Hash the raw pointer instead of object @@ -124,7 +127,7 @@ namespace Sass { bool PtrObjEqualityFn(const T* lhs, const T* rhs) { if (lhs == nullptr) return rhs == nullptr; else if (rhs == nullptr) return false; - else return *lhs == *rhs; + else return lhs == rhs || *lhs == *rhs; } struct PtrObjEquality { @@ -149,40 +152,16 @@ namespace Sass { } }; - // ########################################################################### - // Special compare function only for hashes. - // We need to make sure to not have objects equal that - // have different hashes. This is currently an issue, - // since `1px` is equal to `1` but have different hashes. - // This goes away once we remove unitless equality. - // ########################################################################### - - template - // Compare the objects and its hashes - bool ObjHashEqualityFn(const T& lhs, const T& rhs) { - if (lhs == nullptr) return rhs == nullptr; - else if (rhs == nullptr) return false; - else return lhs->hash() == rhs->hash(); - } - struct ObjHashEquality { - template - // Compare the objects and its contents and hashes - bool operator() (const T& lhs, const T& rhs) const { - return ObjEqualityFn(lhs, rhs) && - ObjHashEqualityFn(lhs, rhs); - } - }; - - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // Implement ordering operations for AST Nodes - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# template // Compare the objects behind pointers bool PtrObjLessThanFn(const T* lhs, const T* rhs) { if (lhs == nullptr) return rhs != nullptr; else if (rhs == nullptr) return false; - else return *lhs < *rhs; + else return lhs != rhs && *lhs < *rhs; } struct PtrObjLessThan { @@ -207,9 +186,9 @@ namespace Sass { } }; - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // Some STL helper functions - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // Check if all elements are equal template empty(); } + // Return if Vector is empty + // template + // bool listIsInvisible(T* cnt) { + // return cnt && (cnt->empty() || (cnt->hasInvisible() && !cnt->hasInvisible())); + // } + // Erase items from vector that match predicate template void listEraseItemIf(T& vec, UnaryPredicate* predicate) @@ -247,69 +232,69 @@ namespace Sass { return true; } - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [name] is the name of a pseudo-element // that can be written with pseudo-class syntax (CSS2 vs CSS3): // `:before`, `:after`, `:first-line`, or `:first-letter` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// inline bool isFakePseudoElement(const sass::string& name) { - return Util::equalsLiteral("after", name) - || Util::equalsLiteral("before", name) - || Util::equalsLiteral("first-line", name) - || Util::equalsLiteral("first-letter", name); + return StringUtils::equalsIgnoreCase(name, "after", 5) + || StringUtils::equalsIgnoreCase(name, "before", 6) + || StringUtils::equalsIgnoreCase(name, "first-line", 10) + || StringUtils::equalsIgnoreCase(name, "first-letter", 12); } - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Names of pseudo selectors that take selectors as arguments, // and that are subselectors of their arguments. // For example, `.foo` is a superselector of `:matches(.foo)`. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// inline bool isSubselectorPseudo(const sass::string& norm) { - return Util::equalsLiteral("any", norm) - || Util::equalsLiteral("matches", norm) - || Util::equalsLiteral("nth-child", norm) - || Util::equalsLiteral("nth-last-child", norm); + return StringUtils::equalsIgnoreCase(norm, "any", 3) + || StringUtils::equalsIgnoreCase(norm, "matches", 7) + || StringUtils::equalsIgnoreCase(norm, "nth-child", 9) + || StringUtils::equalsIgnoreCase(norm, "nth-last-child", 14); } // EO isSubselectorPseudo - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // Pseudo-class selectors that take unadorned selectors as arguments. - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# inline bool isSelectorPseudoClass(const sass::string& test) { - return Util::equalsLiteral("not", test) - || Util::equalsLiteral("matches", test) - || Util::equalsLiteral("current", test) - || Util::equalsLiteral("any", test) - || Util::equalsLiteral("has", test) - || Util::equalsLiteral("host", test) - || Util::equalsLiteral("host-context", test); + return StringUtils::equalsIgnoreCase(test, "not", 3) + || StringUtils::equalsIgnoreCase(test, "matches", 7) + || StringUtils::equalsIgnoreCase(test, "current", 7) + || StringUtils::equalsIgnoreCase(test, "any", 3) + || StringUtils::equalsIgnoreCase(test, "has", 3) + || StringUtils::equalsIgnoreCase(test, "host", 4) + || StringUtils::equalsIgnoreCase(test, "host-context", 12); } // EO isSelectorPseudoClass - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // Pseudo-element selectors that take unadorned selectors as arguments. - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# inline bool isSelectorPseudoElement(const sass::string& test) { - return Util::equalsLiteral("slotted", test); + return StringUtils::equalsIgnoreCase(test, "slotted", 7); } // EO isSelectorPseudoElement - // ########################################################################### - // Pseudo-element selectors that has binominals - // ########################################################################### - inline bool isSelectorPseudoBinominal(const sass::string& test) - { - return Util::equalsLiteral("nth-child", test) - || Util::equalsLiteral("nth-last-child", test); - } - // isSelectorPseudoBinominal - - // ########################################################################### - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# + // Pseudo-element selectors that has binomials + /////////////////////////////////////////////////////////////////////////# + // inline bool isSelectorPseudoBinominal(const sass::string& test) + // { + // return StringUtils::equalsIgnoreCase(test, "nth-child", 11) + // || StringUtils::equalsIgnoreCase(test, "nth-last-child", 14); + // } + // EO isSelectorPseudoBinominal + + /////////////////////////////////////////////////////////////////////////# + /////////////////////////////////////////////////////////////////////////# } diff --git a/src/ast_nodes.cpp b/src/ast_nodes.cpp new file mode 100644 index 0000000000..c8da00d7d6 --- /dev/null +++ b/src/ast_nodes.cpp @@ -0,0 +1,613 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "ast_nodes.hpp" + +#include "ast_css.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" +#include "stylesheet.hpp" +#include "ast_values.hpp" +#include "ast_selectors.hpp" +#include "parser_selector.hpp" +#include "parser_at_root_query.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Interpolant::Interpolant( + const SourceSpan& pstate) : + AstNode(pstate) + {} + + Interpolant::Interpolant( + SourceSpan&& pstate) : + AstNode(std::move(pstate)) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ItplString::ItplString( + const SourceSpan& pstate, + sass::string&& text) : + Interpolant(pstate), + text_(std::move(text)) + {} + + ItplString::ItplString( + const SourceSpan& pstate, + const sass::string& text) : + Interpolant(pstate), + text_(text) + {} + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + Interpolation::Interpolation( + const SourceSpan& pstate, + Interpolant* interpolation) : + AstNode(pstate) + { + if (interpolation != nullptr) { + append(interpolation); + } + } + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + Expression::Expression(SourceSpan&& pstate) + : Interpolant(std::move(pstate)) + {} + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + const sass::string& Interpolation::getPlainString() const + { + if (size() != 1) { + return Strings::empty; + } + return first()->getText(); + } + + const sass::string& Interpolation::getInitialPlain() const + { + if (empty()) return Strings::empty; + if (Interpolant* str = first()->isaItplString()) { + return str->getText(); + } + return Strings::empty; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ImportBase::ImportBase( + const SourceSpan& pstate) : + AstNode(pstate) + {} + + ImportBase::ImportBase( + const ImportBase* ptr) : + AstNode(ptr) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + StaticImport::StaticImport( + const SourceSpan& pstate, + InterpolationObj url, + SupportsConditionObj supports, + InterpolationObj media) : + ImportBase(pstate), + url_(url), + supports_(supports), + media_(media), + outOfOrder_(true) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + IncludeImport::IncludeImport( + const SourceSpan& pstate, + StyleSheet* sheet) : + ImportBase(pstate), + sheet_(sheet) + {} + + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// + + Values::iterator::iterator(Value* val, bool end) : + val(val), last(0), cur(0) + { + if (val == nullptr) { + type = NullPtrIterator; + } + else if (Map* map = val->isaMap()) { + type = MapIterator; + last = map->size(); + } + else if (List* list = val->isaList()) { + type = ListIterator; + last = list->size(); + } + else { + type = SingleIterator; + last = 1; + } + // Move to end position + if (end) cur = last; + } + + Values::iterator& Values::iterator::operator++() + { + switch (type) { + case MapIterator: + cur = std::min(cur + 1, last); + break; + case ListIterator: + cur = std::min(cur + 1, last); + break; + case SingleIterator: + cur = 1; + break; + case NullPtrIterator: + break; + } + return *this; + } + + Value* Values::iterator::operator*() + { + switch (type) { + case MapIterator: + return static_cast(val)->getPairAsList(cur); + case ListIterator: + return static_cast(val)->get(cur); + case SingleIterator: + return val; + case NullPtrIterator: + return nullptr; + } + return nullptr; + } + + bool Values::iterator::operator==(const iterator& other) const + { + return val == other.val && cur == other.cur; + } + + bool Values::iterator::operator!=(const iterator& other) const + { + return val != other.val || cur != other.cur; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Standard value constructor + Value::Value(const SourceSpan& pstate) : + Interpolant(pstate), + hash_(0) + {} + + // Copy constructor + Value::Value(const Value* ptr) : + Interpolant(ptr->pstate()), + hash_(0) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // The SassScript `>` operation. + bool Value::greaterThan(Value* other, Logger& logger, const SourceSpan& pstate) const + { + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " > " + other->inspect() + "\".", + logger, pstate); + } + // EO greaterThan + + // The SassScript `>=` operation. + bool Value::greaterThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const + { + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " >= " + other->inspect() + "\".", + logger, pstate); + } + // EO greaterThanOrEquals + + // The SassScript `<` operation. + bool Value::lessThan(Value* other, Logger& logger, const SourceSpan& pstate) const + { + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " < " + other->inspect() + "\".", + logger, pstate); + } + // EO lessThan + + // The SassScript `<=` operation. + bool Value::lessThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const + { + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " <= " + other->inspect() + "\".", + logger, pstate); + } + // EO lessThanOrEquals + + // The SassScript `*` operation. + Value* Value::times(Value* other, Logger& logger, const SourceSpan& pstate) const + { + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " * " + other->inspect() + "\".", + logger, pstate); + } + // EO times + + // The SassScript `%` operation. + Value* Value::modulo(Value* other, Logger& logger, const SourceSpan& pstate) const + { + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " % " + other->inspect() + "\".", + logger, pstate); + } + // EO sassIndexToListIndex + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // The SassScript `=` operation. + Value* Value::singleEquals(Value* other, Logger& logger, const SourceSpan& pstate) const + { + return SASS_MEMORY_NEW(String, pstate, + toCss(logger) + "=" + other->toCss(logger)); + } + + // The SassScript `+` operation. + Value* Value::plus(Value* other, Logger& logger, const SourceSpan& pstate) const + { + if (String* str = other->isaString()) { + sass::string text(toCss(logger)); + return SASS_MEMORY_NEW(String, pstate, + text + str->value(), + str->hasQuotes()); + } + else { + sass::string text(toCss(logger)); + return SASS_MEMORY_NEW(String, pstate, + text + other->toCss(logger)); + } + } + + // The SassScript `-` operation. + Value* Value::minus(Value* other, Logger& logger, const SourceSpan& pstate) const + { + sass::string text(toCss(logger)); + return SASS_MEMORY_NEW(String, pstate, + text + "-" + other->toCss(logger)); + } + + // The SassScript `/` operation. + Value* Value::dividedBy(Value* other, Logger& logger, const SourceSpan& pstate) const + { + sass::string text(toCss(logger)); + return SASS_MEMORY_NEW(String, pstate, + text + "/" + other->toCss(logger)); + } + + // The SassScript unary `+` operation. + Value* Value::unaryPlus(Logger& logger, const SourceSpan& pstate) const + { + return SASS_MEMORY_NEW(String, + pstate, "+" + toCss(logger)); + } + + // The SassScript unary `-` operation. + Value* Value::unaryMinus(Logger& logger, const SourceSpan& pstate) const + { + return SASS_MEMORY_NEW(String, + pstate, "-" + toCss(logger)); + } + + // The SassScript unary `/` operation. + Value* Value::unaryDivide(Logger& logger, const SourceSpan& pstate) const + { + return SASS_MEMORY_NEW(String, + pstate, "/" + toCss(logger)); + } + + // The SassScript unary `not` operation. + Value* Value::unaryNot(Logger& logger, const SourceSpan& pstate) const + { + return SASS_MEMORY_NEW(Boolean, + pstate, !isTruthy()); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Assert and return a value or throws if incompatible + Value* Value::assertValue(Logger& logger, const sass::string& name) + { + return this; // Nothing to check here + } + + // Assert and return a color or throws if incompatible + const Color* Value::assertColor(Logger& logger, const sass::string& name) const + { + logger.addFinalStackTrace(pstate()); + throw Exception::SassScriptException( + inspect() + " is not a color.", + logger, pstate(), name); + } + + // Assert and return a function or throws if incompatible + Function* Value::assertFunction(Logger& logger, const sass::string& name) + { + logger.addFinalStackTrace(pstate()); + throw Exception::SassScriptException( + inspect() + " is not a function reference.", + logger, pstate(), name); + } + + // Assert and return a map or throws if incompatible + Map* Value::assertMap(Logger& logger, const sass::string& name) + { + logger.addFinalStackTrace(pstate()); + throw Exception::SassScriptException( + inspect() + " is not a map.", + logger, pstate(), name); + } + + // Assert and return a number or throws if incompatible + Number* Value::assertNumber(Logger& logger, const sass::string& name) + { + logger.addFinalStackTrace(pstate()); + throw Exception::SassScriptException( + inspect() + " is not a number.", + logger, pstate(), name); + } + + // Assert and return a number/nullptr or throws if incompatible + Number* Value::assertNumberOrNull(Logger& logger, const sass::string& name) + { + if (this->isNull()) return nullptr; + return this->assertNumber(logger, name); + } + + // Assert and return a string or throws if incompatible + String* Value::assertString(Logger& logger, const sass::string& name) + { + logger.addFinalStackTrace(pstate()); + throw Exception::SassScriptException( + inspect() + " is not a string.", + logger, pstate(), name); + } + + // Assert and return a string/nullptr or throws if incompatible + String* Value::assertStringOrNull(Logger& logger, const sass::string& name) + { + if (this->isNull()) return nullptr; + return this->assertString(logger, name); + } + + // Assert and return an argument list or throws if incompatible + ArgumentList* Value::assertArgumentList(Logger& logger, const sass::string& name) + { + logger.addFinalStackTrace(pstate()); + throw Exception::SassScriptException( + inspect() + " is not an argument list.", + logger, pstate(), name); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Return normalized index for vector from overflow-able sass index + size_t Value::sassIndexToListIndex(Value* sassIndex, Logger& logger, const sass::string& name) + { + long index = sassIndex->assertNumber(logger, name) + ->assertInt(logger, name); + if (index == 0) throw Exception::SassScriptException( + "List index may not be 0.", logger, sassIndex->pstate(), name); + size_t size = lengthAsList(); + if (size < size_t(std::abs(index))) { + sass::sstream strm; + strm << "Invalid index " << index << " for a list "; + strm << "with " << size << " elements."; + throw Exception::SassScriptException( + strm.str(), logger, sassIndex->pstate(), name); + } + return index < 0 ? size + index : size_t(index) - 1; + } + + // Parses [this] as a selector list, in the same manner as the + // `selector-parse()` function. + /// + // Throws a [SassScriptException] if this isn't a type that can be parsed as a + // selector, or if parsing fails. If [allowParent] is `true`, this allows + // [ParentSelector]s. Otherwise, they're considered parse errors. + /// + // If this came from a function argument, [name] is the argument name + // (without the `$`). It's used for error reporting. + SelectorList* Value::assertSelector(Compiler& compiler, const sass::string& name, bool allowParent) const + { + callStackFrame frame(compiler, pstate()); + sass::string text(getSelectorString(compiler, name)); + SourceDataObj source = SASS_MEMORY_NEW(SourceItpl, pstate(), std::move(text)); + SelectorParser parser(compiler, source, allowParent); + return parser.parseSelectorList(); + } + + /// Parses [this] as a compound selector, in the same manner as the + /// `selector-parse()` function. + /// + /// Throws a [SassScriptException] if this isn't a type that can be parsed as a + /// selector, or if parsing fails. If [allowParent] is `true`, this allows + /// [ParentSelector]s. Otherwise, they're considered parse errors. + /// + /// If this came from a function argument, [name] is the argument name + /// (without the `$`). It's used for error reporting. + CompoundSelector* Value::assertCompoundSelector(Compiler& compiler, const sass::string& name, bool allowParent) const + { + callStackFrame frame(compiler, pstate()); + sass::string text(getSelectorString(compiler, name)); + SourceDataObj source = SASS_MEMORY_NEW(SourceItpl, pstate(), std::move(text)); + SelectorParser parser(compiler, source, allowParent); + return parser.parseCompoundSelector(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Converts a `selector-parse()`-style input into a string that + // can be parsed. Returns `false` if [this] isn't a type or a + // structure that can be parsed as a selector. + bool Value::selectorStringOrNull(Logger& logger, sass::string& rv) const + { + if (const String* str = isaString()) { + rv = str->value(); + return true; + } + else if (const List* list = isaList()) { + if (list->empty()) return false; + sass::vector result; + if (list->separator() == SASS_COMMA) { + for (auto complex : list->elements()) { + List* cplxLst = complex->isaList(); + String* cplxStr = complex->isaString(); + if (cplxStr) { result.emplace_back(cplxStr->value()); } + else if (cplxLst && cplxLst->separator() == SASS_SPACE) { + sass::string string = complex->getSelectorString(logger); + if (string.empty()) return false; + result.emplace_back(string); + } + else return false; + } + } + else { + for (auto compound : list->elements()) { + String* cmpdStr = compound->isaString(); + if (cmpdStr) result.emplace_back(cmpdStr->value()); + else return false; + } + } + rv = StringUtils::join(result, list->separator() == SASS_COMMA ? ", " : " "); + return true; + } + return false; + } + // EO selectorStringOrNull + + // Converts a `selector-parse()`-style input into a string that + // can be parsed. Throws a [SassScriptException] if [this] isn't + // a type or a structure that can be parsed as a selector. + sass::string Value::getSelectorString(Logger& logger, const sass::string& name) const + { + sass::string str; + if (selectorStringOrNull(logger, str)) { + return str; + } + throw Exception::SassScriptException( + inspect() + " is not a valid selector: it must be a string,\n" + "a list of strings, or a list of lists of strings.", + logger, pstate(), name); + } + // EO selectorStringOrNull + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + AtRootQuery::AtRootQuery( + SourceSpan&& pstate, + StringSet&& names, + bool include) : + AstNode(std::move(pstate)), + names_(std::move(names)), + include_(include) + {} + + // Whether this includes or excludes style rules. + inline bool AtRootQuery::rule() const { + return names_.count("rule") == 1; + } + + // Whether this includes or excludes media rules. + inline bool AtRootQuery::media() const { + return names_.count("media") == 1; + } + + // Whether this includes or excludes *all* rules. + inline bool AtRootQuery::all() const { + return names_.count("all") == 1; + } + + // Returns whether [this] excludes a node with the given [name]. + bool AtRootQuery::excludesName(const sass::string& name) const { + return (names_.count(name) == 1) != include(); + } + + // Returns whether [this] excludes [node]. + bool AtRootQuery::excludes(CssParentNode* node) const + { + if (all()) return !include(); + if (rule() && node->isaCssStyleRule()) return !include(); + return excludesName(node->getAtRuleName()); + } + + // Whether this excludes `@media` rules. + // Note that this takes [include] into account. + bool AtRootQuery::excludesMedia() const { + return (all() || media()) != include(); + } + + // Whether this excludes style rules. + // Note that this takes [include] into account. + bool AtRootQuery::excludesStyleRules() const { + return (all() || rule()) != include(); + } + + // Parses an at-root query from [contents]. If passed, [url] + // is the name of the file from which [contents] comes. + // Throws a [SassFormatException] if parsing fails. + AtRootQuery* AtRootQuery::parse(SourceData* source, Compiler& ctx) + { + AtRootQueryParser parser(ctx, source); + return parser.parse(); + } + + + // The default at-root query, which excludes only style rules. + AtRootQuery* AtRootQuery::defaultQuery(SourceSpan&& pstate) + { + StringSet wihtoutStyleRule; + wihtoutStyleRule.insert("rule"); + return SASS_MEMORY_NEW(AtRootQuery, std::move(pstate), + std::move(wihtoutStyleRule), false); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/ast_nodes.hpp b/src/ast_nodes.hpp new file mode 100644 index 0000000000..5ffee21057 --- /dev/null +++ b/src/ast_nodes.hpp @@ -0,0 +1,698 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_NODES_HPP +#define SASS_AST_NODES_HPP + +#include "backtrace.hpp" +#include "source_span.hpp" +#include "ast_containers.hpp" +#include "visitor_value.hpp" +#include "visitor_statement.hpp" +#include "visitor_expression.hpp" +#include "environment_key.hpp" +#include "environment_cnt.hpp" + +namespace Sass { + + ////////////////////////////////////////////////////////////////////// + // define cast template now (need complete type) + ////////////////////////////////////////////////////////////////////// + + template + T* Cast(AstNode* ptr) { + return dynamic_cast(ptr); + }; + + template + const T* Cast(const AstNode* ptr) { + return dynamic_cast(ptr); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + uint8_t sass_op_to_precedence(enum SassOperator op); + const char* sass_op_to_name(enum SassOperator op); + const char* sass_op_separator(enum SassOperator op); + + typedef LocalStack ScopedStack; + typedef LocalStack ScopedImport; + typedef LocalStack ScopedSelector; + + ///////////////////////////////////////////////////////////////////////// + // Abstract base class for all abstract syntax tree nodes. + ///////////////////////////////////////////////////////////////////////// + class AstNode : public SharedObj + { + private: + + ADD_CONSTREF(SourceSpan, pstate); + + public: + + // Value copy constructor + AstNode(const SourceSpan& pstate) + : pstate_(pstate) + {} + + // Value move constructor + AstNode(SourceSpan&& pstate) + : pstate_(std::move(pstate)) + {} + + // Copy constructor + AstNode(const AstNode* ptr) + : pstate_(ptr->pstate_) + {} + + // Delete compare operators to make implementation more clear + // Helps us spot cases where we use undefined implementations + // virtual bool operator==(const AstNode& rhs) const = delete; + // virtual bool operator!=(const AstNode& rhs) const = delete; + // virtual bool operator>=(const AstNode& rhs) const = delete; + // virtual bool operator<=(const AstNode& rhs) const = delete; + // virtual bool operator>(const AstNode& rhs) const = delete; + // virtual bool operator<(const AstNode& rhs) const = delete; + + }; + + ///////////////////////////////////////////////////////////////////////// + // Abstract base class for items in interpolations. + // Must be one of ItplString, an Expression or a Value. + ///////////////////////////////////////////////////////////////////////// + class Interpolant : public AstNode + { + public: + + // Value constructors + Interpolant(SourceSpan&& pstate); + Interpolant(const SourceSpan& pstate); + + // We know four types + enum Type { + ValueInterpolant, + LiteralInterpolant, + ExpressionInterpolant, + }; + + // Interface to be implemented + virtual Type getType() const = 0; + virtual const sass::string& getText() const { return Strings::empty; }; + + // Declare up-casting methods + DECLARE_ISA_CASTER(Value); + DECLARE_ISA_CASTER(String); + DECLARE_ISA_CASTER(ItplString); + DECLARE_ISA_CASTER(Expression); + }; + // EO Interpolant + + /////////////////////////////////////////////////////////////////////// + // A native string wrapped as an interpolant + /////////////////////////////////////////////////////////////////////// + class ItplString final : public Interpolant + { + private: + + ADD_CONSTREF(sass::string, text) + + public: + + ItplString(const SourceSpan& pstate, sass::string&& text); + ItplString(const SourceSpan& pstate, const sass::string& text); + Type getType() const override final { return LiteralInterpolant; } + const sass::string& getText() const override final { return text_; } + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(ItplString); + }; + // EO ItplString + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + class Interpolation final : public AstNode, + public Vectorized + { + public: + + // Value constructor + Interpolation(const SourceSpan& pstate, + Interpolant* interpolant = nullptr); + + const sass::string& getPlainString() const; + const sass::string& getInitialPlain() const; + + StringExpression* wrapInStringExpression(); + + }; + // EO Interpolation + + ////////////////////////////////////////////////////////////////////// + // Abstract base class for expressions. This side of the AST + // hierarchy represents elements in value contexts, which + // exist primarily to be evaluated and returned. + ////////////////////////////////////////////////////////////////////// + class Expression : public Interpolant, + public ExpressionVisitable + { + public: + + // Value constructor + Expression(SourceSpan&& pstate); + + // Needed here to avoid ambiguity from base-classes!?? + virtual Value* accept(ExpressionVisitor* visitor) override = 0; + // virtual void accept(ExpressionVisitor* visitor) override = 0; + + // Implementation for parent Interpolant interface + Type getType() const override final { return ExpressionInterpolant; } + + // Declare up-casting methods + DECLARE_ISA_CASTER(VariableExpression); + DECLARE_ISA_CASTER(FunctionExpression); + DECLARE_ISA_CASTER(NumberExpression); + // Implement our up-casting + IMPLEMENT_ISA_CASTER(Expression); + }; + + ///////////////////////////////////////////////////////////////////////// + // Abstract base class for statements. This side of the AST hierarchy + // represents elements in expansion contexts, which exist primarily to be + // rewritten and macro-expanded. + ///////////////////////////////////////////////////////////////////////// + class Statement : public AstNode, + public StatementVisitable + { + private: + + ADD_CONSTREF(size_t, tabs) + + public: + + // Value copy constructor + Statement(const SourceSpan& pstate) + : AstNode(pstate) + {} + + // Value move constructor + Statement(SourceSpan&& pstate) + : AstNode(std::move(pstate)) + {} + + // Copy constructor + Statement(const Statement* ptr) : + AstNode(ptr), + tabs_(ptr->tabs_) + {} + + // Interface to be implemented by content rule + virtual bool hasContent() const { return false; } + + // Needed here to avoid ambiguity from base-classes!?? + // virtual void accept(ExpressionVisitor* visitor) override = 0; + virtual Value* accept(StatementVisitor* visitor) override = 0; + + // Declare up-casting methods + DECLARE_ISA_CASTER(StyleRule); + }; + + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// + + class ImportBase : public AstNode + { + public: + ImportBase(const SourceSpan& pstate); + ImportBase(const ImportBase* ptr); + // Declare up-casting methods + DECLARE_ISA_CASTER(StaticImport); + DECLARE_ISA_CASTER(IncludeImport); + }; + + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// + + class StaticImport final : public ImportBase + { + private: + + // The URL for this import. + // This already contains quotes. + ADD_CONSTREF(InterpolationObj, url); + + // The supports condition attached to this import, + // or `null` if no condition is attached. + ADD_CONSTREF(SupportsConditionObj, supports); + + // The media query attached to this import, + // or `null` if no condition is attached. + ADD_CONSTREF(InterpolationObj, media); + + // Flag to hoist import to the top. + ADD_CONSTREF(bool, outOfOrder); + + public: + + // Object constructor by values + StaticImport(const SourceSpan& pstate, + InterpolationObj url, + SupportsConditionObj supports = {}, + InterpolationObj media = {}); + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(StaticImport); + }; + // EO class StaticImport + + // Dynamic import beside its name must have a static url + // We do not support to load sass partials programmatic + // They also don't allow any supports or media queries. + class IncludeImport final : public ImportBase + { + private: + + ADD_CONSTREF(StyleSheetObj, sheet); + + public: + + IncludeImport(const SourceSpan& pstate, StyleSheet* sheet); + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(IncludeImport); + }; + + ////////////////////////////////////////////////////////////////////// + // Helper class to iterator over different Value types. + // Depending on the type of the Value (e.g. List vs String), + // we either want to iterate over a container or a single value. + // In order to avoid unnecessary copies, we use this iterator. + ////////////////////////////////////////////////////////////////////// + class Values final { + + public: + + // We know four types + enum Type { + MapIterator, + ListIterator, + SingleIterator, + NullPtrIterator, + }; + + // Internal iterator helper class + class iterator + { + + private: + + // The value we are iterating over + Value* val; + // The detected value/iterator type + Type type; + // The final item to iterator to + // For null ptr this is zero, for + // single items this is 1 and for + // lists/maps the container size. + size_t last; + // The current iteration item + size_t cur; + + public: + + // Some typedefs to satisfy C++ type traits + typedef std::input_iterator_tag iterator_category; + typedef iterator self_type; + typedef Value* value_type; + typedef Value* reference; + typedef Value* pointer; + typedef int difference_type; + + // Create iterator for start (or end) + iterator(Value* val, bool end); + + // Copy constructor + iterator(const iterator& it) : + val(it.val), + type(it.type), + last(it.last), + cur(it.cur) {} + + // Dereference current item + reference operator*(); + + // Move to the next item + iterator& operator++(); + + // Compare operators + bool operator==(const iterator& other) const; + bool operator!=(const iterator& other) const; + + }; + // EO class iterator + + // The value we are iterating over + Value* val; + // The detected value/iterator type + Type type; + + // Standard value constructor + Values(Value* val) : val(val), type(SingleIterator) {} + + // Get iterator at the given position + iterator begin() { return iterator(val, false); } + iterator end() { return iterator(val, true); } + + }; + // EO class Values + + ////////////////////////////////////////////////////////////////////// + // base class for values that support operations + ////////////////////////////////////////////////////////////////////// + class Value : public Interpolant, + public ValueVisitable, + public ValueVisitable { + + public: + + // Needed here to avoid ambiguity from base-classes!?? + virtual void accept(ValueVisitor* visitor) override = 0; + virtual Value* accept(ValueVisitor* visitor) override = 0; + sass::string inspect(int precision = SassDefaultPrecision, bool quotes = true) const; + + // Getters to avoid need for dynamic cast (slightly faster) + Type getType() const override final { return ValueInterpolant; } + + protected: + + // Hash is only calculated once and afterwards the value + // must not be mutated, which is the case with how sass + // works, although we must be a bit careful not to alter + // any value that has already been added to a set or map. + // Must create a copy if you need to alter such an object. + mutable size_t hash_; + + public: + + // Default implementation does nothing + virtual Value* cloneChildren(SASS_MEMORY_ARGS_VOID) { return this; } + + public: + + // Standard value constructor + Value(const SourceSpan& pstate); + + // Copy constructor + Value(const Value* ptr); + + // Use macro to allow location debugging + virtual Value* copy(SASS_MEMORY_ARGS bool childless = false) const { + throw std::runtime_error("Copy not implemented"); + } + + public: + + // Hash value when used as key in hash table + virtual size_t hash() const = 0; + + // Interface to be implemented by our classes + virtual enum SassValueType getTag() const = 0; + + // Whether the value will be represented in CSS as the empty string. + virtual bool isBlank() const { return false; } + + // Return the length of this item as a list + virtual size_t lengthAsList() const { return 1; } + + // Get the type in string format (for output) + virtual const sass::string& type() const = 0; + + // Get a values iterator + virtual Values iterator() { + return Values{ this }; + } + + // Search the position of the given value + virtual size_t indexOf(Value* value) { + return *this == *value ? 0 : sass::string::npos; + } + + // Return the list separator + virtual SassSeparator separator() const { + return SASS_UNDEF; + } + + // Check if we has comma separator + bool hasCommaSeparator() const { + return separator() == SASS_COMMA; + } + + // Check if we has space separator + bool hasSpaceSeparator() const { + return separator() == SASS_SPACE; + } + + // Check if we are bracketed + virtual bool hasBrackets() { + return false; + } + + // Check if it evaluates to true + virtual bool isTruthy() const { + return true; + } + + // Check if it is null + virtual bool isNull() const { + return false; + } + + // Reset delayed value + virtual Value* withoutSlash() { + return this; + } + + // The SassScript `==` operation (never throws). + virtual bool operator== (const Value& rhs) const = 0; + + // The SassScript `>` operation. + virtual bool greaterThan(Value* other, Logger& logger, const SourceSpan& pstate) const; + + // The SassScript `>=` operation. + virtual bool greaterThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const; + + // The SassScript `<` operation. + virtual bool lessThan(Value* other, Logger& logger, const SourceSpan& pstate) const; + + // The SassScript `<=` operation. + virtual bool lessThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const; + + // The SassScript `*` operation. + virtual Value* times(Value* other, Logger& logger, const SourceSpan& pstate) const; + + // The SassScript `%` operation. + virtual Value* modulo(Value* other, Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript `=` operation. + virtual Value* singleEquals(Value* other, Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript `+` operation. + virtual Value* plus(Value* other, Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript `-` operation. + virtual Value* minus(Value* other, Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript `/` operation. + virtual Value* dividedBy(Value* other, Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript unary `+` operation. + virtual Value* unaryPlus(Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript unary `-` operation. + virtual Value* unaryMinus(Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript unary `/` operation. + virtual Value* unaryDivide(Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript unary `not` operation. + virtual Value* unaryNot(Logger& logger, const SourceSpan& pstate) const; + + // Assert and return a value or throws if incompatible + virtual Value* assertValue(Logger& logger, const sass::string& name); + + // Assert and return a color or throws if incompatible + virtual const Color* assertColor(Logger& logger, const sass::string& name) const; + + // Assert and return a function or throws if incompatible + virtual Function* assertFunction(Logger& logger, const sass::string& name); + + // Assert and return a map or throws if incompatible + virtual Map* assertMap(Logger& logger, const sass::string& name); + + // Assert and return a number or throws if incompatible + virtual Number* assertNumber(Logger& logger, const sass::string& name); + + // Assert and return a number/nullptr or throws if incompatible + virtual Number* assertNumberOrNull(Logger& logger, const sass::string& name); + + // Assert and return a string or throws if incompatible + virtual String* assertString(Logger& logger, const sass::string& name); + + // Assert and return a string/nullptr or throws if incompatible + virtual String* assertStringOrNull(Logger& logger, const sass::string& name); + + // Assert and return an argument list or throws if incompatible + virtual ArgumentList* assertArgumentList(Logger& logger, const sass::string& name); + + // Only used for nth sass function + // Single values act like lists with 1 item + // Doesn't allow overflow of index (throw error) + // Allows negative index but no overflow either + virtual Value* getValueAt(Value* index, Logger& logger); + + // Return normalized index for vector from overflowable sass index + size_t sassIndexToListIndex(Value* sassIndex, Logger& logger, const sass::string& name); + + /// Parses [this] as a selector list, in the same manner as the + /// `selector-parse()` function. + /// + /// Throws a [SassScriptException] if this isn't a type that can be parsed as a + /// selector, or if parsing fails. If [allowParent] is `true`, this allows + /// [ParentSelector]s. Otherwise, they're considered parse errors. + /// + /// If this came from a function argument, [name] is the argument name + /// (without the `$`). It's used for error reporting. + SelectorList* assertSelector(Compiler& ctx, const sass::string& name = Strings::empty, bool allowParent = false) const; + + /// Parses [this] as a compound selector, in the same manner as the + /// `selector-parse()` function. + /// + /// Throws a [SassScriptException] if this isn't a type that can be parsed as a + /// selector, or if parsing fails. If [allowParent] is `true`, this allows + /// [ParentSelector]s. Otherwise, they're considered parse errors. + /// + /// If this came from a function argument, [name] is the argument name + /// (without the `$`). It's used for error reporting. + CompoundSelector* assertCompoundSelector(Compiler& ctx, const sass::string& name = Strings::empty, bool allowParent = false) const; + + /// Returns a valid CSS representation of [this]. + /// + /// Throws a [SassScriptException] if [this] can't be represented in plain + /// CSS. Use [toString] instead to get a string representation even if this + /// isn't valid CSS. + /// + /// If [quote] is `false`, quoted strings are emitted without quotes. + sass::string toCss(Logger& logger, bool quote = true) const; + + private: + + // Converts a `selector-parse()`-style input into a string that + // can be parsed. Returns `false` if [this] isn't a type or a + // structure that can be parsed as a selector. + bool selectorStringOrNull(Logger& logger, sass::string& rv) const; + + // Converts a `selector-parse()`-style input into a string that + // can be parsed. Throws a [SassScriptException] if [this] isn't + // a type or a structure that can be parsed as a selector. + sass::string getSelectorString(Logger& logger, const sass::string& name = Strings::empty) const; + + public: + + // Declare up-casting methods + DECLARE_ISA_CASTER(Map); + DECLARE_ISA_CASTER(List); + DECLARE_ISA_CASTER(Number); + DECLARE_ISA_CASTER(Color); + DECLARE_ISA_CASTER(Boolean); + DECLARE_ISA_CASTER(Function); + DECLARE_ISA_CASTER(CustomError); + DECLARE_ISA_CASTER(CustomWarning); + DECLARE_ISA_CASTER(ArgumentList); + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(Value); + // Expose class as SassValue struct to C + CAPI_WRAPPER(Value, SassValue); + }; + + ///////////////////////////////////////////////////////////////////////// + // A query for the `@at-root` rule. + ///////////////////////////////////////////////////////////////////////// + class AtRootQuery final : public AstNode + { + private: + + // The names of the rules included or excluded by this query. There are + // two special names. "all" indicates that all rules are included or + // excluded, and "rule" indicates style rules are included or excluded. + ADD_CONSTREF(StringSet, names); + // Whether the query includes or excludes rules with the specified names. + ADD_CONSTREF(bool, include); + + public: + + // Value constructor + AtRootQuery( + SourceSpan&& pstate, + StringSet&& names, + bool include); + + // Whether this includes or excludes *all* rules. + bool all() const; + + // Whether this includes or excludes style rules. + bool rule() const; + + // Whether this includes or excludes media rules. + bool media() const; + + // Returns the at-rule name for [node], or `null` if it's not an at-rule. + sass::string _nameFor(CssNode* node) const; + + // Returns whether [this] excludes a node with the given [name]. + bool excludesName(const sass::string& name) const; + + // Returns whether [this] excludes [node]. + bool excludes(CssParentNode* node) const; + + // Whether this excludes `@media` rules. + // Note that this takes [include] into account. + bool excludesMedia() const; + + // Whether this excludes style rules. + // Note that this takes [include] into account. + bool excludesStyleRules() const; + + // Parses an at-root query from [contents]. If passed, [url] + // is the name of the file from which [contents] comes. + // Throws a [SassFormatException] if parsing fails. + static AtRootQuery* parse( + SourceData* contents, Compiler& ctx); + + // The default at-root query, which excludes only style rules. + static AtRootQuery* defaultQuery(SourceSpan&& pstate); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class Root final : public AstNode, + public Vectorized + { + public: + + Root(const SourceSpan& pstate, size_t reserve = 0) + : AstNode(pstate), Vectorized(reserve) + {} + + Root(const SourceSpan& pstate, StatementVector&& vec) + : AstNode(pstate), Vectorized(std::move(vec)) + {} + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/ast_sel_cmp.cpp b/src/ast_sel_cmp.cpp index 10f75ecb9f..3ebbd5c4cf 100644 --- a/src/ast_sel_cmp.cpp +++ b/src/ast_sel_cmp.cpp @@ -1,214 +1,56 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* This file contains all ast operator functions in one compile unit. */ +/*****************************************************************************/ #include "ast_selectors.hpp" +#include "ast_statements.hpp" +#include "callstack.hpp" namespace Sass { - /*#########################################################################*/ - // Compare against base class on right hand side - // try to find the most specialized implementation - /*#########################################################################*/ - - // Selector lists can be compared to comma lists - bool SelectorList::operator== (const Expression& rhs) const - { - if (auto l = Cast(&rhs)) { return *this == *l; } - if (auto s = Cast(&rhs)) { return *this == *s; } - if (Cast(&rhs) || Cast(&rhs)) { return false; } - throw std::runtime_error("invalid selector base classes to compare"); - } - - // Selector lists can be compared to comma lists - bool SelectorList::operator== (const Selector& rhs) const - { - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto list = Cast(&rhs)) { return *this == *list; } - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool ComplexSelector::operator== (const Selector& rhs) const - { - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *sel == *this; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool SelectorCombinator::operator== (const Selector& rhs) const - { - if (auto cpx = Cast(&rhs)) { return *this == *cpx; } - return false; - } - - bool CompoundSelector::operator== (const Selector& rhs) const - { - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool SimpleSelector::operator== (const Selector& rhs) const - { - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) return *this == *sel; - throw std::runtime_error("invalid selector base classes to compare"); - } - - /*#########################################################################*/ - /*#########################################################################*/ - + // We want to compare selector lists position independent, so we use a Set. + // This means we either need to implement a less compare method or a hashing + // function. Given that we might compare selectors quite often the hashing + // approach has proven to be slightly faster. It has some memory overhead, + // but trades off nicely for better runtime performance. bool SelectorList::operator== (const SelectorList& rhs) const { if (&rhs == this) return true; - if (rhs.length() != length()) return false; + if (rhs.size() != size()) return false; std::unordered_set lhs_set; - lhs_set.reserve(length()); + lhs_set.reserve(size()); for (const ComplexSelectorObj& element : elements()) { lhs_set.insert(element.ptr()); } for (const ComplexSelectorObj& element : rhs.elements()) { - if (lhs_set.find(element.ptr()) == lhs_set.end()) return false; + if (lhs_set.count(element.ptr()) == 0) return false; } return true; } - - - /*#########################################################################*/ - // Compare SelectorList against all other selector types - /*#########################################################################*/ - - bool SelectorList::operator== (const ComplexSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (length() != 1) return false; - // Compare simple selectors - return *get(0) == rhs; - } - - bool SelectorList::operator== (const CompoundSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (length() != 1) return false; - // Compare simple selectors - return *get(0) == rhs; - } - - bool SelectorList::operator== (const SimpleSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (length() != 1) return false; - // Compare simple selectors - return *get(0) == rhs; - } - - /*#########################################################################*/ - // Compare ComplexSelector against itself - /*#########################################################################*/ - bool ComplexSelector::operator== (const ComplexSelector& rhs) const { - size_t len = length(); - size_t rlen = rhs.length(); + size_t len = size(); + size_t rlen = rhs.size(); if (len != rlen) return false; for (size_t i = 0; i < len; i += 1) { - if (*get(i) != *rhs.get(i)) return false; + if (!(*get(i) == *rhs.get(i))) return false; } return true; } - /*#########################################################################*/ - // Compare ComplexSelector against all other selector types - /*#########################################################################*/ - - bool ComplexSelector::operator== (const SelectorList& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (rhs.length() != 1) return false; - // Compare complex selector - return *this == *rhs.get(0); - } - - bool ComplexSelector::operator== (const CompoundSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (length() != 1) return false; - // Compare compound selector - return *get(0) == rhs; - } - - bool ComplexSelector::operator== (const SimpleSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (length() != 1) return false; - // Compare simple selectors - return *get(0) == rhs; - } - - /*#########################################################################*/ - // Compare SelectorCombinator against itself - /*#########################################################################*/ - bool SelectorCombinator::operator==(const SelectorCombinator& rhs) const { return combinator() == rhs.combinator(); } - /*#########################################################################*/ - // Compare SelectorCombinator against SelectorComponent - /*#########################################################################*/ - - bool SelectorCombinator::operator==(const SelectorComponent& rhs) const - { - if (const SelectorCombinator * sel = rhs.getCombinator()) { - return *this == *sel; - } - return false; - } - - bool CompoundSelector::operator==(const SelectorComponent& rhs) const - { - if (const CompoundSelector * sel = rhs.getCompound()) { - return *this == *sel; - } - return false; - } - - /*#########################################################################*/ - // Compare CompoundSelector against itself - /*#########################################################################*/ - // ToDo: Verifiy implementation - /*#########################################################################*/ - bool CompoundSelector::operator== (const CompoundSelector& rhs) const { - // std::cerr << "comp vs comp\n"; if (&rhs == this) return true; - if (rhs.length() != length()) return false; + if (rhs.size() != size()) return false; std::unordered_set lhs_set; - lhs_set.reserve(length()); + lhs_set.reserve(size()); for (const SimpleSelectorObj& element : elements()) { lhs_set.insert(element.ptr()); } @@ -219,178 +61,49 @@ namespace Sass { return true; } - - /*#########################################################################*/ - // Compare CompoundSelector against all other selector types - /*#########################################################################*/ - - bool CompoundSelector::operator== (const SelectorList& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (rhs.length() != 1) return false; - // Compare complex selector - return *this == *rhs.get(0); - } - - bool CompoundSelector::operator== (const ComplexSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (rhs.length() != 1) return false; - // Compare compound selector - return *this == *rhs.get(0); - } - - bool CompoundSelector::operator== (const SimpleSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return false; - // Must have exactly one item - size_t rlen = length(); - if (rlen > 1) return false; - if (rlen == 0) return true; - // Compare simple selectors - return *get(0) < rhs; - } - - /*#########################################################################*/ - // Compare SimpleSelector against itself (upcast from abstract base) - /*#########################################################################*/ - - // DOES NOT EXIST FOR ABSTRACT BASE CLASS - - /*#########################################################################*/ - // Compare SimpleSelector against all other selector types - /*#########################################################################*/ - - bool SimpleSelector::operator== (const SelectorList& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (rhs.length() != 1) return false; - // Compare complex selector - return *this == *rhs.get(0); - } - - bool SimpleSelector::operator== (const ComplexSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (rhs.length() != 1) return false; - // Compare compound selector - return *this == *rhs.get(0); - } - - bool SimpleSelector::operator== (const CompoundSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return false; - // Must have exactly one item - if (rhs.length() != 1) return false; - // Compare simple selector - return *this == *rhs.get(0); - } - - /*#########################################################################*/ - /*#########################################################################*/ - - bool IDSelector::operator== (const SimpleSelector& rhs) const - { - auto sel = Cast(&rhs); - return sel ? *this == *sel : false; - } - - bool TypeSelector::operator== (const SimpleSelector& rhs) const - { - auto sel = Cast(&rhs); - return sel ? *this == *sel : false; - } - - bool ClassSelector::operator== (const SimpleSelector& rhs) const - { - auto sel = Cast(&rhs); - return sel ? *this == *sel : false; - } - - bool PseudoSelector::operator== (const SimpleSelector& rhs) const - { - auto sel = Cast(&rhs); - return sel ? *this == *sel : false; - } - - bool AttributeSelector::operator== (const SimpleSelector& rhs) const - { - auto sel = Cast(&rhs); - return sel ? *this == *sel : false; - } - - bool PlaceholderSelector::operator== (const SimpleSelector& rhs) const - { - auto sel = Cast(&rhs); - return sel ? *this == *sel : false; - } - - /*#########################################################################*/ - /*#########################################################################*/ - bool IDSelector::operator== (const IDSelector& rhs) const { - // ID has no namespacing + // ID has no namespace return name() == rhs.name(); } bool TypeSelector::operator== (const TypeSelector& rhs) const { - return is_ns_eq(rhs) && name() == rhs.name(); + return nsMatch(rhs) && name() == rhs.name(); } bool ClassSelector::operator== (const ClassSelector& rhs) const { - // Class has no namespacing + // Class has no namespace return name() == rhs.name(); } bool PlaceholderSelector::operator== (const PlaceholderSelector& rhs) const { - // Placeholder has no namespacing + // Placeholder has no namespace return name() == rhs.name(); } bool AttributeSelector::operator== (const AttributeSelector& rhs) const { // smaller return, equal go on, bigger abort - if (is_ns_eq(rhs)) { - if (name() != rhs.name()) return false; - if (matcher() != rhs.matcher()) return false; - if (modifier() != rhs.modifier()) return false; - const String* lhs_val = value(); - const String* rhs_val = rhs.value(); - return PtrObjEquality()(lhs_val, rhs_val); - } - else { return false; } + return nsMatch(rhs) + && op() == rhs.op() + && name() == rhs.name() + && value() == rhs.value() + && modifier() == rhs.modifier(); } bool PseudoSelector::operator== (const PseudoSelector& rhs) const { - if (is_ns_eq(rhs)) { - if (name() != rhs.name()) return false; - if (isElement() != rhs.isElement()) return false; - const String* lhs_arg = argument(); - const String* rhs_arg = rhs.argument(); - if (!PtrObjEquality()(lhs_arg, rhs_arg)) return false; - const SelectorList* lhs_sel = selector(); - const SelectorList* rhs_sel = rhs.selector(); - return PtrObjEquality()(lhs_sel, rhs_sel); - } - else { return false; } + return nsMatch(rhs) + && name() == rhs.name() + && argument() == rhs.argument() + && isPseudoElement() == rhs.isPseudoElement() + && ObjEquality()(selector(), rhs.selector()); } - /*#########################################################################*/ - /*#########################################################################*/ + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/ast_sel_super.cpp b/src/ast_sel_super.cpp index d27bda467d..7762945224 100644 --- a/src/ast_sel_super.cpp +++ b/src/ast_sel_super.cpp @@ -1,49 +1,49 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" - -#include "util_string.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* This file contains all ast superselector functions in one compile unit. */ +/*****************************************************************************/ +#include "ast_selectors.hpp" namespace Sass { - // ########################################################################## - // To compare/debug against libsass you can use debugger.hpp: + ///////////////////////////////////////////////////////////////////////// + // To compare/debug dart-sass vs libsass you can use debugger.hpp: // c++: std::cerr << "result " << debug_vec(compound) << "\n"; // dart: stderr.writeln("result " + compound.toString()); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [list1] is a superselector of [list2]. // That is, whether [list1] matches every element that // [list2] matches, as well as possibly additional elements. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool listIsSuperslector( const sass::vector& list1, const sass::vector& list2); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [complex1] is a superselector of [complex2]. // That is, whether [complex1] matches every element that // [complex2] matches, as well as possibly additional elements. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool complexIsSuperselector( - const sass::vector& complex1, - const sass::vector& complex2); + const SelectorComponentVector& complex1, + const SelectorComponentVector& complex2); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns all pseudo selectors in [compound] that have // a selector argument, and that have the given [name]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector selectorPseudoNamed( - CompoundSelectorObj compound, sass::string name) + const CompoundSelector* compound, const sass::string& name, bool isClass = true) { sass::vector rv; - for (SimpleSelectorObj sel : compound->elements()) { - if (PseudoSelectorObj pseudo = Cast(sel)) { - if (pseudo->isClass() && pseudo->selector()) { + for (const SimpleSelectorObj& sel : compound->elements()) { + if (const PseudoSelector* pseudo = sel->isaPseudoSelector()) { + if (pseudo->isClass() == isClass && pseudo->selector()) { if (sel->name() == name) { - rv.push_back(sel); + rv.emplace_back(sel); } } } @@ -52,29 +52,35 @@ namespace Sass { } // EO selectorPseudoNamed - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [simple1] is a superselector of [simple2]. // That is, whether [simple1] matches every element that // [simple2] matches, as well as possibly additional elements. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool simpleIsSuperselector( - const SimpleSelectorObj& simple1, - const SimpleSelectorObj& simple2) + const SimpleSelector* simple1, + const SimpleSelector* simple2) { + + if (simple1->isUniversal()) { + if (!simple2->isUniversal()) return false; + return simple1->nsMatch(*simple2); + } + // If they are equal they are superselectors - if (ObjEqualityFn(simple1, simple2)) { + if (PtrObjEqualityFn(simple1, simple2)) { return true; } - // Some selector pseudoclasses can match normal selectors. - if (const PseudoSelector* pseudo = Cast(simple2)) { + // Some selector pseudo-classes can match normal selectors. + if (const PseudoSelector* pseudo = simple2->isaPseudoSelector()) { if (pseudo->selector() && isSubselectorPseudo(pseudo->normalized())) { - for (auto complex : pseudo->selector()->elements()) { - // Make sure we have exacly one items - if (complex->length() != 1) { + for (auto& complex : pseudo->selector()->elements()) { + // Make sure we have exactly one items + if (complex->size() != 1) { return false; } // That items must be a compound selector - if (auto compound = Cast(complex->at(0))) { + if (auto compound = complex->at(0)->isaCompoundSelector()) { // It must contain the lhs simple selector if (!compound->contains(simple1)) { return false; @@ -88,17 +94,17 @@ namespace Sass { } // EO simpleIsSuperselector - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [simple] is a superselector of [compound]. // That is, whether [simple] matches every element that // [compound] matches, as well as possibly additional elements. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool simpleIsSuperselectorOfCompound( - const SimpleSelectorObj& simple, - const CompoundSelectorObj& compound) + const SimpleSelector* simple, + const CompoundSelector* compound) { - for (SimpleSelectorObj simple2 : compound->elements()) { - if (simpleIsSuperselector(simple, simple2)) { + for (const SimpleSelectorObj& theirSimple : compound->elements()) { + if (simpleIsSuperselector(simple, theirSimple)) { return true; } } @@ -106,72 +112,72 @@ namespace Sass { } // EO simpleIsSuperselectorOfCompound - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// bool typeIsSuperselectorOfCompound( - const TypeSelectorObj& type, - const CompoundSelectorObj& compound) + const TypeSelector* type, + const CompoundSelector* compound) { for (const SimpleSelectorObj& simple : compound->elements()) { - if (const TypeSelectorObj& rhs = Cast(simple)) { - if (*type != *rhs) return true; + if (const TypeSelector* rhs = simple->isaTypeSelector()) { + if (!(*type == *rhs)) return true; } } return false; } // EO typeIsSuperselectorOfCompound - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// bool idIsSuperselectorOfCompound( - const IDSelectorObj& id, - const CompoundSelectorObj& compound) + const IDSelector* id, + const CompoundSelector* compound) { for (const SimpleSelectorObj& simple : compound->elements()) { - if (const IDSelectorObj& rhs = Cast(simple)) { - if (*id != *rhs) return true; + if (const IDSelector* rhs = simple->isaIDSelector()) { + if (!(*id == *rhs)) return true; } } return false; } // EO idIsSuperselectorOfCompound - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// bool pseudoIsSuperselectorOfPseudo( - const PseudoSelectorObj& pseudo1, - const PseudoSelectorObj& pseudo2, + const PseudoSelector* pseudo1, + const PseudoSelector* pseudo2, const ComplexSelectorObj& parent ) { if (!pseudo2->selector()) return false; if (pseudo1->name() == pseudo2->name()) { - SelectorListObj list = pseudo2->selector(); + const SelectorList* list = pseudo2->selector(); return listIsSuperslector(list->elements(), { parent }); } return false; } // EO pseudoIsSuperselectorOfPseudo - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// bool pseudoNotIsSuperselectorOfCompound( - const PseudoSelectorObj& pseudo1, - const CompoundSelectorObj& compound2, + const PseudoSelector* pseudo1, + const CompoundSelector* compound2, const ComplexSelectorObj& parent) { for (const SimpleSelectorObj& simple2 : compound2->elements()) { - if (const TypeSelectorObj& type2 = Cast(simple2)) { - if (const CompoundSelectorObj& compound1 = Cast(parent->last())) { + if (const TypeSelectorObj& type2 = simple2->isaTypeSelector()) { + if (const CompoundSelector* compound1 = parent->last()->isaCompoundSelector()) { if (typeIsSuperselectorOfCompound(type2, compound1)) return true; } } - else if (const IDSelectorObj& id2 = Cast(simple2)) { - if (const CompoundSelectorObj& compound1 = Cast(parent->last())) { + else if (const IDSelector* id2 = simple2->isaIDSelector()) { + if (const CompoundSelector* compound1 = parent->last()->isaCompoundSelector()) { if (idIsSuperselectorOfCompound(id2, compound1)) return true; } } - else if (const PseudoSelectorObj& pseudo2 = Cast(simple2)) { + else if (const PseudoSelector* pseudo2 = simple2->isaPseudoSelector()) { if (pseudoIsSuperselectorOfPseudo(pseudo1, pseudo2, parent)) return true; } } @@ -179,7 +185,7 @@ namespace Sass { } // pseudoNotIsSuperselectorOfCompound - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [pseudo1] is a superselector of [compound2]. // That is, whether [pseudo1] matches every element that [compound2] // matches, as well as possibly additional elements. This assumes that @@ -187,13 +193,13 @@ namespace Sass { // it represents the parents of [compound2]. This is relevant for pseudo // selectors with selector arguments, where we may need to know if the // parent selectors in the selector argument match [parents]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool selectorPseudoIsSuperselector( - const PseudoSelectorObj& pseudo1, - const CompoundSelectorObj& compound2, + const PseudoSelector* pseudo1, + const CompoundSelector* compound2, // ToDo: is this really the most convenient way to do this? - sass::vector::const_iterator parents_from, - sass::vector::const_iterator parents_to) + SelectorComponentVector::const_iterator parents_from, + SelectorComponentVector::const_iterator parents_to) { // ToDo: move normalization function @@ -202,20 +208,20 @@ namespace Sass { if (name == "matches" || name == "any") { sass::vector pseudos = selectorPseudoNamed(compound2, pseudo1->name()); - SelectorListObj selector1 = pseudo1->selector(); + const SelectorList* selector1 = pseudo1->selector(); for (PseudoSelectorObj pseudo2 : pseudos) { - SelectorListObj selector = pseudo2->selector(); + const SelectorList* selector = pseudo2->selector(); if (selector1->isSuperselectorOf(selector)) { return true; } } for (ComplexSelectorObj complex1 : selector1->elements()) { - sass::vector parents; + SelectorComponentVector parents; for (auto cur = parents_from; cur != parents_to; cur++) { - parents.push_back(*cur); + parents.emplace_back(*cur); } - parents.push_back(compound2); + parents.push_back(const_cast(compound2)); if (complexIsSuperselector(complex1->elements(), parents)) { return true; } @@ -224,10 +230,10 @@ namespace Sass { } else if (name == "has" || name == "host" || name == "host-context" || name == "slotted") { sass::vector pseudos = - selectorPseudoNamed(compound2, pseudo1->name()); - SelectorListObj selector1 = pseudo1->selector(); - for (PseudoSelectorObj pseudo2 : pseudos) { - SelectorListObj selector = pseudo2->selector(); + selectorPseudoNamed(compound2, pseudo1->name(), name != "slotted"); + const SelectorList* selector1 = pseudo1->selector(); + for (const PseudoSelector* pseudo2 : pseudos) { + const SelectorList* selector = pseudo2->selector(); if (selector1->isSuperselectorOf(selector)) { return true; } @@ -235,24 +241,24 @@ namespace Sass { } else if (name == "not") { - for (ComplexSelectorObj complex : pseudo1->selector()->elements()) { + for (ComplexSelectorObj& complex : pseudo1->selector()->elements()) { if (!pseudoNotIsSuperselectorOfCompound(pseudo1, compound2, complex)) return false; } return true; } else if (name == "current") { sass::vector pseudos = - selectorPseudoNamed(compound2, "current"); - for (PseudoSelectorObj pseudo2 : pseudos) { - if (ObjEqualityFn(pseudo1, pseudo2)) return true; + selectorPseudoNamed(compound2, pseudo1->name()); + for (const PseudoSelector* pseudo2 : pseudos) { + if (PtrObjEqualityFn(pseudo1, pseudo2)) return true; } } else if (name == "nth-child" || name == "nth-last-child") { for (auto simple2 : compound2->elements()) { - if (PseudoSelectorObj pseudo2 = simple2->getPseudoSelector()) { + if (const PseudoSelector* pseudo2 = simple2->isaPseudoSelector()) { if (pseudo1->name() != pseudo2->name()) continue; - if (!ObjEqualityFn(pseudo1->argument(), pseudo2->argument())) continue; + if (pseudo1->argument() != pseudo2->argument()) continue; if (pseudo1->selector()->isSuperselectorOf(pseudo2->selector())) return true; } } @@ -264,25 +270,25 @@ namespace Sass { } // EO selectorPseudoIsSuperselector - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [compound1] is a superselector of [compound2]. // That is, whether [compound1] matches every element that [compound2] // matches, as well as possibly additional elements. If [parents] is // passed, it represents the parents of [compound2]. This is relevant // for pseudo selectors with selector arguments, where we may need to // know if the parent selectors in the selector argument match [parents]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool compoundIsSuperselector( - const CompoundSelectorObj& compound1, - const CompoundSelectorObj& compound2, + const CompoundSelector* compound1, + const CompoundSelector* compound2, // ToDo: is this really the most convenient way to do this? - const sass::vector::const_iterator parents_from, - const sass::vector::const_iterator parents_to) + const SelectorComponentVector::const_iterator parents_from, + const SelectorComponentVector::const_iterator parents_to) { // Every selector in [compound1.components] must have // a matching selector in [compound2.components]. - for (SimpleSelectorObj simple1 : compound1->elements()) { - PseudoSelectorObj pseudo1 = Cast(simple1); + for (const SimpleSelector* simple1 : compound1->elements()) { + const PseudoSelector* pseudo1 = simple1->isaPseudoSelector(); if (pseudo1 && pseudo1->selector()) { if (!selectorPseudoIsSuperselector(pseudo1, compound2, parents_from, parents_to)) { return false; @@ -294,9 +300,9 @@ namespace Sass { } // [compound1] can't be a superselector of a selector // with pseudo-elements that [compound2] doesn't share. - for (SimpleSelectorObj simple2 : compound2->elements()) { - PseudoSelectorObj pseudo2 = Cast(simple2); - if (pseudo2 && pseudo2->isElement()) { + for (const SimpleSelector* simple2 : compound2->elements()) { + const PseudoSelector* pseudo2 = simple2->isaPseudoSelector(); + if (pseudo2 && pseudo2->isPseudoElement() && pseudo2->selector() == nullptr) { if (!simpleIsSuperselectorOfCompound(pseudo2, compound1)) { return false; } @@ -306,18 +312,18 @@ namespace Sass { } // EO compoundIsSuperselector - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [compound1] is a superselector of [compound2]. // That is, whether [compound1] matches every element that [compound2] // matches, as well as possibly additional elements. If [parents] is // passed, it represents the parents of [compound2]. This is relevant // for pseudo selectors with selector arguments, where we may need to // know if the parent selectors in the selector argument match [parents]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool compoundIsSuperselector( - const CompoundSelectorObj& compound1, - const CompoundSelectorObj& compound2, - const sass::vector& parents) + const CompoundSelector* compound1, + const CompoundSelector* compound2, + const SelectorComponentVector& parents) { return compoundIsSuperselector( compound1, compound2, @@ -326,19 +332,19 @@ namespace Sass { } // EO compoundIsSuperselector - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [complex1] is a superselector of [complex2]. // That is, whether [complex1] matches every element that // [complex2] matches, as well as possibly additional elements. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool complexIsSuperselector( - const sass::vector& complex1, - const sass::vector& complex2) + const SelectorComponentVector& complex1, + const SelectorComponentVector& complex2) { // Selectors with trailing operators are neither superselectors nor subselectors. - if (!complex1.empty() && Cast(complex1.back())) return false; - if (!complex2.empty() && Cast(complex2.back())) return false; + if (!complex1.empty() && complex1.back()->isaSelectorCombinator()) return false; + if (!complex2.empty() && complex2.back()->isaSelectorCombinator()) return false; size_t i1 = 0, i2 = 0; while (true) { @@ -357,27 +363,23 @@ namespace Sass { // Selectors with leading operators are // neither superselectors nor subselectors. - if (Cast(complex1[i1])) { - return false; - } - if (Cast(complex2[i2])) { - return false; - } + if (complex1[i1]->isaSelectorCombinator()) return false; + if (complex2[i2]->isaSelectorCombinator()) return false; - CompoundSelectorObj compound1 = Cast(complex1[i1]); - CompoundSelectorObj compound2 = Cast(complex2.back()); + const CompoundSelector* compound1 = complex1[i1]->isaCompoundSelector(); + const CompoundSelector* compound2 = complex2.back()->isaCompoundSelector(); if (remaining1 == 1) { - sass::vector::const_iterator parents_to = complex2.end(); - sass::vector::const_iterator parents_from = complex2.begin(); - std::advance(parents_from, i2 + 1); // equivalent to dart `.skip(i2 + 1)` + SelectorComponentVector::const_iterator parents_to = complex2.end(); + SelectorComponentVector::const_iterator parents_from = complex2.begin(); + std::advance(parents_from, i2); std::advance(parents_to, -1); bool rv = compoundIsSuperselector(compound1, compound2, parents_from, parents_to); - sass::vector pp; + SelectorComponentVector pp; - sass::vector::const_iterator end = parents_to; - sass::vector::const_iterator beg = parents_from; + SelectorComponentVector::const_iterator end = parents_to; + SelectorComponentVector::const_iterator beg = parents_from; while (beg != end) { - pp.push_back(*beg); + pp.emplace_back(*beg); beg++; } @@ -385,16 +387,16 @@ namespace Sass { } // Find the first index where `complex2.sublist(i2, afterSuperselector)` - // is a subselector of [compound1]. We stop before the superselector + // is a sub-selector of [compound1]. We stop before the superselector // would encompass all of [complex2] because we know [complex1] has // more than one element, and consuming all of [complex2] wouldn't // leave anything for the rest of [complex1] to match. size_t afterSuperselector = i2 + 1; for (; afterSuperselector < complex2.size(); afterSuperselector++) { SelectorComponentObj component2 = complex2[afterSuperselector - 1]; - if (CompoundSelectorObj compound2 = Cast(component2)) { - sass::vector::const_iterator parents_to = complex2.begin(); - sass::vector::const_iterator parents_from = complex2.begin(); + if (const CompoundSelector* compound2 = component2->isaCompoundSelector()) { + SelectorComponentVector::const_iterator parents_to = complex2.begin(); + SelectorComponentVector::const_iterator parents_from = complex2.begin(); // complex2.take(afterSuperselector - 1).skip(i2 + 1) std::advance(parents_from, i2 + 1); // equivalent to dart `.skip` std::advance(parents_to, afterSuperselector); // equivalent to dart `.take` @@ -410,12 +412,12 @@ namespace Sass { SelectorComponentObj component1 = complex1[i1 + 1], component2 = complex2[afterSuperselector]; - SelectorCombinatorObj combinator1 = Cast(component1); - SelectorCombinatorObj combinator2 = Cast(component2); + SelectorCombinator* combinator1 = component1->isaSelectorCombinator(); + SelectorCombinator* combinator2 = component2->isaSelectorCombinator(); - if (!combinator1.isNull()) { + if (combinator1 != nullptr) { - if (combinator2.isNull()) { + if (combinator2 == nullptr) { return false; } // `.a ~ .b` is a superselector of `.a + .b`, @@ -425,7 +427,7 @@ namespace Sass { return false; } } - else if (*combinator1 != *combinator2) { + else if (!(*combinator1 == *combinator2)) { return false; } @@ -439,7 +441,7 @@ namespace Sass { i1 += 2; i2 = afterSuperselector + 1; } - else if (!combinator2.isNull()) { + else if (combinator2 != nullptr) { if (!combinator2->isChildCombinator()) { return false; } @@ -455,44 +457,45 @@ namespace Sass { } // EO complexIsSuperselector - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Like [complexIsSuperselector], but compares [complex1] // and [complex2] as though they shared an implicit base // [SimpleSelector]. For example, `B` is not normally a // superselector of `B A`, since it doesn't match elements // that match `A`. However, it *is* a parent superselector, // since `B X` is a superselector of `B A X`. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool complexIsParentSuperselector( - const sass::vector& complex1, - const sass::vector& complex2) + const SelectorComponentVector& complex1, + const SelectorComponentVector& complex2) { // Try some simple heuristics to see if we can avoid allocations. if (complex1.empty() && complex2.empty()) return false; - if (Cast(complex1.front())) return false; - if (Cast(complex2.front())) return false; + if (complex1.front()->isaSelectorCombinator()) return false; + if (complex2.front()->isaSelectorCombinator()) return false; if (complex1.size() > complex2.size()) return false; // TODO(nweiz): There's got to be a way to do this without a bunch of extra allocations... - sass::vector cplx1(complex1); - sass::vector cplx2(complex2); - CompoundSelectorObj base = SASS_MEMORY_NEW(CompoundSelector, "[tmp]"); - cplx1.push_back(base); cplx2.push_back(base); + SelectorComponentVector cplx1(complex1); + SelectorComponentVector cplx2(complex2); + CompoundSelectorObj base = SASS_MEMORY_NEW(CompoundSelector, + SourceSpan::tmp("[BASE]")); + cplx1.emplace_back(base); cplx2.emplace_back(base); return complexIsSuperselector(cplx1, cplx2); } // EO complexIsParentSuperselector - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [list] has a superselector for [complex]. // That is, whether an item in [list] matches every element that // [complex] matches, as well as possibly additional elements. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool listHasSuperslectorForComplex( sass::vector list, ComplexSelectorObj complex) { // Return true if every [complex] selector on [list2] // is a super selector of the full selector [list1]. - for (ComplexSelectorObj lhs : list) { + for (const ComplexSelector* lhs : list) { if (complexIsSuperselector(lhs->elements(), complex->elements())) { return true; } @@ -501,18 +504,18 @@ namespace Sass { } // listIsSuperslectorOfComplex - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [list1] is a superselector of [list2]. // That is, whether [list1] matches every element that // [list2] matches, as well as possibly additional elements. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool listIsSuperslector( const sass::vector& list1, const sass::vector& list2) { // Return true if every [complex] selector on [list2] // is a super selector of the full selector [list1]. - for (ComplexSelectorObj complex : list2) { + for (const ComplexSelectorObj& complex : list2) { if (!listHasSuperslectorForComplex(list1, complex)) { return false; } @@ -521,9 +524,9 @@ namespace Sass { } // EO listIsSuperslector - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Implement selector methods (dispatch to functions) - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool SelectorList::isSuperselectorOf(const SelectorList* sub) const { return listIsSuperslector(elements(), sub->elements()); @@ -533,7 +536,7 @@ namespace Sass { return complexIsSuperselector(elements(), sub->elements()); } - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/ast_sel_unify.cpp b/src/ast_sel_unify.cpp index c6a2b397d7..6bd5765500 100644 --- a/src/ast_sel_unify.cpp +++ b/src/ast_sel_unify.cpp @@ -1,29 +1,31 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "ast.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* This file contains all ast unify functions in one compile unit. */ +/*****************************************************************************/ +#include "ast_selectors.hpp" namespace Sass { - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns the contents of a [SelectorList] that matches only // elements that are matched by both [complex1] and [complex2]. // If no such list can be produced, returns `null`. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // ToDo: fine-tune API to avoid unnecessary wrapper allocations - // ########################################################################## - sass::vector> unifyComplex( - const sass::vector>& complexes) + ///////////////////////////////////////////////////////////////////////// + sass::vector unifyComplex( + const sass::vector& complexes) { SASS_ASSERT(!complexes.empty(), "Can't unify empty list"); if (complexes.size() == 1) return complexes; - CompoundSelectorObj unifiedBase = SASS_MEMORY_NEW(CompoundSelector, SourceSpan("[phony]")); + CompoundSelectorObj unifiedBase = SASS_MEMORY_NEW(CompoundSelector, + SourceSpan::tmp("[BASE]")); for (auto complex : complexes) { SelectorComponentObj base = complex.back(); - if (CompoundSelector * comp = base->getCompound()) { + if (CompoundSelector * comp = base->isaCompoundSelector()) { if (unifiedBase->empty()) { unifiedBase->concat(comp); } @@ -39,25 +41,25 @@ namespace Sass { } } - sass::vector> complexesWithoutBases; + sass::vector complexesWithoutBases; for (size_t i = 0; i < complexes.size(); i += 1) { - sass::vector sel = complexes[i]; + SelectorComponentVector sel = complexes[i]; sel.pop_back(); // remove last item (base) from the list - complexesWithoutBases.push_back(std::move(sel)); + complexesWithoutBases.emplace_back(std::move(sel)); } - complexesWithoutBases.back().push_back(unifiedBase); + complexesWithoutBases.back().emplace_back(unifiedBase); return weave(complexesWithoutBases); } // EO unifyComplex - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns a [CompoundSelector] that matches only elements // that are matched by both [compound1] and [compound2]. // If no such selector can be produced, returns `null`. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// CompoundSelector* CompoundSelector::unifyWith(CompoundSelector* rhs) { if (empty()) return rhs; @@ -70,63 +72,63 @@ namespace Sass { } // EO CompoundSelector::unifyWith(CompoundSelector*) - // ########################################################################## - // Returns the compoments of a [CompoundSelector] that matches only elements + ///////////////////////////////////////////////////////////////////////// + // Returns the components of a [CompoundSelector] that matches only elements // matched by both this and [compound]. By default, this just returns a copy // of [compound] with this selector added to the end, or returns the original // array if this selector already exists in it. Returns `null` if unification // is impossible—for example, if there are multiple ID selectors. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // This is implemented in `selector/simple.dart` as `SimpleSelector::unify` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// CompoundSelector* SimpleSelector::unifyWith(CompoundSelector* rhs) { - - if (rhs->length() == 1) { - if (rhs->get(0)->is_universal()) { - CompoundSelector* this_compound = SASS_MEMORY_NEW(CompoundSelector, pstate()); - this_compound->append(SASS_MEMORY_COPY(this)); - CompoundSelector* unified = rhs->get(0)->unifyWith(this_compound); - if (unified == nullptr || unified != this_compound) delete this_compound; - return unified; + if (rhs->size() == 1) { + if (rhs->get(0)->isUniversal()) { + CompoundSelectorObj compound(SASS_MEMORY_COPY(this)->wrapInCompound()); + // We must store result in an OBJ, as unifyWith may return its argument + // Meaning we get pointer to `compound`, which should not be collected then + CompoundSelectorObj unified = rhs->get(0)->unifyWith(compound); + return unified.detach(); // The `compound` survive } } - for (const SimpleSelectorObj& sel : rhs->elements()) { + for (const SimpleSelector* sel : rhs->elements()) { if (*this == *sel) { return rhs; } } - CompoundSelectorObj result = SASS_MEMORY_NEW(CompoundSelector, rhs->pstate()); + sass::vector results; + results.reserve(rhs->size() + 1); bool addedThis = false; for (auto simple : rhs->elements()) { // Make sure pseudo selectors always come last. - if (!addedThis && simple->getPseudoSelector()) { - result->append(this); + if (!addedThis && simple->isaPseudoSelector()) { + results.push_back(this); addedThis = true; } - result->append(simple); + results.push_back(simple); } - if (!addedThis) { - result->append(this); + results.push_back(this); } - return result.detach(); + return SASS_MEMORY_NEW(CompoundSelector, + rhs->pstate(), std::move(results)); } // EO SimpleSelector::unifyWith(CompoundSelector*) - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // This is implemented in `selector/type.dart` as `PseudoSelector::unify` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// CompoundSelector* TypeSelector::unifyWith(CompoundSelector* rhs) { if (rhs->empty()) { rhs->append(this); return rhs; } - TypeSelector* type = Cast(rhs->at(0)); + TypeSelector* type = rhs->at(0)->isaTypeSelector(); if (type != nullptr) { SimpleSelector* unified = unifyWith(type); if (unified == nullptr) { @@ -134,124 +136,126 @@ namespace Sass { } rhs->elements()[0] = unified; } - else if (!is_universal() || (has_ns_ && ns_ != "*")) { + else if (!isUniversal() || (hasNs_ && ns_ != "*")) { rhs->insert(rhs->begin(), this); } return rhs; } - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // This is implemented in `selector/id.dart` as `PseudoSelector::unify` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// CompoundSelector* IDSelector::unifyWith(CompoundSelector* rhs) { for (const SimpleSelector* sel : rhs->elements()) { - if (const IDSelector* id_sel = Cast(sel)) { + if (const IDSelector* id_sel = sel->isaIDSelector()) { if (id_sel->name() != name()) return nullptr; } } return SimpleSelector::unifyWith(rhs); } - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // This is implemented in `selector/pseudo.dart` as `PseudoSelector::unify` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// CompoundSelector* PseudoSelector::unifyWith(CompoundSelector* compound) { - if (compound->length() == 1 && compound->first()->is_universal()) { - // std::cerr << "implement universal pseudo\n"; - } - for (const SimpleSelectorObj& sel : compound->elements()) { if (*this == *sel) { return compound; } } - CompoundSelectorObj result = SASS_MEMORY_NEW(CompoundSelector, compound->pstate()); + sass::vector results; + results.reserve(compound->size() + 1); + // CompoundSelectorObj result = SASS_MEMORY_NEW(CompoundSelector, compound->pstate()); bool addedThis = false; for (auto simple : compound->elements()) { // Make sure pseudo selectors always come last. - if (PseudoSelectorObj pseudo = simple->getPseudoSelector()) { - if (pseudo->isElement()) { + if (PseudoSelectorObj pseudo = simple->isaPseudoSelector()) { + if (pseudo->isPseudoElement()) { // A given compound selector may only contain one pseudo element. If // [compound] has a different one than [this], unification fails. - if (isElement()) { + if (isPseudoElement()) { return {}; } // Otherwise, this is a pseudo selector and - // should come before pseduo elements. - result->append(this); + // should come before pseudo elements. + results.push_back(this); addedThis = true; } } - result->append(simple); + results.push_back(simple); } if (!addedThis) { - result->append(this); + results.push_back(this); } - return result.detach(); + return SASS_MEMORY_NEW(CompoundSelector, + compound->pstate(), std::move(results)); } // EO PseudoSelector::unifyWith(CompoundSelector* - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // This is implemented in `extend/functions.dart` as `unifyUniversalAndElement` // Returns a [SimpleSelector] that matches only elements that are matched by // both [selector1] and [selector2], which must both be either [UniversalSelector]s // or [TypeSelector]s. If no such selector can be produced, returns `null`. // Note: libsass handles universal selector directly within the type selector - // ########################################################################## - SimpleSelector* TypeSelector::unifyWith(const SimpleSelector* rhs) + ///////////////////////////////////////////////////////////////////////// + SimpleSelector* TypeSelector::unifyWith(const SimpleSelector* rhs2) { - bool rhs_ns = false; - if (!(is_ns_eq(*rhs) || rhs->is_universal_ns())) { - if (!is_universal_ns()) { - return nullptr; + if (auto rhs = rhs2->isaNameSpaceSelector()) { + bool rhs_ns = false; + if (!(nsEqual(*rhs) || rhs->isUniversalNs())) { + if (!isUniversalNs()) { + return nullptr; + } + rhs_ns = true; } - rhs_ns = true; - } - bool rhs_name = false; - if (!(name_ == rhs->name() || rhs->is_universal())) { - if (!(is_universal())) { - return nullptr; + bool rhs_name = false; + if (!(name_ == rhs->name() || rhs->isUniversal())) { + if (!(isUniversal())) { + return nullptr; + } + rhs_name = true; } - rhs_name = true; - } - if (rhs_ns) { - ns(rhs->ns()); - has_ns(rhs->has_ns()); + if (rhs_ns) { + ns(rhs->ns()); + hasNs(rhs->hasNs()); + } + if (rhs_name) name(rhs->name()); } - if (rhs_name) name(rhs->name()); return this; } // EO TypeSelector::unifyWith(const SimpleSelector*) - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Unify two complex selectors. Internally calls `unifyComplex` // and then wraps the result in newly create ComplexSelectors. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// SelectorList* ComplexSelector::unifyWith(ComplexSelector* rhs) { - SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate()); - sass::vector> rv = + sass::vector rv = unifyComplex({ elements(), rhs->elements() }); - for (sass::vector items : rv) { - ComplexSelectorObj sel = SASS_MEMORY_NEW(ComplexSelector, pstate()); - sel->elements() = std::move(items); - list->append(sel); + sass::vector list; + list.reserve(rv.size()); + for (SelectorComponentVector& items : rv) { + list.push_back(SASS_MEMORY_NEW(ComplexSelector, + pstate(), std::move(items))); } - return list.detach(); + return SASS_MEMORY_NEW(SelectorList, + pstate(), std::move(list)); } // EO ComplexSelector::unifyWith(ComplexSelector*) - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // only called from the sass function `selector-unify` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// SelectorList* SelectorList::unifyWith(SelectorList* rhs) { SelectorList* slist = SASS_MEMORY_NEW(SelectorList, pstate()); @@ -269,7 +273,7 @@ namespace Sass { } // EO SelectorList::unifyWith(SelectorList*) - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/ast_sel_weave.cpp b/src/ast_sel_weave.cpp index 46b83861bf..46cb066530 100644 --- a/src/ast_sel_weave.cpp +++ b/src/ast_sel_weave.cpp @@ -1,62 +1,66 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "ast_selectors.hpp" -#include "ast.hpp" #include "permutate.hpp" #include "dart_helpers.hpp" namespace Sass { - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether or not [compound] contains a `::root` selector. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool hasRoot(const CompoundSelector* compound) { - // Libsass does not yet know the root selector + for (const SimpleSelector* simple : compound->elements()) { + if (const PseudoSelector* pseudo = simple->isaPseudoSelector()) { + if (pseudo->isClass() && pseudo->normalized() == "root") { + return true; + } + } + } return false; } // EO hasRoot - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether a [CompoundSelector] may contain only // one simple selector of the same type as [simple]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool isUnique(const SimpleSelector* simple) { - if (Cast(simple)) return true; - if (const PseudoSelector * pseudo = Cast(simple)) { - if (pseudo->is_pseudo_element()) return true; + if (simple->isaIDSelector()) return true; + if (const PseudoSelector * pseudo = simple->isaPseudoSelector()) { + if (pseudo->isPseudoElement()) return true; } return false; } // EO isUnique - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [complex1] and [complex2] need to be unified to // produce a valid combined selector. This is necessary when both // selectors contain the same unique simple selector, such as an ID. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool mustUnify( - const sass::vector& complex1, - const sass::vector& complex2) + const SelectorComponentVector& complex1, + const SelectorComponentVector& complex2) { sass::vector uniqueSelectors1; for (const SelectorComponent* component : complex1) { - if (const CompoundSelector * compound = component->getCompound()) { + if (const CompoundSelector* compound = component->isaCompoundSelector()) { for (const SimpleSelector* sel : compound->elements()) { if (isUnique(sel)) { - uniqueSelectors1.push_back(sel); + uniqueSelectors1.emplace_back(sel); } } } } if (uniqueSelectors1.empty()) return false; - - // ToDo: unsure if this is correct for (const SelectorComponent* component : complex2) { - if (const CompoundSelector * compound = component->getCompound()) { + if (const CompoundSelector* compound = component->isaCompoundSelector()) { for (const SimpleSelector* sel : compound->elements()) { if (isUnique(sel)) { for (auto check : uniqueSelectors1) { @@ -68,29 +72,31 @@ namespace Sass { } return false; - } // EO isUnique - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used by `weaveParents` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool cmpGroups( - const sass::vector& group1, - const sass::vector& group2, - sass::vector& select) + const SelectorComponentVector& group1, + const SelectorComponentVector& group2, + SelectorComponentVector& select) { - if (group1.size() == group2.size() && std::equal(group1.begin(), group1.end(), group2.begin(), PtrObjEqualityFn)) { + if (group1.size() == group2.size() && std::equal( + group1.begin(), group1.end(), group2.begin(), + PtrObjEqualityFn)) + { select = group1; return true; } - if (!Cast(group1.front())) { + if (!group1.front()->isaCompoundSelector()) { select = {}; return false; } - if (!Cast(group2.front())) { + if (!group2.front()->isaCompoundSelector()) { select = {}; return false; } @@ -105,59 +111,57 @@ namespace Sass { } if (!mustUnify(group1, group2)) { - select = {}; + select.clear(); return false; } - sass::vector> unified - = unifyComplex({ group1, group2 }); - if (unified.empty()) return false; - if (unified.size() > 1) return false; - select = unified.front(); + auto unified = unifyComplex({ group1, group2 }); + if (unified.size() != 1) return false; + select = std::move(unified.front()); return true; } // EO cmpGroups - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used by `weaveParents` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// template bool checkForEmptyChild(const T& item) { return item.empty(); } // EO checkForEmptyChild - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used by `weaveParents` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool cmpChunkForEmptySequence( - const sass::vector>& seq, - const sass::vector& group) + const sass::vector& seq, + const SelectorComponentVector& group) { return seq.empty(); } // EO cmpChunkForEmptySequence - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used by `weaveParents` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool cmpChunkForParentSuperselector( - const sass::vector>& seq, - const sass::vector& group) + const sass::vector& seq, + const SelectorComponentVector& group) { return seq.empty() || complexIsParentSuperselector(seq.front(), group); } // EO cmpChunkForParentSuperselector - // ########################################################################## - // Returns all orderings of initial subseqeuences of [queue1] and [queue2]. + ///////////////////////////////////////////////////////////////////////// + // Returns all orderings of initial subsequences of [queue1] and [queue2]. // The [done] callback is used to determine the extent of the initial // subsequences. It's called with each queue until it returns `true`. // Destructively removes the initial subsequences of [queue1] and [queue2]. // For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|` denoting // the boundary of the initial subsequence), this would return `[(A B C 1 2), // (1 2 A B C)]`. The queues would then contain `(D E)` and `(3 4 5)`. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// template sass::vector> getChunks( sass::vector& queue1, sass::vector& queue2, @@ -166,13 +170,13 @@ namespace Sass { sass::vector chunk1; while (!done(queue1, group)) { - chunk1.push_back(queue1.front()); + chunk1.emplace_back(queue1.front()); queue1.erase(queue1.begin()); } sass::vector chunk2; while (!done(queue2, group)) { - chunk2.push_back(queue2.front()); + chunk2.emplace_back(queue2.front()); queue2.erase(queue2.begin()); } @@ -180,23 +184,27 @@ namespace Sass { else if (chunk1.empty()) return { chunk2 }; else if (chunk2.empty()) return { chunk1 }; - sass::vector choice1(chunk1), choice2(chunk2); - std::move(std::begin(chunk2), std::end(chunk2), - std::inserter(choice1, std::end(choice1))); - std::move(std::begin(chunk1), std::end(chunk1), - std::inserter(choice2, std::end(choice2))); - return { choice1, choice2 }; + sass::vector> result; + result.emplace_back(chunk1); + result.emplace_back(chunk2); + result.front().insert(result.front().end(), + std::make_move_iterator(chunk2.begin()), + std::make_move_iterator(chunk2.end())); + result.back().insert(result.back().end(), + std::make_move_iterator(chunk1.begin()), + std::make_move_iterator(chunk1.end())); + return result; } // EO getChunks - // ########################################################################## - // If the first element of [queue] has a `::root` + ///////////////////////////////////////////////////////////////////////// + // If the first element of [queue] has a `::root` // selector, removes and returns that element. - // ########################################################################## - CompoundSelectorObj getFirstIfRoot(sass::vector& queue) { + ///////////////////////////////////////////////////////////////////////// + CompoundSelectorObj getFirstIfRoot(SelectorComponentVector& queue) { if (queue.empty()) return {}; SelectorComponent* first = queue.front(); - if (CompoundSelector* sel = Cast(first)) { + if (CompoundSelector* sel = first->isaCompoundSelector()) { if (!hasRoot(sel)) return {}; queue.erase(queue.begin()); return sel; @@ -205,67 +213,67 @@ namespace Sass { } // EO getFirstIfRoot - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns [complex], grouped into sub-lists such that no sub-list // contains two adjacent [ComplexSelector]s. For example, // `(A B > C D + E ~ > G)` is grouped into `[(A) (B > C) (D + E ~ > G)]`. - // ########################################################################## - sass::vector> groupSelectors( - const sass::vector& components) + ///////////////////////////////////////////////////////////////////////// + sass::vector groupSelectors( + const SelectorComponentVector& components) { bool lastWasCompound = false; - sass::vector group; - sass::vector> groups; + SelectorComponentVector group; + sass::vector groups; for (size_t i = 0; i < components.size(); i += 1) { - if (CompoundSelector* compound = components[i]->getCompound()) { + if (CompoundSelector* compound = components[i]->isaCompoundSelector()) { if (lastWasCompound) { - groups.push_back(group); + groups.emplace_back(group); group.clear(); } - group.push_back(compound); + group.emplace_back(compound); lastWasCompound = true; } - else if (SelectorCombinator* combinator = components[i]->getCombinator()) { - group.push_back(combinator); + else if (SelectorCombinator* combinator = components[i]->isaSelectorCombinator()) { + group.emplace_back(combinator); lastWasCompound = false; } } if (!group.empty()) { - groups.push_back(group); + groups.emplace_back(group); } return groups; } // EO groupSelectors - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extracts leading [Combinator]s from [components1] and [components2] // and merges them together into a single list of combinators. // If there are no combinators to be merged, returns an empty list. // If the combinators can't be merged, returns `null`. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool mergeInitialCombinators( - sass::vector& components1, - sass::vector& components2, - sass::vector& result) + SelectorComponentVector& components1, + SelectorComponentVector& components2, + SelectorComponentVector& result) { - sass::vector combinators1; - while (!components1.empty() && Cast(components1.front())) { - SelectorCombinatorObj front = Cast(components1.front()); + SelectorComponentVector combinators1; + while (!components1.empty() && components1.front()->isaSelectorCombinator()) { + SelectorCombinator* front = components1.front()->isaSelectorCombinator(); components1.erase(components1.begin()); - combinators1.push_back(front); + combinators1.emplace_back(front); } - sass::vector combinators2; - while (!components2.empty() && Cast(components2.front())) { - SelectorCombinatorObj front = Cast(components2.front()); + SelectorComponentVector combinators2; + while (!components2.empty() && components2.front()->isaSelectorCombinator()) { + SelectorCombinator* front = components2.front()->isaSelectorCombinator(); components2.erase(components2.begin()); - combinators2.push_back(front); + combinators2.emplace_back(front); } // If neither sequence of combinators is a subsequence // of the other, they cannot be merged successfully. - sass::vector LCS = lcs(combinators1, combinators2); + SelectorComponentVector LCS = lcs(combinators1, combinators2); if (ListEquality(LCS, combinators1, PtrObjEqualityFn)) { result = combinators2; @@ -281,45 +289,45 @@ namespace Sass { } // EO mergeInitialCombinators - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extracts trailing [Combinator]s, and the selectors to which they apply, // from [components1] and [components2] and merges them together into a // single list. If there are no combinators to be merged, returns an // empty list. If the sequences can't be merged, returns `null`. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool mergeFinalCombinators( - sass::vector& components1, - sass::vector& components2, - sass::vector>>& result) + SelectorComponentVector& components1, + SelectorComponentVector& components2, + sass::vector>& result) { - if (components1.empty() || !Cast(components1.back())) { - if (components2.empty() || !Cast(components2.back())) { + if (components1.empty() || components1.back()->isaSelectorCombinator() == nullptr) { + if (components2.empty() || components2.back()->isaSelectorCombinator() == nullptr) { return true; } } - - sass::vector combinators1; - while (!components1.empty() && Cast(components1.back())) { - SelectorCombinatorObj back = Cast(components1.back()); + + SelectorComponentVector combinators1; + while (!components1.empty() && components1.back()->isaSelectorCombinator() != nullptr) { + SelectorCombinatorObj back = components1.back()->isaSelectorCombinator(); components1.erase(components1.end() - 1); - combinators1.push_back(back); + combinators1.emplace_back(back); } - sass::vector combinators2; - while (!components2.empty() && Cast(components2.back())) { - SelectorCombinatorObj back = Cast(components2.back()); + SelectorComponentVector combinators2; + while (!components2.empty() && components2.back()->isaSelectorCombinator() != nullptr) { + SelectorCombinatorObj back = components2.back()->isaSelectorCombinator(); components2.erase(components2.end() - 1); - combinators2.push_back(back); + combinators2.emplace_back(back); } - // reverse now as we used push_back (faster than new alloc) + // reverse now as we used emplace_back (faster than new alloc) std::reverse(combinators1.begin(), combinators1.end()); std::reverse(combinators2.begin(), combinators2.end()); if (combinators1.size() > 1 || combinators2.size() > 1) { - // If there are multiple combinators, something hacky's going on. If one - // is a supersequence of the other, use that, otherwise give up. + // If there are multiple combinators, something strange going on. If one + // is a super-sequence of the other, use that, otherwise give up. auto LCS = lcs(combinators1, combinators2); if (ListEquality(LCS, combinators1, PtrObjEqualityFn)) { result.push_back({ combinators2 }); @@ -341,8 +349,8 @@ namespace Sass { if (!combinator1.isNull() && !combinator2.isNull()) { - CompoundSelector* compound1 = Cast(components1.back()); - CompoundSelector* compound2 = Cast(components2.back()); + CompoundSelector* compound1 = components1.back()->isaCompoundSelector(); + CompoundSelector* compound2 = components2.back()->isaCompoundSelector(); components1.pop_back(); components2.pop_back(); @@ -356,13 +364,13 @@ namespace Sass { result.push_back({ { compound1, combinator1 } }); } else { - sass::vector> choices; + sass::vector choices; choices.push_back({ compound1, combinator1, compound2, combinator2 }); choices.push_back({ compound2, combinator2, compound1, combinator1 }); if (CompoundSelector* unified = compound1->unifyWith(compound2)) { choices.push_back({ unified, combinator1 }); } - result.push_back(choices); + result.emplace_back(choices); } } else if ((combinator1->isGeneralCombinator() && combinator2->isAdjacentCombinator()) || @@ -378,8 +386,8 @@ namespace Sass { } else { CompoundSelectorObj unified = compound1->unifyWith(compound2); - sass::vector> items; - + sass::vector items; + if (!unified.isNull()) { items.push_back({ unified, nextSiblingCombinator @@ -393,19 +401,19 @@ namespace Sass { nextSiblingCombinator, }); - result.push_back(items); + result.emplace_back(items); } } else if (combinator1->isChildCombinator() && (combinator2->isAdjacentCombinator() || combinator2->isGeneralCombinator())) { result.push_back({ { compound2, combinator2 } }); - components1.push_back(compound1); - components1.push_back(combinator1); + components1.emplace_back(compound1); + components1.emplace_back(combinator1); } else if (combinator2->isChildCombinator() && (combinator1->isAdjacentCombinator() || combinator1->isGeneralCombinator())) { result.push_back({ { compound1, combinator1 } }); - components2.push_back(compound2); - components2.push_back(combinator2); + components2.emplace_back(compound2); + components2.emplace_back(combinator2); } else if (*combinator1 == *combinator2) { CompoundSelectorObj unified = compound1->unifyWith(compound2); @@ -422,8 +430,8 @@ namespace Sass { else if (!combinator1.isNull()) { if (combinator1->isChildCombinator() && !components2.empty()) { - const CompoundSelector* back1 = Cast(components1.back()); - const CompoundSelector* back2 = Cast(components2.back()); + const CompoundSelector* back1 = components1.back()->isaCompoundSelector(); + const CompoundSelector* back2 = components2.back()->isaCompoundSelector(); if (back1 && back2 && back2->isSuperselectorOf(back1)) { components2.pop_back(); } @@ -438,8 +446,8 @@ namespace Sass { } if (combinator2->isChildCombinator() && !components1.empty()) { - const CompoundSelector* back1 = Cast(components1.back()); - const CompoundSelector* back2 = Cast(components2.back()); + const CompoundSelector* back1 = components1.back()->isaCompoundSelector(); + const CompoundSelector* back2 = components2.back()->isaCompoundSelector(); if (back1 && back2 && back1->isSuperselectorOf(back2)) { components1.pop_back(); } @@ -454,7 +462,7 @@ namespace Sass { } // EO mergeFinalCombinators - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Expands "parenthesized selectors" in [complexes]. That is, if // we have `.A .B {@extend .C}` and `.D .C {...}`, this conceptually // expands into `.D .C, .D (.A .B)`, and this function translates @@ -462,40 +470,40 @@ namespace Sass { // would also be required, but including merged selectors results in // exponential output for very little gain. The selector `.D (.A .B)` // is represented as the list `[[.D], [.A, .B]]`. - // ########################################################################## - sass::vector> weave( - const sass::vector>& complexes) { + ///////////////////////////////////////////////////////////////////////// + sass::vector weave( + const sass::vector& complexes) { - sass::vector> prefixes; + sass::vector prefixes; - prefixes.push_back(complexes.at(0)); + prefixes.emplace_back(complexes.at(0)); for (size_t i = 1; i < complexes.size(); i += 1) { if (complexes[i].empty()) { continue; } - const sass::vector& complex = complexes[i]; + const SelectorComponentVector& complex = complexes[i]; SelectorComponent* target = complex.back(); if (complex.size() == 1) { for (auto& prefix : prefixes) { - prefix.push_back(target); + prefix.emplace_back(target); } continue; } - sass::vector parents(complex); + SelectorComponentVector parents(complex); parents.pop_back(); - sass::vector> newPrefixes; - for (sass::vector prefix : prefixes) { - sass::vector> + sass::vector newPrefixes; + for (SelectorComponentVector prefix : prefixes) { + sass::vector parentPrefixes = weaveParents(prefix, parents); if (parentPrefixes.empty()) continue; for (auto& parentPrefix : parentPrefixes) { - parentPrefix.push_back(target); - newPrefixes.push_back(parentPrefix); + parentPrefix.emplace_back(target); + newPrefixes.emplace_back(parentPrefix); } } prefixes = newPrefixes; @@ -506,7 +514,7 @@ namespace Sass { } // EO weave - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Interweaves [parents1] and [parents2] as parents of the same target // selector. Returns all possible orderings of the selectors in the // inputs (including using unification) that maintain the relative @@ -518,14 +526,14 @@ namespace Sass { // of elements matched by `AB_i X` is identical to the intersection of all // elements matched by `A X` and all elements matched by `B X`. Some `AB_i` // are elided to reduce the size of the output. - // ########################################################################## - sass::vector> weaveParents( - sass::vector queue1, - sass::vector queue2) + ///////////////////////////////////////////////////////////////////////// + sass::vector weaveParents( + SelectorComponentVector queue1, + SelectorComponentVector queue2) { - sass::vector leads; - sass::vector>> trails; + SelectorComponentVector leads; + sass::vector> trails; if (!mergeInitialCombinators(queue1, queue2, leads)) return {}; if (!mergeFinalCombinators(queue1, queue2, trails)) return {}; // list comes out in reverse order for performance @@ -550,31 +558,31 @@ namespace Sass { } // group into sub-lists so no sub-list contains two adjacent ComplexSelectors. - sass::vector> groups1 = groupSelectors(queue1); - sass::vector> groups2 = groupSelectors(queue2); + sass::vector groups1 = groupSelectors(queue1); + sass::vector groups2 = groupSelectors(queue2); // The main array to store our choices that will be permutated - sass::vector>> choices; + sass::vector> choices; // append initial combinators - choices.push_back({ leads }); + choices.push_back({ std::move(leads) }); - sass::vector> LCS = - lcs>(groups1, groups2, cmpGroups); + sass::vector LCS = + lcs(groups1, groups2, cmpGroups); for (auto group : LCS) { // Create junks from groups1 and groups2 - sass::vector>> - chunks = getChunks>( + sass::vector> + chunks = getChunks( groups1, groups2, group, cmpChunkForParentSuperselector); // Create expanded array by flattening chunks2 inner - sass::vector> + sass::vector expanded = flattenInner(chunks); // Prepare data structures - choices.push_back(expanded); + choices.emplace_back(expanded); choices.push_back({ group }); if (!groups1.empty()) { groups1.erase(groups1.begin()); @@ -586,31 +594,29 @@ namespace Sass { } // Create junks from groups1 and groups2 - sass::vector>> - chunks = getChunks>( + sass::vector> + chunks = getChunks( groups1, groups2, {}, cmpChunkForEmptySequence); // Append chunks with inner arrays flattened choices.emplace_back(flattenInner(chunks)); // append all trailing selectors to choices - std::move(std::begin(trails), std::end(trails), - std::inserter(choices, std::end(choices))); + choices.insert(choices.end(), + std::make_move_iterator(trails.begin()), + std::make_move_iterator(trails.end())); // move all non empty items to the front, then erase the trailing ones choices.erase(std::remove_if(choices.begin(), choices.end(), checkForEmptyChild - >>), choices.end()); + >), choices.end()); // permutate all possible paths through selectors - sass::vector> - results = flattenInner(permutate(choices)); - - return results; + return flattenInner(permutate(choices)); } // EO weaveParents - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/ast_selectors.cpp b/src/ast_selectors.cpp index c142842975..af069f4f6e 100644 --- a/src/ast_selectors.cpp +++ b/src/ast_selectors.cpp @@ -1,1068 +1,787 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +#include "ast_selectors.hpp" -#include "ast.hpp" #include "permutate.hpp" -#include "util_string.hpp" +#include "callstack.hpp" +#include "dart_helpers.hpp" +#include "ast_values.hpp" +#include "exceptions.hpp" +#include "cssize.hpp" namespace Sass { ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Selector::Selector(SourceSpan pstate) - : Expression(pstate), + Selector::Selector( + const SourceSpan& pstate) : + AstNode(pstate), hash_(0) - { concrete_type(SELECTOR); } + {} - Selector::Selector(const Selector* ptr) - : Expression(ptr), - hash_(ptr->hash_) - { concrete_type(SELECTOR); } - - - bool Selector::has_real_parent_ref() const - { - return false; - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Selector_Schema::Selector_Schema(SourceSpan pstate, String_Obj c) - : AST_Node(pstate), - contents_(c), - connect_parent_(true), + Selector::Selector( + const Selector* ptr) : + AstNode(ptr), hash_(0) - { } - Selector_Schema::Selector_Schema(const Selector_Schema* ptr) - : AST_Node(ptr), - contents_(ptr->contents_), - connect_parent_(ptr->connect_parent_), - hash_(ptr->hash_) - { } - - unsigned long Selector_Schema::specificity() const - { - return 0; - } - - size_t Selector_Schema::hash() const { - if (hash_ == 0) { - hash_combine(hash_, contents_->hash()); - } - return hash_; - } - - bool Selector_Schema::has_real_parent_ref() const - { - // Note: disabled since it does not seem to do anything? - // if (String_Schema_Obj schema = Cast(contents())) { - // if (schema->empty()) return false; - // const auto first = schema->first(); - // return Cast(first); - // } - return false; - } + {} ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - SimpleSelector::SimpleSelector(SourceSpan pstate, sass::string n) - : Selector(pstate), ns_(""), name_(n), has_ns_(false) - { - size_t pos = n.find('|'); - // found some namespace - if (pos != sass::string::npos) { - has_ns_ = true; - ns_ = n.substr(0, pos); - name_ = n.substr(pos + 1); - } - } - SimpleSelector::SimpleSelector(const SimpleSelector* ptr) - : Selector(ptr), - ns_(ptr->ns_), - name_(ptr->name_), - has_ns_(ptr->has_ns_) - { } + SimpleSelector::SimpleSelector( + const SourceSpan& pstate, + const sass::string& name) : + Selector(pstate), + name_(name) + {} - sass::string SimpleSelector::ns_name() const - { - if (!has_ns_) return name_; - else return ns_ + "|" + name_; - } + SimpleSelector::SimpleSelector( + const SourceSpan& pstate, + sass::string&& name) : + Selector(pstate), + name_(name) + {} + + SimpleSelector::SimpleSelector( + const SimpleSelector* ptr) : + Selector(ptr), + name_(ptr->name_) + {} size_t SimpleSelector::hash() const { if (hash_ == 0) { + hash_start(hash_, typeid(this).hash_code()); hash_combine(hash_, name()); - hash_combine(hash_, (int)SELECTOR); - hash_combine(hash_, (int)simple_type()); - if (has_ns_) hash_combine(hash_, ns()); } return hash_; } - bool SimpleSelector::empty() const { - return ns().empty() && name().empty(); - } - - // namespace compare functions - bool SimpleSelector::is_ns_eq(const SimpleSelector& r) const - { - return has_ns_ == r.has_ns_ && ns_ == r.ns_; - } - - // namespace query functions - bool SimpleSelector::is_universal_ns() const + CompoundSelector* SimpleSelector::wrapInCompound() { - return has_ns_ && ns_ == "*"; + return SASS_MEMORY_NEW(CompoundSelector, pstate(), { this }, false); } - bool SimpleSelector::is_empty_ns() const + ComplexSelector* SimpleSelector::wrapInComplex() { - return !has_ns_ || ns_ == ""; + return SASS_MEMORY_NEW(ComplexSelector, pstate(), { wrapInCompound() }); } - bool SimpleSelector::has_empty_ns() const - { - return has_ns_ && ns_ == ""; - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - bool SimpleSelector::has_qualified_ns() const - { - return has_ns_ && ns_ != "" && ns_ != "*"; - } + NameSpaceSelector::NameSpaceSelector( + const SourceSpan& pstate, + sass::string&& name, + sass::string&& ns, + bool hasNs) : + SimpleSelector(pstate, + std::move(name)), + hasNs_(hasNs), + ns_(std::move(ns)) + {} - // name query functions - bool SimpleSelector::is_universal() const - { - return name_ == "*"; - } + NameSpaceSelector::NameSpaceSelector( + const NameSpaceSelector* ptr) : + SimpleSelector(ptr), + hasNs_(ptr->hasNs_), + ns_(ptr->ns_) + {} - bool SimpleSelector::has_placeholder() + size_t NameSpaceSelector::hash() const { - return false; + if (hash_ == 0) { + hash_start(hash_, typeid(this).hash_code()); + hash_combine(hash_, SimpleSelector::hash()); + if (hasNs_) hash_combine(hash_, ns()); + } + return hash_; } - bool SimpleSelector::has_real_parent_ref() const - { - return false; - }; + ///////////////////////////////////////////////////////////////////////// - bool SimpleSelector::is_pseudo_element() const + // Up-casts the right hand side first to find specialization + bool NameSpaceSelector::nsMatch(const SimpleSelector& rhs) const { + // if (hasNs() == false || isUniversalNs()) { + // return SimpleSelector::nsMatch(rhs); + // } + if (auto simple = rhs.isaNameSpaceSelector()) { + return NameSpaceSelector::nsMatch(*simple); + } return false; } - CompoundSelectorObj SimpleSelector::wrapInCompound() - { - CompoundSelectorObj selector = - SASS_MEMORY_NEW(CompoundSelector, pstate()); - selector->append(this); - return selector; - } - ComplexSelectorObj SimpleSelector::wrapInComplex() - { - ComplexSelectorObj selector = - SASS_MEMORY_NEW(ComplexSelector, pstate()); - selector->append(wrapInCompound()); - return selector; - } - ///////////////////////////////////////////////////////////////////////// + // A placeholder selector. (e.g. `%foo`). This doesn't match any elements. + // It's intended to be extended using `@extend`. It's not a plain CSS + // selector — it should be removed before emitting a CSS document. ///////////////////////////////////////////////////////////////////////// - PlaceholderSelector::PlaceholderSelector(SourceSpan pstate, sass::string n) - : SimpleSelector(pstate, n) - { simple_type(PLACEHOLDER_SEL); } - PlaceholderSelector::PlaceholderSelector(const PlaceholderSelector* ptr) - : SimpleSelector(ptr) - { simple_type(PLACEHOLDER_SEL); } - unsigned long PlaceholderSelector::specificity() const - { - return Constants::Specificity_Base; - } - bool PlaceholderSelector::has_placeholder() { - return true; - } + PlaceholderSelector::PlaceholderSelector( + const SourceSpan& pstate, + const sass::string& name) : + SimpleSelector(pstate, name) + {} + + PlaceholderSelector::PlaceholderSelector( + const PlaceholderSelector* ptr) : + SimpleSelector(ptr) + {} ///////////////////////////////////////////////////////////////////////// + // A type selector. (e.g., `div`, `span` or `*`). + // This selects elements whose name equals the given name. ///////////////////////////////////////////////////////////////////////// - TypeSelector::TypeSelector(SourceSpan pstate, sass::string n) - : SimpleSelector(pstate, n) - { simple_type(TYPE_SEL); } - TypeSelector::TypeSelector(const TypeSelector* ptr) - : SimpleSelector(ptr) - { simple_type(TYPE_SEL); } + TypeSelector::TypeSelector( + const SourceSpan& pstate, + sass::string&& name, + sass::string&& ns, + bool hasNs) : + NameSpaceSelector(pstate, + std::move(name), + std::move(ns), + hasNs) + {} - unsigned long TypeSelector::specificity() const - { - if (name() == "*") return 0; - else return Constants::Specificity_Element; - } + TypeSelector::TypeSelector( + const TypeSelector* ptr) : + NameSpaceSelector(ptr) + {} ///////////////////////////////////////////////////////////////////////// + // Class selectors -- i.e., .foo. ///////////////////////////////////////////////////////////////////////// - ClassSelector::ClassSelector(SourceSpan pstate, sass::string n) - : SimpleSelector(pstate, n) - { simple_type(CLASS_SEL); } - ClassSelector::ClassSelector(const ClassSelector* ptr) - : SimpleSelector(ptr) - { simple_type(CLASS_SEL); } + ClassSelector::ClassSelector( + const SourceSpan& pstate, + const sass::string& name) + : SimpleSelector(pstate, name) + {} - unsigned long ClassSelector::specificity() const - { - return Constants::Specificity_Class; - } + ClassSelector::ClassSelector( + const ClassSelector* ptr) : + SimpleSelector(ptr) + {} ///////////////////////////////////////////////////////////////////////// + // An ID selector (i.e. `#foo`). This selects elements + // whose `id` attribute exactly matches the given name. ///////////////////////////////////////////////////////////////////////// - IDSelector::IDSelector(SourceSpan pstate, sass::string n) - : SimpleSelector(pstate, n) - { simple_type(ID_SEL); } - IDSelector::IDSelector(const IDSelector* ptr) - : SimpleSelector(ptr) - { simple_type(ID_SEL); } - - unsigned long IDSelector::specificity() const - { - return Constants::Specificity_ID; - } + IDSelector::IDSelector( + const SourceSpan& pstate, + const sass::string& name) : + SimpleSelector(pstate, name) + {} + IDSelector::IDSelector( + const IDSelector* ptr) : + SimpleSelector(ptr) + {} + ///////////////////////////////////////////////////////////////////////// + // An attribute selector. This selects for elements + // with the given attribute, and optionally with a + // value matching certain conditions as well. ///////////////////////////////////////////////////////////////////////// - AttributeSelector::AttributeSelector(SourceSpan pstate, sass::string n, sass::string m, String_Obj v, char o) - : SimpleSelector(pstate, n), matcher_(m), value_(v), modifier_(o) - { simple_type(ATTRIBUTE_SEL); } - AttributeSelector::AttributeSelector(const AttributeSelector* ptr) - : SimpleSelector(ptr), - matcher_(ptr->matcher_), - value_(ptr->value_), - modifier_(ptr->modifier_) - { simple_type(ATTRIBUTE_SEL); } + AttributeSelector::AttributeSelector( + const SourceSpan& pstate, + struct QualifiedName&& name, + sass::string&& op, + sass::string&& value, + bool isIdentifier, + char modifier) : + NameSpaceSelector(pstate, + std::move(name.name), + std::move(name.ns), + name.hasNs), + op_(std::move(op)), + value_(std::move(value)), + modifier_(modifier), + isIdentifier_(isIdentifier) + {} - size_t AttributeSelector::hash() const - { - if (hash_ == 0) { - hash_combine(hash_, SimpleSelector::hash()); - hash_combine(hash_, std::hash()(matcher())); - if (value_) hash_combine(hash_, value_->hash()); - } - return hash_; - } - - unsigned long AttributeSelector::specificity() const - { - return Constants::Specificity_Attr; - } + AttributeSelector::AttributeSelector( + const AttributeSelector* ptr) : + NameSpaceSelector(ptr), + op_(ptr->op_), + value_(ptr->value_), + modifier_(ptr->modifier_), + isIdentifier_(ptr->isIdentifier_) + {} ///////////////////////////////////////////////////////////////////////// + // A pseudo-class or pseudo-element selector (e.g., `:content` + // or `:nth-child`). The semantics of a specific pseudo selector + // depends on its name. Some selectors take arguments, including + // other selectors. Sass manually encodes logic for each pseudo + // selector that takes a selector as an argument, to ensure that + // extension and other selector operations work properly. ///////////////////////////////////////////////////////////////////////// - PseudoSelector::PseudoSelector(SourceSpan pstate, sass::string name, bool element) - : SimpleSelector(pstate, name), + PseudoSelector::PseudoSelector( + const SourceSpan& pstate, + const sass::string& name, + bool element) : + SimpleSelector(pstate, name), normalized_(Util::unvendor(name)), - argument_({}), - selector_({}), + argument_(), + selector_(), isSyntacticClass_(!element), isClass_(!element && !isFakePseudoElement(normalized_)) - { simple_type(PSEUDO_SEL); } - PseudoSelector::PseudoSelector(const PseudoSelector* ptr) - : SimpleSelector(ptr), + {} + + PseudoSelector::PseudoSelector( + const PseudoSelector* ptr) : + SimpleSelector(ptr), normalized_(ptr->normalized()), argument_(ptr->argument()), selector_(ptr->selector()), isSyntacticClass_(ptr->isSyntacticClass()), isClass_(ptr->isClass()) - { simple_type(PSEUDO_SEL); } - - // A pseudo-element is made of two colons (::) followed by the name. - // The `::` notation is introduced by the current document in order to - // establish a discrimination between pseudo-classes and pseudo-elements. - // For compatibility with existing style sheets, user agents must also - // accept the previous one-colon notation for pseudo-elements introduced - // in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and - // :after). This compatibility is not allowed for the new pseudo-elements - // introduced in this specification. - bool PseudoSelector::is_pseudo_element() const + { } + + bool PseudoSelector::hasInvisible() const { - return isElement(); + return selector() && selector()->empty() && name() != "not"; } size_t PseudoSelector::hash() const { if (hash_ == 0) { - hash_combine(hash_, SimpleSelector::hash()); - if (selector_) hash_combine(hash_, selector_->hash()); - if (argument_) hash_combine(hash_, argument_->hash()); + hash_start(hash_, typeid(this).hash_code()); + hash_combine(hash_, argument_); + if (selector_) hash_combine( + hash_, selector_->hash()); } return hash_; } - unsigned long PseudoSelector::specificity() const - { - if (is_pseudo_element()) - return Constants::Specificity_Element; - return Constants::Specificity_Pseudo; - } - - PseudoSelectorObj PseudoSelector::withSelector(SelectorListObj selector) - { - PseudoSelectorObj pseudo = SASS_MEMORY_COPY(this); - pseudo->selector(selector); - return pseudo; - } - - bool PseudoSelector::empty() const - { + // Implement for cleanup phase + bool PseudoSelector::empty() const { // Only considered empty if selector is // available but has no items in it. - return selector() && selector()->empty(); - } - - void PseudoSelector::cloneChildren() - { - if (selector().isNull()) selector({}); - else selector(SASS_MEMORY_CLONE(selector())); - } - - bool PseudoSelector::has_real_parent_ref() const { - if (!selector()) return false; - return selector()->has_real_parent_ref(); - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - SelectorList::SelectorList(SourceSpan pstate, size_t s) - : Selector(pstate), - Vectorized(s), - is_optional_(false) - { } - SelectorList::SelectorList(const SelectorList* ptr) - : Selector(ptr), - Vectorized(*ptr), - is_optional_(ptr->is_optional_) - { } - - size_t SelectorList::hash() const - { - if (Selector::hash_ == 0) { - hash_combine(Selector::hash_, Vectorized::hash()); - } - return Selector::hash_; - } - - bool SelectorList::has_real_parent_ref() const - { - for (ComplexSelectorObj s : elements()) { - if (s && s->has_real_parent_ref()) return true; - } - return false; + return argument_.empty() && name().empty() && + (selector() && selector()->empty()); } - void SelectorList::cloneChildren() + PseudoSelector* PseudoSelector::withSelector(SelectorList* selector) { - for (size_t i = 0, l = length(); i < l; i++) { - at(i) = SASS_MEMORY_CLONE(at(i)); - } + PseudoSelector* pseudo = SASS_MEMORY_COPY(this); + pseudo->selector(selector); + return pseudo; } - unsigned long SelectorList::specificity() const - { - return 0; - } - - bool SelectorList::isInvisible() const - { - if (length() == 0) return true; - for (size_t i = 0; i < length(); i += 1) { - if (get(i)->isInvisible()) return true; - } - return false; + bool PseudoSelector::hasAnyExplicitParent() const { + return selector() && selector()->hasExplicitParent(); } ///////////////////////////////////////////////////////////////////////// + // Complex Selectors are the most important class of selectors. + // A Selector List consists of Complex Selectors (separated by comma) + // Complex Selectors are itself a list of Compounds and Combinators + // Between each item there is an implicit ancestor of combinator ///////////////////////////////////////////////////////////////////////// - ComplexSelector::ComplexSelector(SourceSpan pstate) - : Selector(pstate), - Vectorized(), + ComplexSelector::ComplexSelector( + const SourceSpan& pstate, + SelectorComponentVector&& components) : + Selector(pstate), + Vectorized(std::move(components)), chroots_(false), hasPreLineFeed_(false) - { - } - ComplexSelector::ComplexSelector(const ComplexSelector* ptr) - : Selector(ptr), - Vectorized(ptr->elements()), + {} + + ComplexSelector::ComplexSelector( + const ComplexSelector* ptr, + bool childless) : + Selector(ptr), + Vectorized(ptr, childless), chroots_(ptr->chroots()), hasPreLineFeed_(ptr->hasPreLineFeed()) - { - } + {} - void ComplexSelector::cloneChildren() + unsigned long ComplexSelector::specificity() const { - for (size_t i = 0, l = length(); i < l; i++) { - at(i) = SASS_MEMORY_CLONE(at(i)); + if (specificity_ == 0xFFFFFFFF) { + specificity_ = 0; + for (const auto& component : elements()) { + specificity_ += component->specificity(); + } } + return specificity_; } - unsigned long ComplexSelector::specificity() const + unsigned long ComplexSelector::maxSpecificity() const { - int sum = 0; - for (auto component : elements()) { - sum += component->specificity(); + if (maxSpecificity_ == 0xFFFFFFFF) { + maxSpecificity_ = 0; + for (const auto& component : elements()) { + maxSpecificity_ += component->maxSpecificity(); + } } - return sum; + return maxSpecificity_; } - bool ComplexSelector::isInvisible() const + unsigned long ComplexSelector::minSpecificity() const { - if (length() == 0) return true; - for (size_t i = 0; i < length(); i += 1) { - if (CompoundSelectorObj compound = get(i)->getCompound()) { - if (compound->isInvisible()) return true; + if (minSpecificity_ == 0xFFFFFFFF) { + minSpecificity_ = 0; + for (const auto& component : elements()) { + minSpecificity_ += component->minSpecificity(); } } - return false; + return minSpecificity_; } - bool ComplexSelector::isInvalidCss() const + bool ComplexSelector::hasInvisible() const { - for (size_t i = 0; i < length(); i += 1) { - if (CompoundSelectorObj compound = get(i)->getCompound()) { - if (compound->isInvalidCss()) return true; - } + if (empty()) return true; + for (const auto& component : elements()) { + if (component->hasInvisible()) return true; } return false; } - SelectorListObj ComplexSelector::wrapInList() + SelectorList* ComplexSelector::wrapInList() { - SelectorListObj selector = - SASS_MEMORY_NEW(SelectorList, pstate()); - selector->append(this); - return selector; + return SASS_MEMORY_NEW(SelectorList, pstate(), { this }); } size_t ComplexSelector::hash() const { - if (Selector::hash_ == 0) { - hash_combine(Selector::hash_, Vectorized::hash()); - // ToDo: this breaks some extend lookup - // hash_combine(Selector::hash_, chroots_); + if (Vectorized::hash_ == 0) { + Selector::hash_ = Vectorized::hash(); } return Selector::hash_; } - bool ComplexSelector::has_placeholder() const { - for (size_t i = 0, L = length(); i < L; ++i) { - if (get(i)->has_placeholder()) return true; - } - return false; - } - - bool ComplexSelector::has_real_parent_ref() const + bool ComplexSelector::hasExplicitParent() const { - for (auto item : elements()) { - if (item->has_real_parent_ref()) return true; + for (const SelectorComponentObj& component : elements()) { + if (component->hasAnyExplicitParent()) return true; } return false; } ///////////////////////////////////////////////////////////////////////// + // Base class for complex selector components ///////////////////////////////////////////////////////////////////////// - SelectorComponent::SelectorComponent(SourceSpan pstate, bool postLineBreak) - : Selector(pstate), - hasPostLineBreak_(postLineBreak) - { - } + SelectorComponent::SelectorComponent( + const SourceSpan& pstate, + bool hasPostLineBreak) : + Selector(pstate), + hasPostLineBreak_(hasPostLineBreak) + {} - SelectorComponent::SelectorComponent(const SelectorComponent* ptr) - : Selector(ptr), + SelectorComponent::SelectorComponent( + const SelectorComponent* ptr) : + Selector(ptr), hasPostLineBreak_(ptr->hasPostLineBreak()) - { } - - void SelectorComponent::cloneChildren() - { - } - - unsigned long SelectorComponent::specificity() const - { - return 0; - } - - // Wrap the compound selector with a complex selector - ComplexSelector* SelectorComponent::wrapInComplex() - { - auto complex = SASS_MEMORY_NEW(ComplexSelector, pstate()); - complex->append(this); - return complex; - } + {} ///////////////////////////////////////////////////////////////////////// + // A specific combinator between compound selectors ///////////////////////////////////////////////////////////////////////// - SelectorCombinator::SelectorCombinator(SourceSpan pstate, SelectorCombinator::Combinator combinator, bool postLineBreak) - : SelectorComponent(pstate, postLineBreak), + SelectorCombinator::SelectorCombinator( + const SourceSpan& pstate, + SelectorCombinator::Combinator combinator, + bool hasPostLineBreak) : + SelectorComponent(pstate, hasPostLineBreak), combinator_(combinator) - { - } - SelectorCombinator::SelectorCombinator(const SelectorCombinator* ptr) - : SelectorComponent(ptr->pstate(), false), - combinator_(ptr->combinator()) - { } + {} - void SelectorCombinator::cloneChildren() - { - } + SelectorCombinator::SelectorCombinator( + const SelectorCombinator* ptr) : + SelectorComponent(ptr), + combinator_(ptr->combinator_) + {} - unsigned long SelectorCombinator::specificity() const + // Hash implementation is very simple + size_t SelectorCombinator::hash() const { - return 0; + if (hash_ == 0) { + hash_start(hash_, typeid(this).hash_code()); + hash_combine(hash_, (size_t)combinator()); + } + return hash_; } ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - CompoundSelector::CompoundSelector(SourceSpan pstate, bool postLineBreak) - : SelectorComponent(pstate, postLineBreak), - Vectorized(), - hasRealParent_(false) - { - } - CompoundSelector::CompoundSelector(const CompoundSelector* ptr) - : SelectorComponent(ptr), - Vectorized(*ptr), - hasRealParent_(ptr->hasRealParent()) - { } + // A compound selector consists of multiple simple selectors. It will be + // either implicitly or explicitly connected to its parent sass selector. + // According to the specs we could also unify the tag selector into this, + // as AFAICT only one tag selector is ever allowed. Further we could free + // up the pseudo selectors from being virtual, as they must be last always. + // https://github.com/sass/libsass/pull/3101 + ///////////////////////////////////////////////////////////////////////// + + CompoundSelector::CompoundSelector( + const SourceSpan& pstate, + bool hasPostLineBreak) : + SelectorComponent( + pstate, hasPostLineBreak), + withExplicitParent_(false) + {} + + CompoundSelector::CompoundSelector( + const SourceSpan& pstate, + sass::vector&& selectors, + bool hasPostLineBreak) : + SelectorComponent( + pstate, hasPostLineBreak), + Vectorized(std::move(selectors)), + withExplicitParent_(false) + {} + + CompoundSelector::CompoundSelector( + const CompoundSelector* ptr, + bool childless) : + SelectorComponent(ptr), + Vectorized(ptr, childless), + withExplicitParent_(ptr->withExplicitParent()) + {} size_t CompoundSelector::hash() const { - if (Selector::hash_ == 0) { - hash_combine(Selector::hash_, Vectorized::hash()); - hash_combine(Selector::hash_, hasRealParent_); + if (Vectorized::hash_ == 0) { + Selector::hash_ = Vectorized::hash(); } return Selector::hash_; } - bool CompoundSelector::has_real_parent_ref() const + unsigned long CompoundSelector::specificity() const { - if (hasRealParent()) return true; - // ToDo: dart sass has another check? - // if (Cast(front)) { - // if (front->ns() != "") return false; - // } - for (const SimpleSelector* s : elements()) { - if (s && s->has_real_parent_ref()) return true; + if (specificity_ == 0xFFFFFFFF) { + specificity_ = 0; + for (const auto& component : elements()) { + specificity_ += component->specificity(); + } } - return false; + return specificity_; } - bool CompoundSelector::has_placeholder() const + unsigned long CompoundSelector::maxSpecificity() const { - if (length() == 0) return false; - for (SimpleSelectorObj ss : elements()) { - if (ss->has_placeholder()) return true; + if (maxSpecificity_ == 0xFFFFFFFF) { + maxSpecificity_ = 0; + for (const auto& simple : elements()) { + maxSpecificity_ += simple->maxSpecificity(); + } } - return false; + return maxSpecificity_; } - void CompoundSelector::cloneChildren() + unsigned long CompoundSelector::minSpecificity() const { - for (size_t i = 0, l = length(); i < l; i++) { - at(i) = SASS_MEMORY_CLONE(at(i)); + if (minSpecificity_ == 0xFFFFFFFF) { + minSpecificity_ = 0; + for (const auto& simple : elements()) { + minSpecificity_ += simple->minSpecificity(); + } } + return minSpecificity_; } - unsigned long CompoundSelector::specificity() const - { - int sum = 0; - for (size_t i = 0, L = length(); i < L; ++i) - { sum += get(i)->specificity(); } - return sum; - } - - bool CompoundSelector::isInvisible() const + bool CompoundSelector::hasAnyExplicitParent() const { - for (size_t i = 0; i < length(); i += 1) { - if (!get(i)->isInvisible()) return false; + if (withExplicitParent()) return true; + // ToDo: dart sass has another check? + // if (front->isaNameSpaceSelector()) { + // if (front->ns() != "") return false; + // } + for (const SimpleSelectorObj& s : elements()) { + if (s && s->hasAnyExplicitParent()) return true; } - return true; - } - - bool CompoundSelector::isSuperselectorOf(const CompoundSelector* sub, sass::string wrapped) const - { - CompoundSelector* rhs2 = const_cast(sub); - CompoundSelector* lhs2 = const_cast(this); - return compoundIsSuperselector(lhs2, rhs2, {}); - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - MediaRule::MediaRule(SourceSpan pstate, Block_Obj block) : - ParentStatement(pstate, block), - schema_({}) - { - statement_type(MEDIA); - } - - MediaRule::MediaRule(const MediaRule* ptr) : - ParentStatement(ptr), - schema_(ptr->schema_) - { - statement_type(MEDIA); + return false; } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - CssMediaRule::CssMediaRule(SourceSpan pstate, Block_Obj block) : - ParentStatement(pstate, block), - Vectorized() + bool CompoundSelector::hasPlaceholder() const { - statement_type(MEDIA); + if (size() == 0) return false; + for (const SimpleSelectorObj& ss : elements()) { + if (ss && ss->isaPlaceholderSelector()) return true; + } + return false; } - CssMediaRule::CssMediaRule(const CssMediaRule* ptr) : - ParentStatement(ptr), - Vectorized(*ptr) + bool CompoundSelector::hasInvisible() const { - statement_type(MEDIA); + for (const SimpleSelectorObj& sel : elements()) { + if (sel && sel->hasInvisible()) return true; + } + return false; } - CssMediaQuery::CssMediaQuery(SourceSpan pstate) : - AST_Node(pstate), - modifier_(""), - type_(""), - features_() + // Determine if given `this` is a sub-selector of `sub` + bool CompoundSelector::isSuperselectorOf(const CompoundSelector* sub) const { + return compoundIsSuperselector(this, sub); } ///////////////////////////////////////////////////////////////////////// + // Comma-separated selector groups. ///////////////////////////////////////////////////////////////////////// - bool CssMediaQuery::operator==(const CssMediaQuery& rhs) const - { - return type_ == rhs.type_ - && modifier_ == rhs.modifier_ - && features_ == rhs.features_; - } - - // Implemented after dart-sass (maybe move to other class?) - CssMediaQuery_Obj CssMediaQuery::merge(CssMediaQuery_Obj& other) - { - - sass::string ourType = this->type(); - Util::ascii_str_tolower(&ourType); - - sass::string theirType = other->type(); - Util::ascii_str_tolower(&theirType); - - sass::string ourModifier = this->modifier(); - Util::ascii_str_tolower(&ourModifier); + SelectorList::SelectorList( + const SourceSpan& pstate, + sass::vector&& complexes) : + Selector(pstate), + Vectorized(std::move(complexes)) + {} - sass::string theirModifier = other->modifier(); - Util::ascii_str_tolower(&theirModifier); + SelectorList::SelectorList( + const SelectorList* ptr, + bool childless) : + Selector(ptr), + Vectorized(ptr, childless) + {} - sass::string type; - sass::string modifier; - sass::vector features; - - if (ourType.empty() && theirType.empty()) { - CssMediaQuery_Obj query = SASS_MEMORY_NEW(CssMediaQuery, pstate()); - sass::vector f1(this->features()); - sass::vector f2(other->features()); - features.insert(features.end(), f1.begin(), f1.end()); - features.insert(features.end(), f2.begin(), f2.end()); - query->features(features); - return query; - } - - if ((ourModifier == "not") != (theirModifier == "not")) { - if (ourType == theirType) { - sass::vector negativeFeatures = - ourModifier == "not" ? this->features() : other->features(); - sass::vector positiveFeatures = - ourModifier == "not" ? other->features() : this->features(); - - // If the negative features are a subset of the positive features, the - // query is empty. For example, `not screen and (color)` has no - // intersection with `screen and (color) and (grid)`. - // However, `not screen and (color)` *does* intersect with `screen and - // (grid)`, because it means `not (screen and (color))` and so it allows - // a screen with no color but with a grid. - if (listIsSubsetOrEqual(negativeFeatures, positiveFeatures)) { - return SASS_MEMORY_NEW(CssMediaQuery, pstate()); - } - else { - return {}; - } - } - else if (this->matchesAllTypes() || other->matchesAllTypes()) { - return {}; - } - - if (ourModifier == "not") { - modifier = theirModifier; - type = theirType; - features = other->features(); - } - else { - modifier = ourModifier; - type = ourType; - features = this->features(); - } - } - else if (ourModifier == "not") { - SASS_ASSERT(theirModifier == "not", "modifiers not is sync"); - - // CSS has no way of representing "neither screen nor print". - if (ourType != theirType) return {}; - - auto moreFeatures = this->features().size() > other->features().size() - ? this->features() - : other->features(); - auto fewerFeatures = this->features().size() > other->features().size() - ? other->features() - : this->features(); - - // If one set of features is a superset of the other, - // use those features because they're strictly narrower. - if (listIsSubsetOrEqual(fewerFeatures, moreFeatures)) { - modifier = ourModifier; // "not" - type = ourType; - features = moreFeatures; - } - else { - // Otherwise, there's no way to - // represent the intersection. - return {}; - } - - } - else { - if (this->matchesAllTypes()) { - modifier = theirModifier; - // Omit the type if either input query did, since that indicates that they - // aren't targeting a browser that requires "all and". - type = (other->matchesAllTypes() && ourType.empty()) ? "" : theirType; - sass::vector f1(this->features()); - sass::vector f2(other->features()); - features.insert(features.end(), f1.begin(), f1.end()); - features.insert(features.end(), f2.begin(), f2.end()); - } - else if (other->matchesAllTypes()) { - modifier = ourModifier; - type = ourType; - sass::vector f1(this->features()); - sass::vector f2(other->features()); - features.insert(features.end(), f1.begin(), f1.end()); - features.insert(features.end(), f2.begin(), f2.end()); - } - else if (ourType != theirType) { - return SASS_MEMORY_NEW(CssMediaQuery, pstate()); - } - else { - modifier = ourModifier.empty() ? theirModifier : ourModifier; - type = ourType; - sass::vector f1(this->features()); - sass::vector f2(other->features()); - features.insert(features.end(), f1.begin(), f1.end()); - features.insert(features.end(), f2.begin(), f2.end()); - } - } - - CssMediaQuery_Obj query = SASS_MEMORY_NEW(CssMediaQuery, pstate()); - query->modifier(modifier == ourModifier ? this->modifier() : other->modifier()); - query->type(ourType.empty() ? other->type() : this->type()); - query->features(features); - return query; - } - - CssMediaQuery::CssMediaQuery(const CssMediaQuery* ptr) : - AST_Node(*ptr), - modifier_(ptr->modifier_), - type_(ptr->type_), - features_(ptr->features_) - { - } - - ///////////////////////////////////////////////////////////////////////// - // ToDo: finalize specificity implementation - ///////////////////////////////////////////////////////////////////////// - - size_t SelectorList::maxSpecificity() const + size_t SelectorList::hash() const { - size_t specificity = 0; - for (auto complex : elements()) { - specificity = std::max(specificity, complex->maxSpecificity()); + if (Vectorized::hash_ == 0) { + Selector::hash_ = Vectorized::hash(); } - return specificity; + return Selector::hash_; } - size_t SelectorList::minSpecificity() const + unsigned long SelectorList::maxSpecificity() const { - size_t specificity = 0; - for (auto complex : elements()) { - specificity = std::min(specificity, complex->minSpecificity()); + if (maxSpecificity_ == 0xFFFFFFFF) { + maxSpecificity_ = 0; + for (const auto& complex : elements()) { + maxSpecificity_ = std::max( + complex->maxSpecificity(), + maxSpecificity_); + } } - return specificity; + return maxSpecificity_; } - size_t CompoundSelector::maxSpecificity() const + unsigned long SelectorList::minSpecificity() const { - size_t specificity = 0; - for (auto simple : elements()) { - specificity += simple->maxSpecificity(); + if (minSpecificity_ == 0xFFFFFFFF) { + minSpecificity_ = 0; + for (const auto& complex : elements()) { + maxSpecificity_ = std::min( + complex->minSpecificity(), + maxSpecificity_); + } } - return specificity; + return minSpecificity_; } - size_t CompoundSelector::minSpecificity() const + bool SelectorList::hasExplicitParent() const { - size_t specificity = 0; - for (auto simple : elements()) { - specificity += simple->minSpecificity(); + for (const auto& s : elements()) { + if (s && s->hasExplicitParent()) return true; } - return specificity; + return false; } - size_t ComplexSelector::maxSpecificity() const + Value* SelectorList::toValue() const { - size_t specificity = 0; - for (auto component : elements()) { - specificity += component->maxSpecificity(); + ListObj list = SASS_MEMORY_NEW(List, + pstate(), {}, SASS_COMMA); + list->elements().reserve(size()); + for (ComplexSelector* complex : elements()) { + list->append(complex->toList()); } - return specificity; + if (list->size()) return list.detach(); + return SASS_MEMORY_NEW(Null, pstate()); } - size_t ComplexSelector::minSpecificity() const + // Wrap the compound selector with a complex selector + ComplexSelector* SelectorComponent::wrapInComplex() { - size_t specificity = 0; - for (auto component : elements()) { - specificity += component->minSpecificity(); - } - return specificity; + return SASS_MEMORY_NEW(ComplexSelector, pstate(), { this }); } ///////////////////////////////////////////////////////////////////////// - // ToDo: this might be done easier with new selector format + // Below are the resolveParentSelectors implementations ///////////////////////////////////////////////////////////////////////// sass::vector - CompoundSelector::resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent) + CompoundSelector::resolveParentSelectors( + SelectorList* parent, BackTraces& traces, bool implicit_parent) { - auto parent = pstack.back(); sass::vector rv; - for (SimpleSelectorObj simple : elements()) { - if (PseudoSelector * pseudo = Cast(simple)) { - if (SelectorList* sel = Cast(pseudo->selector())) { - if (parent) { - pseudo->selector(sel->resolve_parent_refs( - pstack, traces, implicit_parent)); + if (parent) { + for (SimpleSelectorObj simple : elements()) { + if (PseudoSelector * pseudo = simple->isaPseudoSelector()) { + if (SelectorList* sel = pseudo->selector()) { + pseudo->selector(sel->resolveParentSelectors( + parent, traces, implicit_parent)); } } } } // Mix with parents from stack - if (hasRealParent()) { - - if (parent.isNull()) { - return { wrapInComplex() }; - } - else { - for (auto complex : parent->elements()) { - // The parent complex selector has a compound selector - if (CompoundSelectorObj tail = Cast(complex->last())) { - // Create a copy to alter it - complex = SASS_MEMORY_COPY(complex); - tail = SASS_MEMORY_COPY(tail); - - // Check if we can merge front with back - if (length() > 0 && tail->length() > 0) { - SimpleSelectorObj back = tail->last(); - SimpleSelectorObj front = first(); - auto simple_back = Cast(back); - auto simple_front = Cast(front); - if (simple_front && simple_back) { - simple_back = SASS_MEMORY_COPY(simple_back); - auto name = simple_back->name(); - name += simple_front->name(); - simple_back->name(name); - tail->elements().back() = simple_back; - tail->elements().insert(tail->end(), - begin() + 1, end()); - } - else { - tail->concat(this); - } + // Equivalent to dart-sass parent selector tail + if (withExplicitParent()) { + SASS_ASSERT(parent != nullptr, "Parent must be defined"); + for (auto complex : parent->elements()) { + // The parent complex selector has a compound selector + if (CompoundSelector* tail = complex->last()->isaCompoundSelector()) { + // Create copies to alter them + tail = SASS_MEMORY_COPY(tail); + complex = SASS_MEMORY_COPY(complex); + + // Check if we can merge front with back + if (size() > 0 && tail->size() > 0) { + SimpleSelector* front = first(); + auto simple_back = tail->last(); + auto simple_front = front->isaTypeSelector(); + // If they are type/simple selectors ... + if (simple_front && simple_back) { + // ... we can combine the names into one + simple_back = SASS_MEMORY_COPY(simple_back); + auto name = simple_back->name(); + name += simple_front->name(); + simple_back->name(name); + // Replace with modified simple selector + tail->elements().back() = simple_back; + // Append rest of selector components + tail->elements().insert(tail->end(), + begin() + 1, end()); } else { + // Append us to parent tail->concat(this); } - - complex->elements().back() = tail; - // Append to results - rv.push_back(complex); } else { - // Can't insert parent that ends with a combinator - // where the parent selector is followed by something - if (parent && length() > 0) { - throw Exception::InvalidParent(parent, traces, this); - } - // Create a copy to alter it - complex = SASS_MEMORY_COPY(complex); - // Just append ourself - complex->append(this); - // Append to results - rv.push_back(complex); + // Append us to parent + tail->concat(this); } + // Reset the parent selector tail with + // the combination of parent plus ourself + complex->elements().back() = tail; + // Append to results + rv.emplace_back(complex); + } + // SelectorCombinator + else { + // Can't insert parent that ends with a combinator + // where the parent selector is followed by something + callStackFrame frame(traces, complex->last()->pstate()); + if (size() > 0) { throw Exception::InvalidParent(parent, traces, this); } + // Just append ourself to results + rv.emplace_back(wrapInComplex()); } } - } - // No parents + } + // No parent else { - // Create a new wrapper to wrap ourself - auto complex = SASS_MEMORY_NEW(ComplexSelector, pstate()); - // Just append ourself - complex->append(this); - // Append to results - rv.push_back(complex); + // Wrap and append compound selector + rv.emplace_back(wrapInComplex()); } return rv; } + // EO CompoundSelector::resolveParentSelectors - bool cmpSimpleSelectors(SimpleSelector* a, SimpleSelector* b) + List* ComplexSelector::toList() const { - return (a->getSortOrder() < b->getSortOrder()); - } - - void CompoundSelector::sortChildren() - { - std::sort(begin(), end(), cmpSimpleSelectors); - } - - bool CompoundSelector::isInvalidCss() const - { - size_t current = 0, next = 0; - for (const SimpleSelector* sel : elements()) { - next = sel->getSortOrder(); - // Must only have one type selector - if (current == 1 && next == 1) { - return true; - } - if (next < current) { - return true; - } - current = next; + ListObj list = SASS_MEMORY_NEW(List, + pstate(), {}, SASS_SPACE); + for (SelectorComponent* component : elements()) { + SassOutputOptionsCpp out({ + SASS_STYLE_TO_CSS, + SassDefaultPrecision }); + Cssize inspect(out, false); + component->accept(&inspect); + list->append(SASS_MEMORY_NEW(String, + pstate(), inspect.get_buffer())); } - return false; + return list.detach(); } - /* better return sass::vector? only - is empty container anyway? */ - SelectorList* ComplexSelector::resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent) + sass::vector ComplexSelector::resolveParentSelectors( + SelectorList* parent, BackTraces& traces, bool implicit_parent) { - sass::vector> vars; - - auto parent = pstack.back(); - - if (has_real_parent_ref() && !parent) { + if (hasExplicitParent() && !parent) { throw Exception::TopLevelParent(traces, pstate()); } - if (!chroots() && parent) { - - if (!has_real_parent_ref() && !implicit_parent) { - SelectorList* retval = SASS_MEMORY_NEW(SelectorList, pstate(), 1); - retval->append(this); - return retval; - } + sass::vector> selectors; - vars.push_back(parent->elements()); + // Check if selector should implicit get a parent + if (!chroots() && !hasExplicitParent()) { + // Check if we should never connect to parent + if (!implicit_parent) { return { this }; } + // Otherwise add parent selectors at the beginning + if (parent) { selectors.emplace_back(parent->elements()); } } - for (auto sel : elements()) { - if (CompoundSelectorObj comp = Cast(sel)) { - auto asd = comp->resolve_parent_refs(pstack, traces, implicit_parent); - if (asd.size() > 0) vars.push_back(asd); + // Loop all items from the complex selector + for (SelectorComponent* component : this->elements()) { + if (CompoundSelector* compound = component->isaCompoundSelector()) { + sass::vector complexes = + compound->resolveParentSelectors(parent, traces, implicit_parent); + // for (auto sel : complexes) { sel->hasPreLineFeed(hasPreLineFeed()); } + if (complexes.size() > 0) selectors.emplace_back(complexes); } else { - // ToDo: merge together sequences whenever possible - auto cont = SASS_MEMORY_NEW(ComplexSelector, pstate()); - cont->append(sel); - vars.push_back({ cont }); + // component->hasPreLineFeed(hasPreLineFeed()); + selectors.push_back({ component->wrapInComplex() }); } } - // Need complex selectors to preserve linefeeds - sass::vector> res = permutateAlt(vars); - - // std::reverse(std::begin(res), std::end(res)); - - auto lst = SASS_MEMORY_NEW(SelectorList, pstate()); - for (auto items : res) { - if (items.size() > 0) { - ComplexSelectorObj first = SASS_MEMORY_COPY(items[0]); - first->hasPreLineFeed(first->hasPreLineFeed() || (!has_real_parent_ref() && hasPreLineFeed())); - // ToDo: remove once we know how to handle line feeds - // ToDo: currently a mashup between ruby and dart sass - // if (has_real_parent_ref()) first->has_line_feed(false); - // first->has_line_break(first->has_line_break() || has_line_break()); - first->chroots(true); // has been resolved by now - for (size_t i = 1; i < items.size(); i += 1) { - first->concat(items[i]); + // Permutate through all paths + selectors = permutateAlt(selectors); + + // Create final selectors from path permutations + sass::vector resolved; + for (sass::vector& items : selectors) { + if (items.empty()) continue; + ComplexSelectorObj first = SASS_MEMORY_COPY(items[0]); + // ToDo: this seems suspicious, why this logic? + if (hasPreLineFeed() && !hasExplicitParent()) { + first->hasPreLineFeed(true); + } + // ToDo: remove once we know how to handle line feeds + // ToDo: currently a mash-up between ruby and dart sass + // if (has_real_parent_ref()) first->has_line_feed(false); + // first->has_line_break(first->has_line_break() || has_line_break()); + first->chroots(true); // has been resolved by now + for (size_t i = 1; i < items.size(); i += 1) { + if (items[i]->hasPreLineFeed()) { + first->hasPreLineFeed(true); } - lst->append(first); + first->concat(items[i]); } + resolved.emplace_back(first); } - return lst; - + return resolved; } + // EO ComplexSelector::resolveParentSelectors - SelectorList* SelectorList::resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent) + SelectorList* SelectorList::resolveParentSelectors( + SelectorList* parent, BackTraces& traces, bool implicit_parent) { - SelectorList* rv = SASS_MEMORY_NEW(SelectorList, pstate()); - for (auto sel : elements()) { - // Note: this one is tricky as we get back a pointer from resolve parents ... - SelectorListObj res = sel->resolve_parent_refs(pstack, traces, implicit_parent); - // Note: ... and concat will only append the items in elements - // Therefore by passing it directly, the container will leak! - rv->concat(res); + sass::vector> lists; + for (ComplexSelector* sel : elements()) { + lists.emplace_back(sel->resolveParentSelectors + (parent, traces, implicit_parent)); } - return rv; + return SASS_MEMORY_NEW(SelectorList, pstate(), + flattenVertically(lists)); } + // EO SelectorList::resolveParentSelectors ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - IMPLEMENT_AST_OPERATORS(Selector_Schema); - IMPLEMENT_AST_OPERATORS(PlaceholderSelector); - IMPLEMENT_AST_OPERATORS(AttributeSelector); - IMPLEMENT_AST_OPERATORS(TypeSelector); - IMPLEMENT_AST_OPERATORS(ClassSelector); - IMPLEMENT_AST_OPERATORS(IDSelector); - IMPLEMENT_AST_OPERATORS(PseudoSelector); - IMPLEMENT_AST_OPERATORS(SelectorCombinator); - IMPLEMENT_AST_OPERATORS(CompoundSelector); - IMPLEMENT_AST_OPERATORS(ComplexSelector); - IMPLEMENT_AST_OPERATORS(SelectorList); - } diff --git a/src/ast_selectors.hpp b/src/ast_selectors.hpp index bdc25b4422..ee09fde561 100644 --- a/src/ast_selectors.hpp +++ b/src/ast_selectors.hpp @@ -1,359 +1,627 @@ -#ifndef SASS_AST_SEL_H -#define SASS_AST_SEL_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_SELECTORS_HPP +#define SASS_AST_SELECTORS_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_nodes.hpp" +#include "constants.hpp" +#include "visitor_selector.hpp" namespace Sass { ///////////////////////////////////////////////////////////////////////// - // Some helper functions + // Some helpers for superselector and weave parts ///////////////////////////////////////////////////////////////////////// bool compoundIsSuperselector( - const CompoundSelectorObj& compound1, - const CompoundSelectorObj& compound2, - const sass::vector& parents); + const CompoundSelector* compound1, + const CompoundSelector* compound2, + const SelectorComponentVector& parents = {}); bool complexIsParentSuperselector( - const sass::vector& complex1, - const sass::vector& complex2); - - sass::vector> weave( - const sass::vector>& complexes); + const SelectorComponentVector& complex1, + const SelectorComponentVector& complex2); - sass::vector> weaveParents( - sass::vector parents1, - sass::vector parents2); + sass::vector weave( + const sass::vector& complexes); - sass::vector unifyCompound( - const sass::vector& compound1, - const sass::vector& compound2); + // ToDo: What happens if we modify our parent? + sass::vector weaveParents( + SelectorComponentVector parents1, + SelectorComponentVector parents2); - sass::vector> unifyComplex( - const sass::vector>& complexes); + sass::vector unifyComplex( + const sass::vector& complexes); - ///////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // Abstract base class for CSS selectors. - ///////////////////////////////////////// - class Selector : public Expression { + ///////////////////////////////////////////////////////////////////////// + class Selector : public AstNode, + public SelectorVisitable + { protected: + + // Hash is only calculated once and afterwards the value + // must not be mutated, which is the case with how sass + // works, although we must be a bit careful not to alter + // any value that has already been added to a set or map. + // Must create a copy if you need to alter such an object. + // Selectors are mostly used as keys in @extend rules. mutable size_t hash_; + public: - Selector(SourceSpan pstate); - virtual ~Selector() = 0; - size_t hash() const override = 0; - virtual bool has_real_parent_ref() const; - // you should reset this to null on containers + + // Base value constructor + Selector(const SourceSpan& pstate); + + // Base copy constructor + Selector(const Selector* ptr); + + // To be implemented by specialization + virtual size_t hash() const = 0; virtual unsigned long specificity() const = 0; - // by default we return the regular specificity - // you must override this for all containers - virtual size_t maxSpecificity() const { return specificity(); } - virtual size_t minSpecificity() const { return specificity(); } - // dispatch to correct handlers - ATTACH_VIRTUAL_CMP_OPERATIONS(Selector) - ATTACH_VIRTUAL_AST_OPERATIONS(Selector) + // By default we return the regular specificity + // Override this for selectors with children + virtual unsigned long maxSpecificity() const { return specificity(); } + virtual unsigned long minSpecificity() const { return specificity(); } + + // Convert the selector to string, mostly for debugging + sass::string inspect(int precision = SassDefaultPrecision) const; + + // Returns if any compound selector has an explicit parent `&` selector. + // Only compound selectors are allowed to have this beside interpolations, + // which are handled very different and separately. Pseudo-selector like + // `:not` can also have an impact here, which is currently the sole use + // for having this as a virtual function. It is certainly questionable + // why a list returns true here if only one compound selector has it!? + virtual bool hasAnyExplicitParent() const { return false; } + + // Calls the appropriate visit method on [visitor]. + // Needed here to avoid ambiguity from base-classes!?? + virtual void accept(SelectorVisitor* visitor) override = 0; + + // To be implemented by specialization + virtual bool operator==(const Selector& rhs) const = 0; + + // Base copy method with [childless] being void most of the times + virtual Selector* copy(SASS_MEMORY_ARGS bool childless = false) const = 0; + + // Declare up-casting methods + DECLARE_ISA_CASTER(IDSelector); + DECLARE_ISA_CASTER(TypeSelector); + DECLARE_ISA_CASTER(PseudoSelector); + DECLARE_ISA_CASTER(ClassSelector); + DECLARE_ISA_CASTER(AttributeSelector); + DECLARE_ISA_CASTER(PlaceholderSelector); + DECLARE_ISA_CASTER(NameSpaceSelector); + DECLARE_ISA_CASTER(ComplexSelector); + DECLARE_ISA_CASTER(SelectorCombinator); + DECLARE_ISA_CASTER(CompoundSelector); + DECLARE_ISA_CASTER(SelectorList); }; - inline Selector::~Selector() { } ///////////////////////////////////////////////////////////////////////// - // Interpolated selectors -- the interpolated String will be expanded and - // re-parsed into a normal selector class. + // Abstract base class for simple selectors. ///////////////////////////////////////////////////////////////////////// - class Selector_Schema final : public AST_Node { - ADD_PROPERTY(String_Schema_Obj, contents) - ADD_PROPERTY(bool, connect_parent); - // store computed hash - mutable size_t hash_; + class SimpleSelector : public Selector + { + private: + + ADD_CONSTREF(sass::string, name); + public: - Selector_Schema(SourceSpan pstate, String_Obj c); - - bool has_real_parent_ref() const; - // selector schema is not yet a final selector, so we do not - // have a specificity for it yet. We need to - virtual unsigned long specificity() const; - size_t hash() const override; - ATTACH_AST_OPERATIONS(Selector_Schema) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + SimpleSelector( + const SourceSpan& pstate, + const sass::string& name); + + // Value constructor + SimpleSelector( + const SourceSpan& pstate, + sass::string&& name); + + // Copy constructor + SimpleSelector( + const SimpleSelector* ptr); + + // Wrap inside another selector type + ComplexSelector* wrapInComplex(); + CompoundSelector* wrapInCompound(); + + // Implement hash functionality + virtual size_t hash() const override; + + // Implement for cleanup phase + virtual bool empty() const { + return name().empty(); + } + + // Unify simple selector with multiple simple selectors + virtual CompoundSelector* unifyWith(CompoundSelector*); + + // Returns true if name equals '*' + bool isUniversal() const { + return name_ == "*"; + } + + // Checker if the name + virtual bool nsMatch(const SimpleSelector& r) const { return true; } + + virtual bool hasInvisible() const { return false; } + + // This is a very interesting line, as it seems pointless, since the base class + // already marks this as an unimplemented interface methods, but by defining this + // line here, we make sure that callers know the return is a bit more specific. + virtual SimpleSelector* copy(SASS_MEMORY_ARGS bool childless = false) const override = 0; + }; - //////////////////////////////////////////// - // Abstract base class for simple selectors. - //////////////////////////////////////////// - class SimpleSelector : public Selector { - public: - enum Simple_Type { - ID_SEL, - TYPE_SEL, - CLASS_SEL, - PSEUDO_SEL, - ATTRIBUTE_SEL, - PLACEHOLDER_SEL, - }; - public: - HASH_CONSTREF(sass::string, ns) - HASH_CONSTREF(sass::string, name) - ADD_PROPERTY(Simple_Type, simple_type) - HASH_PROPERTY(bool, has_ns) + ///////////////////////////////////////////////////////////////////////// + // Base class for all selectors that support name-spaces + ///////////////////////////////////////////////////////////////////////// + + struct QualifiedName { + sass::string name; + sass::string ns; + bool hasNs; + }; + + class NameSpaceSelector : public SimpleSelector + { + private: + + ADD_CONSTREF(bool, hasNs); + ADD_CONSTREF(sass::string, ns); + public: - SimpleSelector(SourceSpan pstate, sass::string n = ""); - // ordering within parent (peudos go last) - virtual int getSortOrder() const = 0; - virtual sass::string ns_name() const; - size_t hash() const override; - virtual bool empty() const; - // namespace compare functions - bool is_ns_eq(const SimpleSelector& r) const; - // namespace query functions - bool is_universal_ns() const; - bool is_empty_ns() const; - bool has_empty_ns() const; - bool has_qualified_ns() const; - // name query functions - bool is_universal() const; - virtual bool has_placeholder(); - - virtual ~SimpleSelector() = 0; - virtual CompoundSelector* unifyWith(CompoundSelector*); - /* helper function for syntax sugar */ - virtual IDSelector* getIdSelector() { return NULL; } - virtual TypeSelector* getTypeSelector() { return NULL; } - virtual PseudoSelector* getPseudoSelector() { return NULL; } + // Value constructor + NameSpaceSelector( + const SourceSpan& pstate, + sass::string&& name, + sass::string&& ns, + bool hasNs = false); + + // Copy constructor + NameSpaceSelector( + const NameSpaceSelector* ptr); + + // Implement hash functionality + virtual size_t hash() const override; + + // Implement for cleanup phase + virtual bool empty() const override { + return ns().empty() && SimpleSelector::empty(); + } - ComplexSelectorObj wrapInComplex(); - CompoundSelectorObj wrapInCompound(); + // Returns true if namespaces match exactly + bool nsEqual(const NameSpaceSelector& rhs) const { + return hasNs_ == rhs.hasNs_ && ns_ == rhs.ns_; + } - virtual bool isInvisible() const { return false; } - virtual bool is_pseudo_element() const; - virtual bool has_real_parent_ref() const override; + // Returns true if namespaces are considered compatible + bool nsMatch(const NameSpaceSelector& rhs) const { + return /* (isUniversalNs() || */ nsEqual(rhs); + } - bool operator==(const Selector& rhs) const final override; + // Returns true if namespace was explicitly set to '*' + bool isUniversalNs() const { + return hasNs_ && ns_ == "*"; + } - virtual bool operator==(const SelectorList& rhs) const; - virtual bool operator==(const ComplexSelector& rhs) const; - virtual bool operator==(const CompoundSelector& rhs) const; + // Up-casts the right hand side first to find specialization + bool nsMatch(const SimpleSelector& rhs) const override final; - ATTACH_VIRTUAL_CMP_OPERATIONS(SimpleSelector); - ATTACH_VIRTUAL_AST_OPERATIONS(SimpleSelector); - ATTACH_CRTP_PERFORM_METHODS(); + // This is a very interesting line, as it seems pointless, since the base class + // already marks this as an unimplemented interface methods, but by defining this + // line here, we make sure that callers know the return is a bit more specific. + virtual NameSpaceSelector* copy(SASS_MEMORY_ARGS bool childless = false) const override = 0; + IMPLEMENT_ISA_CASTER(NameSpaceSelector); }; - inline SimpleSelector::~SimpleSelector() { } ///////////////////////////////////////////////////////////////////////// - // Placeholder selectors (e.g., "%foo") for use in extend-only selectors. + // A placeholder selector. (e.g. `%foo`). This doesn't match any elements. + // It's intended to be extended using `@extend`. It's not a plain CSS + // selector — it should be removed before emitting a CSS document. ///////////////////////////////////////////////////////////////////////// - class PlaceholderSelector final : public SimpleSelector { + class PlaceholderSelector final : public SimpleSelector + { public: - PlaceholderSelector(SourceSpan pstate, sass::string n); - int getSortOrder() const override final { return 0; } - bool isInvisible() const override { return true; } - virtual unsigned long specificity() const override; - virtual bool has_placeholder() override; - bool operator==(const SimpleSelector& rhs) const override; - ATTACH_CMP_OPERATIONS(PlaceholderSelector) - ATTACH_AST_OPERATIONS(PlaceholderSelector) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + PlaceholderSelector( + const SourceSpan& pstate, + const sass::string& name); + + // Copy constructor + PlaceholderSelector( + const PlaceholderSelector* ptr); + + // Implement specialized specificity function + unsigned long specificity() const override final { + return Constants::Specificity::Base; + } + + // Returns whether this is a private selector. + // That is, whether it begins with `-` or `_`. + bool isPrivate() const { + return name_[0] == Character::$dash + || name_[0] == Character::$underscore; + } + + IMPLEMENT_SEL_COPY_IGNORE(PlaceholderSelector); + IMPLEMENT_ACCEPT(void, Selector, PlaceholderSelector); + IMPLEMENT_EQ_OPERATOR(Selector, PlaceholderSelector); + IMPLEMENT_ISA_CASTER(PlaceholderSelector); }; - ///////////////////////////////////////////////////////////////////// - // Type selectors (and the universal selector) -- e.g., div, span, *. - ///////////////////////////////////////////////////////////////////// - class TypeSelector final : public SimpleSelector { + ///////////////////////////////////////////////////////////////////////// + // A type selector. (e.g., `div`, `span` or `*`). + // This selects elements whose name equals the given name. + ///////////////////////////////////////////////////////////////////////// + class TypeSelector final : public NameSpaceSelector { public: - TypeSelector(SourceSpan pstate, sass::string n); - int getSortOrder() const override final { return 1; } - virtual unsigned long specificity() const override; - SimpleSelector* unifyWith(const SimpleSelector*); + + // Value constructor + TypeSelector( + const SourceSpan& pstate, + sass::string&& name, + sass::string&& ns, + bool hasNs = false); + + // Copy constructor + TypeSelector( + const TypeSelector* ptr); + + // Implement specialized specificity function + virtual unsigned long specificity() const override { + return isUniversal() ? 0 : Constants::Specificity::Element; + } + + // Unify Type selector with multiple simple selectors CompoundSelector* unifyWith(CompoundSelector*) override; - TypeSelector* getTypeSelector() override { return this; } - bool operator==(const SimpleSelector& rhs) const final override; - ATTACH_CMP_OPERATIONS(TypeSelector) - ATTACH_AST_OPERATIONS(TypeSelector) - ATTACH_CRTP_PERFORM_METHODS() + + // Unify two simple selectors with each other + SimpleSelector* unifyWith(const SimpleSelector*); + + IMPLEMENT_SEL_COPY_IGNORE(TypeSelector); + IMPLEMENT_ACCEPT(void, Selector, TypeSelector); + IMPLEMENT_EQ_OPERATOR(Selector, TypeSelector); + IMPLEMENT_ISA_CASTER(TypeSelector); }; - //////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // Class selectors -- i.e., .foo. - //////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// class ClassSelector final : public SimpleSelector { public: - ClassSelector(SourceSpan pstate, sass::string n); - int getSortOrder() const override final { return 2; } - virtual unsigned long specificity() const override; - bool operator==(const SimpleSelector& rhs) const final override; - ATTACH_CMP_OPERATIONS(ClassSelector) - ATTACH_AST_OPERATIONS(ClassSelector) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + ClassSelector( + const SourceSpan& pstate, + const sass::string& name); + + // Copy constructor + ClassSelector( + const ClassSelector* ptr); + + // Implement specialized specificity function + virtual unsigned long specificity() const override { + return Constants::Specificity::Class; + } + + IMPLEMENT_SEL_COPY_IGNORE(ClassSelector); + IMPLEMENT_ACCEPT(void, Selector, ClassSelector); + IMPLEMENT_EQ_OPERATOR(Selector, ClassSelector); + IMPLEMENT_ISA_CASTER(ClassSelector); }; - //////////////////////////////////////////////// - // ID selectors -- i.e., #foo. - //////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + // An ID selector (i.e. `#foo`). This selects elements + // whose `id` attribute exactly matches the given name. + ///////////////////////////////////////////////////////////////////////// class IDSelector final : public SimpleSelector { public: - IDSelector(SourceSpan pstate, sass::string n); - int getSortOrder() const override final { return 2; } - virtual unsigned long specificity() const override; + + // Value constructor + IDSelector( + const SourceSpan& pstate, + const sass::string& name); + + // Copy constructor + IDSelector( + const IDSelector* ptr); + + // Implement specialized specificity function + virtual unsigned long specificity() const override { + return Constants::Specificity::ID; + } + + // Unify ID selector with multiple simple selectors CompoundSelector* unifyWith(CompoundSelector*) override; - IDSelector* getIdSelector() final override { return this; } - bool operator==(const SimpleSelector& rhs) const final override; - ATTACH_CMP_OPERATIONS(IDSelector) - ATTACH_AST_OPERATIONS(IDSelector) - ATTACH_CRTP_PERFORM_METHODS() + + IMPLEMENT_SEL_COPY_IGNORE(IDSelector); + IMPLEMENT_ACCEPT(void, Selector, IDSelector); + IMPLEMENT_EQ_OPERATOR(Selector, IDSelector); + IMPLEMENT_ISA_CASTER(IDSelector); }; - /////////////////////////////////////////////////// - // Attribute selectors -- e.g., [src*=".jpg"], etc. - /////////////////////////////////////////////////// - class AttributeSelector final : public SimpleSelector { - ADD_CONSTREF(sass::string, matcher) - // this cannot be changed to obj atm!!!!!!????!!!!!!! - ADD_PROPERTY(String_Obj, value) // might be interpolated - ADD_PROPERTY(char, modifier); + ///////////////////////////////////////////////////////////////////////// + // An attribute selector. This selects for elements + // with the given attribute, and optionally with a + // value matching certain conditions as well. + ///////////////////////////////////////////////////////////////////////// + class AttributeSelector final : public NameSpaceSelector { + + // The operator that defines the semantics of [value]. + // If this is empty, this matches any element with the given property, + // regardless of this value. It's empty if and only if [value] is empty. + ADD_CONSTREF(sass::string, op); + + // An assertion about the value of [name]. + // The precise semantics of this string are defined by [op]. + // If this is `null`, this matches any element with the given property, + // regardless of this value. It's `null` if and only if [op] is `null`. + ADD_CONSTREF(sass::string, value); + + // The modifier which indicates how the attribute selector should be + // processed. See for example [case-sensitivity][] modifiers. + // [case-sensitivity]: https://www.w3.org/TR/selectors-4/#attribute-case + // If [op] is empty, this is always empty as well. + ADD_CONSTREF(char, modifier); + + // Defines if we parsed an identifier value. Dart-sass + // does this check again in serialize.visitAttributeSelector. + // We want to avoid this and do the check at parser stage. + ADD_CONSTREF(bool, isIdentifier); + public: - AttributeSelector(SourceSpan pstate, sass::string n, sass::string m, String_Obj v, char o = 0); - int getSortOrder() const override final { return 2; } - size_t hash() const override; - virtual unsigned long specificity() const override; - bool operator==(const SimpleSelector& rhs) const final override; - ATTACH_CMP_OPERATIONS(AttributeSelector) - ATTACH_AST_OPERATIONS(AttributeSelector) - ATTACH_CRTP_PERFORM_METHODS() + + // By value constructor + AttributeSelector( + const SourceSpan& pstate, + struct QualifiedName&& name, + sass::string&& op = "", + sass::string&& value = "", + bool isIdentifier = false, + char modifier = 0); + + // Copy constructor + AttributeSelector( + const AttributeSelector* ptr); + + // Implement specialized specificity function + virtual unsigned long specificity() const override { + return Constants::Specificity::Attr; + } + + IMPLEMENT_SEL_COPY_IGNORE(AttributeSelector); + IMPLEMENT_ACCEPT(void, Selector, AttributeSelector); + IMPLEMENT_EQ_OPERATOR(Selector, AttributeSelector); + IMPLEMENT_ISA_CASTER(AttributeSelector); }; - ////////////////////////////////////////////////////////////////// - // Pseudo selectors -- e.g., :first-child, :nth-of-type(...), etc. - ////////////////////////////////////////////////////////////////// - // Pseudo Selector cannot have any namespace? + ///////////////////////////////////////////////////////////////////////// + // A pseudo-class or pseudo-element selector (e.g., `:content` + // or `:nth-child`). The semantics of a specific pseudo selector + // depends on its name. Some selectors take arguments, including + // other selectors. Sass manually encodes logic for each pseudo + // selector that takes a selector as an argument, to ensure that + // extension and other selector operations work properly. + ///////////////////////////////////////////////////////////////////////// class PseudoSelector final : public SimpleSelector { - ADD_PROPERTY(sass::string, normalized) - ADD_PROPERTY(String_Obj, argument) - ADD_PROPERTY(SelectorListObj, selector) - ADD_PROPERTY(bool, isSyntacticClass) - ADD_PROPERTY(bool, isClass) + + // Like [name], but without any vendor prefixes. + ADD_CONSTREF(sass::string, normalized); + + // The non-selector argument passed to this selector. This is + // `null` if there's no argument. If [argument] and [selector] + // are both non-`null`, the selector follows the argument. + ADD_CONSTREF(sass::string, argument); + + // The selector argument passed to this selector. This is `null` + // if there's no selector. If [argument] and [selector] are + // both non-`null`, the selector follows the argument. + ADD_CONSTREF(SelectorListObj, selector); + + // Whether this is syntactically a pseudo-class selector. This is + // the same as [isClass] unless this selector is a pseudo-element + // that was written syntactically as a pseudo-class (`:before`, + // `:after`, `:first-line`, or `:first-letter`). This is + // `true` if and only if [isSyntacticElement] is `false`. + ADD_CONSTREF(bool, isSyntacticClass); + + // Whether this is a pseudo-class selector. + // This is `true` if and only if [isPseudoElement] is `false`. + ADD_CONSTREF(bool, isClass); + public: - PseudoSelector(SourceSpan pstate, sass::string n, bool element = false); - int getSortOrder() const override final { return 3; } - virtual bool is_pseudo_element() const override; - size_t hash() const override; - bool empty() const override; + // Value constructor + PseudoSelector( + const SourceSpan& pstate, + const sass::string& name, + bool element = false); + + // Copy constructor + PseudoSelector( + const PseudoSelector* ptr); + + // Returns true if there is a wrapped selector with an + // explicit `&` parent selector. Certainly questionable + // since the selector list may have compound selectors + // with and some without explicit parent selector!? + bool hasAnyExplicitParent() const override final; + + bool hasInvisible() const override final; - bool has_real_parent_ref() const override; + // Implement hash functionality + size_t hash() const override final; + + // Implement for cleanup phase + // Only considered empty if selector is + // available but has no items in it. + bool empty() const override final; // Whether this is a pseudo-element selector. // This is `true` if and only if [isClass] is `false`. - bool isElement() const { return !isClass(); } + // A pseudo-element is made of two colons (::) followed by the name. + // The `::` notation is introduced by the current document in order to + // establish a discrimination between pseudo-classes and pseudo-elements. + // For compatibility with existing style sheets, user agents must also + // accept the previous one-colon notation for pseudo-elements introduced + // in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and + // :after). This compatibility is not allowed for the new pseudo-elements + // introduced in this specification. + bool isPseudoElement() const { return !isClass(); } // Whether this is syntactically a pseudo-element selector. // This is `true` if and only if [isSyntacticClass] is `false`. bool isSyntacticElement() const { return !isSyntacticClass(); } - virtual unsigned long specificity() const override; - PseudoSelectorObj withSelector(SelectorListObj selector); + // Returns a new [PseudoSelector] based on ourself, + // but with the selector replaced with [selector]. + PseudoSelector* withSelector(SelectorList* selector); + + // Implement specialized specificity function + virtual unsigned long specificity() const override { + return isPseudoElement() + ? Constants::Specificity::Element + : Constants::Specificity::Pseudo; + } + // Unify Pseudo selector with multiple simple selectors CompoundSelector* unifyWith(CompoundSelector*) override; - PseudoSelector* getPseudoSelector() final override { return this; } - bool operator==(const SimpleSelector& rhs) const final override; - ATTACH_CMP_OPERATIONS(PseudoSelector) - ATTACH_AST_OPERATIONS(PseudoSelector) - void cloneChildren() override; - ATTACH_CRTP_PERFORM_METHODS() - }; + IMPLEMENT_SEL_COPY_IGNORE(PseudoSelector); + IMPLEMENT_ACCEPT(void, Selector, PseudoSelector); + IMPLEMENT_EQ_OPERATOR(Selector, PseudoSelector); + IMPLEMENT_ISA_CASTER(PseudoSelector); + }; - //////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // Complex Selectors are the most important class of selectors. // A Selector List consists of Complex Selectors (separated by comma) // Complex Selectors are itself a list of Compounds and Combinators // Between each item there is an implicit ancestor of combinator - //////////////////////////////////////////////////////////////////////////// - class ComplexSelector final : public Selector, public Vectorized { - ADD_PROPERTY(bool, chroots); + ///////////////////////////////////////////////////////////////////////// + class ComplexSelector final : public Selector, + public Vectorized + { + private: + + ADD_CONSTREF(bool, chroots); // line break before list separator - ADD_PROPERTY(bool, hasPreLineFeed); + ADD_CONSTREF(bool, hasPreLineFeed); + + // Calculate specificity only once + mutable unsigned long specificity_ = 0xFFFFFFFF; + mutable unsigned long maxSpecificity_ = 0xFFFFFFFF; + mutable unsigned long minSpecificity_ = 0xFFFFFFFF; + public: - ComplexSelector(SourceSpan pstate); + + // Value constructor + ComplexSelector( + const SourceSpan& pstate, + SelectorComponentVector&& components = {}); + + // Copy constructor + ComplexSelector( + const ComplexSelector* ptr, + bool childless = false); // Returns true if the first components // is a compound selector and fulfills // a few other criteria. - bool isInvisible() const; - bool isInvalidCss() const; + bool hasInvisible() const; - size_t hash() const override; - void cloneChildren() override; - bool has_placeholder() const; - bool has_real_parent_ref() const override; + // Wrap inside another selector type + SelectorList* wrapInList(); - SelectorList* resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent = true); - virtual unsigned long specificity() const override; + // Implement hash functionality + size_t hash() const override final; - SelectorList* unifyWith(ComplexSelector* rhs); + bool hasExplicitParent() const; - bool isSuperselectorOf(const ComplexSelector* sub) const; + // Convert to value list + List* toList() const; - SelectorListObj wrapInList(); + sass::vector + resolveParentSelectors( + SelectorList* parent, + BackTraces& traces, + bool implicit_parent = true); - size_t maxSpecificity() const override; - size_t minSpecificity() const override; - bool operator==(const Selector& rhs) const override; - bool operator==(const SelectorList& rhs) const; - bool operator==(const CompoundSelector& rhs) const; - bool operator==(const SimpleSelector& rhs) const; + // Unify two complex selectors with each other + SelectorList* unifyWith(ComplexSelector* rhs); + + // Determine if given `this` is a sub-selector of `sub` + bool isSuperselectorOf(const ComplexSelector* sub) const; + + // Specialize all specificity functions + unsigned long specificity() const override final; + unsigned long maxSpecificity() const override final; + unsigned long minSpecificity() const override final; - ATTACH_CMP_OPERATIONS(ComplexSelector) - ATTACH_AST_OPERATIONS(ComplexSelector) - ATTACH_CRTP_PERFORM_METHODS() + IMPLEMENT_SEL_COPY_CHILDREN(ComplexSelector); + IMPLEMENT_EQ_OPERATOR(Selector, ComplexSelector) + IMPLEMENT_ACCEPT(void, Selector, ComplexSelector); + IMPLEMENT_ISA_CASTER(ComplexSelector); }; - //////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // Base class for complex selector components - //////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// class SelectorComponent : public Selector { // line break after list separator - ADD_PROPERTY(bool, hasPostLineBreak) + ADD_CONSTREF(bool, hasPostLineBreak); public: - SelectorComponent(SourceSpan pstate, bool postLineBreak = false); - size_t hash() const override = 0; - void cloneChildren() override; + // Value constructor + SelectorComponent( + const SourceSpan& pstate, + bool hasPostLineBreak = false); + + // Copy constructor + SelectorComponent( + const SelectorComponent* ptr); + + // Implement hash functionality + virtual size_t hash() const override = 0; + //void cloneChildren(const Selector*) override; // By default we consider instances not empty virtual bool empty() const { return false; } - virtual bool has_placeholder() const = 0; - bool has_real_parent_ref() const override = 0; - - ComplexSelector* wrapInComplex(); + virtual bool hasInvisible() const { return false; } - size_t maxSpecificity() const override { return 0; } - size_t minSpecificity() const override { return 0; } + // Specialized by CompoundSelector + virtual bool hasPlaceholder() const { return false; } - virtual bool isCompound() const { return false; }; - virtual bool isCombinator() const { return false; }; + // Wrap inside another selector type + ComplexSelector* wrapInComplex(); - /* helper function for syntax sugar */ - virtual CompoundSelector* getCompound() { return NULL; } - virtual SelectorCombinator* getCombinator() { return NULL; } - virtual const CompoundSelector* getCompound() const { return NULL; } - virtual const SelectorCombinator* getCombinator() const { return NULL; } + // This is a very interesting line, as it seems pointless, since the base class + // already marks this as an unimplemented interface methods, but by defining this + // line here, we make sure that callers know the return is a bit more specific. + virtual SelectorComponent* copy(SASS_MEMORY_ARGS bool childless = false) const override = 0; - virtual unsigned long specificity() const override; - bool operator==(const Selector& rhs) const override = 0; - ATTACH_VIRTUAL_CMP_OPERATIONS(SelectorComponent); - ATTACH_VIRTUAL_AST_OPERATIONS(SelectorComponent); }; - //////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // A specific combinator between compound selectors - //////////////////////////////////////////////////////////////////////////// - class SelectorCombinator final : public SelectorComponent { + ///////////////////////////////////////////////////////////////////////// + class SelectorCombinator final : public SelectorComponent + { public: // Enumerate all possible selector combinators. There is some @@ -363,20 +631,19 @@ namespace Sass { private: // Store the type of this combinator - HASH_CONSTREF(Combinator, combinator) + ADD_CONSTREF(Combinator, combinator); public: - SelectorCombinator(SourceSpan pstate, Combinator combinator, bool postLineBreak = false); - - bool has_real_parent_ref() const override { return false; } - bool has_placeholder() const override { return false; } - /* helper function for syntax sugar */ - SelectorCombinator* getCombinator() final override { return this; } - const SelectorCombinator* getCombinator() const final override { return this; } + // Value constructor + SelectorCombinator( + const SourceSpan& pstate, + Combinator combinator, + bool hasPostLineBreak = false); - // Query type of combinator - bool isCombinator() const override { return true; }; + // Copy constructor + SelectorCombinator( + const SelectorCombinator* ptr); // Matches the right-hand selector if it's a direct child of the left- // hand selector in the DOM tree. Dart-sass also calls this `child` @@ -393,130 +660,159 @@ namespace Sass { // https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator bool isAdjacentCombinator() const { return combinator_ == ADJACENT; } // + - size_t maxSpecificity() const override { return 0; } - size_t minSpecificity() const override { return 0; } + // The combinators do not add anything to the specificity + unsigned long specificity() const override final { return 0; } - size_t hash() const override { - return std::hash()(combinator_); - } - void cloneChildren() override; - virtual unsigned long specificity() const override; - bool operator==(const Selector& rhs) const override; - bool operator==(const SelectorComponent& rhs) const override; - - ATTACH_CMP_OPERATIONS(SelectorCombinator) - ATTACH_AST_OPERATIONS(SelectorCombinator) - ATTACH_CRTP_PERFORM_METHODS() + // Implement hash functionality + size_t hash() const override final; + + IMPLEMENT_SEL_COPY_IGNORE(SelectorCombinator); + IMPLEMENT_ACCEPT(void, Selector, SelectorCombinator); + IMPLEMENT_EQ_OPERATOR(Selector, SelectorCombinator); + IMPLEMENT_ISA_CASTER(SelectorCombinator); }; - //////////////////////////////////////////////////////////////////////////// - // A compound selector consists of multiple simple selectors - //////////////////////////////////////////////////////////////////////////// - class CompoundSelector final : public SelectorComponent, public Vectorized { - ADD_PROPERTY(bool, hasRealParent) + ///////////////////////////////////////////////////////////////////////// + // A compound selector consists of multiple simple selectors. It will be + // either implicitly or explicitly connected to its parent sass selector. + // According to the specs we could also unify the tag selector into this, + // as AFAICT only one tag selector is ever allowed. Further we could free + // up the pseudo selectors from being virtual, as they must be last always. + // https://github.com/sass/libsass/pull/3101 + ///////////////////////////////////////////////////////////////////////// + class CompoundSelector final : public SelectorComponent, public Vectorized + { + private: + + // This is one of the most important flags for selectors. + // The `&` parent selector can only occur at the start of + // a compound selector. Interpolations `#{&}` are handle in + // another code-path. If an explicit parent is given we will + // not implicitly connect the selector to its scoped parent. + ADD_CONSTREF(bool, withExplicitParent); + + // Calculate specificity only once + mutable unsigned long specificity_ = 0xFFFFFFFF; + mutable unsigned long maxSpecificity_ = 0xFFFFFFFF; + mutable unsigned long minSpecificity_ = 0xFFFFFFFF; + public: - CompoundSelector(SourceSpan pstate, bool postLineBreak = false); - // Returns true if this compound selector - // fulfills various criteria. - bool isInvisible() const; + // Value Constructor + CompoundSelector( + const SourceSpan& pstate, + bool hasPostLineBreak = false); - bool empty() const override { - return Vectorized::empty(); - } + // Value move Constructor + CompoundSelector( + const SourceSpan& pstate, + sass::vector&& selectors, + bool hasPostLineBreak = false); - size_t hash() const override; - CompoundSelector* unifyWith(CompoundSelector* rhs); + // Copy constructor + CompoundSelector( + const CompoundSelector* ptr, + bool childless = false); - /* helper function for syntax sugar */ - CompoundSelector* getCompound() final override { return this; } - const CompoundSelector* getCompound() const final override { return this; } + // Returns true if any selector is invisible. + bool hasInvisible() const override final; - bool isSuperselectorOf(const CompoundSelector* sub, sass::string wrapped = "") const; + // Implement for cleanup phase + // Dispatch to underlying list + bool empty() const override { + return Vectorized::empty(); + } - void cloneChildren() override; - bool has_real_parent_ref() const override; - bool has_placeholder() const override; - sass::vector resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent = true); + // Implement hash functionality + size_t hash() const override final; - virtual bool isCompound() const override { return true; }; - virtual unsigned long specificity() const override; + // Unify two lists of simple selectors + CompoundSelector* unifyWith(CompoundSelector* rhs); - size_t maxSpecificity() const override; - size_t minSpecificity() const override; + bool hasAnyExplicitParent() const override final; - bool operator==(const Selector& rhs) const override; + bool hasPlaceholder() const override final; - bool operator==(const SelectorComponent& rhs) const override; + // Resolve parents and form the final selector + sass::vector + resolveParentSelectors( + SelectorList* parent, + BackTraces& traces, + bool implicit_parent = true); - bool operator==(const SelectorList& rhs) const; - bool operator==(const ComplexSelector& rhs) const; - bool operator==(const SimpleSelector& rhs) const; + // Determine if given `this` is a sub-selector of `sub` + bool isSuperselectorOf(const CompoundSelector* sub) const; - void sortChildren(); - bool isInvalidCss() const; + // Specialize all specificity functions + unsigned long specificity() const override final; + unsigned long maxSpecificity() const override final; + unsigned long minSpecificity() const override final; - ATTACH_CMP_OPERATIONS(CompoundSelector) - ATTACH_AST_OPERATIONS(CompoundSelector) - ATTACH_CRTP_PERFORM_METHODS() + IMPLEMENT_SEL_COPY_CHILDREN(CompoundSelector); + IMPLEMENT_ACCEPT(void, Selector, CompoundSelector); + IMPLEMENT_EQ_OPERATOR(Selector, CompoundSelector); + IMPLEMENT_ISA_CASTER(CompoundSelector); }; - /////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // Comma-separated selector groups. - /////////////////////////////////// - class SelectorList final : public Selector, public Vectorized { + ///////////////////////////////////////////////////////////////////////// + class SelectorList final : public Selector, + public Vectorized + { private: - // maybe we have optional flag - // ToDo: should be at ExtendRule? - ADD_PROPERTY(bool, is_optional) + + // Calculate specificity only once + mutable unsigned long maxSpecificity_ = 0xFFFFFFFF; + mutable unsigned long minSpecificity_ = 0xFFFFFFFF; + public: - SelectorList(SourceSpan pstate, size_t s = 0); - sass::string type() const override { return "list"; } - size_t hash() const override; + // Value move constructor + SelectorList( + const SourceSpan& pstate, + sass::vector&& = {}); + + // Copy constructor + SelectorList(const SelectorList* ptr, + bool childless = false); + + // Implement hash functionality + size_t hash() const override final; + + // Unify two selector lists with each other SelectorList* unifyWith(SelectorList*); - // Returns true if all complex selectors - // can have real parents, meaning every - // first component does allow for it - bool isInvisible() const; + bool hasExplicitParent() const; - void cloneChildren() override; - bool has_real_parent_ref() const override; - SelectorList* resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent = true); - virtual unsigned long specificity() const override; + // Convert to `List` or `Null` + Value* toValue() const; + SelectorList* resolveParentSelectors( + SelectorList* parent, + BackTraces& traces, + bool implicit_parent = true); + + // Determine if given `this` is a sub-selector of `sub` bool isSuperselectorOf(const SelectorList* sub) const; - size_t maxSpecificity() const override; - size_t minSpecificity() const override; + // This implementation is not available, don't call + unsigned long specificity() const override final { + throw std::runtime_error("specificity not implemented"); + } - bool operator==(const Selector& rhs) const override; - bool operator==(const ComplexSelector& rhs) const; - bool operator==(const CompoundSelector& rhs) const; - bool operator==(const SimpleSelector& rhs) const; - // Selector Lists can be compared to comma lists - bool operator==(const Expression& rhs) const override; + // Specialize min and max specificity functions + unsigned long maxSpecificity() const override final; + unsigned long minSpecificity() const override final; - ATTACH_CMP_OPERATIONS(SelectorList) - ATTACH_AST_OPERATIONS(SelectorList) - ATTACH_CRTP_PERFORM_METHODS() + IMPLEMENT_SEL_COPY_CHILDREN(SelectorList); + IMPLEMENT_ACCEPT(void, Selector, SelectorList); + IMPLEMENT_EQ_OPERATOR(Selector, SelectorList); + IMPLEMENT_ISA_CASTER(SelectorList); }; - //////////////////////////////// - // The Sass `@extend` directive. - //////////////////////////////// - class ExtendRule final : public Statement { - ADD_PROPERTY(bool, isOptional) - // This should be a simple selector only! - ADD_PROPERTY(SelectorListObj, selector) - ADD_PROPERTY(Selector_Schema_Obj, schema) - public: - ExtendRule(SourceSpan pstate, SelectorListObj s); - ExtendRule(SourceSpan pstate, Selector_Schema_Obj s); - ATTACH_AST_OPERATIONS(ExtendRule) - ATTACH_CRTP_PERFORM_METHODS() - }; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/ast_statements.cpp b/src/ast_statements.cpp new file mode 100644 index 0000000000..20fc2c3c47 --- /dev/null +++ b/src/ast_statements.cpp @@ -0,0 +1,405 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "ast_statements.hpp" + +#include "ast_supports.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ParentStatement::ParentStatement( + SourceSpan&& pstate, + StatementVector&& children, + VarRefs* idxs) : + Statement(std::move(pstate)), + Vectorized(std::move(children)), + idxs_(idxs) + {} + + // Returns whether we have a child content block + bool ParentStatement::hasContent() const + { + if (Statement::hasContent()) return true; + for (const StatementObj& child : elements_) { + if (child->hasContent()) return true; + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + StyleRule::StyleRule( + SourceSpan&& pstate, + Interpolation* interpolation, + VarRefs* idxs, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + interpolation_(interpolation) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Declaration::Declaration( + SourceSpan&& pstate, + Interpolation* name, + Expression* value, + bool is_custom_property, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children)), + name_(name), + value_(value), + is_custom_property_(is_custom_property) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ForRule::ForRule( + SourceSpan&& pstate, + const EnvKey& varname, + Expression* lower_bound, + Expression* upper_bound, + bool is_inclusive, + VarRefs* idxs, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + varname_(varname), + lower_bound_(lower_bound), + upper_bound_(upper_bound), + is_inclusive_(is_inclusive) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + EachRule::EachRule( + SourceSpan&& pstate, + const EnvKeys& variables, + Expression* expressions, + VarRefs* idxs, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + variables_(variables), + expressions_(expressions) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + WhileRule::WhileRule( + SourceSpan&& pstate, + Expression* condition, + VarRefs* idxs, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + condition_(condition) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + MediaRule::MediaRule( + SourceSpan&& pstate, + Interpolation* query, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children)), + query_(query) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + AtRule::AtRule( + SourceSpan&& pstate, + Interpolation* name, + Interpolation* value, + bool isChildless, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children)), + name_(name), + value_(value), + isChildless_(isChildless) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + AtRootRule::AtRootRule( + SourceSpan&& pstate, + Interpolation* query, + VarRefs* idxs, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + query_(query) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + IfRule::IfRule( + SourceSpan&& pstate, + VarRefs* idxs, + StatementVector&& children, + Expression* predicate, + IfRule* alternative) : + ParentStatement( + std::move(pstate), + std::move(children)), + idxs_(idxs), + predicate_(predicate), + alternative_(alternative) + {} + + // Also check alternative for content block + bool IfRule::hasContent() const + { + if (ParentStatement::hasContent()) return true; + return alternative_ && alternative_->hasContent(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SupportsRule::SupportsRule( + SourceSpan&& pstate, + SupportsCondition* condition, + VarRefs* idxs, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + condition_(condition) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CallableDeclaration::CallableDeclaration( + SourceSpan&& pstate, + const EnvKey& name, + ArgumentDeclaration* arguments, + StatementVector&& children, + SilentComment* comment, + VarRefs* idxs) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + name_(name), + comment_(comment), + arguments_(arguments) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + IncludeRule::IncludeRule( + SourceSpan&& pstate, + const EnvKey& name, + ArgumentInvocation* arguments, + const sass::string& ns, + ContentBlock* content) : + Statement(std::move(pstate)), + CallableInvocation(arguments), + ns_(ns), + name_(name), + content_(content) + {} + + bool IncludeRule::hasContent() const + { + return !content_.isNull(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ContentBlock::ContentBlock( + SourceSpan&& pstate, + ArgumentDeclaration* arguments, + VarRefs* idxs, + StatementVector&& children, + SilentComment* comment) : + CallableDeclaration( + std::move(pstate), + Keys::contentRule, + arguments, + std::move(children), + comment, idxs) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + FunctionRule::FunctionRule( + SourceSpan&& pstate, + const EnvKey& name, + ArgumentDeclaration* arguments, + VarRefs* idxs, + StatementVector&& children, + SilentComment* comment) : + CallableDeclaration( + std::move(pstate), + name, arguments, + std::move(children), + comment, idxs) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + MixinRule::MixinRule( + SourceSpan&& pstate, + const sass::string& name, + ArgumentDeclaration* arguments, + VarRefs* idxs, + StatementVector&& children, + SilentComment* comment) : + CallableDeclaration( + std::move(pstate), + name, arguments, + std::move(children), + comment, idxs) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + WarnRule::WarnRule( + SourceSpan&& pstate, + Expression* expression) : + Statement(std::move(pstate)), + expression_(expression) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ErrorRule::ErrorRule( + SourceSpan&& pstate, + Expression* expression) : + Statement(std::move(pstate)), + expression_(expression) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + DebugRule::DebugRule( + SourceSpan&& pstate, + Expression* expression) : + Statement(std::move(pstate)), + expression_(expression) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ReturnRule::ReturnRule( + SourceSpan&& pstate, + Expression* value) : + Statement(std::move(pstate)), + value_(value) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ContentRule::ContentRule( + SourceSpan&& pstate, + ArgumentInvocation* arguments) : + Statement(std::move(pstate)), + arguments_(arguments) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ExtendRule::ExtendRule( + SourceSpan&& pstate, + Interpolation* selector, + bool is_optional) : + Statement(std::move(pstate)), + selector_(selector), + is_optional_(is_optional) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + LoudComment::LoudComment( + SourceSpan&& pstate, + Interpolation* text) : + Statement(std::move(pstate)), + text_(text) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SilentComment::SilentComment( + SourceSpan&& pstate, + sass::string&& text) : + Statement(pstate), + text_(text) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ImportRule::ImportRule( + const SourceSpan& pstate) : + Statement(pstate) + {} + + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + AssignRule::AssignRule( + const SourceSpan& pstate, + const EnvKey& variable, + VarRef vidx, + Expression* value, + bool is_default, + bool is_global) : + Statement(pstate), + variable_(variable), + value_(value), + vidxs_({ vidx }), + is_default_(is_default), + is_global_(is_global) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + diff --git a/src/ast_statements.hpp b/src/ast_statements.hpp new file mode 100644 index 0000000000..294f54cf46 --- /dev/null +++ b/src/ast_statements.hpp @@ -0,0 +1,561 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_STATEMENTS_HPP +#define SASS_AST_STATEMENTS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_nodes.hpp" +#include "ast_callables.hpp" +#include "environment_stack.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + // Abstract base class for statements that contain blocks of statements. + ///////////////////////////////////////////////////////////////////////// + + class ParentStatement : public Statement, public Vectorized + { + ADD_PROPERTY(VarRefs*, idxs); + public: + // Value constructor + ParentStatement( + SourceSpan&& pstate, + StatementVector&& children, + VarRefs* idxs = nullptr); + // Returns whether we have a child content block + virtual bool hasContent() const override; + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////////////// + // A style rule. This applies style declarations to elements + // that match a given selector. Formerly known as `Ruleset`. + ///////////////////////////////////////////////////////////////////////// + class StyleRule final : public ParentStatement + { + ADD_CONSTREF(InterpolationObj, interpolation); + public: + // Value constructor + StyleRule(SourceSpan&& pstate, + Interpolation* interpolation, + VarRefs* idxs = nullptr, + StatementVector&& children = {}); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitStyleRule(this); + } + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(StyleRule); + }; + + ///////////////////////////////////////////////////////////////////////// + // Declarations -- style rules consisting of a property name and values. + ///////////////////////////////////////////////////////////////////////// + class Declaration final : public ParentStatement + { + ADD_CONSTREF(InterpolationObj, name); + ADD_CONSTREF(ExpressionObj, value); + ADD_CONSTREF(bool, is_custom_property); + public: + // Value constructor + Declaration(SourceSpan&& pstate, + Interpolation* name, + Expression* value = nullptr, + bool is_custom_property = false, + StatementVector&& children = {}); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitDeclaration(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@for` control directive. + ///////////////////////////////////////////////////////////////////////// + class ForRule final : public ParentStatement + { + ADD_CONSTREF(EnvKey, varname); // unused? + ADD_CONSTREF(ExpressionObj, lower_bound); + ADD_CONSTREF(ExpressionObj, upper_bound); + ADD_CONSTREF(bool, is_inclusive); + public: + // Value constructor + ForRule( + SourceSpan&& pstate, + const EnvKey& varname, + Expression* lower_bound, + Expression* upper_bound, + bool is_inclusive = false, + VarRefs* idxs = nullptr, + StatementVector&& children = {}); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitForRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@each` control directive. + ///////////////////////////////////////////////////////////////////////// + class EachRule final : public ParentStatement + { + ADD_CONSTREF(EnvKeys, variables); + ADD_CONSTREF(ExpressionObj, expressions); + public: + // Value constructor + EachRule( + SourceSpan&& pstate, + const EnvKeys& variables, + Expression* expressions, + VarRefs* idxs = nullptr, + StatementVector&& children = {}); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitEachRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@while` control directive. + ///////////////////////////////////////////////////////////////////////// + class WhileRule final : public ParentStatement + { + ADD_CONSTREF(ExpressionObj, condition); + public: + // Value constructor + WhileRule( + SourceSpan&& pstate, + Expression* condition, + VarRefs* idxs = nullptr, + StatementVector&& children = {}); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitWhileRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // The query that determines on which platforms the styles will be in + // effect. This is only parsed after the interpolation has been resolved. + ///////////////////////////////////////////////////////////////////////// + class MediaRule final : public ParentStatement + { + ADD_CONSTREF(InterpolationObj, query) + public: + // Value constructor + MediaRule( + SourceSpan&& pstate, + Interpolation* query, + StatementVector&& children = {}); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitMediaRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // At-rules -- arbitrary directives beginning with "@" + // The rules may have more optional statement blocks. + ///////////////////////////////////////////////////////////////////////// + class AtRule final : public ParentStatement + { + ADD_CONSTREF(InterpolationObj, name); + ADD_CONSTREF(InterpolationObj, value); + ADD_CONSTREF(bool, isChildless); + public: + // Value constructor + AtRule( + SourceSpan&& pstate, + Interpolation* name, + Interpolation* value, + bool is_childless = true, + StatementVector&& children = {}); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitAtRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // An `@at-root` rule + ///////////////////////////////////////////////////////////////////////// + class AtRootRule final : public ParentStatement + { + ADD_CONSTREF(InterpolationObj, query); + public: + // Value constructor + AtRootRule( + SourceSpan&& pstate, + Interpolation* query = nullptr, + VarRefs* idxs = nullptr, + StatementVector&& children = {}); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitAtRootRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@if` control directive. + ///////////////////////////////////////////////////////////////////////// + class IfRule final : public ParentStatement + { + // Variables for children scope + ADD_PROPERTY(VarRefs*, idxs); + // Predicate is optional, which indicates an else block. + // In this case further `alternatives` are simply ignored. + ADD_CONSTREF(ExpressionObj, predicate); + // The else or else-if block + ADD_REF(IfRuleObj, alternative); + public: + // Value constructor + IfRule(SourceSpan&& pstate, + VarRefs* idxs, + StatementVector&& children = {}, + Expression* predicate = nullptr, + IfRule* alternative = {}); + // Also check alternative for content block + bool hasContent() const override final; + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitIfRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // `@supports` rule. + ///////////////////////////////////////////////////////////////////////// + class SupportsRule final : public ParentStatement + { + ADD_CONSTREF(SupportsConditionObj, condition); + public: + SupportsRule( + SourceSpan&& pstate, + SupportsCondition* condition, + VarRefs* idxs = nullptr, + StatementVector&& children = {}); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitSupportsRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + class CallableDeclaration : public ParentStatement + { + // The name of this callable. + ADD_CONSTREF(EnvKey, name); + // The comment immediately preceding this declaration. + ADD_CONSTREF(SilentCommentObj, comment); + // The declared arguments this callable accepts. + ADD_CONSTREF(ArgumentDeclarationObj, arguments); + public: + // Value constructor + CallableDeclaration( + SourceSpan&& pstate, + const EnvKey& name, + ArgumentDeclaration* arguments, + StatementVector&& children = {}, + SilentComment* comment = nullptr, + VarRefs* idxs = nullptr); + // Declare up-casting methods + DECLARE_ISA_CASTER(MixinRule); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class ContentBlock final : + public CallableDeclaration + { + // Content function reference + ADD_CONSTREF(VarRef, cidx); + public: + // Value constructor + ContentBlock( + SourceSpan&& pstate, + ArgumentDeclaration* arguments = nullptr, + VarRefs* idxs = nullptr, + StatementVector&& children = {}, + SilentComment* comment = nullptr); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitContentBlock(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class FunctionRule final : + public CallableDeclaration + { + // Function reference + ADD_CONSTREF(VarRef, fidx); + public: + // Value constructor + FunctionRule( + SourceSpan&& pstate, + const EnvKey& name, + ArgumentDeclaration* arguments, + VarRefs* idxs = nullptr, + StatementVector&& children = {}, + SilentComment* comment = nullptr); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitFunctionRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class MixinRule final : + public CallableDeclaration + { + // Mixin function reference + ADD_CONSTREF(VarRef, midx); + // Content function reference + ADD_CONSTREF(VarRef, cidx); + public: + // Value constructor + MixinRule( + SourceSpan&& pstate, + const sass::string& name, + ArgumentDeclaration* arguments, + VarRefs* idxs = nullptr, + StatementVector&& children = {}, + SilentComment* comment = nullptr); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitMixinRule(this); + } + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(MixinRule); + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@warn` directive. + ///////////////////////////////////////////////////////////////////////// + class WarnRule final : public Statement + { + ADD_CONSTREF(ExpressionObj, expression); + public: + // Value constructor + WarnRule(SourceSpan&& pstate, + Expression* expression); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitWarnRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@error` directive. + ///////////////////////////////////////////////////////////////////////// + class ErrorRule final : public Statement + { + ADD_CONSTREF(ExpressionObj, expression); + public: + // Value constructor + ErrorRule(SourceSpan&& pstate, + Expression* expression); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitErrorRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@debug` directive. + ///////////////////////////////////////////////////////////////////////// + class DebugRule final : public Statement + { + ADD_CONSTREF(ExpressionObj, expression); + public: + // Value constructor + DebugRule(SourceSpan&& pstate, + Expression* expression); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitDebugRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // The @return directive for use inside SassScript functions. + ///////////////////////////////////////////////////////////////////////// + class ReturnRule final : public Statement + { + ADD_CONSTREF(ExpressionObj, value); + public: + // Value constructor + ReturnRule(SourceSpan&& pstate, + Expression* value = nullptr); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitReturnRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // The @content directive for mixin content blocks. + ///////////////////////////////////////////////////////////////////////// + class ContentRule final : public Statement + { + ADD_CONSTREF(ArgumentInvocationObj, arguments); + public: + // Value constructor + ContentRule(SourceSpan&& pstate, + ArgumentInvocation* arguments); + bool hasContent() const override final { return true; } + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitContentRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@extend` directive. + ///////////////////////////////////////////////////////////////////////// + class ExtendRule final : public Statement + { + // This should be a simple selector only! + ADD_CONSTREF(InterpolationObj, selector); + // Flag if extend had optional flag + ADD_CONSTREF(bool, is_optional); + public: + // Value constructor + ExtendRule( + SourceSpan&& pstate, + Interpolation* selector, + bool is_optional = false); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitExtendRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // CSS comments. These may be interpolated. + ///////////////////////////////////////////////////////////////////////// + class LoudComment final : public Statement + { + // The interpolated text of this comment, including comment characters. + ADD_CONSTREF(InterpolationObj, text) + public: + // Value constructor + LoudComment( + SourceSpan&& pstate, + Interpolation* text); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitLoudComment(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // Silent comment, starting with `//`. + ///////////////////////////////////////////////////////////////////////// + class SilentComment final : public Statement + { + // The text of this comment, including comment characters. + ADD_CONSTREF(sass::string, text) + public: + // Value constructor + SilentComment( + SourceSpan&& pstate, + sass::string&& text); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitSilentComment(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // `@import` rule. + ///////////////////////////////////////////////////////////////////////// + class ImportRule final : public Statement, + public Vectorized + { + public: + // Value constructor + ImportRule(const SourceSpan& pstate); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitImportRule(this); + } + }; + + ///////////////////////////////////// + // Assignments -- variable and value. + ///////////////////////////////////// + class AssignRule final : public Statement + { + ADD_CONSTREF(EnvKey, variable); + ADD_CONSTREF(ExpressionObj, value); + ADD_REF(std::vector, vidxs); + ADD_CONSTREF(bool, is_default); + ADD_CONSTREF(bool, is_global); + public: + // Value constructor + AssignRule( + const SourceSpan& pstate, + const EnvKey& variable, + VarRef vidx, + Expression* value, + bool is_default = false, + bool is_global = false); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitAssignRule(this); + } + }; + + /////////////////////////////////////////// + // The @include mixin invocation rule + /////////////////////////////////////////// + class IncludeRule final : public Statement, + public CallableInvocation + { + + // The namespace of the mixin being invoked, or + // `null` if it's invoked without a namespace. + ADD_CONSTREF(sass::string, ns); + + // The name of the mixin being invoked. + ADD_CONSTREF(EnvKey, name); + + // The block that will be invoked for [ContentRule]s in the mixin + // being invoked, or `null` if this doesn't pass a content block. + ADD_CONSTREF(ContentBlockObj, content); + + ADD_CONSTREF(VarRef, midx); + + public: + + IncludeRule( + SourceSpan&& pstate, + const EnvKey& name, + ArgumentInvocation* arguments, + const sass::string& ns = "", + ContentBlock* content = nullptr); + + bool hasContent() const override final; + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitIncludeRule(this); + } + }; + +} + +#endif diff --git a/src/ast_supports.cpp b/src/ast_supports.cpp index 9cd0bf3684..a81ebfdd0c 100644 --- a/src/ast_supports.cpp +++ b/src/ast_supports.cpp @@ -1,112 +1,68 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "ast_supports.hpp" namespace Sass { ///////////////////////////////////////////////////////////////////////// + // The abstract superclass of all Supports conditions. ///////////////////////////////////////////////////////////////////////// - SupportsRule::SupportsRule(SourceSpan pstate, SupportsConditionObj condition, Block_Obj block) - : ParentStatement(pstate, block), condition_(condition) - { statement_type(SUPPORTS); } - SupportsRule::SupportsRule(const SupportsRule* ptr) - : ParentStatement(ptr), condition_(ptr->condition_) - { statement_type(SUPPORTS); } - bool SupportsRule::bubbles() { return true; } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - SupportsCondition::SupportsCondition(SourceSpan pstate) - : Expression(pstate) - { } - - SupportsCondition::SupportsCondition(const SupportsCondition* ptr) - : Expression(ptr) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - SupportsOperation::SupportsOperation(SourceSpan pstate, SupportsConditionObj l, SupportsConditionObj r, Operand o) - : SupportsCondition(pstate), left_(l), right_(r), operand_(o) - { } - SupportsOperation::SupportsOperation(const SupportsOperation* ptr) - : SupportsCondition(ptr), - left_(ptr->left_), - right_(ptr->right_), - operand_(ptr->operand_) - { } - - bool SupportsOperation::needs_parens(SupportsConditionObj cond) const - { - if (SupportsOperationObj op = Cast(cond)) { - return op->operand() != operand(); - } - return Cast(cond) != NULL; - } + SupportsCondition::SupportsCondition( + const SourceSpan& pstate) : + AstNode(pstate) + {} ///////////////////////////////////////////////////////////////////////// + // An operator condition (e.g. `CONDITION1 and CONDITION2`). ///////////////////////////////////////////////////////////////////////// - SupportsNegation::SupportsNegation(SourceSpan pstate, SupportsConditionObj c) - : SupportsCondition(pstate), condition_(c) - { } - SupportsNegation::SupportsNegation(const SupportsNegation* ptr) - : SupportsCondition(ptr), condition_(ptr->condition_) - { } - - bool SupportsNegation::needs_parens(SupportsConditionObj cond) const - { - return Cast(cond) || - Cast(cond); - } + SupportsOperation::SupportsOperation( + const SourceSpan& pstate, + SupportsConditionObj lhs, + SupportsConditionObj rhs, + Operand operand) : + SupportsCondition(pstate), + left_(lhs), + right_(rhs), + operand_(operand) + {} ///////////////////////////////////////////////////////////////////////// + // A negation condition (`not CONDITION`). ///////////////////////////////////////////////////////////////////////// - SupportsDeclaration::SupportsDeclaration(SourceSpan pstate, ExpressionObj f, ExpressionObj v) - : SupportsCondition(pstate), feature_(f), value_(v) - { } - SupportsDeclaration::SupportsDeclaration(const SupportsDeclaration* ptr) - : SupportsCondition(ptr), - feature_(ptr->feature_), - value_(ptr->value_) - { } - - bool SupportsDeclaration::needs_parens(SupportsConditionObj cond) const - { - return false; - } + SupportsNegation::SupportsNegation( + const SourceSpan& pstate, + SupportsCondition* condition) : + SupportsCondition(pstate), + condition_(condition) + {} ///////////////////////////////////////////////////////////////////////// + // A declaration condition (e.g. `(feature: value)`). ///////////////////////////////////////////////////////////////////////// - Supports_Interpolation::Supports_Interpolation(SourceSpan pstate, ExpressionObj v) - : SupportsCondition(pstate), value_(v) - { } - Supports_Interpolation::Supports_Interpolation(const Supports_Interpolation* ptr) - : SupportsCondition(ptr), - value_(ptr->value_) - { } - - bool Supports_Interpolation::needs_parens(SupportsConditionObj cond) const - { - return false; - } + SupportsDeclaration::SupportsDeclaration( + const SourceSpan& pstate, + Expression* feature, + Expression* value) + : SupportsCondition(pstate), + feature_(feature), + value_(value) + {} ///////////////////////////////////////////////////////////////////////// + // An interpolation condition (e.g. `#{$var}`). ///////////////////////////////////////////////////////////////////////// - IMPLEMENT_AST_OPERATORS(SupportsRule); - IMPLEMENT_AST_OPERATORS(SupportsCondition); - IMPLEMENT_AST_OPERATORS(SupportsOperation); - IMPLEMENT_AST_OPERATORS(SupportsNegation); - IMPLEMENT_AST_OPERATORS(SupportsDeclaration); - IMPLEMENT_AST_OPERATORS(Supports_Interpolation); + SupportsInterpolation::SupportsInterpolation( + const SourceSpan& pstate, + Expression* value) : + SupportsCondition(pstate), + value_(value) + {} ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// diff --git a/src/ast_supports.hpp b/src/ast_supports.hpp index 8b083ebdf4..487ea45aba 100644 --- a/src/ast_supports.hpp +++ b/src/ast_supports.hpp @@ -1,121 +1,107 @@ -#ifndef SASS_AST_SUPPORTS_H -#define SASS_AST_SUPPORTS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_SUPPORTS_HPP +#define SASS_AST_SUPPORTS_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include "sass/base.h" -#include "ast_fwd_decl.hpp" - -#include "util.hpp" -#include "units.hpp" -#include "context.hpp" -#include "position.hpp" -#include "constants.hpp" -#include "operation.hpp" -#include "position.hpp" -#include "inspect.hpp" -#include "source_map.hpp" -#include "environment.hpp" -#include "error_handling.hpp" -#include "ast_def_macros.hpp" -#include "ast_fwd_decl.hpp" -#include "source_map.hpp" -#include "fn_utils.hpp" - -#include "sass.h" +#include "ast_nodes.hpp" namespace Sass { - //////////////////// - // `@supports` rule. - //////////////////// - class SupportsRule : public ParentStatement { - ADD_PROPERTY(SupportsConditionObj, condition) - public: - SupportsRule(SourceSpan pstate, SupportsConditionObj condition, Block_Obj block = {}); - bool bubbles() override; - ATTACH_AST_OPERATIONS(SupportsRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // The abstract superclass of all Supports conditions. - ////////////////////////////////////////////////////// - class SupportsCondition : public Expression { + ///////////////////////////////////////////////////////////////////////// + class SupportsCondition : public AstNode + { public: - SupportsCondition(SourceSpan pstate); - virtual bool needs_parens(SupportsConditionObj cond) const { return false; } - ATTACH_AST_OPERATIONS(SupportsCondition) - ATTACH_CRTP_PERFORM_METHODS() + // Value constructor + SupportsCondition( + const SourceSpan& pstate); + // Declare up-casting methods + DECLARE_ISA_CASTER(SupportsOperation); + DECLARE_ISA_CASTER(SupportsNegation); + DECLARE_ISA_CASTER(SupportsDeclaration); + DECLARE_ISA_CASTER(SupportsInterpolation); }; - //////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // An operator condition (e.g. `CONDITION1 and CONDITION2`). - //////////////////////////////////////////////////////////// - class SupportsOperation : public SupportsCondition { + ///////////////////////////////////////////////////////////////////////// + class SupportsOperation final : public SupportsCondition + { public: enum Operand { AND, OR }; private: - ADD_PROPERTY(SupportsConditionObj, left); - ADD_PROPERTY(SupportsConditionObj, right); - ADD_PROPERTY(Operand, operand); + ADD_CONSTREF(SupportsConditionObj, left); + ADD_CONSTREF(SupportsConditionObj, right); + ADD_CONSTREF(Operand, operand); public: - SupportsOperation(SourceSpan pstate, SupportsConditionObj l, SupportsConditionObj r, Operand o); - virtual bool needs_parens(SupportsConditionObj cond) const override; - ATTACH_AST_OPERATIONS(SupportsOperation) - ATTACH_CRTP_PERFORM_METHODS() + // Value constructor + SupportsOperation( + const SourceSpan& pstate, + SupportsConditionObj lhs, + SupportsConditionObj rhs, + Operand operand); + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(SupportsOperation); }; - ////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // A negation condition (`not CONDITION`). - ////////////////////////////////////////// - class SupportsNegation : public SupportsCondition { + ///////////////////////////////////////////////////////////////////////// + class SupportsNegation final : public SupportsCondition + { private: - ADD_PROPERTY(SupportsConditionObj, condition); + ADD_CONSTREF(SupportsConditionObj, condition); public: - SupportsNegation(SourceSpan pstate, SupportsConditionObj c); - virtual bool needs_parens(SupportsConditionObj cond) const override; - ATTACH_AST_OPERATIONS(SupportsNegation) - ATTACH_CRTP_PERFORM_METHODS() + // Value constructor + SupportsNegation( + const SourceSpan& pstate, + SupportsCondition* condition); + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(SupportsNegation); }; - ///////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // A declaration condition (e.g. `(feature: value)`). - ///////////////////////////////////////////////////// - class SupportsDeclaration : public SupportsCondition { + ///////////////////////////////////////////////////////////////////////// + class SupportsDeclaration final : public SupportsCondition + { private: - ADD_PROPERTY(ExpressionObj, feature); - ADD_PROPERTY(ExpressionObj, value); + ADD_CONSTREF(ExpressionObj, feature); + ADD_CONSTREF(ExpressionObj, value); public: - SupportsDeclaration(SourceSpan pstate, ExpressionObj f, ExpressionObj v); - virtual bool needs_parens(SupportsConditionObj cond) const override; - ATTACH_AST_OPERATIONS(SupportsDeclaration) - ATTACH_CRTP_PERFORM_METHODS() + // Value constructor + SupportsDeclaration( + const SourceSpan& pstate, + Expression* feature, + Expression* value); + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(SupportsDeclaration); }; - /////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // An interpolation condition (e.g. `#{$var}`). - /////////////////////////////////////////////// - class Supports_Interpolation : public SupportsCondition { + ///////////////////////////////////////////////////////////////////////// + class SupportsInterpolation final : public SupportsCondition { private: - ADD_PROPERTY(ExpressionObj, value); + ADD_CONSTREF(ExpressionObj, value); public: - Supports_Interpolation(SourceSpan pstate, ExpressionObj v); - virtual bool needs_parens(SupportsConditionObj cond) const override; - ATTACH_AST_OPERATIONS(Supports_Interpolation) - ATTACH_CRTP_PERFORM_METHODS() + // Value constructor + SupportsInterpolation( + const SourceSpan& pstate, + Expression* value); + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(SupportsInterpolation); }; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } #endif diff --git a/src/ast_values.cpp b/src/ast_values.cpp index fccb0967a7..1ad037a766 100644 --- a/src/ast_values.cpp +++ b/src/ast_values.cpp @@ -1,881 +1,1146 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "ast_values.hpp" -namespace Sass { +#include "logger.hpp" +#include "fn_utils.hpp" +#include "exceptions.hpp" +#include "dart_helpers.hpp" +#include - void str_rtrim(sass::string& str, const sass::string& delimiters = " \f\n\r\t\v") - { - str.erase( str.find_last_not_of( delimiters ) + 1 ); - } +namespace Sass { ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - PreValue::PreValue(SourceSpan pstate, bool d, bool e, bool i, Type ct) - : Expression(pstate, d, e, i, ct) - { } - PreValue::PreValue(const PreValue* ptr) - : Expression(ptr) - { } + // This should be thread-safe + static std::hash boolHasher; + static std::hash doubleHasher; + static std::hash sizetHasher; + static std::hash stringHasher; ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Value::Value(SourceSpan pstate, bool d, bool e, bool i, Type ct) - : PreValue(pstate, d, e, i, ct) - { } - Value::Value(const Value* ptr) - : PreValue(ptr) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// + CustomError::CustomError( + const SourceSpan& pstate, + const sass::string& msg) : + Value(pstate), + message_(msg) + {} - List::List(SourceSpan pstate, size_t size, enum Sass_Separator sep, bool argl, bool bracket) - : Value(pstate), - Vectorized(size), - separator_(sep), - is_arglist_(argl), - is_bracketed_(bracket), - from_selector_(false) - { concrete_type(LIST); } + CustomError::CustomError(const CustomError* ptr) + : Value(ptr), message_(ptr->message_) + {} - List::List(const List* ptr) - : Value(ptr), - Vectorized(*ptr), - separator_(ptr->separator_), - is_arglist_(ptr->is_arglist_), - is_bracketed_(ptr->is_bracketed_), - from_selector_(ptr->from_selector_) - { concrete_type(LIST); } + ///////////////////////////////////////////////////////////////////////// - size_t List::hash() const + bool CustomError::operator== (const Value& rhs) const { - if (hash_ == 0) { - hash_ = std::hash()(sep_string()); - hash_combine(hash_, std::hash()(is_bracketed())); - for (size_t i = 0, L = length(); i < L; ++i) - hash_combine(hash_, (elements()[i])->hash()); + if (auto right = rhs.isaCustomError()) { + return *this == *right; } - return hash_; + return false; } - void List::set_delayed(bool delayed) + bool CustomError::operator== (const CustomError& rhs) const { - is_delayed(delayed); - // don't set children + return message() == rhs.message(); } - bool List::operator< (const Expression& rhs) const - { - if (auto r = Cast(&rhs)) { - if (length() < r->length()) return true; - if (length() > r->length()) return false; - const auto& left = elements(); - const auto& right = r->elements(); - for (size_t i = 0; i < left.size(); i += 1) { - if (*left[i] < *right[i]) return true; - if (*left[i] == *right[i]) continue; - return false; - } - return false; - } - // compare/sort by type - return type() < rhs.type(); + ///////////////////////////////////////////////////////////////////////// + + void CustomError::accept(ValueVisitor* visitor) { + throw std::runtime_error("CustomError::accept not implemented"); + } + Value* CustomError::accept(ValueVisitor* visitor) { + throw std::runtime_error("CustomError::accept not implemented"); } - bool List::operator== (const Expression& rhs) const + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CustomWarning::CustomWarning( + const SourceSpan& pstate, + const sass::string& msg) : + Value(pstate), + message_(msg) + {} + + CustomWarning::CustomWarning(const CustomWarning* ptr) + : Value(ptr), message_(ptr->message_) + {} + + ///////////////////////////////////////////////////////////////////////// + + bool CustomWarning::operator== (const Value& rhs) const { - if (auto r = Cast(&rhs)) { - if (length() != r->length()) return false; - if (separator() != r->separator()) return false; - if (is_bracketed() != r->is_bracketed()) return false; - for (size_t i = 0, L = length(); i < L; ++i) { - auto rv = r->at(i); - auto lv = this->at(i); - if (!lv && rv) return false; - else if (!rv && lv) return false; - else if (*lv != *rv) return false; - } - return true; + if (auto right = rhs.isaCustomWarning()) { + return *this == *right; } return false; } - size_t List::size() const { - if (!is_arglist_) return length(); - // arglist expects a list of arguments - // so we need to break before keywords - for (size_t i = 0, L = length(); i < L; ++i) { - ExpressionObj obj = this->at(i); - if (Argument* arg = Cast(obj)) { - if (!arg->name().empty()) return i; - } - } - return length(); + bool CustomWarning::operator== (const CustomWarning& rhs) const + { + return message() == rhs.message(); } + ///////////////////////////////////////////////////////////////////////// - ExpressionObj List::value_at_index(size_t i) { - ExpressionObj obj = this->at(i); - if (is_arglist_) { - if (Argument* arg = Cast(obj)) { - return arg->value(); - } else { - return obj; - } - } else { - return obj; - } + void CustomWarning::accept(ValueVisitor* visitor) { + throw std::runtime_error("CustomWarning::accept not implemented"); + } + Value* CustomWarning::accept(ValueVisitor* visitor) { + throw std::runtime_error("CustomWarning::accept not implemented"); } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Map::Map(SourceSpan pstate, size_t size) - : Value(pstate), - Hashed(size) - { concrete_type(MAP); } + Null::Null(const SourceSpan& pstate) + : Value(pstate) + {} - Map::Map(const Map* ptr) - : Value(ptr), - Hashed(*ptr) - { concrete_type(MAP); } - - bool Map::operator< (const Expression& rhs) const - { - if (auto r = Cast(&rhs)) { - if (length() < r->length()) return true; - if (length() > r->length()) return false; - const auto& lkeys = keys(); - const auto& rkeys = r->keys(); - for (size_t i = 0; i < lkeys.size(); i += 1) { - if (*lkeys[i] < *rkeys[i]) return true; - if (*lkeys[i] == *rkeys[i]) continue; - return false; - } - const auto& lvals = values(); - const auto& rvals = r->values(); - for (size_t i = 0; i < lvals.size(); i += 1) { - if (*lvals[i] < *rvals[i]) return true; - if (*lvals[i] == *rvals[i]) continue; - return false; - } - return false; - } - // compare/sort by type - return type() < rhs.type(); + Null::Null(const Null* ptr) + : Value(ptr) + {} + + bool Null::operator== (const Value& rhs) const + { + return rhs.isNull(); } - bool Map::operator== (const Expression& rhs) const + size_t Null::hash() const { - if (auto r = Cast(&rhs)) { - if (length() != r->length()) return false; - for (auto key : keys()) { - auto rv = r->at(key); - auto lv = this->at(key); - if (!lv && rv) return false; - else if (!rv && lv) return false; - else if (*lv != *rv) return false; - } - return true; - } - return false; + return typeid(Null).hash_code(); } - List_Obj Map::to_list(SourceSpan& pstate) { - List_Obj ret = SASS_MEMORY_NEW(List, pstate, length(), SASS_COMMA); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Color::Color( + const SourceSpan& pstate, + double alpha, + const sass::string& disp) : + Value(pstate), + disp_(disp), + a_(alpha) + {} - for (auto key : keys()) { - List_Obj l = SASS_MEMORY_NEW(List, pstate, 2); - l->append(key); - l->append(at(key)); - ret->append(l); + Color::Color(const Color* ptr) + : Value(ptr), + // Reset on copy + // disp_(ptr->disp_), + a_(ptr->a_) + {} + + ///////////////////////////////////////////////////////////////////////// + // Implement value operators for color + ///////////////////////////////////////////////////////////////////////// + + Value* Color::plus(Value* other, Logger& logger, const SourceSpan& pstate) const + { + if (other->isaNumber() || other->isaColor()) { + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " + " + other->inspect() + "\".", + logger, pstate); } + return Value::plus(other, logger, pstate); + } - return ret; + Value* Color::minus(Value* other, Logger& logger, const SourceSpan& pstate) const + { + if (other->isaNumber() || other->isaColor()) { + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " - " + other->inspect() + "\".", + logger, pstate); + } + return Value::minus(other, logger, pstate); } - size_t Map::hash() const + Value* Color::dividedBy(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (hash_ == 0) { - for (auto key : keys()) { - hash_combine(hash_, key->hash()); - hash_combine(hash_, at(key)->hash()); - } + if (other->isaNumber() || other->isaColor()) { + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " / " + other->inspect() + "\".", + logger, pstate); } + return Value::dividedBy(other, logger, pstate); + } - return hash_; + Value* Color::modulo(Value* other, Logger& logger, const SourceSpan& pstate) const + { + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " % " + other->inspect() + "\".", + logger, pstate); } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Binary_Expression::Binary_Expression(SourceSpan pstate, - Operand op, ExpressionObj lhs, ExpressionObj rhs) - : PreValue(pstate), op_(op), left_(lhs), right_(rhs), hash_(0) - { } + ColorRgba::ColorRgba( + const SourceSpan& pstate, + double red, + double green, + double blue, + double alpha, + const sass::string& disp) : + Color(pstate, alpha, disp), + r_(red), + g_(green), + b_(blue) + {} + + ColorRgba::ColorRgba(const ColorRgba* ptr) + : Color(ptr), + r_(ptr->r_), + g_(ptr->g_), + b_(ptr->b_) + {} - Binary_Expression::Binary_Expression(const Binary_Expression* ptr) - : PreValue(ptr), - op_(ptr->op_), - left_(ptr->left_), - right_(ptr->right_), - hash_(ptr->hash_) - { } + ///////////////////////////////////////////////////////////////////////// - bool Binary_Expression::is_left_interpolant(void) const + bool ColorRgba::operator== (const Value& rhs) const { - return is_interpolant() || (left() && left()->is_left_interpolant()); + if (const Color* color = rhs.isaColor()) { + ColorRgba* rgba = color->toRGBA(); + return *this == *rgba; + } + return false; } - bool Binary_Expression::is_right_interpolant(void) const + + bool ColorRgba::operator== (const ColorRgba& rhs) const { - return is_interpolant() || (right() && right()->is_right_interpolant()); + return r_ == rhs.r() && + g_ == rhs.g() && + b_ == rhs.b() && + a_ == rhs.a(); } - const sass::string Binary_Expression::type_name() + size_t ColorRgba::hash() const { - return sass_op_to_name(optype()); + if (hash_ == 0) { + hash_start(hash_, typeid(ColorRgba).hash_code()); + hash_combine(hash_, doubleHasher(a_)); + hash_combine(hash_, doubleHasher(r_)); + hash_combine(hash_, doubleHasher(g_)); + hash_combine(hash_, doubleHasher(b_)); + } + return hash_; } - const sass::string Binary_Expression::separator() + ///////////////////////////////////////////////////////////////////////// + + ColorHsla* ColorRgba::copyAsHSLA() const { - return sass_op_separator(optype()); + + // Algorithm from http://en.wikipedia.org/wiki/wHSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + double r = r_ / 255.0; + double g = g_ / 255.0; + double b = b_ / 255.0; + + double max = std::max(r, std::max(g, b)); + double min = std::min(r, std::min(g, b)); + double delta = max - min; + + double h = 0; + double s; + double l = (max + min) / 2.0; + + if (NEAR_EQUAL(max, min)) { + h = s = 0; // achromatic + } + else { + if (l < 0.5) s = delta / (max + min); + else s = delta / (2.0 - max - min); + + if (r == max) h = (g - b) / delta + (g < b ? 6 : 0); + else if (g == max) h = (b - r) / delta + 2; + else if (b == max) h = (r - g) / delta + 4; + } + + // HSL hsl_struct; + h = h * 60; + s = s * 100; + l = l * 100; + + return SASS_MEMORY_NEW(ColorHsla, + pstate(), h, s, l, a(), "" + ); } - bool Binary_Expression::has_interpolant() const + ColorHwba* ColorRgba::copyAsHWBA() const { - return is_left_interpolant() || - is_right_interpolant(); + + // Algorithm from http://en.wikipedia.org/wiki/wHSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + double r = r_ / 255.0; + double g = g_ / 255.0; + double b = b_ / 255.0; + + double max = std::max(r, std::max(g, b)); + double min = std::min(r, std::min(g, b)); + double delta = max - min; + + double h = 0; + + if (NEAR_EQUAL(max, min)) { + h = 0; // achromatic + } + else { + if (r == max) h = (g - b) / delta + (g < b ? 6 : 0); + else if (g == max) h = (b - r) / delta + 2; + else if (b == max) h = (r - g) / delta + 4; + } + + double _w = std::min(r, std::min(g, b)); + double _b = 1.0 - std::max(r, std::max(g, b)); + + // HSL hsl_struct; + h = h * 60; + _w *= 100; + _b *= 100; + + return SASS_MEMORY_NEW(ColorHwba, pstate_, h, _w, _b, a_); + } - void Binary_Expression::set_delayed(bool delayed) + ColorHsla* ColorRgba::toHSLA() const { - right()->set_delayed(delayed); - left()->set_delayed(delayed); - is_delayed(delayed); + return copyAsHSLA(); } - bool Binary_Expression::operator<(const Expression& rhs) const + ColorHwba* ColorRgba::toHWBA() const { - if (auto m = Cast(&rhs)) { - return type() < m->type() || - *left() < *m->left() || - *right() < *m->right(); - } - // compare/sort by type - return type() < rhs.type(); + return copyAsHWBA(); } - bool Binary_Expression::operator==(const Expression& rhs) const + ColorRgba* ColorRgba::copyAsRGBA() const { - if (auto m = Cast(&rhs)) { - return type() == m->type() && - *left() == *m->left() && - *right() == *m->right(); - } - return false; + return SASS_MEMORY_COPY(this); } - size_t Binary_Expression::hash() const + ColorRgba* ColorRgba::toRGBA() const { - if (hash_ == 0) { - hash_ = std::hash()(optype()); - hash_combine(hash_, left()->hash()); - hash_combine(hash_, right()->hash()); - } - return hash_; + // This is safe, I know what I do! + return const_cast(this); } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Function::Function(SourceSpan pstate, Definition_Obj def, bool css) - : Value(pstate), definition_(def), is_css_(css) - { concrete_type(FUNCTION_VAL); } + ColorHsla::ColorHsla( + const SourceSpan& pstate, + double hue, + double saturation, + double lightness, + double alpha, + const sass::string& disp) : + Color(pstate, alpha, disp), + h_(absmod(hue, 360.0)), + s_(clamp(saturation, 0.0, 100.0)), + l_(clamp(lightness, 0.0, 100.0)) + {} + + ColorHsla::ColorHsla(const ColorHsla* ptr) + : Color(ptr), + h_(ptr->h_), + s_(ptr->s_), + l_(ptr->l_) + {} - Function::Function(const Function* ptr) - : Value(ptr), definition_(ptr->definition_), is_css_(ptr->is_css_) - { concrete_type(FUNCTION_VAL); } + ///////////////////////////////////////////////////////////////////////// - bool Function::operator< (const Expression& rhs) const + bool ColorHsla::operator== (const Value& rhs) const { - if (auto r = Cast(&rhs)) { - auto d1 = Cast(definition()); - auto d2 = Cast(r->definition()); - if (d1 == nullptr) return d2 != nullptr; - else if (d2 == nullptr) return false; - if (is_css() == r->is_css()) { - return d1 < d2; - } - return r->is_css(); + if (const Color* color = rhs.isaColor()) { + ColorHsla* hsla = color->toHSLA(); + return *this == *hsla; } - // compare/sort by type - return type() < rhs.type(); + return false; } - bool Function::operator== (const Expression& rhs) const + bool ColorHsla::operator== (const ColorHsla& rhs) const { - if (auto r = Cast(&rhs)) { - auto d1 = Cast(definition()); - auto d2 = Cast(r->definition()); - return d1 && d2 && d1 == d2 && is_css() == r->is_css(); - } - return false; + return h_ == rhs.h() && + s_ == rhs.s() && + l_ == rhs.l() && + a_ == rhs.a(); } - sass::string Function::name() { - if (definition_) { - return definition_->name(); + size_t ColorHsla::hash() const + { + if (hash_ == 0) { + hash_start(hash_, typeid(ColorHsla).hash_code()); + hash_combine(hash_, doubleHasher(a_)); + hash_combine(hash_, doubleHasher(h_)); + hash_combine(hash_, doubleHasher(s_)); + hash_combine(hash_, doubleHasher(l_)); } - return ""; + return hash_; } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Function_Call::Function_Call(SourceSpan pstate, String_Obj n, Arguments_Obj args, void* cookie) - : PreValue(pstate), sname_(n), arguments_(args), func_(), via_call_(false), cookie_(cookie), hash_(0) - { concrete_type(FUNCTION); } - Function_Call::Function_Call(SourceSpan pstate, String_Obj n, Arguments_Obj args, Function_Obj func) - : PreValue(pstate), sname_(n), arguments_(args), func_(func), via_call_(false), cookie_(0), hash_(0) - { concrete_type(FUNCTION); } - Function_Call::Function_Call(SourceSpan pstate, String_Obj n, Arguments_Obj args) - : PreValue(pstate), sname_(n), arguments_(args), via_call_(false), cookie_(0), hash_(0) - { concrete_type(FUNCTION); } - - Function_Call::Function_Call(SourceSpan pstate, sass::string n, Arguments_Obj args, void* cookie) - : PreValue(pstate), sname_(SASS_MEMORY_NEW(String_Constant, pstate, n)), arguments_(args), func_(), via_call_(false), cookie_(cookie), hash_(0) - { concrete_type(FUNCTION); } - Function_Call::Function_Call(SourceSpan pstate, sass::string n, Arguments_Obj args, Function_Obj func) - : PreValue(pstate), sname_(SASS_MEMORY_NEW(String_Constant, pstate, n)), arguments_(args), func_(func), via_call_(false), cookie_(0), hash_(0) - { concrete_type(FUNCTION); } - Function_Call::Function_Call(SourceSpan pstate, sass::string n, Arguments_Obj args) - : PreValue(pstate), sname_(SASS_MEMORY_NEW(String_Constant, pstate, n)), arguments_(args), via_call_(false), cookie_(0), hash_(0) - { concrete_type(FUNCTION); } + ColorHwba::ColorHwba( + const SourceSpan& pstate, + double hue, + double whiteness, + double blackness, + double alpha, + const sass::string& disp) : + Color(pstate, alpha, disp), + h_(absmod(hue, 360.0)), + w_(clamp(whiteness, 0.0, 100.0)), + b_(clamp(blackness, 0.0, 100.0)) + {} + + ColorHwba::ColorHwba(const ColorHwba* ptr) + : Color(ptr), + h_(ptr->h_), + w_(ptr->w_), + b_(ptr->b_) + {} - Function_Call::Function_Call(const Function_Call* ptr) - : PreValue(ptr), - sname_(ptr->sname_), - arguments_(ptr->arguments_), - func_(ptr->func_), - via_call_(ptr->via_call_), - cookie_(ptr->cookie_), - hash_(ptr->hash_) - { concrete_type(FUNCTION); } + ///////////////////////////////////////////////////////////////////////// - bool Function_Call::operator==(const Expression& rhs) const + bool ColorHwba::operator== (const Value& rhs) const { - if (auto m = Cast(&rhs)) { - if (*sname() != *m->sname()) return false; - if (arguments()->length() != m->arguments()->length()) return false; - for (size_t i = 0, L = arguments()->length(); i < L; ++i) - if (*arguments()->get(i) != *m->arguments()->get(i)) return false; - return true; + if (const Color* color = rhs.isaColor()) { + ColorHwba* hwba = color->toHWBA(); + return *this == *hwba; } return false; } - size_t Function_Call::hash() const + bool ColorHwba::operator== (const ColorHwba& rhs) const + { + return h_ == rhs.h() && + w_ == rhs.w() && + b_ == rhs.b() && + a_ == rhs.a(); + } + + size_t ColorHwba::hash() const { if (hash_ == 0) { - hash_ = std::hash()(name()); - for (auto argument : arguments()->elements()) - hash_combine(hash_, argument->hash()); + hash_start(hash_, typeid(ColorHsla).hash_code()); + hash_combine(hash_, doubleHasher(a_)); + hash_combine(hash_, doubleHasher(h_)); + hash_combine(hash_, doubleHasher(w_)); + hash_combine(hash_, doubleHasher(b_)); } return hash_; } - sass::string Function_Call::name() const + + ColorHwba* ColorHwba::copyAsHWBA() const { - return sname(); + return SASS_MEMORY_COPY(this); } - bool Function_Call::is_css() { - if (func_) return func_->is_css(); - return false; + ColorRgba* ColorHwba::copyAsRGBA() const + { + double h = h_ / 360.0; + double wh = w_ / 100.0; + double bl = b_ / 100.0; + double ratio = wh + bl; + double v, f, n; + if (ratio > 1) { + wh /= ratio; + bl /= ratio; + } + int i = (int)floor(6.0 * h); + v = 1.0 - bl; + f = 6.0 * h - i; + if ((i & 1) != 0) { + f = 1 - f; + } + n = wh + f * (v - wh); + double r, g, b; + switch (i) { + default: + case 6: + case 0: r = v; g = n; b = wh; break; + case 1: r = n; g = v; b = wh; break; + case 2: r = wh; g = v; b = n; break; + case 3: r = wh; g = n; b = v; break; + case 4: r = n; g = wh; b = v; break; + case 5: r = v; g = wh; b = n; break; + } + return SASS_MEMORY_NEW(ColorRgba, + pstate_, r * 255.0, g * 255.0, b * 255.0, a_); } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Variable::Variable(SourceSpan pstate, sass::string n) - : PreValue(pstate), name_(n) - { concrete_type(VARIABLE); } + ColorHsla* ColorHwba::copyAsHSLA() const + { + ColorRgbaObj rgba(copyAsRGBA()); + return rgba->copyAsHSLA(); + } - Variable::Variable(const Variable* ptr) - : PreValue(ptr), name_(ptr->name_) - { concrete_type(VARIABLE); } + ColorHsla* ColorHwba::toHSLA() const + { + return copyAsHSLA(); + } - bool Variable::operator==(const Expression& rhs) const + ColorHwba* ColorHwba::toHWBA() const { - if (auto e = Cast(&rhs)) { - return name() == e->name(); - } - return false; + return const_cast(this);; } - size_t Variable::hash() const + ColorRgba* ColorHwba::toRGBA() const { - return std::hash()(name()); + return copyAsRGBA(); } - ///////////////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////////////// - Number::Number(SourceSpan pstate, double val, sass::string u, bool zero) - : Value(pstate), - Units(), - value_(val), - zero_(zero), - hash_(0) + // hue to RGB helper function + double h_to_rgb(double m1, double m2, double h) { - size_t l = 0; - size_t r; - if (!u.empty()) { - bool nominator = true; - while (true) { - r = u.find_first_of("*/", l); - sass::string unit(u.substr(l, r == sass::string::npos ? r : r - l)); - if (!unit.empty()) { - if (nominator) numerators.push_back(unit); - else denominators.push_back(unit); - } - if (r == sass::string::npos) break; - // ToDo: should error for multiple slashes - // if (!nominator && u[r] == '/') error(...) - if (u[r] == '/') - nominator = false; - // strange math parsing? - // else if (u[r] == '*') - // nominator = true; - l = r + 1; - } - } - concrete_type(NUMBER); + h = absmod(h, 1.0); + if (h * 6.0 < 1) return m1 + (m2 - m1) * h * 6; + if (h * 2.0 < 1) return m2; + if (h * 3.0 < 2) return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6; + return m1; } - Number::Number(const Number* ptr) - : Value(ptr), - Units(ptr), - value_(ptr->value_), zero_(ptr->zero_), - hash_(ptr->hash_) - { concrete_type(NUMBER); } + ColorRgba* ColorHsla::copyAsRGBA() const + { + double h = absmod(h_ / 360.0, 1.0); + double s = clamp(s_ / 100.0, 0.0, 1.0); + double l = clamp(l_ / 100.0, 0.0, 1.0); - // cancel out unnecessary units - void Number::reduce() + // Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color. + double m2; + if (l <= 0.5) m2 = l * (s + 1.0); + else m2 = (l + s) - (l * s); + double m1 = (l * 2.0) - m2; + // round the results -- consider moving this into the Color constructor + double r = (h_to_rgb(m1, m2, h + 1.0 / 3.0) * 255.0); + double g = (h_to_rgb(m1, m2, h) * 255.0); + double b = (h_to_rgb(m1, m2, h - 1.0 / 3.0) * 255.0); + + return SASS_MEMORY_NEW(ColorRgba, + pstate(), r, g, b, a(), "" + ); + } + + ColorHwba* ColorHsla::copyAsHWBA() const { - // apply conversion factor - value_ *= this->Units::reduce(); + ColorRgbaObj rgba(copyAsRGBA()); + return rgba->copyAsHWBA(); + + throw std::runtime_error("invalid"); + return nullptr; } - void Number::normalize() + ColorHsla* ColorHsla::copyAsHSLA() const { - // apply conversion factor - value_ *= this->Units::normalize(); + return SASS_MEMORY_COPY(this); } - size_t Number::hash() const + ColorRgba* ColorHsla::toRGBA() const { - if (hash_ == 0) { - hash_ = std::hash()(value_); - for (const auto& numerator : numerators) - hash_combine(hash_, std::hash()(numerator)); - for (const auto& denominator : denominators) - hash_combine(hash_, std::hash()(denominator)); - } - return hash_; + return copyAsRGBA(); } - bool Number::operator< (const Expression& rhs) const + ColorHwba* ColorHsla::toHWBA() const { - if (auto n = Cast(&rhs)) { - return *this < *n; + return copyAsHWBA(); + } + + ColorHsla* ColorHsla::toHSLA() const + { + // This is safe, I know what I do! + return const_cast(this); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + Number::Number( + const SourceSpan& pstate, + double value, + const sass::string& units) : + Value(pstate), + Units(units), + value_(value), + lhsAsSlash_(), + rhsAsSlash_() + {} + + // Copy constructor + Number::Number(const Number* ptr) : + Value(ptr), + Units(ptr), + value_(ptr->value_), + lhsAsSlash_(ptr->lhsAsSlash_), + rhsAsSlash_(ptr->rhsAsSlash_) + {} + + ///////////////////////////////////////////////////////////////////////// + // Implement base value equality comparator + ///////////////////////////////////////////////////////////////////////// + + // Helper to determine if we can work with both numbers directly + bool isSimpleNumberComparison(const Number& lhs, const Number& rhs) + { + // Gather statistics from the units + size_t l_n_units = lhs.numerators.size(); + size_t r_n_units = rhs.numerators.size(); + size_t l_d_units = lhs.denominators.size(); + size_t r_d_units = rhs.denominators.size(); + size_t l_units = l_n_units + l_d_units; + size_t r_units = r_n_units + r_d_units; + + // Old ruby sass behavior (deprecated) + if (l_units == 0) return true; + if (r_units == 0) return true; + + // check if both sides have exactly the same units + if (l_n_units == r_n_units && l_d_units == r_d_units) { + return (lhs.numerators == rhs.numerators) + && (lhs.denominators == rhs.denominators); } + return false; } + // EO isSimpleNumberComparison - bool Number::operator== (const Expression& rhs) const + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool Number::operator== (const Value& rhs) const { - if (auto n = Cast(&rhs)) { - return *this == *n; + if (const Number* number = rhs.isaNumber()) { + return *this == *number; } return false; } bool Number::operator== (const Number& rhs) const { - // unitless or only having one unit are equivalent (3.4) - // therefore we need to reduce the units beforehand - Number l(*this), r(rhs); l.reduce(); r.reduce(); - size_t lhs_units = l.numerators.size() + l.denominators.size(); - size_t rhs_units = r.numerators.size() + r.denominators.size(); - if (!lhs_units || !rhs_units) { - return NEAR_EQUAL(l.value(), r.value()); + if (isUnitless() && rhs.isUnitless()) { + return NEAR_EQUAL(value(), rhs.value()); } - // ensure both have same units + // Ignore units in certain cases + // if (isSimpleNumberComparison(*this, rhs)) { + // return NEAR_EQUAL(value(), rhs.value()); + // } + // Otherwise we need copies + Number l(*this), r(rhs); + // Reduce and normalize + l.reduce(); r.reduce(); l.normalize(); r.normalize(); - Units &lhs_unit = l, &rhs_unit = r; - return lhs_unit == rhs_unit && + // Ensure both have same units + return l.Units::operator==(r) && NEAR_EQUAL(l.value(), r.value()); } - bool Number::operator< (const Number& rhs) const + size_t Number::hash() const { - // unitless or only having one unit are equivalent (3.4) - // therefore we need to reduce the units beforehand - Number l(*this), r(rhs); l.reduce(); r.reduce(); - size_t lhs_units = l.numerators.size() + l.denominators.size(); - size_t rhs_units = r.numerators.size() + r.denominators.size(); - if (!lhs_units || !rhs_units) { - return l.value() < r.value(); - } - // ensure both have same units - l.normalize(); r.normalize(); - Units &lhs_unit = l, &rhs_unit = r; - if (!(lhs_unit == rhs_unit)) { - /* ToDo: do we always get useful backtraces? */ - throw Exception::IncompatibleUnits(rhs, *this); - } - if (lhs_unit == rhs_unit) { - return l.value() < r.value(); - } else { - return lhs_unit < rhs_unit; + if (hash_ == 0) { + hash_start(hash_, doubleHasher(value_)); + for (const auto& numerator : numerators) + hash_combine(hash_, stringHasher(numerator)); + for (const auto& denominator : denominators) + hash_combine(hash_, stringHasher(denominator)); } + return hash_; } ///////////////////////////////////////////////////////////////////////// + // Implement value comparators for number ///////////////////////////////////////////////////////////////////////// - Color::Color(SourceSpan pstate, double a, const sass::string disp) - : Value(pstate), - disp_(disp), a_(a), - hash_(0) - { concrete_type(COLOR); } - - Color::Color(const Color* ptr) - : Value(ptr->pstate()), - // reset on copy - disp_(""), - a_(ptr->a_), - hash_(ptr->hash_) - { concrete_type(COLOR); } - - bool Color::operator< (const Expression& rhs) const + bool Number::greaterThan(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (auto r = Cast(&rhs)) { - return *this < *r; - } - else if (auto r = Cast(&rhs)) { - return *this < *r; - } - else if (auto r = Cast(&rhs)) { - return a_ < r->a(); + if (Number* rhs = other->isaNumber()) { + // Ignore units in certain cases + if (isSimpleNumberComparison(*this, *rhs)) { + return value() > rhs->value(); + } + // Otherwise we need copies + Number l(*this), r(*rhs); + // Reduce and normalize + l.reduce(); r.reduce(); + l.normalize(); r.normalize(); + // Ensure both have same units + if (l.Units::operator==(r)) { + return l.value() > r.value(); + } + // Throw error, unit are incompatible + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Incompatible units " + l.unit() + + " and " + r.unit() + ".", + logger, pstate); } - // compare/sort by type - return type() < rhs.type(); + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " > " + other->inspect() + "\".", + logger, pstate); } + // EO greaterThan - bool Color::operator== (const Expression& rhs) const + bool Number::greaterThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (auto r = Cast(&rhs)) { - return *this == *r; - } - else if (auto r = Cast(&rhs)) { - return *this == *r; - } - else if (auto r = Cast(&rhs)) { - return a_ == r->a(); + if (Number* rhs = other->isaNumber()) { + // Ignore units in certain cases + if (isSimpleNumberComparison(*this, *rhs)) { + return value() >= rhs->value(); + } + // Otherwise we need copies + Number l(*this), r(*rhs); + // Reduce and normalize + l.reduce(); r.reduce(); + l.normalize(); r.normalize(); + // Ensure both have same units + if (l.Units::operator==(r)) { + return l.value() >= r.value(); + } + // Throw error, unit are incompatible + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Incompatible units " + l.unit() + + " and " + r.unit() + ".", + logger, pstate); } - return false; + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " >= " + other->inspect() + "\".", + logger, pstate); } + // EO greaterThanOrEquals - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Color_RGBA::Color_RGBA(SourceSpan pstate, double r, double g, double b, double a, const sass::string disp) - : Color(pstate, a, disp), - r_(r), g_(g), b_(b) - { concrete_type(COLOR); } - - Color_RGBA::Color_RGBA(const Color_RGBA* ptr) - : Color(ptr), - r_(ptr->r_), - g_(ptr->g_), - b_(ptr->b_) - { concrete_type(COLOR); } - - bool Color_RGBA::operator< (const Expression& rhs) const + bool Number::lessThan(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (auto r = Cast(&rhs)) { - if (r_ < r->r()) return true; - if (r_ > r->r()) return false; - if (g_ < r->g()) return true; - if (g_ > r->g()) return false; - if (b_ < r->b()) return true; - if (b_ > r->b()) return false; - if (a_ < r->a()) return true; - if (a_ > r->a()) return false; - return false; // is equal + if (Number* rhs = other->isaNumber()) { + // Ignore units in certain cases + if (isSimpleNumberComparison(*this, *rhs)) { + return value() < rhs->value(); + } + // Otherwise we need copies + Number l(*this), r(*rhs); + // Reduce and normalize + l.reduce(); r.reduce(); + l.normalize(); r.normalize(); + // Ensure both have same units + if (l.Units::operator==(r)) { + return l.value() < r.value(); + } + // Throw error, unit are incompatible + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Incompatible units " + l.unit() + + " and " + r.unit() + ".", + logger, pstate); } - // compare/sort by type - return type() < rhs.type(); + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " < " + other->inspect() + "\".", + logger, pstate); } + // EO lessThan - bool Color_RGBA::operator== (const Expression& rhs) const + bool Number::lessThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (auto r = Cast(&rhs)) { - return r_ == r->r() && - g_ == r->g() && - b_ == r->b() && - a_ == r->a(); + if (Number* rhs = other->isaNumber()) { + // Ignore units in certain cases + if (isSimpleNumberComparison(*this, *rhs)) { + return value() <= rhs->value(); + } + // Otherwise we need copies + Number l(*this), r(*rhs); + // Reduce and normalize + l.reduce(); r.reduce(); + l.normalize(); r.normalize(); + // Ensure both have same units + if (l.Units::operator==(r)) { + return l.value() <= r.value(); + } + // Throw error, unit are incompatible + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Incompatible units " + l.unit() + + " and " + r.unit() + ".", + logger, pstate); } - return false; + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " <= " + other->inspect() + "\".", + logger, pstate); } + // EO lessThanOrEquals + + ///////////////////////////////////////////////////////////////////////// + // Helper functions to do the raw value operations + ///////////////////////////////////////////////////////////////////////// - size_t Color_RGBA::hash() const + // Local functions that implement the value operation + inline double add(double x, double y) { return x + y; } + inline double sub(double x, double y) { return x - y; } + inline double mul(double x, double y) { return x * y; } + inline double div(double x, double y) { return x / y; } + inline double mod(double x, double y) { - if (hash_ == 0) { - hash_ = std::hash()("RGBA"); - hash_combine(hash_, std::hash()(a_)); - hash_combine(hash_, std::hash()(r_)); - hash_combine(hash_, std::hash()(g_)); - hash_combine(hash_, std::hash()(b_)); + if ((x > 0 && y < 0) || (x < 0 && y > 0)) { + double ret = std::fmod(x, y); + return ret ? ret + y : ret; + } + else { + return std::fmod(x, y); } - return hash_; } - Color_HSLA* Color_RGBA::copyAsHSLA() const + ///////////////////////////////////////////////////////////////////////// + // Implement value operators for number + ///////////////////////////////////////////////////////////////////////// + + Value* Number::operate(double (*op)(double, double), const Number& rhs, Logger& logger, const SourceSpan& pstate) const { - // Algorithm from http://en.wikipedia.org/wiki/wHSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV - double r = r_ / 255.0; - double g = g_ / 255.0; - double b = b_ / 255.0; + size_t l_n_units = numerators.size(); + size_t l_d_units = denominators.size(); + size_t r_n_units = rhs.numerators.size(); + size_t r_d_units = rhs.denominators.size(); + size_t l_units = l_n_units + l_d_units; + size_t r_units = r_n_units + r_d_units; - double max = std::max(r, std::max(g, b)); - double min = std::min(r, std::min(g, b)); - double delta = max - min; + double lval = value(); + double rval = rhs.value(); - double h = 0; - double s; - double l = (max + min) / 2.0; + // Catch modulo by zero + if (op == mod && rval == 0) { + return SASS_MEMORY_NEW(Number, pstate, + std::numeric_limits::quiet_NaN()); + } + // Catch division by zero + else if (op == div && rval == 0) { + if (lval) return SASS_MEMORY_NEW(Number, pstate, + std::numeric_limits::infinity()); + else return SASS_MEMORY_NEW(Number, pstate, + std::numeric_limits::quiet_NaN()); + } - if (NEAR_EQUAL(max, min)) { - h = s = 0; // achromatic + // Simplest case with no units + // Just operate on the values + if (r_units == 0) { + if (l_units <= 1) { + Number* copy = SASS_MEMORY_COPY(this); + copy->value(op(lval, rval)); + copy->pstate(pstate); + return copy; + } + } + // Left hand has no unit, so we can just copy + // the units from the right hand side. If units + // are not compatible, op function will throw! + else if (l_units == 0) { + if (r_units == 1) { + Number* copy = SASS_MEMORY_COPY(this); + copy->value(op(lval, rval)); + // Switch units for division + if (op == div) { + copy->numerators = rhs.denominators; + copy->denominators = rhs.numerators; + } + else { + copy->numerators = rhs.numerators; + copy->denominators = rhs.denominators; + } + copy->pstate(pstate); + return copy; + } + } + // Both sides have exactly one unit + // Most used case, so optimize it too! + else if (l_units == 1 && r_units == 1) { + if (numerators == rhs.numerators) { + if (denominators == rhs.denominators) { + Number* copy = SASS_MEMORY_COPY(this); + copy->value(op(lval, rval)); + if (op == div) { + copy->numerators.clear(); + copy->denominators.clear(); + } + else if (op == mul) { + copy->numerators.insert(copy->numerators.end(), + rhs.numerators.begin(), rhs.numerators.end()); + copy->denominators.insert(copy->denominators.end(), + rhs.denominators.begin(), rhs.denominators.end()); + } + copy->pstate(pstate); + return copy; + } + } } - else { - if (l < 0.5) s = delta / (max + min); - else s = delta / (2.0 - max - min); - if (r == max) h = (g - b) / delta + (g < b ? 6 : 0); - else if (g == max) h = (b - r) / delta + 2; - else if (b == max) h = (r - g) / delta + 4; + // Otherwise we go into the generic operation + NumberObj copy = SASS_MEMORY_COPY(this); + + // Move right units for some operations if left has none yet + if (isUnitless() && (op == add || op == sub || op == mod)) { + copy->numerators = rhs.numerators; + copy->denominators = rhs.denominators; } - // HSL hsl_struct; - h = h * 60; - s = s * 100; - l = l * 100; + if (op == mul) { + // Multiply the values + copy->value(op(lval, rval)); + // Add all units for multiplications + copy->numerators.insert(copy->numerators.end(), + rhs.numerators.begin(), rhs.numerators.end()); + copy->denominators.insert(copy->denominators.end(), + rhs.denominators.begin(), rhs.denominators.end()); + // Do logical unit cleanup + copy->reduce(); + } + else if (op == div) { + // Divide the values + copy->value(op(lval, rval)); + // Add reversed units for division + copy->numerators.insert(copy->numerators.end(), + rhs.denominators.begin(), rhs.denominators.end()); + copy->denominators.insert(copy->denominators.end(), + rhs.numerators.begin(), rhs.numerators.end()); + // Do logical unit cleanup + copy->reduce(); + } + else { + // Only needed if at least two units are used + // Can work directly if both sides are equal + Number left(this), right(rhs); + left.reduce(); right.reduce(); + // Get the necessary conversion factor + double f(right.getUnitConvertFactor(left)); + // Returns zero on incompatible units + if (f == 0.0) { + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Incompatible units " + rhs.unit() + + " and " + unit() + ".", + logger, pstate); + } + // Now apply the conversion factor + copy->value(op(lval, right.value() * f)); + } - return SASS_MEMORY_NEW(Color_HSLA, - pstate(), h, s, l, a(), "" - ); + copy->pstate(pstate); + return copy.detach(); } + // EO operate - Color_RGBA* Color_RGBA::copyAsRGBA() const + Value* Number::plus(Value* other, Logger& logger, const SourceSpan& pstate) const { - return SASS_MEMORY_COPY(this); + if (const Number* nr = other->isaNumber()) { + return operate(add, *nr, logger, pstate); + } + if (!other->isaColor()) return Value::plus(other, logger, pstate); + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " + " + other->inspect() + "\".", + logger, pstate); } + // EO plus - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Color_HSLA::Color_HSLA(SourceSpan pstate, double h, double s, double l, double a, const sass::string disp) - : Color(pstate, a, disp), - h_(absmod(h, 360.0)), - s_(clip(s, 0.0, 100.0)), - l_(clip(l, 0.0, 100.0)) - // hash_(0) - { concrete_type(COLOR); } - - Color_HSLA::Color_HSLA(const Color_HSLA* ptr) - : Color(ptr), - h_(ptr->h_), - s_(ptr->s_), - l_(ptr->l_) - // hash_(ptr->hash_) - { concrete_type(COLOR); } - - bool Color_HSLA::operator< (const Expression& rhs) const + Value* Number::minus(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (auto r = Cast(&rhs)) { - if (h_ < r->h()) return true; - if (h_ > r->h()) return false; - if (s_ < r->s()) return true; - if (s_ > r->s()) return false; - if (l_ < r->l()) return true; - if (l_ > r->l()) return false; - if (a_ < r->a()) return true; - if (a_ > r->a()) return false; - return false; // is equal + if (const Number* nr = other->isaNumber()) { + return operate(sub, *nr, logger, pstate); } - // compare/sort by type - return type() < rhs.type(); + if (!other->isaColor()) return Value::minus(other, logger, pstate); + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " - " + other->inspect() + "\".", + logger, pstate); } + // EO minus - bool Color_HSLA::operator== (const Expression& rhs) const + Value* Number::times(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (auto r = Cast(&rhs)) { - return h_ == r->h() && - s_ == r->s() && - l_ == r->l() && - a_ == r->a(); + if (const Number* nr = other->isaNumber()) { + return operate(mul, *nr, logger, pstate); } - return false; + if (!other->isaColor()) return Value::times(other, logger, pstate); + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " * " + other->inspect() + "\".", + logger, pstate); } + // EO times - size_t Color_HSLA::hash() const + Value* Number::modulo(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (hash_ == 0) { - hash_ = std::hash()("HSLA"); - hash_combine(hash_, std::hash()(a_)); - hash_combine(hash_, std::hash()(h_)); - hash_combine(hash_, std::hash()(s_)); - hash_combine(hash_, std::hash()(l_)); + if (const Number* nr = other->isaNumber()) { + return operate(mod, *nr, logger, pstate); } - return hash_; + if (!other->isaColor()) return Value::plus(other, logger, pstate); + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " % " + other->inspect() + "\".", + logger, pstate); } + // EO modulo - // hue to RGB helper function - double h_to_rgb(double m1, double m2, double h) + Value* Number::dividedBy(Value* other, Logger& logger, const SourceSpan& pstate) const { - h = absmod(h, 1.0); - if (h*6.0 < 1) return m1 + (m2 - m1)*h*6; - if (h*2.0 < 1) return m2; - if (h*3.0 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h)*6; - return m1; + if (const Number* nr = other->isaNumber()) { + return operate(div, *nr, logger, pstate); + } + if (!other->isaColor()) return Value::dividedBy(other, logger, pstate); + logger.addFinalStackTrace(pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " / " + other->inspect() + "\".", + logger, pstate); } + // EO dividedBy - Color_RGBA* Color_HSLA::copyAsRGBA() const - { - - double h = absmod(h_ / 360.0, 1.0); - double s = clip(s_ / 100.0, 0.0, 1.0); - double l = clip(l_ / 100.0, 0.0, 1.0); - - // Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color. - double m2; - if (l <= 0.5) m2 = l*(s+1.0); - else m2 = (l+s)-(l*s); - double m1 = (l*2.0)-m2; - // round the results -- consider moving this into the Color constructor - double r = (h_to_rgb(m1, m2, h + 1.0/3.0) * 255.0); - double g = (h_to_rgb(m1, m2, h) * 255.0); - double b = (h_to_rgb(m1, m2, h - 1.0/3.0) * 255.0); + ///////////////////////////////////////////////////////////////////////// + // Implement unary operations for base value class + ///////////////////////////////////////////////////////////////////////// - return SASS_MEMORY_NEW(Color_RGBA, - pstate(), r, g, b, a(), "" - ); + Value* Number::unaryPlus(Logger& logger, const SourceSpan& pstate) const + { + return SASS_MEMORY_COPY(this); } - Color_HSLA* Color_HSLA::copyAsHSLA() const + Value* Number::unaryMinus(Logger& logger, const SourceSpan& pstate) const { - return SASS_MEMORY_COPY(this); + Number* cpy = SASS_MEMORY_COPY(this); + cpy->value(cpy->value() * -1.0); + return cpy; } ///////////////////////////////////////////////////////////////////////// + // Implement number specific assertions ///////////////////////////////////////////////////////////////////////// - Custom_Error::Custom_Error(SourceSpan pstate, sass::string msg) - : Value(pstate), message_(msg) - { concrete_type(C_ERROR); } - - Custom_Error::Custom_Error(const Custom_Error* ptr) - : Value(ptr), message_(ptr->message_) - { concrete_type(C_ERROR); } - - bool Custom_Error::operator< (const Expression& rhs) const + long Number::assertInt(Logger& logger, const sass::string& name) { - if (auto r = Cast(&rhs)) { - return message() < r->message(); + if (fuzzyIsInt(value_, logger.epsilon)) { + return lround(value_); } - // compare/sort by type - return type() < rhs.type(); + SourceSpan span(this->pstate()); + logger.addFinalStackTrace(span); + throw Exception::SassScriptException( + inspect() + " is not an int.", + logger, span, name); } - bool Custom_Error::operator== (const Expression& rhs) const + Number* Number::assertUnitless(Logger& logger, const sass::string& name) { - if (auto r = Cast(&rhs)) { - return message() == r->message(); - } - return false; + if (!hasUnits()) return this; + SourceSpan span(this->pstate()); + logger.addFinalStackTrace(span); + throw Exception::SassScriptException( + "Expected " + inspect() + " to have no units.", + logger, span, name); } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Custom_Warning::Custom_Warning(SourceSpan pstate, sass::string msg) - : Value(pstate), message_(msg) - { concrete_type(C_WARNING); } - - Custom_Warning::Custom_Warning(const Custom_Warning* ptr) - : Value(ptr), message_(ptr->message_) - { concrete_type(C_WARNING); } + Number* Number::assertHasUnits(Logger& logger, const sass::string& unit, const sass::string& name) + { + if (hasUnit(unit)) return this; + SourceSpan span(this->pstate()); + logger.addFinalStackTrace(span); + throw Exception::SassScriptException( + "Expected " + inspect() + " to have unit \"" + unit + "\".", + logger, span, name); + } - bool Custom_Warning::operator< (const Expression& rhs) const + double Number::assertRange(double min, double max, Logger& logger, const sass::string& name) const { - if (auto r = Cast(&rhs)) { - return message() < r->message(); + if (!fuzzyCheckRange(value_, min, max, logger.epsilon)) { + sass::sstream msg; + msg << "Expected " << inspect() << " to be within "; + msg << min << unit() << " and " << max << unit() << "."; + SourceSpan span(this->pstate()); + logger.addFinalStackTrace(span); + throw Exception::SassScriptException( + msg.str(), logger, span, name); } - // compare/sort by type - return type() < rhs.type(); + return value_; } - bool Custom_Warning::operator== (const Expression& rhs) const + ///////////////////////////////////////////////////////////////////////// + // Implement delayed value fetcher + ///////////////////////////////////////////////////////////////////////// + + Value* Number::withoutSlash() { - if (auto r = Cast(&rhs)) { - return message() == r->message(); + if (!hasAsSlash()) return this; + // we are the only holder of this item + // therefore should be safe to alter it + if (this->refcount == 1) { + lhsAsSlash_.clear(); + rhsAsSlash_.clear(); + return this; } - return false; + // Otherwise we need to make a copy first + Number* copy = SASS_MEMORY_COPY(this); + copy->lhsAsSlash_.clear(); + copy->rhsAsSlash_.clear(); + return copy; } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Boolean::Boolean(SourceSpan pstate, bool val) - : Value(pstate), value_(val), - hash_(0) - { concrete_type(BOOLEAN); } + Boolean::Boolean( + const SourceSpan& pstate, + bool value) : + Value(pstate), + value_(value) + {} - Boolean::Boolean(const Boolean* ptr) - : Value(ptr), - value_(ptr->value_), - hash_(ptr->hash_) - { concrete_type(BOOLEAN); } + Boolean::Boolean( + const Boolean* ptr) : + Value(ptr), + value_(ptr->value_) + {} + + ///////////////////////////////////////////////////////////////////////// - bool Boolean::operator< (const Expression& rhs) const + bool Boolean::operator== (const Value& rhs) const { - if (auto r = Cast(&rhs)) { - return (value() < r->value()); + if (auto right = rhs.isaBoolean()) { + return *this == *right; } return false; } - bool Boolean::operator== (const Expression& rhs) const - { - if (auto r = Cast(&rhs)) { - return (value() == r->value()); - } - return false; - } + bool Boolean::operator== (const Boolean& rhs) const + { + return value() == rhs.value(); + } - size_t Boolean::hash() const + size_t Boolean::hash() const { if (hash_ == 0) { - hash_ = std::hash()(value_); + hash_ = boolHasher(value_); } return hash_; } @@ -883,270 +1148,348 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - String::String(SourceSpan pstate, bool delayed) - : Value(pstate, delayed) - { concrete_type(STRING); } - String::String(const String* ptr) - : Value(ptr) - { concrete_type(STRING); } + // Value constructor + String::String( + const SourceSpan& pstate, + const char* value, + bool hasQuotes) : + Value(pstate), + value_(value), + hasQuotes_(hasQuotes) + {} + + String::String( + const SourceSpan& pstate, + sass::string&& value, + bool hasQuotes) : + Value(pstate), + value_(std::move(value)), + hasQuotes_(hasQuotes) + {} + + String::String(const String* ptr) : + Value(ptr), + value_(ptr->value_), + hasQuotes_(ptr->hasQuotes_) + {} ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - String_Schema::String_Schema(SourceSpan pstate, size_t size, bool css) - : String(pstate), Vectorized(size), css_(css), hash_(0) - { concrete_type(STRING); } - - String_Schema::String_Schema(const String_Schema* ptr) - : String(ptr), - Vectorized(*ptr), - css_(ptr->css_), - hash_(ptr->hash_) - { concrete_type(STRING); } - - void String_Schema::rtrim() - { - if (!empty()) { - if (String* str = Cast(last())) str->rtrim(); - } - } - bool String_Schema::is_left_interpolant(void) const + bool String::operator== (const Value& rhs) const { - return length() && first()->is_left_interpolant(); - } - bool String_Schema::is_right_interpolant(void) const - { - return length() && last()->is_right_interpolant(); - } - - bool String_Schema::operator< (const Expression& rhs) const - { - if (auto r = Cast(&rhs)) { - if (length() < r->length()) return true; - if (length() > r->length()) return false; - for (size_t i = 0, L = length(); i < L; ++i) { - if (*get(i) < *r->get(i)) return true; - if (*get(i) == *r->get(i)) continue; - return false; - } - // Is equal - return false; - } - // compare/sort by type - return type() < rhs.type(); - } - - bool String_Schema::operator== (const Expression& rhs) const - { - if (auto r = Cast(&rhs)) { - if (length() != r->length()) return false; - for (size_t i = 0, L = length(); i < L; ++i) { - auto rv = (*r)[i]; - auto lv = (*this)[i]; - if (*lv != *rv) return false; - } - return true; + if (auto right = rhs.isaString()) { + return *this == *right; } return false; } - bool String_Schema::has_interpolants() + bool String::operator== (const String& rhs) const { - for (auto el : elements()) { - if (el->is_interpolant()) return true; - } - return false; + return value() == rhs.value(); } - size_t String_Schema::hash() const + size_t String::hash() const { if (hash_ == 0) { - for (auto string : elements()) - hash_combine(hash_, string->hash()); + hash_ = stringHasher(value_); } return hash_; } - void String_Schema::set_delayed(bool delayed) + ///////////////////////////////////////////////////////////////////////// + + Value* String::plus(Value* other, Logger& logger, const SourceSpan& pstate) const { - is_delayed(delayed); + if (const String* str = other->isaString()) { + sass::string text(value() + str->value()); + return SASS_MEMORY_NEW(String, + pstate, std::move(text), hasQuotes()); + } + sass::string text(value() + other->toCss(logger)); + return SASS_MEMORY_NEW(String, + pstate, std::move(text), hasQuotes()); } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - String_Constant::String_Constant(SourceSpan pstate, sass::string val, bool css) - : String(pstate), quote_mark_(0), value_(read_css_string(val, css)), hash_(0) - { } - String_Constant::String_Constant(SourceSpan pstate, const char* beg, bool css) - : String(pstate), quote_mark_(0), value_(read_css_string(sass::string(beg), css)), hash_(0) - { } - String_Constant::String_Constant(SourceSpan pstate, const char* beg, const char* end, bool css) - : String(pstate), quote_mark_(0), value_(read_css_string(sass::string(beg, end-beg), css)), hash_(0) - { } - String_Constant::String_Constant(SourceSpan pstate, const Token& tok, bool css) - : String(pstate), quote_mark_(0), value_(read_css_string(sass::string(tok.begin, tok.end), css)), hash_(0) - { } + Map::Map( + const SourceSpan& pstate, + Hashed::ordered_map_type&& move) : + Value(pstate), + Hashed(std::move(move)) + {} - String_Constant::String_Constant(const String_Constant* ptr) - : String(ptr), - quote_mark_(ptr->quote_mark_), - value_(ptr->value_), - hash_(ptr->hash_) - { } + Map::Map(const Map* ptr) : + Value(ptr), + Hashed(*ptr) + {} - bool String_Constant::is_invisible() const { - return value_.empty() && quote_mark_ == 0; - } + ///////////////////////////////////////////////////////////////////////// - bool String_Constant::operator< (const Expression& rhs) const + // Maps are equal if they have the same items + // at the same key, order is not important. + bool Map::operator== (const Value& rhs) const { - if (auto qstr = Cast(&rhs)) { - return value() < qstr->value(); + if (const Map* right = rhs.isaMap()) { + return *this == *right; } - else if (auto cstr = Cast(&rhs)) { - return value() < cstr->value(); + if (const List* right = rhs.isaList()) { + return right->empty() && empty(); } - // compare/sort by type - return type() < rhs.type(); + return false; } - bool String_Constant::operator== (const Expression& rhs) const + // Maps are equal if they have the same items + // at the same key, order is not important. + bool Map::operator== (const Map& rhs) const { - if (auto qstr = Cast(&rhs)) { - return value() == qstr->value(); - } - else if (auto cstr = Cast(&rhs)) { - return value() == cstr->value(); + if (size() != rhs.size()) return false; + for (auto kv : elements_) { + auto lv = kv.second; + auto rv = rhs.at(kv.first); + return ObjEqualityFn(lv, rv); } - return false; + return true; } - sass::string String_Constant::inspect() const + size_t Map::hash() const { - return quote(value_, '*'); + if (Hashed::hash_ == 0) { + hash_start(Value::hash_, typeid(Map).hash_code()); + hash_combine(Value::hash_, Hashed::hash()); + } + return Value::hash_; } - void String_Constant::rtrim() - { - str_rtrim(value_); + ///////////////////////////////////////////////////////////////////////// + + // Search the position of the given value + size_t Map::indexOf(Value* value) + { + if (List* list = value->isaList()) { + if (list->size() == 2) { + Value* key = list->get(0); + Value* val = list->get(1); + size_t idx = 0; + for (auto kv : elements_) { + if (*kv.first == *key) { + if (*kv.second == *val) { + return idx; + } + } + ++idx; + } + } + } + return NPOS; } - size_t String_Constant::hash() const + // Return list with two items (key and value) + Value* Map::getPairAsList(size_t idx) { - if (hash_ == 0) { - hash_ = std::hash()(value_); + auto kv = elements_.begin() + idx; + // ToDo: really can't re-use memory? + if (false && itpair->size() == 2) { + itpair->get(0) = kv->first; + itpair->get(1) = kv->second; } - return hash_; + else { + itpair = SASS_MEMORY_NEW( + List, pstate(), + { kv->first, kv->second }, + SASS_SPACE); + } + return itpair.detach(); } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - String_Quoted::String_Quoted(SourceSpan pstate, sass::string val, char q, - bool keep_utf8_escapes, bool skip_unquoting, - bool strict_unquoting, bool css) - : String_Constant(pstate, val, css) - { - if (skip_unquoting == false) { - value_ = unquote(value_, "e_mark_, keep_utf8_escapes, strict_unquoting); - } - if (q && quote_mark_) quote_mark_ = q; - } + List::List( + const SourceSpan& pstate, + const ValueVector& values, + SassSeparator separator, + bool hasBrackets) : + Value(pstate), + Vectorized(values), + separator_(separator), + hasBrackets_(hasBrackets) + {} + + List::List(const SourceSpan& pstate, + ValueVector&& values, + SassSeparator separator, + bool hasBrackets) : + Value(pstate), + Vectorized(std::move(values)), + separator_(separator), + hasBrackets_(hasBrackets) + {} + + List::List( + const List* ptr) : + Value(ptr), + Vectorized(ptr), + separator_(ptr->separator_), + hasBrackets_(ptr->hasBrackets_) + {} - String_Quoted::String_Quoted(const String_Quoted* ptr) - : String_Constant(ptr) - { } + ///////////////////////////////////////////////////////////////////////// - bool String_Quoted::operator< (const Expression& rhs) const + bool List::operator==(const Value& rhs) const { - if (auto qstr = Cast(&rhs)) { - return value() < qstr->value(); + if (const List* right = rhs.isaList()) { + return *this == *right; } - else if (auto cstr = Cast(&rhs)) { - return value() < cstr->value(); + if (const Map* right = rhs.isaMap()) { + return empty() && right->empty(); } - // compare/sort by type - return type() < rhs.type(); + return false; } - bool String_Quoted::operator== (const Expression& rhs) const + bool List::operator==(const List& rhs) const { - if (auto qstr = Cast(&rhs)) { - return value() == qstr->value(); + if (size() != rhs.size()) return false; + if (separator() != rhs.separator()) return false; + if (hasBrackets() != rhs.hasBrackets()) return false; + for (size_t i = 0, L = size(); i < L; ++i) { + auto rv = rhs.get(i); + auto lv = this->get(i); + if (!lv && rv) return false; + else if (!rv && lv) return false; + else if (!(*lv == *rv)) return false; } - else if (auto cstr = Cast(&rhs)) { - return value() == cstr->value(); + return true; + } + + size_t List::hash() const + { + if (Vectorized::hash_ == 0) { + hash_start(Value::hash_, typeid(List).hash_code()); + hash_combine(Value::hash_, Vectorized::hash()); + hash_combine(Value::hash_, sizetHasher(separator())); + hash_combine(Value::hash_, boolHasher(hasBrackets())); } - return false; + return Value::hash_; } - sass::string String_Quoted::inspect() const + ///////////////////////////////////////////////////////////////////////// + + Map* List::assertMap(Logger& logger, const sass::string& name) { - return quote(value_, '*'); + if (!empty()) { return Value::assertMap(logger, name); } + else { return SASS_MEMORY_NEW(Map, pstate()); } } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Null::Null(SourceSpan pstate) - : Value(pstate) - { concrete_type(NULL_VAL); } + ArgumentList::ArgumentList( + const SourceSpan& pstate, + SassSeparator separator, + const ValueVector& values, + const ValueFlatMap& keywords) : + List(pstate, + values, + separator, + false), + _keywords(keywords), + _wereKeywordsAccessed(false) + {} + + ArgumentList::ArgumentList( + const SourceSpan& pstate, + SassSeparator separator, + ValueVector&& values, + ValueFlatMap&& keywords) : + List(pstate, + std::move(values), + separator, + false), + _keywords(std::move(keywords)), + _wereKeywordsAccessed(false) + {} + + ArgumentList::ArgumentList( + const ArgumentList* ptr) : + List(ptr), + _keywords(ptr->_keywords), + _wereKeywordsAccessed(ptr->_wereKeywordsAccessed) + {} - Null::Null(const Null* ptr) : Value(ptr) - { concrete_type(NULL_VAL); } + ///////////////////////////////////////////////////////////////////////// - bool Null::operator< (const Expression& rhs) const + bool ArgumentList::operator==(const Value& rhs) const { - if (Cast(&rhs)) { - return false; + if (const ArgumentList* right = rhs.isaArgumentList()) { + return *this == *right; } - // compare/sort by type - return type() < rhs.type(); + return List::operator==(rhs); } - bool Null::operator== (const Expression& rhs) const + bool ArgumentList::operator==(const ArgumentList& rhs) const { - return Cast(&rhs) != nullptr; + return _keywords == rhs._keywords; } - size_t Null::hash() const + size_t ArgumentList::hash() const + { + if (Vectorized::hash_ == 0) { + hash_start(Value::hash_, typeid(ArgumentList).hash_code()); + hash_combine(Value::hash_, Vectorized::hash()); + for (auto child : _keywords) { + hash_combine(Value::hash_, child.first.hash()); + hash_combine(Value::hash_, child.second->hash()); + } + } + return Value::hash_; + } + + ///////////////////////////////////////////////////////////////////////// + + // Convert native string keys to sass strings + Map* ArgumentList::keywordsAsSassMap() const { - return -1; + Map* map = SASS_MEMORY_NEW(Map, pstate()); + for (auto kv : _keywords) { + String* keystr = SASS_MEMORY_NEW( + String, kv.second->pstate(), + sass::string(kv.first.orig())); + map->insert(keystr, kv.second); + } + return map; } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Parent_Reference::Parent_Reference(SourceSpan pstate) - : Value(pstate) - { concrete_type(PARENT); } + Function::Function( + const SourceSpan& pstate, + CallableObj callable) : + Value(pstate), + callable_(callable) + {} - Parent_Reference::Parent_Reference(const Parent_Reference* ptr) - : Value(ptr) - { concrete_type(PARENT); } + Function::Function(const Function* ptr) : + Value(ptr), + callable_(ptr->callable_) + {} - ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - IMPLEMENT_AST_OPERATORS(List); - IMPLEMENT_AST_OPERATORS(Map); - IMPLEMENT_AST_OPERATORS(Binary_Expression); - IMPLEMENT_AST_OPERATORS(Function); - IMPLEMENT_AST_OPERATORS(Function_Call); - IMPLEMENT_AST_OPERATORS(Variable); - IMPLEMENT_AST_OPERATORS(Number); - IMPLEMENT_AST_OPERATORS(Color_RGBA); - IMPLEMENT_AST_OPERATORS(Color_HSLA); - IMPLEMENT_AST_OPERATORS(Custom_Error); - IMPLEMENT_AST_OPERATORS(Custom_Warning); - IMPLEMENT_AST_OPERATORS(Boolean); - IMPLEMENT_AST_OPERATORS(String_Schema); - IMPLEMENT_AST_OPERATORS(String_Constant); - IMPLEMENT_AST_OPERATORS(String_Quoted); - IMPLEMENT_AST_OPERATORS(Null); - IMPLEMENT_AST_OPERATORS(Parent_Reference); + bool Function::operator== (const Value& rhs) const + { + if (const Function* fn = rhs.isaFunction()) { + return *this == *fn; + } + return false; + } + + bool Function::operator== (const Function& rhs) const + { + return ObjEqualityFn(callable_, rhs.callable()); + } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// diff --git a/src/ast_values.hpp b/src/ast_values.hpp index 6a240bfb5a..be9860c58f 100644 --- a/src/ast_values.hpp +++ b/src/ast_values.hpp @@ -1,498 +1,875 @@ -#ifndef SASS_AST_VALUES_H -#define SASS_AST_VALUES_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_VALUES_HPP +#define SASS_AST_VALUES_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "units.hpp" +#include "ast_nodes.hpp" +#include "ast_callables.hpp" namespace Sass { - ////////////////////////////////////////////////////////////////////// - // Still just an expression, but with a to_string method - ////////////////////////////////////////////////////////////////////// - class PreValue : public Expression { - public: - PreValue(SourceSpan pstate, bool d = false, bool e = false, bool i = false, Type ct = NONE); - ATTACH_VIRTUAL_AST_OPERATIONS(PreValue); - virtual ~PreValue() { } - }; + class Compiler; + + ///////////////////////////////////////////////////////////////////////// + // Errors from Sass_Values. + ///////////////////////////////////////////////////////////////////////// + class CustomError final : public Value + { + private: + + ADD_CONSTREF(sass::string, message) - ////////////////////////////////////////////////////////////////////// - // base class for values that support operations - ////////////////////////////////////////////////////////////////////// - class Value : public PreValue { public: - Value(SourceSpan pstate, bool d = false, bool e = false, bool i = false, Type ct = NONE); - // Some obects are not meant to be compared - // ToDo: maybe fallback to pointer comparison? - virtual bool operator< (const Expression& rhs) const override = 0; - virtual bool operator== (const Expression& rhs) const override = 0; + // Value constructor + CustomError( + const SourceSpan& pstate, + const sass::string& message); - // We can give some reasonable implementations by using - // inverst operators on the specialized implementations - virtual bool operator> (const Expression& rhs) const { - return rhs < *this; - } - virtual bool operator!= (const Expression& rhs) const { - return !(*this == rhs); - } + // Copy constructor + CustomError(const CustomError* ptr); + + // Implement interface for base Value class + size_t hash() const override final { return 0; } + enum SassValueType getTag() const override final { return SASS_ERROR; } + const sass::string& getText() const override final { return message_; } + const sass::string& type() const override final { return Strings::error; } - ATTACH_VIRTUAL_AST_OPERATIONS(Value); + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const CustomError& rhs) const; + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final; + Value* accept(ValueVisitor* visitor) override final; + + // Copy operations for childless items + CustomError* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CustomError, this); + } + IMPLEMENT_ISA_CASTER(CustomError); }; - /////////////////////////////////////////////////////////////////////// - // Lists of values, both comma- and space-separated (distinguished by a - // type-tag.) Also used to represent variable-length argument lists. - /////////////////////////////////////////////////////////////////////// - class List : public Value, public Vectorized { - void adjust_after_pushing(ExpressionObj e) override { is_expanded(false); } + ///////////////////////////////////////////////////////////////////////// + // Warnings from Sass_Values. + ///////////////////////////////////////////////////////////////////////// + class CustomWarning final : public Value + { private: - ADD_PROPERTY(enum Sass_Separator, separator) - ADD_PROPERTY(bool, is_arglist) - ADD_PROPERTY(bool, is_bracketed) - ADD_PROPERTY(bool, from_selector) + + ADD_CONSTREF(sass::string, message) + public: - List(SourceSpan pstate, size_t size = 0, enum Sass_Separator sep = SASS_SPACE, bool argl = false, bool bracket = false); - sass::string type() const override { return is_arglist_ ? "arglist" : "list"; } - static sass::string type_name() { return "list"; } - const char* sep_string(bool compressed = false) const { - return separator() == SASS_SPACE ? - " " : (compressed ? "," : ", "); - } - bool is_invisible() const override { return empty() && !is_bracketed(); } - ExpressionObj value_at_index(size_t i); - virtual size_t hash() const override; - virtual size_t size() const; - virtual void set_delayed(bool delayed) override; + // Value constructor + CustomWarning( + const SourceSpan& pstate, + const sass::string& message); + + // Copy constructor + CustomWarning(const CustomWarning* ptr); - virtual bool operator< (const Expression& rhs) const override; - virtual bool operator== (const Expression& rhs) const override; + // Implement interface for base Value class + size_t hash() const override final { return 0; } + enum SassValueType getTag() const override final { return SASS_WARNING; } + const sass::string& getText() const override final { return message_; } + const sass::string& type() const override final { return Strings::warning; } + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const CustomWarning& rhs) const; + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final; + Value* accept(ValueVisitor* visitor) override final; + + // Copy operations for childless items + CustomWarning* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CustomWarning, this); + } - ATTACH_AST_OPERATIONS(List) - ATTACH_CRTP_PERFORM_METHODS() }; /////////////////////////////////////////////////////////////////////// - // Key value paris. + // The null value. /////////////////////////////////////////////////////////////////////// - class Map : public Value, public Hashed { - void adjust_after_pushing(std::pair p) override { is_expanded(false); } + class Null final : public Value + { public: - Map(SourceSpan pstate, size_t size = 0); - sass::string type() const override { return "map"; } - static sass::string type_name() { return "map"; } - bool is_invisible() const override { return empty(); } - List_Obj to_list(SourceSpan& pstate); - virtual size_t hash() const override; + // Value constructor + Null(const SourceSpan& pstate); - virtual bool operator< (const Expression& rhs) const override; - virtual bool operator== (const Expression& rhs) const override; + // Copy constructor + Null(const Null* ptr); - ATTACH_AST_OPERATIONS(Map) - ATTACH_CRTP_PERFORM_METHODS() - }; + // Implement simple checkers for base value class + bool isNull() const override final { return true; } + bool isBlank() const override final { return true; } + bool isTruthy() const override final { return false; } + + // Implement interface for base Value class + size_t hash() const override final; + + enum SassValueType getTag() const override final { return SASS_NULL; } + const sass::string& type() const override final { return Strings::null; } + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitNull(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitNull(this); + } - ////////////////////////////////////////////////////////////////////////// - // Binary expressions. Represents logical, relational, and arithmetic - // operations. Templatized to avoid large switch statements and repetitive - // subclassing. - ////////////////////////////////////////////////////////////////////////// - class Binary_Expression : public PreValue { + // Copy operations for childless items + Null* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(Null, this); + } + }; + + /////////////////////////////////////////////////////////////////////// + // Base class for colors (either rgba or hsla). + /////////////////////////////////////////////////////////////////////// + class Color : public Value + { private: - HASH_PROPERTY(Operand, op) - HASH_PROPERTY(ExpressionObj, left) - HASH_PROPERTY(ExpressionObj, right) - mutable size_t hash_; + + ADD_CONSTREF(sass::string, disp); + ADD_CONSTREF(double, a); + public: - Binary_Expression(SourceSpan pstate, - Operand op, ExpressionObj lhs, ExpressionObj rhs); - const sass::string type_name(); - const sass::string separator(); - bool is_left_interpolant(void) const override; - bool is_right_interpolant(void) const override; - bool has_interpolant() const override; + // Value constructor + Color(const SourceSpan& pstate, + double alpha = 1, + const sass::string& disp = Strings::empty); - virtual void set_delayed(bool delayed) override; + // Copy constructor + Color(const Color* ptr); - virtual bool operator< (const Expression& rhs) const override; - virtual bool operator==(const Expression& rhs) const override; + // Convert and copy only if necessary + virtual ColorRgba* toRGBA() const = 0; + virtual ColorHsla* toHSLA() const = 0; + virtual ColorHwba* toHWBA() const = 0; + // Convert if necessary and return a copy + virtual ColorRgba* copyAsRGBA() const = 0; + virtual ColorHsla* copyAsHSLA() const = 0; + virtual ColorHwba* copyAsHWBA() const = 0; - virtual size_t hash() const override; - enum Sass_OP optype() const { return op_.operand; } - ATTACH_AST_OPERATIONS(Binary_Expression) - ATTACH_CRTP_PERFORM_METHODS() - }; + // Implement interface for base Value class + virtual size_t hash() const override = 0; + enum SassValueType getTag() const override final { return SASS_COLOR; } + const sass::string& type() const override final { return Strings::color; } - //////////////////////////////////////////////////// - // Function reference. - //////////////////////////////////////////////////// - class Function final : public Value { - public: - ADD_PROPERTY(Definition_Obj, definition) - ADD_PROPERTY(bool, is_css) - public: - Function(SourceSpan pstate, Definition_Obj def, bool css); + // Implement some operations for base value class + Value* plus(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* minus(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* dividedBy(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* modulo(Value* other, Logger& logger, const SourceSpan& pstate) const override final; - sass::string type() const override { return "function"; } - static sass::string type_name() { return "function"; } - bool is_invisible() const override { return true; } + // Implement type fetcher for base value class (throws in base implementation) + const Color* assertColor(Logger& logger, const sass::string& name = Strings::empty) const override final { return this; } - sass::string name(); + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitColor(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitColor(this); + } - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; + // This is a very interesting line, as it seems pointless, since the base class + // already marks this as an unimplemented interface methods, but by defining this + // line here, we make sure that callers know the return is a bit more specific. + virtual Color* copy(SASS_MEMORY_ARGS bool childless = false) const override = 0; - ATTACH_AST_OPERATIONS(Function) - ATTACH_CRTP_PERFORM_METHODS() + IMPLEMENT_ISA_CASTER(Color); }; - ////////////////// - // Function calls. - ////////////////// - class Function_Call final : public PreValue { - HASH_CONSTREF(String_Obj, sname) - HASH_PROPERTY(Arguments_Obj, arguments) - HASH_PROPERTY(Function_Obj, func) - ADD_PROPERTY(bool, via_call) - ADD_PROPERTY(void*, cookie) - mutable size_t hash_; + /////////////////////////////////////////////////////////////////////// + // A sass color in RGBA representation. + /////////////////////////////////////////////////////////////////////// + class ColorRgba final : public Color + { + private: + + ADD_CONSTREF(double, r); + ADD_CONSTREF(double, g); + ADD_CONSTREF(double, b); + public: - Function_Call(SourceSpan pstate, sass::string n, Arguments_Obj args, void* cookie); - Function_Call(SourceSpan pstate, sass::string n, Arguments_Obj args, Function_Obj func); - Function_Call(SourceSpan pstate, sass::string n, Arguments_Obj args); - Function_Call(SourceSpan pstate, String_Obj n, Arguments_Obj args, void* cookie); - Function_Call(SourceSpan pstate, String_Obj n, Arguments_Obj args, Function_Obj func); - Function_Call(SourceSpan pstate, String_Obj n, Arguments_Obj args); + // Value constructor + ColorRgba(const SourceSpan& pstate, + double red, double green, double blue, double alpha = 1.0, + const sass::string& disp = Strings::empty); + + // Copy constructor + ColorRgba(const ColorRgba* ptr); + + // Convert and copy only if necessary + ColorRgba* toRGBA() const override final; + ColorHsla* toHSLA() const override final; + ColorHwba* toHWBA() const override final; + // Convert if necessary and return a copy + ColorRgba* copyAsRGBA() const override final; + ColorHsla* copyAsHSLA() const override final; + ColorHwba* copyAsHWBA() const override final; + + // Implement interface for base color class + size_t hash() const override final; + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const ColorRgba& rhs) const; + + // Copy operations for childless items + ColorRgba* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(ColorRgba, this); + } + }; + - sass::string name() const; - bool is_css(); + /////////////////////////////////////////////////////////////////////// + // A sass color in HSLA representation. + /////////////////////////////////////////////////////////////////////// + class ColorHsla final : public Color + { + private: - bool operator==(const Expression& rhs) const override; + ADD_CONSTREF(double, h); + ADD_CONSTREF(double, s); + ADD_CONSTREF(double, l); - size_t hash() const override; + public: - ATTACH_AST_OPERATIONS(Function_Call) - ATTACH_CRTP_PERFORM_METHODS() + // Value constructor + ColorHsla(const SourceSpan& pstate, + double hue, double saturation, double lightness, double alpha = 1, + const sass::string& disp = Strings::empty); + + // Copy constructor + ColorHsla(const ColorHsla* ptr); + + // Convert and copy only if necessary + ColorRgba* toRGBA() const override final; + ColorHsla* toHSLA() const override final; + ColorHwba* toHWBA() const override final; + // Convert if necessary and return a copy + ColorRgba* copyAsRGBA() const override final; + ColorHsla* copyAsHSLA() const override final; + ColorHwba* copyAsHWBA() const override final; + + // Implement interface for base color class + size_t hash() const override final; + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const ColorHsla& rhs) const; + + // Copy operations for childless items + ColorHsla* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(ColorHsla, this); + } }; - /////////////////////// - // Variable references. - /////////////////////// - class Variable final : public PreValue { - ADD_CONSTREF(sass::string, name) + /////////////////////////////////////////////////////////////////////// + // A sass color in HSLA representation. + /////////////////////////////////////////////////////////////////////// + class ColorHwba final : public Color + { + private: + + ADD_CONSTREF(double, h); + ADD_CONSTREF(double, w); + ADD_CONSTREF(double, b); + public: - Variable(SourceSpan pstate, sass::string n); - virtual bool operator==(const Expression& rhs) const override; - virtual size_t hash() const override; - ATTACH_AST_OPERATIONS(Variable) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + ColorHwba(const SourceSpan& pstate, + double hue, double whiteness, double blackness, double alpha = 1, + const sass::string& disp = Strings::empty); + + // Copy constructor + ColorHwba(const ColorHwba* ptr); + + // Convert and copy only if necessary + ColorRgba* toRGBA() const override final; + ColorHsla* toHSLA() const override final; + ColorHwba* toHWBA() const override final; + // Convert if necessary and return a copy + ColorRgba* copyAsRGBA() const override final; + ColorHsla* copyAsHSLA() const override final; + ColorHwba* copyAsHWBA() const override final; + + // Implement interface for base color class + size_t hash() const override final; + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const ColorHwba& rhs) const; + + // Copy operations for childless items + ColorHwba* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(ColorHwba, this); + } }; - //////////////////////////////////////////////// - // Numbers, percentages, dimensions, and colors. - //////////////////////////////////////////////// - class Number final : public Value, public Units { - HASH_PROPERTY(double, value) - ADD_PROPERTY(bool, zero) - mutable size_t hash_; + /////////////////////////////////////////////////////////////////////// + // A sass number with optional units + /////////////////////////////////////////////////////////////////////// + + class Number final : public Value, public Units + { + private: + + ADD_CONSTREF(double, value); + // The representation of this number as two + // slash-separated numbers, if it has one. + ADD_CONSTREF(NumberObj, lhsAsSlash); + ADD_CONSTREF(NumberObj, rhsAsSlash); + public: - Number(SourceSpan pstate, double val, sass::string u = "", bool zero = true); - bool zero() { return zero_; } + // Value constructor + Number( + const SourceSpan& pstate, + double value = 0.0, + const sass::string& units = ""); + + // Copy constructor + Number(const Number* ptr); - sass::string type() const override { return "number"; } - static sass::string type_name() { return "number"; } + // Check if we have delayed value info + bool hasAsSlash() { + return !lhsAsSlash_.isNull() + && !rhsAsSlash_.isNull(); + } + + // Check if number matches [unit] + bool hasUnit(const sass::string& unit) const { + return numerators.size() == 1 && + denominators.empty() && + numerators.front() == unit; + } // cancel out unnecessary units // result will be in input units - void reduce(); + void reduce() + { + // apply conversion factor + value_ *= this->Units::reduce(); + } // normalize units to defaults // needed to compare two numbers - void normalize(); - - size_t hash() const override; + void normalize() + { + // apply conversion factor + value_ *= this->Units::normalize(); + } - bool operator< (const Number& rhs) const; + // Implement delayed value fetcher + Value* withoutSlash() override final; + + // Implement interface for base Value class + size_t hash() const override final; + enum SassValueType getTag() const override final { return SASS_NUMBER; } + const sass::string& type() const override final { return Strings::number; } + + // Implement some comparators for base value class + bool greaterThan(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + bool greaterThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + bool lessThan(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + bool lessThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + + // Implement some operations for base value class + Value* plus(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* minus(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* times(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* modulo(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* dividedBy(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + + // Implement unary operations for base value class + Value* unaryPlus(Logger& logger, const SourceSpan& pstate) const override final; + Value* unaryMinus(Logger& logger, const SourceSpan& pstate) const override final; + + // Implement type fetcher for base value class (throws in base implementation) + Number* assertNumber(Logger& logger, const sass::string& name = Strings::empty) override final { return this; } + + // Implement number specific assertions + long assertInt(Logger& logger, const sass::string& name = Strings::empty); + Number* assertUnitless(Logger& logger, const sass::string& name = Strings::empty); + Number* assertHasUnits(Logger& logger, const sass::string& unit, const sass::string& name = Strings::empty); + double assertRange(double min, double max, Logger& logger, const sass::string& name = Strings::empty) const; + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator bool operator== (const Number& rhs) const; - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; - ATTACH_AST_OPERATIONS(Number) - ATTACH_CRTP_PERFORM_METHODS() + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitNumber(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitNumber(this); + } + + // Copy operations for childless items + Number* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(Number, this); + } + + private: + + Value* operate(double (*op)(double, double), const Number& rhs, Logger& logger, const SourceSpan& pstate) const; + + IMPLEMENT_ISA_CASTER(Number); }; - ////////// - // Colors. - ////////// - class Color : public Value { - ADD_CONSTREF(sass::string, disp) - HASH_PROPERTY(double, a) - protected: - mutable size_t hash_; + /////////////////////////////////////////////////////////////////////// + // A sass boolean (either true or false) + /////////////////////////////////////////////////////////////////////// + + class Boolean final : public Value + { + private: + + ADD_CONSTREF(bool, value) + public: - Color(SourceSpan pstate, double a = 1, const sass::string disp = ""); - sass::string type() const override { return "color"; } - static sass::string type_name() { return "color"; } + // Value constructor + Boolean( + const SourceSpan& pstate, + bool value = false); - virtual size_t hash() const override = 0; + // Copy constructor + Boolean(const Boolean* ptr); - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; + // Implement simple checkers for base value class + bool isTruthy() const override final { return value_; } - virtual Color_RGBA* copyAsRGBA() const = 0; - virtual Color_RGBA* toRGBA() = 0; + // Implement interface for base Value class + size_t hash() const override final; + enum SassValueType getTag() const override final { return SASS_BOOLEAN; } + const sass::string& type() const override final { return Strings::boolean; } - virtual Color_HSLA* copyAsHSLA() const = 0; - virtual Color_HSLA* toHSLA() = 0; + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const Boolean& rhs) const; - ATTACH_VIRTUAL_AST_OPERATIONS(Color) + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitBoolean(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitBoolean(this); + } + + // Copy operations for childless items + Boolean* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(Boolean, this); + } + + IMPLEMENT_ISA_CASTER(Boolean); }; - ////////// - // Colors. - ////////// - class Color_RGBA final : public Color { - HASH_PROPERTY(double, r) - HASH_PROPERTY(double, g) - HASH_PROPERTY(double, b) + /////////////////////////////////////////////////////////////////////// + // A sass string (optionally quoted on rendering) + /////////////////////////////////////////////////////////////////////// + class String final : public Value + { + private: + + ADD_CONSTREF(sass::string, value); + ADD_CONSTREF(bool, hasQuotes); + public: - Color_RGBA(SourceSpan pstate, double r, double g, double b, double a = 1, const sass::string disp = ""); - sass::string type() const override { return "color"; } - static sass::string type_name() { return "color"; } + // Value constructor + String( + const SourceSpan& pstate, + const char* value, + bool hasQuotes = false); + + String( + const SourceSpan& pstate, + sass::string&& value, + bool hasQuotes = false); + + // Copy constructor + String(const String* ptr); + + // Check if value would render empty + bool isBlank() const override final { + if (hasQuotes_) return false; + return value_.empty(); + } + + // Implement interface for base Value class + size_t hash() const override final; + enum SassValueType getTag() const override final { return SASS_STRING; } + const sass::string& getText() const override final { return value_; } + const sass::string& type() const override { return Strings::string; } - size_t hash() const override; + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const String& rhs) const; - Color_RGBA* copyAsRGBA() const override; - Color_RGBA* toRGBA() override { return this; } + // Implement type fetcher for base value class (throws in base implementation) + String* assertString(Logger& logger, const sass::string& name = Strings::empty) override final { return this; } - Color_HSLA* copyAsHSLA() const override; - Color_HSLA* toHSLA() override { return copyAsHSLA(); } + // Implement some operations for base value class + Value* plus(Value* other, Logger& logger, const SourceSpan& pstate) const override final; - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; + // Main entry point for Value Visitor pattern + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitString(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitString(this); + } - ATTACH_AST_OPERATIONS(Color_RGBA) - ATTACH_CRTP_PERFORM_METHODS() + // Copy operations for childless items + String* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(String, this); + } + + IMPLEMENT_ISA_CASTER(String); }; + /////////////////////////////////////////////////////////////////////// + // A sass map (which keeps the insertion order) + /////////////////////////////////////////////////////////////////////// + class Map final : public Value, public Hashed + { + private: + + // Helper for getPairAsList to avoid memory leaks + // Returned by `Values::iterator::operator*()` + ListObj itpair; - ////////// - // Colors. - ////////// - class Color_HSLA final : public Color { - HASH_PROPERTY(double, h) - HASH_PROPERTY(double, s) - HASH_PROPERTY(double, l) public: - Color_HSLA(SourceSpan pstate, double h, double s, double l, double a = 1, const sass::string disp = ""); - sass::string type() const override { return "color"; } - static sass::string type_name() { return "color"; } + // Value constructor + Map( + const SourceSpan& pstate, + Hashed::ordered_map_type&& move = {}); - size_t hash() const override; + // Copy constructor + Map(const Map* ptr); - Color_RGBA* copyAsRGBA() const override; - Color_RGBA* toRGBA() override { return copyAsRGBA(); } + // Return the list separator + SassSeparator separator() const override final { + return empty() ? SASS_UNDEF : SASS_COMMA; + } - Color_HSLA* copyAsHSLA() const override; - Color_HSLA* toHSLA() override { return this; } + // Return the length of this item as a list + size_t lengthAsList() const override { + return size(); + } - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; + // Search the position of the given value + size_t indexOf(Value* value) override final; - ATTACH_AST_OPERATIONS(Color_HSLA) - ATTACH_CRTP_PERFORM_METHODS() - }; + // Return list with two items (key and value) + Value* getPairAsList(size_t idx); - ////////////////////////////// - // Errors from Sass_Values. - ////////////////////////////// - class Custom_Error final : public Value { - ADD_CONSTREF(sass::string, message) - public: - Custom_Error(SourceSpan pstate, sass::string msg); - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; - ATTACH_AST_OPERATIONS(Custom_Error) - ATTACH_CRTP_PERFORM_METHODS() - }; + // Only used for nth sass function + // Doesn't allow overflow of index (throw error) + // Allows negative index but no overflow either + Value* getValueAt(Value* index, Logger& logger) override final; - ////////////////////////////// - // Warnings from Sass_Values. - ////////////////////////////// - class Custom_Warning final : public Value { - ADD_CONSTREF(sass::string, message) - public: - Custom_Warning(SourceSpan pstate, sass::string msg); - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; - ATTACH_AST_OPERATIONS(Custom_Warning) - ATTACH_CRTP_PERFORM_METHODS() - }; + // Implement interface for base Value class + size_t hash() const override final; + enum SassValueType getTag() const override final { return SASS_MAP; } + const sass::string& type() const override final { return Strings::map; } - //////////// - // Booleans. - //////////// - class Boolean final : public Value { - HASH_PROPERTY(bool, value) - mutable size_t hash_; - public: - Boolean(SourceSpan pstate, bool val); - operator bool() override { return value_; } + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const Map& rhs) const; - sass::string type() const override { return "bool"; } - static sass::string type_name() { return "bool"; } + // Implement type fetcher for base value class (throws in base implementation) + Map* assertMap(Logger& logger, const sass::string& name) override { return this; } - size_t hash() const override; + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitMap(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitMap(this); + } + // Copy operations for childless items + Map* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(Map, this); + } - bool is_false() override { return !value_; } + protected: - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; + // Clone all items in-place + Map* cloneChildren(SASS_MEMORY_ARGS_VOID) override final { + for (auto it = begin(); it != end(); ++it) { + it.value() = it.value()->copy(SASS_MEMORY_PARAMS_VOID); + it.value()->cloneChildren(SASS_MEMORY_PARAMS_VOID); + } + return this; + } - ATTACH_AST_OPERATIONS(Boolean) - ATTACH_CRTP_PERFORM_METHODS() + IMPLEMENT_ISA_CASTER(Map); }; - //////////////////////////////////////////////////////////////////////// - // Abstract base class for Sass string values. Includes interpolated and - // "flat" strings. - //////////////////////////////////////////////////////////////////////// - class String : public Value { + /////////////////////////////////////////////////////////////////////// + // Lists of values, both comma- and space-separated (distinguished by a + // type-tag.) Also used to represent variable-length argument lists. + /////////////////////////////////////////////////////////////////////// + + class List : public Value, public Vectorized + { + private: + + enum SassSeparator separator_; + ADD_CONSTREF(bool, hasBrackets); + public: - String(SourceSpan pstate, bool delayed = false); - static sass::string type_name() { return "string"; } - virtual ~String() = 0; - virtual void rtrim() = 0; - virtual bool operator<(const Expression& rhs) const override { - return this->to_string() < rhs.to_string(); - }; - virtual bool operator==(const Expression& rhs) const override { - return this->to_string() == rhs.to_string(); - }; - ATTACH_VIRTUAL_AST_OPERATIONS(String); - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + List(const SourceSpan& pstate, + const ValueVector& values = {}, + enum SassSeparator separator = SASS_SPACE, + bool hasBrackets = false); + + // Value constructor + List(const SourceSpan& pstate, + ValueVector&& values, + enum SassSeparator separator = SASS_SPACE, + bool hasBrackets = false); + + // Copy constructor + List(const List* ptr); + + // Return the list separator + SassSeparator separator() const override final { + return separator_; + } + + // Set the list separator + void separator(SassSeparator separator) { + separator_ = separator; + } + + // Return the length of this item as a list + size_t lengthAsList() const override final { + return size(); + } + + // Check if list has surrounding brackets + bool hasBrackets() override final { + return hasBrackets_; + } + + // Check if value would render empty + bool isBlank() const override final { + for (const Value* value : elements()) { + if (!value->isBlank()) return false; + } + return true; + } + + // Search the position of the given value + size_t indexOf(Value* value) override final; + + // Only used for nth sass function + // Allows negative index but no overflow either + // Doesn't allow overflow of index (throw error) + Value* getValueAt(Value* index, Logger& logger) override final; + + // Implement interface for base Value class + virtual size_t hash() const override; + enum SassValueType getTag() const override final { return SASS_LIST; } + virtual const sass::string& type() const override { return Strings::list; } + + // Implement equality comparators for base value class + virtual bool operator== (const Value& rhs) const override; + // Implement same class compare operator + bool operator== (const List& rhs) const; + + // Implement type fetcher for base value class (throws in base implementation) + Map* assertMap(Logger& logger, const sass::string& name) override final; + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitList(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitList(this); + } + // Copy operations for childless items + List* copy(SASS_MEMORY_ARGS bool childless) const override { + return SASS_MEMORY_NEW_DBG(List, this); + } + + protected: + + // Clone all items in-place + List* cloneChildren(SASS_MEMORY_ARGS_VOID) override { + for (ValueObj& entry : elements_) { + entry = entry->copy(SASS_MEMORY_PARAMS_VOID); + entry->cloneChildren(SASS_MEMORY_PARAMS_VOID); + } + return this; + } + + IMPLEMENT_ISA_CASTER(List); }; - inline String::~String() { }; /////////////////////////////////////////////////////////////////////// - // Interpolated strings. Meant to be reduced to flat strings during the - // evaluation phase. /////////////////////////////////////////////////////////////////////// - class String_Schema final : public String, public Vectorized { - ADD_PROPERTY(bool, css) - mutable size_t hash_; + + class ArgumentList final : public List + { + private: + + ValueFlatMap _keywords; + mutable bool _wereKeywordsAccessed; + public: - String_Schema(SourceSpan pstate, size_t size = 0, bool css = true); - sass::string type() const override { return "string"; } - static sass::string type_name() { return "string"; } + // Value copy constructor + ArgumentList(const SourceSpan& pstate, + SassSeparator sep = SASS_SPACE, + ValueVector&& values = {}, + ValueFlatMap&& keywords = {}); - bool is_left_interpolant(void) const override; - bool is_right_interpolant(void) const override; + // Value move constructor + ArgumentList(const SourceSpan& pstate, + SassSeparator sep = SASS_SPACE, + const ValueVector& values = {}, + const ValueFlatMap& keywords = {}); - bool has_interpolants(); - void rtrim() override; - size_t hash() const override; - virtual void set_delayed(bool delayed) override; + // Copy constructor + ArgumentList(const ArgumentList* ptr); + + ValueFlatMap& keywords() { + _wereKeywordsAccessed = true; + return _keywords; + } + + bool wereKeywordsAccessed() const { + return _wereKeywordsAccessed; + } + + bool hasAllKeywordsConsumed() const { + return _keywords.empty() || + _wereKeywordsAccessed; + } + + Map* keywordsAsSassMap() const; + + // Implement interface for base Value class + size_t hash() const override final; + const sass::string& type() const override final { return Strings::arglist; } + + ArgumentList* assertArgumentList(Logger& logger, const sass::string& name = Strings::empty) override final { + return this; + } - bool operator< (const Expression& rhs) const override; - bool operator==(const Expression& rhs) const override; - ATTACH_AST_OPERATIONS(String_Schema) - ATTACH_CRTP_PERFORM_METHODS() - }; - //////////////////////////////////////////////////////// - // Flat strings -- the lowest level of raw textual data. - //////////////////////////////////////////////////////// - class String_Constant : public String { - ADD_PROPERTY(char, quote_mark) - HASH_CONSTREF(sass::string, value) + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const ArgumentList& rhs) const; + + // Copy operations for childless items + ArgumentList* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(ArgumentList, this); + } + protected: - mutable size_t hash_; - public: - String_Constant(SourceSpan pstate, sass::string val, bool css = true); - String_Constant(SourceSpan pstate, const char* beg, bool css = true); - String_Constant(SourceSpan pstate, const char* beg, const char* end, bool css = true); - String_Constant(SourceSpan pstate, const Token& tok, bool css = true); - sass::string type() const override { return "string"; } - static sass::string type_name() { return "string"; } - bool is_invisible() const override; - virtual void rtrim() override; - size_t hash() const override; - bool operator< (const Expression& rhs) const override; - bool operator==(const Expression& rhs) const override; - // quotes are forced on inspection - virtual sass::string inspect() const override; - ATTACH_AST_OPERATIONS(String_Constant) - ATTACH_CRTP_PERFORM_METHODS() - }; - //////////////////////////////////////////////////////// - // Possibly quoted string (unquote on instantiation) - //////////////////////////////////////////////////////// - class String_Quoted final : public String_Constant { - public: - String_Quoted(SourceSpan pstate, sass::string val, char q = 0, - bool keep_utf8_escapes = false, bool skip_unquoting = false, - bool strict_unquoting = true, bool css = true); - bool operator< (const Expression& rhs) const override; - bool operator==(const Expression& rhs) const override; - // quotes are forced on inspection - sass::string inspect() const override; - ATTACH_AST_OPERATIONS(String_Quoted) - ATTACH_CRTP_PERFORM_METHODS() + // Clone all items in-place + ArgumentList* cloneChildren(SASS_MEMORY_ARGS_VOID) override final { + for (auto it : _keywords) { + it.second = it.second->copy(SASS_MEMORY_PARAMS_VOID); + it.second->cloneChildren(SASS_MEMORY_PARAMS_VOID); + } + return this; + } + + IMPLEMENT_ISA_CASTER(ArgumentList); }; - ////////////////// - // The null value. - ////////////////// - class Null final : public Value { + /////////////////////////////////////////////////////////////////////// + // A sass function reference. + /////////////////////////////////////////////////////////////////////// + class Function final : public Value + { + private: + + ADD_CONSTREF(CallableObj, callable); + public: - Null(SourceSpan pstate); - sass::string type() const override { return "null"; } - static sass::string type_name() { return "null"; } - bool is_invisible() const override { return true; } - operator bool() override { return false; } - bool is_false() override { return true; } - size_t hash() const override; + // Value constructor + Function( + const SourceSpan& pstate, + CallableObj callable); - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; + // Copy constructor + Function(const Function* ptr); - ATTACH_AST_OPERATIONS(Null) - ATTACH_CRTP_PERFORM_METHODS() - }; + // Implement interface for base Value class + size_t hash() const override final { return 0; } + enum SassValueType getTag() const override final { return SASS_FUNCTION; } + const sass::string& type() const override final { return Strings::function; } - ////////////////////////////////// - // The Parent Reference Expression. - ////////////////////////////////// - class Parent_Reference final : public Value { - public: - Parent_Reference(SourceSpan pstate); - sass::string type() const override { return "parent"; } - static sass::string type_name() { return "parent"; } - bool operator< (const Expression& rhs) const override { - return false; // they are always equal - } - bool operator==(const Expression& rhs) const override { - return true; // they are always equal - }; - ATTACH_AST_OPERATIONS(Parent_Reference) - ATTACH_CRTP_PERFORM_METHODS() + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const Function& rhs) const; + + Function* assertFunction(Logger& logger, const sass::string& name = Strings::empty) override final { return this; } + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitFunction(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitFunction(this); + } + // Copy operations for childless items + Function* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(Function, this); + } + + IMPLEMENT_ISA_CASTER(Function); }; + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + } #endif diff --git a/src/b64/cencode.h b/src/b64/cencode.h index 1d71e83fdb..4b71c136a3 100644 --- a/src/b64/cencode.h +++ b/src/b64/cencode.h @@ -8,25 +8,29 @@ For details, see http://sourceforge.net/projects/libb64 #ifndef BASE64_CENCODE_H #define BASE64_CENCODE_H -typedef enum -{ - step_A, step_B, step_C -} base64_encodestep; +namespace base64 { -typedef struct -{ - base64_encodestep step; - char result; - int stepcount; -} base64_encodestate; + typedef enum + { + step_A, step_B, step_C + } base64_encodestep; -void base64_init_encodestate(base64_encodestate* state_in); + typedef struct + { + base64_encodestep step; + char result; + int stepcount; + } base64_encodestate; -char base64_encode_value(char value_in); + void base64_init_encodestate(base64_encodestate* state_in); -int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + char base64_encode_value(char value_in); -int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + + int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +} #endif /* BASE64_CENCODE_H */ diff --git a/src/b64/encode.h b/src/b64/encode.h index 92df8ec702..3f2a63c770 100644 --- a/src/b64/encode.h +++ b/src/b64/encode.h @@ -9,14 +9,10 @@ For details, see http://sourceforge.net/projects/libb64 #define BASE64_ENCODE_H #include +#include "cencode.h" namespace base64 { - extern "C" - { - #include "cencode.h" - } - struct encoder { base64_encodestate _state; @@ -47,9 +43,9 @@ namespace base64 { base64_init_encodestate(&_state); // - const int N = _buffersize; + size_t N = _buffersize; char* plaintext = new char[N]; - char* code = new char[2*N]; + char* code = new char[N*2]; int plainlength; int codelength; diff --git a/src/backtrace.cpp b/src/backtrace.cpp deleted file mode 100644 index 9e6f6a5fea..0000000000 --- a/src/backtrace.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "backtrace.hpp" - -namespace Sass { - - const sass::string traces_to_string(Backtraces traces, sass::string indent) { - - sass::ostream ss; - sass::string cwd(File::get_cwd()); - - bool first = true; - size_t i_beg = traces.size() - 1; - size_t i_end = sass::string::npos; - for (size_t i = i_beg; i != i_end; i --) { - - const Backtrace& trace = traces[i]; - - // make path relative to the current directory - sass::string rel_path(File::abs2rel(trace.pstate.getPath(), cwd, cwd)); - - // skip functions on error cases (unsure why ruby sass does this) - // if (trace.caller.substr(0, 6) == ", in f") continue; - - if (first) { - ss << indent; - ss << "on line "; - ss << trace.pstate.getLine(); - ss << ":"; - ss << trace.pstate.getColumn(); - ss << " of " << rel_path; - // ss << trace.caller; - first = false; - } else { - ss << trace.caller; - ss << std::endl; - ss << indent; - ss << "from line "; - ss << trace.pstate.getLine(); - ss << ":"; - ss << trace.pstate.getColumn(); - ss << " of " << rel_path; - } - - } - - ss << std::endl; - return ss.str(); - - } - -}; diff --git a/src/backtrace.hpp b/src/backtrace.hpp index 26efb985da..2a0fd8cc7a 100644 --- a/src/backtrace.hpp +++ b/src/backtrace.hpp @@ -1,28 +1,105 @@ #ifndef SASS_BACKTRACE_H #define SASS_BACKTRACE_H -#include -#include -#include "file.hpp" -#include "position.hpp" +#include "strings.hpp" +#include "source_span.hpp" +#include "ast_def_macros.hpp" + +// During runtime we need stack traces in order to produce meaningful +// error messages. Since the error catching might be done outside of +// the main compile function, certain values might already be garbage +// collected. Therefore we need to carry copies of those in any error. +// In order to optimize runtime, we don't want to create these copies +// during the evaluation stage, as most of the time we would throw them +// out right away. Therefore we only keep references during that phase +// (BackTrace), and copy them once an actual error is thrown (StackTrace). namespace Sass { - struct Backtrace { + class Traced { + public: + CAPI_WRAPPER(Traced, SassTrace); + virtual const SourceSpan& getPstate() const = 0; + virtual const sass::string& getName() const = 0; + virtual bool isFn() const = 0; + virtual ~Traced() {}; + }; + + // Holding actual copies + class StackTrace : public Traced { + + public: SourceSpan pstate; - sass::string caller; + sass::string name; + bool fn; - Backtrace(SourceSpan pstate, sass::string c = "") - : pstate(pstate), - caller(c) - { } + StackTrace( + SourceSpan pstate, + sass::string name = Strings::empty, + bool fn = false) : + pstate(pstate), + name(name), + fn(fn) + {} + + const SourceSpan& getPstate() const override final { + return pstate; + } + + const sass::string& getName() const override final { + return name; + } + + bool isFn() const override final { + return fn; + } }; - typedef sass::vector Backtraces; + // Holding only references + class BackTrace : public Traced { + + public: + + const SourceSpan& pstate; + const sass::string& name; + bool fn; + + BackTrace( + const SourceSpan& pstate, + const sass::string& name = Strings::empty, + bool fn = false) : + pstate(pstate), + name(name), + fn(fn) + {} + + const SourceSpan& getPstate() const override final { + return pstate; + } + + const sass::string& getName() const override final { + return name; + } + + bool isFn() const override final { + return fn; + } + + // Create copies on convert + operator StackTrace() + { + return StackTrace( + pstate, name, fn); + } + + }; - const sass::string traces_to_string(Backtraces traces, sass::string indent = "\t"); + // Some related and often used aliases + typedef sass::vector Traces; + typedef sass::vector BackTraces; + typedef sass::vector StackTraces; } diff --git a/src/base64vlq.cpp b/src/base64vlq.cpp deleted file mode 100644 index e8c3eea69b..0000000000 --- a/src/base64vlq.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "base64vlq.hpp" - -namespace Sass { - - sass::string Base64VLQ::encode(const int number) const - { - sass::string encoded = ""; - - int vlq = to_vlq_signed(number); - - do { - int digit = vlq & VLQ_BASE_MASK; - vlq >>= VLQ_BASE_SHIFT; - if (vlq > 0) { - digit |= VLQ_CONTINUATION_BIT; - } - encoded += base64_encode(digit); - } while (vlq > 0); - - return encoded; - } - - char Base64VLQ::base64_encode(const int number) const - { - int index = number; - if (index < 0) index = 0; - if (index > 63) index = 63; - return CHARACTERS[index]; - } - - int Base64VLQ::to_vlq_signed(const int number) const - { - return (number < 0) ? ((-number) << 1) + 1 : (number << 1) + 0; - } - - const char* Base64VLQ::CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - const int Base64VLQ::VLQ_BASE_SHIFT = 5; - const int Base64VLQ::VLQ_BASE = 1 << VLQ_BASE_SHIFT; - const int Base64VLQ::VLQ_BASE_MASK = VLQ_BASE - 1; - const int Base64VLQ::VLQ_CONTINUATION_BIT = VLQ_BASE; - -} diff --git a/src/base64vlq.hpp b/src/base64vlq.hpp index 2df8c6ee60..3d16a03b12 100644 --- a/src/base64vlq.hpp +++ b/src/base64vlq.hpp @@ -2,27 +2,52 @@ #define SASS_BASE64VLQ_H #include +#include "memory.hpp" namespace Sass { class Base64VLQ { + const char* CHARACTERS = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + const int VLQ_BASE_SHIFT = 5; + const int VLQ_BASE = 1 << VLQ_BASE_SHIFT; + const int VLQ_BASE_MASK = VLQ_BASE - 1; + const int VLQ_CONTINUATION_BIT = VLQ_BASE; + public: - sass::string encode(const int number) const; + void encode(sass::string& buffer, const int number) const + { + int vlq = (number < 0) ? ((-number) << 1) + 1 : (number << 1) + 0; - private: + do { + int digit = vlq & VLQ_BASE_MASK; + vlq >>= VLQ_BASE_SHIFT; + if (vlq > 0) { + digit |= VLQ_CONTINUATION_BIT; + } + buffer += base64_encode(digit); + } while (vlq > 0); - char base64_encode(const int number) const; + } + + private: - int to_vlq_signed(const int number) const; + inline char base64_encode(const int number) const + { + int index = number; + if (index < 0) index = 0; + if (index > 63) index = 63; + return CHARACTERS[index]; + } - static const char* CHARACTERS; + // int to_vlq_signed(const int number) const + // { + // return (number < 0) ? ((-number) << 1) + 1 : (number << 1) + 0; + // } - static const int VLQ_BASE_SHIFT; - static const int VLQ_BASE; - static const int VLQ_BASE_MASK; - static const int VLQ_CONTINUATION_BIT; }; } diff --git a/src/bind.cpp b/src/bind.cpp deleted file mode 100644 index fdb375df06..0000000000 --- a/src/bind.cpp +++ /dev/null @@ -1,312 +0,0 @@ -#include "sass.hpp" -#include "bind.hpp" -#include "ast.hpp" -#include "backtrace.hpp" -#include "context.hpp" -#include "expand.hpp" -#include "eval.hpp" -#include -#include -#include - -namespace Sass { - - void bind(sass::string type, sass::string name, Parameters_Obj ps, Arguments_Obj as, Env* env, Eval* eval, Backtraces& traces) - { - sass::string callee(type + " " + name); - - std::map param_map; - List_Obj varargs = SASS_MEMORY_NEW(List, as->pstate()); - varargs->is_arglist(true); // enable keyword size handling - - for (size_t i = 0, L = as->length(); i < L; ++i) { - if (auto str = Cast((*as)[i]->value())) { - // force optional quotes (only if needed) - if (str->quote_mark()) { - str->quote_mark('*'); - } - } - } - - // Set up a map to ensure named arguments refer to actual parameters. Also - // eval each default value left-to-right, wrt env, populating env as we go. - for (size_t i = 0, L = ps->length(); i < L; ++i) { - Parameter_Obj p = ps->at(i); - param_map[p->name()] = p; - // if (p->default_value()) { - // env->local_frame()[p->name()] = p->default_value()->perform(eval->with(env)); - // } - } - - // plug in all args; if we have leftover params, deal with it later - size_t ip = 0, LP = ps->length(); - size_t ia = 0, LA = as->length(); - while (ia < LA) { - Argument_Obj a = as->at(ia); - if (ip >= LP) { - // skip empty rest arguments - if (a->is_rest_argument()) { - if (List_Obj l = Cast(a->value())) { - if (l->length() == 0) { - ++ ia; continue; - } - } - } - sass::ostream msg; - msg << "wrong number of arguments (" << LA << " for " << LP << ")"; - msg << " for `" << name << "'"; - return error(msg.str(), as->pstate(), traces); - } - Parameter_Obj p = ps->at(ip); - - // If the current parameter is the rest parameter, process and break the loop - if (p->is_rest_parameter()) { - // The next argument by coincidence provides a rest argument - if (a->is_rest_argument()) { - - // We should always get a list for rest arguments - if (List_Obj rest = Cast(a->value())) { - // create a new list object for wrapped items - List* arglist = SASS_MEMORY_NEW(List, - p->pstate(), - 0, - rest->separator(), - true); - // wrap each item from list as an argument - for (ExpressionObj item : rest->elements()) { - if (Argument_Obj arg = Cast(item)) { - arglist->append(SASS_MEMORY_COPY(arg)); // copy - } else { - arglist->append(SASS_MEMORY_NEW(Argument, - item->pstate(), - item, - "", - false, - false)); - } - } - // assign new arglist to environment - env->local_frame()[p->name()] = arglist; - } - // invalid state - else { - throw std::runtime_error("invalid state"); - } - } else if (a->is_keyword_argument()) { - - // expand keyword arguments into their parameters - List* arglist = SASS_MEMORY_NEW(List, p->pstate(), 0, SASS_COMMA, true); - env->local_frame()[p->name()] = arglist; - Map_Obj argmap = Cast(a->value()); - for (auto key : argmap->keys()) { - if (String_Constant_Obj str = Cast(key)) { - sass::string param = unquote(str->value()); - arglist->append(SASS_MEMORY_NEW(Argument, - key->pstate(), - argmap->at(key), - "$" + param, - false, - false)); - } else { - traces.push_back(Backtrace(key->pstate())); - throw Exception::InvalidVarKwdType(key->pstate(), traces, key->inspect(), a); - } - } - - } else { - - // create a new list object for wrapped items - List_Obj arglist = SASS_MEMORY_NEW(List, - p->pstate(), - 0, - SASS_COMMA, - true); - // consume the next args - while (ia < LA) { - // get and post inc - a = (*as)[ia++]; - // maybe we have another list as argument - List_Obj ls = Cast(a->value()); - // skip any list completely if empty - if (ls && ls->empty() && a->is_rest_argument()) continue; - - ExpressionObj value = a->value(); - if (Argument_Obj arg = Cast(value)) { - arglist->append(arg); - } - // check if we have rest argument - else if (a->is_rest_argument()) { - // preserve the list separator from rest args - if (List_Obj rest = Cast(a->value())) { - arglist->separator(rest->separator()); - - for (size_t i = 0, L = rest->length(); i < L; ++i) { - ExpressionObj obj = rest->value_at_index(i); - arglist->append(SASS_MEMORY_NEW(Argument, - obj->pstate(), - obj, - "", - false, - false)); - } - } - // no more arguments - break; - } - // wrap all other value types into Argument - else { - arglist->append(SASS_MEMORY_NEW(Argument, - a->pstate(), - a->value(), - a->name(), - false, - false)); - } - } - // assign new arglist to environment - env->local_frame()[p->name()] = arglist; - } - // consumed parameter - ++ip; - // no more parameters - break; - } - - // If the current argument is the rest argument, extract a value for processing - else if (a->is_rest_argument()) { - // normal param and rest arg - List_Obj arglist = Cast(a->value()); - if (!arglist) { - if (ExpressionObj arg = Cast(a->value())) { - arglist = SASS_MEMORY_NEW(List, a->pstate(), 1); - arglist->append(arg); - } - } - - // empty rest arg - treat all args as default values - if (!arglist || !arglist->length()) { - break; - } else { - if (arglist->length() > LP - ip && !ps->has_rest_parameter()) { - size_t arg_count = (arglist->length() + LA - 1); - sass::ostream msg; - msg << callee << " takes " << LP; - msg << (LP == 1 ? " argument" : " arguments"); - msg << " but " << arg_count; - msg << (arg_count == 1 ? " was passed" : " were passed."); - deprecated_bind(msg.str(), as->pstate()); - - while (arglist->length() > LP - ip) { - arglist->elements().erase(arglist->elements().end() - 1); - } - } - } - // otherwise move one of the rest args into the param, converting to argument if necessary - ExpressionObj obj = arglist->at(0); - if (!(a = Cast(obj))) { - Expression* a_to_convert = obj; - a = SASS_MEMORY_NEW(Argument, - a_to_convert->pstate(), - a_to_convert, - "", - false, - false); - } - arglist->elements().erase(arglist->elements().begin()); - if (!arglist->length() || (!arglist->is_arglist() && ip + 1 == LP)) { - ++ia; - } - - } else if (a->is_keyword_argument()) { - Map_Obj argmap = Cast(a->value()); - - for (auto key : argmap->keys()) { - String_Constant* val = Cast(key); - if (val == NULL) { - traces.push_back(Backtrace(key->pstate())); - throw Exception::InvalidVarKwdType(key->pstate(), traces, key->inspect(), a); - } - sass::string param = "$" + unquote(val->value()); - - if (!param_map.count(param)) { - sass::ostream msg; - msg << callee << " has no parameter named " << param; - error(msg.str(), a->pstate(), traces); - } - env->local_frame()[param] = argmap->at(key); - } - ++ia; - continue; - } else { - ++ia; - } - - if (a->name().empty()) { - if (env->has_local(p->name())) { - sass::ostream msg; - msg << "parameter " << p->name() - << " provided more than once in call to " << callee; - error(msg.str(), a->pstate(), traces); - } - // ordinal arg -- bind it to the next param - env->local_frame()[p->name()] = a->value(); - ++ip; - } - else { - // named arg -- bind it to the appropriately named param - if (!param_map.count(a->name())) { - if (ps->has_rest_parameter()) { - varargs->append(a); - } else { - sass::ostream msg; - msg << callee << " has no parameter named " << a->name(); - error(msg.str(), a->pstate(), traces); - } - } - if (param_map[a->name()]) { - if (param_map[a->name()]->is_rest_parameter()) { - sass::ostream msg; - msg << "argument " << a->name() << " of " << callee - << "cannot be used as named argument"; - error(msg.str(), a->pstate(), traces); - } - } - if (env->has_local(a->name())) { - sass::ostream msg; - msg << "parameter " << p->name() - << "provided more than once in call to " << callee; - error(msg.str(), a->pstate(), traces); - } - env->local_frame()[a->name()] = a->value(); - } - } - // EO while ia - - // If we make it here, we're out of args but may have leftover params. - // That's only okay if they have default values, or were already bound by - // named arguments, or if it's a single rest-param. - for (size_t i = ip; i < LP; ++i) { - Parameter_Obj leftover = ps->at(i); - // cerr << "env for default params:" << endl; - // env->print(); - // cerr << "********" << endl; - if (!env->has_local(leftover->name())) { - if (leftover->is_rest_parameter()) { - env->local_frame()[leftover->name()] = varargs; - } - else if (leftover->default_value()) { - Expression* dv = leftover->default_value()->perform(eval); - env->local_frame()[leftover->name()] = dv; - } - else { - // param is unbound and has no default value -- error - throw Exception::MissingArgument(as->pstate(), traces, name, leftover->name(), type); - } - } - } - - return; - } - - -} diff --git a/src/bind.hpp b/src/bind.hpp deleted file mode 100644 index cb56fae08d..0000000000 --- a/src/bind.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef SASS_BIND_H -#define SASS_BIND_H - -#include -#include "backtrace.hpp" -#include "environment.hpp" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - void bind(sass::string type, sass::string name, Parameters_Obj, Arguments_Obj, Env*, Eval*, Backtraces& traces); - -} - -#endif diff --git a/src/c2ast.cpp b/src/c2ast.cpp deleted file mode 100644 index 7a4880c968..0000000000 --- a/src/c2ast.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "ast.hpp" -#include "units.hpp" -#include "position.hpp" -#include "backtrace.hpp" -#include "sass/values.h" -#include "ast_fwd_decl.hpp" -#include "error_handling.hpp" - -namespace Sass { - - Value* c2ast(union Sass_Value* v, Backtraces traces, SourceSpan pstate) - { - using std::strlen; - using std::strcpy; - Value* e = NULL; - switch (sass_value_get_tag(v)) { - case SASS_BOOLEAN: { - e = SASS_MEMORY_NEW(Boolean, pstate, !!sass_boolean_get_value(v)); - } break; - case SASS_NUMBER: { - e = SASS_MEMORY_NEW(Number, pstate, sass_number_get_value(v), sass_number_get_unit(v)); - } break; - case SASS_COLOR: { - e = SASS_MEMORY_NEW(Color_RGBA, pstate, sass_color_get_r(v), sass_color_get_g(v), sass_color_get_b(v), sass_color_get_a(v)); - } break; - case SASS_STRING: { - if (sass_string_is_quoted(v)) - e = SASS_MEMORY_NEW(String_Quoted, pstate, sass_string_get_value(v)); - else { - e = SASS_MEMORY_NEW(String_Constant, pstate, sass_string_get_value(v)); - } - } break; - case SASS_LIST: { - List* l = SASS_MEMORY_NEW(List, pstate, sass_list_get_length(v), sass_list_get_separator(v)); - for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) { - l->append(c2ast(sass_list_get_value(v, i), traces, pstate)); - } - l->is_bracketed(sass_list_get_is_bracketed(v)); - e = l; - } break; - case SASS_MAP: { - Map* m = SASS_MEMORY_NEW(Map, pstate); - for (size_t i = 0, L = sass_map_get_length(v); i < L; ++i) { - *m << std::make_pair( - c2ast(sass_map_get_key(v, i), traces, pstate), - c2ast(sass_map_get_value(v, i), traces, pstate)); - } - e = m; - } break; - case SASS_NULL: { - e = SASS_MEMORY_NEW(Null, pstate); - } break; - case SASS_ERROR: { - error("Error in C function: " + sass::string(sass_error_get_message(v)), pstate, traces); - } break; - case SASS_WARNING: { - error("Warning in C function: " + sass::string(sass_warning_get_message(v)), pstate, traces); - } break; - default: break; - } - return e; - } - -} diff --git a/src/c2ast.hpp b/src/c2ast.hpp deleted file mode 100644 index 006c490e71..0000000000 --- a/src/c2ast.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef SASS_C2AST_H -#define SASS_C2AST_H - -#include "position.hpp" -#include "backtrace.hpp" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - Value* c2ast(union Sass_Value* v, Backtraces traces, SourceSpan pstate); - -} - -#endif diff --git a/src/callstack.hpp b/src/callstack.hpp new file mode 100644 index 0000000000..9980000125 --- /dev/null +++ b/src/callstack.hpp @@ -0,0 +1,64 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CALLSTACK_HPP +#define SASS_CALLSTACK_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "backtrace.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Utility class to add a frame onto a call-stack (RAII). + // Cleans up automatically when object goes out of scope. + // We assume this happens in well defined order, as + // we do not check if we actually remove ourself! + // ToDo: rename to callTrace + class callStackFrame { + + private: + + // The shared callStack + BackTraces& backTraces; + + // The current stack frame + BackTrace frame; + + // Are we invoked by `call` + bool viaCall; + + public: + + // Create object and add frame to stack + callStackFrame(BackTraces& backTraces, + const BackTrace& frame, + bool viaCall = false) : + backTraces(backTraces), + frame(frame), + viaCall(viaCall) + { + // Append frame to stack + if (!viaCall) backTraces.push_back(frame); + } + + // Remove frame from stack on destruction + ~callStackFrame() + { + // Pop frame from stack + if (!viaCall) backTraces.pop_back(); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/capi_base.cpp b/src/capi_base.cpp new file mode 100644 index 0000000000..6fcbd285c9 --- /dev/null +++ b/src/capi_base.cpp @@ -0,0 +1,29 @@ +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "file.hpp" +#include "terminal.hpp" + +using namespace Sass; + +extern "C" { + + void ADDCALL sass_chdir(const char* path) + { + if (path != nullptr) { + CWD = File::rel2abs(path, CWD) + "/"; + } + } + + void ADDCALL sass_print_stderr(const char* message) + { + Terminal::print(message, true); + } + + void ADDCALL sass_print_stdout(const char* message) + { + Terminal::print(message, false); + } + +} diff --git a/src/capi_compiler.cpp b/src/capi_compiler.cpp new file mode 100644 index 0000000000..729ede1b29 --- /dev/null +++ b/src/capi_compiler.cpp @@ -0,0 +1,574 @@ +#include "capi_compiler.hpp" + +#include "source.hpp" +#include "exceptions.hpp" + +#ifdef _MSC_VER +// #include +#endif + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + // Some specific implementations that could also go into C++ part + ///////////////////////////////////////////////////////////////////////// + + // Format the error and fill compiler error states + static int handle_error(Compiler& compiler, StackTraces* traces, const char* what, int status) + { + + sass::ostream formatted; + bool has_final_lf = false; + Logger& logger(compiler); + formatted << "Error: "; + // Add message and ensure it is + // added with a final line-feed. + if (what != nullptr) { + formatted << what; + while (*what) { + has_final_lf = + *what == '\r' || + *what == '\n'; + ++what; + } + if (!has_final_lf) { + formatted << STRMLF; + } + } + + // Clear the previous array + compiler.error.traces.clear(); + // Some stuff is only logged if we have some traces + // Otherwise we don't know where the error comes from + if (traces && traces->size() > 0) { + logger.writeStackTraces(formatted, *traces, " "); + // Copy items over to error object + compiler.error.traces = *traces; + } + + // Attach stuff to error object + compiler.error.what.clear(); + compiler.error.status = status; + if (what) compiler.error.what = what; + compiler.error.formatted = formatted.str(); + + return status; + + } + // EO handle_error + + // Main entry point to catch errors + static int handle_error(Compiler& compiler) + { + // Re-throw last error + try { throw; } + // Catch LibSass specific error cases + catch (Exception::Base & e) { handle_error(compiler, &e.traces, e.what(), 1); } + // Bad allocations can always happen, maybe we should exit in this case? + catch (std::bad_alloc & e) { handle_error(compiler, nullptr, e.what(), 2); } + // Other errors should not really happen and indicate more severe issues! + catch (std::exception & e) { handle_error(compiler, nullptr, e.what(), 3); } + catch (sass::string & e) { handle_error(compiler, nullptr, e.c_str(), 4); } + catch (const char* e) { handle_error(compiler, nullptr, e, 4); } + catch (...) { handle_error(compiler, nullptr, "unknown", 5); } + // Return the error state + return compiler.error.status; + } + // EO handle_error + + // allow one error handler to throw another error + // this can happen with invalid utf8 and json lib + // Note: this might be obsolete but doesn't hurt!? + static int handle_errors(Compiler& compiler) + { + try { return handle_error(compiler); } + catch (...) { return handle_error(compiler); } + } + // EO handle_errors + + // Main implementation (caller is wrapped in try/catch) + void __sass_compiler_parse(Compiler& compiler) + { + compiler.parse(); + } + // EO __sass_compiler_parse + + // Main implementation (caller is wrapped in try/catch) + void __sass_compiler_compile(Compiler& compiler) + { + compiler.compile(); + } + // EO __sass_compiler_compile + + // Main implementation (caller is wrapped in try/catch) + void __sass_compiler_render(Compiler& compiler) + { + + // Bail out if we had any previous errors + if (compiler.error.status != 0) return; + // Make sure compile step was called before + if (compiler.compiled == nullptr) return; + + // This will hopefully use move semantics + OutputBuffer output(compiler.renderCss()); + compiler.content = std::move(output.buffer); + + // Create options to render source map and footer. + struct SassSrcMapOptions options(compiler.srcmap_options); + // Deduct some options always from original values. + // ToDo: is there really any need to customize this? + if (options.origin.empty() || options.origin == "stream://stdout") { + options.origin = compiler.getOutputPath(); + } + if (options.path.empty() || options.path == "stream://stdout") { + options.path = options.origin + ".map"; + } + + switch (options.mode) { + case SASS_SRCMAP_NONE: + compiler.srcmap = 0; + compiler.footer = 0; + break; + case SASS_SRCMAP_CREATE: + compiler.srcmap = compiler.renderSrcMapJson(options, *output.smap); + compiler.footer = nullptr; // Don't add link, just create map file + break; + case SASS_SRCMAP_EMBED_LINK: + compiler.srcmap = compiler.renderSrcMapJson(options, *output.smap); + compiler.footer = compiler.renderSrcMapLink(options, *output.smap); + break; + case SASS_SRCMAP_EMBED_JSON: + compiler.srcmap = compiler.renderSrcMapJson(options, *output.smap); + compiler.footer = compiler.renderEmbeddedSrcMap(options, *output.smap); + break; + } + + } + // EO __sass_compiler_render + + ///////////////////////////////////////////////////////////////////////// + // On windows we can improve the error handling by also catching + // structured exceptions. In order for this to work we need a few + // additional wrapper functions, which fortunately don't cost much. + ///////////////////////////////////////////////////////////////////////// + + #ifdef _MSC_VER + int filter(unsigned int code, struct _EXCEPTION_POINTERS* ep) + { + // Handle exceptions we can't handle otherwise + // Because these are not regular C++ exceptions + if (code == EXCEPTION_ACCESS_VIOLATION) + { + return EXCEPTION_EXECUTE_HANDLER; + } + else if (code == EXCEPTION_STACK_OVERFLOW) + { + return EXCEPTION_EXECUTE_HANDLER; + } + else + { + // Do not handle any other exceptions here + // Should be handled by regular try/catch + return EXCEPTION_CONTINUE_SEARCH; + } + } + + // Convert exception codes to strings + const char* seException(unsigned int code) + { + switch (code) { + case EXCEPTION_ACCESS_VIOLATION: return "EXCEPTION_ACCESS_VIOLATION"; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; + case EXCEPTION_BREAKPOINT: return "EXCEPTION_BREAKPOINT"; + case EXCEPTION_DATATYPE_MISALIGNMENT: return "EXCEPTION_DATATYPE_MISALIGNMENT"; + case EXCEPTION_FLT_DENORMAL_OPERAND: return "EXCEPTION_FLT_DENORMAL_OPERAND"; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; + case EXCEPTION_FLT_INEXACT_RESULT: return "EXCEPTION_FLT_INEXACT_RESULT"; + case EXCEPTION_FLT_INVALID_OPERATION: return "EXCEPTION_FLT_INVALID_OPERATION"; + case EXCEPTION_FLT_OVERFLOW: return "EXCEPTION_FLT_OVERFLOW"; + case EXCEPTION_FLT_STACK_CHECK: return "EXCEPTION_FLT_STACK_CHECK"; + case EXCEPTION_FLT_UNDERFLOW: return "EXCEPTION_FLT_UNDERFLOW"; + case EXCEPTION_ILLEGAL_INSTRUCTION: return "EXCEPTION_ILLEGAL_INSTRUCTION"; + case EXCEPTION_IN_PAGE_ERROR: return "EXCEPTION_IN_PAGE_ERROR"; + case EXCEPTION_INT_DIVIDE_BY_ZERO: return "EXCEPTION_INT_DIVIDE_BY_ZERO"; + case EXCEPTION_INT_OVERFLOW: return "EXCEPTION_INT_OVERFLOW"; + case EXCEPTION_INVALID_DISPOSITION: return "EXCEPTION_INVALID_DISPOSITION"; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; + case EXCEPTION_PRIV_INSTRUCTION: return "EXCEPTION_PRIV_INSTRUCTION"; + case EXCEPTION_SINGLE_STEP: return "EXCEPTION_SINGLE_STEP"; + case EXCEPTION_STACK_OVERFLOW: return "EXCEPTION_STACK_OVERFLOW"; + default: return "UNKNOWN EXCEPTION"; + } + } + #endif + + // Wrap Structured Exceptions for MSVC + void _sass_compiler_parse(Compiler& compiler) + { + #ifdef _MSC_VER + __try { + #endif + __sass_compiler_parse(compiler); + #ifdef _MSC_VER + } + __except (filter(GetExceptionCode(), GetExceptionInformation())) { + throw std::runtime_error(seException(GetExceptionCode())); + } + #endif + } + + // Wrap Structured Exceptions for MSVC + void _sass_compiler_compile(Compiler& compiler) + { + #ifdef _MSC_VER + __try { + #endif + __sass_compiler_compile(compiler); + #ifdef _MSC_VER + } + __except (filter(GetExceptionCode(), GetExceptionInformation())) { + throw std::runtime_error(seException(GetExceptionCode())); + } + #endif + } + + // Wrap Structured Exceptions for MSVC + void _sass_compiler_render(Compiler& compiler) + { + #ifdef _MSC_VER + __try { + #endif + __sass_compiler_render(compiler); + #ifdef _MSC_VER + } + __except (filter(GetExceptionCode(), GetExceptionInformation())) { + throw std::runtime_error(seException(GetExceptionCode())); + } + #endif + } + + // Main entry point (wrap try/catch then MSVC) + void sass_compiler_parse(Compiler& compiler) + { + Logger& logger(compiler); + try { _sass_compiler_parse(compiler); } + catch (...) { handle_errors(compiler); } + compiler.warnings = logger.logstrm.str(); + } + + // Main entry point (wrap try/catch then MSVC) + void sass_compiler_compile(Compiler& compiler) + { + Logger& logger(compiler); + try { _sass_compiler_compile(compiler); } + catch (...) { handle_errors(compiler); } + compiler.warnings = logger.logstrm.str(); + } + + // Main entry point (wrap try/catch then MSVC) + void sass_compiler_render(Compiler& compiler) + { + Logger& logger(compiler); + try { _sass_compiler_render(compiler); } + catch (...) { handle_errors(compiler); } + compiler.warnings = logger.logstrm.str(); + } + + extern "C" { + + bool ADDCALL sass_compiler_get_source_comments(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).source_comments; + } + + // void ADDCALL sass_compiler_set_source_comments(struct SassCompiler* compiler, bool source_comments) + // { + // Compiler::unwrap(compiler).source_comments = source_comments; + // } + + // Returns pointer to error object associated with compiler. + // Will be valid until the associated compiler is destroyed. + const struct SassError* ADDCALL sass_compiler_get_error(struct SassCompiler* compiler) + { + return &Compiler::unwrap(compiler).error; + } + + struct SassImport* ADDCALL sass_compiler_get_last_import(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).import_stack.back()->wrap(); + } + + size_t ADDCALL sass_compiler_count_traces(struct SassCompiler* compiler) + { + Logger& logger(Compiler::unwrap(compiler)); + return logger.callStack.size(); + } + + const struct SassTrace* ADDCALL sass_compiler_last_trace(struct SassCompiler* compiler) + { + Logger& logger(Compiler::unwrap(compiler)); + return logger.callStack.back().wrap(); + } + + const struct SassTrace* ADDCALL sass_compiler_get_trace(struct SassCompiler* compiler, size_t i) + { + Logger& logger(Compiler::unwrap(compiler)); + return logger.callStack.at(i).wrap(); + } + + } + +} + + +///////////////////////////////////////////////////////////////////////// +// The actual C-API Implementations +///////////////////////////////////////////////////////////////////////// + +using namespace Sass; + +extern "C" { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + struct SassCompiler* ADDCALL sass_make_compiler() + { + return Compiler::wrap(new Compiler()); + } + + void ADDCALL sass_delete_compiler(struct SassCompiler* compiler) + { + delete& Compiler::unwrap(compiler); + #ifdef DEBUG_SHARED_PTR + SharedObj::dumpMemLeaks(); + #endif + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Push function for include paths (no manipulation support for now) + void ADDCALL sass_compiler_add_include_paths(struct SassCompiler* compiler, const char* paths) + { + Compiler::unwrap(compiler).addIncludePaths(paths); + } + + // Push function for plugin paths (no manipulation support for now) + void ADDCALL sass_compiler_load_plugins(struct SassCompiler* compiler, const char* paths) + { + Compiler::unwrap(compiler).loadPlugins(paths); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void ADDCALL sass_compiler_add_custom_header(struct SassCompiler* compiler, struct SassImporter* header) + { + Compiler::unwrap(compiler).addCustomHeader(header); + } + + void ADDCALL sass_compiler_add_custom_importer(struct SassCompiler* compiler, struct SassImporter* importer) + { + Compiler::unwrap(compiler).addCustomImporter(importer); + } + + void ADDCALL sass_compiler_add_custom_function(struct SassCompiler* compiler, struct SassFunction* function) + { + Compiler::unwrap(compiler).addCustomFunction(function); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void ADDCALL sass_compiler_set_precision(struct SassCompiler* compiler, int precision) + { + Compiler::unwrap(compiler).precision = precision; + Compiler::unwrap(compiler).setLogPrecision(precision); + } + + int ADDCALL sass_compiler_get_precision(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).precision; + } + + const char* ADDCALL sass_compiler_get_output_path(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).output_path.c_str(); + } + + struct SassImport* ADDCALL sass_compiler_get_entry_point(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).entry_point->wrap(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void ADDCALL sass_compiler_set_srcmap_mode(struct SassCompiler* compiler, enum SassSrcMapMode mode) + { + Compiler::unwrap(compiler).srcmap_options.mode = mode; + } + + void ADDCALL sass_compiler_set_srcmap_root(struct SassCompiler* compiler, const char* root) + { + Compiler::unwrap(compiler).srcmap_options.root = root; + } + + void ADDCALL sass_compiler_set_srcmap_path(struct SassCompiler* compiler, const char* path) + { + Compiler::unwrap(compiler).srcmap_options.path = path; + } + + void ADDCALL sass_compiler_set_srcmap_file_urls(struct SassCompiler* compiler, bool enable) + { + Compiler::unwrap(compiler).srcmap_options.file_urls = enable; + } + + void ADDCALL sass_compiler_set_srcmap_embed_contents(struct SassCompiler* compiler, bool enable) + { + Compiler::unwrap(compiler).srcmap_options.embed_contents = enable; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + size_t ADDCALL sass_compiler_get_included_files_count(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).included_sources.size(); + } + + const char* ADDCALL sass_compiler_get_included_file_path(struct SassCompiler* compiler, size_t n) + { + return Compiler::unwrap(compiler).included_sources.at(n)->getAbsPath(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Implement file-loading from given data + struct SassImport* ADDCALL sass_make_stdin_import(const char* path) + { + std::istreambuf_iterator begin(std::cin), end; + sass::string text(begin, end); // consume everything + Import* import = SASS_MEMORY_NEW(Import, + SASS_MEMORY_NEW(SourceString, + path ? path : "stream://stdin", + std::move(text)), + SASS_IMPORT_AUTO); + // Use reference counter + import->refcount += 1; + // Create the shared source object + return Import::wrap(import); + } + // EO sass_make_stdin_import + + // Implement initial data-loading without import path resolving. + struct SassImport* ADDCALL sass_make_content_import(char* content, const char* path) + { + Import* loaded = SASS_MEMORY_NEW(Import, + SASS_MEMORY_NEW(SourceString, + path ? path : "stream://stdin", + path ? path : "stream://stdin", + content ? content : "", + ""), + SASS_IMPORT_AUTO); + loaded->refcount += 1; + return loaded->wrap(); + } + + // Implement initial file-loading without import path resolving. + struct SassImport* ADDCALL sass_make_file_import(const char* imp_path) + { + sass::string abs_path(File::rel2abs(imp_path, CWD)); + Import* loaded = SASS_MEMORY_NEW(Import, + SASS_MEMORY_NEW(SourceFile, + imp_path, abs_path.c_str(), + nullptr, nullptr), + SASS_IMPORT_AUTO); + loaded->refcount += 1; + return loaded->wrap(); + } + // EO sass_make_file_import + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void ADDCALL sass_compiler_set_entry_point(struct SassCompiler* compiler, struct SassImport* import) + { + auto& qwe = Import::unwrap(import); + // std::cerr << "1) " << qwe.refcount << "\n"; + Compiler::unwrap(compiler).entry_point = &qwe; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void ADDCALL sass_compiler_set_output_path(struct SassCompiler* compiler, const char* output_path) + { + Compiler::unwrap(compiler).output_path = output_path ? output_path : "stream://stdout"; + } + + void ADDCALL sass_compiler_set_output_style(struct SassCompiler* compiler, enum SassOutputStyle output_style) + { + Compiler::unwrap(compiler).output_style = output_style; + } + + void ADDCALL sass_compiler_set_logger_style(struct SassCompiler* compiler, enum SassLoggerStyle log_style) + { + Compiler::unwrap(compiler).setLogStyle(log_style); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void ADDCALL sass_compiler_parse(struct SassCompiler* sass_compiler) + { + sass_compiler_parse(Compiler::unwrap(sass_compiler)); + } + + void ADDCALL sass_compiler_compile(struct SassCompiler* sass_compiler) + { + sass_compiler_compile(Compiler::unwrap(sass_compiler)); + } + + void ADDCALL sass_compiler_render(struct SassCompiler* sass_compiler) + { + sass_compiler_render(Compiler::unwrap(sass_compiler)); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + const char* ADDCALL sass_compiler_get_output_string(struct SassCompiler* compiler) + { + if (Compiler::unwrap(compiler).content.empty()) return nullptr; + return Compiler::unwrap(compiler).content.c_str(); + } + + const char* ADDCALL sass_compiler_get_warn_string(struct SassCompiler* compiler) + { + if (Compiler::unwrap(compiler).warnings.empty()) return nullptr; + return Compiler::unwrap(compiler).warnings.c_str(); + } + + const char* ADDCALL sass_compiler_get_footer_string(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).footer; + } + + const char* ADDCALL sass_compiler_get_srcmap_string(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).srcmap; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/capi_compiler.hpp b/src/capi_compiler.hpp new file mode 100644 index 0000000000..f1a4426083 --- /dev/null +++ b/src/capi_compiler.hpp @@ -0,0 +1,16 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_SASS_COMPILER_HPP +#define SASS_SASS_COMPILER_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include +#include + +#include "compiler.hpp" + +#endif diff --git a/src/capi_context.cpp b/src/capi_context.cpp new file mode 100644 index 0000000000..cb6b4fa958 --- /dev/null +++ b/src/capi_context.cpp @@ -0,0 +1,157 @@ +#include "capi_context.hpp" + +#include "file.hpp" +#include "source.hpp" +#include "capi_error.hpp" + +using namespace Sass; + +extern "C" { + + struct SassTraces* ADDCALL sass_error_get_traces(struct SassError* error) + { + return reinterpret_cast(&error->traces); + } + + ///////////////////////////////////////////////////////////////////////// + // Implementation related to imports + ///////////////////////////////////////////////////////////////////////// + + const char* SASS_PATH_DATA = "sass://data"; + + struct SassImport* ADDCALL sass_make_data_import(char* content, const char* imp_path) + { + // Create the import struct + return sass_make_import( + imp_path ? imp_path : SASS_PATH_DATA, + imp_path ? imp_path : SASS_PATH_DATA, + content, nullptr, + SASS_IMPORT_AUTO + ); + + return nullptr; + } + + // struct SassImport* ADDCALL sass_make_file_import(struct SassCompiler* compiler, const char* imp_path) + // { + // // Check if entry file-path is given + // if (imp_path == nullptr) return nullptr; + // + // // Create absolute path from input filename + // sass::string abs_path(File::rel2abs(imp_path, CWD)); + // + // // Try to load the entry file + // char* content = File::slurp_file(abs_path, CWD); + // + // // Check if something was read + // if (content == nullptr) { + // return nullptr; + // } + // + // // Create the import struct + // return sass_make_import( + // imp_path, + // abs_path.c_str(), + // content, nullptr, + // SASS_IMPORT_AUTO + // ); + // } + + void ADDCALL sass_import_set_format(struct SassImport* import, enum SassImportFormat format) + { + Import::unwrap(import).syntax = format; + } + + // void ADDCALL sass_entry_set_abs_path(struct SassImport* import, const char* path) + // { + // Import::unwrap(import).setAbsPath(path); + // } + + ///////////////////////////////////////////////////////////////////////// + // Implementation related to traces + ///////////////////////////////////////////////////////////////////////// + + // size_t ADDCALL sass_traces_get_size(struct SassTraces* traces) + // { + // return reinterpret_cast(traces)->size(); + // } + // + // struct SassTrace* ADDCALL sass_traces_get_trace(struct SassTraces* traces, size_t i) + // { + // return reinterpret_cast(traces)->at(i).wrap(); + // } + + const char* ADDCALL sass_trace_get_name(struct SassTrace* trace) + { + return Traced::unwrap(trace).getName().c_str(); + } + + bool ADDCALL sass_trace_get_was_fncall(struct SassTrace* trace) + { + return Traced::unwrap(trace).isFn(); + } + + const struct SassSrcSpan* ADDCALL sass_trace_get_srcspan(struct SassTrace* trace) + { + return SourceSpan::wrap(&Traced::unwrap(trace).getPstate()); + } + + ///////////////////////////////////////////////////////////////////////// + // Implementation related to source-spans + ///////////////////////////////////////////////////////////////////////// + + size_t ADDCALL sass_srcspan_get_src_ln(struct SassSrcSpan* pstate) + { + return SourceSpan::unwrap(pstate).position.line; + } + size_t ADDCALL sass_srcspan_get_src_col(struct SassSrcSpan* pstate) + { + return SourceSpan::unwrap(pstate).position.column; + } + size_t ADDCALL sass_srcspan_get_src_line(struct SassSrcSpan* pstate) + { + return SourceSpan::unwrap(pstate).getLine(); + } + size_t ADDCALL sass_srcspan_get_src_column(struct SassSrcSpan* pstate) + { + return SourceSpan::unwrap(pstate).getColumn(); + } + size_t ADDCALL sass_srcspan_get_span_ln(struct SassSrcSpan* pstate) + { + return SourceSpan::unwrap(pstate).span.line; + } + size_t ADDCALL sass_srcspan_get_span_col(struct SassSrcSpan* pstate) + { + return SourceSpan::unwrap(pstate).span.column; + } + + struct SassSource* ADDCALL sass_srcspan_get_source(struct SassSrcSpan* pstate) + { + return SourceData::wrap(SourceSpan::unwrap(pstate).getSource()); + } + + ///////////////////////////////////////////////////////////////////////// + // Implementation related to sources + ///////////////////////////////////////////////////////////////////////// + + const char* ADDCALL sass_source_get_abs_path(struct SassSource* source) + { + return SourceData::unwrap(source).getAbsPath(); + } + + const char* ADDCALL sass_source_get_imp_path(struct SassSource* source) + { + return SourceData::unwrap(source).getImpPath(); + } + + const char* ADDCALL sass_source_get_content(struct SassSource* source) + { + return SourceData::unwrap(source).content(); + } + + const char* ADDCALL sass_source_get_srcmap(struct SassSource* source) + { + return SourceData::unwrap(source).srcmaps(); + } + +} diff --git a/src/capi_context.hpp b/src/capi_context.hpp new file mode 100644 index 0000000000..1d909082a1 --- /dev/null +++ b/src/capi_context.hpp @@ -0,0 +1,47 @@ +#ifndef SASS_SASS_CONTEXT_HPP +#define SASS_SASS_CONTEXT_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "sass/base.h" +#include "sass/context.h" +#include "source_map.hpp" + +// Case 1: create no source-maps +// Case 2: create source-maps, but no reference in css +// Case 3: create source-maps, reference to file in css +// Case 4: create source-maps, embed the json in the css +// Note: Writing source-maps to disk depends on implementor + +struct SassSrcMapOptions { + + enum SassSrcMapMode mode; + + // Flag to embed full sources + // Ignored for SASS_SRCMAP_NONE + bool embed_contents; + + // create file URLs for sources + bool file_urls; + + // Directly inserted in source maps + sass::string root; + + // Path where source map is saved + sass::string path; + + // Path to file that loads us + sass::string origin; + + // Init everything to false + SassSrcMapOptions() : + mode(SASS_SRCMAP_NONE), + embed_contents(true), + file_urls(false) + {} + +}; + +#endif diff --git a/src/capi_error.cpp b/src/capi_error.cpp new file mode 100644 index 0000000000..e695ea5406 --- /dev/null +++ b/src/capi_error.cpp @@ -0,0 +1,104 @@ +#include "capi_error.hpp" + +#include "json.hpp" + +#ifdef __cplusplus +using namespace Sass; +extern "C" { +#endif + + int ADDCALL sass_error_get_status(const struct SassError* error) { return error->status; } + char* ADDCALL sass_error_get_json(const struct SassError* error) { return error->getJson(true); } + const char* ADDCALL sass_error_get_what(const struct SassError* error) { return error->what.c_str(); } + // const char* ADDCALL sass_error_get_messages(struct SassError* error) { return error->messages86.c_str(); } + // const char* ADDCALL sass_error_get_warnings(struct SassError* error) { return error->warnings86.c_str(); } + const char* ADDCALL sass_error_get_formatted(const struct SassError* error) { return error->formatted.c_str(); } + + const char* ADDCALL sass_error_get_path(const struct SassError* error) + { + if (error->traces.empty()) return nullptr; + return error->traces.back().pstate.getAbsPath(); + } + + size_t ADDCALL sass_error_get_line(const struct SassError* error) + { + if (error->traces.empty()) return 0; + return error->traces.back().pstate.getLine(); + } + size_t ADDCALL sass_error_get_column(const struct SassError* error) + { + if (error->traces.empty()) return 0; + return error->traces.back().pstate.getColumn(); + } + const char* ADDCALL sass_error_get_content(const struct SassError* error) + { + if (error->traces.empty()) return 0; + return error->traces.back().pstate.getContent(); + } + + size_t ADDCALL sass_error_count_traces(const struct SassError* error) + { + return error->traces.size(); + } + + const struct SassTrace* ADDCALL sass_error_last_trace(const struct SassError* error) + { + if (error->traces.empty()) return nullptr; + return error->traces.back().wrap(); + } + + const struct SassTrace* ADDCALL sass_error_get_trace(const struct SassError* error, size_t i) + { + if (error->traces.size() < i) return nullptr; + return error->traces.at(i).wrap(); + } + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + + +// Create error formatted as serialized json. +// You must free the data returned from here. +// You must do so by using `sass_free_c_string` +char* SassError::getJson(bool include_sources) const +{ + + // Create json root object + JsonNode* json = json_mkobject(); + + // Attach all stack traces + if (traces.size() > 0) { + JsonNode* json_traces = json_mkarray(); + for (const Sass::StackTrace& trace : traces) { + JsonNode* json_trace = json_mkobject(); + const Sass::SourceSpan& pstate(trace.pstate); + json_append_member(json_trace, "file", json_mkstring(pstate.getAbsPath())); + json_append_member(json_trace, "line", json_mknumber((double)(pstate.getLine()))); + json_append_member(json_trace, "column", json_mknumber((double)(pstate.getColumn()))); + if (include_sources) json_append_member(json_trace, "source", json_mkstring(pstate.getContent())); + json_append_element(json_traces, json_trace); + } + json_append_member(json, "traces", json_traces); + } + + // Attach the generic error reporting items + json_append_member(json, "status", json_mknumber(status)); + json_append_member(json, "error", json_mkstring(what.c_str())); + // json_append_member(json, "messages", json_mkstring(messages86.c_str())); + // json_append_member(json, "warnings", json_mkstring(warnings86.c_str())); + json_append_member(json, "formatted", json_mkstring(formatted.c_str())); + + char* serialized = nullptr; + // Stringification may fail for strange reason + try { serialized = json_stringify(json, " "); } + // If it fails at least return a valid json with special status 9999 + catch (...) { serialized = sass_copy_c_string("{\"status\":9999}"); } + + // Delete json tree + json_delete(json); + + // Return new memory + return serialized; + +} diff --git a/src/capi_error.hpp b/src/capi_error.hpp new file mode 100644 index 0000000000..7b4d5b3475 --- /dev/null +++ b/src/capi_error.hpp @@ -0,0 +1,51 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_SASS_ERROR_HPP +#define SASS_SASS_ERROR_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +// #include "sass/base.h" +#include "backtrace.hpp" +#include "source_span.hpp" +#include "capi_functions.hpp" + +struct SassTraces; + +struct SassError { + +public: + + // Error status + int status; + + // Specific error message + sass::string what; + + // Traces leading up to error + Sass::StackTraces traces; + + // Streams from logger + // Also when status is 0 + + // sass::string messages86; + + // Contains all @debug and deprecation warnings + // Must be all in one to keep the output order + // sass::string warnings86; + + sass::string formatted; + + // Constructor + SassError() : + status(0) + {} + + char* getJson(bool include_sources) const; + +}; + +#endif diff --git a/src/capi_functions.cpp b/src/capi_functions.cpp new file mode 100644 index 0000000000..fd9996df01 --- /dev/null +++ b/src/capi_functions.cpp @@ -0,0 +1,169 @@ +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "capi_functions.hpp" +#include "source.hpp" + +using namespace Sass; + +extern "C" { + + struct SassFunction* ADDCALL sass_make_function(const char* signature, SassFunctionLambda callback, void* cookie) + { + struct SassFunction* cb = new SassFunction{}; + if (cb == 0 || signature == 0) return 0; + cb->signature = sass_copy_c_string(signature); + cb->function = callback; + cb->cookie = cookie; + return cb; + } + + void ADDCALL sass_delete_function(struct SassFunction* function) + { + sass_free_c_string(function->signature); + delete function; + } + + // Setters and getters for callbacks on function lists + // struct SassFunction* ADDCALL sass_function_get_list_entry(struct SassFunctionList* list, uint32_t pos) { return list[pos]; } + // void sass_function_set_list_entry(struct SassFunctionList* list, uint32_t pos, struct SassFunction* cb) { list[pos] = cb; } + + const char* ADDCALL sass_function_get_signature(struct SassFunction* function) + { + return function->signature; + } + + SassFunctionLambda ADDCALL sass_function_get_function(struct SassFunction* function) + { + return function->function; + } + + void* ADDCALL sass_function_get_cookie(struct SassFunction* function) + { + return function->cookie; + } + + struct SassImporter* ADDCALL sass_make_importer(SassImporterLambda callback, double priority, void* cookie) + { + struct SassImporter* importer = new SassImporter{}; + if (importer == 0) return 0; + importer->importer = callback; + importer->priority = priority; + importer->cookie = cookie; + return importer; + } + + SassImporterLambda ADDCALL sass_importer_get_callback(struct SassImporter* importer) { return importer->importer; } + double ADDCALL sass_importer_get_priority (struct SassImporter* importer) { return importer->priority; } + void* ADDCALL sass_importer_get_cookie(struct SassImporter* importer) { return importer->cookie; } + + // Just in case we have some stray import structs + void ADDCALL sass_delete_importer (struct SassImporter* importer) + { + delete importer; + } + + // Creator for a single import entry returned by the custom importer inside the list + // We take ownership of the memory for source and srcmap (freed when context is destroyed) + struct SassImport* ADDCALL sass_make_import(const char* imp_path, const char* abs_path, char* source, char* srcmap, enum SassImportFormat format) + { + Import* import = + SASS_MEMORY_NEW(Import, + SASS_MEMORY_NEW(SourceFile, + imp_path, abs_path, + source ? source : 0, + srcmap ? srcmap : 0), + format); + // Use reference counter + import->refcount += 1; + // Create the shared source object + return Import::wrap(import); + } + + // struct SassImport* ADDCALL sass_make_import_error(const char* error) + // { + // return LoadedImport::wrap( + // new LoadedImport(error) + // ); + // } + + + // Upgrade a normal import entry to throw an error (original path can be re-used by error reporting) + void ADDCALL sass_import_set_error_msg(struct SassImport* import, const char* error, uint32_t line, uint32_t col) + { + if (import == nullptr) return; + // import->err_msg = error ? error : ""; + } + + // Setters and getters for entries on the import list + // void ADDCALL sass_import_set_list_entry(struct SassImportList* list, uint32_t idx, struct SassImport* entry) { list[idx] = entry; } + // struct SassImport* ADDCALL sass_import_get_list_entry(struct SassImportList* list, uint32_t idx) { return list[idx]; } + + // Just in case we have some stray import structs + void ADDCALL sass_delete_import(struct SassImport* import) + { + // std::cerr << "Delete " << (void*)import << "\n"; + Import& object = Import::unwrap(import); + if (object.refcount <= 1) { + object.refcount = 0; + delete& object; + } + else { + object.refcount -= 1; + } + } + + // Getter for callee entry + // const char* ADDCALL sass_callee_get_name(struct SassCallee* entry) { return entry->name; } + // const char* ADDCALL sass_callee_get_path(struct SassCallee* entry) { return entry->path; } + // uint32_t ADDCALL sass_callee_get_line(struct SassCallee* entry) { return entry->line; } + // uint32_t ADDCALL sass_callee_get_column(struct SassCallee* entry) { return entry->column; } + // enum Sass_Callee_Type ADDCALL sass_callee_get_type(struct SassCallee* entry) { return entry->type; } + + // Getters and Setters for environments (lexical, local and global) + struct SassValue* ADDCALL sass_env_get_lexical (struct SassCompiler* compiler, const char* name) { + return 0; // Value::wrap(reinterpret_cast(compiler->context)->getVariableIdx(Sass::EnvKey(name))); + } + + void ADDCALL sass_env_set_lexical (struct SassCompiler* compiler, const char* name, struct SassValue* val) { + // Compiler::unwrap(compiler).setLexicalVariable(Sass::EnvKey(name), reinterpret_cast(val)); + } + + struct SassValue* ADDCALL sass_env_get_local (struct SassCompiler* compiler, const char* name) { + return 0; // return Value::wrap(reinterpret_cast(compiler->context)->getLocalVariable(Sass::EnvKey(name))); + } + + void ADDCALL sass_env_set_local (struct SassCompiler* compiler, const char* name, struct SassValue* val) { + // reinterpret_cast(compiler->context)->setLocalVariable(Sass::EnvKey(name), reinterpret_cast(val)); + } + + struct SassValue* ADDCALL sass_env_get_global (struct SassCompiler* compiler, const char* name) { + return 0; // Value::wrap(reinterpret_cast(compiler->context)->getGlobalVariable(Sass::EnvKey(name))); + } + + void ADDCALL sass_env_set_global (struct SassCompiler* compiler, const char* name, struct SassValue* val) { + // Compiler::unwrap(compiler).setGlobalVariable(Sass::EnvKey(name), reinterpret_cast(val)); + } + + // Getter for import entry + const char* ADDCALL sass_import_get_imp_path(struct SassImport* entry) { return Import::unwrap(entry).getImpPath(); } + const char* ADDCALL sass_import_get_abs_path(struct SassImport* entry) { return Import::unwrap(entry).getAbsPath(); } + // const char* ADDCALL sass_import_get_source(struct SassImport* entry) { return Import::unwrap(entry).getContents(); } + // const char* ADDCALL sass_import_get_srcmap(struct SassImport* entry) { return Import::unwrap(entry).source->srcmaps(); } + enum SassImportFormat ADDCALL sass_import_get_type(struct SassImport* entry) { return Import::unwrap(entry).syntax; } + + // Getter for import error entry + // uint32_t ADDCALL sass_import_get_error_line(struct SassImport* entry) { return entry->line; } + // uint32_t ADDCALL sass_import_get_error_column(struct SassImport* entry) { return entry->column; } + const char* ADDCALL sass_import_get_error_message(struct SassImport* entry) { + return 0; + // return entry->err_msg.empty() ? 0 : entry->err_msg.c_str(); + } + + // 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 SassImport* entry) { char* ptr = entry->source; entry->source = 0; return ptr; } + // char* ADDCALL sass_import_take_srcmap(struct SassImport* entry) { char* ptr = entry->srcmap; entry->srcmap = 0; return ptr; } + +} diff --git a/src/capi_functions.hpp b/src/capi_functions.hpp new file mode 100644 index 0000000000..b8eff8bdb7 --- /dev/null +++ b/src/capi_functions.hpp @@ -0,0 +1,24 @@ +#ifndef SASS_SASS_FUNCTIONS_HPP +#define SASS_SASS_FUNCTIONS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +// Struct to hold custom function callback +struct SassFunction { + char* signature; + SassFunctionLambda function; + void* cookie; +}; + +class SourceDataObj; + +// Struct to hold importer callback +struct SassImporter { + SassImporterLambda importer; + double priority; + void* cookie; +}; + +#endif diff --git a/src/capi_lists.cpp b/src/capi_lists.cpp new file mode 100644 index 0000000000..2766b68b57 --- /dev/null +++ b/src/capi_lists.cpp @@ -0,0 +1,214 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "capi_lists.hpp" + +using namespace Sass; + +extern "C" { + + struct SassImportList* ADDCALL sass_make_import_list() + { + return new SassImportList; + } + + struct SassImporterList* ADDCALL sass_make_importer_list() + { + return new SassImporterList; + } + + struct SassFunctionList* ADDCALL sass_make_function_list() + { + return new SassFunctionList; + } + + void ADDCALL sass_delete_import_list(struct SassImportList* list) + { + if (list == nullptr) return; + for (auto import : *list) { + sass_delete_import(import); + } + delete list; + } + + void ADDCALL sass_delete_importer_list(struct SassImporterList* list) + { + if (list == nullptr) return; + for (auto importer : *list) { + sass_delete_importer(importer); + } + delete list; + } + + void ADDCALL sass_delete_function_list(struct SassFunctionList* list) + { + if (list == nullptr) return; + for (auto importer : *list) { + sass_delete_function(importer); + } + delete list; + } + + size_t ADDCALL sass_import_list_size(struct SassImportList* list) + { + return list == nullptr ? 0 : list->size(); + } + + size_t ADDCALL sass_importer_list_size(struct SassImporterList* list) + { + return list == nullptr ? 0 : list->size(); + } + + size_t ADDCALL sass_function_list_size(struct SassFunctionList* list) + { + return list == nullptr ? 0 : list->size(); + } + + struct SassImport* ADDCALL sass_import_list_shift(struct SassImportList* list) + { + if (list == nullptr) return nullptr; + if (list->empty()) return nullptr; + auto ptr = list->front(); + list->pop_front(); + return ptr; + } + + struct SassImporter* ADDCALL sass_importer_list_shift(struct SassImporterList* list) + { + if (list == nullptr) return nullptr; + if (list->empty()) return nullptr; + auto ptr = list->front(); + list->pop_front(); + return ptr; + } + + struct SassFunction* ADDCALL sass_function_list_shift(struct SassFunctionList* list) + { + if (list == nullptr) return nullptr; + if (list->empty()) return nullptr; + auto ptr = list->front(); + //if (list->size() > 0) { + // list->erase(list->begin()); + //} + list->pop_front(); + return ptr; + } + + void ADDCALL sass_import_list_push(struct SassImportList* list, struct SassImport* import) + { + if (list == nullptr) return; + list->push_back(import); + } + + void ADDCALL sass_importer_list_push(struct SassImporterList* list, struct SassImporter* importer) + { + if (list == nullptr) return; + list->push_back(importer); + } + + void ADDCALL sass_function_list_push(struct SassFunctionList* list, struct SassFunction* function) + { + if (list == nullptr) return; + list->push_back(function); + } + +} + +namespace Sass { + + extern "C" { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + /* + + + + */ + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + /* + struct SassFunctionList* ADDCALL sass_make_function_list() + { + return new SassFunctionList{}; + } + + void ADDCALL sass_delete_function_list(struct SassFunctionList* list) + { + if (list == nullptr) return; + for (auto function : *list) { + sass_delete_function(function); + } + delete list; + } + + size_t ADDCALL sass_function_list_size(struct SassFunctionList* list) + { + return list == nullptr ? 0 : list->size(); + } + + SassFunction* ADDCALL sass_function_list_shift(struct SassFunctionList* list) + { + if (list == nullptr) return nullptr; + if (list->empty()) return nullptr; + auto ptr = list->front(); + list->pop_front(); + return ptr; + } + + void ADDCALL sass_function_list_push(struct SassFunctionList* list, struct SassFunction* fn) + { + if (list != nullptr) { + if (fn != nullptr) { + list->emplace_back(fn); + } + } + } + */ + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + /* + struct SassImporterList* ADDCALL sass_make_importer_list() + { + return new SassImporterList{}; + } + + void ADDCALL sass_delete_importer_list(struct SassImporterList* list) + { + if (list == nullptr) return; + for (auto importer : *list) { + sass_delete_importer(importer); + } + delete list; + } + + size_t ADDCALL sass_importer_list_size(struct SassImporterList* list) + { + return list == nullptr ? 0 : list->size(); + } + + struct SassImporter* ADDCALL sass_importer_list_shift(struct SassImporterList* list) + { + if (list == nullptr) return nullptr; + if (list->empty()) return nullptr; + auto ptr = list->front(); + list->pop_front(); + return ptr; + } + + void ADDCALL sass_importer_list_push(struct SassImporterList* list, struct SassImporter* importer) + { + if (list != nullptr) { + if (importer != nullptr) { + list->emplace_back(importer); + } + } + } + */ + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + } + +} diff --git a/src/capi_lists.hpp b/src/capi_lists.hpp new file mode 100644 index 0000000000..7a7959db3c --- /dev/null +++ b/src/capi_lists.hpp @@ -0,0 +1,17 @@ +#ifndef SASS_SASS_LISTS_H +#define SASS_SASS_LISTS_H + +#include +#include +#include + +#include + +// Some structures are simple c++ vectors. +// There might be a more efficient way to achieve this? +// Although compiler optimization should see this case easily! +struct SassImportList : std::deque {}; +struct SassImporterList : std::deque {}; +struct SassFunctionList : std::deque {}; + +#endif diff --git a/src/capi_sass.cpp b/src/capi_sass.cpp new file mode 100644 index 0000000000..907a08d736 --- /dev/null +++ b/src/capi_sass.cpp @@ -0,0 +1,129 @@ +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include +#include "source.hpp" +#include "compiler.hpp" +#include + +namespace Sass { +extern "C" { + + // Allocate a memory block on the heap of (at least) [size]. + // Make sure to release to acquired memory at some later point via + // `sass_free_memory`. You need to go through my utility function in + // case your code and my main program don't use the same memory manager. + void* ADDCALL sass_alloc_memory(size_t size) + { + void* ptr = malloc(size); + if (ptr == NULL) { + std::cerr << "Out of memory.\n"; + exit(EXIT_FAILURE); + } + return ptr; + } + + // Allocate a memory block on the heap and copy [string] into it. + // Make sure to release to acquired memory at some later point via + // `sass_free_memory`. You need to go through my utility function in + // case your code and my main program don't use the same memory manager. + char* ADDCALL sass_copy_c_string(const char* string) + { + if (string == nullptr) return nullptr; + size_t len = ::strlen(string) + 1; + char* cpy = (char*)sass_alloc_memory(len); + ::memcpy(cpy, string, len); + return cpy; + } + + // Deallocate libsass heap memory + void ADDCALL sass_free_c_string(char* ptr) + { + if (ptr) free(ptr); + } + + // Deallocate libsass heap memory + void ADDCALL sass_free_memory(void* ptr) + { + if (ptr) free(ptr); + } + + char* ADDCALL sass_compiler_find_include (const char* file, struct SassCompiler* compiler) + { + std::cerr << "YEP, find include\n"; + /* + // get the last import entry to get current base directory + SassImportPtr import = sass_compiler_get_last_import(compiler); + const sass::vector& incs = compiler->cpp_ctx->includePaths; + // create the vector with paths to lookup + sass::vector paths(1 + incs.size()); + paths.emplace_back(File::dir_name(import->srcdata->getAbsPath())); + paths.insert( paths.end(), incs.begin(), incs.end() ); + // now resolve the file path relative to lookup paths + sass::string resolved(File::find_include(file, + compiler->cpp_ctx->CWD, paths, compiler->cpp_ctx->fileExistsCache)); + return sass_copy_c_string(resolved.c_str()); + */ + return 0; + } + + char* ADDCALL sass_compiler_find_file (const char* file, struct SassCompiler* compiler) + { + sass::string path(file); + path = Compiler::unwrap(compiler).findFile(path); + return path.empty() ? nullptr : sass_copy_c_string(path.c_str()); + } + + // Get compiled libsass version + const char* ADDCALL libsass_version(void) + { + return LIBSASS_VERSION; + } + + // Get compiled libsass version + const char* ADDCALL sass2scss_version(void) + { + return "obsolete"; + } + + // Get compiled libsass version + const char* ADDCALL libsass_language_version(void) + { + return LIBSASS_LANGUAGE_VERSION; + } + +} +} + +namespace Sass { + + double round32(double val, int precision) + { + // https://github.com/sass/sass/commit/4e3e1d5684cc29073a507578fc977434ff488c93 + if (std::fmod(val, 1) - 0.5 > -std::pow(0.1, precision + 1)) return std::ceil(val); + else if (std::fmod(val, 1) - 0.5 > std::pow(0.1, precision)) return std::floor(val); + // Work around some compiler issue + // Cygwin has it not defined in std + using namespace std; + return ::round(val); + } + + // helper to aid dreaded MSVC debug mode + char* sass_copy_string(sass::string str) + { + // In MSVC the following can lead to segfault: + // sass_copy_c_string(stream.str().c_str()); + // Reason is that the string returned by str() is disposed before + // sass_copy_c_string is invoked. The string is actually a stack + // object, so indeed nobody is holding on to it. So it seems + // perfectly fair to release it right away. So the const char* + // by c_str will point to invalid memory. I'm not sure if this is + // the behavior for all compiler, but I'm pretty sure we would + // have gotten more issues reported if that would be the case. + // Wrapping it in a functions seems the cleanest approach as the + // function must hold on to the stack variable until it's done. + return sass_copy_c_string(str.c_str()); + } + +} diff --git a/src/sass.hpp b/src/capi_sass.hpp similarity index 51% rename from src/sass.hpp rename to src/capi_sass.hpp index f202bf79bf..ee9d8ccc1c 100644 --- a/src/sass.hpp +++ b/src/capi_sass.hpp @@ -2,13 +2,16 @@ #ifndef SASS_SASS_H #define SASS_SASS_H -// undefine extensions macro to tell sys includes +// #define DEBUG_SHARED_PTR + +// Undefine extensions macro to tell sys includes // that we do not want any macros to be exported // mainly fixes an issue on SmartOS (SEC macro) #undef __EXTENSIONS__ #ifdef _MSC_VER #pragma warning(disable : 4005) +#pragma warning(disable : 26812) #endif // applies to MSVC and MinGW @@ -47,29 +50,50 @@ # endif #endif +// OS specific line feed +// since std::endl flushes +#ifndef STRMLF +# ifdef _WIN32 +# define STRMLF '\n' +# else +# define STRMLF '\n' +# endif +#endif -// Include C-API header +// include C-API header #include "sass/base.h" // Include allocator #include "memory.hpp" -// For C++ helper -#include -#include +// Include random seed +#include "randomize.hpp" + +// #include "../../parallel-hashmap/parallel_hashmap/phmap.h" +// #include "../../ordered-map/include/tsl/ordered_set.h" +// #include "robin_hood.hpp" + +#ifdef USE_TSL_HOPSCOTCH +#include "tessil/hopscotch_map.h" +#include "tessil/hopscotch_set.h" +#define UnorderedMap tsl::hopscotch_map +#define UnorderedSet tsl::hopscotch_set +#else +#include +#include +#define UnorderedMap std::unordered_map +#define UnorderedSet std::unordered_set +#endif + +// Always use tessil implementation +#include "tessil/ordered_map.h" +#define OrderedMap tsl::ordered_map -// output behavior -namespace Sass { +// Small helper to avoid typing +#define NPOS std::string::npos - // create some C++ aliases for the most used options - const static Sass_Output_Style NESTED = SASS_STYLE_NESTED; - const static Sass_Output_Style COMPACT = SASS_STYLE_COMPACT; - const static Sass_Output_Style EXPANDED = SASS_STYLE_EXPANDED; - const static Sass_Output_Style COMPRESSED = SASS_STYLE_COMPRESSED; - // only used internal to trigger ruby inspect behavior - const static Sass_Output_Style INSPECT = SASS_STYLE_INSPECT; - const static Sass_Output_Style TO_SASS = SASS_STYLE_TO_SASS; - const static Sass_Output_Style TO_CSS = SASS_STYLE_TO_CSS; +// output behaviors +namespace Sass { // helper to aid dreaded MSVC debug mode // see implementation for more details @@ -77,32 +101,25 @@ namespace Sass { } -// input behaviors -enum Sass_Input_Style { - SASS_CONTEXT_NULL, - SASS_CONTEXT_FILE, - SASS_CONTEXT_DATA, - SASS_CONTEXT_FOLDER -}; // simple linked list -struct string_list { - string_list* next; - char* string; -}; +//struct string_list { +// string_list* next; +// char* string; +//}; // sass config options structure struct Sass_Inspect_Options { - // Output style for the generated css code + // Output style for the generated CSS code // A value from above SASS_STYLE_* constants - enum Sass_Output_Style output_style; + enum SassOutputStyle output_style; // Precision for fractional numbers int precision; // initialization list (constructor with defaults) - Sass_Inspect_Options(Sass_Output_Style style = Sass::NESTED, + Sass_Inspect_Options(SassOutputStyle style = SASS_STYLE_NESTED, int precision = 10) : output_style(style), precision(precision) { } @@ -110,7 +127,7 @@ struct Sass_Inspect_Options { }; // sass config options structure -struct Sass_Output_Options : Sass_Inspect_Options { +struct SassOutputOptionsCpp : Sass_Inspect_Options { // String to be used for indentation const char* indent; @@ -122,7 +139,7 @@ struct Sass_Output_Options : Sass_Inspect_Options { bool source_comments; // initialization list (constructor with defaults) - Sass_Output_Options(struct Sass_Inspect_Options opt, + SassOutputOptionsCpp(struct Sass_Inspect_Options& opt, const char* indent = " ", const char* linefeed = "\n", bool source_comments = false) @@ -132,7 +149,7 @@ struct Sass_Output_Options : Sass_Inspect_Options { { } // initialization list (constructor with defaults) - Sass_Output_Options(Sass_Output_Style style = Sass::NESTED, + SassOutputOptionsCpp(SassOutputStyle style = SASS_STYLE_NESTED, int precision = 10, const char* indent = " ", const char* linefeed = "\n", @@ -144,4 +161,40 @@ struct Sass_Output_Options : Sass_Inspect_Options { }; +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +namespace Sass { + + template + T clamp(const T& n, const T& lower, const T& upper) { + return std::max(lower, std::min(n, upper)); + } + + template + T absmod(const T& n, const T& r) { + T m = std::fmod(n, r); + if (m < 0.0) m += r; + return m; + } + + double round32(double val, int precision = SassDefaultPrecision); + +} + + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +#ifdef NDEBUG +// #define SASS_ASSERT(cond, msg) ((void)0) +#define SASS_ASSERT(cond, msg) assert(cond && msg) +#else +#ifdef DEBUG +#define SASS_ASSERT(cond, msg) assert(cond && msg) +#else +#define SASS_ASSERT(cond, msg) ((void)0) +#endif +#endif + #endif diff --git a/src/capi_values.cpp b/src/capi_values.cpp new file mode 100644 index 0000000000..f3823ed255 --- /dev/null +++ b/src/capi_values.cpp @@ -0,0 +1,318 @@ +#include "capi_values.hpp" + +#include "exceptions.hpp" +#include "ast_values.hpp" + +using namespace Sass; + +struct SassMapIterator { + Hashed::ordered_map_type::iterator pos; + Hashed::ordered_map_type::iterator end; +}; + +extern "C" { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Map* getMap(struct SassValue* value) { return reinterpret_cast(value); } + List* getList(struct SassValue* value) { return reinterpret_cast(value); } + Value* getValue(struct SassValue* value) { return reinterpret_cast(value); } + Number* getNumber(struct SassValue* value) { return reinterpret_cast(value); } + String* getString(struct SassValue* value) { return reinterpret_cast(value); } + Boolean* getBoolean(struct SassValue* value) { return reinterpret_cast(value); } + ColorRgba* getColor(struct SassValue* value) { return reinterpret_cast(value); } + CustomError* getError(struct SassValue* value) { return reinterpret_cast(value); } + CustomWarning* getWarning(struct SassValue* value) { return reinterpret_cast(value); } + + // Return another reference to an existing value. We simply re-use the reference counted + // object and re-implement the memory handling also partially here (SharedImpl lite). + struct SassValue* newSassValue(Value* value) { value->refcount += 1; return value->wrap(); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Return the sass tag for a generic sass value + enum SassValueType ADDCALL sass_value_get_tag(struct SassValue* v) { return getValue(v)->getTag(); } + + // Check value for a specific type (dispatch to virtual check methods) + bool ADDCALL sass_value_is_null(struct SassValue* val) { return Value::unwrap(val).isNull(); } + bool ADDCALL sass_value_is_number(struct SassValue* val) { return Value::unwrap(val).isaNumber(); } + bool ADDCALL sass_value_is_string(struct SassValue* val) { return Value::unwrap(val).isaString(); } + bool ADDCALL sass_value_is_boolean(struct SassValue* val) { return Value::unwrap(val).isaBoolean(); } + bool ADDCALL sass_value_is_color(struct SassValue* val) { return Value::unwrap(val).isaColor(); } + bool ADDCALL sass_value_is_list(struct SassValue* val) { return Value::unwrap(val).isaList(); } + bool ADDCALL sass_value_is_map(struct SassValue* val) { return Value::unwrap(val).isaMap(); } + bool ADDCALL sass_value_is_error(struct SassValue* val) { return Value::unwrap(val).isaCustomError(); } + bool ADDCALL sass_value_is_warning(struct SassValue* val) { return Value::unwrap(val).isaCustomWarning(); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getters and setters for Sass_Number (UB if `sass_value_is_number` is false) + double ADDCALL sass_number_get_value(struct SassValue* number) { return getNumber(number)->value(); } + void ADDCALL sass_number_set_value(struct SassValue* number, double value) { getNumber(number)->value(value); } + const char* ADDCALL sass_number_get_unit(struct SassValue* number) { return getNumber(number)->unit().c_str(); } + void ADDCALL sass_number_set_unit(struct SassValue* number, const char* unit) { getNumber(number)->unit(unit); } + // Normalize number and its units to standard units, e.g. `ms` will become `s` (useful for comparisons) + void ADDCALL sass_number_normalize(struct SassValue* number) { getNumber(number)->normalize(); } + // Reduce number and its units to a minimal form, e.g. `ms*ms/s` will become `ms` (useful for output) + void ADDCALL sass_number_reduce(struct SassValue* number) { getNumber(number)->reduce(); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getters and setters for Sass_String (UB if `sass_value_is_boolean` is false) + const char* ADDCALL sass_string_get_value(struct SassValue* string) { return getString(string)->value().c_str(); } + void ADDCALL sass_string_set_value(struct SassValue* string, char* value) { getString(string)->value(value); } + bool ADDCALL sass_string_is_quoted(struct SassValue* string) { return getString(string)->hasQuotes(); } + void ADDCALL sass_string_set_quoted(struct SassValue* string, bool quoted) { getString(string)->hasQuotes(quoted); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getters and setters for Sass_Boolean (UB if `sass_value_is_number` is false) + bool ADDCALL sass_boolean_get_value(struct SassValue* boolean) { return getBoolean(boolean)->value(); } + void ADDCALL sass_boolean_set_value(struct SassValue* boolean, bool value) { getBoolean(boolean)->value(value); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getters and setters for Sass_Color (UB if `sass_value_is_color` is false) + double ADDCALL sass_color_get_r(struct SassValue* color) { return getColor(color)->r(); } + void ADDCALL sass_color_set_r(struct SassValue* color, double r) { getColor(color)->r(r); } + double ADDCALL sass_color_get_g(struct SassValue* color) { return getColor(color)->g(); } + void ADDCALL sass_color_set_g(struct SassValue* color, double g) { getColor(color)->g(g); } + double ADDCALL sass_color_get_b(struct SassValue* color) { return getColor(color)->b(); } + void ADDCALL sass_color_set_b(struct SassValue* color, double b) { getColor(color)->b(b); } + double ADDCALL sass_color_get_a(struct SassValue* color) { return getColor(color)->a(); } + void ADDCALL sass_color_set_a(struct SassValue* color, double a) { getColor(color)->a(a); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Return the value stored at the given key (or nullptr if it doesn't exist) + struct SassValue* ADDCALL sass_map_get(struct SassValue* map, struct SassValue* key) + { + auto value = getMap(map)->at(getValue(key)); + if (value.isNull()) return nullptr; + return Value::wrap(value); + } + + // Set or create the value in the map for the given key + void ADDCALL sass_map_set(struct SassValue* map, struct SassValue* key, struct SassValue* value) + { + getMap(map)->insertOrSet(getValue(key), getValue(value)); + } + + // Create an iterator to loop over all key/value pairs of this map + // This iterator will get invalid once you alter the underlying map + struct SassMapIterator* ADDCALL sass_map_make_iterator(struct SassValue* map) + { + return new SassMapIterator{ getMap(map)->begin(), getMap(map)->end() }; + } + + // Delete the iterator after you are done with it + void ADDCALL sass_map_delete_iterator(struct SassMapIterator* it) + { + delete it; + } + + // Get key for the current map iterator position + struct SassValue* ADDCALL sass_map_iterator_get_key(struct SassMapIterator* it) + { + return Value::wrap(it->pos->first); + } + + // Get value for the current map iterator position + struct SassValue* ADDCALL sass_map_iterator_get_value(struct SassMapIterator* it) + { + return Value::wrap(it->pos->second); + } + + // Returns true once the iterator has reached the end + bool ADDCALL sass_map_iterator_exhausted(struct SassMapIterator* it) + { + return it->pos == it->end; + } + + // Advance the iterator to the next key/value pair or the end + void ADDCALL sass_map_iterator_next(struct SassMapIterator* it) + { + it->pos += 1; + } + + ///////////////////////////////////////////////////////////////////////// + // ToDo: should list also have an iterator, is it so useful and cool? + // ToDo: Index access has the advantage it is not invalidated ever. + ///////////////////////////////////////////////////////////////////////// + + // Getters and setters for Sass_List + size_t ADDCALL sass_list_get_size(struct SassValue* v) { return getList(v)->size(); } + + enum SassSeparator ADDCALL sass_list_get_separator(struct SassValue* v) { return getList(v)->separator(); } + void ADDCALL sass_list_set_separator(struct SassValue* v, enum SassSeparator separator) { getList(v)->separator(separator); } + bool ADDCALL sass_list_get_is_bracketed(struct SassValue* v) { return getList(v)->hasBrackets(); } + void ADDCALL sass_list_set_is_bracketed(struct SassValue* v, bool is_bracketed) { getList(v)->hasBrackets(is_bracketed); } + // Getters and setters for Sass_List values + // TODO: also have at! + struct SassValue* ADDCALL sass_list_get_value(struct SassValue* v, size_t i) { return Value::wrap(getList(v)->at(i)); } + void ADDCALL sass_list_set_value(struct SassValue* v, size_t i, struct SassValue* value) { getList(v)->at(i) = getValue(value); } + + // Getters and setters for Sass_Error + const char* ADDCALL sass_error_get_message(struct SassValue* v) { return getError(v)->message().c_str(); }; + void ADDCALL sass_error_set_message(struct SassValue* v, const char* msg) { getError(v)->message(msg); }; + + // Getters and setters for Sass_Warning + const char* ADDCALL sass_warning_get_message(struct SassValue* v) { return getWarning(v)->message().c_str(); }; + void ADDCALL sass_warning_set_message(struct SassValue* v, const char* msg) { getWarning(v)->message(msg); }; + + void ADDCALL sass_list_push(struct SassValue* list, struct SassValue* value) { getList(list)->append(getValue(value)); } + void ADDCALL sass_list_unshift(struct SassValue* list, struct SassValue* value) { getList(list)->unshift(getValue(value)); } + struct SassValue* ADDCALL sass_list_at(struct SassValue* list, size_t i) { return Value::wrap(getList(list)->at(i)); } + struct SassValue* ADDCALL sass_list_pop(struct SassValue* list, struct SassValue* value) { return Value::wrap(getList(list)->pop()); } + struct SassValue* ADDCALL sass_list_shift(struct SassValue* list, struct SassValue* value) { return Value::wrap(getList(list)->shift()); } + + ///////////////////////////////////////////////////////////////////////// + // Constructor functions for all value types + ///////////////////////////////////////////////////////////////////////// + + struct SassValue* ADDCALL sass_make_boolean(bool state) + { + return newSassValue(SASS_MEMORY_NEW( + Boolean, SourceSpan::tmp("sass://boolean"), state)); + } + + struct SassValue* ADDCALL sass_make_number(double val, const char* unit) + { + return newSassValue(SASS_MEMORY_NEW( + Number, SourceSpan::tmp("sass://number"), val, unit ? unit : "")); + } + + struct SassValue* ADDCALL sass_make_color(double r, double g, double b, double a) + { + return newSassValue(SASS_MEMORY_NEW( + ColorRgba, SourceSpan::tmp("sass://color"), r, g, b, a)); + } + + struct SassValue* ADDCALL sass_make_string(const char* val, bool is_quoted) + { + return newSassValue(SASS_MEMORY_NEW( + String, SourceSpan::tmp("sass://string"), val, is_quoted)); + } + + struct SassValue* ADDCALL sass_make_list(enum SassSeparator sep, bool is_bracketed) + { + return newSassValue(SASS_MEMORY_NEW( + List, SourceSpan::tmp("sass://list"), {}, sep, is_bracketed)); + } + + struct SassValue* ADDCALL sass_make_map() + { + return newSassValue(SASS_MEMORY_NEW( + Map, SourceSpan::tmp("sass://map"))); + } + + struct SassValue* ADDCALL sass_make_null(void) + { + return newSassValue(SASS_MEMORY_NEW( + Null, SourceSpan::tmp("sass://null"))); + } + + struct SassValue* ADDCALL sass_make_error(const char* msg) + { + return newSassValue(SASS_MEMORY_NEW( + CustomError, SourceSpan::tmp("sass://error"), msg)); + } + + struct SassValue* ADDCALL sass_make_warning(const char* msg) + { + return newSassValue(SASS_MEMORY_NEW( + CustomWarning, SourceSpan::tmp("sass://warning"), msg)); + } + + ///////////////////////////////////////////////////////////////////////// + // will free all associated sass values + ///////////////////////////////////////////////////////////////////////// + + void ADDCALL sass_delete_value(struct SassValue* val) + { + Value* value = getValue(val); + if (value) { + value->refcount -= 1; + if (value->refcount == 0) { + delete value; + } + } + } + + ///////////////////////////////////////////////////////////////////////// + // Make a deep cloned copy of the given sass value + ///////////////////////////////////////////////////////////////////////// + + struct SassValue* ADDCALL sass_clone_value(struct SassValue* val) + { + Value* copy = getValue(val)->copy(SASS_MEMORY_POS_VOID); + copy->cloneChildren(SASS_MEMORY_POS_VOID); + return newSassValue(copy); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + struct SassValue* ADDCALL sass_value_stringify(struct SassValue* v, bool compressed, int precision) + { + Value* val = getValue(v); + // Sass_Inspect_Options options(compressed ? + // SASS_STYLE_COMPRESSED : SASS_STYLE_NESTED, precision); + sass::string str(val->inspect(/*options*/)); + return sass_make_string(str.c_str(), true); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // ToDo: remove and re-implement, we need to log stuff! + struct SassValue* ADDCALL sass_value_op(enum SassOperator op, struct SassValue* left, struct SassValue* right) + { + + Logger logger; + SourceSpan pstate; + Sass::ValueObj copy; + Value* lhs = getValue(left); + Value* rhs = getValue(right); + + try { + + switch (op) { + case SassOperator::OR: return Value::wrap(lhs->isTruthy() ? lhs : rhs); + case SassOperator::AND: return Value::wrap(lhs->isTruthy() ? rhs : lhs); + case SassOperator::ADD: copy = lhs->plus(rhs, logger, pstate); break; + case SassOperator::SUB: copy = lhs->minus(rhs, logger, pstate); break; + case SassOperator::MUL: copy = lhs->times(rhs, logger, pstate); break; + case SassOperator::DIV: copy = lhs->dividedBy(rhs, logger, pstate); break; + case SassOperator::MOD: copy = lhs->modulo(rhs, logger, pstate); break; + case SassOperator::EQ: return sass_make_boolean(PtrObjEqualityFn(rhs, lhs)); + case SassOperator::NEQ: return sass_make_boolean(!PtrObjEqualityFn(rhs, lhs)); + case SassOperator::GT: return sass_make_boolean(lhs->greaterThan(rhs, logger, pstate)); + case SassOperator::GTE: return sass_make_boolean(lhs->greaterThanOrEquals(rhs, logger, pstate)); + case SassOperator::LT: return sass_make_boolean(lhs->lessThan(rhs, logger, pstate)); + case SassOperator::LTE: return sass_make_boolean(lhs->lessThanOrEquals(rhs, logger, pstate)); + default: throw Exception::SassScriptException("Not implemented.", logger, pstate); + } + + copy->refcount += 1; + return copy->wrap(); + } +// +// // simply pass the error message back to the caller for now +// // catch (Exception::InvalidSass& e) { return sass_make_error(e.what()); } + catch (std::bad_alloc&) { return sass_make_error("memory exhausted"); } + catch (std::exception & e) { return sass_make_error(e.what()); } + catch (sass::string & e) { return sass_make_error(e.c_str()); } + catch (const char* e) { return sass_make_error(e); } + catch (...) { return sass_make_error("unknown"); } + + } + +} diff --git a/src/capi_values.hpp b/src/capi_values.hpp new file mode 100644 index 0000000000..ee4f8f953f --- /dev/null +++ b/src/capi_values.hpp @@ -0,0 +1,11 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_SASS_VALUES_HPP +#define SASS_SASS_VALUES_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#endif diff --git a/src/cencode.c b/src/cencode.c deleted file mode 100644 index 3932fcc3e2..0000000000 --- a/src/cencode.c +++ /dev/null @@ -1,106 +0,0 @@ -/* -cencoder.c - c source to a base64 encoding algorithm implementation - -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ - -#include "b64/cencode.h" - -void base64_init_encodestate(base64_encodestate* state_in) -{ - state_in->step = step_A; - state_in->result = 0; - state_in->stepcount = 0; -} - -char base64_encode_value(char value_in) -{ - static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - if (value_in > 63) return '='; - return encoding[(int)value_in]; -} - -int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) -{ - const char* plainchar = plaintext_in; - const char* const plaintextend = plaintext_in + length_in; - char* codechar = code_out; - char result; - char fragment; - - result = state_in->result; - - switch (state_in->step) - { - while (1) - { - case step_A: - if (plainchar == plaintextend) - { - state_in->result = result; - state_in->step = step_A; - return (int)(codechar - code_out); - } - fragment = *plainchar++; - result = (fragment & 0x0fc) >> 2; - *codechar++ = base64_encode_value(result); - result = (fragment & 0x003) << 4; - /* fall through */ - - case step_B: - if (plainchar == plaintextend) - { - state_in->result = result; - state_in->step = step_B; - return (int)(codechar - code_out); - } - fragment = *plainchar++; - result |= (fragment & 0x0f0) >> 4; - *codechar++ = base64_encode_value(result); - result = (fragment & 0x00f) << 2; - /* fall through */ - - case step_C: - if (plainchar == plaintextend) - { - state_in->result = result; - state_in->step = step_C; - return (int)(codechar - code_out); - } - fragment = *plainchar++; - result |= (fragment & 0x0c0) >> 6; - *codechar++ = base64_encode_value(result); - result = (fragment & 0x03f) >> 0; - *codechar++ = base64_encode_value(result); - - ++(state_in->stepcount); - } - } - /* control should not reach here */ - return (int)(codechar - code_out); -} - -int base64_encode_blockend(char* code_out, base64_encodestate* state_in) -{ - char* codechar = code_out; - - switch (state_in->step) - { - case step_B: - *codechar++ = base64_encode_value(state_in->result); - *codechar++ = '='; - *codechar++ = '='; - break; - case step_C: - *codechar++ = base64_encode_value(state_in->result); - *codechar++ = '='; - break; - case step_A: - break; - } - *codechar++ = '\n'; - - return (int)(codechar - code_out); -} - diff --git a/src/cencode.cpp b/src/cencode.cpp new file mode 100644 index 0000000000..d40cccdee6 --- /dev/null +++ b/src/cencode.cpp @@ -0,0 +1,108 @@ +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#include "b64/cencode.h" + +namespace base64 { + + void base64_init_encodestate(base64_encodestate* state_in) + { + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; + } + + char base64_encode_value(char value_in) + { + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; + } + + int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) + { + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + /* fall through */ + + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + /* fall through */ + + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + } + } + /* control should not reach here */ + return (int)(codechar - code_out); + } + + int base64_encode_blockend(char* code_out, base64_encodestate* state_in) + { + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + + return (int)(codechar - code_out); + } + +} diff --git a/src/character.cpp b/src/character.cpp new file mode 100644 index 0000000000..d3b5d5dc64 --- /dev/null +++ b/src/character.cpp @@ -0,0 +1,97 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "character.hpp" + +namespace Sass +{ + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Character + { + + const std::bitset<256> tblNewline( + "00000000000000000000000000000000" // 255 - 224 + "00000000000000000000000000000000" // 223 - 192 + "00000000000000000000000000000000" // 192 - 160 + "00000000000000000000000000000000" // 159 - 128 + "00000000000000000000000000000000" // 127 - 96 + "00000000000000000000000000000000" // 95 - 64 + "00000000000000000000000000000000" // 63 - 32 + "00000000000000000011010000000000" // 31 - 0 + ); + + const std::bitset<256> tblSpaceOrTab( + "00000000000000000000000000000000" // 255 - 224 + "00000000000000000000000000000000" // 223 - 192 + "00000000000000000000000000000000" // 192 - 160 + "00000000000000000000000000000000" // 159 - 128 + "00000000000000000000000000000000" // 127 - 96 + "00000000000000000000000000000000" // 95 - 64 + "00000000000000000000000000000001" // 63 - 32 + "00000000000000000000101000000000" // 31 - 0 + ); + + const std::bitset<256> tblWhitespace( + "00000000000000000000000000000000" // 255 - 224 + "00000000000000000000000000000000" // 223 - 192 + "00000000000000000000000000000000" // 192 - 160 + "00000000000000000000000000000000" // 159 - 128 + "00000000000000000000000000000000" // 127 - 96 + "00000000000000000000000000000000" // 95 - 64 + "00000000000000000000000000000001" // 63 - 32 + "00000000000000000011111000000000" // 31 - 0 + ); + + const std::bitset<256> tblAlphabetic( + "00000000000000000000000000000000" // 255 - 224 + "00000000000000000000000000000000" // 223 - 192 + "00000000000000000000000000000000" // 192 - 160 + "00000000000000000000000000000000" // 159 - 128 + "00000111111111111111111111111110" // 127 - 96 + "00000111111111111111111111111110" // 95 - 64 + "00000000000000000000000000000000" // 63 - 32 + "00000000000000000000000000000000" // 31 - 0 + ); + + const std::bitset<256> tblDigit( + "00000000000000000000000000000000" // 255 - 224 + "00000000000000000000000000000000" // 223 - 192 + "00000000000000000000000000000000" // 192 - 160 + "00000000000000000000000000000000" // 159 - 128 + "00000000000000000000000000000000" // 127 - 96 + "00000000000000000000000000000000" // 95 - 64 + "00000011111111110000000000000000" // 63 - 32 + "00000000000000000000000000000000" // 31 - 0 + ); + + const std::bitset<256> tblAlphanumeric( + "00000000000000000000000000000000" // 255 - 224 + "00000000000000000000000000000000" // 223 - 192 + "00000000000000000000000000000000" // 192 - 160 + "00000000000000000000000000000000" // 159 - 128 + "00000111111111111111111111111110" // 127 - 96 + "00000111111111111111111111111110" // 95 - 64 + "00000011111111110000000000000000" // 63 - 32 + "00000000000000000000000000000000" // 31 - 0 + ); + + const std::bitset<256> tblHex( + "00000000000000000000000000000000" // 255 - 224 + "00000000000000000000000000000000" // 223 - 192 + "00000000000000000000000000000000" // 192 - 160 + "00000000000000000000000000000000" // 159 - 128 + "00000000000000000000000001111110" // 127 - 96 + "00000000000000000000000001111110" // 95 - 64 + "00000011111111110000000000000000" // 63 - 32 + "00000000000000000000000000000000" // 31 - 0 + ); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + } + +} diff --git a/src/character.hpp b/src/character.hpp new file mode 100644 index 0000000000..069f8da843 --- /dev/null +++ b/src/character.hpp @@ -0,0 +1,227 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CHARACTER_HPP +#define SASS_CHARACTER_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include +#include "charcode.hpp" + +namespace Sass { + + namespace Character { + + using namespace Charcode; + + // The difference between upper- and lowercase ASCII letters. + // `0b100000` can be bitwise-ORed with uppercase ASCII letters + // to get their lowercase equivalents. + const uint8_t asciiCaseBit = 0x20; + + // The ASCII lookup tables + extern const std::bitset<256> tblNewline; + extern const std::bitset<256> tblSpaceOrTab; + extern const std::bitset<256> tblWhitespace; + extern const std::bitset<256> tblAlphabetic; + extern const std::bitset<256> tblDigit; + extern const std::bitset<256> tblAlphanumeric; + extern const std::bitset<256> tblHex; + + // skip over 10xxxxxx and 01xxxxxx + // count ASCII and initial utf8 bytes + inline bool isCharacter(uint8_t character) { + // Ignore all `10xxxxxx` chars + // '0xxxxxxx' are ASCII chars + // '11xxxxxx' are utf8 starts + // 64 => initial utf8 byte + // 128 => regular ASCII char + return (character & 192) != 128; + // return (character & (128|64)) != 0; + } + + // Returns whether [character] is + // starting a utf8 multibyte sequence. + inline bool isUtf8StartByte(uint8_t character) { + return (character & 192) == 192; + } + + // Returns whether [character] is + // part of a utf8 multibyte sequence. + inline bool isUtf8Continuation(uint8_t character) { + return (character & 192) == 128; + } + + // Returns whether [character] is the + // beginning of a UTF-16 surrogate pair. + // bool isUtf8HighSurrogate(uint16_t character) { + // return character >= 0xD800 && character <= 0xDBFF; + // } + + // Returns whether [character] is an ASCII newline. + inline bool isNewline(uint8_t character) { + return tblNewline[character]; + } + + // Returns whether [character] is a space or a tab character. + inline bool isSpaceOrTab(uint8_t character) { + return tblSpaceOrTab[character]; + } + + // Returns whether [character] is an ASCII whitespace character. + inline bool isWhitespace(uint8_t character) { + return tblWhitespace[character]; + } + + // Returns whether [character] is a letter. + inline bool isAlphabetic(uint8_t character) { + return tblAlphabetic[character]; + } + + // Returns whether [character] is a number. + inline bool isDigit(uint8_t character) { + return tblDigit[character]; + } + + // Returns whether [character] is a letter or number. + inline bool isAlphanumeric(uint8_t character) { + return tblAlphanumeric[character]; + } + + // Returns whether [character] is legal as the start of a Sass identifier. + inline bool isNameStart(uint8_t character) { + return character == $_ + || tblAlphabetic[character] + || character >= 0x0080; + } + + // Returns whether [character] is legal as the start of a Sass identifier. + inline bool isNameStart(uint32_t character) { + return character == $_ + || (character >= 'a' && character <= 'z') + || (character >= 'A' && character <= 'Z') + || character >= 0x0080; + } + + // Returns whether [character] is legal in the body of a Sass identifier. + inline bool isName(uint8_t character) { + return character == $_ + || character == $minus + || tblAlphanumeric[character] + || character >= 0x0080; + } + + // Returns whether [character] is a hexadecimal digit. + inline bool isHex(uint8_t character) { + return tblHex[character]; + } + + // Returns whether [character] can start a simple + // selector other than a type selector. + inline bool isSimpleSelectorStart(uint8_t character) + { + return character == $asterisk + || character == $lbracket + || character == $dot + || character == $hash + || character == $percent + || character == $colon; + } + + // Returns the value of [character] as a hex digit. + // Assumes that [character] is a hex digit. + inline uint8_t asHex(uint8_t character) + { + // assert(isHex(character)); + if (character <= $9) return character - $0; + if (character <= $F) return 10 + character - $A; + return 10 + character - $a; + } + + // Returns the hexadecimal digit for [number]. + // Assumes that [number] is less than 16. + inline uint8_t hexCharFor(uint8_t number) + { + // assert(number < 0x10); + return number < 0xA ? $0 + number + : $a - 0xA + number; + } + + // Returns the value of [character] as a decimal digit. + // Assumes that [character] is a decimal digit. + inline double asDecimal(uint8_t character) + { + // assert(character >= $0 && character <= $9); + return character - $0; + } + + // Returns the decimal digit for [number]. + // Assumes that [number] is less than 10. + inline uint8_t decimalCharFor(uint8_t number) + { + // assert(number < 10); + return $0 + number; + } + + // Assumes that [character] is a left-hand brace-like + // character, and returns the right-hand version. + inline uint8_t opposite(uint8_t character) + { + switch (character) { + case $lparen: + return $rparen; + case $lbrace: + return $rbrace; + case $lbracket: + return $rbracket; + default: + return 0; + } + } + + // Returns [character], converted to upper- + // case if it's an ASCII lowercase letter. + inline uint8_t toUpperCase(uint8_t character) + { + return (character >= $a && character <= $z) + ? character & ~asciiCaseBit : character; + } + + // Returns [character], converted to lower- + // case if it's an ASCII uppercase letter. + inline uint8_t toLowerCase(uint8_t character) + { + return (character >= $A && character <= $Z) + ? character | asciiCaseBit : character; + } + + // Returns whether [character1] and [character2] are the same, modulo ASCII case. + inline bool characterEqualsIgnoreCase(uint8_t character1, uint8_t character2) + { + if (character1 == character2) return true; + + // If this check fails, the characters are definitely different. If it + // succeeds *and* either character is an ASCII letter, they're equivalent. + if ((character1 ^ character2) != asciiCaseBit) return false; + + // Now we just need to verify that one of the characters is an ASCII letter. + uint8_t upperCase1 = character1 & ~asciiCaseBit; + return upperCase1 >= $A && upperCase1 <= $Z; + } + + // Like [characterEqualsIgnoreCase], but optimized for the + // fact that [letter] is known to be a lowercase ASCII letter. + inline bool equalsLetterIgnoreCase(uint8_t letter, uint8_t actual) + { + // assert(letter >= $a && letter <= $z); + return (actual | asciiCaseBit) == letter; + } + + } + +} + +#endif diff --git a/src/charcode.hpp b/src/charcode.hpp new file mode 100644 index 0000000000..9d0d22dbb4 --- /dev/null +++ b/src/charcode.hpp @@ -0,0 +1,508 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CHARCODE_HPP +#define SASS_CHARCODE_HPP + +#include +#include "utf8/core.h" + +namespace Sass { + + namespace Charcode { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // "Null character" control character. + const uint8_t $nul = 0x00; + + // "Start of Header" control character. + const uint8_t $soh = 0x01; + + // "Start of Text" control character. + const uint8_t $stx = 0x02; + + // "End of Text" control character. + const uint8_t $etx = 0x03; + + // "End of Transmission" control character. + const uint8_t $eot = 0x04; + + // "Enquiry" control character. + const uint8_t $enq = 0x05; + + // "Acknowledgment" control character. + const uint8_t $ack = 0x06; + + // "Bell" control character. + const uint8_t $bel = 0x07; + + // "Backspace" control character. + const uint8_t $bs = 0x08; + + // "Horizontal Tab" control character. + const uint8_t $ht = 0x09; + + // "Horizontal Tab" control character, common name. + const uint8_t $tab = 0x09; + + // "Line feed" control character. + const uint8_t $lf = 0x0A; + + // "Vertical Tab" control character. + const uint8_t $vt = 0x0B; + + // "Form feed" control character. + const uint8_t $ff = 0x0C; + + // "Carriage return" control character. + const uint8_t $cr = 0x0D; + + // "Shift Out" control character. + const uint8_t $so = 0x0E; + + // "Shift In" control character. + const uint8_t $si = 0x0F; + + // "Data Link Escape" control character. + const uint8_t $dle = 0x10; + + // "Device Control 1" control character (oft. XON). + const uint8_t $dc1 = 0x11; + + // "Device Control 2" control character. + const uint8_t $dc2 = 0x12; + + // "Device Control 3" control character (oft. XOFF). + const uint8_t $dc3 = 0x13; + + // "Device Control 4" control character. + const uint8_t $dc4 = 0x14; + + // "Negative Acknowledgment" control character. + const uint8_t $nak = 0x15; + + // "Synchronous idle" control character. + const uint8_t $syn = 0x16; + + // "End of Transmission Block" control character. + const uint8_t $etb = 0x17; + + // "Cancel" control character. + const uint8_t $can = 0x18; + + // "End of Medium" control character. + const uint8_t $em = 0x19; + + // "Substitute" control character. + const uint8_t $sub = 0x1A; + + // "Escape" control character. + const uint8_t $esc = 0x1B; + + // "File Separator" control character. + const uint8_t $fs = 0x1C; + + // "Group Separator" control character. + const uint8_t $gs = 0x1D; + + // "Record Separator" control character. + const uint8_t $rs = 0x1E; + + // "Unit Separator" control character. + const uint8_t $us = 0x1F; + + // "Delete" control character. + const uint8_t $del = 0x7F; + + ///////////////////////////////////////////////////////////////////////// + // Visible characters. + ///////////////////////////////////////////////////////////////////////// + + // Space character. + const uint8_t $space = 0x20; + + // Character '!'. + const uint8_t $exclamation = 0x21; + + // Character '"', short name. + const uint8_t $quot = 0x22; + + // Character '"'. + const uint8_t $quote = 0x22; + + // Character '"'. + const uint8_t $double_quote = 0x22; + + // Character '"'. + const uint8_t $quotation = 0x22; + + // Character '#'. + const uint8_t $hash = 0x23; + + // Character '$'. + const uint8_t $$ = 0x24; + + // Character '$'. + const uint8_t $dollar = 0x24; + + // Character '%'. + const uint8_t $percent = 0x25; + + // Character '&', short name. + const uint8_t $amp = 0x26; + + // Character '&'. + const uint8_t $ampersand = 0x26; + + // Character "'". + const uint8_t $apos = 0x27; + + // Character '''. + const uint8_t $apostrophe = 0x27; + + // Character '''. + const uint8_t $single_quote = 0x27; + + // Character '('. + const uint8_t $lparen = 0x28; + + // Character '('. + const uint8_t $open_paren = 0x28; + + // Character '('. + const uint8_t $open_parenthesis = 0x28; + + // Character ')'. + const uint8_t $rparen = 0x29; + + // Character ')'. + const uint8_t $close_paren = 0x29; + + // Character ')'. + const uint8_t $close_parenthesis = 0x29; + + // Character '*'. + const uint8_t $asterisk = 0x2A; + + // Character '+'. + const uint8_t $plus = 0x2B; + + // Character ','. + const uint8_t $comma = 0x2C; + + // Character '-'. + const uint8_t $minus = 0x2D; + + // Character '-'. + const uint8_t $dash = 0x2D; + + // Character '.'. + const uint8_t $dot = 0x2E; + + // Character '.'. + const uint8_t $fullstop = 0x2E; + + // Character '/'. + const uint8_t $slash = 0x2F; + + // Character '/'. + const uint8_t $solidus = 0x2F; + + // Character '/'. + const uint8_t $division = 0x2F; + + // Character '0'. + const uint8_t $0 = 0x30; + + // Character '1'. + const uint8_t $1 = 0x31; + + // Character '2'. + const uint8_t $2 = 0x32; + + // Character '3'. + const uint8_t $3 = 0x33; + + // Character '4'. + const uint8_t $4 = 0x34; + + // Character '5'. + const uint8_t $5 = 0x35; + + // Character '6'. + const uint8_t $6 = 0x36; + + // Character '7'. + const uint8_t $7 = 0x37; + + // Character '8'. + const uint8_t $8 = 0x38; + + // Character '9'. + const uint8_t $9 = 0x39; + + // Character ':'. + const uint8_t $colon = 0x3A; + + // Character ';'. + const uint8_t $semicolon = 0x3B; + + // Character '<'. + const uint8_t $lt = 0x3C; + + // Character '<'. + const uint8_t $less_than = 0x3C; + + // Character '<'. + const uint8_t $langle = 0x3C; + + // Character '<'. + const uint8_t $open_angle = 0x3C; + + // Character '='. + const uint8_t $equal = 0x3D; + + // Character '>'. + const uint8_t $gt = 0x3E; + + // Character '>'. + const uint8_t $greater_than = 0x3E; + + // Character '>'. + const uint8_t $rangle = 0x3E; + + // Character '>'. + const uint8_t $close_angle = 0x3E; + + // Character '?'. + const uint8_t $question = 0x3F; + + // Character '@'. + const uint8_t $at = 0x40; + + // Character 'A'. + const uint8_t $A = 0x41; + + // Character 'B'. + const uint8_t $B = 0x42; + + // Character 'C'. + const uint8_t $C = 0x43; + + // Character 'D'. + const uint8_t $D = 0x44; + + // Character 'E'. + const uint8_t $E = 0x45; + + // Character 'F'. + const uint8_t $F = 0x46; + + // Character 'G'. + const uint8_t $G = 0x47; + + // Character 'H'. + const uint8_t $H = 0x48; + + // Character 'I'. + const uint8_t $I = 0x49; + + // Character 'J'. + const uint8_t $J = 0x4A; + + // Character 'K'. + const uint8_t $K = 0x4B; + + // Character 'L'. + const uint8_t $L = 0x4C; + + // Character 'M'. + const uint8_t $M = 0x4D; + + // Character 'N'. + const uint8_t $N = 0x4E; + + // Character 'O'. + const uint8_t $O = 0x4F; + + // Character 'P'. + const uint8_t $P = 0x50; + + // Character 'Q'. + const uint8_t $Q = 0x51; + + // Character 'R'. + const uint8_t $R = 0x52; + + // Character 'S'. + const uint8_t $S = 0x53; + + // Character 'T'. + const uint8_t $T = 0x54; + + // Character 'U'. + const uint8_t $U = 0x55; + + // Character 'V'. + const uint8_t $V = 0x56; + + // Character 'W'. + const uint8_t $W = 0x57; + + // Character 'X'. + const uint8_t $X = 0x58; + + // Character 'Y'. + const uint8_t $Y = 0x59; + + // Character 'Z'. + const uint8_t $Z = 0x5A; + + // Character '['. + const uint8_t $lbracket = 0x5B; + + // Character '['. + const uint8_t $open_bracket = 0x5B; + + // Character '\'. + const uint8_t $backslash = 0x5C; + + // Character ']'. + const uint8_t $rbracket = 0x5D; + + // Character ']'. + const uint8_t $close_bracket = 0x5D; + + // Character '^'. + const uint8_t $circumflex = 0x5E; + + // Character '^'. + const uint8_t $caret = 0x5E; + + // Character '^'. + const uint8_t $hat = 0x5E; + + // Character '_'. + const uint8_t $_ = 0x5F; + + // Character '_'. + const uint8_t $underscore = 0x5F; + + // Character '_'. + const uint8_t $underline = 0x5F; + + // Character '`'. + const uint8_t $backquote = 0x60; + + // Character '`'. + const uint8_t $grave = 0x60; + + // Character 'a'. + const uint8_t $a = 0x61; + + // Character 'b'. + const uint8_t $b = 0x62; + + // Character 'c'. + const uint8_t $c = 0x63; + + // Character 'd'. + const uint8_t $d = 0x64; + + // Character 'e'. + const uint8_t $e = 0x65; + + // Character 'f'. + const uint8_t $f = 0x66; + + // Character 'g'. + const uint8_t $g = 0x67; + + // Character 'h'. + const uint8_t $h = 0x68; + + // Character 'i'. + const uint8_t $i = 0x69; + + // Character 'j'. + const uint8_t $j = 0x6A; + + // Character 'k'. + const uint8_t $k = 0x6B; + + // Character 'l'. + const uint8_t $l = 0x6C; + + // Character 'm'. + const uint8_t $m = 0x6D; + + // Character 'n'. + const uint8_t $n = 0x6E; + + // Character 'o'. + const uint8_t $o = 0x6F; + + // Character 'p'. + const uint8_t $p = 0x70; + + // Character 'q'. + const uint8_t $q = 0x71; + + // Character 'r'. + const uint8_t $r = 0x72; + + // Character 's'. + const uint8_t $s = 0x73; + + // Character 't'. + const uint8_t $t = 0x74; + + // Character 'u'. + const uint8_t $u = 0x75; + + // Character 'v'. + const uint8_t $v = 0x76; + + // Character 'w'. + const uint8_t $w = 0x77; + + // Character 'x'. + const uint8_t $x = 0x78; + + // Character 'y'. + const uint8_t $y = 0x79; + + // Character 'z'. + const uint8_t $z = 0x7A; + + // Character '{'. + const uint8_t $lbrace = 0x7B; + + // Character '{'. + const uint8_t $open_brace = 0x7B; + + // Character '|'. + const uint8_t $pipe = 0x7C; + + // Character '|'. + const uint8_t $bar = 0x7C; + + // Character '}'. + const uint8_t $rbrace = 0x7D; + + // Character '}'. + const uint8_t $close_brace = 0x7D; + + // Character '~'. + const uint8_t $tilde = 0x7E; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + } + +} + +#endif diff --git a/src/check_nesting.cpp b/src/check_nesting.cpp deleted file mode 100644 index 8e099535e5..0000000000 --- a/src/check_nesting.cpp +++ /dev/null @@ -1,393 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" -#include "check_nesting.hpp" - -namespace Sass { - - CheckNesting::CheckNesting() - : parents(sass::vector()), - traces(sass::vector()), - parent(0), current_mixin_definition(0) - { } - - void error(AST_Node* node, Backtraces traces, sass::string msg) { - traces.push_back(Backtrace(node->pstate())); - throw Exception::InvalidSass(node->pstate(), traces, msg); - } - - Statement* CheckNesting::visit_children(Statement* parent) - { - Statement* old_parent = this->parent; - - if (AtRootRule* root = Cast(parent)) { - sass::vector old_parents = this->parents; - sass::vector new_parents; - - for (size_t i = 0, L = this->parents.size(); i < L; i++) { - Statement* p = this->parents.at(i); - if (!root->exclude_node(p)) { - new_parents.push_back(p); - } - } - this->parents = new_parents; - - for (size_t i = this->parents.size(); i > 0; i--) { - Statement* p = 0; - Statement* gp = 0; - if (i > 0) p = this->parents.at(i - 1); - if (i > 1) gp = this->parents.at(i - 2); - - if (!this->is_transparent_parent(p, gp)) { - this->parent = p; - break; - } - } - - AtRootRule* ar = Cast(parent); - Block* ret = ar->block(); - - if (ret != NULL) { - for (auto n : ret->elements()) { - n->perform(this); - } - } - - this->parent = old_parent; - this->parents = old_parents; - - return ret; - } - - if (!this->is_transparent_parent(parent, old_parent)) { - this->parent = parent; - } - - this->parents.push_back(parent); - - Block* b = Cast(parent); - - if (Trace* trace = Cast(parent)) { - if (trace->type() == 'i') { - this->traces.push_back(Backtrace(trace->pstate())); - } - } - - if (!b) { - if (ParentStatement* bb = Cast(parent)) { - b = bb->block(); - } - } - - if (b) { - for (auto n : b->elements()) { - n->perform(this); - } - } - - this->parent = old_parent; - this->parents.pop_back(); - - if (Trace* trace = Cast(parent)) { - if (trace->type() == 'i') { - this->traces.pop_back(); - } - } - - return b; - } - - - Statement* CheckNesting::operator()(Block* b) - { - return this->visit_children(b); - } - - Statement* CheckNesting::operator()(Definition* n) - { - if (!this->should_visit(n)) return NULL; - if (!is_mixin(n)) { - visit_children(n); - return n; - } - - Definition* old_mixin_definition = this->current_mixin_definition; - this->current_mixin_definition = n; - - visit_children(n); - - this->current_mixin_definition = old_mixin_definition; - - return n; - } - - Statement* CheckNesting::operator()(If* i) - { - this->visit_children(i); - - if (Block* b = Cast(i->alternative())) { - for (auto n : b->elements()) n->perform(this); - } - - return i; - } - - bool CheckNesting::should_visit(Statement* node) - { - if (!this->parent) return true; - - if (Cast(node)) - { this->invalid_content_parent(this->parent, node); } - - if (is_charset(node)) - { this->invalid_charset_parent(this->parent, node); } - - if (Cast(node)) - { this->invalid_extend_parent(this->parent, node); } - - // if (Cast(node)) - // { this->invalid_import_parent(this->parent); } - - if (this->is_mixin(node)) - { this->invalid_mixin_definition_parent(this->parent, node); } - - if (this->is_function(node)) - { this->invalid_function_parent(this->parent, node); } - - if (this->is_function(this->parent)) - { this->invalid_function_child(node); } - - if (Declaration* d = Cast(node)) - { - this->invalid_prop_parent(this->parent, node); - this->invalid_value_child(d->value()); - } - - if (Cast(this->parent)) - { this->invalid_prop_child(node); } - - if (Cast(node)) - { this->invalid_return_parent(this->parent, node); } - - return true; - } - - void CheckNesting::invalid_content_parent(Statement* parent, AST_Node* node) - { - if (!this->current_mixin_definition) { - error(node, traces, "@content may only be used within a mixin."); - } - } - - void CheckNesting::invalid_charset_parent(Statement* parent, AST_Node* node) - { - if (!( - is_root_node(parent) - )) { - error(node, traces, "@charset may only be used at the root of a document."); - } - } - - void CheckNesting::invalid_extend_parent(Statement* parent, AST_Node* node) - { - if (!( - Cast(parent) || - Cast(parent) || - is_mixin(parent) - )) { - error(node, traces, "Extend directives may only be used within rules."); - } - } - - // void CheckNesting::invalid_import_parent(Statement* parent, AST_Node* node) - // { - // for (auto pp : this->parents) { - // if ( - // Cast(pp) || - // Cast(pp) || - // Cast(pp) || - // Cast(pp) || - // Cast(pp) || - // Cast(pp) || - // is_mixin(pp) - // ) { - // error(node, traces, "Import directives may not be defined within control directives or other mixins."); - // } - // } - - // if (this->is_root_node(parent)) { - // return; - // } - - // if (false/*n.css_import?*/) { - // error(node, traces, "CSS import directives may only be used at the root of a document."); - // } - // } - - void CheckNesting::invalid_mixin_definition_parent(Statement* parent, AST_Node* node) - { - for (Statement* pp : this->parents) { - if ( - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - is_mixin(pp) - ) { - error(node, traces, "Mixins may not be defined within control directives or other mixins."); - } - } - } - - void CheckNesting::invalid_function_parent(Statement* parent, AST_Node* node) - { - for (Statement* pp : this->parents) { - if ( - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - is_mixin(pp) - ) { - error(node, traces, "Functions may not be defined within control directives or other mixins."); - } - } - } - - void CheckNesting::invalid_function_child(Statement* child) - { - if (!( - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - // Ruby Sass doesn't distinguish variables and assignments - Cast(child) || - Cast(child) || - Cast(child) - )) { - error(child, traces, "Functions can only contain variable declarations and control directives."); - } - } - - void CheckNesting::invalid_prop_child(Statement* child) - { - if (!( - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) - )) { - error(child, traces, "Illegal nesting: Only properties may be nested beneath properties."); - } - } - - void CheckNesting::invalid_prop_parent(Statement* parent, AST_Node* node) - { - if (!( - is_mixin(parent) || - is_directive_node(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) - )) { - error(node, traces, "Properties are only allowed within rules, directives, mixin includes, or other properties."); - } - } - - void CheckNesting::invalid_value_child(AST_Node* d) - { - if (Map* m = Cast(d)) { - traces.push_back(Backtrace(m->pstate())); - throw Exception::InvalidValue(traces, *m); - } - if (Number* n = Cast(d)) { - if (!n->is_valid_css_unit()) { - traces.push_back(Backtrace(n->pstate())); - throw Exception::InvalidValue(traces, *n); - } - } - - // error(dbg + " isn't a valid CSS value.", m->pstate(),); - - } - - void CheckNesting::invalid_return_parent(Statement* parent, AST_Node* node) - { - if (!this->is_function(parent)) { - error(node, traces, "@return may only be used within a function."); - } - } - - bool CheckNesting::is_transparent_parent(Statement* parent, Statement* grandparent) - { - bool parent_bubbles = parent && parent->bubbles(); - - bool valid_bubble_node = parent_bubbles && - !is_root_node(grandparent) && - !is_at_root_node(grandparent); - - return Cast(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) || - valid_bubble_node; - } - - bool CheckNesting::is_charset(Statement* n) - { - AtRule* d = Cast(n); - return d && d->keyword() == "charset"; - } - - bool CheckNesting::is_mixin(Statement* n) - { - Definition* def = Cast(n); - return def && def->type() == Definition::MIXIN; - } - - bool CheckNesting::is_function(Statement* n) - { - Definition* def = Cast(n); - return def && def->type() == Definition::FUNCTION; - } - - bool CheckNesting::is_root_node(Statement* n) - { - if (Cast(n)) return false; - - Block* b = Cast(n); - return b && b->is_root(); - } - - bool CheckNesting::is_at_root_node(Statement* n) - { - return Cast(n) != NULL; - } - - bool CheckNesting::is_directive_node(Statement* n) - { - return Cast(n) || - Cast(n) || - Cast(n) || - Cast(n) || - Cast(n); - } -} diff --git a/src/check_nesting.hpp b/src/check_nesting.hpp deleted file mode 100644 index 48bd99c6e4..0000000000 --- a/src/check_nesting.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef SASS_CHECK_NESTING_H -#define SASS_CHECK_NESTING_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" -#include "operation.hpp" -#include - -namespace Sass { - - class CheckNesting : public Operation_CRTP { - - sass::vector parents; - Backtraces traces; - Statement* parent; - Definition* current_mixin_definition; - - Statement* before(Statement*); - Statement* visit_children(Statement*); - - public: - CheckNesting(); - ~CheckNesting() { } - - Statement* operator()(Block*); - Statement* operator()(Definition*); - Statement* operator()(If*); - - template - Statement* fallback(U x) { - Statement* s = Cast(x); - if (s && this->should_visit(s)) { - Block* b1 = Cast(s); - ParentStatement* b2 = Cast(s); - if (b1 || b2) return visit_children(s); - } - return s; - } - - private: - void invalid_content_parent(Statement*, AST_Node*); - void invalid_charset_parent(Statement*, AST_Node*); - void invalid_extend_parent(Statement*, AST_Node*); - // void invalid_import_parent(Statement*); - void invalid_mixin_definition_parent(Statement*, AST_Node*); - void invalid_function_parent(Statement*, AST_Node*); - - void invalid_function_child(Statement*); - void invalid_prop_child(Statement*); - void invalid_prop_parent(Statement*, AST_Node*); - void invalid_return_parent(Statement*, AST_Node*); - void invalid_value_child(AST_Node*); - - bool is_transparent_parent(Statement*, Statement*); - - bool should_visit(Statement*); - - bool is_charset(Statement*); - bool is_mixin(Statement*); - bool is_function(Statement*); - bool is_root_node(Statement*); - bool is_at_root_node(Statement*); - bool is_directive_node(Statement*); - }; - -} - -#endif diff --git a/src/color_maps.cpp b/src/color_maps.cpp index 15dba2ebf5..7fd4d1c3cb 100644 --- a/src/color_maps.cpp +++ b/src/color_maps.cpp @@ -1,652 +1,656 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "ast.hpp" +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// #include "color_maps.hpp" + #include "util_string.hpp" +#include "string_utils.hpp" -namespace Sass { +namespace Sass +{ - namespace ColorNames - { - const char aliceblue [] = "aliceblue"; - const char antiquewhite [] = "antiquewhite"; - const char cyan [] = "cyan"; - const char aqua [] = "aqua"; - const char aquamarine [] = "aquamarine"; - const char azure [] = "azure"; - const char beige [] = "beige"; - const char bisque [] = "bisque"; - const char black [] = "black"; - const char blanchedalmond [] = "blanchedalmond"; - const char blue [] = "blue"; - const char blueviolet [] = "blueviolet"; - const char brown [] = "brown"; - const char burlywood [] = "burlywood"; - const char cadetblue [] = "cadetblue"; - const char chartreuse [] = "chartreuse"; - const char chocolate [] = "chocolate"; - const char coral [] = "coral"; - const char cornflowerblue [] = "cornflowerblue"; - const char cornsilk [] = "cornsilk"; - const char crimson [] = "crimson"; - const char darkblue [] = "darkblue"; - const char darkcyan [] = "darkcyan"; - const char darkgoldenrod [] = "darkgoldenrod"; - const char darkgray [] = "darkgray"; - const char darkgrey [] = "darkgrey"; - const char darkgreen [] = "darkgreen"; - const char darkkhaki [] = "darkkhaki"; - const char darkmagenta [] = "darkmagenta"; - const char darkolivegreen [] = "darkolivegreen"; - const char darkorange [] = "darkorange"; - const char darkorchid [] = "darkorchid"; - const char darkred [] = "darkred"; - const char darksalmon [] = "darksalmon"; - const char darkseagreen [] = "darkseagreen"; - const char darkslateblue [] = "darkslateblue"; - const char darkslategray [] = "darkslategray"; - const char darkslategrey [] = "darkslategrey"; - const char darkturquoise [] = "darkturquoise"; - const char darkviolet [] = "darkviolet"; - const char deeppink [] = "deeppink"; - const char deepskyblue [] = "deepskyblue"; - const char dimgray [] = "dimgray"; - const char dimgrey [] = "dimgrey"; - const char dodgerblue [] = "dodgerblue"; - const char firebrick [] = "firebrick"; - const char floralwhite [] = "floralwhite"; - const char forestgreen [] = "forestgreen"; - const char magenta [] = "magenta"; - const char fuchsia [] = "fuchsia"; - const char gainsboro [] = "gainsboro"; - const char ghostwhite [] = "ghostwhite"; - const char gold [] = "gold"; - const char goldenrod [] = "goldenrod"; - const char gray [] = "gray"; - const char grey [] = "grey"; - const char green [] = "green"; - const char greenyellow [] = "greenyellow"; - const char honeydew [] = "honeydew"; - const char hotpink [] = "hotpink"; - const char indianred [] = "indianred"; - const char indigo [] = "indigo"; - const char ivory [] = "ivory"; - const char khaki [] = "khaki"; - const char lavender [] = "lavender"; - const char lavenderblush [] = "lavenderblush"; - const char lawngreen [] = "lawngreen"; - const char lemonchiffon [] = "lemonchiffon"; - const char lightblue [] = "lightblue"; - const char lightcoral [] = "lightcoral"; - const char lightcyan [] = "lightcyan"; - const char lightgoldenrodyellow [] = "lightgoldenrodyellow"; - const char lightgray [] = "lightgray"; - const char lightgrey [] = "lightgrey"; - const char lightgreen [] = "lightgreen"; - const char lightpink [] = "lightpink"; - const char lightsalmon [] = "lightsalmon"; - const char lightseagreen [] = "lightseagreen"; - const char lightskyblue [] = "lightskyblue"; - const char lightslategray [] = "lightslategray"; - const char lightslategrey [] = "lightslategrey"; - const char lightsteelblue [] = "lightsteelblue"; - const char lightyellow [] = "lightyellow"; - const char lime [] = "lime"; - const char limegreen [] = "limegreen"; - const char linen [] = "linen"; - const char maroon [] = "maroon"; - const char mediumaquamarine [] = "mediumaquamarine"; - const char mediumblue [] = "mediumblue"; - const char mediumorchid [] = "mediumorchid"; - const char mediumpurple [] = "mediumpurple"; - const char mediumseagreen [] = "mediumseagreen"; - const char mediumslateblue [] = "mediumslateblue"; - const char mediumspringgreen [] = "mediumspringgreen"; - const char mediumturquoise [] = "mediumturquoise"; - const char mediumvioletred [] = "mediumvioletred"; - const char midnightblue [] = "midnightblue"; - const char mintcream [] = "mintcream"; - const char mistyrose [] = "mistyrose"; - const char moccasin [] = "moccasin"; - const char navajowhite [] = "navajowhite"; - const char navy [] = "navy"; - const char oldlace [] = "oldlace"; - const char olive [] = "olive"; - const char olivedrab [] = "olivedrab"; - const char orange [] = "orange"; - const char orangered [] = "orangered"; - const char orchid [] = "orchid"; - const char palegoldenrod [] = "palegoldenrod"; - const char palegreen [] = "palegreen"; - const char paleturquoise [] = "paleturquoise"; - const char palevioletred [] = "palevioletred"; - const char papayawhip [] = "papayawhip"; - const char peachpuff [] = "peachpuff"; - const char peru [] = "peru"; - const char pink [] = "pink"; - const char plum [] = "plum"; - const char powderblue [] = "powderblue"; - const char purple [] = "purple"; - const char red [] = "red"; - const char rosybrown [] = "rosybrown"; - const char royalblue [] = "royalblue"; - const char saddlebrown [] = "saddlebrown"; - const char salmon [] = "salmon"; - const char sandybrown [] = "sandybrown"; - const char seagreen [] = "seagreen"; - const char seashell [] = "seashell"; - const char sienna [] = "sienna"; - const char silver [] = "silver"; - const char skyblue [] = "skyblue"; - const char slateblue [] = "slateblue"; - const char slategray [] = "slategray"; - const char slategrey [] = "slategrey"; - const char snow [] = "snow"; - const char springgreen [] = "springgreen"; - const char steelblue [] = "steelblue"; - const char tan [] = "tan"; - const char teal [] = "teal"; - const char thistle [] = "thistle"; - const char tomato [] = "tomato"; - const char turquoise [] = "turquoise"; - const char violet [] = "violet"; - const char wheat [] = "wheat"; - const char white [] = "white"; - const char whitesmoke [] = "whitesmoke"; - const char yellow [] = "yellow"; - const char yellowgreen [] = "yellowgreen"; - const char rebeccapurple [] = "rebeccapurple"; - const char transparent [] = "transparent"; - } + namespace ColorNames + { + const char aliceblue[] = "aliceblue"; + const char antiquewhite[] = "antiquewhite"; + const char cyan[] = "cyan"; + const char aqua[] = "aqua"; + const char aquamarine[] = "aquamarine"; + const char azure[] = "azure"; + const char beige[] = "beige"; + const char bisque[] = "bisque"; + const char black[] = "black"; + const char blanchedalmond[] = "blanchedalmond"; + const char blue[] = "blue"; + const char blueviolet[] = "blueviolet"; + const char brown[] = "brown"; + const char burlywood[] = "burlywood"; + const char cadetblue[] = "cadetblue"; + const char chartreuse[] = "chartreuse"; + const char chocolate[] = "chocolate"; + const char coral[] = "coral"; + const char cornflowerblue[] = "cornflowerblue"; + const char cornsilk[] = "cornsilk"; + const char crimson[] = "crimson"; + const char darkblue[] = "darkblue"; + const char darkcyan[] = "darkcyan"; + const char darkgoldenrod[] = "darkgoldenrod"; + const char darkgray[] = "darkgray"; + const char darkgrey[] = "darkgrey"; + const char darkgreen[] = "darkgreen"; + const char darkkhaki[] = "darkkhaki"; + const char darkmagenta[] = "darkmagenta"; + const char darkolivegreen[] = "darkolivegreen"; + const char darkorange[] = "darkorange"; + const char darkorchid[] = "darkorchid"; + const char darkred[] = "darkred"; + const char darksalmon[] = "darksalmon"; + const char darkseagreen[] = "darkseagreen"; + const char darkslateblue[] = "darkslateblue"; + const char darkslategray[] = "darkslategray"; + const char darkslategrey[] = "darkslategrey"; + const char darkturquoise[] = "darkturquoise"; + const char darkviolet[] = "darkviolet"; + const char deeppink[] = "deeppink"; + const char deepskyblue[] = "deepskyblue"; + const char dimgray[] = "dimgray"; + const char dimgrey[] = "dimgrey"; + const char dodgerblue[] = "dodgerblue"; + const char firebrick[] = "firebrick"; + const char floralwhite[] = "floralwhite"; + const char forestgreen[] = "forestgreen"; + const char magenta[] = "magenta"; + const char fuchsia[] = "fuchsia"; + const char gainsboro[] = "gainsboro"; + const char ghostwhite[] = "ghostwhite"; + const char gold[] = "gold"; + const char goldenrod[] = "goldenrod"; + const char gray[] = "gray"; + const char grey[] = "grey"; + const char green[] = "green"; + const char greenyellow[] = "greenyellow"; + const char honeydew[] = "honeydew"; + const char hotpink[] = "hotpink"; + const char indianred[] = "indianred"; + const char indigo[] = "indigo"; + const char ivory[] = "ivory"; + const char khaki[] = "khaki"; + const char lavender[] = "lavender"; + const char lavenderblush[] = "lavenderblush"; + const char lawngreen[] = "lawngreen"; + const char lemonchiffon[] = "lemonchiffon"; + const char lightblue[] = "lightblue"; + const char lightcoral[] = "lightcoral"; + const char lightcyan[] = "lightcyan"; + const char lightgoldenrodyellow[] = "lightgoldenrodyellow"; + const char lightgray[] = "lightgray"; + const char lightgrey[] = "lightgrey"; + const char lightgreen[] = "lightgreen"; + const char lightpink[] = "lightpink"; + const char lightsalmon[] = "lightsalmon"; + const char lightseagreen[] = "lightseagreen"; + const char lightskyblue[] = "lightskyblue"; + const char lightslategray[] = "lightslategray"; + const char lightslategrey[] = "lightslategrey"; + const char lightsteelblue[] = "lightsteelblue"; + const char lightyellow[] = "lightyellow"; + const char lime[] = "lime"; + const char limegreen[] = "limegreen"; + const char linen[] = "linen"; + const char maroon[] = "maroon"; + const char mediumaquamarine[] = "mediumaquamarine"; + const char mediumblue[] = "mediumblue"; + const char mediumorchid[] = "mediumorchid"; + const char mediumpurple[] = "mediumpurple"; + const char mediumseagreen[] = "mediumseagreen"; + const char mediumslateblue[] = "mediumslateblue"; + const char mediumspringgreen[] = "mediumspringgreen"; + const char mediumturquoise[] = "mediumturquoise"; + const char mediumvioletred[] = "mediumvioletred"; + const char midnightblue[] = "midnightblue"; + const char mintcream[] = "mintcream"; + const char mistyrose[] = "mistyrose"; + const char moccasin[] = "moccasin"; + const char navajowhite[] = "navajowhite"; + const char navy[] = "navy"; + const char oldlace[] = "oldlace"; + const char olive[] = "olive"; + const char olivedrab[] = "olivedrab"; + const char orange[] = "orange"; + const char orangered[] = "orangered"; + const char orchid[] = "orchid"; + const char palegoldenrod[] = "palegoldenrod"; + const char palegreen[] = "palegreen"; + const char paleturquoise[] = "paleturquoise"; + const char palevioletred[] = "palevioletred"; + const char papayawhip[] = "papayawhip"; + const char peachpuff[] = "peachpuff"; + const char peru[] = "peru"; + const char pink[] = "pink"; + const char plum[] = "plum"; + const char powderblue[] = "powderblue"; + const char purple[] = "purple"; + const char red[] = "red"; + const char rosybrown[] = "rosybrown"; + const char royalblue[] = "royalblue"; + const char saddlebrown[] = "saddlebrown"; + const char salmon[] = "salmon"; + const char sandybrown[] = "sandybrown"; + const char seagreen[] = "seagreen"; + const char seashell[] = "seashell"; + const char sienna[] = "sienna"; + const char silver[] = "silver"; + const char skyblue[] = "skyblue"; + const char slateblue[] = "slateblue"; + const char slategray[] = "slategray"; + const char slategrey[] = "slategrey"; + const char snow[] = "snow"; + const char springgreen[] = "springgreen"; + const char steelblue[] = "steelblue"; + const char tan[] = "tan"; + const char teal[] = "teal"; + const char thistle[] = "thistle"; + const char tomato[] = "tomato"; + const char turquoise[] = "turquoise"; + const char violet[] = "violet"; + const char wheat[] = "wheat"; + const char white[] = "white"; + const char whitesmoke[] = "whitesmoke"; + const char yellow[] = "yellow"; + const char yellowgreen[] = "yellowgreen"; + const char rebeccapurple[] = "rebeccapurple"; + const char transparent[] = "transparent"; + } // namespace ColorNames - namespace Colors { - const SourceSpan color_table("[COLOR TABLE]"); - const Color_RGBA aliceblue(color_table, 240, 248, 255, 1); - const Color_RGBA antiquewhite(color_table, 250, 235, 215, 1); - const Color_RGBA cyan(color_table, 0, 255, 255, 1); - const Color_RGBA aqua(color_table, 0, 255, 255, 1); - const Color_RGBA aquamarine(color_table, 127, 255, 212, 1); - const Color_RGBA azure(color_table, 240, 255, 255, 1); - const Color_RGBA beige(color_table, 245, 245, 220, 1); - const Color_RGBA bisque(color_table, 255, 228, 196, 1); - const Color_RGBA black(color_table, 0, 0, 0, 1); - const Color_RGBA blanchedalmond(color_table, 255, 235, 205, 1); - const Color_RGBA blue(color_table, 0, 0, 255, 1); - const Color_RGBA blueviolet(color_table, 138, 43, 226, 1); - const Color_RGBA brown(color_table, 165, 42, 42, 1); - const Color_RGBA burlywood(color_table, 222, 184, 135, 1); - const Color_RGBA cadetblue(color_table, 95, 158, 160, 1); - const Color_RGBA chartreuse(color_table, 127, 255, 0, 1); - const Color_RGBA chocolate(color_table, 210, 105, 30, 1); - const Color_RGBA coral(color_table, 255, 127, 80, 1); - const Color_RGBA cornflowerblue(color_table, 100, 149, 237, 1); - const Color_RGBA cornsilk(color_table, 255, 248, 220, 1); - const Color_RGBA crimson(color_table, 220, 20, 60, 1); - const Color_RGBA darkblue(color_table, 0, 0, 139, 1); - const Color_RGBA darkcyan(color_table, 0, 139, 139, 1); - const Color_RGBA darkgoldenrod(color_table, 184, 134, 11, 1); - const Color_RGBA darkgray(color_table, 169, 169, 169, 1); - const Color_RGBA darkgrey(color_table, 169, 169, 169, 1); - const Color_RGBA darkgreen(color_table, 0, 100, 0, 1); - const Color_RGBA darkkhaki(color_table, 189, 183, 107, 1); - const Color_RGBA darkmagenta(color_table, 139, 0, 139, 1); - const Color_RGBA darkolivegreen(color_table, 85, 107, 47, 1); - const Color_RGBA darkorange(color_table, 255, 140, 0, 1); - const Color_RGBA darkorchid(color_table, 153, 50, 204, 1); - const Color_RGBA darkred(color_table, 139, 0, 0, 1); - const Color_RGBA darksalmon(color_table, 233, 150, 122, 1); - const Color_RGBA darkseagreen(color_table, 143, 188, 143, 1); - const Color_RGBA darkslateblue(color_table, 72, 61, 139, 1); - const Color_RGBA darkslategray(color_table, 47, 79, 79, 1); - const Color_RGBA darkslategrey(color_table, 47, 79, 79, 1); - const Color_RGBA darkturquoise(color_table, 0, 206, 209, 1); - const Color_RGBA darkviolet(color_table, 148, 0, 211, 1); - const Color_RGBA deeppink(color_table, 255, 20, 147, 1); - const Color_RGBA deepskyblue(color_table, 0, 191, 255, 1); - const Color_RGBA dimgray(color_table, 105, 105, 105, 1); - const Color_RGBA dimgrey(color_table, 105, 105, 105, 1); - const Color_RGBA dodgerblue(color_table, 30, 144, 255, 1); - const Color_RGBA firebrick(color_table, 178, 34, 34, 1); - const Color_RGBA floralwhite(color_table, 255, 250, 240, 1); - const Color_RGBA forestgreen(color_table, 34, 139, 34, 1); - const Color_RGBA magenta(color_table, 255, 0, 255, 1); - const Color_RGBA fuchsia(color_table, 255, 0, 255, 1); - const Color_RGBA gainsboro(color_table, 220, 220, 220, 1); - const Color_RGBA ghostwhite(color_table, 248, 248, 255, 1); - const Color_RGBA gold(color_table, 255, 215, 0, 1); - const Color_RGBA goldenrod(color_table, 218, 165, 32, 1); - const Color_RGBA gray(color_table, 128, 128, 128, 1); - const Color_RGBA grey(color_table, 128, 128, 128, 1); - const Color_RGBA green(color_table, 0, 128, 0, 1); - const Color_RGBA greenyellow(color_table, 173, 255, 47, 1); - const Color_RGBA honeydew(color_table, 240, 255, 240, 1); - const Color_RGBA hotpink(color_table, 255, 105, 180, 1); - const Color_RGBA indianred(color_table, 205, 92, 92, 1); - const Color_RGBA indigo(color_table, 75, 0, 130, 1); - const Color_RGBA ivory(color_table, 255, 255, 240, 1); - const Color_RGBA khaki(color_table, 240, 230, 140, 1); - const Color_RGBA lavender(color_table, 230, 230, 250, 1); - const Color_RGBA lavenderblush(color_table, 255, 240, 245, 1); - const Color_RGBA lawngreen(color_table, 124, 252, 0, 1); - const Color_RGBA lemonchiffon(color_table, 255, 250, 205, 1); - const Color_RGBA lightblue(color_table, 173, 216, 230, 1); - const Color_RGBA lightcoral(color_table, 240, 128, 128, 1); - const Color_RGBA lightcyan(color_table, 224, 255, 255, 1); - const Color_RGBA lightgoldenrodyellow(color_table, 250, 250, 210, 1); - const Color_RGBA lightgray(color_table, 211, 211, 211, 1); - const Color_RGBA lightgrey(color_table, 211, 211, 211, 1); - const Color_RGBA lightgreen(color_table, 144, 238, 144, 1); - const Color_RGBA lightpink(color_table, 255, 182, 193, 1); - const Color_RGBA lightsalmon(color_table, 255, 160, 122, 1); - const Color_RGBA lightseagreen(color_table, 32, 178, 170, 1); - const Color_RGBA lightskyblue(color_table, 135, 206, 250, 1); - const Color_RGBA lightslategray(color_table, 119, 136, 153, 1); - const Color_RGBA lightslategrey(color_table, 119, 136, 153, 1); - const Color_RGBA lightsteelblue(color_table, 176, 196, 222, 1); - const Color_RGBA lightyellow(color_table, 255, 255, 224, 1); - const Color_RGBA lime(color_table, 0, 255, 0, 1); - const Color_RGBA limegreen(color_table, 50, 205, 50, 1); - const Color_RGBA linen(color_table, 250, 240, 230, 1); - const Color_RGBA maroon(color_table, 128, 0, 0, 1); - const Color_RGBA mediumaquamarine(color_table, 102, 205, 170, 1); - const Color_RGBA mediumblue(color_table, 0, 0, 205, 1); - const Color_RGBA mediumorchid(color_table, 186, 85, 211, 1); - const Color_RGBA mediumpurple(color_table, 147, 112, 219, 1); - const Color_RGBA mediumseagreen(color_table, 60, 179, 113, 1); - const Color_RGBA mediumslateblue(color_table, 123, 104, 238, 1); - const Color_RGBA mediumspringgreen(color_table, 0, 250, 154, 1); - const Color_RGBA mediumturquoise(color_table, 72, 209, 204, 1); - const Color_RGBA mediumvioletred(color_table, 199, 21, 133, 1); - const Color_RGBA midnightblue(color_table, 25, 25, 112, 1); - const Color_RGBA mintcream(color_table, 245, 255, 250, 1); - const Color_RGBA mistyrose(color_table, 255, 228, 225, 1); - const Color_RGBA moccasin(color_table, 255, 228, 181, 1); - const Color_RGBA navajowhite(color_table, 255, 222, 173, 1); - const Color_RGBA navy(color_table, 0, 0, 128, 1); - const Color_RGBA oldlace(color_table, 253, 245, 230, 1); - const Color_RGBA olive(color_table, 128, 128, 0, 1); - const Color_RGBA olivedrab(color_table, 107, 142, 35, 1); - const Color_RGBA orange(color_table, 255, 165, 0, 1); - const Color_RGBA orangered(color_table, 255, 69, 0, 1); - const Color_RGBA orchid(color_table, 218, 112, 214, 1); - const Color_RGBA palegoldenrod(color_table, 238, 232, 170, 1); - const Color_RGBA palegreen(color_table, 152, 251, 152, 1); - const Color_RGBA paleturquoise(color_table, 175, 238, 238, 1); - const Color_RGBA palevioletred(color_table, 219, 112, 147, 1); - const Color_RGBA papayawhip(color_table, 255, 239, 213, 1); - const Color_RGBA peachpuff(color_table, 255, 218, 185, 1); - const Color_RGBA peru(color_table, 205, 133, 63, 1); - const Color_RGBA pink(color_table, 255, 192, 203, 1); - const Color_RGBA plum(color_table, 221, 160, 221, 1); - const Color_RGBA powderblue(color_table, 176, 224, 230, 1); - const Color_RGBA purple(color_table, 128, 0, 128, 1); - const Color_RGBA red(color_table, 255, 0, 0, 1); - const Color_RGBA rosybrown(color_table, 188, 143, 143, 1); - const Color_RGBA royalblue(color_table, 65, 105, 225, 1); - const Color_RGBA saddlebrown(color_table, 139, 69, 19, 1); - const Color_RGBA salmon(color_table, 250, 128, 114, 1); - const Color_RGBA sandybrown(color_table, 244, 164, 96, 1); - const Color_RGBA seagreen(color_table, 46, 139, 87, 1); - const Color_RGBA seashell(color_table, 255, 245, 238, 1); - const Color_RGBA sienna(color_table, 160, 82, 45, 1); - const Color_RGBA silver(color_table, 192, 192, 192, 1); - const Color_RGBA skyblue(color_table, 135, 206, 235, 1); - const Color_RGBA slateblue(color_table, 106, 90, 205, 1); - const Color_RGBA slategray(color_table, 112, 128, 144, 1); - const Color_RGBA slategrey(color_table, 112, 128, 144, 1); - const Color_RGBA snow(color_table, 255, 250, 250, 1); - const Color_RGBA springgreen(color_table, 0, 255, 127, 1); - const Color_RGBA steelblue(color_table, 70, 130, 180, 1); - const Color_RGBA tan(color_table, 210, 180, 140, 1); - const Color_RGBA teal(color_table, 0, 128, 128, 1); - const Color_RGBA thistle(color_table, 216, 191, 216, 1); - const Color_RGBA tomato(color_table, 255, 99, 71, 1); - const Color_RGBA turquoise(color_table, 64, 224, 208, 1); - const Color_RGBA violet(color_table, 238, 130, 238, 1); - const Color_RGBA wheat(color_table, 245, 222, 179, 1); - const Color_RGBA white(color_table, 255, 255, 255, 1); - const Color_RGBA whitesmoke(color_table, 245, 245, 245, 1); - const Color_RGBA yellow(color_table, 255, 255, 0, 1); - const Color_RGBA yellowgreen(color_table, 154, 205, 50, 1); - const Color_RGBA rebeccapurple(color_table, 102, 51, 153, 1); - const Color_RGBA transparent(color_table, 0, 0, 0, 0); - } + namespace Colors + { + const SourceSpan color_table(SourceSpan::tmp("[COLOR TABLE]")); + const ColorRgba aliceblue(color_table, 240, 248, 255, 1); + const ColorRgba antiquewhite(color_table, 250, 235, 215, 1); + const ColorRgba cyan(color_table, 0, 255, 255, 1); + const ColorRgba aqua(color_table, 0, 255, 255, 1); + const ColorRgba aquamarine(color_table, 127, 255, 212, 1); + const ColorRgba azure(color_table, 240, 255, 255, 1); + const ColorRgba beige(color_table, 245, 245, 220, 1); + const ColorRgba bisque(color_table, 255, 228, 196, 1); + const ColorRgba black(color_table, 0, 0, 0, 1); + const ColorRgba blanchedalmond(color_table, 255, 235, 205, 1); + const ColorRgba blue(color_table, 0, 0, 255, 1); + const ColorRgba blueviolet(color_table, 138, 43, 226, 1); + const ColorRgba brown(color_table, 165, 42, 42, 1); + const ColorRgba burlywood(color_table, 222, 184, 135, 1); + const ColorRgba cadetblue(color_table, 95, 158, 160, 1); + const ColorRgba chartreuse(color_table, 127, 255, 0, 1); + const ColorRgba chocolate(color_table, 210, 105, 30, 1); + const ColorRgba coral(color_table, 255, 127, 80, 1); + const ColorRgba cornflowerblue(color_table, 100, 149, 237, 1); + const ColorRgba cornsilk(color_table, 255, 248, 220, 1); + const ColorRgba crimson(color_table, 220, 20, 60, 1); + const ColorRgba darkblue(color_table, 0, 0, 139, 1); + const ColorRgba darkcyan(color_table, 0, 139, 139, 1); + const ColorRgba darkgoldenrod(color_table, 184, 134, 11, 1); + const ColorRgba darkgray(color_table, 169, 169, 169, 1); + const ColorRgba darkgrey(color_table, 169, 169, 169, 1); + const ColorRgba darkgreen(color_table, 0, 100, 0, 1); + const ColorRgba darkkhaki(color_table, 189, 183, 107, 1); + const ColorRgba darkmagenta(color_table, 139, 0, 139, 1); + const ColorRgba darkolivegreen(color_table, 85, 107, 47, 1); + const ColorRgba darkorange(color_table, 255, 140, 0, 1); + const ColorRgba darkorchid(color_table, 153, 50, 204, 1); + const ColorRgba darkred(color_table, 139, 0, 0, 1); + const ColorRgba darksalmon(color_table, 233, 150, 122, 1); + const ColorRgba darkseagreen(color_table, 143, 188, 143, 1); + const ColorRgba darkslateblue(color_table, 72, 61, 139, 1); + const ColorRgba darkslategray(color_table, 47, 79, 79, 1); + const ColorRgba darkslategrey(color_table, 47, 79, 79, 1); + const ColorRgba darkturquoise(color_table, 0, 206, 209, 1); + const ColorRgba darkviolet(color_table, 148, 0, 211, 1); + const ColorRgba deeppink(color_table, 255, 20, 147, 1); + const ColorRgba deepskyblue(color_table, 0, 191, 255, 1); + const ColorRgba dimgray(color_table, 105, 105, 105, 1); + const ColorRgba dimgrey(color_table, 105, 105, 105, 1); + const ColorRgba dodgerblue(color_table, 30, 144, 255, 1); + const ColorRgba firebrick(color_table, 178, 34, 34, 1); + const ColorRgba floralwhite(color_table, 255, 250, 240, 1); + const ColorRgba forestgreen(color_table, 34, 139, 34, 1); + const ColorRgba magenta(color_table, 255, 0, 255, 1); + const ColorRgba fuchsia(color_table, 255, 0, 255, 1); + const ColorRgba gainsboro(color_table, 220, 220, 220, 1); + const ColorRgba ghostwhite(color_table, 248, 248, 255, 1); + const ColorRgba gold(color_table, 255, 215, 0, 1); + const ColorRgba goldenrod(color_table, 218, 165, 32, 1); + const ColorRgba gray(color_table, 128, 128, 128, 1); + const ColorRgba grey(color_table, 128, 128, 128, 1); + const ColorRgba green(color_table, 0, 128, 0, 1); + const ColorRgba greenyellow(color_table, 173, 255, 47, 1); + const ColorRgba honeydew(color_table, 240, 255, 240, 1); + const ColorRgba hotpink(color_table, 255, 105, 180, 1); + const ColorRgba indianred(color_table, 205, 92, 92, 1); + const ColorRgba indigo(color_table, 75, 0, 130, 1); + const ColorRgba ivory(color_table, 255, 255, 240, 1); + const ColorRgba khaki(color_table, 240, 230, 140, 1); + const ColorRgba lavender(color_table, 230, 230, 250, 1); + const ColorRgba lavenderblush(color_table, 255, 240, 245, 1); + const ColorRgba lawngreen(color_table, 124, 252, 0, 1); + const ColorRgba lemonchiffon(color_table, 255, 250, 205, 1); + const ColorRgba lightblue(color_table, 173, 216, 230, 1); + const ColorRgba lightcoral(color_table, 240, 128, 128, 1); + const ColorRgba lightcyan(color_table, 224, 255, 255, 1); + const ColorRgba lightgoldenrodyellow(color_table, 250, 250, 210, 1); + const ColorRgba lightgray(color_table, 211, 211, 211, 1); + const ColorRgba lightgrey(color_table, 211, 211, 211, 1); + const ColorRgba lightgreen(color_table, 144, 238, 144, 1); + const ColorRgba lightpink(color_table, 255, 182, 193, 1); + const ColorRgba lightsalmon(color_table, 255, 160, 122, 1); + const ColorRgba lightseagreen(color_table, 32, 178, 170, 1); + const ColorRgba lightskyblue(color_table, 135, 206, 250, 1); + const ColorRgba lightslategray(color_table, 119, 136, 153, 1); + const ColorRgba lightslategrey(color_table, 119, 136, 153, 1); + const ColorRgba lightsteelblue(color_table, 176, 196, 222, 1); + const ColorRgba lightyellow(color_table, 255, 255, 224, 1); + const ColorRgba lime(color_table, 0, 255, 0, 1); + const ColorRgba limegreen(color_table, 50, 205, 50, 1); + const ColorRgba linen(color_table, 250, 240, 230, 1); + const ColorRgba maroon(color_table, 128, 0, 0, 1); + const ColorRgba mediumaquamarine(color_table, 102, 205, 170, 1); + const ColorRgba mediumblue(color_table, 0, 0, 205, 1); + const ColorRgba mediumorchid(color_table, 186, 85, 211, 1); + const ColorRgba mediumpurple(color_table, 147, 112, 219, 1); + const ColorRgba mediumseagreen(color_table, 60, 179, 113, 1); + const ColorRgba mediumslateblue(color_table, 123, 104, 238, 1); + const ColorRgba mediumspringgreen(color_table, 0, 250, 154, 1); + const ColorRgba mediumturquoise(color_table, 72, 209, 204, 1); + const ColorRgba mediumvioletred(color_table, 199, 21, 133, 1); + const ColorRgba midnightblue(color_table, 25, 25, 112, 1); + const ColorRgba mintcream(color_table, 245, 255, 250, 1); + const ColorRgba mistyrose(color_table, 255, 228, 225, 1); + const ColorRgba moccasin(color_table, 255, 228, 181, 1); + const ColorRgba navajowhite(color_table, 255, 222, 173, 1); + const ColorRgba navy(color_table, 0, 0, 128, 1); + const ColorRgba oldlace(color_table, 253, 245, 230, 1); + const ColorRgba olive(color_table, 128, 128, 0, 1); + const ColorRgba olivedrab(color_table, 107, 142, 35, 1); + const ColorRgba orange(color_table, 255, 165, 0, 1); + const ColorRgba orangered(color_table, 255, 69, 0, 1); + const ColorRgba orchid(color_table, 218, 112, 214, 1); + const ColorRgba palegoldenrod(color_table, 238, 232, 170, 1); + const ColorRgba palegreen(color_table, 152, 251, 152, 1); + const ColorRgba paleturquoise(color_table, 175, 238, 238, 1); + const ColorRgba palevioletred(color_table, 219, 112, 147, 1); + const ColorRgba papayawhip(color_table, 255, 239, 213, 1); + const ColorRgba peachpuff(color_table, 255, 218, 185, 1); + const ColorRgba peru(color_table, 205, 133, 63, 1); + const ColorRgba pink(color_table, 255, 192, 203, 1); + const ColorRgba plum(color_table, 221, 160, 221, 1); + const ColorRgba powderblue(color_table, 176, 224, 230, 1); + const ColorRgba purple(color_table, 128, 0, 128, 1); + const ColorRgba red(color_table, 255, 0, 0, 1); + const ColorRgba rosybrown(color_table, 188, 143, 143, 1); + const ColorRgba royalblue(color_table, 65, 105, 225, 1); + const ColorRgba saddlebrown(color_table, 139, 69, 19, 1); + const ColorRgba salmon(color_table, 250, 128, 114, 1); + const ColorRgba sandybrown(color_table, 244, 164, 96, 1); + const ColorRgba seagreen(color_table, 46, 139, 87, 1); + const ColorRgba seashell(color_table, 255, 245, 238, 1); + const ColorRgba sienna(color_table, 160, 82, 45, 1); + const ColorRgba silver(color_table, 192, 192, 192, 1); + const ColorRgba skyblue(color_table, 135, 206, 235, 1); + const ColorRgba slateblue(color_table, 106, 90, 205, 1); + const ColorRgba slategray(color_table, 112, 128, 144, 1); + const ColorRgba slategrey(color_table, 112, 128, 144, 1); + const ColorRgba snow(color_table, 255, 250, 250, 1); + const ColorRgba springgreen(color_table, 0, 255, 127, 1); + const ColorRgba steelblue(color_table, 70, 130, 180, 1); + const ColorRgba tan(color_table, 210, 180, 140, 1); + const ColorRgba teal(color_table, 0, 128, 128, 1); + const ColorRgba thistle(color_table, 216, 191, 216, 1); + const ColorRgba tomato(color_table, 255, 99, 71, 1); + const ColorRgba turquoise(color_table, 64, 224, 208, 1); + const ColorRgba violet(color_table, 238, 130, 238, 1); + const ColorRgba wheat(color_table, 245, 222, 179, 1); + const ColorRgba white(color_table, 255, 255, 255, 1); + const ColorRgba whitesmoke(color_table, 245, 245, 245, 1); + const ColorRgba yellow(color_table, 255, 255, 0, 1); + const ColorRgba yellowgreen(color_table, 154, 205, 50, 1); + const ColorRgba rebeccapurple(color_table, 102, 51, 153, 1); + const ColorRgba transparent(color_table, 0, 0, 0, 0); + } // namespace Colors - static const auto* const colors_to_names = new std::unordered_map { - { 240 * 0x10000 + 248 * 0x100 + 255, ColorNames::aliceblue }, - { 250 * 0x10000 + 235 * 0x100 + 215, ColorNames::antiquewhite }, - { 0 * 0x10000 + 255 * 0x100 + 255, ColorNames::cyan }, - { 127 * 0x10000 + 255 * 0x100 + 212, ColorNames::aquamarine }, - { 240 * 0x10000 + 255 * 0x100 + 255, ColorNames::azure }, - { 245 * 0x10000 + 245 * 0x100 + 220, ColorNames::beige }, - { 255 * 0x10000 + 228 * 0x100 + 196, ColorNames::bisque }, - { 0 * 0x10000 + 0 * 0x100 + 0, ColorNames::black }, - { 255 * 0x10000 + 235 * 0x100 + 205, ColorNames::blanchedalmond }, - { 0 * 0x10000 + 0 * 0x100 + 255, ColorNames::blue }, - { 138 * 0x10000 + 43 * 0x100 + 226, ColorNames::blueviolet }, - { 165 * 0x10000 + 42 * 0x100 + 42, ColorNames::brown }, - { 222 * 0x10000 + 184 * 0x100 + 135, ColorNames::burlywood }, - { 95 * 0x10000 + 158 * 0x100 + 160, ColorNames::cadetblue }, - { 127 * 0x10000 + 255 * 0x100 + 0, ColorNames::chartreuse }, - { 210 * 0x10000 + 105 * 0x100 + 30, ColorNames::chocolate }, - { 255 * 0x10000 + 127 * 0x100 + 80, ColorNames::coral }, - { 100 * 0x10000 + 149 * 0x100 + 237, ColorNames::cornflowerblue }, - { 255 * 0x10000 + 248 * 0x100 + 220, ColorNames::cornsilk }, - { 220 * 0x10000 + 20 * 0x100 + 60, ColorNames::crimson }, - { 0 * 0x10000 + 0 * 0x100 + 139, ColorNames::darkblue }, - { 0 * 0x10000 + 139 * 0x100 + 139, ColorNames::darkcyan }, - { 184 * 0x10000 + 134 * 0x100 + 11, ColorNames::darkgoldenrod }, - { 169 * 0x10000 + 169 * 0x100 + 169, ColorNames::darkgray }, - { 0 * 0x10000 + 100 * 0x100 + 0, ColorNames::darkgreen }, - { 189 * 0x10000 + 183 * 0x100 + 107, ColorNames::darkkhaki }, - { 139 * 0x10000 + 0 * 0x100 + 139, ColorNames::darkmagenta }, - { 85 * 0x10000 + 107 * 0x100 + 47, ColorNames::darkolivegreen }, - { 255 * 0x10000 + 140 * 0x100 + 0, ColorNames::darkorange }, - { 153 * 0x10000 + 50 * 0x100 + 204, ColorNames::darkorchid }, - { 139 * 0x10000 + 0 * 0x100 + 0, ColorNames::darkred }, - { 233 * 0x10000 + 150 * 0x100 + 122, ColorNames::darksalmon }, - { 143 * 0x10000 + 188 * 0x100 + 143, ColorNames::darkseagreen }, - { 72 * 0x10000 + 61 * 0x100 + 139, ColorNames::darkslateblue }, - { 47 * 0x10000 + 79 * 0x100 + 79, ColorNames::darkslategray }, - { 0 * 0x10000 + 206 * 0x100 + 209, ColorNames::darkturquoise }, - { 148 * 0x10000 + 0 * 0x100 + 211, ColorNames::darkviolet }, - { 255 * 0x10000 + 20 * 0x100 + 147, ColorNames::deeppink }, - { 0 * 0x10000 + 191 * 0x100 + 255, ColorNames::deepskyblue }, - { 105 * 0x10000 + 105 * 0x100 + 105, ColorNames::dimgray }, - { 30 * 0x10000 + 144 * 0x100 + 255, ColorNames::dodgerblue }, - { 178 * 0x10000 + 34 * 0x100 + 34, ColorNames::firebrick }, - { 255 * 0x10000 + 250 * 0x100 + 240, ColorNames::floralwhite }, - { 34 * 0x10000 + 139 * 0x100 + 34, ColorNames::forestgreen }, - { 255 * 0x10000 + 0 * 0x100 + 255, ColorNames::magenta }, - { 220 * 0x10000 + 220 * 0x100 + 220, ColorNames::gainsboro }, - { 248 * 0x10000 + 248 * 0x100 + 255, ColorNames::ghostwhite }, - { 255 * 0x10000 + 215 * 0x100 + 0, ColorNames::gold }, - { 218 * 0x10000 + 165 * 0x100 + 32, ColorNames::goldenrod }, - { 128 * 0x10000 + 128 * 0x100 + 128, ColorNames::gray }, - { 0 * 0x10000 + 128 * 0x100 + 0, ColorNames::green }, - { 173 * 0x10000 + 255 * 0x100 + 47, ColorNames::greenyellow }, - { 240 * 0x10000 + 255 * 0x100 + 240, ColorNames::honeydew }, - { 255 * 0x10000 + 105 * 0x100 + 180, ColorNames::hotpink }, - { 205 * 0x10000 + 92 * 0x100 + 92, ColorNames::indianred }, - { 75 * 0x10000 + 0 * 0x100 + 130, ColorNames::indigo }, - { 255 * 0x10000 + 255 * 0x100 + 240, ColorNames::ivory }, - { 240 * 0x10000 + 230 * 0x100 + 140, ColorNames::khaki }, - { 230 * 0x10000 + 230 * 0x100 + 250, ColorNames::lavender }, - { 255 * 0x10000 + 240 * 0x100 + 245, ColorNames::lavenderblush }, - { 124 * 0x10000 + 252 * 0x100 + 0, ColorNames::lawngreen }, - { 255 * 0x10000 + 250 * 0x100 + 205, ColorNames::lemonchiffon }, - { 173 * 0x10000 + 216 * 0x100 + 230, ColorNames::lightblue }, - { 240 * 0x10000 + 128 * 0x100 + 128, ColorNames::lightcoral }, - { 224 * 0x10000 + 255 * 0x100 + 255, ColorNames::lightcyan }, - { 250 * 0x10000 + 250 * 0x100 + 210, ColorNames::lightgoldenrodyellow }, - { 211 * 0x10000 + 211 * 0x100 + 211, ColorNames::lightgray }, - { 144 * 0x10000 + 238 * 0x100 + 144, ColorNames::lightgreen }, - { 255 * 0x10000 + 182 * 0x100 + 193, ColorNames::lightpink }, - { 255 * 0x10000 + 160 * 0x100 + 122, ColorNames::lightsalmon }, - { 32 * 0x10000 + 178 * 0x100 + 170, ColorNames::lightseagreen }, - { 135 * 0x10000 + 206 * 0x100 + 250, ColorNames::lightskyblue }, - { 119 * 0x10000 + 136 * 0x100 + 153, ColorNames::lightslategray }, - { 176 * 0x10000 + 196 * 0x100 + 222, ColorNames::lightsteelblue }, - { 255 * 0x10000 + 255 * 0x100 + 224, ColorNames::lightyellow }, - { 0 * 0x10000 + 255 * 0x100 + 0, ColorNames::lime }, - { 50 * 0x10000 + 205 * 0x100 + 50, ColorNames::limegreen }, - { 250 * 0x10000 + 240 * 0x100 + 230, ColorNames::linen }, - { 128 * 0x10000 + 0 * 0x100 + 0, ColorNames::maroon }, - { 102 * 0x10000 + 205 * 0x100 + 170, ColorNames::mediumaquamarine }, - { 0 * 0x10000 + 0 * 0x100 + 205, ColorNames::mediumblue }, - { 186 * 0x10000 + 85 * 0x100 + 211, ColorNames::mediumorchid }, - { 147 * 0x10000 + 112 * 0x100 + 219, ColorNames::mediumpurple }, - { 60 * 0x10000 + 179 * 0x100 + 113, ColorNames::mediumseagreen }, - { 123 * 0x10000 + 104 * 0x100 + 238, ColorNames::mediumslateblue }, - { 0 * 0x10000 + 250 * 0x100 + 154, ColorNames::mediumspringgreen }, - { 72 * 0x10000 + 209 * 0x100 + 204, ColorNames::mediumturquoise }, - { 199 * 0x10000 + 21 * 0x100 + 133, ColorNames::mediumvioletred }, - { 25 * 0x10000 + 25 * 0x100 + 112, ColorNames::midnightblue }, - { 245 * 0x10000 + 255 * 0x100 + 250, ColorNames::mintcream }, - { 255 * 0x10000 + 228 * 0x100 + 225, ColorNames::mistyrose }, - { 255 * 0x10000 + 228 * 0x100 + 181, ColorNames::moccasin }, - { 255 * 0x10000 + 222 * 0x100 + 173, ColorNames::navajowhite }, - { 0 * 0x10000 + 0 * 0x100 + 128, ColorNames::navy }, - { 253 * 0x10000 + 245 * 0x100 + 230, ColorNames::oldlace }, - { 128 * 0x10000 + 128 * 0x100 + 0, ColorNames::olive }, - { 107 * 0x10000 + 142 * 0x100 + 35, ColorNames::olivedrab }, - { 255 * 0x10000 + 165 * 0x100 + 0, ColorNames::orange }, - { 255 * 0x10000 + 69 * 0x100 + 0, ColorNames::orangered }, - { 218 * 0x10000 + 112 * 0x100 + 214, ColorNames::orchid }, - { 238 * 0x10000 + 232 * 0x100 + 170, ColorNames::palegoldenrod }, - { 152 * 0x10000 + 251 * 0x100 + 152, ColorNames::palegreen }, - { 175 * 0x10000 + 238 * 0x100 + 238, ColorNames::paleturquoise }, - { 219 * 0x10000 + 112 * 0x100 + 147, ColorNames::palevioletred }, - { 255 * 0x10000 + 239 * 0x100 + 213, ColorNames::papayawhip }, - { 255 * 0x10000 + 218 * 0x100 + 185, ColorNames::peachpuff }, - { 205 * 0x10000 + 133 * 0x100 + 63, ColorNames::peru }, - { 255 * 0x10000 + 192 * 0x100 + 203, ColorNames::pink }, - { 221 * 0x10000 + 160 * 0x100 + 221, ColorNames::plum }, - { 176 * 0x10000 + 224 * 0x100 + 230, ColorNames::powderblue }, - { 128 * 0x10000 + 0 * 0x100 + 128, ColorNames::purple }, - { 255 * 0x10000 + 0 * 0x100 + 0, ColorNames::red }, - { 188 * 0x10000 + 143 * 0x100 + 143, ColorNames::rosybrown }, - { 65 * 0x10000 + 105 * 0x100 + 225, ColorNames::royalblue }, - { 139 * 0x10000 + 69 * 0x100 + 19, ColorNames::saddlebrown }, - { 250 * 0x10000 + 128 * 0x100 + 114, ColorNames::salmon }, - { 244 * 0x10000 + 164 * 0x100 + 96, ColorNames::sandybrown }, - { 46 * 0x10000 + 139 * 0x100 + 87, ColorNames::seagreen }, - { 255 * 0x10000 + 245 * 0x100 + 238, ColorNames::seashell }, - { 160 * 0x10000 + 82 * 0x100 + 45, ColorNames::sienna }, - { 192 * 0x10000 + 192 * 0x100 + 192, ColorNames::silver }, - { 135 * 0x10000 + 206 * 0x100 + 235, ColorNames::skyblue }, - { 106 * 0x10000 + 90 * 0x100 + 205, ColorNames::slateblue }, - { 112 * 0x10000 + 128 * 0x100 + 144, ColorNames::slategray }, - { 255 * 0x10000 + 250 * 0x100 + 250, ColorNames::snow }, - { 0 * 0x10000 + 255 * 0x100 + 127, ColorNames::springgreen }, - { 70 * 0x10000 + 130 * 0x100 + 180, ColorNames::steelblue }, - { 210 * 0x10000 + 180 * 0x100 + 140, ColorNames::tan }, - { 0 * 0x10000 + 128 * 0x100 + 128, ColorNames::teal }, - { 216 * 0x10000 + 191 * 0x100 + 216, ColorNames::thistle }, - { 255 * 0x10000 + 99 * 0x100 + 71, ColorNames::tomato }, - { 64 * 0x10000 + 224 * 0x100 + 208, ColorNames::turquoise }, - { 238 * 0x10000 + 130 * 0x100 + 238, ColorNames::violet }, - { 245 * 0x10000 + 222 * 0x100 + 179, ColorNames::wheat }, - { 255 * 0x10000 + 255 * 0x100 + 255, ColorNames::white }, - { 245 * 0x10000 + 245 * 0x100 + 245, ColorNames::whitesmoke }, - { 255 * 0x10000 + 255 * 0x100 + 0, ColorNames::yellow }, - { 154 * 0x10000 + 205 * 0x100 + 50, ColorNames::yellowgreen }, - { 102 * 0x10000 + 51 * 0x100 + 153, ColorNames::rebeccapurple } - }; + static const auto* const colors_to_names = new std::unordered_map{ + {240 * 0x10000 + 248 * 0x100 + 255, ColorNames::aliceblue}, + {250 * 0x10000 + 235 * 0x100 + 215, ColorNames::antiquewhite}, + {0 * 0x10000 + 255 * 0x100 + 255, ColorNames::cyan}, + {127 * 0x10000 + 255 * 0x100 + 212, ColorNames::aquamarine}, + {240 * 0x10000 + 255 * 0x100 + 255, ColorNames::azure}, + {245 * 0x10000 + 245 * 0x100 + 220, ColorNames::beige}, + {255 * 0x10000 + 228 * 0x100 + 196, ColorNames::bisque}, + {0 * 0x10000 + 0 * 0x100 + 0, ColorNames::black}, + {255 * 0x10000 + 235 * 0x100 + 205, ColorNames::blanchedalmond}, + {0 * 0x10000 + 0 * 0x100 + 255, ColorNames::blue}, + {138 * 0x10000 + 43 * 0x100 + 226, ColorNames::blueviolet}, + {165 * 0x10000 + 42 * 0x100 + 42, ColorNames::brown}, + {222 * 0x10000 + 184 * 0x100 + 135, ColorNames::burlywood}, + {95 * 0x10000 + 158 * 0x100 + 160, ColorNames::cadetblue}, + {127 * 0x10000 + 255 * 0x100 + 0, ColorNames::chartreuse}, + {210 * 0x10000 + 105 * 0x100 + 30, ColorNames::chocolate}, + {255 * 0x10000 + 127 * 0x100 + 80, ColorNames::coral}, + {100 * 0x10000 + 149 * 0x100 + 237, ColorNames::cornflowerblue}, + {255 * 0x10000 + 248 * 0x100 + 220, ColorNames::cornsilk}, + {220 * 0x10000 + 20 * 0x100 + 60, ColorNames::crimson}, + {0 * 0x10000 + 0 * 0x100 + 139, ColorNames::darkblue}, + {0 * 0x10000 + 139 * 0x100 + 139, ColorNames::darkcyan}, + {184 * 0x10000 + 134 * 0x100 + 11, ColorNames::darkgoldenrod}, + {169 * 0x10000 + 169 * 0x100 + 169, ColorNames::darkgray}, + {0 * 0x10000 + 100 * 0x100 + 0, ColorNames::darkgreen}, + {189 * 0x10000 + 183 * 0x100 + 107, ColorNames::darkkhaki}, + {139 * 0x10000 + 0 * 0x100 + 139, ColorNames::darkmagenta}, + {85 * 0x10000 + 107 * 0x100 + 47, ColorNames::darkolivegreen}, + {255 * 0x10000 + 140 * 0x100 + 0, ColorNames::darkorange}, + {153 * 0x10000 + 50 * 0x100 + 204, ColorNames::darkorchid}, + {139 * 0x10000 + 0 * 0x100 + 0, ColorNames::darkred}, + {233 * 0x10000 + 150 * 0x100 + 122, ColorNames::darksalmon}, + {143 * 0x10000 + 188 * 0x100 + 143, ColorNames::darkseagreen}, + {72 * 0x10000 + 61 * 0x100 + 139, ColorNames::darkslateblue}, + {47 * 0x10000 + 79 * 0x100 + 79, ColorNames::darkslategray}, + {0 * 0x10000 + 206 * 0x100 + 209, ColorNames::darkturquoise}, + {148 * 0x10000 + 0 * 0x100 + 211, ColorNames::darkviolet}, + {255 * 0x10000 + 20 * 0x100 + 147, ColorNames::deeppink}, + {0 * 0x10000 + 191 * 0x100 + 255, ColorNames::deepskyblue}, + {105 * 0x10000 + 105 * 0x100 + 105, ColorNames::dimgray}, + {30 * 0x10000 + 144 * 0x100 + 255, ColorNames::dodgerblue}, + {178 * 0x10000 + 34 * 0x100 + 34, ColorNames::firebrick}, + {255 * 0x10000 + 250 * 0x100 + 240, ColorNames::floralwhite}, + {34 * 0x10000 + 139 * 0x100 + 34, ColorNames::forestgreen}, + {255 * 0x10000 + 0 * 0x100 + 255, ColorNames::magenta}, + {220 * 0x10000 + 220 * 0x100 + 220, ColorNames::gainsboro}, + {248 * 0x10000 + 248 * 0x100 + 255, ColorNames::ghostwhite}, + {255 * 0x10000 + 215 * 0x100 + 0, ColorNames::gold}, + {218 * 0x10000 + 165 * 0x100 + 32, ColorNames::goldenrod}, + {128 * 0x10000 + 128 * 0x100 + 128, ColorNames::gray}, + {0 * 0x10000 + 128 * 0x100 + 0, ColorNames::green}, + {173 * 0x10000 + 255 * 0x100 + 47, ColorNames::greenyellow}, + {240 * 0x10000 + 255 * 0x100 + 240, ColorNames::honeydew}, + {255 * 0x10000 + 105 * 0x100 + 180, ColorNames::hotpink}, + {205 * 0x10000 + 92 * 0x100 + 92, ColorNames::indianred}, + {75 * 0x10000 + 0 * 0x100 + 130, ColorNames::indigo}, + {255 * 0x10000 + 255 * 0x100 + 240, ColorNames::ivory}, + {240 * 0x10000 + 230 * 0x100 + 140, ColorNames::khaki}, + {230 * 0x10000 + 230 * 0x100 + 250, ColorNames::lavender}, + {255 * 0x10000 + 240 * 0x100 + 245, ColorNames::lavenderblush}, + {124 * 0x10000 + 252 * 0x100 + 0, ColorNames::lawngreen}, + {255 * 0x10000 + 250 * 0x100 + 205, ColorNames::lemonchiffon}, + {173 * 0x10000 + 216 * 0x100 + 230, ColorNames::lightblue}, + {240 * 0x10000 + 128 * 0x100 + 128, ColorNames::lightcoral}, + {224 * 0x10000 + 255 * 0x100 + 255, ColorNames::lightcyan}, + {250 * 0x10000 + 250 * 0x100 + 210, ColorNames::lightgoldenrodyellow}, + {211 * 0x10000 + 211 * 0x100 + 211, ColorNames::lightgray}, + {144 * 0x10000 + 238 * 0x100 + 144, ColorNames::lightgreen}, + {255 * 0x10000 + 182 * 0x100 + 193, ColorNames::lightpink}, + {255 * 0x10000 + 160 * 0x100 + 122, ColorNames::lightsalmon}, + {32 * 0x10000 + 178 * 0x100 + 170, ColorNames::lightseagreen}, + {135 * 0x10000 + 206 * 0x100 + 250, ColorNames::lightskyblue}, + {119 * 0x10000 + 136 * 0x100 + 153, ColorNames::lightslategray}, + {176 * 0x10000 + 196 * 0x100 + 222, ColorNames::lightsteelblue}, + {255 * 0x10000 + 255 * 0x100 + 224, ColorNames::lightyellow}, + {0 * 0x10000 + 255 * 0x100 + 0, ColorNames::lime}, + {50 * 0x10000 + 205 * 0x100 + 50, ColorNames::limegreen}, + {250 * 0x10000 + 240 * 0x100 + 230, ColorNames::linen}, + {128 * 0x10000 + 0 * 0x100 + 0, ColorNames::maroon}, + {102 * 0x10000 + 205 * 0x100 + 170, ColorNames::mediumaquamarine}, + {0 * 0x10000 + 0 * 0x100 + 205, ColorNames::mediumblue}, + {186 * 0x10000 + 85 * 0x100 + 211, ColorNames::mediumorchid}, + {147 * 0x10000 + 112 * 0x100 + 219, ColorNames::mediumpurple}, + {60 * 0x10000 + 179 * 0x100 + 113, ColorNames::mediumseagreen}, + {123 * 0x10000 + 104 * 0x100 + 238, ColorNames::mediumslateblue}, + {0 * 0x10000 + 250 * 0x100 + 154, ColorNames::mediumspringgreen}, + {72 * 0x10000 + 209 * 0x100 + 204, ColorNames::mediumturquoise}, + {199 * 0x10000 + 21 * 0x100 + 133, ColorNames::mediumvioletred}, + {25 * 0x10000 + 25 * 0x100 + 112, ColorNames::midnightblue}, + {245 * 0x10000 + 255 * 0x100 + 250, ColorNames::mintcream}, + {255 * 0x10000 + 228 * 0x100 + 225, ColorNames::mistyrose}, + {255 * 0x10000 + 228 * 0x100 + 181, ColorNames::moccasin}, + {255 * 0x10000 + 222 * 0x100 + 173, ColorNames::navajowhite}, + {0 * 0x10000 + 0 * 0x100 + 128, ColorNames::navy}, + {253 * 0x10000 + 245 * 0x100 + 230, ColorNames::oldlace}, + {128 * 0x10000 + 128 * 0x100 + 0, ColorNames::olive}, + {107 * 0x10000 + 142 * 0x100 + 35, ColorNames::olivedrab}, + {255 * 0x10000 + 165 * 0x100 + 0, ColorNames::orange}, + {255 * 0x10000 + 69 * 0x100 + 0, ColorNames::orangered}, + {218 * 0x10000 + 112 * 0x100 + 214, ColorNames::orchid}, + {238 * 0x10000 + 232 * 0x100 + 170, ColorNames::palegoldenrod}, + {152 * 0x10000 + 251 * 0x100 + 152, ColorNames::palegreen}, + {175 * 0x10000 + 238 * 0x100 + 238, ColorNames::paleturquoise}, + {219 * 0x10000 + 112 * 0x100 + 147, ColorNames::palevioletred}, + {255 * 0x10000 + 239 * 0x100 + 213, ColorNames::papayawhip}, + {255 * 0x10000 + 218 * 0x100 + 185, ColorNames::peachpuff}, + {205 * 0x10000 + 133 * 0x100 + 63, ColorNames::peru}, + {255 * 0x10000 + 192 * 0x100 + 203, ColorNames::pink}, + {221 * 0x10000 + 160 * 0x100 + 221, ColorNames::plum}, + {176 * 0x10000 + 224 * 0x100 + 230, ColorNames::powderblue}, + {128 * 0x10000 + 0 * 0x100 + 128, ColorNames::purple}, + {255 * 0x10000 + 0 * 0x100 + 0, ColorNames::red}, + {188 * 0x10000 + 143 * 0x100 + 143, ColorNames::rosybrown}, + {65 * 0x10000 + 105 * 0x100 + 225, ColorNames::royalblue}, + {139 * 0x10000 + 69 * 0x100 + 19, ColorNames::saddlebrown}, + {250 * 0x10000 + 128 * 0x100 + 114, ColorNames::salmon}, + {244 * 0x10000 + 164 * 0x100 + 96, ColorNames::sandybrown}, + {46 * 0x10000 + 139 * 0x100 + 87, ColorNames::seagreen}, + {255 * 0x10000 + 245 * 0x100 + 238, ColorNames::seashell}, + {160 * 0x10000 + 82 * 0x100 + 45, ColorNames::sienna}, + {192 * 0x10000 + 192 * 0x100 + 192, ColorNames::silver}, + {135 * 0x10000 + 206 * 0x100 + 235, ColorNames::skyblue}, + {106 * 0x10000 + 90 * 0x100 + 205, ColorNames::slateblue}, + {112 * 0x10000 + 128 * 0x100 + 144, ColorNames::slategray}, + {255 * 0x10000 + 250 * 0x100 + 250, ColorNames::snow}, + {0 * 0x10000 + 255 * 0x100 + 127, ColorNames::springgreen}, + {70 * 0x10000 + 130 * 0x100 + 180, ColorNames::steelblue}, + {210 * 0x10000 + 180 * 0x100 + 140, ColorNames::tan}, + {0 * 0x10000 + 128 * 0x100 + 128, ColorNames::teal}, + {216 * 0x10000 + 191 * 0x100 + 216, ColorNames::thistle}, + {255 * 0x10000 + 99 * 0x100 + 71, ColorNames::tomato}, + {64 * 0x10000 + 224 * 0x100 + 208, ColorNames::turquoise}, + {238 * 0x10000 + 130 * 0x100 + 238, ColorNames::violet}, + {245 * 0x10000 + 222 * 0x100 + 179, ColorNames::wheat}, + {255 * 0x10000 + 255 * 0x100 + 255, ColorNames::white}, + {245 * 0x10000 + 245 * 0x100 + 245, ColorNames::whitesmoke}, + {255 * 0x10000 + 255 * 0x100 + 0, ColorNames::yellow}, + {154 * 0x10000 + 205 * 0x100 + 50, ColorNames::yellowgreen}, + {102 * 0x10000 + 51 * 0x100 + 153, ColorNames::rebeccapurple}}; - static const auto *const names_to_colors = new std::unordered_map - { - { ColorNames::aliceblue, &Colors::aliceblue }, - { ColorNames::antiquewhite, &Colors::antiquewhite }, - { ColorNames::cyan, &Colors::cyan }, - { ColorNames::aqua, &Colors::aqua }, - { ColorNames::aquamarine, &Colors::aquamarine }, - { ColorNames::azure, &Colors::azure }, - { ColorNames::beige, &Colors::beige }, - { ColorNames::bisque, &Colors::bisque }, - { ColorNames::black, &Colors::black }, - { ColorNames::blanchedalmond, &Colors::blanchedalmond }, - { ColorNames::blue, &Colors::blue }, - { ColorNames::blueviolet, &Colors::blueviolet }, - { ColorNames::brown, &Colors::brown }, - { ColorNames::burlywood, &Colors::burlywood }, - { ColorNames::cadetblue, &Colors::cadetblue }, - { ColorNames::chartreuse, &Colors::chartreuse }, - { ColorNames::chocolate, &Colors::chocolate }, - { ColorNames::coral, &Colors::coral }, - { ColorNames::cornflowerblue, &Colors::cornflowerblue }, - { ColorNames::cornsilk, &Colors::cornsilk }, - { ColorNames::crimson, &Colors::crimson }, - { ColorNames::darkblue, &Colors::darkblue }, - { ColorNames::darkcyan, &Colors::darkcyan }, - { ColorNames::darkgoldenrod, &Colors::darkgoldenrod }, - { ColorNames::darkgray, &Colors::darkgray }, - { ColorNames::darkgrey, &Colors::darkgrey }, - { ColorNames::darkgreen, &Colors::darkgreen }, - { ColorNames::darkkhaki, &Colors::darkkhaki }, - { ColorNames::darkmagenta, &Colors::darkmagenta }, - { ColorNames::darkolivegreen, &Colors::darkolivegreen }, - { ColorNames::darkorange, &Colors::darkorange }, - { ColorNames::darkorchid, &Colors::darkorchid }, - { ColorNames::darkred, &Colors::darkred }, - { ColorNames::darksalmon, &Colors::darksalmon }, - { ColorNames::darkseagreen, &Colors::darkseagreen }, - { ColorNames::darkslateblue, &Colors::darkslateblue }, - { ColorNames::darkslategray, &Colors::darkslategray }, - { ColorNames::darkslategrey, &Colors::darkslategrey }, - { ColorNames::darkturquoise, &Colors::darkturquoise }, - { ColorNames::darkviolet, &Colors::darkviolet }, - { ColorNames::deeppink, &Colors::deeppink }, - { ColorNames::deepskyblue, &Colors::deepskyblue }, - { ColorNames::dimgray, &Colors::dimgray }, - { ColorNames::dimgrey, &Colors::dimgrey }, - { ColorNames::dodgerblue, &Colors::dodgerblue }, - { ColorNames::firebrick, &Colors::firebrick }, - { ColorNames::floralwhite, &Colors::floralwhite }, - { ColorNames::forestgreen, &Colors::forestgreen }, - { ColorNames::magenta, &Colors::magenta }, - { ColorNames::fuchsia, &Colors::fuchsia }, - { ColorNames::gainsboro, &Colors::gainsboro }, - { ColorNames::ghostwhite, &Colors::ghostwhite }, - { ColorNames::gold, &Colors::gold }, - { ColorNames::goldenrod, &Colors::goldenrod }, - { ColorNames::gray, &Colors::gray }, - { ColorNames::grey, &Colors::grey }, - { ColorNames::green, &Colors::green }, - { ColorNames::greenyellow, &Colors::greenyellow }, - { ColorNames::honeydew, &Colors::honeydew }, - { ColorNames::hotpink, &Colors::hotpink }, - { ColorNames::indianred, &Colors::indianred }, - { ColorNames::indigo, &Colors::indigo }, - { ColorNames::ivory, &Colors::ivory }, - { ColorNames::khaki, &Colors::khaki }, - { ColorNames::lavender, &Colors::lavender }, - { ColorNames::lavenderblush, &Colors::lavenderblush }, - { ColorNames::lawngreen, &Colors::lawngreen }, - { ColorNames::lemonchiffon, &Colors::lemonchiffon }, - { ColorNames::lightblue, &Colors::lightblue }, - { ColorNames::lightcoral, &Colors::lightcoral }, - { ColorNames::lightcyan, &Colors::lightcyan }, - { ColorNames::lightgoldenrodyellow, &Colors::lightgoldenrodyellow }, - { ColorNames::lightgray, &Colors::lightgray }, - { ColorNames::lightgrey, &Colors::lightgrey }, - { ColorNames::lightgreen, &Colors::lightgreen }, - { ColorNames::lightpink, &Colors::lightpink }, - { ColorNames::lightsalmon, &Colors::lightsalmon }, - { ColorNames::lightseagreen, &Colors::lightseagreen }, - { ColorNames::lightskyblue, &Colors::lightskyblue }, - { ColorNames::lightslategray, &Colors::lightslategray }, - { ColorNames::lightslategrey, &Colors::lightslategrey }, - { ColorNames::lightsteelblue, &Colors::lightsteelblue }, - { ColorNames::lightyellow, &Colors::lightyellow }, - { ColorNames::lime, &Colors::lime }, - { ColorNames::limegreen, &Colors::limegreen }, - { ColorNames::linen, &Colors::linen }, - { ColorNames::maroon, &Colors::maroon }, - { ColorNames::mediumaquamarine, &Colors::mediumaquamarine }, - { ColorNames::mediumblue, &Colors::mediumblue }, - { ColorNames::mediumorchid, &Colors::mediumorchid }, - { ColorNames::mediumpurple, &Colors::mediumpurple }, - { ColorNames::mediumseagreen, &Colors::mediumseagreen }, - { ColorNames::mediumslateblue, &Colors::mediumslateblue }, - { ColorNames::mediumspringgreen, &Colors::mediumspringgreen }, - { ColorNames::mediumturquoise, &Colors::mediumturquoise }, - { ColorNames::mediumvioletred, &Colors::mediumvioletred }, - { ColorNames::midnightblue, &Colors::midnightblue }, - { ColorNames::mintcream, &Colors::mintcream }, - { ColorNames::mistyrose, &Colors::mistyrose }, - { ColorNames::moccasin, &Colors::moccasin }, - { ColorNames::navajowhite, &Colors::navajowhite }, - { ColorNames::navy, &Colors::navy }, - { ColorNames::oldlace, &Colors::oldlace }, - { ColorNames::olive, &Colors::olive }, - { ColorNames::olivedrab, &Colors::olivedrab }, - { ColorNames::orange, &Colors::orange }, - { ColorNames::orangered, &Colors::orangered }, - { ColorNames::orchid, &Colors::orchid }, - { ColorNames::palegoldenrod, &Colors::palegoldenrod }, - { ColorNames::palegreen, &Colors::palegreen }, - { ColorNames::paleturquoise, &Colors::paleturquoise }, - { ColorNames::palevioletred, &Colors::palevioletred }, - { ColorNames::papayawhip, &Colors::papayawhip }, - { ColorNames::peachpuff, &Colors::peachpuff }, - { ColorNames::peru, &Colors::peru }, - { ColorNames::pink, &Colors::pink }, - { ColorNames::plum, &Colors::plum }, - { ColorNames::powderblue, &Colors::powderblue }, - { ColorNames::purple, &Colors::purple }, - { ColorNames::red, &Colors::red }, - { ColorNames::rosybrown, &Colors::rosybrown }, - { ColorNames::royalblue, &Colors::royalblue }, - { ColorNames::saddlebrown, &Colors::saddlebrown }, - { ColorNames::salmon, &Colors::salmon }, - { ColorNames::sandybrown, &Colors::sandybrown }, - { ColorNames::seagreen, &Colors::seagreen }, - { ColorNames::seashell, &Colors::seashell }, - { ColorNames::sienna, &Colors::sienna }, - { ColorNames::silver, &Colors::silver }, - { ColorNames::skyblue, &Colors::skyblue }, - { ColorNames::slateblue, &Colors::slateblue }, - { ColorNames::slategray, &Colors::slategray }, - { ColorNames::slategrey, &Colors::slategrey }, - { ColorNames::snow, &Colors::snow }, - { ColorNames::springgreen, &Colors::springgreen }, - { ColorNames::steelblue, &Colors::steelblue }, - { ColorNames::tan, &Colors::tan }, - { ColorNames::teal, &Colors::teal }, - { ColorNames::thistle, &Colors::thistle }, - { ColorNames::tomato, &Colors::tomato }, - { ColorNames::turquoise, &Colors::turquoise }, - { ColorNames::violet, &Colors::violet }, - { ColorNames::wheat, &Colors::wheat }, - { ColorNames::white, &Colors::white }, - { ColorNames::whitesmoke, &Colors::whitesmoke }, - { ColorNames::yellow, &Colors::yellow }, - { ColorNames::yellowgreen, &Colors::yellowgreen }, - { ColorNames::rebeccapurple, &Colors::rebeccapurple }, - { ColorNames::transparent, &Colors::transparent } - }; + static const auto* const names_to_colors = + new std::unordered_map{ + {ColorNames::aliceblue, &Colors::aliceblue}, + {ColorNames::antiquewhite, &Colors::antiquewhite}, + {ColorNames::cyan, &Colors::cyan}, + {ColorNames::aqua, &Colors::aqua}, + {ColorNames::aquamarine, &Colors::aquamarine}, + {ColorNames::azure, &Colors::azure}, + {ColorNames::beige, &Colors::beige}, + {ColorNames::bisque, &Colors::bisque}, + {ColorNames::black, &Colors::black}, + {ColorNames::blanchedalmond, &Colors::blanchedalmond}, + {ColorNames::blue, &Colors::blue}, + {ColorNames::blueviolet, &Colors::blueviolet}, + {ColorNames::brown, &Colors::brown}, + {ColorNames::burlywood, &Colors::burlywood}, + {ColorNames::cadetblue, &Colors::cadetblue}, + {ColorNames::chartreuse, &Colors::chartreuse}, + {ColorNames::chocolate, &Colors::chocolate}, + {ColorNames::coral, &Colors::coral}, + {ColorNames::cornflowerblue, &Colors::cornflowerblue}, + {ColorNames::cornsilk, &Colors::cornsilk}, + {ColorNames::crimson, &Colors::crimson}, + {ColorNames::darkblue, &Colors::darkblue}, + {ColorNames::darkcyan, &Colors::darkcyan}, + {ColorNames::darkgoldenrod, &Colors::darkgoldenrod}, + {ColorNames::darkgray, &Colors::darkgray}, + {ColorNames::darkgrey, &Colors::darkgrey}, + {ColorNames::darkgreen, &Colors::darkgreen}, + {ColorNames::darkkhaki, &Colors::darkkhaki}, + {ColorNames::darkmagenta, &Colors::darkmagenta}, + {ColorNames::darkolivegreen, &Colors::darkolivegreen}, + {ColorNames::darkorange, &Colors::darkorange}, + {ColorNames::darkorchid, &Colors::darkorchid}, + {ColorNames::darkred, &Colors::darkred}, + {ColorNames::darksalmon, &Colors::darksalmon}, + {ColorNames::darkseagreen, &Colors::darkseagreen}, + {ColorNames::darkslateblue, &Colors::darkslateblue}, + {ColorNames::darkslategray, &Colors::darkslategray}, + {ColorNames::darkslategrey, &Colors::darkslategrey}, + {ColorNames::darkturquoise, &Colors::darkturquoise}, + {ColorNames::darkviolet, &Colors::darkviolet}, + {ColorNames::deeppink, &Colors::deeppink}, + {ColorNames::deepskyblue, &Colors::deepskyblue}, + {ColorNames::dimgray, &Colors::dimgray}, + {ColorNames::dimgrey, &Colors::dimgrey}, + {ColorNames::dodgerblue, &Colors::dodgerblue}, + {ColorNames::firebrick, &Colors::firebrick}, + {ColorNames::floralwhite, &Colors::floralwhite}, + {ColorNames::forestgreen, &Colors::forestgreen}, + {ColorNames::magenta, &Colors::magenta}, + {ColorNames::fuchsia, &Colors::fuchsia}, + {ColorNames::gainsboro, &Colors::gainsboro}, + {ColorNames::ghostwhite, &Colors::ghostwhite}, + {ColorNames::gold, &Colors::gold}, + {ColorNames::goldenrod, &Colors::goldenrod}, + {ColorNames::gray, &Colors::gray}, + {ColorNames::grey, &Colors::grey}, + {ColorNames::green, &Colors::green}, + {ColorNames::greenyellow, &Colors::greenyellow}, + {ColorNames::honeydew, &Colors::honeydew}, + {ColorNames::hotpink, &Colors::hotpink}, + {ColorNames::indianred, &Colors::indianred}, + {ColorNames::indigo, &Colors::indigo}, + {ColorNames::ivory, &Colors::ivory}, + {ColorNames::khaki, &Colors::khaki}, + {ColorNames::lavender, &Colors::lavender}, + {ColorNames::lavenderblush, &Colors::lavenderblush}, + {ColorNames::lawngreen, &Colors::lawngreen}, + {ColorNames::lemonchiffon, &Colors::lemonchiffon}, + {ColorNames::lightblue, &Colors::lightblue}, + {ColorNames::lightcoral, &Colors::lightcoral}, + {ColorNames::lightcyan, &Colors::lightcyan}, + {ColorNames::lightgoldenrodyellow, &Colors::lightgoldenrodyellow}, + {ColorNames::lightgray, &Colors::lightgray}, + {ColorNames::lightgrey, &Colors::lightgrey}, + {ColorNames::lightgreen, &Colors::lightgreen}, + {ColorNames::lightpink, &Colors::lightpink}, + {ColorNames::lightsalmon, &Colors::lightsalmon}, + {ColorNames::lightseagreen, &Colors::lightseagreen}, + {ColorNames::lightskyblue, &Colors::lightskyblue}, + {ColorNames::lightslategray, &Colors::lightslategray}, + {ColorNames::lightslategrey, &Colors::lightslategrey}, + {ColorNames::lightsteelblue, &Colors::lightsteelblue}, + {ColorNames::lightyellow, &Colors::lightyellow}, + {ColorNames::lime, &Colors::lime}, + {ColorNames::limegreen, &Colors::limegreen}, + {ColorNames::linen, &Colors::linen}, + {ColorNames::maroon, &Colors::maroon}, + {ColorNames::mediumaquamarine, &Colors::mediumaquamarine}, + {ColorNames::mediumblue, &Colors::mediumblue}, + {ColorNames::mediumorchid, &Colors::mediumorchid}, + {ColorNames::mediumpurple, &Colors::mediumpurple}, + {ColorNames::mediumseagreen, &Colors::mediumseagreen}, + {ColorNames::mediumslateblue, &Colors::mediumslateblue}, + {ColorNames::mediumspringgreen, &Colors::mediumspringgreen}, + {ColorNames::mediumturquoise, &Colors::mediumturquoise}, + {ColorNames::mediumvioletred, &Colors::mediumvioletred}, + {ColorNames::midnightblue, &Colors::midnightblue}, + {ColorNames::mintcream, &Colors::mintcream}, + {ColorNames::mistyrose, &Colors::mistyrose}, + {ColorNames::moccasin, &Colors::moccasin}, + {ColorNames::navajowhite, &Colors::navajowhite}, + {ColorNames::navy, &Colors::navy}, + {ColorNames::oldlace, &Colors::oldlace}, + {ColorNames::olive, &Colors::olive}, + {ColorNames::olivedrab, &Colors::olivedrab}, + {ColorNames::orange, &Colors::orange}, + {ColorNames::orangered, &Colors::orangered}, + {ColorNames::orchid, &Colors::orchid}, + {ColorNames::palegoldenrod, &Colors::palegoldenrod}, + {ColorNames::palegreen, &Colors::palegreen}, + {ColorNames::paleturquoise, &Colors::paleturquoise}, + {ColorNames::palevioletred, &Colors::palevioletred}, + {ColorNames::papayawhip, &Colors::papayawhip}, + {ColorNames::peachpuff, &Colors::peachpuff}, + {ColorNames::peru, &Colors::peru}, + {ColorNames::pink, &Colors::pink}, + {ColorNames::plum, &Colors::plum}, + {ColorNames::powderblue, &Colors::powderblue}, + {ColorNames::purple, &Colors::purple}, + {ColorNames::red, &Colors::red}, + {ColorNames::rosybrown, &Colors::rosybrown}, + {ColorNames::royalblue, &Colors::royalblue}, + {ColorNames::saddlebrown, &Colors::saddlebrown}, + {ColorNames::salmon, &Colors::salmon}, + {ColorNames::sandybrown, &Colors::sandybrown}, + {ColorNames::seagreen, &Colors::seagreen}, + {ColorNames::seashell, &Colors::seashell}, + {ColorNames::sienna, &Colors::sienna}, + {ColorNames::silver, &Colors::silver}, + {ColorNames::skyblue, &Colors::skyblue}, + {ColorNames::slateblue, &Colors::slateblue}, + {ColorNames::slategray, &Colors::slategray}, + {ColorNames::slategrey, &Colors::slategrey}, + {ColorNames::snow, &Colors::snow}, + {ColorNames::springgreen, &Colors::springgreen}, + {ColorNames::steelblue, &Colors::steelblue}, + {ColorNames::tan, &Colors::tan}, + {ColorNames::teal, &Colors::teal}, + {ColorNames::thistle, &Colors::thistle}, + {ColorNames::tomato, &Colors::tomato}, + {ColorNames::turquoise, &Colors::turquoise}, + {ColorNames::violet, &Colors::violet}, + {ColorNames::wheat, &Colors::wheat}, + {ColorNames::white, &Colors::white}, + {ColorNames::whitesmoke, &Colors::whitesmoke}, + {ColorNames::yellow, &Colors::yellow}, + {ColorNames::yellowgreen, &Colors::yellowgreen}, + {ColorNames::rebeccapurple, &Colors::rebeccapurple}, + {ColorNames::transparent, &Colors::transparent}}; - const Color_RGBA* name_to_color(const char* key) - { - return name_to_color(sass::string(key)); - } + const ColorRgba* name_to_color(const char* key) + { + return name_to_color(sass::string(key)); + } - const Color_RGBA* name_to_color(const sass::string& key) - { - // case insensitive lookup. See #2462 - sass::string lower = key; - Util::ascii_str_tolower(&lower); + const ColorRgba* name_to_color(const sass::string& key) + { + // case insensitive lookup. See #2462 + sass::string lcKey = key; + StringUtils::makeLowerCase(lcKey); - auto p = names_to_colors->find(lower); - if (p != names_to_colors->end()) { - return p->second; - } - return nullptr; - } + auto p = names_to_colors->find(lcKey); + if(p != names_to_colors->end()) + { + return p->second; + } - const char* color_to_name(const int key) - { - auto p = colors_to_names->find(key); - if (p != colors_to_names->end()) { - return p->second; - } - return nullptr; - } + return nullptr; + } - const char* color_to_name(const double key) - { - return color_to_name((int)key); - } + const char* color_to_name(const int key) + { + auto p = colors_to_names->find(key); + if(p != colors_to_names->end()) + { + sass::string rv = p->second; + // Match dart-sass output + if(rv == "magenta") + { + return "fuchsia"; + } + if(rv == "cyan") + { + return "aqua"; + } + return p->second; + } + return nullptr; + } - const char* color_to_name(const Color_RGBA& c) - { - double key = c.r() * 0x10000 - + c.g() * 0x100 - + c.b(); - return color_to_name(key); - } + const char* color_to_name(const double key) + { + return color_to_name((int)key); + } -} +} // namespace Sass diff --git a/src/color_maps.hpp b/src/color_maps.hpp index c365f3ba2a..59f50e9be6 100644 --- a/src/color_maps.hpp +++ b/src/color_maps.hpp @@ -1,9 +1,14 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_COLOR_MAPS_HPP +#define SASS_COLOR_MAPS_HPP -#ifndef SASS_COLOR_MAPS_H -#define SASS_COLOR_MAPS_H +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include -#include "ast.hpp" +#include "ast_values.hpp" namespace Sass { @@ -161,161 +166,160 @@ namespace Sass { } namespace Colors { - extern const Color_RGBA aliceblue; - extern const Color_RGBA antiquewhite; - extern const Color_RGBA cyan; - extern const Color_RGBA aqua; - extern const Color_RGBA aquamarine; - extern const Color_RGBA azure; - extern const Color_RGBA beige; - extern const Color_RGBA bisque; - extern const Color_RGBA black; - extern const Color_RGBA blanchedalmond; - extern const Color_RGBA blue; - extern const Color_RGBA blueviolet; - extern const Color_RGBA brown; - extern const Color_RGBA burlywood; - extern const Color_RGBA cadetblue; - extern const Color_RGBA chartreuse; - extern const Color_RGBA chocolate; - extern const Color_RGBA coral; - extern const Color_RGBA cornflowerblue; - extern const Color_RGBA cornsilk; - extern const Color_RGBA crimson; - extern const Color_RGBA darkblue; - extern const Color_RGBA darkcyan; - extern const Color_RGBA darkgoldenrod; - extern const Color_RGBA darkgray; - extern const Color_RGBA darkgrey; - extern const Color_RGBA darkgreen; - extern const Color_RGBA darkkhaki; - extern const Color_RGBA darkmagenta; - extern const Color_RGBA darkolivegreen; - extern const Color_RGBA darkorange; - extern const Color_RGBA darkorchid; - extern const Color_RGBA darkred; - extern const Color_RGBA darksalmon; - extern const Color_RGBA darkseagreen; - extern const Color_RGBA darkslateblue; - extern const Color_RGBA darkslategray; - extern const Color_RGBA darkslategrey; - extern const Color_RGBA darkturquoise; - extern const Color_RGBA darkviolet; - extern const Color_RGBA deeppink; - extern const Color_RGBA deepskyblue; - extern const Color_RGBA dimgray; - extern const Color_RGBA dimgrey; - extern const Color_RGBA dodgerblue; - extern const Color_RGBA firebrick; - extern const Color_RGBA floralwhite; - extern const Color_RGBA forestgreen; - extern const Color_RGBA magenta; - extern const Color_RGBA fuchsia; - extern const Color_RGBA gainsboro; - extern const Color_RGBA ghostwhite; - extern const Color_RGBA gold; - extern const Color_RGBA goldenrod; - extern const Color_RGBA gray; - extern const Color_RGBA grey; - extern const Color_RGBA green; - extern const Color_RGBA greenyellow; - extern const Color_RGBA honeydew; - extern const Color_RGBA hotpink; - extern const Color_RGBA indianred; - extern const Color_RGBA indigo; - extern const Color_RGBA ivory; - extern const Color_RGBA khaki; - extern const Color_RGBA lavender; - extern const Color_RGBA lavenderblush; - extern const Color_RGBA lawngreen; - extern const Color_RGBA lemonchiffon; - extern const Color_RGBA lightblue; - extern const Color_RGBA lightcoral; - extern const Color_RGBA lightcyan; - extern const Color_RGBA lightgoldenrodyellow; - extern const Color_RGBA lightgray; - extern const Color_RGBA lightgrey; - extern const Color_RGBA lightgreen; - extern const Color_RGBA lightpink; - extern const Color_RGBA lightsalmon; - extern const Color_RGBA lightseagreen; - extern const Color_RGBA lightskyblue; - extern const Color_RGBA lightslategray; - extern const Color_RGBA lightslategrey; - extern const Color_RGBA lightsteelblue; - extern const Color_RGBA lightyellow; - extern const Color_RGBA lime; - extern const Color_RGBA limegreen; - extern const Color_RGBA linen; - extern const Color_RGBA maroon; - extern const Color_RGBA mediumaquamarine; - extern const Color_RGBA mediumblue; - extern const Color_RGBA mediumorchid; - extern const Color_RGBA mediumpurple; - extern const Color_RGBA mediumseagreen; - extern const Color_RGBA mediumslateblue; - extern const Color_RGBA mediumspringgreen; - extern const Color_RGBA mediumturquoise; - extern const Color_RGBA mediumvioletred; - extern const Color_RGBA midnightblue; - extern const Color_RGBA mintcream; - extern const Color_RGBA mistyrose; - extern const Color_RGBA moccasin; - extern const Color_RGBA navajowhite; - extern const Color_RGBA navy; - extern const Color_RGBA oldlace; - extern const Color_RGBA olive; - extern const Color_RGBA olivedrab; - extern const Color_RGBA orange; - extern const Color_RGBA orangered; - extern const Color_RGBA orchid; - extern const Color_RGBA palegoldenrod; - extern const Color_RGBA palegreen; - extern const Color_RGBA paleturquoise; - extern const Color_RGBA palevioletred; - extern const Color_RGBA papayawhip; - extern const Color_RGBA peachpuff; - extern const Color_RGBA peru; - extern const Color_RGBA pink; - extern const Color_RGBA plum; - extern const Color_RGBA powderblue; - extern const Color_RGBA purple; - extern const Color_RGBA red; - extern const Color_RGBA rosybrown; - extern const Color_RGBA royalblue; - extern const Color_RGBA saddlebrown; - extern const Color_RGBA salmon; - extern const Color_RGBA sandybrown; - extern const Color_RGBA seagreen; - extern const Color_RGBA seashell; - extern const Color_RGBA sienna; - extern const Color_RGBA silver; - extern const Color_RGBA skyblue; - extern const Color_RGBA slateblue; - extern const Color_RGBA slategray; - extern const Color_RGBA slategrey; - extern const Color_RGBA snow; - extern const Color_RGBA springgreen; - extern const Color_RGBA steelblue; - extern const Color_RGBA tan; - extern const Color_RGBA teal; - extern const Color_RGBA thistle; - extern const Color_RGBA tomato; - extern const Color_RGBA turquoise; - extern const Color_RGBA violet; - extern const Color_RGBA wheat; - extern const Color_RGBA white; - extern const Color_RGBA whitesmoke; - extern const Color_RGBA yellow; - extern const Color_RGBA yellowgreen; - extern const Color_RGBA rebeccapurple; - extern const Color_RGBA transparent; + extern const ColorRgba aliceblue; + extern const ColorRgba antiquewhite; + extern const ColorRgba cyan; + extern const ColorRgba aqua; + extern const ColorRgba aquamarine; + extern const ColorRgba azure; + extern const ColorRgba beige; + extern const ColorRgba bisque; + extern const ColorRgba black; + extern const ColorRgba blanchedalmond; + extern const ColorRgba blue; + extern const ColorRgba blueviolet; + extern const ColorRgba brown; + extern const ColorRgba burlywood; + extern const ColorRgba cadetblue; + extern const ColorRgba chartreuse; + extern const ColorRgba chocolate; + extern const ColorRgba coral; + extern const ColorRgba cornflowerblue; + extern const ColorRgba cornsilk; + extern const ColorRgba crimson; + extern const ColorRgba darkblue; + extern const ColorRgba darkcyan; + extern const ColorRgba darkgoldenrod; + extern const ColorRgba darkgray; + extern const ColorRgba darkgrey; + extern const ColorRgba darkgreen; + extern const ColorRgba darkkhaki; + extern const ColorRgba darkmagenta; + extern const ColorRgba darkolivegreen; + extern const ColorRgba darkorange; + extern const ColorRgba darkorchid; + extern const ColorRgba darkred; + extern const ColorRgba darksalmon; + extern const ColorRgba darkseagreen; + extern const ColorRgba darkslateblue; + extern const ColorRgba darkslategray; + extern const ColorRgba darkslategrey; + extern const ColorRgba darkturquoise; + extern const ColorRgba darkviolet; + extern const ColorRgba deeppink; + extern const ColorRgba deepskyblue; + extern const ColorRgba dimgray; + extern const ColorRgba dimgrey; + extern const ColorRgba dodgerblue; + extern const ColorRgba firebrick; + extern const ColorRgba floralwhite; + extern const ColorRgba forestgreen; + extern const ColorRgba magenta; + extern const ColorRgba fuchsia; + extern const ColorRgba gainsboro; + extern const ColorRgba ghostwhite; + extern const ColorRgba gold; + extern const ColorRgba goldenrod; + extern const ColorRgba gray; + extern const ColorRgba grey; + extern const ColorRgba green; + extern const ColorRgba greenyellow; + extern const ColorRgba honeydew; + extern const ColorRgba hotpink; + extern const ColorRgba indianred; + extern const ColorRgba indigo; + extern const ColorRgba ivory; + extern const ColorRgba khaki; + extern const ColorRgba lavender; + extern const ColorRgba lavenderblush; + extern const ColorRgba lawngreen; + extern const ColorRgba lemonchiffon; + extern const ColorRgba lightblue; + extern const ColorRgba lightcoral; + extern const ColorRgba lightcyan; + extern const ColorRgba lightgoldenrodyellow; + extern const ColorRgba lightgray; + extern const ColorRgba lightgrey; + extern const ColorRgba lightgreen; + extern const ColorRgba lightpink; + extern const ColorRgba lightsalmon; + extern const ColorRgba lightseagreen; + extern const ColorRgba lightskyblue; + extern const ColorRgba lightslategray; + extern const ColorRgba lightslategrey; + extern const ColorRgba lightsteelblue; + extern const ColorRgba lightyellow; + extern const ColorRgba lime; + extern const ColorRgba limegreen; + extern const ColorRgba linen; + extern const ColorRgba maroon; + extern const ColorRgba mediumaquamarine; + extern const ColorRgba mediumblue; + extern const ColorRgba mediumorchid; + extern const ColorRgba mediumpurple; + extern const ColorRgba mediumseagreen; + extern const ColorRgba mediumslateblue; + extern const ColorRgba mediumspringgreen; + extern const ColorRgba mediumturquoise; + extern const ColorRgba mediumvioletred; + extern const ColorRgba midnightblue; + extern const ColorRgba mintcream; + extern const ColorRgba mistyrose; + extern const ColorRgba moccasin; + extern const ColorRgba navajowhite; + extern const ColorRgba navy; + extern const ColorRgba oldlace; + extern const ColorRgba olive; + extern const ColorRgba olivedrab; + extern const ColorRgba orange; + extern const ColorRgba orangered; + extern const ColorRgba orchid; + extern const ColorRgba palegoldenrod; + extern const ColorRgba palegreen; + extern const ColorRgba paleturquoise; + extern const ColorRgba palevioletred; + extern const ColorRgba papayawhip; + extern const ColorRgba peachpuff; + extern const ColorRgba peru; + extern const ColorRgba pink; + extern const ColorRgba plum; + extern const ColorRgba powderblue; + extern const ColorRgba purple; + extern const ColorRgba red; + extern const ColorRgba rosybrown; + extern const ColorRgba royalblue; + extern const ColorRgba saddlebrown; + extern const ColorRgba salmon; + extern const ColorRgba sandybrown; + extern const ColorRgba seagreen; + extern const ColorRgba seashell; + extern const ColorRgba sienna; + extern const ColorRgba silver; + extern const ColorRgba skyblue; + extern const ColorRgba slateblue; + extern const ColorRgba slategray; + extern const ColorRgba slategrey; + extern const ColorRgba snow; + extern const ColorRgba springgreen; + extern const ColorRgba steelblue; + extern const ColorRgba tan; + extern const ColorRgba teal; + extern const ColorRgba thistle; + extern const ColorRgba tomato; + extern const ColorRgba turquoise; + extern const ColorRgba violet; + extern const ColorRgba wheat; + extern const ColorRgba white; + extern const ColorRgba whitesmoke; + extern const ColorRgba yellow; + extern const ColorRgba yellowgreen; + extern const ColorRgba rebeccapurple; + extern const ColorRgba transparent; } - const Color_RGBA* name_to_color(const char*); - const Color_RGBA* name_to_color(const sass::string&); + const ColorRgba* name_to_color(const char*); + const ColorRgba* name_to_color(const sass::string&); const char* color_to_name(const int); - const char* color_to_name(const Color_RGBA&); const char* color_to_name(const double); } diff --git a/src/compiler.cpp b/src/compiler.cpp new file mode 100644 index 0000000000..54d9eeeea6 --- /dev/null +++ b/src/compiler.cpp @@ -0,0 +1,878 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "compiler.hpp" + +#include "json.hpp" +#include "eval.hpp" +#include "output.hpp" +#include "stylesheet.hpp" +#include "capi_lists.hpp" +#include "parser_scss.hpp" +#include "parser_sass.hpp" +#include "parser_css.hpp" +#include "exceptions.hpp" +#include "remove_placeholders.hpp" + +// Functions to register +#include "fn_maps.hpp" +#include "fn_meta.hpp" +#include "fn_lists.hpp" +#include "fn_texts.hpp" +#include "fn_colors.hpp" +#include "fn_numbers.hpp" +#include "fn_selectors.hpp" + +#include +#include +#ifdef _MSC_VER +#include +#endif + +#include "debugger.hpp" + +struct SassImport; + +namespace Sass { + + + + struct SassValue* get_global(struct SassCompiler* comp, struct SassValue* name) + { + Value& value(Value::unwrap(name)); + Compiler& compiler(Compiler::unwrap(comp)); + ValueObj rv = compiler.getVariable( + value.assertString(compiler, "name")->getText(), true); + if (rv) rv->refcount += 1; + return rv ? Value::wrap(rv) : sass_make_null(); + } + + struct SassValue* get_lexical(struct SassCompiler* comp, struct SassValue* name) + { + Value& value(Value::unwrap(name)); + Compiler& compiler(Compiler::unwrap(comp)); + ValueObj rv = compiler.getVariable( + value.assertString(compiler, "name")->getText()); + if (rv) rv->refcount += 1; + return rv ? Value::wrap(rv) : sass_make_null(); + } + + struct SassValue* get_local(struct SassCompiler* comp, struct SassValue* name) + { + Value& value(Value::unwrap(name)); + Compiler& compiler(Compiler::unwrap(comp)); + ValueObj rv = compiler.getVariable( + value.assertString(compiler, "name")->getText()); + if (rv) rv->refcount += 1; + return rv ? Value::wrap(rv) : sass_make_null(); + } + + struct SassValue* set_global(struct SassCompiler* comp, struct SassValue* name, struct SassValue* value) + { + Value& key(Value::unwrap(name)); + Value& val(Value::unwrap(value)); + Compiler& compiler(Compiler::unwrap(comp)); + compiler.setVariable(key.assertString(compiler, "name")->getText(), &val, true); + return sass_make_null(); + } + + struct SassValue* set_lexical(struct SassCompiler* comp, struct SassValue* name, struct SassValue* value) + { + Value& key(Value::unwrap(name)); + Value& val(Value::unwrap(value)); + Compiler& compiler(Compiler::unwrap(comp)); + compiler.setVariable(key.assertString(compiler, "name")->getText(), &val); + return sass_make_null(); + } + + struct SassValue* set_local(struct SassCompiler* comp, struct SassValue* name, struct SassValue* value) + { + Value& key(Value::unwrap(name)); + Value& val(Value::unwrap(value)); + Compiler& compiler(Compiler::unwrap(comp)); + compiler.setVariable(key.assertString(compiler, "name")->getText(), &val); + return sass_make_null(); + } + + // so far only pow has two arguments +#define IMPLEMENT_2_ARG_FN(fn) \ +struct SassValue* fn_##fn(struct SassValue* s_args, Sass_Function_Entry cb, struct SassCompiler* comp) \ +{ \ + if (!sass_value_is_list(s_args)) { \ + return sass_make_error("Invalid arguments for " #fn); \ + } \ + if (sass_list_get_size(s_args) != 2) { \ + return sass_make_error("Exactly two arguments expected for " #fn); \ + } \ + struct SassValue* name = sass_list_get_value(s_args, 0); \ + struct SassValue* value = sass_list_get_value(s_args, 1); \ + return fn(comp, name, value); \ +} \ + + // so far only pow has two arguments +#define IMPLEMENT_1_ARG_FN(fn) \ +struct SassValue* fn_##fn(struct SassValue* s_args, Sass_Function_Entry cb, struct SassCompiler* comp) \ +{ \ + if (!sass_value_is_list(s_args)) { \ + return sass_make_error("Invalid arguments for " #fn); \ + } \ + if (sass_list_get_size(s_args) != 1) { \ + return sass_make_error("Exactly one arguments expected for " #fn); \ + } \ + struct SassValue* name = sass_list_get_value(s_args, 0); \ + return fn(comp, name); \ +} \ + +// one argument functions + // IMPLEMENT_2_ARG_FN(pow); + + IMPLEMENT_1_ARG_FN(get_global); + IMPLEMENT_1_ARG_FN(get_lexical); + IMPLEMENT_1_ARG_FN(get_local); + IMPLEMENT_2_ARG_FN(set_global); + IMPLEMENT_2_ARG_FN(set_lexical); + IMPLEMENT_2_ARG_FN(set_local); + + // create a custom header to define to variables + struct SassImportList* custom_header(const char* cur_path, struct SassImporter* cb, struct SassCompiler* comp) + { + // create a list to hold our import entries + struct SassImportList* incs = sass_make_import_list(); + + // sass_env_set_global(comp, "test", sass_make_number(42, "")); + // sass_env_set_global(comp, "$test", sass_make_number(42, "")); + // create our only import entry (must make copy) + sass_import_list_push(incs, sass_make_import( + "[math]", "[math]", sass_copy_c_string( + "$E: 2.718281828459045235360287471352;\n" + "$PI: 3.141592653589793238462643383275;\n" + "$TAU: 6.283185307179586476925286766559;\n" + ), 0, SASS_IMPORT_AUTO)); + // return imports + return incs; + } + + Compiler::Compiler() : + Context(), + state(SASS_COMPILER_CREATED), + output_path("stream://stdout"), + entry_point(), + footer(nullptr), + srcmap(nullptr), + error() + { + // allocate a custom function caller + // Sass_Importer_Entry c_header = + // sass_make_importer(custom_header, 5000, (void*)0); + // addCustomHeader(c_header); + } + + // Get path of compilation entry point + // Returns the resolved/absolute path + sass::string Compiler::getInputPath() const + { + // Simply return absolute path of entry point + if (entry_point && entry_point->getAbsPath()) { + return entry_point->getAbsPath(); + } + // Fall back to stdin + return "stream://stdin"; + } + // EO getInputPath + + // Get the output path for this compilation + // Can be explicit or deducted from input path + sass::string Compiler::getOutputPath() const + { + // Specific output path was provided + if (!output_path.empty()) { + return output_path; + } + // Otherwise deduct it from input path + sass::string path(getInputPath()); + // Check if input is coming from stdin + if (path == "stream://stdin") { + return "stream://stdout"; + } + // Otherwise remove the file extension + size_t dotpos = path.find_last_of('.'); + if (dotpos != sass::string::npos) { + path.erase(dotpos); + } + // Use css extension + path += ".css"; + return path; + } + // EO getOutputPath + + void Compiler::parse() + { + // Do initial loading + entry_point->loadIfNeeded(); + // Now parse the entry point stylesheet + sheet = parseRoot(entry_point); + // Update the compiler state + state = SASS_COMPILER_PARSED; + } + + // parse root block from includes (Move to compiler) + CssRootObj Compiler::compileRoot(bool plainCss) + { + RootObj root = sheet->root2; + if (root == nullptr) return {}; + + #ifdef DEBUG_SHARED_PTR + // Enable reference tracking + SharedObj::taint = true; + #endif + + // abort if there is no data + if (included_sources.size() == 0) return {}; + // abort on invalid root + if (root.isNull()) return {}; + + Eval eval(*this, *this, plainCss); + EnvScope scoped(varRoot, varRoot.idxs); + for (size_t i = 0; i < fnList.size(); i++) { + varRoot.functions[i] = fnList[i]; + } + + // debug_ast(root); + CssRootObj compiled = eval.acceptRoot(root); // 50% + // debug_ast(compiled); + + Extension unsatisfied; + // check that all extends were used + if (eval.checkForUnsatisfiedExtends(unsatisfied)) { + throw Exception::UnsatisfiedExtend(*this, unsatisfied); + } + + // clean up by removing empty placeholders + // ToDo: maybe we can do this somewhere else? + Remove_Placeholders remove_placeholders; + remove_placeholders.visitCssRoot(compiled); // 3% + + #ifdef DEBUG_SHARED_PTR + // Enable reference tracking + SharedObj::taint = false; + #endif + + // return processed tree + return compiled; + } + + void Compiler::compile() + { + // Only act right after parsing + if (state == SASS_COMPILER_PARSED) { + // Compile the parsed ast-tree + compiled = compileRoot(false); + // Update the compiler state + state = SASS_COMPILER_COMPILED; + } + } + + OutputBuffer Compiler::renderCss() + { + // Create the emitter object + Output emitter(*this, srcmap_options.mode != SASS_SRCMAP_NONE); + emitter.reserve(1024 * 1024); // 1MB + emitter.in_declaration = false; + // Start the render process + if (compiled != nullptr) { + emitter.visitCssRoot(compiled); + } + // Finish emitter stream + emitter.finalize(); + // Update the compiler state + state = SASS_COMPILER_RENDERED; + // Move buffer from stream + return emitter.getBuffer(); + } + // EO renderCss + + // Case 1) output to stdout, source map must be fully inline + // Case 2) output to path, source map output is deducted from it + char* Compiler::renderSrcMapLink(struct SassSrcMapOptions options, const SourceMap& source_map) + { + // Source map json must already be there + if (srcmap == nullptr) return nullptr; + // Check if we output to stdout (any link would be useless) + if (output_path.empty() || output_path == "stream://stdout") { + // Instead always embed the source-map on stdout + return renderEmbeddedSrcMap(options, source_map); + } + // Create resulting footer and return a copy + return sass_copy_string("\n/*# sourceMappingURL=" + + File::abs2rel(options.path, options.origin) + " */"); + + } + // EO renderSrcMapLink + + // Memory returned by this function must be freed by caller via `sass_free_c_string` + char* Compiler::renderEmbeddedSrcMap(struct SassSrcMapOptions options, const SourceMap& source_map) + { + // Source map json must already be there + if (srcmap == nullptr) return nullptr; + // Encode json to base64 + sass::istream is(srcmap); + sass::ostream buffer; + buffer << "\n/*# sourceMappingURL="; + buffer << "data:application/json;base64,"; + base64::encoder E; + E.encode(is, buffer); + buffer << " */"; + return sass_copy_string(buffer.str()); + } + // EO renderEmbeddedSrcMap + + // Memory returned by this function must be freed by caller via `sass_free_c_string` + char* Compiler::renderSrcMapJson(struct SassSrcMapOptions options, const SourceMap& source_map) + { + // Create the emitter object + // Sass::OutputBuffer buffer; + + /**********************************************/ + // Create main object to render json + /**********************************************/ + JsonNode* json_srcmap = json_mkobject(); + + /**********************************************/ + // Create the source-map version information + /**********************************************/ + json_append_member(json_srcmap, "version", json_mknumber(3)); + + /**********************************************/ + // Create file reference to whom our mappings apply + /**********************************************/ + sass::string origin(options.origin); + origin = File::abs2rel(origin, CWD); + JsonNode* json_file_name = json_mkstring(origin.c_str()); + json_append_member(json_srcmap, "file", json_file_name); + + /**********************************************/ + // pass-through source_map_root option + /**********************************************/ + if (!options.root.empty()) { + json_append_member(json_srcmap, "sourceRoot", + json_mkstring(options.root.c_str())); + } + + /**********************************************/ + // Create the included sources array + /**********************************************/ + JsonNode* json_sources = json_mkarray(); + for (size_t i = 0; i < included_sources.size(); ++i) { + const SourceData* source(included_sources[i]); + sass::string path(source->getAbsPath()); + path = File::rel2abs(path, ".", CWD); + // Optionally convert to file urls + if (options.file_urls) { + if (path[0] == '/') { + // ends up with three slashes + path = "file://" + path; + } + else { + // needs an additional slash + path = "file:///" + path; + } + // Append item to json array + json_append_element(json_sources, + json_mkstring(path.c_str())); + } + else { + path = File::abs2rel(path, ".", CWD); + // Append item to json array + json_append_element(json_sources, + json_mkstring(path.c_str())); + } + } + json_append_member(json_srcmap, "sources", json_sources); + + // add a relative link to the source map output file + // srcmap_links88.emplace_back(abs2rel(abs_path, file88, CWD)); + + /**********************************************/ + // Check if we have any includes to render + /**********************************************/ + if (options.embed_contents) { + JsonNode* json_contents = json_mkarray(); + for (size_t i = 0; i < included_sources.size(); ++i) { + const SourceData* source = included_sources[i]; + JsonNode* json_content = json_mkstring(source->content()); + json_append_element(json_contents, json_content); + } + json_append_member(json_srcmap, "sourcesContent", json_contents); + } + + /**********************************************/ + // So far we have no implementation for names + /**********************************************/ + json_append_member(json_srcmap, "names", json_mkarray()); + + /**********************************************/ + // Create source remapping lookup table + // For source-maps we need to output sources in + // consecutive manner, but we might have used various + // different stylesheets from the prolonged context + /**********************************************/ + std::unordered_map idxremap; + for (auto& source : included_sources) { + idxremap.insert(std::make_pair( + source->getSrcIdx(), + idxremap.size())); + } + + /**********************************************/ + // Finally render the actual source mappings + // Remap context srcidx to consecutive ordering + /**********************************************/ + sass::string mappings(source_map.render(idxremap)); + JsonNode* json_mappings = json_mkstring(mappings.c_str()); + json_append_member(json_srcmap, "mappings", json_mappings); + + /**********************************************/ + // Render the json and return result + // Memory must be freed by consumer! + /**********************************************/ + char* data = json_stringify(json_srcmap, "\t"); + json_delete(json_srcmap); + return data; + + } + // EO renderSrcMapJson + + ///////////////////////////////////////////////////////////////////////// + // @param imp_path The relative or custom path for be imported + // @param pstate SourceSpan where import occurred (parent context) + // @param rule The backing ImportRule that is added to the document + // @param importers Array of custom importers/headers to go through + // @param singleton Whether to use all importers or only first successful + ///////////////////////////////////////////////////////////////////////// + bool Compiler::callCustomLoader(const sass::string& imp_path, SourceSpan& pstate, + ImportRule* rule, const sass::vector& importers, bool singleton) + { + // unique counter + size_t count = 0; + // need one correct import + bool has_import = false; + + const char* ctx_path = pstate.getAbsPath(); + + + // Process custom importers and headers. + // They must be presorted by priorities. + for (struct SassImporter* importer : importers) { + + // Get the external importer function + SassImporterLambda fn = sass_importer_get_callback(importer); + + // std::cerr << "Calling custom loader " << fn << "\n"; + + // Call the external function, then check what it returned + struct SassImportList* imports = fn(imp_path.c_str(), importer, this->wrap()); + // External provider want to handle this + if (imports != nullptr) { + + // std::cerr << "HAS imports\n"; + + // Get the list of possible imports + // A list with zero items does nothing + while (sass_import_list_size(imports) > 0) { + // Increment counter + ++count; + // Consume the first item from the list + struct SassImport* entry = sass_import_list_shift(imports); + Import& import = Import::unwrap(entry); + // Create a unique path to use as key + sass::string uniq_path = imp_path; + // Append counter to the path + // Note: only for headers! + if (!singleton && count) { + sass::sstream path_strm; + path_strm << uniq_path << ":" << count; + uniq_path = path_strm.str(); + } + // create the importer struct + ImportRequest importer(uniq_path, ctx_path); + // query data from the current include + SourceDataObj source = import.source; + // const char* content = sass_import_get_source(import); + // const char* srcmap = sass_import_get_srcmap(import); + // auto format = sass_import_get_type(import2); + + //if (sass_import_get_error_message(import)) { + // BackTraces& traces = *this; + // traces.push_back(BackTrace(pstate)); + // Exception::CustomImportError err(traces, "custom error"); + // sass_delete_import_list(imports); + // sass_delete_import(import2); + // throw err; + // + // /* + // + // + // + //// Handle error message passed back from custom importer + //// It may (or may not) override the line and column info + // size_t line = sass_import_get_error_line(import); + // size_t column = sass_import_get_error_column(import); + // // sass_delete_import(import); // will error afterwards + // if (line == sass::string::npos) error(err_message, pstate, *logger123); + // else if (column == sass::string::npos) error(err_message, pstate, *logger123); + // else error(err_message, { source, Offset::init(line, column) }, *logger123); + // */ + //} + // Content for import was set. + // No need to load it ourself. + //else + if (source) { + + const char* rel_path = import.getImpPath(); + const char* abs_path = import.getAbsPath(); + + if (import.isLoaded()) { + // Resolved abs_path should be set by custom importer + // Use the created uniq_path as fall-back (enforce?) + sass::string path_key(abs_path ? abs_path : uniq_path); + // Import is ready to be served + if (import.syntax == SASS_IMPORT_AUTO) + import.syntax = SASS_IMPORT_SCSS; + StyleSheet* sheet = registerImport(&import); + // Add a dynamic import to the import rule + rule->append(SASS_MEMORY_NEW(IncludeImport, + pstate, sheet)); + } + // Only a path was returned + // Try to load it like normal + else if (abs_path || rel_path) { + // Create a copy for possible error reporting + sass::string path(abs_path ? abs_path : rel_path); + // Pass it on as if it was a regular import + ImportRequest request(path, ctx_path); + + // Search for valid imports (e.g. partials) on the file-system + // Returns multiple valid results for ambiguous import path + const sass::vector resolved(findIncludes(request)); + + + // Error if no file to import was found + if (resolved.empty()) { + BackTraces& traces = *this; + traces.push_back(BackTrace(pstate)); + Exception::CustomImportNotFound err(traces, path); + sass_delete_import_list(imports); + sass_delete_import(entry); + throw err; + } + // Error if multiple files to import were found + else if (resolved.size() > 1) { + BackTraces& traces = *this; + traces.push_back(BackTrace(pstate)); + Exception::CustomImportAmbigous err(traces, path); + sass_delete_import_list(imports); + sass_delete_import(entry); + throw err; + } + + // We made sure exactly one entry was found, load its content + if (ImportObj loaded = loadImport(resolved[0])) { + StyleSheet* sheet = registerImport(loaded); + rule->append(SASS_MEMORY_NEW(IncludeImport, pstate, sheet)); + } + else { + BackTraces& traces = *this; + traces.push_back(BackTrace(pstate)); + Exception::CustomImportLoadError err(traces, path); + sass_delete_import_list(imports); + sass_delete_import(entry); + throw err; + } + + } + } + sass_delete_import(entry); + } + + // Deallocate the returned memory + sass_delete_import_list(imports); + // Set success flag + has_import = true; + // Break out of loop + if (singleton) break; + } + } + // Return result + return has_import; + } + // EO callCustomLoader + + + + + void Compiler::applyCustomHeaders(StatementVector& statements, SourceSpan pstate) + { + // create a custom import to resolve headers + ImportRuleObj rule = SASS_MEMORY_NEW(ImportRule, pstate); + // dispatch headers which will add custom functions + // custom headers are added to the import instance + if (callCustomHeaders("sass://header", pstate, rule)) { + statements.push_back(rule); + } + } + + + ///////////////////////////////////////////////////////////////////////// + // Interface for built in functions + ///////////////////////////////////////////////////////////////////////// + + // Register built-in function with only one parameter list. + void Compiler::registerBuiltInFunction(const sass::string& name, + const sass::string& signature, SassFnSig cb) + { + EnvRoot root(varStack); + SourceDataObj source = SASS_MEMORY_NEW(SourceString, + "sass://signature", "(" + signature + ")"); + auto args = ArgumentDeclaration::parse(*this, source); + auto callable = SASS_MEMORY_NEW(BuiltInCallable, name, args, cb); + fnLookup.insert(std::make_pair(callable->envkey(), callable)); + varRoot.createFunction(callable->envkey()); + fnList.push_back(callable); + } + // EO registerBuiltInFunction + + // Register built-in functions that can take different + // functional arguments (best suited will be chosen). + void Compiler::registerBuiltInOverloadFns(const sass::string& name, + const std::vector>& overloads) + { + SassFnPairs pairs; + for (auto overload : overloads) { + EnvRoot root(varStack); + SourceDataObj source = SASS_MEMORY_NEW(SourceString, + "sass://signature", "(" + overload.first + ")"); + auto args = ArgumentDeclaration::parse(*this, source); + pairs.emplace_back(std::make_pair(args, overload.second)); + } + auto callable = SASS_MEMORY_NEW(BuiltInCallables, name, pairs); + fnLookup.insert(std::make_pair(name, callable)); + varRoot.createFunction(name); + fnList.push_back(callable); + } + // EO registerBuiltInOverloadFns + + ///////////////////////////////////////////////////////////////////////// + // Interface for external custom functions + ///////////////////////////////////////////////////////////////////////// + + // Create a new external callable from the sass function. Parses + // function signature into function name and argument declaration. + ExternalCallable* Compiler::makeExternalCallable(struct SassFunction* function) + { + // Create temporary source object for signature + SourceStringObj source = SASS_MEMORY_NEW(SourceString, + "sass://signature", function->signature); + // Create a new scss parser instance + ScssParser parser(*this, source); + ExternalCallable* callable = + parser.parseExternalCallable(); + callable->function(function); + return callable; + } + // EO makeExternalCallable + + // Register an external custom sass function on the global scope. + // Main entry point for custom functions passed through the C-API. + void Compiler::registerCustomFunction(struct SassFunction* function) + { + EnvRoot root(varStack); + // Create a new external callable from the sass function + ExternalCallable* callable = makeExternalCallable(function); + // Currently external functions are treated globally + if (fnLookup.count(callable->envkey()) == 0) { + fnLookup.insert(std::make_pair(callable->envkey(), callable)); + varRoot.createFunction(callable->envkey()); + fnList.push_back(callable); + } + } + // EO registerCustomFunction + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Invoke parser according to import format + RootObj Compiler::parseSource(ImportObj import) + { + if (import->syntax == SASS_IMPORT_CSS) + { + CssParser parser(*this, import->source); + return parser.parseRoot(); + } + else if (import->syntax == SASS_IMPORT_SASS) + { + SassParser parser(*this, import->source); + return parser.parseRoot(); + } + else { + ScssParser parser(*this, import->source); + return parser.parseRoot(); + } + } + // EO parseSource + + // Parse the import (updates syntax flag if AUTO was set) + // Results will be stored at `sheets[source->getAbsPath()]` + StyleSheet* Compiler::registerImport(ImportObj import) + { + + SassImportFormat& format(import->syntax); + const SourceDataObj& source(import->source); + const sass::string& abs_path(source->getAbsPath()); + + // ToDo: We don't take format into account + auto cached = sheets.find(abs_path); + if (cached != sheets.end()) { + return cached->second; + } + + // Assign unique index to the source + source->setSrcIdx(included_sources.size()); + // Add source to global include array + included_sources.emplace_back(source); + // Put import onto the stack array + import_stack.emplace_back(import); + + // check existing import stack for possible recursion + for (size_t i = 0; i < import_stack.size() - 2; ++i) { + const SourceDataObj parent = import_stack[i]->source; + if (std::strcmp(parent->getAbsPath(), source->getAbsPath()) == 0) { + // make path relative to the current directory + sass::string stack("An @import loop has been found:"); + for (size_t n = 1; n < i + 2; ++n) { + stack += "\n " + sass::string(File::abs2rel(import_stack[n]->source->getAbsPath(), CWD, CWD)) + + " imports " + sass::string(File::abs2rel(import_stack[n + 1]->source->getAbsPath(), CWD, CWD)); + } + // implement error throw directly until we + // decided how to handle full stack traces + throw Exception::RuntimeException(*this, stack); + } + } + + // Auto detect input file format + if (format == SASS_IMPORT_AUTO) { + using namespace StringUtils; + if (endsWithIgnoreCase(abs_path, ".css", 4)) { + format = SASS_IMPORT_CSS; + } + else if (endsWithIgnoreCase(abs_path, ".sass", 5)) { + format = SASS_IMPORT_SASS; + } + else if (endsWithIgnoreCase(abs_path, ".scss", 5)) { + format = SASS_IMPORT_SCSS; + } + else { + throw Exception::RuntimeException(*this, + "Can't find stylesheet to import."); + } + } + + // Invoke correct parser according to format + StyleSheet* stylesheet = SASS_MEMORY_NEW( + StyleSheet, import, parseSource(import)); + + // Pop from import stack + import_stack.pop_back(); + + // Put the parsed stylesheet into the map + sheets.insert({ abs_path, stylesheet }); + + // Return pointer + return stylesheet; + + } + // EO registerImport + + // Called once to register all built-in functions. + // This will invoke parsing for parameter lists. + void Compiler::loadBuiltInFunctions() + { + Functions::Meta::registerFunctions(*this); + Functions::Math::registerFunctions(*this); + Functions::Maps::registerFunctions(*this); + Functions::Lists::registerFunctions(*this); + Functions::Colors::registerFunctions(*this); + Functions::Texts::registerFunctions(*this); + Functions::Selectors::registerFunctions(*this); + } + // EO loadBuiltInFunctions + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void sigHandler(int s) + { + std::cerr << "Abnormal program termination detected!\n"; + std::cerr << " With Signal " << s << "\n"; + exit(EXIT_FAILURE); + } + + StyleSheet* Compiler::parseRoot(ImportObj import) + { + + // Attach signal handlers + signal(SIGABRT, sigHandler); + signal(SIGFPE, sigHandler); + signal(SIGILL, sigHandler); + signal(SIGINT, sigHandler); + signal(SIGSEGV, sigHandler); + signal(SIGTERM, sigHandler); + + // Insert ourself onto the sources cache + sources.insert({ import->getAbsPath(), import }); + + import_stack.emplace_back(import); + + loadBuiltInFunctions(); + + + // auto qwe = sass_make_function("pow($foo, $bar)", fn_pow, (void*)31); + + // registerCustomFunction(qwe); + + registerCustomFunction(sass_make_function("set-local($name, $value)", fn_set_local, (void*)31)); + registerCustomFunction(sass_make_function("set-global($name, $value)", fn_set_global, (void*)31)); + registerCustomFunction(sass_make_function("set-lexical($name, $value)", fn_set_lexical, (void*)31)); + + registerCustomFunction(sass_make_function("get-local($name)", fn_get_local, (void*)31)); + registerCustomFunction(sass_make_function("get-global($name)", fn_get_global, (void*)31)); + registerCustomFunction(sass_make_function("get-lexical($name)", fn_get_lexical, (void*)31)); + + // ScopedStack scoped(varStack, &varRoot); + + // register custom functions (defined via C-API) + for (auto& function : cFunctions) + registerCustomFunction(function); + + #ifdef DEBUG_SHARED_PTR + // Enable reference tracking + SharedObj::taint = true; + #endif + + // load and register import + StyleSheet* sheet = registerImport(import); + + #ifdef DEBUG_SHARED_PTR + // Disable reference tracking + SharedObj::taint = false; + #endif + + // Return root node + return sheet; + + } + // EO parseRoot + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/compiler.hpp b/src/compiler.hpp new file mode 100644 index 0000000000..887806073c --- /dev/null +++ b/src/compiler.hpp @@ -0,0 +1,184 @@ +#ifndef SASS_COMPILER_H +#define SASS_COMPILER_H + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "logger.hpp" +#include "context.hpp" +#include "capi_error.hpp" +#include "capi_context.hpp" + +struct SassImport; + +namespace Sass { + + // The split between Context and Compiler is technically not really needed. + // But it helps a little to organize the different aspects of the compilation. + // The Context mainly stores all the read-only values (e.g. settings). + class Compiler final : public Context, public Logger { + + public: + + // The current state the compiler is in. + enum SassCompilerState state; + + // Option struct for source map options + struct SassSrcMapOptions srcmap_options; + + // Where we want to store the output. + // Source-map path is deducted from it. + // Defaults to `stream://stdout`. + sass::string output_path; + + // main entry point for compilation + ImportObj entry_point; + + // Parsed ast-tree + StyleSheetObj sheet; + + // Evaluated ast-tree + CssRootObj compiled; + + // The rendered css content. + sass::string content; + + // Rendered warnings and debugs. They can be emitted at any stage. + // Therefore we make a copy into our string after each stage from + // the actual logger instance (created by context). This is needed + // in order to return a `c_str` on the API from the output-stream. + sass::string warnings; + + // The rendered output footer. This includes the + // rendered css comment footer for the source-map. + // Append after output for the full output document. + char* footer; + + // The rendered source map. This is what an implementor + // would normally write out to the `output.css.map` file + char* srcmap; + + // Runtime error + SassError error; + + // Constructor + Compiler(); + + // Parse ast tree + void parse(); + + // Compile parsed tree + void compile(); + + // Render compiled tree + OutputBuffer renderCss(); + + // Get path of compilation entry point + // Returns the resolved/absolute path + sass::string getInputPath() const; + + // Get the output path for this compilation + // Can be explicit or deducted from input path + sass::string getOutputPath() const; + + // ToDO, maybe belongs to compiler? + CssRootObj compileRoot(bool plainCss); + + char* renderSrcMapJson(struct SassSrcMapOptions options, + const SourceMap& source_map); + + char* renderSrcMapLink(struct SassSrcMapOptions options, + const SourceMap& source_map); + + char* renderEmbeddedSrcMap(struct SassSrcMapOptions options, + const SourceMap& source_map); + + ///////////////////////////////////////////////////////////////////////// + // Register built-in function with only one parameter list. + ///////////////////////////////////////////////////////////////////////// + void registerBuiltInFunction(const sass::string& name, + const sass::string& signature, SassFnSig cb); + + ///////////////////////////////////////////////////////////////////////// + // Register built-in functions that can take different + // functional arguments (best suited will be chosen). + ///////////////////////////////////////////////////////////////////////// + void registerBuiltInOverloadFns(const sass::string& name, + const std::vector>& overloads); + + ///////////////////////////////////////////////////////////////////////// + // @param imp_path The relative or custom path for be imported + // @param pstate SourceSpan where import occurred (parent context) + // @param rule The backing ImportRule that is added to the document + ///////////////////////////////////////////////////////////////////////// + bool callCustomHeaders(const sass::string& imp_path, SourceSpan& pstate, ImportRule* rule) + { + return callCustomLoader(imp_path, pstate, rule, cHeaders, false); + }; + + ///////////////////////////////////////////////////////////////////////// + // @param imp_path The relative or custom path for be imported + // @param pstate SourceSpan where import occurred (parent context) + // @param rule The backing ImportRule that is added to the document + ///////////////////////////////////////////////////////////////////////// + bool callCustomImporters(const sass::string& imp_path, SourceSpan& pstate, ImportRule* rule) + { + return callCustomLoader(imp_path, pstate, rule, cImporters, true); + }; + + // Parse the import (updates syntax flag if AUTO was set) + // Results will be stored at `sheets[source->getAbsPath()]` + StyleSheet* registerImport(ImportObj import); + + // Called by parserStylesheet on the very first parse call + void applyCustomHeaders(StatementVector& root, SourceSpan pstate); + + protected: + + ///////////////////////////////////////////////////////////////////////// + // Called once to register all built-in functions. + // This will invoke parsing for parameter lists. + ///////////////////////////////////////////////////////////////////////// + void loadBuiltInFunctions(); + + StyleSheet* parseRoot(ImportObj import); + + private: + + ///////////////////////////////////////////////////////////////////////// + // @param imp_path The relative or custom path for be imported + // @param pstate SourceSpan where import occurred (parent context) + // @param rule The backing ImportRule that is added to the document + // @param importers Array of custom importers/headers to go through + // @param singleton Whether to use all importers or only first successful + ///////////////////////////////////////////////////////////////////////// + bool callCustomLoader(const sass::string& imp_path, SourceSpan& pstate, ImportRule* rule, + const sass::vector& importers, bool singletone = true); + + ///////////////////////////////////////////////////////////////////////// + // Create a new external callable from the sass function. Parses + // function signature into function name and argument declaration. + // The function you pass in will be taken over and freed by us! + ///////////////////////////////////////////////////////////////////////// + ExternalCallable* makeExternalCallable(struct SassFunction* function); + + ///////////////////////////////////////////////////////////////////////// + // Register an external custom sass function on the global scope. + // Main entry point for custom functions passed through the C-API. + // The function you pass in will be taken over and freed by us! + ///////////////////////////////////////////////////////////////////////// + void registerCustomFunction(struct SassFunction* function); + + // Invoke parser according to import format + RootObj parseSource(ImportObj source); + + public: + + CAPI_WRAPPER(Compiler, SassCompiler); + + }; + +} + +#endif diff --git a/src/constants.cpp b/src/constants.cpp index 8d9b64a4a8..9e4e26369e 100644 --- a/src/constants.cpp +++ b/src/constants.cpp @@ -1,199 +1,79 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "constants.hpp" +#include "terminal.hpp" + namespace Sass { - namespace Constants { - extern const unsigned long MaxCallStack = 1024; + namespace Constants { // https://github.com/sass/libsass/issues/592 - // https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity - // https://github.com/sass/sass/issues/1495#issuecomment-61189114 - extern const unsigned long Specificity_Star = 0; - extern const unsigned long Specificity_Universal = 0; - extern const unsigned long Specificity_Element = 1; - extern const unsigned long Specificity_Base = 1000; - extern const unsigned long Specificity_Class = 1000; - extern const unsigned long Specificity_Attr = 1000; - extern const unsigned long Specificity_Pseudo = 1000; - extern const unsigned long Specificity_ID = 1000000; - - extern const int UnificationOrder_Element = 1; - extern const int UnificationOrder_Id = 2; - extern const int UnificationOrder_Class = 2; - extern const int UnificationOrder_Attribute = 3; - extern const int UnificationOrder_PseudoClass = 4; - extern const int UnificationOrder_Wrapped = 5; - extern const int UnificationOrder_PseudoElement = 6; - extern const int UnificationOrder_Placeholder = 7; - - // sass keywords - extern const char at_root_kwd[] = "@at-root"; - extern const char import_kwd[] = "@import"; - extern const char mixin_kwd[] = "@mixin"; - extern const char function_kwd[] = "@function"; - extern const char return_kwd[] = "@return"; - extern const char include_kwd[] = "@include"; - extern const char content_kwd[] = "@content"; - extern const char extend_kwd[] = "@extend"; - extern const char if_kwd[] = "@if"; - extern const char else_kwd[] = "@else"; - extern const char if_after_else_kwd[] = "if"; - extern const char for_kwd[] = "@for"; - extern const char from_kwd[] = "from"; - extern const char to_kwd[] = "to"; - extern const char of_kwd[] = "of"; - extern const char through_kwd[] = "through"; - extern const char each_kwd[] = "@each"; - extern const char in_kwd[] = "in"; - extern const char while_kwd[] = "@while"; - extern const char warn_kwd[] = "@warn"; - extern const char error_kwd[] = "@error"; - extern const char debug_kwd[] = "@debug"; - extern const char default_kwd[] = "default"; - extern const char global_kwd[] = "global"; - extern const char null_kwd[] = "null"; - extern const char optional_kwd[] = "optional"; - extern const char with_kwd[] = "with"; - extern const char without_kwd[] = "without"; - extern const char all_kwd[] = "all"; - extern const char rule_kwd[] = "rule"; - - // css standard units - extern const char em_kwd[] = "em"; - extern const char ex_kwd[] = "ex"; - extern const char px_kwd[] = "px"; - extern const char cm_kwd[] = "cm"; - extern const char mm_kwd[] = "mm"; - extern const char pt_kwd[] = "pt"; - extern const char pc_kwd[] = "pc"; - extern const char deg_kwd[] = "deg"; - extern const char rad_kwd[] = "rad"; - extern const char grad_kwd[] = "grad"; - extern const char turn_kwd[] = "turn"; - extern const char ms_kwd[] = "ms"; - extern const char s_kwd[] = "s"; - extern const char Hz_kwd[] = "Hz"; - extern const char kHz_kwd[] = "kHz"; - - // vendor prefixes - extern const char vendor_opera_kwd[] = "-o-"; - extern const char vendor_webkit_kwd[] = "-webkit-"; - extern const char vendor_mozilla_kwd[] = "-moz-"; - extern const char vendor_ms_kwd[] = "-ms-"; - extern const char vendor_khtml_kwd[] = "-khtml-"; - - // css functions and keywords - extern const char charset_kwd[] = "@charset"; - extern const char media_kwd[] = "@media"; - extern const char supports_kwd[] = "@supports"; - extern const char keyframes_kwd[] = "keyframes"; - extern const char only_kwd[] = "only"; - extern const char rgb_fn_kwd[] = "rgb("; - extern const char url_fn_kwd[] = "url("; - extern const char url_kwd[] = "url"; - // extern const char url_prefix_fn_kwd[] = "url-prefix("; - extern const char important_kwd[] = "important"; - extern const char pseudo_not_fn_kwd[] = ":not("; - extern const char even_kwd[] = "even"; - extern const char odd_kwd[] = "odd"; - extern const char progid_kwd[] = "progid"; - extern const char expression_kwd[] = "expression"; - extern const char calc_fn_kwd[] = "calc"; - - extern const char almost_any_value_class[] = "\"'#!;{}"; - - // css selector keywords - extern const char sel_deep_kwd[] = "/deep/"; - - // css attribute-matching operators - extern const char tilde_equal[] = "~="; - extern const char pipe_equal[] = "|="; - extern const char caret_equal[] = "^="; - extern const char dollar_equal[] = "$="; - extern const char star_equal[] = "*="; - - // relational & logical operators and constants - extern const char and_kwd[] = "and"; - extern const char or_kwd[] = "or"; - extern const char not_kwd[] = "not"; - extern const char gt[] = ">"; - extern const char gte[] = ">="; - extern const char lt[] = "<"; - extern const char lte[] = "<="; - extern const char eq[] = "=="; - extern const char neq[] = "!="; - extern const char true_kwd[] = "true"; - extern const char false_kwd[] = "false"; - - // definition keywords - extern const char using_kwd[] = "using"; - - // miscellaneous punctuation and delimiters - extern const char percent_str[] = "%"; - extern const char empty_str[] = ""; - extern const char slash_slash[] = "//"; - extern const char slash_star[] = "/*"; - extern const char star_slash[] = "*/"; - extern const char hash_lbrace[] = "#{"; - extern const char rbrace[] = "}"; - extern const char rparen[] = ")"; - extern const char sign_chars[] = "-+"; - extern const char op_chars[] = "-+"; - extern const char hyphen[] = "-"; - extern const char ellipsis[] = "..."; - // extern const char url_space_chars[] = " \t\r\n\f"; - // type names - extern const char numeric_name[] = "numeric value"; - extern const char number_name[] = "number"; - extern const char percentage_name[] = "percentage"; - extern const char dimension_name[] = "numeric dimension"; - extern const char string_name[] = "string"; - extern const char bool_name[] = "bool"; - extern const char color_name[] = "color"; - extern const char list_name[] = "list"; - extern const char map_name[] = "map"; - extern const char arglist_name[] = "arglist"; - - // constants for uri parsing (RFC 3986 Appendix A.) - extern const char uri_chars[] = ":;/?!%&#@|[]{}'`^\"*+-.,_=~"; - extern const char real_uri_chars[] = "#%&"; - - extern const char selector_combinator_child[] = ">"; - extern const char selector_combinator_general[] = "~"; - extern const char selector_combinator_adjacent[] = "+"; - - // some specific constant character classes - // they must be static to be useable by lexer - extern const char static_ops[] = "*/%"; - // some character classes for the parser - extern const char selector_list_delims[] = "){};!"; - extern const char complex_selector_delims[] = ",){};!"; - extern const char selector_combinator_ops[] = "+~>"; - // optional modifiers for alternative compare context - extern const char attribute_compare_modifiers[] = "~|^$*"; - extern const char selector_lookahead_ops[] = "*&%,()[]"; + // https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity + // https://github.com/sass/sass/issues/1495#issuecomment-61189114 + namespace Specificity + { + extern const unsigned long Star = 0; + extern const unsigned long Universal = 0; + extern const unsigned long Element = 1; + extern const unsigned long Base = 1000; + extern const unsigned long Class = 1000; + extern const unsigned long Attr = 1000; + extern const unsigned long Pseudo = 1000; + extern const unsigned long ID = 1000000; + } + + + // http://en.wikipedia.org/wiki/Byte_order_mark + namespace BOM { + extern const unsigned char utf_8[] = {0xEF, 0xBB, 0xBF}; + extern const unsigned char utf_16_be[] = {0xFE, 0xFF}; + extern const unsigned char utf_16_le[] = {0xFF, 0xFE}; + extern const unsigned char utf_32_be[] = {0x00, 0x00, 0xFE, 0xFF}; + extern const unsigned char utf_32_le[] = {0xFF, 0xFE, 0x00, 0x00}; + extern const unsigned char utf_7_1[] = {0x2B, 0x2F, 0x76, 0x38}; + extern const unsigned char utf_7_2[] = {0x2B, 0x2F, 0x76, 0x39}; + extern const unsigned char utf_7_3[] = {0x2B, 0x2F, 0x76, 0x2B}; + extern const unsigned char utf_7_4[] = {0x2B, 0x2F, 0x76, 0x2F}; + extern const unsigned char utf_7_5[] = {0x2B, 0x2F, 0x76, 0x38, 0x2D}; + extern const unsigned char utf_1[] = {0xF7, 0x64, 0x4C}; + extern const unsigned char utf_ebcdic[] = {0xDD, 0x73, 0x66, 0x73}; + extern const unsigned char scsu[] = {0x0E, 0xFE, 0xFF}; + extern const unsigned char bocu_1[] = {0xFB, 0xEE, 0x28}; + extern const unsigned char gb_18030[] = {0x84, 0x31, 0x95, 0x33}; + } + + + namespace Terminal { + const char reset[] = "\033[m"; + const char bold[] = "\033[1m"; + const char red[] = "\033[31m"; + const char green[] = "\033[32m"; + const char yellow[] = "\033[33m"; + const char blue[] = "\033[34m"; + const char magenta[] = "\033[35m"; + const char cyan[] = "\033[36m"; + const char bold_red[] = "\033[1;31m"; + const char bold_green[] = "\033[1;32m"; + const char bold_yellow[] = "\033[1;33m"; + const char bold_blue[] = "\033[1;34m"; + const char bold_magenta[] = "\033[1;35m"; + const char bold_cyan[] = "\033[1;36m"; + const char bg_red[] = "\033[41m"; + const char bg_green[] = "\033[42m"; + const char bg_yellow[] = "\033[43m"; + const char bg_blue[] = "\033[44m"; + const char bg_magenta[] = "\033[45m"; + const char bg_cyan[] = "\033[46m"; + } + + namespace String { + + const char empty[] = ""; + + } - // byte order marks - // (taken from http://en.wikipedia.org/wiki/Byte_order_mark) - extern const unsigned char utf_8_bom[] = { 0xEF, 0xBB, 0xBF }; - extern const unsigned char utf_16_bom_be[] = { 0xFE, 0xFF }; - extern const unsigned char utf_16_bom_le[] = { 0xFF, 0xFE }; - extern const unsigned char utf_32_bom_be[] = { 0x00, 0x00, 0xFE, 0xFF }; - extern const unsigned char utf_32_bom_le[] = { 0xFF, 0xFE, 0x00, 0x00 }; - extern const unsigned char utf_7_bom_1[] = { 0x2B, 0x2F, 0x76, 0x38 }; - extern const unsigned char utf_7_bom_2[] = { 0x2B, 0x2F, 0x76, 0x39 }; - extern const unsigned char utf_7_bom_3[] = { 0x2B, 0x2F, 0x76, 0x2B }; - extern const unsigned char utf_7_bom_4[] = { 0x2B, 0x2F, 0x76, 0x2F }; - extern const unsigned char utf_7_bom_5[] = { 0x2B, 0x2F, 0x76, 0x38, 0x2D }; - extern const unsigned char utf_1_bom[] = { 0xF7, 0x64, 0x4C }; - extern const unsigned char utf_ebcdic_bom[] = { 0xDD, 0x73, 0x66, 0x73 }; - extern const unsigned char scsu_bom[] = { 0x0E, 0xFE, 0xFF }; - extern const unsigned char bocu_1_bom[] = { 0xFB, 0xEE, 0x28 }; - extern const unsigned char gb_18030_bom[] = { 0x84, 0x31, 0x95, 0x33 }; } } diff --git a/src/constants.hpp b/src/constants.hpp index 81ada27494..9ffa701cc1 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -1,200 +1,93 @@ -#ifndef SASS_CONSTANTS_H -#define SASS_CONSTANTS_H - -namespace Sass { - namespace Constants { - - // The maximum call stack that can be created - extern const unsigned long MaxCallStack; - - // https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity - // The following list of selectors is by increasing specificity: - extern const unsigned long Specificity_Star; - extern const unsigned long Specificity_Universal; - extern const unsigned long Specificity_Element; - extern const unsigned long Specificity_Base; - extern const unsigned long Specificity_Class; - extern const unsigned long Specificity_Attr; - extern const unsigned long Specificity_Pseudo; - extern const unsigned long Specificity_ID; - - // Selector unification order; - extern const int UnificationOrder_Element; - extern const int UnificationOrder_Id; - extern const int UnificationOrder_Class; - extern const int UnificationOrder_Attribute; - extern const int UnificationOrder_PseudoClass; - extern const int UnificationOrder_Wrapped; - extern const int UnificationOrder_PseudoElement; - extern const int UnificationOrder_Placeholder; - - // sass keywords - extern const char at_root_kwd[]; - extern const char import_kwd[]; - extern const char mixin_kwd[]; - extern const char function_kwd[]; - extern const char return_kwd[]; - extern const char include_kwd[]; - extern const char content_kwd[]; - extern const char extend_kwd[]; - extern const char if_kwd[]; - extern const char else_kwd[]; - extern const char if_after_else_kwd[]; - extern const char for_kwd[]; - extern const char from_kwd[]; - extern const char to_kwd[]; - extern const char of_kwd[]; - extern const char through_kwd[]; - extern const char each_kwd[]; - extern const char in_kwd[]; - extern const char while_kwd[]; - extern const char warn_kwd[]; - extern const char error_kwd[]; - extern const char debug_kwd[]; - extern const char default_kwd[]; - extern const char global_kwd[]; - extern const char null_kwd[]; - extern const char optional_kwd[]; - extern const char with_kwd[]; - extern const char without_kwd[]; - extern const char all_kwd[]; - extern const char rule_kwd[]; - - // css standard units - extern const char em_kwd[]; - extern const char ex_kwd[]; - extern const char px_kwd[]; - extern const char cm_kwd[]; - extern const char mm_kwd[]; - extern const char pt_kwd[]; - extern const char pc_kwd[]; - extern const char deg_kwd[]; - extern const char rad_kwd[]; - extern const char grad_kwd[]; - extern const char turn_kwd[]; - extern const char ms_kwd[]; - extern const char s_kwd[]; - extern const char Hz_kwd[]; - extern const char kHz_kwd[]; - - // vendor prefixes - extern const char vendor_opera_kwd[]; - extern const char vendor_webkit_kwd[]; - extern const char vendor_mozilla_kwd[]; - extern const char vendor_ms_kwd[]; - extern const char vendor_khtml_kwd[]; - - // css functions and keywords - extern const char charset_kwd[]; - extern const char media_kwd[]; - extern const char supports_kwd[]; - extern const char keyframes_kwd[]; - extern const char only_kwd[]; - extern const char rgb_fn_kwd[]; - extern const char url_fn_kwd[]; - extern const char url_kwd[]; - // extern const char url_prefix_fn_kwd[]; - extern const char important_kwd[]; - extern const char pseudo_not_fn_kwd[]; - extern const char even_kwd[]; - extern const char odd_kwd[]; - extern const char progid_kwd[]; - extern const char expression_kwd[]; - extern const char calc_fn_kwd[]; - - // char classes for "regular expressions" - extern const char almost_any_value_class[]; - - // css selector keywords - extern const char sel_deep_kwd[]; - - // css attribute-matching operators - extern const char tilde_equal[]; - extern const char pipe_equal[]; - extern const char caret_equal[]; - extern const char dollar_equal[]; - extern const char star_equal[]; - - // relational & logical operators and constants - extern const char and_kwd[]; - extern const char or_kwd[]; - extern const char not_kwd[]; - extern const char gt[]; - extern const char gte[]; - extern const char lt[]; - extern const char lte[]; - extern const char eq[]; - extern const char neq[]; - extern const char true_kwd[]; - extern const char false_kwd[]; - - // definition keywords - extern const char using_kwd[]; - - // miscellaneous punctuation and delimiters - extern const char percent_str[]; - extern const char empty_str[]; - extern const char slash_slash[]; - extern const char slash_star[]; - extern const char star_slash[]; - extern const char hash_lbrace[]; - extern const char rbrace[]; - extern const char rparen[]; - extern const char sign_chars[]; - extern const char op_chars[]; - extern const char hyphen[]; - extern const char ellipsis[]; - // extern const char url_space_chars[]; - - // type names - extern const char numeric_name[]; - extern const char number_name[]; - extern const char percentage_name[]; - extern const char dimension_name[]; - extern const char string_name[]; - extern const char bool_name[]; - extern const char color_name[]; - extern const char list_name[]; - extern const char map_name[]; - extern const char arglist_name[]; - - // constants for uri parsing (RFC 3986 Appendix A.) - extern const char uri_chars[]; - extern const char real_uri_chars[]; - - // constants for selector combinators - extern const char selector_combinator_child[]; - extern const char selector_combinator_general[]; - extern const char selector_combinator_adjacent[]; - - // some specific constant character classes - // they must be static to be useable by lexer - extern const char static_ops[]; - extern const char selector_list_delims[]; - extern const char complex_selector_delims[]; - extern const char selector_combinator_ops[]; - extern const char attribute_compare_modifiers[]; - extern const char selector_lookahead_ops[]; - - // byte order marks - // (taken from http://en.wikipedia.org/wiki/Byte_order_mark) - extern const unsigned char utf_8_bom[]; - extern const unsigned char utf_16_bom_be[]; - extern const unsigned char utf_16_bom_le[]; - extern const unsigned char utf_32_bom_be[]; - extern const unsigned char utf_32_bom_le[]; - extern const unsigned char utf_7_bom_1[]; - extern const unsigned char utf_7_bom_2[]; - extern const unsigned char utf_7_bom_3[]; - extern const unsigned char utf_7_bom_4[]; - extern const unsigned char utf_7_bom_5[]; - extern const unsigned char utf_1_bom[]; - extern const unsigned char utf_ebcdic_bom[]; - extern const unsigned char scsu_bom[]; - extern const unsigned char bocu_1_bom[]; - extern const unsigned char gb_18030_bom[]; - - } -} +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CONSTANTS_HPP +#define SASS_CONSTANTS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +namespace Sass +{ + + namespace Constants + { + + // https://github.com/sass/libsass/issues/592 + // https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity + // https://github.com/sass/sass/issues/1495#issuecomment-61189114 + namespace Specificity + { + + extern const unsigned long Star; + extern const unsigned long Universal; + extern const unsigned long Element; + extern const unsigned long Base; + extern const unsigned long Class; + extern const unsigned long Attr; + extern const unsigned long Pseudo; + extern const unsigned long ID; + + } // namespace Specificity + + // http://en.wikipedia.org/wiki/Byte_order_mark + namespace BOM + { + + extern const unsigned char utf_8[]; + extern const unsigned char utf_16_be[]; + extern const unsigned char utf_16_le[]; + extern const unsigned char utf_32_be[]; + extern const unsigned char utf_32_le[]; + extern const unsigned char utf_7_1[]; + extern const unsigned char utf_7_2[]; + extern const unsigned char utf_7_3[]; + extern const unsigned char utf_7_4[]; + extern const unsigned char utf_7_5[]; + extern const unsigned char utf_1[]; + extern const unsigned char utf_ebcdic[]; + extern const unsigned char scsu[]; + extern const unsigned char bocu_1[]; + extern const unsigned char gb_18030[]; + + } // namespace BOM + + namespace Terminal + { + + extern const char reset[]; + extern const char bold[]; + extern const char red[]; + extern const char green[]; + extern const char yellow[]; + extern const char blue[]; + extern const char magenta[]; + extern const char cyan[]; + extern const char bold_red[]; + extern const char bold_green[]; + extern const char bold_yellow[]; + extern const char bold_blue[]; + extern const char bold_magenta[]; + extern const char bold_cyan[]; + extern const char bg_red[]; + extern const char bg_green[]; + extern const char bg_yellow[]; + extern const char bg_blue[]; + extern const char bg_magenta[]; + extern const char bg_cyan[]; + + } // namespace Terminal + + namespace String + { + + extern const char empty[]; + + } // namespace String + + } // namespace Constants + +} // namespace Sass #endif diff --git a/src/context.cpp b/src/context.cpp index 7fa7d78183..b292ebd009 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -1,863 +1,162 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" - -#include "remove_placeholders.hpp" -#include "sass_functions.hpp" -#include "check_nesting.hpp" -#include "fn_selectors.hpp" -#include "fn_strings.hpp" -#include "fn_numbers.hpp" -#include "fn_colors.hpp" -#include "fn_miscs.hpp" -#include "fn_lists.hpp" -#include "fn_maps.hpp" #include "context.hpp" -#include "expand.hpp" -#include "parser.hpp" -#include "cssize.hpp" + #include "source.hpp" +#include "plugins.hpp" +#include "exceptions.hpp" namespace Sass { - using namespace Constants; + using namespace File; - using namespace Sass; - inline bool sort_importers (const Sass_Importer_Entry& i, const Sass_Importer_Entry& j) - { return sass_importer_get_priority(i) > sass_importer_get_priority(j); } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - static sass::string safe_input(const char* in_path) - { - if (in_path == nullptr || in_path[0] == '\0') return "stdin"; - return in_path; - } + // Initialize current directory once + sass::string CWD(get_cwd()); - static sass::string safe_output(const char* out_path, sass::string input_path) + // Helper function to sort header and importer arrays by priorities + inline bool cmpImporterPrio(struct SassImporter* i, struct SassImporter* j) { - if (out_path == nullptr || out_path[0] == '\0') { - if (input_path.empty()) return "stdout"; - return input_path.substr(0, input_path.find_last_of(".")) + ".css"; - } - return out_path; + return sass_importer_get_priority(i) > sass_importer_get_priority(j); } - Context::Context(struct Sass_Context& c_ctx) - : CWD(File::get_cwd()), - c_options(c_ctx), - entry_path(""), - head_imports(0), - plugins(), - emitter(c_options), - - ast_gc(), - strings(), - resources(), - sheets(), - import_stack(), - callee_stack(), - traces(), - extender(Extender::NORMAL, traces), - c_compiler(NULL), - - c_headers (sass::vector()), - c_importers (sass::vector()), - c_functions (sass::vector()), - - indent (safe_str(c_options.indent, " ")), - linefeed (safe_str(c_options.linefeed, "\n")), - - input_path (make_canonical_path(safe_input(c_options.input_path))), - output_path (make_canonical_path(safe_output(c_options.output_path, input_path))), - source_map_file (make_canonical_path(safe_str(c_options.source_map_file, ""))), - source_map_root (make_canonical_path(safe_str(c_options.source_map_root, ""))) + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + // Default constructor + Context::Context() : + SassOutputOptionsCpp(), + varRoot(varStack) { - // Sass 3.4: The current working directory will no longer be placed onto the Sass load path by default. // If you need the current working directory to be available, set SASS_PATH=. in your shell's environment. - // include_paths.push_back(CWD); - - // collect more paths from different options - collect_include_paths(c_options.include_path); - collect_include_paths(c_options.include_paths); - collect_plugin_paths(c_options.plugin_path); - collect_plugin_paths(c_options.plugin_paths); - - // load plugins and register custom behaviors - for(auto plug : plugin_paths) plugins.load_plugins(plug); - for(auto fn : plugins.get_headers()) c_headers.push_back(fn); - for(auto fn : plugins.get_importers()) c_importers.push_back(fn); - for(auto fn : plugins.get_functions()) c_functions.push_back(fn); - - // sort the items by priority (lowest first) - sort (c_headers.begin(), c_headers.end(), sort_importers); - sort (c_importers.begin(), c_importers.end(), sort_importers); - - emitter.set_filename(abs2rel(output_path, source_map_file, CWD)); - - } - - void Context::add_c_function(Sass_Function_Entry function) - { - c_functions.push_back(function); - } - void Context::add_c_header(Sass_Importer_Entry header) - { - c_headers.push_back(header); + // Or add it explicitly in your implementation, e.g. includePaths.emplace_back(CWD or '.'); + } + // EO Context ctor + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Add additional include paths, which can be path separated + void Context::addIncludePaths(const sass::string& paths) + { + // Check if we have anything to do + if (paths.empty()) return; + // Load plugins from all paths + sass::vector split = + StringUtils::split(paths, PATH_SEP, true); + for (sass::string& path : split) { + if (*path.rbegin() != '/') path += '/'; + includePaths.emplace_back(path); + } + } + // EO addIncludePaths + + // Load plugins from path, which can be path separated + void Context::loadPlugins(const sass::string& paths) + { + Plugins plugins; + // Check if we have anything to do + if (paths.empty()) return; + // Load plugins from all paths + sass::vector split = + StringUtils::split(paths, PATH_SEP, true); + for (sass::string& path : split) { + std::cerr << "Load " << path << "\n"; + if (*path.rbegin() != '/') path += '/'; + plugins.load_plugins(path); + std::cerr << "Loaded " << path << "\n"; + } + // Take over ownership from plugin + plugins.consume_headers(cHeaders); + plugins.consume_importers(cImporters); + plugins.consume_functions(cFunctions); + // Sort the merged arrays by callback priorities + sort(cHeaders.begin(), cHeaders.end(), cmpImporterPrio); + sort(cImporters.begin(), cImporters.end(), cmpImporterPrio); + } + // EO loadPlugins + + ///////////////////////////////////////////////////////////////////////// + // Helpers for `sass_prepare_context` + // Obsolete when c_ctx and cpp_ctx are merged. + ///////////////////////////////////////////////////////////////////////// + + void Context::addCustomHeader(struct SassImporter* header) + { + if (header == nullptr) return; + cHeaders.emplace_back(header); // need to sort the array afterwards (no big deal) - sort (c_headers.begin(), c_headers.end(), sort_importers); - } - void Context::add_c_importer(Sass_Importer_Entry importer) - { - c_importers.push_back(importer); - // need to sort the array afterwards (no big deal) - sort (c_importers.begin(), c_importers.end(), sort_importers); - } - - Context::~Context() - { - // resources were allocated by malloc - for (size_t i = 0; i < resources.size(); ++i) { - free(resources[i].contents); - free(resources[i].srcmap); - } - // free all strings we kept alive during compiler execution - for (size_t n = 0; n < strings.size(); ++n) free(strings[n]); - // everything that gets put into sources will be freed by us - // this shouldn't have anything in it anyway!? - for (size_t m = 0; m < import_stack.size(); ++m) { - sass_import_take_source(import_stack[m]); - sass_import_take_srcmap(import_stack[m]); - sass_delete_import(import_stack[m]); - } - // clear inner structures (vectors) and input source - resources.clear(); import_stack.clear(); - sheets.clear(); - } - - Data_Context::~Data_Context() - { - // --> this will be freed by resources - // make sure we free the source even if not processed! - // if (resources.size() == 0 && source_c_str) free(source_c_str); - // if (resources.size() == 0 && srcmap_c_str) free(srcmap_c_str); - // source_c_str = 0; srcmap_c_str = 0; + sort(cHeaders.begin(), cHeaders.end(), cmpImporterPrio); } - File_Context::~File_Context() + void Context::addCustomImporter(struct SassImporter* importer) { + if (importer == nullptr) return; + cImporters.emplace_back(importer); + // need to sort the array afterwards (no big deal) + sort(cImporters.begin(), cImporters.end(), cmpImporterPrio); } - void Context::collect_include_paths(const char* paths_str) - { - if (paths_str) { - const char* beg = paths_str; - const char* end = Prelexer::find_first(beg); - - while (end) { - sass::string path(beg, end - beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - include_paths.push_back(path); - } - beg = end + 1; - end = Prelexer::find_first(beg); - } - - sass::string path(beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - include_paths.push_back(path); - } - } - } - - void Context::collect_include_paths(string_list* paths_array) + void Context::addCustomFunction(struct SassFunction* function) { - while (paths_array) - { - collect_include_paths(paths_array->string); - paths_array = paths_array->next; - } + if (function == nullptr) return; + cFunctions.emplace_back(function); } - 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) { - sass::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); - } - - sass::string path(beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - plugin_paths.push_back(path); - } - } - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - void Context::collect_plugin_paths(string_list* paths_array) + // Implementation for `sass_compiler_find_file` + sass::string Context::findFile(const sass::string& path) { - while (paths_array) - { - collect_plugin_paths(paths_array->string); - paths_array = paths_array->next; - } + // Get last import entry for current base + Import& import = import_stack.back(); + // create the vector with paths to lookup + sass::vector incpaths(1 + includePaths.size()); + incpaths.emplace_back(dir_name(import.source->getAbsPath())); + incpaths.insert(incpaths.end(), includePaths.begin(), includePaths.end()); + return find_file(path, CWD, incpaths, fileExistsCache); } - // resolve the imp_path in base_path or include_paths - // looks for alternatives and returns a list from one directory - sass::vector Context::find_includes(const Importer& import) + // Look for all possible filename variants (e.g. partials) + // Returns all results (e.g. for ambiguous valid imports) + sass::vector Context::findIncludes(const ImportRequest& import) { // make sure we resolve against an absolute path - sass::string base_path(rel2abs(import.base_path)); + sass::string base_path(rel2abs(import.base_path, ".", CWD)); // first try to resolve the load path relative to the base path - sass::vector vec(resolve_includes(base_path, import.imp_path)); + sass::vector vec(resolve_includes( + base_path, import.imp_path, CWD, fileExistsCache)); // then search in every include path (but only if nothing found yet) - for (size_t i = 0, S = include_paths.size(); vec.size() == 0 && i < S; ++i) + for (size_t i = 0, S = includePaths.size(); vec.size() == 0 && i < S; ++i) { - // call resolve_includes and individual base path and append all results - sass::vector resolved(resolve_includes(include_paths[i], import.imp_path)); - if (resolved.size()) vec.insert(vec.end(), resolved.begin(), resolved.end()); + sass::vector resolved(resolve_includes( + includePaths[i], import.imp_path, CWD, fileExistsCache)); + vec.insert(vec.end(), resolved.begin(), resolved.end()); } // return vector return vec; } - // register include with resolved path and its content - // memory of the resources will be freed by us on exit - void Context::register_resource(const Include& inc, const Resource& res) - { - - // do not parse same resource twice - // maybe raise an error in this case - // if (sheets.count(inc.abs_path)) { - // free(res.contents); free(res.srcmap); - // throw std::runtime_error("duplicate resource registered"); - // return; - // } - - // get index for this resource - size_t idx = resources.size(); - - // tell emitter about new resource - emitter.add_source_index(idx); - - // put resources under our control - // the memory will be freed later - resources.push_back(res); - - // add a relative link to the working directory - included_files.push_back(inc.abs_path); - // add a relative link to the source map output file - srcmap_links.push_back(abs2rel(inc.abs_path, source_map_file, CWD)); - - // get pointer to the loaded content - Sass_Import_Entry import = sass_make_import( - inc.imp_path.c_str(), - inc.abs_path.c_str(), - res.contents, - res.srcmap - ); - // add the entry to the stack - import_stack.push_back(import); - - // get pointer to the loaded content - const char* contents = resources[idx].contents; - SourceFileObj source = SASS_MEMORY_NEW(SourceFile, - inc.abs_path.c_str(), contents, idx); - - // create the initial parser state from resource - SourceSpan pstate(source); - - // check existing import stack for possible recursion - for (size_t i = 0; i < import_stack.size() - 2; ++i) { - auto parent = import_stack[i]; - if (std::strcmp(parent->abs_path, import->abs_path) == 0) { - sass::string cwd(File::get_cwd()); - // make path relative to the current directory - sass::string stack("An @import loop has been found:"); - for (size_t n = 1; n < i + 2; ++n) { - stack += "\n " + sass::string(File::abs2rel(import_stack[n]->abs_path, cwd, cwd)) + - " imports " + sass::string(File::abs2rel(import_stack[n+1]->abs_path, cwd, cwd)); - } - // implement error throw directly until we - // decided how to handle full stack traces - throw Exception::InvalidSyntax(pstate, traces, stack); - // error(stack, prstate ? *prstate : pstate, import_stack); - } - } - - // create a parser instance from the given c_str buffer - Parser p(source, *this, traces); - // do not yet dispose these buffers - sass_import_take_source(import); - sass_import_take_srcmap(import); - // then parse the root block - Block_Obj root = p.parse(); - // delete memory of current stack frame - sass_delete_import(import_stack.back()); - // remove current stack frame - import_stack.pop_back(); - // create key/value pair for ast node - std::pair - ast_pair(inc.abs_path, { res, root }); - // register resulting resource - sheets.insert(ast_pair); - } - - // register include with resolved path and its content - // memory of the resources will be freed by us on exit - void Context::register_resource(const Include& inc, const Resource& res, SourceSpan& prstate) - { - traces.push_back(Backtrace(prstate)); - register_resource(inc, res); - traces.pop_back(); - } - - // Add a new import to the context (called from `import_url`) - Include Context::load_import(const Importer& imp, SourceSpan pstate) - { - - // search for valid imports (ie. partials) on the filesystem - // this may return more than one valid result (ambiguous imp_path) - const sass::vector resolved(find_includes(imp)); - - // error nicely on ambiguous imp_path - if (resolved.size() > 1) { - sass::ostream msg_stream; - msg_stream << "It's not clear which file to import for "; - msg_stream << "'@import \"" << imp.imp_path << "\"'." << "\n"; - msg_stream << "Candidates:" << "\n"; - for (size_t i = 0, L = resolved.size(); i < L; ++i) - { msg_stream << " " << resolved[i].imp_path << "\n"; } - msg_stream << "Please delete or rename all but one of these files." << "\n"; - error(msg_stream.str(), pstate, traces); - } - - // process the resolved entry - else if (resolved.size() == 1) { - bool use_cache = c_importers.size() == 0; - // use cache for the resource loading - if (use_cache && sheets.count(resolved[0].abs_path)) return resolved[0]; - // try to read the content of the resolved file entry - // the memory buffer returned must be freed by us! - if (char* contents = read_file(resolved[0].abs_path)) { - // register the newly resolved file resource - register_resource(resolved[0], { contents, 0 }, pstate); - // return resolved entry - return resolved[0]; - } - } - - // nothing found - return { imp, "" }; - - } - - void Context::import_url (Import* imp, sass::string load_path, const sass::string& ctx_path) { - - SourceSpan pstate(imp->pstate()); - sass::string imp_path(unquote(load_path)); - sass::string protocol("file"); - - using namespace Prelexer; - if (const char* proto = sequence< identifier, exactly<':'>, exactly<'/'>, exactly<'/'> >(imp_path.c_str())) { - - protocol = sass::string(imp_path.c_str(), proto - 3); - // if (protocol.compare("file") && true) { } - } - - // add urls (protocol other than file) and urls without protocol to `urls` member - // ToDo: if ctx_path is already a file resource, we should not add it here? - if (imp->import_queries() || protocol != "file" || imp_path.substr(0, 2) == "//") { - imp->urls().push_back(SASS_MEMORY_NEW(String_Quoted, imp->pstate(), load_path)); - } - else if (imp_path.length() > 4 && imp_path.substr(imp_path.length() - 4, 4) == ".css") { - String_Constant* loc = SASS_MEMORY_NEW(String_Constant, pstate, unquote(load_path)); - Argument_Obj loc_arg = SASS_MEMORY_NEW(Argument, pstate, loc); - Arguments_Obj loc_args = SASS_MEMORY_NEW(Arguments, pstate); - loc_args->append(loc_arg); - Function_Call* new_url = SASS_MEMORY_NEW(Function_Call, pstate, sass::string("url"), loc_args); - imp->urls().push_back(new_url); - } - else { - const Importer importer(imp_path, ctx_path); - Include include(load_import(importer, pstate)); - if (include.abs_path.empty()) { - error("File to import not found or unreadable: " + imp_path + ".", pstate, traces); - } - imp->incs().push_back(include); - } - - } - - - // call custom importers on the given (unquoted) load_path and eventually parse the resulting style_sheet - bool Context::call_loader(const sass::string& load_path, const char* ctx_path, SourceSpan& pstate, Import* imp, sass::vector importers, bool only_one) - { - // unique counter - size_t count = 0; - // need one correct import - bool has_import = false; - // process all custom importers (or custom headers) - for (Sass_Importer_Entry& importer_ent : importers) { - // int priority = sass_importer_get_priority(importer); - Sass_Importer_Fn fn = sass_importer_get_function(importer_ent); - // skip importer if it returns NULL - if (Sass_Import_List includes = - fn(load_path.c_str(), importer_ent, c_compiler) - ) { - // get c pointer copy to iterate over - Sass_Import_List it_includes = includes; - while (*it_includes) { ++count; - // create unique path to use as key - sass::string uniq_path = load_path; - if (!only_one && count) { - sass::ostream path_strm; - path_strm << uniq_path << ":" << count; - uniq_path = path_strm.str(); - } - // create the importer struct - Importer importer(uniq_path, ctx_path); - // query data from the current include - Sass_Import_Entry include_ent = *it_includes; - char* source = sass_import_take_source(include_ent); - char* srcmap = sass_import_take_srcmap(include_ent); - size_t line = sass_import_get_error_line(include_ent); - size_t column = sass_import_get_error_column(include_ent); - const char *abs_path = sass_import_get_abs_path(include_ent); - // handle error message passed back from custom importer - // it may (or may not) override the line and column info - if (const char* err_message = sass_import_get_error_message(include_ent)) { - if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, pstate); - if (line == sass::string::npos && column == sass::string::npos) error(err_message, pstate, traces); - else { error(err_message, { pstate.source, { line, column } }, traces); } - } - // content for import was set - else if (source) { - // resolved abs_path should be set by custom importer - // use the created uniq_path as fallback (maybe enforce) - sass::string path_key(abs_path ? abs_path : uniq_path); - // create the importer struct - Include include(importer, path_key); - // attach information to AST node - imp->incs().push_back(include); - // register the resource buffers - register_resource(include, { source, srcmap }, pstate); - } - // only a path was retuned - // try to load it like normal - else if(abs_path) { - // checks some urls to preserve - // `http://`, `https://` and `//` - // or dispatchs to `import_file` - // which will check for a `.css` extension - // or resolves the file on the filesystem - // added and resolved via `add_file` - // finally stores everything on `imp` - import_url(imp, abs_path, ctx_path); - } - // move to next - ++it_includes; - } - // deallocate the returned memory - sass_delete_import_list(includes); - // set success flag - has_import = true; - // break out of loop - if (only_one) break; - } - } - // return result - return has_import; - } - - void register_function(Context&, Signature sig, Native_Function f, Env* env); - void register_function(Context&, Signature sig, Native_Function f, size_t arity, Env* env); - void register_overload_stub(Context&, sass::string name, Env* env); - void register_built_in_functions(Context&, Env* env); - void register_c_functions(Context&, Env* env, Sass_Function_List); - void register_c_function(Context&, Env* env, Sass_Function_Entry); - - char* Context::render(Block_Obj root) - { - // check for valid block - if (!root) return 0; - // start the render process - root->perform(&emitter); - // finish emitter stream - emitter.finalize(); - // get the resulting buffer from stream - OutputBuffer emitted = emitter.get_buffer(); - // should we append a source map url? - if (!c_options.omit_source_map_url) { - // generate an embedded source map - if (c_options.source_map_embed) { - emitted.buffer += linefeed; - emitted.buffer += format_embedded_source_map(); - } - // or just link the generated one - else if (source_map_file != "") { - emitted.buffer += linefeed; - emitted.buffer += format_source_mapping_url(source_map_file); - } - } - // create a copy of the resulting buffer string - // this must be freed or taken over by implementor - return sass_copy_c_string(emitted.buffer.c_str()); - } - - void Context::apply_custom_headers(Block_Obj root, const char* ctx_path, SourceSpan pstate) - { - // create a custom import to resolve headers - Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); - // dispatch headers which will add custom functions - // custom headers are added to the import instance - call_headers(entry_path, ctx_path, pstate, imp); - // increase head count to skip later - head_imports += resources.size() - 1; - // add the statement if we have urls - if (!imp->urls().empty()) root->append(imp); - // process all other resources (add Import_Stub nodes) - for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { - root->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); - } - } - - Block_Obj File_Context::parse() - { - - // check if entry file is given - if (input_path.empty()) return {}; - - // create absolute path from input filename - // ToDo: this should be resolved via custom importers - sass::string abs_path(rel2abs(input_path, CWD)); - - // try to load the entry file - char* contents = read_file(abs_path); - - // alternatively also look inside each include path folder - // I think this differs from ruby sass (IMO too late to remove) - for (size_t i = 0, S = include_paths.size(); contents == 0 && i < S; ++i) { - // build absolute path for this include path entry - abs_path = rel2abs(input_path, include_paths[i]); - // try to load the resulting path - contents = read_file(abs_path); - } - - // abort early if no content could be loaded (various reasons) - if (!contents) throw std::runtime_error( - "File to read not found or unreadable: " - + std::string(input_path.c_str())); - - // store entry path - entry_path = abs_path; - - // create entry only for import stack - Sass_Import_Entry import = sass_make_import( - input_path.c_str(), - entry_path.c_str(), - contents, - 0 - ); - // add the entry to the stack - import_stack.push_back(import); - - // create the source entry for file entry - register_resource({{ input_path, "." }, abs_path }, { contents, 0 }); - - // create root ast tree node - return compile(); - - } - - Block_Obj Data_Context::parse() - { - - // check if source string is given - if (!source_c_str) return {}; - - // convert indented sass syntax - if(c_options.is_indented_syntax_src) { - // call sass2scss to convert the string - char * converted = sass2scss(source_c_str, - // preserve the structure as much as possible - SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT); - // replace old source_c_str with converted - free(source_c_str); source_c_str = converted; - } - - // remember entry path (defaults to stdin for string) - entry_path = input_path.empty() ? "stdin" : input_path; - - // ToDo: this may be resolved via custom importers - sass::string abs_path(rel2abs(entry_path)); - char* abs_path_c_str = sass_copy_c_string(abs_path.c_str()); - strings.push_back(abs_path_c_str); - - // create entry only for the import stack - Sass_Import_Entry import = sass_make_import( - entry_path.c_str(), - abs_path_c_str, - source_c_str, - srcmap_c_str - ); - // add the entry to the stack - import_stack.push_back(import); - - // register a synthetic resource (path does not really exist, skip in includes) - register_resource({{ input_path, "." }, input_path }, { source_c_str, srcmap_c_str }); - - // create root ast tree node - return compile(); - } - - // parse root block from includes - Block_Obj Context::compile() + // Load import from the file-system and create source object + Import* Context::loadImport(const ResolvedImport& import) { - // abort if there is no data - if (resources.size() == 0) return {}; - // get root block from the first style sheet - Block_Obj root = sheets.at(entry_path).root; - // abort on invalid root - if (root.isNull()) return {}; - Env global; // create root environment - // register built-in functions on env - register_built_in_functions(*this, &global); - // register custom functions (defined via C-API) - for (size_t i = 0, S = c_functions.size(); i < S; ++i) - { register_c_function(*this, &global, c_functions[i]); } - // create initial backtrace entry - // create crtp visitor objects - Expand expand(*this, &global); - Cssize cssize(*this); - CheckNesting check_nesting; - // check nesting in all files - for (auto sheet : sheets) { - auto styles = sheet.second; - check_nesting(styles.root); - } - // expand and eval the tree - root = expand(root); - - Extension unsatisfied; - // check that all extends were used - if (extender.checkForUnsatisfiedExtends(unsatisfied)) { - throw Exception::UnsatisfiedExtend(traces, unsatisfied); + // Try to find the item in the cache first + auto cached = sources.find(import.abs_path); + if (cached != sources.end()) return cached->second; + // Try to read source and (ToDo) optional mappings + if (ImportObj loaded = read_file(import)) { + sources.insert({ import.abs_path, loaded }); + return loaded.detach(); } - - // check nesting - check_nesting(root); - // merge and bubble certain rules - root = cssize(root); - - // clean up by removing empty placeholders - // ToDo: maybe we can do this somewhere else? - Remove_Placeholders remove_placeholders; - root->perform(&remove_placeholders); - - // return processed tree - return root; - } - // EO compile - - sass::string Context::format_embedded_source_map() - { - sass::string map = emitter.render_srcmap(*this); - sass::istream is( map.c_str() ); - sass::ostream buffer; - base64::encoder E; - E.encode(is, buffer); - sass::string url = "data:application/json;base64," + buffer.str(); - url.erase(url.size() - 1); - return "/*# sourceMappingURL=" + url + " */"; - } - - sass::string Context::format_source_mapping_url(const sass::string& file) - { - sass::string url = abs2rel(file, output_path, CWD); - return "/*# sourceMappingURL=" + url + " */"; - } - - char* Context::render_srcmap() - { - if (source_map_file == "") return 0; - sass::string map = emitter.render_srcmap(*this); - return sass_copy_c_string(map.c_str()); - } - - - // for data context we want to start after "stdin" - // we probably always want to skip the header includes? - sass::vector Context::get_included_files(bool skip, size_t headers) - { - // create a copy of the vector for manipulations - sass::vector includes = included_files; - if (includes.size() == 0) return includes; - if (skip) { includes.erase( includes.begin(), includes.begin() + 1 + headers); } - else { includes.erase( includes.begin() + 1, includes.begin() + 1 + headers); } - includes.erase( std::unique( includes.begin(), includes.end() ), includes.end() ); - std::sort( includes.begin() + (skip ? 0 : 1), includes.end() ); - return includes; - } - - void register_function(Context& ctx, Signature sig, Native_Function f, Env* env) - { - Definition* def = make_native_function(sig, f, ctx); - def->environment(env); - (*env)[def->name() + "[f]"] = def; - } - - void register_function(Context& ctx, Signature sig, Native_Function f, size_t arity, Env* env) - { - Definition* def = make_native_function(sig, f, ctx); - sass::ostream ss; - ss << def->name() << "[f]" << arity; - def->environment(env); - (*env)[ss.str()] = def; - } - - void register_overload_stub(Context& ctx, sass::string name, Env* env) - { - Definition* stub = SASS_MEMORY_NEW(Definition, - SourceSpan{ "[built-in function]" }, - nullptr, - name, - Parameters_Obj{}, - nullptr, - true); - (*env)[name + "[f]"] = stub; - } - - - void register_built_in_functions(Context& ctx, Env* env) - { - using namespace Functions; - // RGB Functions - register_function(ctx, rgb_sig, rgb, env); - register_overload_stub(ctx, "rgba", env); - register_function(ctx, rgba_4_sig, rgba_4, 4, env); - register_function(ctx, rgba_2_sig, rgba_2, 2, env); - register_function(ctx, red_sig, red, env); - register_function(ctx, green_sig, green, env); - register_function(ctx, blue_sig, blue, env); - register_function(ctx, mix_sig, mix, env); - // HSL Functions - register_function(ctx, hsl_sig, hsl, env); - register_function(ctx, hsla_sig, hsla, env); - register_function(ctx, hue_sig, hue, env); - register_function(ctx, saturation_sig, saturation, env); - register_function(ctx, lightness_sig, lightness, env); - register_function(ctx, adjust_hue_sig, adjust_hue, env); - register_function(ctx, lighten_sig, lighten, env); - register_function(ctx, darken_sig, darken, env); - register_function(ctx, saturate_sig, saturate, env); - register_function(ctx, desaturate_sig, desaturate, env); - register_function(ctx, grayscale_sig, grayscale, env); - register_function(ctx, complement_sig, complement, env); - register_function(ctx, invert_sig, invert, env); - // Opacity Functions - register_function(ctx, alpha_sig, alpha, env); - register_function(ctx, opacity_sig, alpha, env); - register_function(ctx, opacify_sig, opacify, env); - register_function(ctx, fade_in_sig, opacify, env); - register_function(ctx, transparentize_sig, transparentize, env); - register_function(ctx, fade_out_sig, transparentize, env); - // Other Color Functions - register_function(ctx, adjust_color_sig, adjust_color, env); - register_function(ctx, scale_color_sig, scale_color, env); - register_function(ctx, change_color_sig, change_color, env); - register_function(ctx, ie_hex_str_sig, ie_hex_str, env); - // String Functions - register_function(ctx, unquote_sig, sass_unquote, env); - register_function(ctx, quote_sig, sass_quote, env); - register_function(ctx, str_length_sig, str_length, env); - register_function(ctx, str_insert_sig, str_insert, env); - register_function(ctx, str_index_sig, str_index, env); - register_function(ctx, str_slice_sig, str_slice, env); - register_function(ctx, to_upper_case_sig, to_upper_case, env); - register_function(ctx, to_lower_case_sig, to_lower_case, env); - // Number Functions - register_function(ctx, percentage_sig, percentage, env); - register_function(ctx, round_sig, round, env); - register_function(ctx, ceil_sig, ceil, env); - register_function(ctx, floor_sig, floor, env); - register_function(ctx, abs_sig, abs, env); - register_function(ctx, min_sig, min, env); - register_function(ctx, max_sig, max, env); - register_function(ctx, random_sig, random, env); - // List Functions - register_function(ctx, length_sig, length, env); - register_function(ctx, nth_sig, nth, env); - register_function(ctx, set_nth_sig, set_nth, env); - register_function(ctx, index_sig, index, env); - register_function(ctx, join_sig, join, env); - register_function(ctx, append_sig, append, env); - register_function(ctx, zip_sig, zip, env); - register_function(ctx, list_separator_sig, list_separator, env); - register_function(ctx, is_bracketed_sig, is_bracketed, env); - // Map Functions - register_function(ctx, map_get_sig, map_get, env); - register_function(ctx, map_merge_sig, map_merge, env); - register_function(ctx, map_remove_sig, map_remove, env); - register_function(ctx, map_keys_sig, map_keys, env); - register_function(ctx, map_values_sig, map_values, env); - register_function(ctx, map_has_key_sig, map_has_key, env); - register_function(ctx, keywords_sig, keywords, env); - // Introspection Functions - register_function(ctx, type_of_sig, type_of, env); - register_function(ctx, unit_sig, unit, env); - register_function(ctx, unitless_sig, unitless, env); - register_function(ctx, comparable_sig, comparable, env); - register_function(ctx, variable_exists_sig, variable_exists, env); - register_function(ctx, global_variable_exists_sig, global_variable_exists, env); - register_function(ctx, function_exists_sig, function_exists, env); - register_function(ctx, mixin_exists_sig, mixin_exists, env); - register_function(ctx, feature_exists_sig, feature_exists, env); - register_function(ctx, call_sig, call, env); - register_function(ctx, content_exists_sig, content_exists, env); - register_function(ctx, get_function_sig, get_function, env); - // Boolean Functions - register_function(ctx, not_sig, sass_not, env); - register_function(ctx, if_sig, sass_if, env); - // Misc Functions - register_function(ctx, inspect_sig, inspect, env); - register_function(ctx, unique_id_sig, unique_id, env); - // Selector functions - register_function(ctx, selector_nest_sig, selector_nest, env); - register_function(ctx, selector_append_sig, selector_append, env); - register_function(ctx, selector_extend_sig, selector_extend, env); - register_function(ctx, selector_replace_sig, selector_replace, env); - register_function(ctx, selector_unify_sig, selector_unify, env); - register_function(ctx, is_superselector_sig, is_superselector, env); - register_function(ctx, simple_selectors_sig, simple_selectors, env); - register_function(ctx, selector_parse_sig, selector_parse, env); + // Throw error if read has failed + throw Exception::OperationError( + "File to read not found or unreadable."); } + // EO loadImport - void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs) - { - while (descrs && *descrs) { - register_c_function(ctx, env, *descrs); - ++descrs; - } - } - void register_c_function(Context& ctx, Env* env, Sass_Function_Entry descr) - { - Definition* def = make_c_function(descr, ctx); - def->environment(env); - (*env)[def->name() + "[f]"] = def; - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/context.hpp b/src/context.hpp index 29c5fc7d45..8d5a51ef81 100644 --- a/src/context.hpp +++ b/src/context.hpp @@ -1,139 +1,134 @@ -#ifndef SASS_CONTEXT_H -#define SASS_CONTEXT_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" +#ifndef SASS_CONTEXT_HPP +#define SASS_CONTEXT_HPP +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #define BUFFERSIZE 255 #include "b64/encode.h" -#include "sass_context.hpp" -#include "stylesheet.hpp" -#include "plugins.hpp" -#include "output.hpp" +#include +#include "file.hpp" +#include "ast_callables.hpp" + +// #include "output.hpp" +// #include "sass_context.hpp" +// #include "stylesheet.hpp" +// #include "environment_stack.hpp" namespace Sass { - class Context { - public: - void import_url (Import* imp, sass::string load_path, const sass::string& ctx_path); - bool call_headers(const sass::string& load_path, const char* ctx_path, SourceSpan& pstate, Import* imp) - { return call_loader(load_path, ctx_path, pstate, imp, c_headers, false); }; - bool call_importers(const sass::string& load_path, const char* ctx_path, SourceSpan& pstate, Import* imp) - { return call_loader(load_path, ctx_path, pstate, imp, c_importers, true); }; + // Compiler is stateful, while Context is more low-level + class Context : public SassOutputOptionsCpp { private: - bool call_loader(const sass::string& load_path, const char* ctx_path, SourceSpan& pstate, Import* imp, sass::vector importers, bool only_one = true); - public: - const sass::string CWD; - struct Sass_Options& c_options; - sass::string entry_path; - size_t head_imports; - Plugins plugins; - Output emitter; - - // generic ast node garbage container - // used to avoid possible circular refs - CallStack ast_gc; - // resources add under our control - // these are guaranteed to be freed - sass::vector strings; - sass::vector resources; - std::map sheets; - ImporterStack import_stack; - sass::vector callee_stack; - sass::vector traces; - Extender extender; - - struct Sass_Compiler* c_compiler; - - // absolute paths to includes - sass::vector included_files; - // relative includes for sourcemap - sass::vector srcmap_links; - // vectors above have same size - - sass::vector plugin_paths; // relative paths to load plugins - sass::vector include_paths; // lookup paths for includes - - void apply_custom_headers(Block_Obj root, const char* path, SourceSpan pstate); - - sass::vector c_headers; - sass::vector c_importers; - sass::vector c_functions; - - void add_c_header(Sass_Importer_Entry header); - void add_c_importer(Sass_Importer_Entry importer); - void add_c_function(Sass_Function_Entry function); - - const sass::string indent; // String to be used for indentation - const sass::string linefeed; // String to be used for line feeds - const sass::string input_path; // for relative paths in src-map - const sass::string output_path; // for relative paths to the output - const sass::string source_map_file; // path to source map file (enables feature) - const sass::string source_map_root; // path for sourceRoot property (pass-through) - - virtual ~Context(); - Context(struct Sass_Context&); - virtual Block_Obj parse() = 0; - virtual Block_Obj compile(); - virtual char* render(Block_Obj root); - virtual char* render_srcmap(); - - void register_resource(const Include&, const Resource&); - void register_resource(const Include&, const Resource&, SourceSpan&); - sass::vector find_includes(const Importer& import); - Include load_import(const Importer&, SourceSpan pstate); - - Sass_Output_Style output_style() { return c_options.output_style; }; - sass::vector get_included_files(bool skip = false, size_t headers = 0); + // Checking if a file exists can be quite extensive + // Keep an internal map to avoid repeated system calls + std::unordered_map fileExistsCache; - private: - void collect_plugin_paths(const char* paths_str); - void collect_plugin_paths(string_list* paths_array); - void collect_include_paths(const char* paths_str); - void collect_include_paths(string_list* paths_array); - sass::string format_embedded_source_map(); - sass::string format_source_mapping_url(const sass::string& out_path); + // Include paths are local to context since we need to know + // it for lookups during parsing. You may reset this for + // another compilation when reusing the context. + sass::vector includePaths; + protected: - // void register_built_in_functions(Env* env); - // void register_function(Signature sig, Native_Function f, Env* env); - // void register_function(Signature sig, Native_Function f, size_t arity, Env* env); - // void register_overload_stub(sass::string name, Env* env); + // Functions in order of appearance + // Same order needed for function stack + std::vector fnList; - public: - const sass::string& cwd() { return CWD; }; - }; + // Lookup functions by function name + // Due to EnvKayMap case insensitive. + EnvKeyMap fnLookup; - class File_Context : public Context { - public: - File_Context(struct Sass_File_Context& ctx) - : Context(ctx) - { } - virtual ~File_Context(); - virtual Block_Obj parse(); - }; + // Sheets are filled after resources are parsed + // This could be shared, should go to engine!? + // ToDo: should be case insensitive on windows? + std::map sheets; + + // Only used to cache `loadImport` calls + std::map sources; + + // Additional C-API stuff for interaction + sass::vector cHeaders; + sass::vector cImporters; + sass::vector cFunctions; - class Data_Context : public Context { public: - char* source_c_str; - char* srcmap_c_str; - Data_Context(struct Sass_Data_Context& ctx) - : Context(ctx) - { - source_c_str = ctx.source_string; - srcmap_c_str = ctx.srcmap_string; - ctx.source_string = 0; // passed away - ctx.srcmap_string = 0; // passed away + + // Stack of environment frames. New frames are appended + // when parser encounters a new environment scoping. + sass::vector varStack; + + // The root environment where parsed root variables + // and (custom) functions plus mixins are registered. + EnvRoot varRoot; // Must be after varStack! + + // The import stack during evaluation phase + sass::vector import_stack; + + // List of all sources that have been included + sass::vector included_sources; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Constructor + Context(); + + ///////////////////////////////////////////////////////////////////////// + // Helpers for `sass_prepare_context` + ///////////////////////////////////////////////////////////////////////// + + void addCustomHeader(struct SassImporter* header); + void addCustomImporter(struct SassImporter* importer); + void addCustomFunction(struct SassFunction* function); + + ///////////////////////////////////////////////////////////////////////// + // Some simple delegations to variable root for runtime queries + ///////////////////////////////////////////////////////////////////////// + + // Get the value object for the variable by [name] on runtime. + // If [global] flag is given, we + ValueObj getVariable(const EnvKey& name, bool global = false) { + return varRoot.getVariable(name, global); + } + + void setVariable(const EnvKey& name, ValueObj val, bool global = false) { + return varRoot.setVariable(name, val, global); } - virtual ~Data_Context(); - virtual Block_Obj parse(); + + // Functions only for evaluation phase (C-API functions and eval itself) + CallableObj getMixin(const EnvKey& name) { return varRoot.getMixin(name); } + CallableObj getFunction(const EnvKey& name) { return varRoot.getFunction(name); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Load plugins from path, which can be path separated + void loadPlugins(const sass::string& paths); + + // Add additional include paths, which can be path separated + void addIncludePaths(const sass::string& paths); + + // Load import from the file-system and create source object + // Results will be stored at `sources[source->getAbsPath()]` + Import* loadImport(const ResolvedImport& import); + + // Implementation for `sass_compiler_find_file` + // Looks for the file in regard to the current + // import and then looks in all include folders. + sass::string findFile(const sass::string& path); + + // Implementation for `resolveDynamicImport` + // Look for all possible filename variants (e.g. partials) + // Returns all results (e.g. for ambiguous but valid imports) + sass::vector findIncludes(const ImportRequest& import); + }; + // EO Context } diff --git a/src/cssize.cpp b/src/cssize.cpp index a651186eec..557efc5006 100644 --- a/src/cssize.cpp +++ b/src/cssize.cpp @@ -1,521 +1,62 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include - #include "cssize.hpp" -#include "context.hpp" - -namespace Sass { - - Cssize::Cssize(Context& ctx) - : traces(ctx.traces), - block_stack(BlockStack()), - p_stack(sass::vector()) - { } - - Statement* Cssize::parent() - { - return p_stack.size() ? p_stack.back() : block_stack.front(); - } - - Block* Cssize::operator()(Block* b) - { - Block_Obj bb = SASS_MEMORY_NEW(Block, b->pstate(), b->length(), b->is_root()); - // bb->tabs(b->tabs()); - block_stack.push_back(bb); - append_block(b, bb); - block_stack.pop_back(); - return bb.detach(); - } - - Statement* Cssize::operator()(Trace* t) - { - traces.push_back(Backtrace(t->pstate())); - auto result = t->block()->perform(this); - traces.pop_back(); - return result; - } - - Statement* Cssize::operator()(Declaration* d) - { - String_Obj property = Cast(d->property()); - - if (Declaration* dd = Cast(parent())) { - String_Obj parent_property = Cast(dd->property()); - property = SASS_MEMORY_NEW(String_Constant, - d->property()->pstate(), - parent_property->to_string() + "-" + property->to_string()); - if (!dd->value()) { - d->tabs(dd->tabs() + 1); - } - } - - Declaration_Obj dd = SASS_MEMORY_NEW(Declaration, - d->pstate(), - property, - d->value(), - d->is_important(), - d->is_custom_property()); - dd->is_indented(d->is_indented()); - dd->tabs(d->tabs()); - - p_stack.push_back(dd); - Block_Obj bb = d->block() ? operator()(d->block()) : NULL; - p_stack.pop_back(); - - if (bb && bb->length()) { - if (dd->value() && !dd->value()->is_invisible()) { - bb->unshift(dd); - } - return bb.detach(); - } - else if (dd->value() && !dd->value()->is_invisible()) { - return dd.detach(); - } - - return 0; - } - - Statement* Cssize::operator()(AtRule* r) - { - if (!r->block() || !r->block()->length()) return r; - - if (parent()->statement_type() == Statement::RULESET) - { - return r->is_keyframes() ? SASS_MEMORY_NEW(Bubble, r->pstate(), r) : bubble(r); - } - - p_stack.push_back(r); - AtRuleObj rr = SASS_MEMORY_NEW(AtRule, - r->pstate(), - r->keyword(), - r->selector(), - r->block() ? operator()(r->block()) : 0); - if (r->value()) rr->value(r->value()); - p_stack.pop_back(); - - bool directive_exists = false; - size_t L = rr->block() ? rr->block()->length() : 0; - for (size_t i = 0; i < L && !directive_exists; ++i) { - Statement_Obj s = r->block()->at(i); - if (s->statement_type() != Statement::BUBBLE) directive_exists = true; - else { - Bubble_Obj s_obj = Cast(s); - s = s_obj->node(); - if (s->statement_type() != Statement::DIRECTIVE) directive_exists = false; - else directive_exists = (Cast(s)->keyword() == rr->keyword()); - } - - } - - Block* result = SASS_MEMORY_NEW(Block, rr->pstate()); - if (!(directive_exists || rr->is_keyframes())) - { - AtRule* empty_node = Cast(rr); - empty_node->block(SASS_MEMORY_NEW(Block, rr->block() ? rr->block()->pstate() : rr->pstate())); - result->append(empty_node); - } - - Block_Obj db = rr->block(); - if (db.isNull()) db = SASS_MEMORY_NEW(Block, rr->pstate()); - Block_Obj ss = debubble(db, rr); - for (size_t i = 0, L = ss->length(); i < L; ++i) { - result->append(ss->at(i)); - } - - return result; - } - - Statement* Cssize::operator()(Keyframe_Rule* r) - { - if (!r->block() || !r->block()->length()) return r; - - Keyframe_Rule_Obj rr = SASS_MEMORY_NEW(Keyframe_Rule, - r->pstate(), - operator()(r->block())); - if (!r->name().isNull()) rr->name(r->name()); - - return debubble(rr->block(), rr); - } - - Statement* Cssize::operator()(StyleRule* r) - { - p_stack.push_back(r); - // this can return a string schema - // string schema is not a statement! - // r->block() is already a string schema - // and that is coming from propset expand - Block* bb = operator()(r->block()); - // this should protect us (at least a bit) from our mess - // fixing this properly is harder that it should be ... - if (Cast(bb) == NULL) { - error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); - } - StyleRuleObj rr = SASS_MEMORY_NEW(StyleRule, - r->pstate(), - r->selector(), - bb); - - rr->is_root(r->is_root()); - // rr->tabs(r->block()->tabs()); - p_stack.pop_back(); - if (!rr->block()) { - error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); - } - - Block_Obj props = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - Block* rules = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - for (size_t i = 0, L = rr->block()->length(); i < L; i++) - { - Statement* s = rr->block()->at(i); - if (bubblable(s)) rules->append(s); - if (!bubblable(s)) props->append(s); - } - - if (props->length()) - { - Block_Obj pb = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - pb->concat(props); - rr->block(pb); - - for (size_t i = 0, L = rules->length(); i < L; i++) - { - Statement* stm = rules->at(i); - stm->tabs(stm->tabs() + 1); - } - - rules->unshift(rr); - } - - Block* ptr = rules; - rules = debubble(rules); - void* lp = ptr; - void* rp = rules; - if (lp != rp) { - Block_Obj obj = ptr; - } - - if (!(!rules->length() || - !bubblable(rules->last()) || - parent()->statement_type() == Statement::RULESET)) - { - rules->last()->group_end(true); - } - return rules; - } +#include "charcode.hpp" +#include "character.hpp" +#include "ast_values.hpp" +#include "exceptions.hpp" - Statement* Cssize::operator()(Null* m) - { - return 0; - } - - Statement* Cssize::operator()(CssMediaRule* m) - { - if (parent()->statement_type() == Statement::RULESET) - { - return bubble(m); - } - - if (parent()->statement_type() == Statement::MEDIA) - { - return SASS_MEMORY_NEW(Bubble, m->pstate(), m); - } - - p_stack.push_back(m); - - CssMediaRuleObj mm = SASS_MEMORY_NEW(CssMediaRule, m->pstate(), m->block()); - mm->concat(m->elements()); - mm->block(operator()(m->block())); - mm->tabs(m->tabs()); - - p_stack.pop_back(); +namespace Sass { - return debubble(mm->block(), mm); - } + // Import some namespaces + using namespace Charcode; + using namespace Character; - Statement* Cssize::operator()(SupportsRule* m) + void Cssize::visitFunction(Function* f) { - if (!m->block()->length()) - { return m; } - - if (parent()->statement_type() == Statement::RULESET) - { return bubble(m); } - - p_stack.push_back(m); - - SupportsRuleObj mm = SASS_MEMORY_NEW(SupportsRule, - m->pstate(), - m->condition(), - operator()(m->block())); - mm->tabs(m->tabs()); - - p_stack.pop_back(); - - return debubble(mm->block(), mm); + throw Exception::InvalidCssValue({}, *f); } - Statement* Cssize::operator()(AtRootRule* m) + void Cssize::visitMap(Map* value) { - bool tmp = false; - for (size_t i = 0, L = p_stack.size(); i < L; ++i) { - Statement* s = p_stack[i]; - tmp |= m->exclude_node(s); - } - - if (!tmp && m->block()) - { - Block* bb = operator()(m->block()); - for (size_t i = 0, L = bb->length(); i < L; ++i) { - // (bb->elements())[i]->tabs(m->tabs()); - Statement_Obj stm = bb->at(i); - if (bubblable(stm)) stm->tabs(stm->tabs() + m->tabs()); - } - if (bb->length() && bubblable(bb->last())) bb->last()->group_end(m->group_end()); - return bb; - } - - if (m->exclude_node(parent())) - { - return SASS_MEMORY_NEW(Bubble, m->pstate(), m); + if (output_style() == SASS_STYLE_TO_CSS) { + // should be handle in check_expression + throw Exception::InvalidCssValue({}, *value); } + if (value->empty()) return; - return bubble(m); - } - - Statement* Cssize::bubble(AtRule* m) - { - Block* bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); - ParentStatementObj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); - new_rule->block(bb); - new_rule->tabs(this->parent()->tabs()); - new_rule->block()->concat(m->block()); - - Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, m->block() ? m->block()->pstate() : m->pstate()); - wrapper_block->append(new_rule); - AtRuleObj mm = SASS_MEMORY_NEW(AtRule, - m->pstate(), - m->keyword(), - m->selector(), - wrapper_block); - if (m->value()) mm->value(m->value()); - - Bubble* bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - return bubble; + Inspect::visitMap(value); } - Statement* Cssize::bubble(AtRootRule* m) + void Cssize::visitList(List* list) { - if (!m || !m->block()) return NULL; - Block* bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); - ParentStatementObj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); - Block* wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); - if (new_rule) { - new_rule->block(bb); - new_rule->tabs(this->parent()->tabs()); - new_rule->block()->concat(m->block()); - wrapper_block->append(new_rule); + if (list->empty() && !list->hasBrackets()) { + throw Exception::InvalidCssValue({}, *list); } - - AtRootRule* mm = SASS_MEMORY_NEW(AtRootRule, - m->pstate(), - wrapper_block, - m->expression()); - Bubble* bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - return bubble; - } - - Statement* Cssize::bubble(SupportsRule* m) - { - StyleRuleObj parent = Cast(SASS_MEMORY_COPY(this->parent())); - - Block* bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); - StyleRule* new_rule = SASS_MEMORY_NEW(StyleRule, - parent->pstate(), - parent->selector(), - bb); - new_rule->tabs(parent->tabs()); - new_rule->block()->concat(m->block()); - - Block* wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); - wrapper_block->append(new_rule); - SupportsRule* mm = SASS_MEMORY_NEW(SupportsRule, - m->pstate(), - m->condition(), - wrapper_block); - - mm->tabs(m->tabs()); - - Bubble* bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - return bubble; + Inspect::visitList(list); } - Statement* Cssize::bubble(CssMediaRule* m) + void Cssize::visitNumber(Number* n) { - StyleRuleObj parent = Cast(SASS_MEMORY_COPY(this->parent())); - - Block* bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); - StyleRule* new_rule = SASS_MEMORY_NEW(StyleRule, - parent->pstate(), - parent->selector(), - bb); - new_rule->tabs(parent->tabs()); - new_rule->block()->concat(m->block()); - - Block* wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); - wrapper_block->append(new_rule); - CssMediaRuleObj mm = SASS_MEMORY_NEW(CssMediaRule, - m->pstate(), - wrapper_block); - mm->concat(m->elements()); - mm->tabs(m->tabs()); - - return SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - } - - bool Cssize::bubblable(Statement* s) - { - return Cast(s) || (s && s->bubbles()); - } - - Block* Cssize::flatten(const Block* b) - { - Block* result = SASS_MEMORY_NEW(Block, b->pstate(), 0, b->is_root()); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* ss = b->at(i); - if (const Block* bb = Cast(ss)) { - Block_Obj bs = flatten(bb); - for (size_t j = 0, K = bs->length(); j < K; ++j) { - result->append(bs->at(j)); - } - } - else { - result->append(ss); - } + if (n->lhsAsSlash() && n->rhsAsSlash()) { + n->lhsAsSlash()->accept(this); + append_string("/"); + n->rhsAsSlash()->accept(this); + return; } - return result; - } - sass::vector> Cssize::slice_by_bubble(Block* b) - { - sass::vector> results; - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj value = b->at(i); - bool key = Cast(value) != NULL; + // reduce units + n->reduce(); - if (!results.empty() && results.back().first == key) - { - Block_Obj wrapper_block = results.back().second; - wrapper_block->append(value); - } - else - { - Block* wrapper_block = SASS_MEMORY_NEW(Block, value->pstate()); - wrapper_block->append(value); - results.push_back(std::make_pair(key, wrapper_block)); - } + if (opt.output_style == SASS_STYLE_TO_CSS && !n->isValidCssUnit()) { + // traces.push_back(BackTrace(nr->pstate())); + // issue_1804 + throw Exception::InvalidCssValue({}, *n); } - return results; - } - - Block* Cssize::debubble(Block* children, Statement* parent) - { - ParentStatementObj previous_parent; - sass::vector> baz = slice_by_bubble(children); - Block_Obj result = SASS_MEMORY_NEW(Block, children->pstate()); - - for (size_t i = 0, L = baz.size(); i < L; ++i) { - bool is_bubble = baz[i].first; - Block_Obj slice = baz[i].second; - - if (!is_bubble) { - if (!parent) { - result->append(slice); - } - else if (previous_parent) { - previous_parent->block()->concat(slice); - } - else { - previous_parent = SASS_MEMORY_COPY(parent); - previous_parent->block(slice); - previous_parent->tabs(parent->tabs()); - - result->append(previous_parent); - } - continue; - } - for (size_t j = 0, K = slice->length(); j < K; ++j) - { - Statement_Obj ss; - Statement_Obj stm = slice->at(j); - // this has to go now here (too bad) - Bubble_Obj node = Cast(stm); + Inspect::visitNumber(n); - CssMediaRule* rule1 = NULL; - CssMediaRule* rule2 = NULL; - if (parent) rule1 = Cast(parent); - if (node) rule2 = Cast(node->node()); - if (rule1 || rule2) { - ss = node->node(); - } - - ss = node->node(); - - if (!ss) { - continue; - } - - ss->tabs(ss->tabs() + node->tabs()); - ss->group_end(node->group_end()); - - Block_Obj bb = SASS_MEMORY_NEW(Block, - children->pstate(), - children->length(), - children->is_root()); - auto evaled = ss->perform(this); - if (evaled) bb->append(evaled); - - Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, - children->pstate(), - children->length(), - children->is_root()); - - Block* wrapper = flatten(bb); - wrapper_block->append(wrapper); - - if (wrapper->length()) { - previous_parent = {}; - } - - if (wrapper_block) { - result->append(wrapper_block); - } - } - } - - return flatten(result); - } - - void Cssize::append_block(Block* b, Block* cur) - { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj ith = b->at(i)->perform(this); - if (Block_Obj bb = Cast(ith)) { - for (size_t j = 0, K = bb->length(); j < K; ++j) { - cur->append(bb->at(j)); - } - } - else if (ith) { - cur->append(ith); - } - } } } + diff --git a/src/cssize.hpp b/src/cssize.hpp index 4cb13d79c4..d1f04c2913 100644 --- a/src/cssize.hpp +++ b/src/cssize.hpp @@ -1,69 +1,36 @@ -#ifndef SASS_CSSIZE_H -#define SASS_CSSIZE_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CSSIZE_HPP +#define SASS_CSSIZE_HPP -#include "ast.hpp" -#include "context.hpp" -#include "operation.hpp" -#include "environment.hpp" +#include "inspect.hpp" namespace Sass { - struct Backtrace; - - class Cssize : public Operation_CRTP { - - Backtraces& traces; - BlockStack block_stack; - sass::vector p_stack; - + class Cssize : public Inspect { public: - Cssize(Context&); - ~Cssize() { } - Block* operator()(Block*); - Statement* operator()(StyleRule*); - // Statement* operator()(Bubble*); - Statement* operator()(CssMediaRule*); - Statement* operator()(SupportsRule*); - Statement* operator()(AtRootRule*); - Statement* operator()(AtRule*); - Statement* operator()(Keyframe_Rule*); - Statement* operator()(Trace*); - Statement* operator()(Declaration*); - // Statement* operator()(Assignment*); - // Statement* operator()(Import*); - // Statement* operator()(Import_Stub*); - // Statement* operator()(WarningRule*); - // Statement* operator()(Error*); - // Statement* operator()(Comment*); - // Statement* operator()(If*); - // Statement* operator()(ForRule*); - // Statement* operator()(EachRule*); - // Statement* operator()(WhileRule*); - // Statement* operator()(Return*); - // Statement* operator()(ExtendRule*); - // Statement* operator()(Definition*); - // Statement* operator()(Mixin_Call*); - // Statement* operator()(Content*); - Statement* operator()(Null*); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Cssize( + SassOutputOptionsCpp& opt, + bool srcmap_enabled) : + Inspect(opt, srcmap_enabled) + {} - Statement* parent(); - sass::vector> slice_by_bubble(Block*); - Statement* bubble(AtRule*); - Statement* bubble(AtRootRule*); - Statement* bubble(CssMediaRule*); - Statement* bubble(SupportsRule*); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - Block* debubble(Block* children, Statement* parent = 0); - Block* flatten(const Block*); - bool bubblable(Statement*); + virtual void visitFunction(Function*) override; + virtual void visitNumber(Number*) override; + virtual void visitList(List*) override; + virtual void visitMap(Map*) override; - // generic fallback - template - Statement* fallback(U x) - { return Cast(x); } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - void append_block(Block*, Block*); }; } diff --git a/src/dart_helpers.hpp b/src/dart_helpers.hpp index 28b79af610..c741de701e 100644 --- a/src/dart_helpers.hpp +++ b/src/dart_helpers.hpp @@ -5,12 +5,46 @@ #include #include #include +#include "ast_helpers.hpp" namespace Sass { - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + // Returns a new list containing the elements between [start] and [end]. + ///////////////////////////////////////////////////////////////////////// + template + sass::vector sublist(const sass::vector& vec, + size_t start, size_t end = sass::string::npos) + { + if (end == sass::string::npos) { end = vec.size(); } + return sass::vector(vec.begin() + start, vec.begin() + end); + } + + ///////////////////////////////////////////////////////////////////////// + // Removes the objects in the range [start] inclusive to [end] exclusive. + ///////////////////////////////////////////////////////////////////////// + template + void removeRange(sass::vector& vec, + size_t start, size_t end = sass::string::npos) + { + if (end == sass::string::npos) { end = vec.size(); } + vec.erase(vec.begin() + start, vec.begin() + end); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + template + size_t indexOf(const sass::vector& vec, const V& item) + { + for (size_t i = 0; i < vec.size(); i += 1) { + if (ObjEqualityFn(vec[i], item)) return i; + } + return sass::string::npos; + } + + ///////////////////////////////////////////////////////////////////////// // Flatten `vector>` to `vector` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// template T flatten(const sass::vector& all) { @@ -22,12 +56,12 @@ namespace Sass { return flattened; } - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Expands each element of this Iterable into zero or more elements. // Calls a function on every element and ads all results to flat array - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Equivalent to dart `cnt.any` - // Pass additional closure variables to `fn` + // Passes additional closure variables to `fn` template T expand(const T& cnt, U fn, Args... args) { T flattened; @@ -39,8 +73,8 @@ namespace Sass { return flattened; } - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// template T flattenInner(const sass::vector& vec) { @@ -52,10 +86,41 @@ namespace Sass { } // EO flattenInner - // ########################################################################## + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + template + sass::vector flattenVertically(sass::vector> lists) + { + sass::vector result; + // Loop until all arrays are exhausted + size_t lvl = 0; bool consumed = false; do { + // aborts when nothing more can be consumed + consumed = false; + // loop over all arrays at the 1st level + for (size_t i = 0, iL = lists.size(); i < iL; ++i) { + // check for items to consume at 2nd level + if (lists[i].size() > lvl) { + // consume item at 2nd level depth + result.emplace_back(lists[i][lvl]); + // maybe we have some more + consumed = true; + } + } + // check next depth level + ++lvl; + } + // abort once nothing is consumed + while (consumed); + // return flat list + return result; + } + // EO flatVertically + + ///////////////////////////////////////////////////////////////////////// // Equivalent to dart `cnt.any` - // Pass additional closure variables to `fn` - // ########################################################################## + // Passes additional closure variables to `fn` + ///////////////////////////////////////////////////////////////////////// template bool hasAny(const T& cnt, U fn, Args... args) { for (const auto& sub : cnt) { @@ -67,10 +132,10 @@ namespace Sass { } // EO hasAny - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Equivalent to dart `cnt.take(len).any` - // Pass additional closure variables to `fn` - // ########################################################################## + // Passes additional closure variables to `fn` + ///////////////////////////////////////////////////////////////////////// template bool hasSubAny(const T& cnt, size_t len, U fn, Args... args) { for (size_t i = 0; i < len; i++) { @@ -81,9 +146,9 @@ namespace Sass { return false; } - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Default predicate for lcs algorithm - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// template inline bool lcsIdentityCmp(const T& X, const T& Y, T& result) { @@ -98,9 +163,9 @@ namespace Sass { } // EO lcsIdentityCmp - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Longest common subsequence with predicate - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// template sass::vector lcs( const sass::vector& X, const sass::vector& Y, @@ -117,9 +182,9 @@ namespace Sass { // To circumvent, allocate one array on the heap // Then use a macro to access via double index // e.g. `size_t L[m][n]` is supported by gcc - size_t* len = new size_t[mm * nn + 1]; - bool* acc = new bool[mm * nn + 1]; - T* res = new T[mm * nn + 1]; + size_t* len = new size_t[mm * nn + 8]; + bool* acc = new bool[mm * nn + 8]{}; + T* res = new T[mm * nn + 8]; #define LEN(x, y) len[(x) * nn + (y)] #define ACC(x, y) acc[(x) * nn + (y)] @@ -159,7 +224,7 @@ namespace Sass { // Note: we push instead of unshift // Note: reverse the vector later // ToDo: is deque more performant? - lcs.push_back(RES(i - 1, j - 1)); + lcs.emplace_back(RES(i - 1, j - 1)); // reduce values of i, j and index i -= 1; j -= 1; index -= 1; } @@ -175,7 +240,7 @@ namespace Sass { } - // reverse now as we used push_back + // reverse now as we used emplace_back std::reverse(lcs.begin(), lcs.end()); // Delete temp memory on heap @@ -191,8 +256,8 @@ namespace Sass { } // EO lcs - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/debug.hpp b/src/debug.hpp deleted file mode 100644 index 43fe05e67e..0000000000 --- a/src/debug.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef SASS_DEBUG_H -#define SASS_DEBUG_H - -#include - -#ifndef UINT32_MAX - #define UINT32_MAX 0xffffffffU -#endif - -enum dbg_lvl_t : uint32_t { - NONE = 0, - TRIM = 1, - CHUNKS = 2, - SUBWEAVE = 4, - WEAVE = 8, - EXTEND_COMPOUND = 16, - EXTEND_COMPLEX = 32, - LCS = 64, - EXTEND_OBJECT = 128, - ALL = UINT32_MAX -}; - -#ifdef DEBUG - -#ifndef DEBUG_LVL -const uint32_t debug_lvl = UINT32_MAX; -#else -const uint32_t debug_lvl = (DEBUG_LVL); -#endif // DEBUG_LVL - -#define DEBUG_PRINT(lvl, x) if((lvl) & debug_lvl) { std::cerr << x; } -#define DEBUG_PRINTLN(lvl, x) if((lvl) & debug_lvl) { std::cerr << x << std::endl; } -#define DEBUG_EXEC(lvl, x) if((lvl) & debug_lvl) { x; } - -#else // DEBUG - -#define DEBUG_PRINT(lvl, x) -#define DEBUG_PRINTLN(lvl, x) -#define DEBUG_EXEC(lvl, x) - -#endif // DEBUG - -#endif // SASS_DEBUG diff --git a/src/debugger.hpp b/src/debugger.hpp index 703d387183..0010308d55 100644 --- a/src/debugger.hpp +++ b/src/debugger.hpp @@ -1,9 +1,9 @@ -#ifndef SASS_DEBUGGER_H -#define SASS_DEBUGGER_H +#ifndef SASS_DEBUGGER_HPP +#define SASS_DEBUGGER_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include #include @@ -11,21 +11,39 @@ #include #include "ast.hpp" #include "ast_fwd_decl.hpp" +#include "extender.hpp" #include "extension.hpp" #include "ordered_map.hpp" using namespace Sass; -inline void debug_ast(AST_Node* node, sass::string ind = "", Env* env = 0); +inline void debug_ast(AstNode* node, std::string ind = ""); -inline sass::string debug_vec(const AST_Node* node) { - if (node == NULL) return "null"; - else return node->to_string(); +inline sass::string ns_name(NameSpaceSelector* s) +{ + if (!s->hasNs()) return s->name(); + else return s->ns() + "|" + s->name(); } -inline sass::string debug_dude(sass::vector> vec) { - sass::sstream out; +inline std::string debug_pstate(SourceSpan pstate) { + std::stringstream str; + str << pstate.getLine() << ":"; + str << pstate.getColumn() << " - "; + return str.str(); +} + +inline sass::string dbgValStr(CssString* str) { + return str ? str->text() : "{nullptr}"; +} + +// inline sass::string debug_vec(const AstNode* node) { +// if (node == NULL) return "null"; +// else return node->inspect(); +// } + +inline std::string debug_dude(sass::vector> vec) { + std::stringstream out; out << "{"; bool joinOut = false; for (auto ct : vec) { @@ -44,25 +62,27 @@ inline sass::string debug_dude(sass::vector> vec) { return out.str(); } -inline sass::string debug_vec(sass::string& str) { +inline std::string debug_vec(std::string& str) { return str; } -inline sass::string debug_vec(Extension& ext) { - sass::sstream out; - out << debug_vec(ext.extender); - out << " {@extend "; - out << debug_vec(ext.target); - if (ext.isOptional) { - out << " !optional"; - } - out << "}"; + +inline std::string debug_vec(EnvKey key) { + return key.norm().c_str(); +} + +inline std::string debug_vec(sass::vector vec) { + std::stringstream out; + out << "["; + SelectorListObj slist = SASS_MEMORY_NEW(SelectorList, SourceSpan::tmp("DBG"), std::move(vec)); + out << slist->inspect(); + out << "]"; return out.str(); } template -inline sass::string debug_vec(sass::vector vec) { - sass::sstream out; +inline std::string debug_vec(const sass::vector& vec) { + std::stringstream out; out << "["; for (size_t i = 0; i < vec.size(); i += 1) { if (i > 0) out << ", "; @@ -73,8 +93,8 @@ inline sass::string debug_vec(sass::vector vec) { } template -inline sass::string debug_vec(std::queue vec) { - sass::sstream out; +inline std::string debug_vec(std::queue vec) { + std::stringstream out; out << "{"; for (size_t i = 0; i < vec.size(); i += 1) { if (i > 0) out << ", "; @@ -85,8 +105,8 @@ inline sass::string debug_vec(std::queue vec) { } template -inline sass::string debug_vec(std::map vec) { - sass::sstream out; +inline std::string debug_vec(std::map vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -102,8 +122,8 @@ inline sass::string debug_vec(std::map vec) { } template -inline sass::string debug_vec(const ordered_map& vec) { - sass::sstream out; +inline std::string debug_vec(const ordered_map& vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -118,8 +138,8 @@ inline sass::string debug_vec(const ordered_map& vec) { } template -inline sass::string debug_vec(std::unordered_map vec) { - sass::sstream out; +inline std::string debug_vec(std::unordered_map vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -135,8 +155,8 @@ inline sass::string debug_vec(std::unordered_map vec) { } template -inline sass::string debug_keys(std::unordered_map vec) { - sass::sstream out; +inline std::string debug_keys(std::unordered_map vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -149,14 +169,14 @@ inline sass::string debug_keys(std::unordered_map vec) { return out.str(); } -inline sass::string debug_vec(ExtListSelSet& vec) { - sass::sstream out; +inline std::string debug_vec(ExtListSelSet& vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) { if (joinit) out << ", "; - out << debug_vec(*it); // string (key) + // out << debug_vec(*it); // string (key) joinit = true; } out << "}"; @@ -165,8 +185,8 @@ inline sass::string debug_vec(ExtListSelSet& vec) { /* template -inline sass::string debug_values(tsl::ordered_map vec) { - sass::sstream out; +inline std::string debug_values(tsl::ordered_map vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -178,10 +198,10 @@ inline sass::string debug_values(tsl::ordered_map vec) { out << "}"; return out.str(); } - + template -inline sass::string debug_vec(tsl::ordered_map vec) { - sass::sstream out; +inline std::string debug_vec(tsl::ordered_map vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -197,8 +217,8 @@ inline sass::string debug_vec(tsl::ordered_map vec) { } template -inline sass::string debug_vals(tsl::ordered_map vec) { - sass::sstream out; +inline std::string debug_vals(tsl::ordered_map vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -212,8 +232,8 @@ inline sass::string debug_vals(tsl::ordered_map vec) { } template -inline sass::string debug_keys(tsl::ordered_map vec) { - sass::sstream out; +inline std::string debug_keys(tsl::ordered_map vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -228,8 +248,8 @@ inline sass::string debug_keys(tsl::ordered_map vec) { */ template -inline sass::string debug_vec(std::set vec) { - sass::sstream out; +inline std::string debug_vec(std::set vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto item : vec) { @@ -243,8 +263,8 @@ inline sass::string debug_vec(std::set vec) { /* template -inline sass::string debug_vec(tsl::ordered_set vec) { - sass::sstream out; +inline std::string debug_vec(tsl::ordered_set vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto item : vec) { @@ -258,8 +278,8 @@ inline sass::string debug_vec(tsl::ordered_set vec) { */ template -inline sass::string debug_vec(std::unordered_set vec) { - sass::sstream out; +inline std::string debug_vec(std::unordered_set vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto item : vec) { @@ -271,25 +291,26 @@ inline sass::string debug_vec(std::unordered_set vec) { return out.str(); } -inline sass::string debug_bool(bool val) { +inline std::string debug_bool(bool val) { return val ? "true" : "false"; } -inline sass::string debug_vec(ExtSmplSelSet* node) { - if (node == NULL) return "null"; - else return debug_vec(*node); +inline std::string debug_vec(ExtSmplSelSet* node) { + // if (node == NULL) return "null"; + // else return debug_vec(*node); + return ""; } -inline void debug_ast(const AST_Node* node, sass::string ind = "", Env* env = 0) { - debug_ast(const_cast(node), ind, env); +inline void debug_ast(const AstNode* node, std::string ind = "") { + debug_ast(const_cast(node), ind); } inline sass::string str_replace(sass::string str, const sass::string& oldStr, const sass::string& newStr) { size_t pos = 0; - while((pos = str.find(oldStr, pos)) != sass::string::npos) + while ((pos = str.find(oldStr, pos)) != std::string::npos) { - str.replace(pos, oldStr.length(), newStr); - pos += newStr.length(); + str.replace(pos, oldStr.length(), newStr); + pos += newStr.length(); } return str; } @@ -301,233 +322,329 @@ inline sass::string prettyprint(const sass::string& str) { return clean; } -inline sass::string longToHex(long long t) { - sass::sstream is; +inline std::string longToHex(long long t) { + std::stringstream is; is << std::hex << t; return is.str(); } -inline sass::string pstate_source_position(AST_Node* node) +inline std::string parent_block(VarRefs* node) { - sass::sstream str; - Offset start(node->pstate().position); - Offset end(start + node->pstate().offset); - size_t file = node->pstate().getSrcId(); - str << (file == sass::string::npos ? 99999999 : file) - << "@[" << start.line << ":" << start.column << "]" - << "-[" << end.line << ":" << end.column << "]"; + std::stringstream str; + if (!node) return ""; + str << "SG: " << (node->permeable ? "true" : "false"); + return str.str(); +} + +inline std::string pstate_source_position(AstNode* node) +{ + std::stringstream str; + SourceSpan pstate(node->pstate()); + if (pstate.getSource()) { + str << (pstate.getSrcIdx() == std::string::npos ? 9999999 : pstate.getSrcIdx()) + << "@[" << (pstate.position.line) << ":" << (pstate.position.column) << "]" + << "+[" << (pstate.span.line) << ":" << (pstate.span.column) << "]" + << "X" << node->refcount; + } + else { + str << "[NOSRC]"; + } #ifdef DEBUG_SHARED_PTR - str << "x" << node->getRefCount() << "" - << " " << node->getDbgFile() - << "@" << node->getDbgLine(); + str << "x" << node->getRefCount() << "" + << " {#" << node->objId << "}" + << " " << node->getDbgFile() + << "@" << node->getDbgLine(); #endif return str.str(); } +inline void debug_block(ParentStatement* node, std::string ind) +{ + for (auto item : node->elements()) { + debug_ast(item, ind); + } +} -inline void debug_ast(AST_Node* node, sass::string ind, Env* env) +inline void debug_block(CssParentNode* node, std::string ind) +{ + for (auto item : node->elements()) { + debug_ast(item, ind); + } +} + +inline void debug_block(Root* node, std::string ind) +{ + for (auto item : node->elements()) { + debug_ast(item, ind); + } +} + +inline void debug_css_parent_node(CssParentNode* rule, std::string ind = "") +{ + std::cerr << ind << "CssParentNode " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << std::endl; + debug_block(rule, ind + " "); +} + +inline void debug_ast(AstNode* node, std::string ind) { if (node == 0) return; if (ind == "") std::cerr << "####################################################################\n"; - if (Cast(node)) { - Bubble* bubble = Cast(node); - std::cerr << ind << "Bubble " << bubble; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << bubble->tabs(); + if (Cast(node)) { + Root* root = Cast(node); + std::cerr << ind << "Root " << root; + std::cerr << " (" << pstate_source_position(root) << ")"; std::cerr << std::endl; - debug_ast(bubble->node(), ind + " ", env); - } else if (Cast(node)) { - Trace* trace = Cast(node); - std::cerr << ind << "Trace " << trace; - std::cerr << " (" << pstate_source_position(node) << ")" - << " [name:" << trace->name() << ", type: " << trace->type() << "]" - << std::endl; - debug_ast(trace->block(), ind + " ", env); - } else if (Cast(node)) { + debug_block(root, ind + " "); + } + else if (Cast(node)) { + CssRoot* root = Cast(node); + std::cerr << ind << "CssRoot " << root; + std::cerr << " (" << pstate_source_position(root) << ")"; + std::cerr << std::endl; + debug_block(root, ind + " "); + } + else if (Cast(node)) { + CssComment* comment = Cast(node); + std::cerr << ind << "CssComment " << comment; + std::cerr << " (" << pstate_source_position(comment) << ")"; + std::cerr << std::endl; + } + else if (Cast(node)) { AtRootRule* root_block = Cast(node); std::cerr << ind << "AtRootRule " << root_block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << root_block->tabs(); std::cerr << std::endl; - debug_ast(root_block->expression(), ind + ":", env); - debug_ast(root_block->block(), ind + " ", env); - } else if (Cast(node)) { + debug_ast(root_block->query(), ind + ":"); + debug_block(root_block, ind + " "); + } + else if (Cast(node)) { + AtRootQuery* query = Cast(node); + std::cerr << ind << "AtRootQuery " << query; + std::cerr << " (" << pstate_source_position(node) << ")"; + // std::cerr << " <" << query->inspect() << ">"; + std::cerr << std::endl; + } + else if (Cast(node)) { SelectorList* selector = Cast(node); std::cerr << ind << "SelectorList " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << (selector->is_invisible() ? " [is_invisible]" : " -"); - std::cerr << (selector->isInvisible() ? " [isInvisible]" : " -"); - std::cerr << (selector->has_real_parent_ref() ? " [real-parent]": " -"); + // std::cerr << (selector->hasInvisible() ? " [hasInvisible]" : " -"); + // std::cerr << (selector->has_real_parent_ref() ? " [real-parent]" : " -"); std::cerr << std::endl; - for(const ComplexSelector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + for (const ComplexSelectorObj& i : selector->elements()) { debug_ast(i, ind + " "); } - } else if (Cast(node)) { + } + else if (Cast(node)) { ComplexSelector* selector = Cast(node); - std::cerr << ind << "ComplexSelector " << selector - << " (" << pstate_source_position(node) << ")" - << " <" << selector->hash() << ">" - << " [" << (selector->chroots() ? "CHROOT" : "CONNECT") << "]" - << " [length:" << longToHex(selector->length()) << "]" - << " [weight:" << longToHex(selector->specificity()) << "]" - << (selector->is_invisible() ? " [is_invisible]" : " -") - << (selector->isInvisible() ? " [isInvisible]" : " -") - << (selector->hasPreLineFeed() ? " [hasPreLineFeed]" : " -") + std::cerr << ind << "ComplexSelector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << (selector->chroots() ? "CHROOT" : "CONNECT") << "]"; + + // << " [" << (selector->hasInvisible() ? "hasInvisible" : "") << "]" + + std::cerr << " [length:" << longToHex(selector->size()) << "]"; + std::cerr << " [weight:" << longToHex(selector->specificity()) << "]"; + // << (selector->hasInvisible() ? " [hasInvisible]" : " -") + std::cerr << (selector->hasPreLineFeed() ? " [hasPreLineFeed]" : " -"); - // << (selector->is_invisible() ? " [INVISIBLE]": " -") // << (selector->has_placeholder() ? " [PLACEHOLDER]": " -") // << (selector->is_optional() ? " [is_optional]": " -") - << (selector->has_real_parent_ref() ? " [real parent]": " -") + // << (selector->has_real_parent_ref() ? " [real parent]" : " -") // << (selector->has_line_feed() ? " [line-feed]": " -") // << (selector->has_line_break() ? " [line-break]": " -") - << " -- \n"; + std::cerr << " -- \n"; - for(const SelectorComponentObj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + for (const SelectorComponentObj& i : selector->elements()) { debug_ast(i, ind + " "); } - } else if (Cast(node)) { + } + else if (Cast(node)) { SelectorCombinator* selector = Cast(node); std::cerr << ind << "SelectorCombinator " << selector << " (" << pstate_source_position(node) << ")" - << " <" << selector->hash() << ">" << " [weight:" << longToHex(selector->specificity()) << "]" - << (selector->has_real_parent_ref() ? " [real parent]": " -") + // << (selector->has_real_parent_ref() ? " [real parent]" : " -") << " -- "; - sass::string del; - switch (selector->combinator()) { - case SelectorCombinator::CHILD: del = ">"; break; - case SelectorCombinator::GENERAL: del = "~"; break; - case SelectorCombinator::ADJACENT: del = "+"; break; - } + std::string del; + switch (selector->combinator()) { + case SelectorCombinator::CHILD: del = ">"; break; + case SelectorCombinator::GENERAL: del = "~"; break; + case SelectorCombinator::ADJACENT: del = "+"; break; + } - std::cerr << "[" << del << "]" << "\n"; + std::cerr << "[" << del << "]" << "\n"; - } else if (Cast(node)) { + } + else if (Cast(node)) { CompoundSelector* selector = Cast(node); std::cerr << ind << "CompoundSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << (selector->hasRealParent() ? " [REAL PARENT]" : "") << ">"; + std::cerr << (selector->withExplicitParent() ? " [EXPLICIT PARENT]" : "") << ">"; std::cerr << " [weight:" << longToHex(selector->specificity()) << "]"; std::cerr << (selector->hasPostLineBreak() ? " [hasPostLineBreak]" : " -"); - std::cerr << (selector->is_invisible() ? " [is_invisible]" : " -"); - std::cerr << (selector->isInvisible() ? " [isInvisible]" : " -"); + // std::cerr << (selector->hasInvisible() ? " [hasInvisible]" : " -"); std::cerr << "\n"; - for(const SimpleSelector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + for (const SimpleSelectorObj& i : selector->elements()) { debug_ast(i, ind + " "); } + + } + else if (Cast(node)) { + ParentExpression* selector = Cast(node); + std::cerr << ind << "ParentExpression " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << std::endl; + } + + else if (Cast(node)) { + BooleanExpression* selector = Cast(node); + std::cerr << ind << "ParentExpression " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << std::endl; + } + + else if (Cast(node)) { + ColorExpression* selector = Cast(node); + std::cerr << ind << "ParentExpression " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << std::endl; + } - } else if (Cast(node)) { - Parent_Reference* selector = Cast(node); - std::cerr << ind << "Parent_Reference " << selector; + else if (Cast(node)) { + NumberExpression* selector = Cast(node); + std::cerr << ind << "NumberExpression " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; std::cerr << std::endl; + } - } else if (Cast(node)) { + else if (Cast(node)) { + NullExpression* selector = Cast(node); + std::cerr << ind << "NullExpression " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << std::endl; + } + else if (Cast(node)) { PseudoSelector* selector = Cast(node); std::cerr << ind << "PseudoSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->isClass() ? " [isClass]": " -"); - std::cerr << (selector->isSyntacticClass() ? " [isSyntacticClass]": " -"); + std::cerr << " <<" << selector->name() << ">>"; + std::cerr << (selector->isClass() ? " [isClass]" : " -"); + std::cerr << (selector->isPseudoElement() ? " [isPseudoElement]" : " -"); + std::cerr << (selector->isSyntacticClass() ? " [isSyntacticClass]" : " -"); + // std::cerr << (selector->hasInvisible() ? " [hasInvisible]" : " -"); std::cerr << std::endl; - debug_ast(selector->argument(), ind + " <= ", env); - debug_ast(selector->selector(), ind + " || ", env); - } else if (Cast(node)) { + std::cerr << ind << " <= " << selector->argument() << std::endl; + debug_ast(selector->selector(), ind + " || "); + } + else if (Cast(node)) { AttributeSelector* selector = Cast(node); std::cerr << ind << "AttributeSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << " <<" << ns_name(selector) << ">>"; std::cerr << std::endl; - debug_ast(selector->value(), ind + "[" + selector->matcher() + "] ", env); - } else if (Cast(node)) { + std::cerr << ind << "[" << selector->op() << "] "; + std::cerr << selector->value() << std::endl; + } + else if (Cast(node)) { ClassSelector* selector = Cast(node); std::cerr << ind << "ClassSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << " <<" << selector->name() << ">>"; std::cerr << std::endl; - } else if (Cast(node)) { + } + else if (Cast(node)) { IDSelector* selector = Cast(node); std::cerr << ind << "IDSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << " <<" << selector->name() << ">>"; std::cerr << std::endl; - } else if (Cast(node)) { + } + else if (Cast(node)) { TypeSelector* selector = Cast(node); std::cerr << ind << "TypeSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << " <<" << ns_name(selector) << ">>"; + // std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">"; std::cerr << std::endl; - } else if (Cast(node)) { + } + else if (Cast(node)) { PlaceholderSelector* selector = Cast(node); - std::cerr << ind << "PlaceholderSelector [" << selector->ns_name() << "] " << selector; + std::cerr << ind << "PlaceholderSelector [" << selector->name() << "] " << selector; std::cerr << " (" << pstate_source_position(selector) << ")" - << " <" << selector->hash() << ">" - << (selector->isInvisible() ? " [isInvisible]" : " -") - << std::endl; + // << (selector->hasInvisible() ? " [hasInvisible]" : " -") + << std::endl; - } else if (Cast(node)) { + } + else if (Cast(node)) { SimpleSelector* selector = Cast(node); std::cerr << ind << "SimpleSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - } else if (Cast(node)) { - Selector_Schema* selector = Cast(node); - std::cerr << ind << "Selector_Schema " << selector; - std::cerr << " (" << pstate_source_position(node) << ")" - << (selector->connect_parent() ? " [connect-parent]": " -") - << std::endl; - - debug_ast(selector->contents(), ind + " "); - // for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } - - } else if (Cast(node)) { + } + else if (Cast(node)) { Selector* selector = Cast(node); std::cerr << ind << "Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; - - } else if (Cast(node)) { - Media_Query_Expression* block = Cast(node); - std::cerr << ind << "Media_Query_Expression " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (block->is_interpolated() ? " [is_interpolated]": " -") - << std::endl; - debug_ast(block->feature(), ind + " feature) "); - debug_ast(block->value(), ind + " value) "); - - } else if (Cast(node)) { - Media_Query* block = Cast(node); - std::cerr << ind << "Media_Query " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (block->is_negated() ? " [is_negated]": " -") - << (block->is_restricted() ? " [is_restricted]": " -") - << std::endl; - debug_ast(block->media_type(), ind + " "); - for(const auto& i : block->elements()) { debug_ast(i, ind + " ", env); } + << std::endl; } + else if (Cast(node)) { + ContentRule* rule = Cast(node); + std::cerr << ind << "ContentRule " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << std::endl; + debug_ast(rule->arguments(), ind + " =@ "); + } + else if (Cast(node)) { + UserDefinedCallable* rule = Cast(node); + std::cerr << ind << "UserDefinedCallable " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << std::endl; + debug_ast(rule->declaration(), ind + " =@ "); + } + else if (Cast(node)) { + ValueExpression* rule = Cast(node); + std::cerr << ind << "ValueExpression " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << std::endl; + debug_ast(rule->value(), ind + " =@ "); + } + else if (Cast(node)) { MediaRule* rule = Cast(node); std::cerr << ind << "MediaRule " << rule; std::cerr << " (" << pstate_source_position(rule) << ")"; std::cerr << " " << rule->tabs() << std::endl; - debug_ast(rule->schema(), ind + " =@ "); - debug_ast(rule->block(), ind + " "); + debug_ast(rule->query(), ind + " =@ "); + debug_block(rule, ind + " "); } else if (Cast(node)) { CssMediaRule* rule = Cast(node); std::cerr << ind << "CssMediaRule " << rule; std::cerr << " (" << pstate_source_position(rule) << ")"; - std::cerr << " " << rule->tabs() << std::endl; + for (auto item : rule->queries()) { + debug_ast(item, ind + "() "); + } for (auto item : rule->elements()) { - debug_ast(item, ind + " == "); + debug_ast(item, ind + " !! "); } - debug_ast(rule->block(), ind + " "); + debug_css_parent_node(rule, ind + " :: "); + } + else if (Cast(node)) { + CssDeclaration* rule = Cast(node); + std::cerr << ind << "CssDeclaration " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << "\n"; + debug_ast(rule->name(), ind + " name: "); + debug_ast(rule->value(), ind + " prop: "); + } + else if (Cast(node)) { + CssString* rule = Cast(node); + std::cerr << ind << "CssString " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << " <" << rule->text() << ">"; + std::cerr << std::endl; } else if (Cast(node)) { CssMediaQuery* query = Cast(node); @@ -537,426 +654,566 @@ inline void debug_ast(AST_Node* node, sass::string ind, Env* env) std::cerr << " [" << (query->type()) << "] "; std::cerr << " " << debug_vec(query->features()); std::cerr << std::endl; - } else if (Cast(node)) { + } + else if (Cast(node)) { + SupportsRule* block = Cast(node); + std::cerr << ind << "SupportsRule " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->condition(), ind + " =@ "); + debug_block(block, ind + " <>"); + std::cerr << std::endl; + } + else if (Cast(node)) + { + CssSupportsRule* block = Cast(node); + std::cerr << ind << "CssSupportsRule " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + debug_ast(block->condition(), ind + " =@ "); + for (auto stmt : block->elements()) { + debug_ast(stmt, ind + " <>"); + } + } + else if (Cast(node)) { SupportsRule* block = Cast(node); std::cerr << ind << "SupportsRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; debug_ast(block->condition(), ind + " =@ "); - debug_ast(block->block(), ind + " <>"); - } else if (Cast(node)) { + debug_block(block, ind + " <>"); + } + else if (Cast(node)) { SupportsOperation* block = Cast(node); std::cerr << ind << "SupportsOperation " << block; std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; + << std::endl; debug_ast(block->left(), ind + " left) "); debug_ast(block->right(), ind + " right) "); - } else if (Cast(node)) { + } + else if (Cast(node)) { SupportsNegation* block = Cast(node); std::cerr << ind << "SupportsNegation " << block; std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; + << std::endl; debug_ast(block->condition(), ind + " condition) "); - } else if (Cast(node)) { - At_Root_Query* block = Cast(node); - std::cerr << ind << "At_Root_Query " << block; - std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; - debug_ast(block->feature(), ind + " feature) "); - debug_ast(block->value(), ind + " value) "); - } else if (Cast(node)) { + } + else if (Cast(node)) { SupportsDeclaration* block = Cast(node); std::cerr << ind << "SupportsDeclaration " << block; std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; + << std::endl; debug_ast(block->feature(), ind + " feature) "); debug_ast(block->value(), ind + " value) "); - } else if (Cast(node)) { - Block* root_block = Cast(node); - std::cerr << ind << "Block " << root_block; - std::cerr << " (" << pstate_source_position(node) << ")"; - if (root_block->is_root()) std::cerr << " [root]"; - if (root_block->isInvisible()) std::cerr << " [isInvisible]"; - std::cerr << " " << root_block->tabs() << std::endl; - for(const Statement_Obj& i : root_block->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - WarningRule* block = Cast(node); - std::cerr << ind << "WarningRule " << block; + } + else if (Cast< SupportsCondition>(node)) { + SupportsCondition* block = Cast(node); + std::cerr << ind << "SupportsDeclaration " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << std::endl; + + } + else if (Cast(node)) { + WarnRule* block = Cast(node); + std::cerr << ind << "WarnRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->message(), ind + " : "); - } else if (Cast(node)) { + debug_ast(block->expression(), ind + " : "); + } + else if (Cast(node)) { ErrorRule* block = Cast(node); std::cerr << ind << "ErrorRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - } else if (Cast(node)) { + debug_ast(block->expression(), ind + " "); + } + else if (Cast(node)) { DebugRule* block = Cast(node); std::cerr << ind << "DebugRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->value(), ind + " "); - } else if (Cast(node)) { - Comment* block = Cast(node); - std::cerr << ind << "Comment " << block; + debug_ast(block->expression(), ind + " "); + } + else if (Cast(node)) { + LoudComment* block = Cast(node); + std::cerr << ind << "LoudComment " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->text(), ind + "// ", env); - } else if (Cast(node)) { - If* block = Cast(node); - std::cerr << ind << "If " << block; + debug_ast(block->text(), ind + "// "); + } + else if (Cast(node)) { + SilentComment* block = Cast(node); + std::cerr << ind << "SilentComment " << block; std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << " [" << block->text() << "]" << std::endl; + // debug_ast(block->text(), ind + "// "); + } + else if (Cast(node)) { + IfRule* block = Cast(node); + std::cerr << ind << "IfRule " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << parent_block(block->idxs()) << "]"; std::cerr << " " << block->tabs() << std::endl; debug_ast(block->predicate(), ind + " = "); - debug_ast(block->block(), ind + " <>"); + debug_block(block, ind + " <>"); debug_ast(block->alternative(), ind + " ><"); - } else if (Cast(node)) { - Return* block = Cast(node); - std::cerr << ind << "Return " << block; + } + else if (Cast(node)) { + ReturnRule* block = Cast(node); + std::cerr << ind << "ReturnRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs(); - std::cerr << " [" << block->value()->to_string() << "]" << std::endl; - } else if (Cast(node)) { + std::cerr << std::endl; + debug_ast(block->value(), ind + " => "); + // std::cerr << " [" << block->value()->inspect() << "]"; + } + else if (Cast(node)) { ExtendRule* block = Cast(node); std::cerr << ind << "ExtendRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->selector(), ind + "-> ", env); - } else if (Cast(node)) { - Content* block = Cast(node); - std::cerr << ind << "Content " << block; + debug_ast(block->selector(), ind + "-> "); + } + else if (Cast(node)) { + ContentRule* block = Cast(node); + std::cerr << ind << "ContentRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->arguments(), ind + " args: ", env); - } else if (Cast(node)) { - Import_Stub* block = Cast(node); - std::cerr << ind << "Import_Stub " << block; + debug_ast(block->arguments(), ind + " args: "); + } + else if (Cast(node)) { + StaticImport* block = Cast(node); + std::cerr << ind << "StaticImport " << block; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << block->imp_path() << "] "; - std::cerr << " " << block->tabs() << std::endl; - } else if (Cast(node)) { - Import* block = Cast(node); - std::cerr << ind << "Import " << block; + // std::cerr << " [" << block->imp_path() << "] "; + // std::cerr << " " << block->tabs(); + std::cerr << std::endl; + debug_ast(block->url(), "url: "); + debug_ast(block->supports(), "supports: "); + debug_ast(block->media(), "media: "); + } + else if (Cast(node)) { + CssImport* block = Cast(node); + std::cerr << ind << "CssImport " << block; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - // sass::vector files_; - for (auto imp : block->urls()) debug_ast(imp, ind + "@: ", env); - debug_ast(block->import_queries(), ind + "@@ "); - } else if (Cast(node)) { - Assignment* block = Cast(node); - std::cerr << ind << "Assignment " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <<" << block->variable() << ">> " << block->tabs() << std::endl; - debug_ast(block->value(), ind + "=", env); - } else if (Cast(node)) { + std::cerr << " [" << block->url()->text() << "] "; + std::cerr << std::endl; + } + else if (Cast(node)) { + IncludeImport* block = Cast(node); + std::cerr << ind << "IncludeImport " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + // std::cerr << " [" << block->sheet() << "] "; + std::cerr << std::endl; + } + else if (Cast(node)) { + ImportRule* block = Cast(node); + std::cerr << ind << "ImportRule " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + // std::cerr << " [" << block->imp_path() << "] "; + // std::cerr << " " << block->tabs(); + std::cerr << std::endl; + for (auto imp : block->elements()) debug_ast(imp, ind + "@: "); + } + else if (Cast(node)) { + AssignRule* block = Cast(node); + std::cerr << ind << "AssignRule " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <<" << block->variable().orig() << ">> " << block->tabs(); + std::cerr << " vidx("; + bool join = false; + for (auto pidx : block->vidxs()) { + if (join) std::cerr << ", "; + std::cerr << pidx.toString(); + join = true; + } + std::cerr << ")" << std::endl; + + debug_ast(block->value(), ind + "="); + } + else if (Cast(node)) { Declaration* block = Cast(node); std::cerr << ind << "Declaration " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [is_custom_property: " << block->is_custom_property() << "] "; std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->property(), ind + " prop: ", env); - debug_ast(block->value(), ind + " value: ", env); - debug_ast(block->block(), ind + " ", env); - } else if (Cast(node)) { - Keyframe_Rule* ParentStatement = Cast(node); - std::cerr << ind << "Keyframe_Rule " << ParentStatement; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << ParentStatement->tabs() << std::endl; - if (ParentStatement->name()) debug_ast(ParentStatement->name(), ind + "@"); - if (ParentStatement->block()) for(const Statement_Obj& i : ParentStatement->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + debug_ast(block->name(), ind + " name: "); + debug_ast(block->value(), ind + " value: "); + debug_block(block, ind + " "); + } + else if (Cast(node)) { AtRule* block = Cast(node); std::cerr << ind << "AtRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << block->keyword() << "] " << block->tabs() << std::endl; - debug_ast(block->selector(), ind + "~", env); - debug_ast(block->value(), ind + "+", env); - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + // std::cerr << " [" << block->keyword() << "] " << block->tabs(); + std::cerr << std::endl; + // debug_ast(block->interpolation(), ind + "#"); + // debug_ast(block->value(), ind + "+"); + + debug_ast(block->name(), ind + "#"); + debug_ast(block->value(), ind + "="); + + for (const StatementObj& i : block->elements()) { debug_ast(i, ind + " "); } + } + else if (Cast(node)) { EachRule* block = Cast(node); - std::cerr << ind << "EachRule " << block; + std::cerr << ind << "EachRule [" << debug_vec(block->variables()) << "]" << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + for (const StatementObj& i : block->elements()) { debug_ast(i, ind + " "); } + } + else if (Cast(node)) { ForRule* block = Cast(node); std::cerr << ind << "ForRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + for (const StatementObj& i : block->elements()) { debug_ast(i, ind + " "); } + } + else if (Cast(node)) { WhileRule* block = Cast(node); std::cerr << ind << "WhileRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Definition* block = Cast(node); - std::cerr << ind << "Definition " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [name: " << block->name() << "] "; - std::cerr << " [type: " << (block->type() == Sass::Definition::Type::MIXIN ? "Mixin " : "Function ") << "] "; - // this seems to lead to segfaults some times? - // std::cerr << " [signature: " << block->signature() << "] "; - std::cerr << " [native: " << block->native_function() << "] "; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->parameters(), ind + " params: ", env); - if (block->block()) debug_ast(block->block(), ind + " ", env); - } else if (Cast(node)) { - Mixin_Call* block = Cast(node); - std::cerr << ind << "Mixin_Call " << block << " " << block->tabs(); - std::cerr << " (" << pstate_source_position(block) << ")"; - std::cerr << " [" << block->name() << "]"; - std::cerr << " [has_content: " << block->has_content() << "] " << std::endl; - debug_ast(block->arguments(), ind + " args: ", env); - debug_ast(block->block_parameters(), ind + " block_params: ", env); - if (block->block()) debug_ast(block->block(), ind + " ", env); - } else if (StyleRule* ruleset = Cast(node)) { + debug_ast(block->condition(), ind + " ?? "); + for (const StatementObj& i : block->elements()) { debug_ast(i, ind + " "); } + } + + + else if (PlainCssCallable* ruleset = Cast(node)) { + std::cerr << ind << "PlainCssCallable " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; + // std::cerr << " [indent: " << ruleset->tabs() << "]"; + // std::cerr << " [" << ruleset->name() << "]"; + std::cerr << std::endl; + // debug_ast(ruleset->content(), ind + " @ "); + } + else if (IncludeRule* ruleset = Cast(node)) { + std::cerr << ind << "IncludeRule " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [indent: " << ruleset->tabs() << "]"; + // std::cerr << " [" << ruleset->name() << "]"; + std::cerr << std::endl; + debug_ast(ruleset->content(), ind + " @ "); + } + else if (ContentBlock* ruleset = Cast(node)) { + std::cerr << ind << "ContentBlock " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [indent: " << ruleset->tabs() << "]"; + std::cerr << " [" << ruleset->name().orig() << "]"; + std::cerr << std::endl; + debug_block(ruleset, ind + " "); + } + + else if (MixinRule* ruleset = Cast(node)) { + std::cerr << ind << "MixinRule " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [indent: " << ruleset->tabs() << "]"; + std::cerr << " [" << ruleset->name().orig() << "]"; + std::cerr << std::endl; + debug_ast(ruleset->arguments(), ind + "$"); + debug_block(ruleset, ind + " "); + } + else if (ArgumentDeclaration* args = Cast(node)) { + std::cerr << ind << "MixinRArgumentDeclarationule " << args; + std::cerr << " (" << pstate_source_position(node) << ")"; + // std::cerr << " [indent: " << ruleset->tabs() << "]"; + // std::cerr << " [" << ruleset->name() << "]"; + std::cerr << std::endl; + // debug_ast(ruleset->arguments(), ind + "$"); + // debug_ast(ruleset, ind + " "); + } + + else if (FunctionRule* ruleset = Cast(node)) { + std::cerr << ind << "FunctionRule " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [indent: " << ruleset->tabs() << "]"; + std::cerr << " [" << ruleset->name().orig() << "]"; + std::cerr << std::endl; + debug_block(ruleset, ind + " "); + } + else if (StyleRule* ruleset = Cast(node)) { std::cerr << ind << "StyleRule " << ruleset; std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << parent_block(ruleset->idxs()) << "]"; std::cerr << " [indent: " << ruleset->tabs() << "]"; - std::cerr << (ruleset->is_invisible() ? " [INVISIBLE]" : ""); - std::cerr << (ruleset->is_root() ? " [root]" : ""); + std::cerr << std::endl; + debug_ast(ruleset->interpolation(), ind + "#"); + debug_block(ruleset, ind + " "); + } + else if (CssStyleRule* ruleset = Cast(node)) { + std::cerr << ind << "CssStyleRule " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << std::endl; debug_ast(ruleset->selector(), ind + ">"); - debug_ast(ruleset->block(), ind + " "); - } else if (Cast(node)) { - Block* block = Cast(node); - std::cerr << ind << "Block " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (block->is_invisible() ? " [INVISIBLE]" : ""); - std::cerr << " [indent: " << block->tabs() << "]" << std::endl; - for(const Statement_Obj& i : block->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Variable* expression = Cast(node); - std::cerr << ind << "Variable " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->name() << "]" << std::endl; - sass::string name(expression->name()); - if (env && env->has(name)) debug_ast(Cast((*env)[name]), ind + " -> ", env); - } else if (Cast(node)) { - Function_Call* expression = Cast(node); - std::cerr << ind << "Function_Call " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->name() << "]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; - if (expression->is_css()) std::cerr << " [css]"; - std::cerr << std::endl; - debug_ast(expression->arguments(), ind + " args: ", env); - debug_ast(expression->func(), ind + " func: ", env); - } else if (Cast(node)) { - Function* expression = Cast(node); - std::cerr << ind << "Function " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->is_css()) std::cerr << " [css]"; - std::cerr << std::endl; - debug_ast(expression->definition(), ind + " definition: ", env); - } else if (Cast(node)) { - Arguments* expression = Cast(node); - std::cerr << ind << "Arguments " << expression; - if (expression->is_delayed()) std::cerr << " [delayed]"; - std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->has_named_arguments()) std::cerr << " [has_named_arguments]"; - if (expression->has_rest_argument()) std::cerr << " [has_rest_argument]"; - if (expression->has_keyword_argument()) std::cerr << " [has_keyword_argument]"; - std::cerr << std::endl; - for(const Argument_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + // debug_ast(ruleset->interpolation(), ind + "#"); + for (auto stmt : ruleset->elements()) { + debug_ast(stmt, ind + " !! "); + } + // debug_ast(ruleset, ind + " :: "); + } + else if (Cast(node)) { + VariableExpression* expression = Cast(node); + std::cerr << ind << "VariableExpression " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->name().orig() << "]"; + std::cerr << " vidx("; + bool join = false; + for (auto pidx : expression->vidxs()) { + if (join) std::cerr << ", "; + std::cerr << pidx.toString(); + join = true; + } + std::cerr << ")" << std::endl; + + // sass::string name(expression->name()); + // if (env && env->has(name)) debug_ast(Cast((*env)[name]), ind + " -> "); + } + else if (Cast(node)) { + ArgumentInvocation* arguments = Cast(node); + std::cerr << ind << "ArgumentInvocation " << arguments; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << std::endl; + // std::cerr << ind << " positional: " << debug_vec(arguments->positional()) << "\n"; + std::cerr << ind << " named: " << arguments->named().size() << "\n"; + // std::cerr << ind << " restArg: " << debug_vec(arguments->restArg()) << "\n"; + // std::cerr << ind << " kwdRest: " << debug_vec(arguments->kwdRest()) << "\n"; + + } + else if (Cast(node)) { + FunctionExpression* expression = Cast(node); + std::cerr << ind << "FunctionExpression " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [selfAssign: " << expression->selfAssign() << "]"; + // if (expression->is_css()) std::cerr << " [css]"; + std::cerr << std::endl; + debug_ast(expression->name(), ind + " name: "); + debug_ast(expression->arguments(), ind + " args: "); + // debug_ast(expression->func(), ind + " func: "); + } + else if (Cast(node)) { Argument* expression = Cast(node); std::cerr << ind << "Argument " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->value().ptr() << "]"; - std::cerr << " [name: " << expression->name() << "] "; + std::cerr << " [" << expression->defval().ptr() << "]"; + std::cerr << " [name: " << expression->name().orig() << "] "; std::cerr << " [rest: " << expression->is_rest_argument() << "] "; std::cerr << " [keyword: " << expression->is_keyword_argument() << "] " << std::endl; - debug_ast(expression->value(), ind + " value: ", env); - } else if (Cast(node)) { - Parameters* expression = Cast(node); - std::cerr << ind << "Parameters " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [has_optional: " << expression->has_optional_parameters() << "] "; - std::cerr << " [has_rest: " << expression->has_rest_parameter() << "] "; - std::cerr << std::endl; - for(const Parameter_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Parameter* expression = Cast(node); - std::cerr << ind << "Parameter " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [name: " << expression->name() << "] "; - std::cerr << " [default: " << expression->default_value().ptr() << "] "; - std::cerr << " [rest: " << expression->is_rest_parameter() << "] " << std::endl; - } else if (Cast(node)) { - Unary_Expression* expression = Cast(node); - std::cerr << ind << "Unary_Expression " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->type() << "]" << std::endl; - debug_ast(expression->operand(), ind + " operand: ", env); - } else if (Cast(node)) { - Binary_Expression* expression = Cast(node); - std::cerr << ind << "Binary_Expression " << expression; - if (expression->is_interpolant()) std::cerr << " [is interpolant] "; - if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; - if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " [ws_before: " << expression->op().ws_before << "] "; - std::cerr << " [ws_after: " << expression->op().ws_after << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->type_name() << "]" << std::endl; - debug_ast(expression->left(), ind + " left: ", env); - debug_ast(expression->right(), ind + " right: ", env); - } else if (Cast(node)) { + debug_ast(expression->defval(), ind + " value: "); + } + else if (Cast(node)) { + UnaryOpExpression* expression = Cast(node); + std::cerr << ind << "UnaryOpExpression " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + // std::cerr << " [" << expression->type() << "]" << std::endl; + debug_ast(expression->operand(), ind + " operand: "); + } + else if (Cast(node)) { + ParenthesizedExpression* expression = Cast(node); + std::cerr << ind << "ParenthesizedExpression " << expression; + std::cerr << " (" << pstate_source_position(expression) << ")"; + std::cerr << std::endl; + debug_ast(expression->expression(), ind + "() "); + + } + else if (Cast(node)) { + BinaryOpExpression* expression = Cast(node); + std::cerr << ind << "BinaryOpExpression " << expression; + // std::cerr << " [ws_before: " << expression->operand().ws_before << "] "; + // std::cerr << " [ws_after: " << expression->operand().ws_after << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << std::endl; + debug_ast(expression->left(), ind + " left: "); + debug_ast(expression->right(), ind + " right: "); + } + else if (Cast(node)) { Map* expression = Cast(node); std::cerr << ind << "Map " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [Hashed]" << std::endl; for (const auto& i : expression->elements()) { debug_ast(i.first, ind + " key: "); debug_ast(i.second, ind + " val: "); } - } else if (Cast(node)) { + } + else if (Cast(node)) { + ListExpression* expression = Cast(node); + std::cerr << ind << "ListExpression " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " (" << expression->size() << ") " << + (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_UNDEF ? "Unkonwn" : "Space ") << + " [bracketed: " << expression->hasBrackets() << "] " << + std::endl; + for (size_t i = 0; i < expression->size(); i++) { + debug_ast(expression->get(i), ind + " "); + } + } + else if (Cast(node)) { + MapExpression* expression = Cast(node); + std::cerr << ind << "MapExpression " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " (" << expression->size() << ") " << + std::endl; + for (size_t i = 0; i < expression->size(); i++) { + debug_ast(expression->get(i), ind + " "); + } + } + else if (Cast(node)) { + + ArgumentList* expression = Cast(node); + std::cerr << ind << "ArgumentList " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " (" << expression->size() << ") " << + (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_UNDEF ? "Unkonwn" : "Space ") << + " [bracketed: " << expression->hasBrackets() << "] " << + " [hash: " << expression->hash() << "] " << + std::endl; + ValueFlatMap keywords = expression->keywords(); + for (const auto& i : expression->elements()) { debug_ast(i, ind + " [] "); } + for (const auto& kv : keywords) { debug_ast(kv.second, ind + " " + kv.first.orig().c_str() + " "); } + } + else if (Cast(node)) { List* expression = Cast(node); std::cerr << ind << "List " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " (" << expression->length() << ") " << - (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_HASH ? "Map " : "Space ") << - " [delayed: " << expression->is_delayed() << "] " << - " [interpolant: " << expression->is_interpolant() << "] " << - " [listized: " << expression->from_selector() << "] " << - " [arglist: " << expression->is_arglist() << "] " << - " [bracketed: " << expression->is_bracketed() << "] " << - " [expanded: " << expression->is_expanded() << "] " << + std::cerr << " (" << expression->size() << ") " << + (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_UNDEF ? "Unkonwn" : "Space ") << + " [bracketed: " << expression->hasBrackets() << "] " << " [hash: " << expression->hash() << "] " << std::endl; - for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + for (const auto& i : expression->elements()) { debug_ast(i, ind + " "); } + } + else if (Cast(node)) { Boolean* expression = Cast(node); std::cerr << ind << "Boolean " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [" << expression->value() << "]" << std::endl; - } else if (Cast(node)) { - Color_RGBA* expression = Cast(node); + } + else if (Cast(node)) { + ColorRgba* expression = Cast(node); std::cerr << ind << "Color " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [name: " << expression->disp() << "] "; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " rgba[" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; - } else if (Cast(node)) { - Color_HSLA* expression = Cast(node); + std::cerr << " rgba[" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; + } + else if (Cast(node)) { + ColorHsla* expression = Cast(node); std::cerr << ind << "Color " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [name: " << expression->disp() << "] "; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " hsla[" << expression->h() << ":" << expression->s() << ":" << expression->l() << "@" << expression->a() << "]" << std::endl; - } else if (Cast(node)) { + std::cerr << " hsla[" << expression->h() << ":" << expression->s() << ":" << expression->l() << "@" << expression->a() << "]" << std::endl; + } + else if (Cast(node)) { Number* expression = Cast(node); std::cerr << ind << "Number " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [" << expression->value() << expression->unit() << "]" << " [hash: " << expression->hash() << "] " << std::endl; - } else if (Cast(node)) { + debug_ast(expression->lhsAsSlash(), ind + "[lhs] "); + debug_ast(expression->rhsAsSlash(), ind + "[rhs] "); + } + else if (Cast(node)) { Null* expression = Cast(node); std::cerr << ind << "Null " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] " + std::cerr << " (" << pstate_source_position(node) << ")" // " [hash: " << expression->hash() << "] " << std::endl; - } else if (Cast(node)) { - String_Quoted* expression = Cast(node); - std::cerr << ind << "String_Quoted " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << prettyprint(expression->value()) << "]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; - if (expression->quote_mark()) std::cerr << " [quote_mark: " << expression->quote_mark() << "]"; - std::cerr << std::endl; - } else if (Cast(node)) { - String_Constant* expression = Cast(node); - std::cerr << ind << "String_Constant " << expression; - if (expression->concrete_type()) { - std::cerr << " " << expression->concrete_type(); - } + } + else if (Cast(node)) { + StringExpression* expression = Cast(node); + // std::cerr << ind << "StringExpression " << expression; + // std::cerr << " [" << prettyprint(expression->text()) << "]"; + std::cerr << ind << "StringExpression"; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << prettyprint(expression->value()) << "]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; + if (expression->hasQuotes()) std::cerr << " {quoted}"; std::cerr << std::endl; - } else if (Cast(node)) { - String_Schema* expression = Cast(node); - std::cerr << ind << "String_Schema " << expression; - std::cerr << " (" << pstate_source_position(expression) << ")"; - std::cerr << " " << expression->concrete_type(); + debug_ast(expression->text(), ind + " "); + } + else if (Cast(node)) { + ItplString* expression = Cast(node); + std::cerr << ind << "ItplString " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->css()) std::cerr << " [css]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [is interpolant]"; - if (expression->has_interpolant()) std::cerr << " [has interpolant]"; - if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; - if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; + std::cerr << " [" << prettyprint(expression->text()) << "]"; + std::cerr << std::endl; - for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + } + else if (Cast(node)) { String* expression = Cast(node); - std::cerr << ind << "String " << expression; - std::cerr << " " << expression->concrete_type(); + std::cerr << ind << "String "; + if (expression->hasQuotes()) { + std::cerr << "[QUOTED] "; + } + std::cerr << expression; + std::cerr << " " << expression->type(); std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; + std::cerr << " [" << prettyprint(expression->value()) << "]"; + // std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" + std::cerr << std::endl; + } + else if (Cast(node)) { + Interpolation* expression = Cast(node); + std::cerr << ind << "Interpolation"; + std::cerr << " (" << pstate_source_position(expression) << ")"; + + // std::cerr << ind << "Interpolation " << expression; + // std::cerr << " " << expression->concrete_type(); + // std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << std::endl; - } else if (Cast(node)) { + for (const auto& i : expression->elements()) { debug_ast(i, ind + " "); } + } + else if (Cast(node)) { Expression* expression = Cast(node); std::cerr << ind << "Expression " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; - switch (expression->concrete_type()) { - case Expression::Type::NONE: std::cerr << " [NONE]"; break; - case Expression::Type::BOOLEAN: std::cerr << " [BOOLEAN]"; break; - case Expression::Type::NUMBER: std::cerr << " [NUMBER]"; break; - case Expression::Type::COLOR: std::cerr << " [COLOR]"; break; - case Expression::Type::STRING: std::cerr << " [STRING]"; break; - case Expression::Type::LIST: std::cerr << " [LIST]"; break; - case Expression::Type::MAP: std::cerr << " [MAP]"; break; - case Expression::Type::SELECTOR: std::cerr << " [SELECTOR]"; break; - case Expression::Type::NULL_VAL: std::cerr << " [NULL_VAL]"; break; - case Expression::Type::C_WARNING: std::cerr << " [C_WARNING]"; break; - case Expression::Type::C_ERROR: std::cerr << " [C_ERROR]"; break; - case Expression::Type::FUNCTION: std::cerr << " [FUNCTION]"; break; - case Expression::Type::NUM_TYPES: std::cerr << " [NUM_TYPES]"; break; - case Expression::Type::VARIABLE: std::cerr << " [VARIABLE]"; break; - case Expression::Type::FUNCTION_VAL: std::cerr << " [FUNCTION_VAL]"; break; - case Expression::Type::PARENT: std::cerr << " [PARENT]"; break; - } + // std::cerr << " [" << expression->type() << "]"; std::cerr << std::endl; - } else if (Cast(node)) { - ParentStatement* parent = Cast(node); - std::cerr << ind << "ParentStatement " << parent; + } + else if (Cast(node)) { + ParentStatement* has_block = Cast(node); + std::cerr << ind << "Has_Block " << has_block; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << parent->tabs() << std::endl; - if (parent->block()) for(const Statement_Obj& i : parent->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + std::cerr << " " << has_block->tabs() << std::endl; + for (const StatementObj& i : has_block->elements()) { debug_ast(i, ind + " "); } + } + else if (Cast(node)) { Statement* statement = Cast(node); std::cerr << ind << "Statement " << statement; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << statement->tabs() << std::endl; } + else if (Cast(node)) { + CssAtRule* rule = Cast(node); + std::cerr << ind << "CssAtRule " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << " [name: " << dbgValStr(rule->name()) << "] "; + std::cerr << " [value: " << dbgValStr(rule->value()) << "] "; + std::cerr << std::endl; + debug_block(rule, ind + " "); + } + else if (Cast(node)) { + CssParentNode* rule = Cast(node); + std::cerr << ind << "CssParentNode " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << std::endl; + debug_block(rule, ind + " "); + } + else if (Cast(node)) { + CssNode* rule = Cast(node); + std::cerr << ind << "CssNode " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << std::endl; + } + else { + std::cerr << ind << "Undetected" << std::endl; + } if (ind == "") std::cerr << "####################################################################\n"; } /* -inline void debug_ast(const AST_Node* node, sass::string ind = "", Env* env = 0) +inline void debug_ast(const AstNode* node, std::string ind = ""* env = 0) { - debug_ast(const_cast(node), ind, env); + debug_ast(const_cast(node), ind); } */ diff --git a/src/emitter.cpp b/src/emitter.cpp index fa50ef9c9b..d1069d61c3 100644 --- a/src/emitter.cpp +++ b/src/emitter.cpp @@ -1,14 +1,18 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" #include "emitter.hpp" -#include "util_string.hpp" -#include "util.hpp" + +#include "charcode.hpp" +#include "character.hpp" +#include "string_utils.hpp" namespace Sass { + // Default constructor + + // Import some namespaces + using namespace Charcode; + using namespace Character; - Emitter::Emitter(struct Sass_Output_Options& opt) - : wbuf(), + Emitter::Emitter(struct SassOutputOptionsCpp& opt, bool srcmap_enabled) + : wbuf(srcmap_enabled), opt(opt), indentation(0), scheduled_space(0), @@ -17,44 +21,52 @@ namespace Sass { scheduled_crutch(0), scheduled_mapping(0), in_custom_property(false), - in_comment(false), - in_wrapped(false), - in_media_block(false), - in_declaration(false), + in_declaration(true), + separators(), in_space_array(false), in_comma_array(false) { } // return buffer as string - sass::string Emitter::get_buffer(void) + sass::string Emitter::get_buffer(bool trim) { - return wbuf.buffer; + finalize(false); // flush stuff + sass::string text(std::move(wbuf.buffer)); + // This potentially makes mappings invalid!? + if (trim) StringUtils::makeTrimmed(text); + return text; // Should use RVO } - Sass_Output_Style Emitter::output_style(void) const + enum SassOutputStyle Emitter::output_style(void) const { return opt.output_style; } // PROXY METHODS FOR SOURCE MAPS - + void Emitter::add_source_index(size_t idx) - { wbuf.smap.source_index.push_back(idx); } - - sass::string Emitter::render_srcmap(Context &ctx) - { return wbuf.smap.render_srcmap(ctx); } + { + if (wbuf.smap) wbuf.smap->source_index.push_back(idx); } void Emitter::set_filename(const sass::string& str) - { wbuf.smap.file = str; } - - void Emitter::schedule_mapping(const AST_Node* node) - { scheduled_mapping = node; } - void Emitter::add_open_mapping(const AST_Node* node) - { wbuf.smap.add_open_mapping(node); } - void Emitter::add_close_mapping(const AST_Node* node) - { wbuf.smap.add_close_mapping(node); } + { if (wbuf.smap) wbuf.smap->file = str; } + + void Emitter::schedule_mapping(const AstNode* node) + { + scheduled_mapping = node; + } + void Emitter::add_open_mapping(const AstNode* node) + { + if (wbuf.smap) wbuf.smap->add_open_mapping(node); + } + void Emitter::add_close_mapping(const AstNode* node) + { + if (wbuf.smap) wbuf.smap->add_close_mapping(node); + } SourceSpan Emitter::remap(const SourceSpan& pstate) - { return wbuf.smap.remap(pstate); } + { + return wbuf.smap ? wbuf.smap->remap(pstate) : pstate; + } // MAIN BUFFER MANIPULATION @@ -80,23 +92,32 @@ namespace Sass { linefeeds += opt.linefeed; scheduled_space = 0; scheduled_linefeed = 0; - append_string(linefeeds); + if (scheduled_delimiter) { + scheduled_delimiter = false; + write_char(';'); + } + write_string(linefeeds); - } else if (scheduled_space) { + } + else if (scheduled_space) { sass::string spaces(scheduled_space, ' '); scheduled_space = 0; - append_string(spaces); + if (scheduled_delimiter) { + scheduled_delimiter = false; + write_char(';'); + } + write_string(spaces); } - if (scheduled_delimiter) { + else if (scheduled_delimiter) { scheduled_delimiter = false; - append_string(";"); + write_char(';'); } } // prepend some text or token to the buffer void Emitter::prepend_output(const OutputBuffer& output) { - wbuf.smap.prepend(output); + if (wbuf.smap) wbuf.smap->prepend(output); wbuf.buffer = output.buffer + wbuf.buffer; } @@ -106,7 +127,7 @@ namespace Sass { // do not adjust mappings for utf8 bom // seems they are not counted in any UA if (text.compare("\xEF\xBB\xBF") != 0) { - wbuf.smap.prepend(Offset(text)); + if (wbuf.smap) wbuf.smap->prepend(Offset(text)); } wbuf.buffer = text + wbuf.buffer; } @@ -117,51 +138,76 @@ namespace Sass { } // append a single char to the buffer - void Emitter::append_char(const char chr) + void Emitter::append_char(uint8_t chr) { // write space/lf flush_schedules(); // add to buffer - wbuf.buffer += chr; + wbuf.buffer.push_back((unsigned char) chr); + // account for data in source-maps + if (wbuf.smap) wbuf.smap->append(Offset(chr)); + } + + // append a single char to the buffer + void Emitter::write_char(uint8_t chr) + { + // add to buffer + wbuf.buffer.push_back((unsigned char)chr); // account for data in source-maps - wbuf.smap.append(Offset(chr)); + if (wbuf.smap) wbuf.smap->append(Offset(chr)); } // append some text or token to the buffer void Emitter::append_string(const sass::string& text) { - // write space/lf flush_schedules(); + // add to buffer + wbuf.buffer.append(text); + // account for data in source-maps + if (wbuf.smap) wbuf.smap->append(Offset(text)); + } - if (in_comment) { - sass::string out = Util::normalize_newlines(text); - if (output_style() == COMPACT) { - out = comment_to_compact_string(out); - } - wbuf.smap.append(Offset(out)); - wbuf.buffer += std::move(out); - } else { - // add to buffer - wbuf.buffer += text; - // account for data in source-maps - wbuf.smap.append(Offset(text)); + // append some text or token to the buffer + void Emitter::write_string(const sass::string& text) + { + // add to buffer + wbuf.buffer.append(text); + // account for data in source-maps + if (wbuf.smap) wbuf.smap->append(Offset(text)); + } + + // append some text or token to the buffer + void Emitter::append_string(const sass::string& text, size_t repeat) + { + // write space/lf + flush_schedules(); + // add to buffer + // wbuf.buffer.append(text, repeat); + for (size_t i = 0; i < repeat; i += 1) { + wbuf.buffer.append(text); } + // account for data in source-maps + if (wbuf.smap) wbuf.smap->append(Offset(text) * (uint32_t)repeat); } - // append some white-space only text - void Emitter::append_wspace(const sass::string& text) + // append some text or token to the buffer + void Emitter::append_string(const char* text, size_t repeat) { - if (text.empty()) return; - if (peek_linefeed(text.c_str())) { - scheduled_space = 0; - append_mandatory_linefeed(); + // write space/lf + flush_schedules(); + // add to buffer + // wbuf.buffer.append(text, repeat); + for (size_t i = 0; i < repeat; i += 1) { + wbuf.buffer.append(text); } + // account for data in source-maps + if (wbuf.smap) wbuf.smap->append(Offset(text) * (uint32_t)repeat); } // append some text or token to the buffer // this adds source-mappings for node start and end - void Emitter::append_token(const sass::string& text, const AST_Node* node) + void Emitter::append_token(const sass::string& text, const AstNode* node) { flush_schedules(); add_open_mapping(node); @@ -171,7 +217,7 @@ namespace Sass { add_open_mapping(scheduled_crutch); scheduled_crutch = 0; } - append_string(text); + write_string(text); add_close_mapping(node); } @@ -179,27 +225,24 @@ namespace Sass { void Emitter::append_indentation() { - if (output_style() == COMPRESSED) return; - if (output_style() == COMPACT) return; + if (output_style() == SASS_STYLE_COMPRESSED) return; + if (output_style() == SASS_STYLE_COMPACT) return; if (in_declaration && in_comma_array) return; if (scheduled_linefeed && indentation) scheduled_linefeed = 1; - sass::string indent = ""; - for (size_t i = 0; i < indentation; i++) - indent += opt.indent; - append_string(indent); + append_string(opt.indent, indentation); // 1.5% (realloc) } void Emitter::append_delimiter() { scheduled_delimiter = true; - if (output_style() == COMPACT) { + if (output_style() == SASS_STYLE_COMPACT) { if (indentation == 0) { append_mandatory_linefeed(); } else { append_mandatory_space(); } - } else if (output_style() != COMPRESSED) { + } else if (output_style() != SASS_STYLE_COMPRESSED) { append_optional_linefeed(); } } @@ -207,14 +250,14 @@ namespace Sass { void Emitter::append_comma_separator() { // scheduled_space = 0; - append_string(","); + append_char(','); append_optional_space(); } void Emitter::append_colon_separator() { scheduled_space = 0; - append_string(":"); + append_char(':'); if (!in_custom_property) append_optional_space(); } @@ -225,7 +268,7 @@ namespace Sass { void Emitter::append_optional_space() { - if ((output_style() != COMPRESSED) && buffer().size()) { + if ((output_style() != SASS_STYLE_COMPRESSED) && wbuf.buffer.size()) { unsigned char lst = buffer().at(buffer().length() - 1); if (!isspace(lst) || scheduled_delimiter) { if (last_char() != '(') { @@ -237,7 +280,7 @@ namespace Sass { void Emitter::append_special_linefeed() { - if (output_style() == COMPACT) { + if (output_style() == SASS_STYLE_COMPACT) { append_mandatory_linefeed(); for (size_t p = 0; p < indentation; p++) append_string(opt.indent); @@ -247,7 +290,7 @@ namespace Sass { void Emitter::append_optional_linefeed() { if (in_declaration && in_comma_array) return; - if (output_style() == COMPACT) { + if (output_style() == SASS_STYLE_COMPACT) { append_mandatory_space(); } else { append_mandatory_linefeed(); @@ -256,41 +299,49 @@ namespace Sass { void Emitter::append_mandatory_linefeed() { - if (output_style() != COMPRESSED) { + if (output_style() != SASS_STYLE_COMPRESSED) { scheduled_linefeed = 1; scheduled_space = 0; // flush_schedules(); } } - void Emitter::append_scope_opener(AST_Node* node) + void Emitter::append_scope_opener(AstNode* node) { scheduled_linefeed = 0; append_optional_space(); flush_schedules(); if (node) add_open_mapping(node); - append_string("{"); + write_char('{'); append_optional_linefeed(); // append_optional_space(); ++ indentation; } - void Emitter::append_scope_closer(AST_Node* node) + void Emitter::append_scope_closer(AstNode* node) { -- indentation; scheduled_linefeed = 0; - if (output_style() == COMPRESSED) - scheduled_delimiter = false; - if (output_style() == EXPANDED) { - append_optional_linefeed(); - append_indentation(); - } else { - append_optional_space(); + if (last_char() == '{') { + scheduled_space = false; + scheduled_linefeed = false; + } + else { + if (output_style() == SASS_STYLE_COMPRESSED) + scheduled_delimiter = false; + if (output_style() == SASS_STYLE_EXPANDED) { + append_optional_linefeed(); + append_indentation(); + } + else { + append_optional_space(); + } } - append_string("}"); + + append_char('}'); if (node) add_close_mapping(node); append_optional_linefeed(); if (indentation != 0) return; - if (output_style() != COMPRESSED) + if (output_style() != SASS_STYLE_COMPRESSED) scheduled_linefeed = 2; } diff --git a/src/emitter.hpp b/src/emitter.hpp index 1a2a9d0e1b..b399486d4b 100644 --- a/src/emitter.hpp +++ b/src/emitter.hpp @@ -1,11 +1,12 @@ -#ifndef SASS_EMITTER_H -#define SASS_EMITTER_H +#ifndef SASS_EMITTER_HPP +#define SASS_EMITTER_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include "sass/base.h" +#include "sass/values.h" #include "source_map.hpp" #include "ast_fwd_decl.hpp" @@ -15,53 +16,49 @@ namespace Sass { class Emitter { public: - Emitter(struct Sass_Output_Options& opt); - virtual ~Emitter() { } + Emitter(struct SassOutputOptionsCpp& opt, bool srcmap_enabled); protected: OutputBuffer wbuf; public: + void reserve(size_t bytes) { + wbuf.buffer.reserve(bytes); + if (wbuf.smap) wbuf.smap->reserve(bytes / 20); + } const sass::string& buffer(void) { return wbuf.buffer; } - const SourceMap smap(void) { return wbuf.smap; } - const OutputBuffer output(void) { return wbuf; } + const OutputBuffer& output(void) { return wbuf; } // proxy methods for source maps void add_source_index(size_t idx); void set_filename(const sass::string& str); - void add_open_mapping(const AST_Node* node); - void add_close_mapping(const AST_Node* node); - void schedule_mapping(const AST_Node* node); - sass::string render_srcmap(Context &ctx); + void add_open_mapping(const AstNode* node); + void add_close_mapping(const AstNode* node); + void schedule_mapping(const AstNode* node); SourceSpan remap(const SourceSpan& pstate); public: - struct Sass_Output_Options& opt; + struct SassOutputOptionsCpp& opt; size_t indentation; size_t scheduled_space; size_t scheduled_linefeed; bool scheduled_delimiter; - const AST_Node* scheduled_crutch; - const AST_Node* scheduled_mapping; + const AstNode* scheduled_crutch; + const AstNode* scheduled_mapping; public: // output strings different in custom css properties bool in_custom_property; - // output strings different in comments - bool in_comment; - // selector list does not get linefeeds - bool in_wrapped; - // lists always get a space after delimiter - bool in_media_block; // nested list must not have parentheses bool in_declaration; // nested lists need parentheses + sass::vector separators; bool in_space_array; bool in_comma_array; public: // return buffer as sass::string - sass::string get_buffer(void); + sass::string get_buffer(bool trim = false); // flush scheduled space/linefeed - Sass_Output_Style output_style(void) const; + enum SassOutputStyle output_style(void) const; // add outstanding linefeed void finalize(bool final = true); // flush scheduled space/linefeed @@ -70,14 +67,16 @@ namespace Sass { void prepend_string(const sass::string& text); void prepend_output(const OutputBuffer& out); // append some text or token to the buffer + void write_string(const sass::string& text); void append_string(const sass::string& text); + void append_string(const char* text, size_t repeat); + void append_string(const sass::string& text, size_t repeat); // append a single character to buffer - void append_char(const char chr); - // append some white-space only text - void append_wspace(const sass::string& text); + void write_char(uint8_t chr); + void append_char(uint8_t chr); // append some text or token to the buffer // this adds source-mappings for node start and end - void append_token(const sass::string& text, const AST_Node* node); + void append_token(const sass::string& text, const AstNode* node); // query last appended character char last_char(); @@ -88,8 +87,8 @@ namespace Sass { void append_special_linefeed(void); void append_optional_linefeed(void); void append_mandatory_linefeed(void); - void append_scope_opener(AST_Node* node = 0); - void append_scope_closer(AST_Node* node = 0); + void append_scope_opener(AstNode* node = 0); + void append_scope_closer(AstNode* node = 0); void append_comma_separator(void); void append_colon_separator(void); void append_delimiter(void); diff --git a/src/emscripten-bug.cpp b/src/emscripten-bug.cpp new file mode 100644 index 0000000000..8e1c36cd10 --- /dev/null +++ b/src/emscripten-bug.cpp @@ -0,0 +1,13 @@ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void emscripten_bug() { + std::random_device rd; +} + +#ifdef __cplusplus +} +#endif diff --git a/src/environment.cpp b/src/environment.cpp index 86dbda0c74..ad550665df 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -1,260 +1,5 @@ -#include "sass.hpp" -#include "ast.hpp" #include "environment.hpp" namespace Sass { - template - Environment::Environment(bool is_shadow) - : local_frame_(environment_map()), - parent_(0), is_shadow_(false) - { } - template - Environment::Environment(Environment* env, bool is_shadow) - : local_frame_(environment_map()), - parent_(env), is_shadow_(is_shadow) - { } - template - Environment::Environment(Environment& env, bool is_shadow) - : local_frame_(environment_map()), - parent_(&env), is_shadow_(is_shadow) - { } - - // link parent to create a stack - template - void Environment::link(Environment& env) { parent_ = &env; } - template - void Environment::link(Environment* env) { parent_ = env; } - - // this is used to find the global frame - // which is the second last on the stack - template - bool Environment::is_lexical() const - { - return !! parent_ && parent_->parent_; - } - - // only match the real root scope - // there is still a parent around - // not sure what it is actually use for - // I guess we store functions etc. there - template - bool Environment::is_global() const - { - return parent_ && ! parent_->parent_; - } - - template - environment_map& Environment::local_frame() { - return local_frame_; - } - - template - bool Environment::has_local(const sass::string& key) const - { return local_frame_.find(key) != local_frame_.end(); } - - template EnvResult - Environment::find_local(const sass::string& key) - { - auto end = local_frame_.end(); - auto it = local_frame_.find(key); - return EnvResult(it, it != end); - } - - template - T& Environment::get_local(const sass::string& key) - { return local_frame_[key]; } - - template - void Environment::set_local(const sass::string& key, const T& val) - { - local_frame_[key] = val; - } - template - void Environment::set_local(const sass::string& key, T&& val) - { - local_frame_[key] = val; - } - - template - void Environment::del_local(const sass::string& key) - { local_frame_.erase(key); } - - template - Environment* Environment::global_env() - { - Environment* cur = this; - while (cur->is_lexical()) { - cur = cur->parent_; - } - return cur; - } - - template - bool Environment::has_global(const sass::string& key) - { return global_env()->has(key); } - - template - T& Environment::get_global(const sass::string& key) - { return (*global_env())[key]; } - - template - void Environment::set_global(const sass::string& key, const T& val) - { - global_env()->local_frame_[key] = val; - } - template - void Environment::set_global(const sass::string& key, T&& val) - { - global_env()->local_frame_[key] = val; - } - - template - void Environment::del_global(const sass::string& key) - { global_env()->local_frame_.erase(key); } - - template - Environment* Environment::lexical_env(const sass::string& key) - { - Environment* cur = this; - while (cur) { - if (cur->has_local(key)) { - return cur; - } - cur = cur->parent_; - } - return this; - } - - // see if we have a lexical variable - // move down the stack but stop before we - // reach the global frame (is not included) - template - bool Environment::has_lexical(const sass::string& key) const - { - auto cur = this; - while (cur->is_lexical()) { - if (cur->has_local(key)) return true; - cur = cur->parent_; - } - return false; - } - - // see if we have a lexical we could update - // either update already existing lexical value - // or if flag is set, we create one if no lexical found - template - void Environment::set_lexical(const sass::string& key, const T& val) - { - Environment* cur = this; - bool shadow = false; - while ((cur && cur->is_lexical()) || shadow) { - EnvResult rv(cur->find_local(key)); - if (rv.found) { - rv.it->second = val; - return; - } - shadow = cur->is_shadow(); - cur = cur->parent_; - } - set_local(key, val); - } - // this one moves the value - template - void Environment::set_lexical(const sass::string& key, T&& val) - { - Environment* cur = this; - bool shadow = false; - while ((cur && cur->is_lexical()) || shadow) { - EnvResult rv(cur->find_local(key)); - if (rv.found) { - rv.it->second = val; - return; - } - shadow = cur->is_shadow(); - cur = cur->parent_; - } - set_local(key, val); - } - - // look on the full stack for key - // include all scopes available - template - bool Environment::has(const sass::string& key) const - { - auto cur = this; - while (cur) { - if (cur->has_local(key)) { - return true; - } - cur = cur->parent_; - } - return false; - } - - // look on the full stack for key - // include all scopes available - template EnvResult - Environment::find(const sass::string& key) - { - auto cur = this; - while (true) { - EnvResult rv(cur->find_local(key)); - if (rv.found) return rv; - cur = cur->parent_; - if (!cur) return rv; - } - }; - - // use array access for getter and setter functions - template - T& Environment::get(const sass::string& key) - { - auto cur = this; - while (cur) { - if (cur->has_local(key)) { - return cur->get_local(key); - } - cur = cur->parent_; - } - return get_local(key); - } - - // use array access for getter and setter functions - template - T& Environment::operator[](const sass::string& key) - { - auto cur = this; - while (cur) { - if (cur->has_local(key)) { - return cur->get_local(key); - } - cur = cur->parent_; - } - return get_local(key); - } -/* - #ifdef DEBUG - template - size_t Environment::print(sass::string prefix) - { - size_t indent = 0; - if (parent_) indent = parent_->print(prefix) + 1; - std::cerr << prefix << sass::string(indent, ' ') << "== " << this << std::endl; - for (typename environment_map::iterator i = local_frame_.begin(); i != local_frame_.end(); ++i) { - if (!ends_with(i->first, "[f]") && !ends_with(i->first, "[f]4") && !ends_with(i->first, "[f]2")) { - std::cerr << prefix << sass::string(indent, ' ') << i->first << " " << i->second; - if (Value* val = Cast(i->second)) - { std::cerr << " : " << val->to_string(); } - std::cerr << std::endl; - } - } - return indent ; - } - #endif -*/ - // compile implementation for AST_Node - template class Environment; - } - diff --git a/src/environment.hpp b/src/environment.hpp index 31b0b7401b..b646b673b6 100644 --- a/src/environment.hpp +++ b/src/environment.hpp @@ -1,123 +1,29 @@ -#ifndef SASS_ENVIRONMENT_H -#define SASS_ENVIRONMENT_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include "ast_fwd_decl.hpp" -#include "ast_def_macros.hpp" - -namespace Sass { - - // this defeats the whole purpose of environment being templatable!! - typedef environment_map::iterator EnvIter; - - class EnvResult { - public: - EnvIter it; - bool found; - public: - EnvResult(EnvIter it, bool found) - : it(it), found(found) {} - }; - - template - class Environment { - // TODO: test with map - environment_map local_frame_; - ADD_PROPERTY(Environment*, parent) - ADD_PROPERTY(bool, is_shadow) - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_ENVIRONMENT_HPP +#define SASS_ENVIRONMENT_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_nodes.hpp" +#include "environment_key.hpp" +#include "environment_cnt.hpp" + +namespace std { + template <> + struct hash { public: - Environment(bool is_shadow = false); - Environment(Environment* env, bool is_shadow = false); - Environment(Environment& env, bool is_shadow = false); - - // link parent to create a stack - void link(Environment& env); - void link(Environment* env); - - // this is used to find the global frame - // which is the second last on the stack - bool is_lexical() const; - - // only match the real root scope - // there is still a parent around - // not sure what it is actually use for - // I guess we store functions etc. there - bool is_global() const; - - // scope operates on the current frame - - environment_map& local_frame(); - - bool has_local(const sass::string& key) const; - - EnvResult find_local(const sass::string& key); - - T& get_local(const sass::string& key); - - // set variable on the current frame - void set_local(const sass::string& key, const T& val); - void set_local(const sass::string& key, T&& val); - - void del_local(const sass::string& key); - - // global operates on the global frame - // which is the second last on the stack - Environment* global_env(); - // get the env where the variable already exists - // if it does not yet exist, we return current env - Environment* lexical_env(const sass::string& key); - - bool has_global(const sass::string& key); - - T& get_global(const sass::string& key); - - // set a variable on the global frame - void set_global(const sass::string& key, const T& val); - void set_global(const sass::string& key, T&& val); - - void del_global(const sass::string& key); - - // see if we have a lexical variable - // move down the stack but stop before we - // reach the global frame (is not included) - bool has_lexical(const sass::string& key) const; - - // see if we have a lexical we could update - // either update already existing lexical value - // or we create a new one on the current frame - void set_lexical(const sass::string& key, T&& val); - void set_lexical(const sass::string& key, const T& val); - - // look on the full stack for key - // include all scopes available - bool has(const sass::string& key) const; - - // look on the full stack for key - // include all scopes available - T& get(const sass::string& key); - - // look on the full stack for key - // include all scopes available - EnvResult find(const sass::string& key); - - // use array access for getter and setter functions - T& operator[](const sass::string& key); - - #ifdef DEBUG - size_t print(sass::string prefix = ""); - #endif - + inline size_t operator()(const Sass::EnvKey& name) const + { + return name.hash(); + } }; +}; - // define typedef for our use case - typedef Environment Env; - typedef sass::vector EnvStack; +namespace Sass { } diff --git a/src/environment_cnt.hpp b/src/environment_cnt.hpp new file mode 100644 index 0000000000..23f93a37cd --- /dev/null +++ b/src/environment_cnt.hpp @@ -0,0 +1,42 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_ENVIRONMENT_CNT_H +#define SASS_ENVIRONMENT_CNT_H + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "flat_map.hpp" +#include "ast_fwd_decl.hpp" +#include "environment_key.hpp" + +namespace Sass { + + template + using EnvKeyMap = UnorderedMap< + const EnvKey, T, hashEnvKey, equalsEnvKey, + Sass::Allocator> + >; + + using EnvKeySet = UnorderedSet< + EnvKey, hashEnvKey, equalsEnvKey, + Sass::Allocator + >; + + template + // Performance comparisons on MSVC and bolt-bench: + // tsl::hopscotch_map is 10% slower than Sass::FlatMap + // std::unordered_map a bit faster than tsl::hopscotch_map + // Sass::FlapMap is 10% faster than any other container + // Note: only due to our very specific usage patterns! + using EnvKeyFlatMap = FlatMap; + + typedef sass::vector EnvKeys; + typedef EnvKeyFlatMap ValueFlatMap; + typedef EnvKeyFlatMap ExpressionFlatMap; + +}; + +#endif diff --git a/src/environment_key.hpp b/src/environment_key.hpp new file mode 100644 index 0000000000..889fc6026f --- /dev/null +++ b/src/environment_key.hpp @@ -0,0 +1,135 @@ +#ifndef SASS_ENVIRONMENT_KEY_HPP +#define SASS_ENVIRONMENT_KEY_HPP + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +namespace Sass { + + class EnvKey { + + private: + + // The original name + sass::string _orig; + // The normalized name + sass::string _norm; + + // Lazy calculated hash + mutable size_t _hash; + + // Called by constructors + inline void normalize() + { + // Normalize string + std::replace( + _norm.begin(), + _norm.end(), + '_', '-'); + } + + public: + + // Empty constructor + EnvKey() : + _hash(0) + {} + + // String copy constructor + EnvKey(const sass::string& orig) : + _orig(orig), + _norm(orig), + _hash(0) + { + normalize(); + } + + // String move constructor + EnvKey(sass::string&& orig) : + _orig(std::move(orig)), + _norm(_orig), + _hash(0) + { + normalize(); + } + + // Copy constructor + EnvKey(const EnvKey& key) : + _orig(key._orig), + _norm(key._norm), + _hash(key._hash) + { + } + + // Move constructor + EnvKey(EnvKey&& key) noexcept : + _orig(std::move(key._orig)), + _norm(std::move(key._norm)), + _hash(std::move(key._hash)) + { + } + + // Copy assignment operator + EnvKey& operator=(const EnvKey& key) + { + _orig = key._orig; + _norm = key._norm; + _hash = key._hash; + return *this; + } + + // Move assignment operator + EnvKey& operator=(EnvKey&& key) noexcept + { + _orig = std::move(key._orig); + _norm = std::move(key._norm); + _hash = std::move(key._hash); + return *this; + } + + // Compare normalization forms + bool operator==(const EnvKey& rhs) const + { + return norm() == rhs.norm(); + } + + // Simple helper + bool empty() const + { + return _norm.empty(); + } + + // Simple constant getter functions + const sass::string& orig() const { return _orig; } + const sass::string& norm() const { return _norm; } + + // Calculate only on demand + size_t hash() const { + if (_hash == 0) { + _hash = MurmurHash2( + (void*)_norm.c_str(), + (int)_norm.size(), + getHashSeed()); + } + return _hash; + } + + }; + + struct hashEnvKey { + inline size_t operator()(const EnvKey& str) const + { + return str.hash(); + } + }; + + struct equalsEnvKey { + bool operator() (const EnvKey& lhs, const EnvKey& rhs) const { + return lhs.norm() == rhs.norm(); + } + }; + +}; + +#endif diff --git a/src/environment_stack.cpp b/src/environment_stack.cpp new file mode 100644 index 0000000000..202e0b2d90 --- /dev/null +++ b/src/environment_stack.cpp @@ -0,0 +1,468 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "environment_stack.hpp" + +#include "ast_expressions.hpp" +#include "ast_statements.hpp" +#include "exceptions.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + // Each parsed scope gets its own environment frame + ///////////////////////////////////////////////////////////////////////// + + // The root is used for all runtime state + // Also contains parsed root scope stack + EnvRoot::EnvRoot( + EnvFrameVector& stack) : + EnvFrame(*this, stack) + { + // Initialize as not active yet + root.varFramePtr.push_back(0xFFFFFFFF); + root.mixFramePtr.push_back(0xFFFFFFFF); + root.fnFramePtr.push_back(0xFFFFFFFF); + // Account for allocated memory + root.scopes.push_back(idxs); + // Push onto our stack + stack.push_back(this); + } + // EO EnvRoot ctor + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Root-frame constructor + // Invoked by EnvRoot ctor + EnvFrame::EnvFrame( + EnvRoot& root, + EnvFrameVector& stack) : + stack(stack), + permeable(false), + parent(root), + root(root), + idxs(new VarRefs( + nullptr, 0, 0, 0, + false)), + varIdxs(idxs->varIdxs), + mixIdxs(idxs->mixIdxs), + fnIdxs(idxs->fnIdxs), + assignments(idxs->assignments), + variables(idxs->variables) + { + // Don't access root, not yet initialized + } + // EO EnvFrame ctor + + // Value constructor + EnvFrame::EnvFrame( + EnvFrameVector& stack, + bool permeable) : + stack(stack), + permeable(permeable), + parent(*stack.back()), + root(stack.back()->root), + idxs(new VarRefs(parent.idxs, + uint32_t(root.varFramePtr.size()), + uint32_t(root.mixFramePtr.size()), + uint32_t(root.fnFramePtr.size()), + permeable)), + varIdxs(idxs->varIdxs), + mixIdxs(idxs->mixIdxs), + fnIdxs(idxs->fnIdxs), + assignments(idxs->assignments), + variables(idxs->variables) + { + // Initialize stacks as not active yet + root.varFramePtr.push_back(0xFFFFFFFF); + root.mixFramePtr.push_back(0xFFFFFFFF); + root.fnFramePtr.push_back(0xFFFFFFFF); + // Account for allocated memory + root.scopes.push_back(idxs); + // Check and prevent stack smashing + if (stack.size() > MAX_NESTING) { + throw Exception::RecursionLimitError(); + } + // Push onto our stack + stack.push_back(this); + } + // EO EnvFrame ctor + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Remove frame from stack on destruction + EnvFrame::~EnvFrame() + { + // Pop from stack + stack.pop_back(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Register new variable on local stack + // Invoked mostly by stylesheet parser + VarRef EnvFrame::createVariable( + const EnvKey& name) + { + // Get local offset to new variable + uint32_t offset = (uint32_t)varIdxs.size(); + // Remember the variable name + varIdxs[name] = offset; + // Return stack index reference + return { idxs->varFrame, offset }; + } + // EO createVariable + + // Register new function on local stack + // Mostly invoked by built-in functions + // Then invoked for custom C-API function + // Finally for every parsed function rule + VarRef EnvFrame::createFunction( + const EnvKey& name) + { + // Check for existing function + auto it = fnIdxs.find(name); + if (it != fnIdxs.end()) { + return { idxs->fnFrame, it->second }; + } + // Get local offset to new function + uint32_t offset = (uint32_t)fnIdxs.size(); + // Remember the function name + fnIdxs[name] = offset; + // Return stack index reference + return { idxs->fnFrame, offset }; + } + // EO createFunction + + // Register new mixin on local stack + // Only invoked for mixin rules + // But also for content blocks + VarRef EnvFrame::createMixin( + const EnvKey& name) + { + // Check for existing mixin + auto it = mixIdxs.find(name); + if (it != mixIdxs.end()) { + return { idxs->mixFrame, it->second }; + } + // Get local offset to new mixin + uint32_t offset = (uint32_t)mixIdxs.size(); + // Remember the mixin name + mixIdxs[name] = offset; + // Return stack index reference + return { idxs->mixFrame, offset }; + } + // EO createMixin + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Get local variable by name, needed for most simplistic case + // for static variable optimization in loops. When we know that + // there is an existing local variable, we can always use that! + VarRef EnvFrame::getLocalVariableIdx(const EnvKey& name) + { + auto it = varIdxs.find(name); + if (it == varIdxs.end()) return {}; + return { idxs->varFrame, it->second }; + } + // EO getLocalVariableIdx + + // Return lookups in lexical manner. If [passThrough] is false, + // we abort the lexical lookup on any non-permeable scope frame. + VarRef EnvFrame::getMixinIdx(const EnvKey& name, bool passThrough) + { + EnvFrame* current = this; + while (current != nullptr) { + // Check if we already have this var + auto it = current->mixIdxs.find(name); + if (it != current->mixIdxs.end()) { + return { current->idxs->mixFrame, it->second }; + } + current = current->getParent(passThrough); + } + // Not found + return VarRef{}; + } + // EO getMixinIdx + + // Return lookups in lexical manner. If [passThrough] is false, + // we abort the lexical lookup on any non-permeable scope frame. + VarRef EnvFrame::getFunctionIdx(const EnvKey& name, bool passThrough) + { + EnvFrame* current = this; + while (current != nullptr) { + // Check if we already have this var + auto it = current->fnIdxs.find(name); + if (it != current->fnIdxs.end()) { + return { current->idxs->fnFrame, it->second }; + } + current = current->getParent(passThrough); + } + // Not found + return VarRef{}; + } + // EO getFunctionIdx + + // Return lookups in lexical manner. If [passThrough] is false, + // we abort the lexical lookup on any non-permeable scope frame. + VarRef EnvFrame::getVariableIdx(const EnvKey& name, bool passThrough) + { + EnvFrame* current = this; + while (current != nullptr) { + auto it = current->varIdxs.find(name); + if (it != current->varIdxs.end()) { + return { current->idxs->fnFrame, it->second }; + } + current = current->getParent(passThrough); + } + // Not found + return {}; + } + // EO getVariableIdx + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Update variable references and assignments + void EnvRoot::finalizeScopes() + { + + // Process every scope ever seen + for (VarRefs* scope : scopes) { + + // Process all variable expression (e.g. variables used in sass-scripts). + // Move up the tree to find possible parent scopes also containing this + // variable. On runtime we will return the first item that has a value set. + for (VariableExpression* variable : scope->variables) + { + VarRefs* current = scope; + const EnvKey& name(variable->name()); + while (current != nullptr) { + // Find the variable name in current scope + auto found = current->varIdxs.find(name); + if (found != current->varIdxs.end()) { + // Append alternative + variable->vidxs() + .emplace_back(VarRef{ + current->varFrame, + found->second + }); + } + // Process the parent scope + current = current->pscope; + } + } + // EO variables + + // Process all variable assignment rules. Assignments can bleed up to the + // parent scope under certain conditions. We bleed up regular style rules, + // but not into the root scope itself (until it is semi global scope). + for (AssignRule* assignment : scope->assignments) + { + VarRefs* current = scope; + while (current != nullptr) { + // If current scope is rooted we don't look further, as we + // already created the local variable and assigned reference. + if (!current->permeable) break; + // Start with the parent + current = current->pscope; + // Find the variable name in current scope + auto found = current->varIdxs + .find(assignment->variable()); + if (found != current->varIdxs.end()) { + // Append another alternative + assignment->vidxs() + .emplace_back(VarRef{ + current->varFrame, + found->second + }); + } + // EO found current var + } + // EO while parent + } + // EO assignments + + } + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Get value instance by stack index reference + // Just converting and returning reference to array offset + ValueObj& EnvRoot::getVariable(const VarRef& vidx) + { + return variables[size_t(varFramePtr[vidx.frame]) + vidx.offset]; + } + // EO getVariable + + // Get function instance by stack index reference + // Just converting and returning reference to array offset + UserDefinedCallableObj& EnvRoot::getFunction(const VarRef& fidx) + { + return functions[size_t(fnFramePtr[fidx.frame]) + fidx.offset]; + } + // EO getFunction + + // Get mixin instance by stack index reference + // Just converting and returning reference to array offset + UserDefinedCallableObj& EnvRoot::getMixin(const VarRef& midx) + { + return mixins[size_t(mixFramePtr[midx.frame]) + midx.offset]; + } + // EO getMixin + + // Set items on runtime/evaluation phase via references + // Just converting reference to array offset and assigning + void EnvRoot::setVariable(const VarRef& vidx, ValueObj value) + { + variables[size_t(varFramePtr[vidx.frame]) + vidx.offset] = value; + } + // EO setVariable + + // Set items on runtime/evaluation phase via references + // Just converting reference to array offset and assigning + void EnvRoot::setVariable(uint32_t frame, uint32_t offset, ValueObj value) + { + variables[size_t(varFramePtr[frame]) + offset] = value; + } + // EO setVariable + + // Set items on runtime/evaluation phase via references + // Just converting reference to array offset and assigning + void EnvRoot::setFunction(const VarRef& fidx, UserDefinedCallableObj value) + { + functions[size_t(fnFramePtr[fidx.frame]) + fidx.offset] = value; + } + // EO setFunction + + // Set items on runtime/evaluation phase via references + // Just converting reference to array offset and assigning + void EnvRoot::setMixin(const VarRef& midx, UserDefinedCallableObj value) + { + mixins[size_t(mixFramePtr[midx.frame]) + midx.offset] = value; + } + // EO setMixin + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Get a mixin associated with the under [name]. + // Will lookup from the last runtime stack scope. + // We will move up the runtime stack until we either + // find a defined mixin or run out of parent scopes. + UserDefinedCallable* EnvRoot::getMixin(const EnvKey& name) const + { + if (stack.empty()) return {}; + uint32_t idx = uint32_t(stack.size() - 1); + const VarRefs* current = stack[idx]; + while (current) { + auto it = current->mixIdxs.find(name); + if (it != current->mixIdxs.end()) { + const VarRef vidx{ current->mixFrame, it->second }; + UserDefinedCallableObj& value = root.getMixin(vidx); + if (!value.isNull()) return value; + } + if (current->pscope == nullptr) break; + else current = current->pscope; + } + return {}; + } + // EO getMixin + + // Get a function associated with the under [name]. + // Will lookup from the last runtime stack scope. + // We will move up the runtime stack until we either + // find a defined function or run out of parent scopes. + UserDefinedCallable* EnvRoot::getFunction(const EnvKey& name) const + { + if (stack.empty()) return {}; + uint32_t idx = uint32_t(stack.size() - 1); + const VarRefs* current = stack[idx]; + while (current) { + auto it = current->fnIdxs.find(name); + if (it != current->fnIdxs.end()) { + const VarRef vidx{ current->fnFrame, it->second }; + UserDefinedCallableObj& value = root.getFunction(vidx); + if (!value.isNull()) return value; + } + if (current->pscope == nullptr) break; + else current = current->pscope; + } + return {}; + } + // EO getFunction + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Get a value associated with the variable under [name]. + // If [global] flag is given, the lookup will be in the root. + // Otherwise lookup will be from the last runtime stack scope. + // We will move up the runtime stack until we either find a + // defined variable with a value or run out of parent scopes. + Value* EnvRoot::getVariable(const EnvKey& name, bool global) const + { + if (stack.empty()) return {}; + uint32_t idx = global ? 0 : + (uint32_t)stack.size() - 1; + const VarRefs* current = stack[idx]; + while (current) { + auto it = current->varIdxs.find(name); + if (it != current->varIdxs.end()) { + const VarRef vidx{ current->varFrame, it->second }; + ValueObj& value = root.getVariable(vidx); + if (value != nullptr) { return value; } + } + if (current->pscope == nullptr) break; + else current = current->pscope; + } + return {}; + } + // EO getVariable + + // Set a value associated with the variable under [name]. + // If [global] flag is given, the lookup will be in the root. + // Otherwise lookup will be from the last runtime stack scope. + // We will move up the runtime stack until we either find a + // defined variable with a value or run out of parent scopes. + void EnvRoot::setVariable(const EnvKey& name, ValueObj val, bool global) + { + if (stack.empty()) return; + uint32_t idx = global ? 0 : + (uint32_t)stack.size() - 1; + const VarRefs* current = stack[idx]; + while (current) { + auto it = current->varIdxs.find(name); + if (it != current->varIdxs.end()) { + const VarRef vidx{ current->varFrame, it->second }; + ValueObj& value = root.getVariable(vidx); + if (value != nullptr) { value = val; return; } + } + if (current->pscope == nullptr) break; + else current = current->pscope; + } + } + // EO setVariable + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Very small helper for debugging + sass::string VarRef::toString() const + { + sass::sstream strm; + strm << frame << ":" << offset; + return strm.str(); + } + // EO toString + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/environment_stack.hpp b/src/environment_stack.hpp new file mode 100644 index 0000000000..6a56fe37ef --- /dev/null +++ b/src/environment_stack.hpp @@ -0,0 +1,515 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_VAR_STACK_HPP +#define SASS_VAR_STACK_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" +#include "environment_cnt.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Forward declare + class VarRef; + class VarRefs; + class EnvRoot; + class EnvFrame; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Helper typedefs to test implementations + typedef EnvKeyMap VidxEnvKeyMap; + typedef EnvKeyMap MidxEnvKeyMap; + typedef EnvKeyMap FidxEnvKeyMap; + + // Helper typedef for our frame stack type + typedef sass::vector EnvFrameVector; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Base class/struct for variable references. Each variable belongs to an + // environment frame (determined as we see variables during parsing). This + // is similar to how C organizes local stack variables via frame offsets. + class VarRef { + + public: + + uint32_t frame; + uint32_t offset; + + VarRef() : + frame(0xFFFFFFFF), + offset(0xFFFFFFFF) + {} + + VarRef( + uint32_t frame, + uint32_t offset, + bool overwrites = false) : + frame(frame), + offset(offset) + {} + + bool operator==(const VarRef& rhs) const { + return frame == rhs.frame + && offset == rhs.offset; + } + + bool operator<(const VarRef& rhs) const { + if (frame < rhs.frame) return true; + return offset < rhs.offset; + } + + bool isValid() const { // 3% + return offset != 0xFFFFFFFF + && frame != 0xFFFFFFFF; + } + + operator bool() const { + return isValid(); + } + + // Very small helper for debugging + sass::string toString() const; + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Runtime query structure + // Created for every EnvFrame + // Survives the actual EnvFrame + class VarRefs { + public: + + // Parent is needed during runtime for + // dynamic setter and getter by EnvKey. + VarRefs* pscope; + + // Rules like `@if`, `@for` etc. are semi-global (permeable). + // Assignments directly in those can bleed to the root scope. + bool permeable; + + // Parents for specific types + uint32_t varFrame; + uint32_t mixFrame; + uint32_t fnFrame; + + // Remember named mappings + // Used for runtime lookups + // ToDo: test EnvKeyFlatMap + VidxEnvKeyMap varIdxs; + MidxEnvKeyMap mixIdxs; + FidxEnvKeyMap fnIdxs; + + // Keep track of assignments and variables for dynamic runtime lookups. + // This is needed only for loops, due to sass "weird" variable scoping. + std::vector assignments; + std::vector variables; + + // Value constructor + VarRefs(VarRefs* pscope, + uint32_t varFrame, + uint32_t mixFrame, + uint32_t fnFrame, + bool permeable) : + pscope(pscope), + permeable(permeable), + varFrame(varFrame), + mixFrame(mixFrame), + fnFrame(fnFrame) + {} + + }; + + ///////////////////////////////////////////////////////////////////////// + // EnvFrames are created during the parsing phase. + ///////////////////////////////////////////////////////////////////////// + + class EnvFrame { + + // We work together + friend class EnvRoot; + + public: + + // Reference to stack + // We manage it ourself + EnvFrameVector& stack; + + // New variables are hoisted at closest non-permeable. + // Lookups are still looking at all parents and root. + bool permeable; + + // Reference to parent + EnvFrame& parent; + + // Cache root reference + EnvRoot& root; + + // Our runtime object + VarRefs* idxs; + + // References into runtime object + VidxEnvKeyMap& varIdxs; + MidxEnvKeyMap& mixIdxs; + FidxEnvKeyMap& fnIdxs; + + // Keep track of assignments and variables for dynamic runtime lookups. + // This is needed only for loops, due to sass "weird" variable scoping. + std::vector& assignments; + std::vector& variables; + + private: + + // Root-frame constructor + // Invoked by EnvRoot ctor + EnvFrame( + EnvRoot& root, + EnvFrameVector& stack); + + public: + + // Value constructor + EnvFrame( + EnvFrameVector& stack, + // Rules like `@if`, `@for` etc. are semi-global (permeable). + // Assignments directly in those can bleed to the root scope. + bool permeable = false); + + // Destructor + ~EnvFrame(); + + // Test if we are top frame + bool isRoot() const { + // Check if raw pointers are equal + return this == (EnvFrame*)&root; + } + + // Get next parent, but break on root + EnvFrame* getParent(bool passThrough = false) { + if (isRoot()) + return nullptr; + if (!passThrough) + if (!permeable) + return nullptr; + return &parent; + } + + ///////////////////////////////////////////////////////////////////////// + // Register an occurrence during parsing, reserving the offset. + // Only structures are create when calling this, the real work + // is done on runtime, where actual stack objects are queried. + ///////////////////////////////////////////////////////////////////////// + + // Register new variable on local stack + // Invoked mostly by stylesheet parser + VarRef createVariable(const EnvKey& name); + + // Register new function on local stack + // Mostly invoked by built-in functions + // Then invoked for custom C-API function + // Finally for every parsed function rule + VarRef createFunction(const EnvKey& name); + + // Register new mixin on local stack + // Only invoked for mixin rules + // But also for content blocks + VarRef createMixin(const EnvKey& name); + + // Get local variable by name, needed for most simplistic case + // for static variable optimization in loops. When we know that + // there is an existing local variable, we can always use that! + VarRef getLocalVariableIdx(const EnvKey& name); + + // Return mixin in lexical manner. If [passThrough] is false, + // we abort the lexical lookup on any non-permeable scope frame. + VarRef getMixinIdx(const EnvKey& name, bool passThrough = true); + + // Return function in lexical manner. If [passThrough] is false, + // we abort the lexical lookup on any non-permeable scope frame. + VarRef getFunctionIdx(const EnvKey& name, bool passThrough = true); + + // Return variable in lexical manner. If [passThrough] is false, + // we abort the lexical lookup on any non-permeable scope frame. + VarRef getVariableIdx(const EnvKey& name, bool passThrough = false); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class EnvRoot : + public EnvFrame { + + private: + + // We work together + friend class Compiler; + friend class EnvScope; + friend class EnvFrame; + + // Growable runtime stack (get offset by xxxFramePtr). + // These vectors are the main stacks during runtime. + // When a scope with two variables is executed, two + // new items are added to the variables stack. If the + // same scope is called more than once, its variables + // are added multiple times so we can revert to them. + // variables[varFramePtr[vidx.frame] + vidx.offset] + sass::vector variables; + sass::vector mixins; + sass::vector functions; + + // Every scope we execute in sass gets an entry here. + // The value stored here is the base address of the + // active scope, used to calculate the final offset. + // Gives current offset into growable runtime stack. + // Old values are restored when scopes are exited. + // Access it by absolute `frameOffset` + sass::vector varFramePtr; + sass::vector mixFramePtr; + sass::vector fnFramePtr; + + // All created runtime variable objects. + // Needed to track the memory allocations + // And useful to resolve parents indirectly + // Access it by absolute `frameOffset` + sass::vector scopes; + + // The current runtime stack + sass::vector stack; + + public: + + // Value constructor + EnvRoot(EnvFrameVector& stack); + + // Destructor + ~EnvRoot() { + // Take care of scope pointers + for (VarRefs* idx : scopes) { + delete idx; + } + } + + // Update variable references and assignments + // Process all variable expression (e.g. variables used in sass-scripts). + // Move up the tree to find possible parent scopes also containing this + // variable. On runtime we will return the first item that has a value set. + // Process all variable assignment rules. Assignments can bleed up to the + // parent scope under certain conditions. We bleed up regular style rules, + // but not into the root scope itself (until it is semi global scope). + void finalizeScopes(); + + // Runtime check to see if we are currently in global scope + bool isGlobal() const { return root.stack.size() == 1; } + + // Get value instance by stack index reference + // Just converting and returning reference to array offset + ValueObj& getVariable(const VarRef& vidx); + + // Get function instance by stack index reference + // Just converting and returning reference to array offset + UserDefinedCallableObj& getFunction(const VarRef& vidx); + + // Get mixin instance by stack index reference + // Just converting and returning reference to array offset + UserDefinedCallableObj& getMixin(const VarRef& midx); + + // Set items on runtime/evaluation phase via references + // Just converting reference to array offset and assigning + void setVariable(const VarRef& vidx, ValueObj value); + + // Set items on runtime/evaluation phase via references + // Just converting reference to array offset and assigning + void setVariable(uint32_t frame, uint32_t offset, ValueObj value); + + // Set items on runtime/evaluation phase via references + // Just converting reference to array offset and assigning + void setFunction(const VarRef& fidx, UserDefinedCallableObj value); + + // Set items on runtime/evaluation phase via references + // Just converting reference to array offset and assigning + void setMixin(const VarRef& midx, UserDefinedCallableObj value); + + // Get a mixin associated with the under [name]. + // Will lookup from the last runtime stack scope. + // We will move up the runtime stack until we either + // find a defined mixin or run out of parent scopes. + UserDefinedCallable* getMixin(const EnvKey& name) const; + + // Get a function associated with the under [name]. + // Will lookup from the last runtime stack scope. + // We will move up the runtime stack until we either + // find a defined function or run out of parent scopes. + UserDefinedCallable* getFunction(const EnvKey& name) const; + + // Get a value associated with the variable under [name]. + // If [global] flag is given, the lookup will be in the root. + // Otherwise lookup will be from the last runtime stack scope. + // We will move up the runtime stack until we either find a + // defined variable with a value or run out of parent scopes. + Value* getVariable(const EnvKey& name, bool global = false) const; + + // Set a value associated with the variable under [name]. + // If [global] flag is given, the lookup will be in the root. + // Otherwise lookup will be from the last runtime stack scope. + // We will move up the runtime stack until we either find a + // defined variable with a value or run out of parent scopes. + void setVariable(const EnvKey& name, ValueObj val, bool global = false); + + }; + + ///////////////////////////////////////////////////////////////////////// + // EnvScopes are created during evaluation phase. When we enter a parsed + // scope, e.g. a function, mixin or style-rule, we create a new EnvScope + // object on the stack and pass it the runtime environment and the current + // stack frame (in form of a VarRefs pointer). We will "allocate" the needed + // space for scope items and update any offset pointers. Once we go out of + // scope the previous state is restored by unwinding the runtime stack. + ///////////////////////////////////////////////////////////////////////// + + class EnvScope { + + private: + + // Runtime environment + EnvRoot& env; + + // Frame stack index references + VarRefs* idxs; + + // Remember previous "addresses" + // Restored when we go out of scope + uint32_t oldVarFrame; + uint32_t oldVarOffset; + uint32_t oldMixFrame; + uint32_t oldMixOffset; + uint32_t oldFnFrame; + uint32_t oldFnOffset; + + public: + + // Put frame onto stack + EnvScope( + EnvRoot& env, + VarRefs* idxs) : + env(env), + idxs(idxs), + oldVarFrame(0), + oldVarOffset(0), + oldMixFrame(0), + oldMixOffset(0), + oldFnFrame(0), + oldFnOffset(0) + { + + // The frame might be fully empty + // Meaning it no scoped items at all + if (idxs == nullptr) return; + + // Check if we have scoped variables + if (idxs->varIdxs.size() != 0) { + // Get offset into variable vector + oldVarOffset = (uint32_t)env.variables.size(); + // Remember previous frame "addresses" + oldVarFrame = env.varFramePtr[idxs->varFrame]; + // Update current frame offset address + env.varFramePtr[idxs->varFrame] = oldVarOffset; + // Create space for variables in this frame scope + env.variables.resize(oldVarOffset + idxs->varIdxs.size()); + } + + // Check if we have scoped mixins + if (idxs->mixIdxs.size() != 0) { + // Get offset into mixin vector + oldMixOffset = (uint32_t)env.mixins.size(); + // Remember previous frame "addresses" + oldMixFrame = env.mixFramePtr[idxs->mixFrame]; + // Update current frame offset address + env.mixFramePtr[idxs->mixFrame] = oldMixOffset; + // Create space for mixins in this frame scope + env.mixins.resize(oldMixOffset + idxs->mixIdxs.size()); + } + + // Check if we have scoped functions + if (idxs->fnIdxs.size() != 0) { + // Get offset into function vector + oldFnOffset = (uint32_t)env.functions.size(); + // Remember previous frame "addresses" + oldFnFrame = env.fnFramePtr[idxs->fnFrame]; + // Update current frame offset address + env.fnFramePtr[idxs->fnFrame] = oldFnOffset; + // Create space for functions in this frame scope + env.functions.resize(oldFnOffset + idxs->fnIdxs.size()); + } + + // Push frame onto stack + // Mostly for dynamic lookups + env.stack.push_back(idxs); + + } + // EO ctor + + // Restore old state on destruction + ~EnvScope() + { + + // The frame might be fully empty + // Meaning it no scoped items at all + if (idxs == nullptr) return; + + // Check if we had scoped variables + if (idxs->varIdxs.size() != 0) { + // Truncate variable vector + env.variables.resize( + oldVarOffset); + // Restore old frame address + env.varFramePtr[idxs->varFrame] = + oldVarFrame; + } + + // Check if we had scoped mixins + if (idxs->mixIdxs.size() != 0) { + // Truncate existing vector + env.mixins.resize( + oldMixOffset); + // Restore old frame address + env.mixFramePtr[idxs->mixFrame] = + oldMixFrame; + } + + // Check if we had scoped functions + if (idxs->fnIdxs.size() != 0) { + // Truncate existing vector + env.functions.resize( + oldFnOffset); + // Restore old frame address + env.fnFramePtr[idxs->fnFrame] = + oldFnFrame; + } + + // Pop frame from stack + env.stack.pop_back(); + + } + // EO dtor + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/error_handling.cpp b/src/error_handling.cpp index d96fe529eb..23a1440ea5 100644 --- a/src/error_handling.cpp +++ b/src/error_handling.cpp @@ -1,233 +1,305 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "ast.hpp" -#include "prelexer.hpp" -#include "backtrace.hpp" #include "error_handling.hpp" -#include +#include "ast_selectors.hpp" +#include "exceptions.hpp" +#include "extension.hpp" namespace Sass { + StackTraces convertTraces(BackTraces traces) + { + // This will trigger StackTrace constructor + // Copies necessary stuff from BackTrace + return { traces.begin(), traces.end() }; + } + namespace Exception { - Base::Base(SourceSpan pstate, sass::string msg, Backtraces traces) + Base::Base(sass::string msg, BackTraces traces) : std::runtime_error(msg.c_str()), msg(msg), - prefix("Error"), pstate(pstate), traces(traces) - { } - - InvalidSass::InvalidSass(SourceSpan pstate, Backtraces traces, sass::string msg) - : Base(pstate, msg, traces) + traces(traces.begin(), traces.end()) { } - - InvalidParent::InvalidParent(Selector* parent, Backtraces traces, Selector* selector) - : Base(selector->pstate(), def_msg, traces), parent(parent), selector(selector) + Base::Base(sass::string msg, BackTraces traces, SourceSpan pstate) + : Base(msg, traces) { - msg = "Invalid parent selector for " - "\"" + selector->to_string(Sass_Inspect_Options()) + "\": " - "\"" + parent->to_string(Sass_Inspect_Options()) + "\""; + Base::traces.push_back(pstate); } - InvalidVarKwdType::InvalidVarKwdType(SourceSpan pstate, Backtraces traces, sass::string name, const Argument* arg) - : Base(pstate, def_msg, traces), name(name), arg(arg) - { - msg = "Variable keyword argument map must have string keys.\n" + - name + " is not a string in " + arg->to_string() + "."; - } + SassRuntimeException2::SassRuntimeException2( + sass::string msg, BackTraces traces) + : Base(msg, traces) {} - InvalidArgumentType::InvalidArgumentType(SourceSpan pstate, Backtraces traces, sass::string fn, sass::string arg, sass::string type, const Value* value) - : Base(pstate, def_msg, traces), fn(fn), arg(arg), type(type), value(value) - { - msg = arg + ": \""; - if (value) msg += value->to_string(Sass_Inspect_Options()); - msg += "\" is not a " + type + " for `" + fn + "'"; - } + SassRuntimeException2::SassRuntimeException2( + sass::string msg, BackTraces traces, SourceSpan pstate) + : Base(msg, traces, pstate) {} - MissingArgument::MissingArgument(SourceSpan pstate, Backtraces traces, sass::string fn, sass::string arg, sass::string fntype) - : Base(pstate, def_msg, traces), fn(fn), arg(arg), fntype(fntype) + InvalidParent::InvalidParent(Selector* parent, BackTraces traces, Selector* selector) + : Base(def_msg, traces, selector->pstate()), parent(parent), selector(selector) { - msg = fntype + " " + fn + " is missing argument " + arg + "."; + msg = "Parent " + "\"" + parent->inspect() + "\"" + " is incompatible with this selector."; } - InvalidSyntax::InvalidSyntax(SourceSpan pstate, Backtraces traces, sass::string msg) - : Base(pstate, msg, traces) + InvalidSyntax::InvalidSyntax(BackTraces traces, sass::string msg) + : Base(msg, traces) { } - NestingLimitError::NestingLimitError(SourceSpan pstate, Backtraces traces, sass::string msg) - : Base(pstate, msg, traces) + CustomImportError::CustomImportError(BackTraces traces, sass::string msg) + : Base(msg, traces) { } - DuplicateKeyError::DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org) - : Base(org.pstate(), def_msg, traces), dup(dup), org(org) + CustomImportNotFound::CustomImportNotFound(BackTraces traces, sass::string file) + : Base(def_msg, traces) { - msg = "Duplicate key " + dup.get_duplicate_key()->inspect() + " in map (" + org.inspect() + ")."; + msg = "Can't find stylesheet \"" + file + "\"."; + msg += "\nAs requested by custom importer."; } - TypeMismatch::TypeMismatch(Backtraces traces, const Expression& var, const sass::string type) - : Base(var.pstate(), def_msg, traces), var(var), type(type) + CustomImportAmbigous::CustomImportAmbigous(BackTraces traces, sass::string file) + : Base(def_msg, traces) { - msg = var.to_string() + " is not an " + type + "."; + msg = "CustomImportAmbigous \"" + file + "\"."; + msg += "\nAs requested by custom importer."; } - InvalidValue::InvalidValue(Backtraces traces, const Expression& val) - : Base(val.pstate(), def_msg, traces), val(val) + CustomImportLoadError::CustomImportLoadError(BackTraces traces, sass::string file) + : Base(def_msg, traces) { - msg = val.to_string() + " isn't a valid CSS value."; + msg = "CustomImportLoadError \"" + file + "\"."; + msg += "\nAs requested by custom importer."; } + + + RecursionLimitError::RecursionLimitError() + : Base(msg_recursion_limit, {}) {} - StackError::StackError(Backtraces traces, const AST_Node& node) - : Base(node.pstate(), def_msg, traces), node(node) + DuplicateKeyError::DuplicateKeyError(BackTraces traces, const Map& dup, const Value& org) + : Base(def_msg, traces), dup(dup), org(org) { - msg = "stack level too deep"; + // msg = "Duplicate key " + dup.get_duplicate_key()->inspect() + " in map (" + org.inspect() + ")."; + msg = "Duplicate key."; // dart-sass keeps it simple ... } - IncompatibleUnits::IncompatibleUnits(const Units& lhs, const Units& rhs) + sass::string formatUnknownNamedArgument(const EnvKeyFlatMap& names) { - msg = "Incompatible units: '" + rhs.unit() + "' and '" + lhs.unit() + "'."; + sass::string msg("No "); + msg += pluralize(Strings::argument, names.size()); + msg += " named "; + msg += toSentence(names, Strings::_or_); + msg += "."; + return msg; } - IncompatibleUnits::IncompatibleUnits(const UnitType lhs, const UnitType rhs) - { - msg = sass::string("Incompatible units: '") + unit_to_string(rhs) + "' and '" + unit_to_string(lhs) + "'."; + sass::string formatTooManyArguments(size_t given, size_t expected) { + sass::ostream msg; + msg << "Only " << expected << " "; + msg << pluralize("argument", expected); + msg << " allowed, but " << given << " "; + msg << pluralize("was", given, "were"); + msg << " passed."; + return msg.str(); } - AlphaChannelsNotEqual::AlphaChannelsNotEqual(const Expression* lhs, const Expression* rhs, enum Sass_OP op) - : OperationError(), lhs(lhs), rhs(rhs), op(op) - { - msg = "Alpha channels must be equal: " + - lhs->to_string({ NESTED, 5 }) + - " " + sass_op_to_name(op) + " " + - rhs->to_string({ NESTED, 5 }) + "."; + sass::string formatTooManyArguments(const EnvKeyFlatMap& given, const Sass::EnvKeySet& expected) { + Sass::EnvKeySet superfluous; + for (auto pair : given) { + if (expected.count(pair.first) == 0) { + superfluous.insert(pair.first); + } + } + return "No argument named " + + toSentence(superfluous, "or") + "."; } - ZeroDivisionError::ZeroDivisionError(const Expression& lhs, const Expression& rhs) - : OperationError(), lhs(lhs), rhs(rhs) - { - msg = "divided by 0"; + sass::string formatTooManyArguments(const EnvKeyFlatMap& given, const Sass::EnvKeySet& expected) { + Sass::EnvKeySet superfluous; + for (auto pair : given) { + if (expected.count(pair.first) == 0) { + superfluous.insert(pair.first); + } + } + return "No argument named " + + toSentence(superfluous, "or") + "."; } - UndefinedOperation::UndefinedOperation(const Expression* lhs, const Expression* rhs, enum Sass_OP op) - : OperationError(), lhs(lhs), rhs(rhs), op(op) - { - msg = def_op_msg + ": \"" + - lhs->to_string({ NESTED, 5 }) + - " " + sass_op_to_name(op) + " " + - rhs->to_string({ TO_SASS, 5 }) + - "\"."; + sass::string formatTooManyArguments(const EnvKeyFlatMap& superfluous) { + return "No argument named " + + toSentence(superfluous, "or") + "."; } - InvalidNullOperation::InvalidNullOperation(const Expression* lhs, const Expression* rhs, enum Sass_OP op) - : UndefinedOperation(lhs, rhs, op) + TooManyArguments::TooManyArguments(BackTraces traces, size_t given, size_t expected) + : Base(formatTooManyArguments(given, expected), traces) + {} + + TooManyArguments::TooManyArguments(BackTraces traces, const EnvKeyFlatMap& given, const Sass::EnvKeySet& expected) + : Base(formatTooManyArguments(given, expected), traces) + {} + + TooManyArguments::TooManyArguments(BackTraces traces, const EnvKeyFlatMap& superflous) + : Base(formatTooManyArguments(superflous), traces) + {} + + MissingArgument::MissingArgument(BackTraces traces, const EnvKey& name) + : Base("Missing argument " + name.norm() + ".", traces) + {} + + ArgumentGivenTwice::ArgumentGivenTwice(BackTraces traces, const EnvKey& name) + : Base("Argument " + name.norm() + " name was passed both by position and by name.", traces) + {} + + UnknownNamedArgument::UnknownNamedArgument(SourceSpan pstate, BackTraces traces, EnvKeyFlatMap names) + : Base(formatUnknownNamedArgument(names), traces, pstate) { - msg = def_op_null_msg + ": \"" + lhs->inspect() + " " + sass_op_to_name(op) + " " + rhs->inspect() + "\"."; } - SassValueError::SassValueError(Backtraces traces, SourceSpan pstate, OperationError& err) - : Base(pstate, err.what(), traces) + UnknownNamedArgument2::UnknownNamedArgument2(BackTraces traces, EnvKeyFlatMap names) + : Base(formatUnknownNamedArgument(names), traces) { - msg = err.what(); - prefix = err.errtype(); } - TopLevelParent::TopLevelParent(Backtraces traces, SourceSpan pstate) - : Base(pstate, "Top-level selectors may not contain the parent selector \"&\".", traces) + sass::string formatUnknownNamedArgument2(const EnvKeyFlatMap& names) { - + sass::string msg("No "); + msg += pluralize(Strings::argument, names.size()); + msg += " named "; + msg += toSentence(names, Strings::_or_); + msg += "."; + return msg; } - UnsatisfiedExtend::UnsatisfiedExtend(Backtraces traces, Extension extension) - : Base(extension.target->pstate(), "The target selector was not found.\n" - "Use \"@extend " + extension.target->to_string() + " !optional\" to avoid this error.", traces) - { + InvalidCssValue::InvalidCssValue(BackTraces traces, const Value& val) + : Base(val.inspect() + " isn't a valid CSS value.", traces, val.pstate()) + {} + + InvalidCssValue2::InvalidCssValue2(BackTraces traces, const Value& val) + : Base(val.inspect() + " isn't a valid CSS value.", traces, val.pstate()) + {} + + // Thrown when a parent selector is used without any parent + TopLevelParent::TopLevelParent(BackTraces traces, SourceSpan pstate) + : Base("Top-level selectors may not contain the parent selector \"&\".", traces, pstate) + {} + + // Thrown when a non-optional extend found nothing to extend + UnsatisfiedExtend::UnsatisfiedExtend(BackTraces traces, Extension extension) + : Base("The target selector was not found.\n" + // Calling inspect to the placeholder is visible + "Use \"@extend " + extension.target->inspect() + + " !optional\" to avoid this error.", + traces, extension.target->pstate()) + {} + + // Thrown when we extend across incompatible media contexts + ExtendAcrossMedia::ExtendAcrossMedia(BackTraces traces, Extension extension) + : Base("You may not @extend selectors across media queries.", traces) + {} + + // Thrown when we find an unexpected UTF8 sequence + InvalidUnicode::InvalidUnicode(SourceSpan pstate, BackTraces traces) + : Base("Invalid UTF-8.", traces, pstate) + {} + + SassScriptException::SassScriptException(sass::string msg, + BackTraces traces, SourceSpan pstate, sass::string name) : + Base(name.empty() ? msg : "$" + name + ": " + msg, traces) + {} + + + - } - ExtendAcrossMedia::ExtendAcrossMedia(Backtraces traces, Extension extension) - : Base(extension.target->pstate(), "You may not @extend selectors across media queries.\n" - "Use \"@extend " + extension.target->to_string() + " !optional\" to avoid this error.", traces) - { - } - - } - void warn(sass::string msg, SourceSpan pstate) - { - std::cerr << "Warning: " << msg << std::endl; - } - void warning(sass::string msg, SourceSpan pstate) - { - sass::string cwd(Sass::File::get_cwd()); - sass::string abs_path(Sass::File::rel2abs(pstate.getPath(), cwd, cwd)); - sass::string rel_path(Sass::File::abs2rel(pstate.getPath(), cwd, cwd)); - sass::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.getPath())); - std::cerr << "WARNING on line " << pstate.getLine() << ", column " << pstate.getColumn() << " of " << output_path << ":" << std::endl; - std::cerr << msg << std::endl << std::endl; - } - void warn(sass::string msg, SourceSpan pstate, Backtrace* bt) - { - warn(msg, pstate); - } - void deprecated_function(sass::string msg, SourceSpan pstate) - { - sass::string cwd(Sass::File::get_cwd()); - sass::string abs_path(Sass::File::rel2abs(pstate.getPath(), cwd, cwd)); - sass::string rel_path(Sass::File::abs2rel(pstate.getPath(), cwd, cwd)); - sass::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.getPath())); - - std::cerr << "DEPRECATION WARNING: " << msg << std::endl; - std::cerr << "will be an error in future versions of Sass." << std::endl; - std::cerr << " on line " << pstate.getLine() << " of " << output_path << std::endl; - } + + + + + + + + + + + ///////////////////////////////////////////////////////////////////////// + // Various value operation errors + ///////////////////////////////////////////////////////////////////////// + + ZeroDivisionError::ZeroDivisionError(const Value& lhs, const Value& rhs) + : OperationError("divided by 0") + {} + + IncompatibleUnits::IncompatibleUnits(const Units& lhs, const Units& rhs) + : OperationError("Incompatible units " + + rhs.unit() + " and " + + lhs.unit() + ".") + {} + + AlphaChannelsNotEqual::AlphaChannelsNotEqual(const ColorRgba* lhs, const ColorRgba* rhs, enum SassOperator op) + : OperationError("Alpha channels must be equal: " + + lhs->inspect() + " " + + sass_op_to_name(op) + " " + + rhs->inspect() + ".") + {} + + + InvalidNullOperation::InvalidNullOperation(const Value* lhs, const Value* rhs, enum SassOperator op) + : OperationError("Invalid null operation: \"" + + lhs->inspect() + " " + + sass_op_to_name(op) + " " + + rhs->inspect() + "\".") + {} + + UndefinedOperation::UndefinedOperation(const Value* lhs, const Value* rhs, enum SassOperator op) + : OperationError("Undefined operation: \"" + + lhs->inspect() + " " + + sass_op_separator(op) + " " + + rhs->inspect() + "\".") + {} + + + + + + + + + + + + + + + + + + +} void deprecated(sass::string msg, sass::string msg2, bool with_column, SourceSpan pstate) { - sass::string cwd(Sass::File::get_cwd()); - sass::string abs_path(Sass::File::rel2abs(pstate.getPath(), cwd, cwd)); - sass::string rel_path(Sass::File::abs2rel(pstate.getPath(), cwd, cwd)); - sass::string output_path(Sass::File::path_for_console(rel_path, pstate.getPath(), pstate.getPath())); - + sass::string output_path(pstate.getDebugPath()); std::cerr << "DEPRECATION WARNING on line " << pstate.getLine(); - // if (with_column) std::cerr << ", column " << pstate.column + pstate.offset.column + 1; + if (with_column) std::cerr << ", column " << pstate.getColumn(); if (output_path.length()) std::cerr << " of " << output_path; - std::cerr << ":" << std::endl; - std::cerr << msg << std::endl; - if (msg2.length()) std::cerr << msg2 << std::endl; - std::cerr << std::endl; + std::cerr << ':' << STRMLF; + std::cerr << msg << STRMLF; + if (msg2.length()) std::cerr << msg2 << STRMLF; + std::cerr << STRMLF; } - void deprecated_bind(sass::string msg, SourceSpan pstate) - { - sass::string cwd(Sass::File::get_cwd()); - sass::string abs_path(Sass::File::rel2abs(pstate.getPath(), cwd, cwd)); - sass::string rel_path(Sass::File::abs2rel(pstate.getPath(), cwd, cwd)); - sass::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.getPath())); - - std::cerr << "WARNING: " << msg << std::endl; - std::cerr << " on line " << pstate.getLine() << " of " << output_path << std::endl; - std::cerr << "This will be an error in future versions of Sass." << std::endl; - } - - // should be replaced with error with backtraces - void coreError(sass::string msg, SourceSpan pstate) - { - Backtraces traces; - throw Exception::InvalidSyntax(pstate, traces, msg); - } - void error(sass::string msg, SourceSpan pstate, Backtraces& traces) + void error(const sass::string& msg, SourceSpan pstate, BackTraces& traces) { - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidSyntax(pstate, traces, msg); + traces.push_back(BackTrace(pstate)); + throw Exception::InvalidSyntax(traces, msg); } } diff --git a/src/error_handling.hpp b/src/error_handling.hpp deleted file mode 100644 index a7e8efc8ca..0000000000 --- a/src/error_handling.hpp +++ /dev/null @@ -1,239 +0,0 @@ -#ifndef SASS_ERROR_HANDLING_H -#define SASS_ERROR_HANDLING_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include -#include "units.hpp" -#include "position.hpp" -#include "backtrace.hpp" -#include "ast_fwd_decl.hpp" -#include "sass/functions.h" - -namespace Sass { - - struct Backtrace; - - namespace Exception { - - const sass::string def_msg = "Invalid sass detected"; - const sass::string def_op_msg = "Undefined operation"; - const sass::string def_op_null_msg = "Invalid null operation"; - const sass::string def_nesting_limit = "Code too deeply nested"; - - class Base : public std::runtime_error { - protected: - sass::string msg; - sass::string prefix; - public: - SourceSpan pstate; - Backtraces traces; - public: - Base(SourceSpan pstate, sass::string msg, Backtraces traces); - virtual const char* errtype() const { return prefix.c_str(); } - virtual const char* what() const throw() { return msg.c_str(); } - virtual ~Base() throw() {}; - }; - - class InvalidSass : public Base { - public: - InvalidSass(SourceSpan pstate, Backtraces traces, sass::string msg); - virtual ~InvalidSass() throw() {}; - }; - - class InvalidParent : public Base { - protected: - Selector* parent; - Selector* selector; - public: - InvalidParent(Selector* parent, Backtraces traces, Selector* selector); - virtual ~InvalidParent() throw() {}; - }; - - class MissingArgument : public Base { - protected: - sass::string fn; - sass::string arg; - sass::string fntype; - public: - MissingArgument(SourceSpan pstate, Backtraces traces, sass::string fn, sass::string arg, sass::string fntype); - virtual ~MissingArgument() throw() {}; - }; - - class InvalidArgumentType : public Base { - protected: - sass::string fn; - sass::string arg; - sass::string type; - const Value* value; - public: - InvalidArgumentType(SourceSpan pstate, Backtraces traces, sass::string fn, sass::string arg, sass::string type, const Value* value = 0); - virtual ~InvalidArgumentType() throw() {}; - }; - - class InvalidVarKwdType : public Base { - protected: - sass::string name; - const Argument* arg; - public: - InvalidVarKwdType(SourceSpan pstate, Backtraces traces, sass::string name, const Argument* arg = 0); - virtual ~InvalidVarKwdType() throw() {}; - }; - - class InvalidSyntax : public Base { - public: - InvalidSyntax(SourceSpan pstate, Backtraces traces, sass::string msg); - virtual ~InvalidSyntax() throw() {}; - }; - - class NestingLimitError : public Base { - public: - NestingLimitError(SourceSpan pstate, Backtraces traces, sass::string msg = def_nesting_limit); - virtual ~NestingLimitError() throw() {}; - }; - - class DuplicateKeyError : public Base { - protected: - const Map& dup; - const Expression& org; - public: - DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org); - virtual const char* errtype() const { return "Error"; } - virtual ~DuplicateKeyError() throw() {}; - }; - - class TypeMismatch : public Base { - protected: - const Expression& var; - const sass::string type; - public: - TypeMismatch(Backtraces traces, const Expression& var, const sass::string type); - virtual const char* errtype() const { return "Error"; } - virtual ~TypeMismatch() throw() {}; - }; - - class InvalidValue : public Base { - protected: - const Expression& val; - public: - InvalidValue(Backtraces traces, const Expression& val); - virtual const char* errtype() const { return "Error"; } - virtual ~InvalidValue() throw() {}; - }; - - class StackError : public Base { - protected: - const AST_Node& node; - public: - StackError(Backtraces traces, const AST_Node& node); - virtual const char* errtype() const { return "SystemStackError"; } - virtual ~StackError() throw() {}; - }; - - /* common virtual base class (has no pstate or trace) */ - class OperationError : public std::runtime_error { - protected: - sass::string msg; - public: - OperationError(sass::string msg = def_op_msg) - : std::runtime_error(msg.c_str()), msg(msg) - {}; - public: - virtual const char* errtype() const { return "Error"; } - virtual const char* what() const throw() { return msg.c_str(); } - virtual ~OperationError() throw() {}; - }; - - class ZeroDivisionError : public OperationError { - protected: - const Expression& lhs; - const Expression& rhs; - public: - ZeroDivisionError(const Expression& lhs, const Expression& rhs); - virtual const char* errtype() const { return "ZeroDivisionError"; } - virtual ~ZeroDivisionError() throw() {}; - }; - - class IncompatibleUnits : public OperationError { - protected: - // const Sass::UnitType lhs; - // const Sass::UnitType rhs; - public: - IncompatibleUnits(const Units& lhs, const Units& rhs); - IncompatibleUnits(const UnitType lhs, const UnitType rhs); - virtual ~IncompatibleUnits() throw() {}; - }; - - class UndefinedOperation : public OperationError { - protected: - const Expression* lhs; - const Expression* rhs; - const Sass_OP op; - public: - UndefinedOperation(const Expression* lhs, const Expression* rhs, enum Sass_OP op); - // virtual const char* errtype() const { return "Error"; } - virtual ~UndefinedOperation() throw() {}; - }; - - class InvalidNullOperation : public UndefinedOperation { - public: - InvalidNullOperation(const Expression* lhs, const Expression* rhs, enum Sass_OP op); - virtual ~InvalidNullOperation() throw() {}; - }; - - class AlphaChannelsNotEqual : public OperationError { - protected: - const Expression* lhs; - const Expression* rhs; - const Sass_OP op; - public: - AlphaChannelsNotEqual(const Expression* lhs, const Expression* rhs, enum Sass_OP op); - // virtual const char* errtype() const { return "Error"; } - virtual ~AlphaChannelsNotEqual() throw() {}; - }; - - class SassValueError : public Base { - public: - SassValueError(Backtraces traces, SourceSpan pstate, OperationError& err); - virtual ~SassValueError() throw() {}; - }; - - class TopLevelParent : public Base { - public: - TopLevelParent(Backtraces traces, SourceSpan pstate); - virtual ~TopLevelParent() throw() {}; - }; - - class UnsatisfiedExtend : public Base { - public: - UnsatisfiedExtend(Backtraces traces, Extension extension); - virtual ~UnsatisfiedExtend() throw() {}; - }; - - class ExtendAcrossMedia : public Base { - public: - ExtendAcrossMedia(Backtraces traces, Extension extension); - virtual ~ExtendAcrossMedia() throw() {}; - }; - - } - - void warn(sass::string msg, SourceSpan pstate); - void warn(sass::string msg, SourceSpan pstate, Backtrace* bt); - void warning(sass::string msg, SourceSpan pstate); - - void deprecated_function(sass::string msg, SourceSpan pstate); - void deprecated(sass::string msg, sass::string msg2, bool with_column, SourceSpan pstate); - void deprecated_bind(sass::string msg, SourceSpan pstate); - // void deprecated(sass::string msg, SourceSpan pstate, Backtrace* bt); - - void coreError(sass::string msg, SourceSpan pstate); - void error(sass::string msg, SourceSpan pstate, Backtraces& traces); - -} - -#endif diff --git a/src/eval.cpp b/src/eval.cpp index d6540dabfa..e84da110d6 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -1,1543 +1,2241 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include -#include -#include -#include - -#include "file.hpp" #include "eval.hpp" -#include "ast.hpp" -#include "bind.hpp" -#include "util.hpp" -#include "inspect.hpp" -#include "operators.hpp" -#include "environment.hpp" -#include "position.hpp" -#include "sass/values.h" -#include "to_value.hpp" -#include "ast2c.hpp" -#include "c2ast.hpp" -#include "context.hpp" -#include "backtrace.hpp" -#include "lexer.hpp" -#include "prelexer.hpp" -#include "parser.hpp" -#include "expand.hpp" -#include "color_maps.hpp" -#include "sass_functions.hpp" -#include "error_handling.hpp" -#include "util_string.hpp" + +#include "cssize.hpp" +#include "compiler.hpp" +#include "stylesheet.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" +#include "ast_selectors.hpp" +#include "ast_callables.hpp" +#include "ast_statements.hpp" +#include "ast_expressions.hpp" +#include "parser_selector.hpp" +#include "parser_media_query.hpp" +#include "parser_keyframe_selector.hpp" namespace Sass { - Eval::Eval(Expand& exp) - : exp(exp), - ctx(exp.ctx), - traces(exp.traces), - force(false), - is_in_comment(false), - is_in_selector_schema(false) + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Eval::Eval(Compiler& compiler, Logger& logger, bool plainCss) : + logger456(logger), + compiler(compiler), + traces(logger), + extender( + Extender::NORMAL, + logger456), + plainCss(plainCss), + inMixin(false), + inFunction(false), + inUnknownAtRule(false), + atRootExcludingStyleRule(false), + inKeyframes(false) { - bool_true = SASS_MEMORY_NEW(Boolean, "[NA]", true); - bool_false = SASS_MEMORY_NEW(Boolean, "[NA]", false); + + mediaStack.push_back({}); + selectorStack.push_back({}); + originalStack.push_back({}); + + bool_true = SASS_MEMORY_NEW(Boolean, SourceSpan::tmp("[TRUE]"), true); + bool_false = SASS_MEMORY_NEW(Boolean, SourceSpan::tmp("[FALSE]"), false); } - Eval::~Eval() { } - Env* Eval::environment() + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Helper function for the division + Value* doDivision(Value* left, Value* right, + bool allowSlash, Logger& logger, SourceSpan pstate) { - return exp.environment(); + ValueObj result = left->dividedBy(right, logger, pstate); + if (Number* rv = result->isaNumber()) { + if (allowSlash && left && right) { + rv->lhsAsSlash(left->isaNumber()); + rv->rhsAsSlash(right->isaNumber()); + } + else { + rv->lhsAsSlash({}); // reset + rv->lhsAsSlash({}); // reset + } + } + return result.detach(); } - const sass::string Eval::cwd() + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Fetch unevaluated positional argument (optionally by name) + // Will error if argument is missing or available both ways + // Note: only needed for lazy evaluation in if expressions + Expression* Eval::getArgument( + ExpressionVector& positional, + ExpressionFlatMap& named, + size_t idx, const EnvKey& name) { - return ctx.cwd(); + // Try to find the argument by name + auto it = named.find(name); + // Check if requested index is available + if (positional.size() > idx) { + // Check if argument is also known by name + if (it != named.end()) { + // Raise error since it's ambiguous + throw Exception::ArgumentGivenTwice( + logger456, name); + } + // Return the positional value + return positional[idx]; + } + else if (it != named.end()) { + // Return the expression + return it->second; + } + // Raise error since nothing was found + throw Exception::MissingArgument( + logger456, name); } - struct Sass_Inspect_Options& Eval::options() + // Fetch evaluated positional argument (optionally by name) + // Will error if argument is missing or available both ways + // Named arguments are consumed and removed from the hash + Value* Eval::getParameter( + ArgumentResults& results, + size_t idx, const Argument* arg) { - return ctx.c_options; + // Try to find the argument by name + auto it = results.named().find(arg->name()); + // Check if requested index is available + if (results.positional().size() > idx) { + // Check if argument is also known by name + if (it != results.named().end()) { + // Raise error since it's ambiguous + throw Exception::ArgumentGivenTwice( + logger456, arg->name()); + } + // Return the positional value + return results.positional()[idx]; + } + // Check if argument was found be name + else if (it != results.named().end()) { + // Get value object from hash + // Need to hold onto the object + ValueObj val = it->second; + // Item has been consumed + // Would destroy the value + results.named().erase(it); + // Detach to survive + return val.detach(); + } + // Check if we have default values + else if (!arg->defval().isNull()) { + // Return evaluated expression + return arg->defval()->accept(this); + } + // Raise error since nothing was found + throw Exception::MissingArgument( + logger456, arg->name()); } - struct Sass_Compiler* Eval::compiler() + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + //*************************************************// + // Call built-in function with no overloads + //*************************************************// + Value* Eval::_runBuiltInCallable( + ArgumentInvocation* arguments, + BuiltInCallable* callable, + const SourceSpan& pstate, + bool selfAssign) { - return ctx.c_compiler; + ArgumentResults results(_evaluateArguments(arguments)); + const SassFnPair& tuple(callable->callbackFor(results)); + return _callBuiltInCallable(results, tuple, pstate, selfAssign); } - EnvStack& Eval::env_stack() + //*************************************************// + // Call built-in function with overloads + //*************************************************// + Value* Eval::_runBuiltInCallables( + ArgumentInvocation* arguments, + BuiltInCallables* callable, + const SourceSpan& pstate, + bool selfAssign) { - return exp.env_stack; + ArgumentResults results(_evaluateArguments(arguments)); + const SassFnPair& tuple(callable->callbackFor(results)); + return _callBuiltInCallable(results, tuple, pstate, selfAssign); } - sass::vector& Eval::callee_stack() + //*************************************************// + // Helper for _runBuiltInCallable(s) + //*************************************************// + Value* Eval::_callBuiltInCallable( + ArgumentResults& results, + const SassFnPair& function, + const SourceSpan& pstate, + bool selfAssign) { - return ctx.callee_stack; + + // Here the strategy is to re-use the positional arguments if possible + // In the end we need one continuous array to pass to the built-in callable + // So we need to split out restargs into it's own array, where as in other + // implementations we can re-use positional array for this purpose! + + // Get some items from passed parameters + const SassFnSig& callback(function.second); + const ArgumentDeclaration* prototype(function.first); + if (!callback) throw std::runtime_error("Mixin declaration has no callback"); + if (!prototype) throw std::runtime_error("Mixin declaration has no prototype"); + const sass::vector& parameters(prototype->arguments()); + + // Get reference to positional arguments in the result object + // Multiple calls to the same function may re-use the object + ValueVector& positional(results.positional()); + + // Needed here for a specific edge case: restargs must be consumed + // Those can be consumed e.g. by passing them to other functions + // Or simply by calling `keywords` on the rest arguments + ArgumentListObj restargs; + + // If the callable accepts rest argument we can pass all unknown args + // Also if we must pass rest args we must pass only the remaining parts + if (prototype->restArg().empty() == false) { + + // Superfluous function arguments + ValueVector superflous; + + // Check if more arguments provided than parameters + if (positional.size() > parameters.size()) { + // Move superfluous arguments into the array + std::move(positional.begin() + parameters.size(), + positional.end(), back_inserter(superflous)); + // Remove the consumed positional arguments + positional.resize(parameters.size()); + } + + // Try to get named function parameters from argument results + for (size_t i = positional.size(); i < parameters.size(); i += 1) { + positional.push_back(getParameter(results, i, parameters[i])); + } + + // Inherit separator from argument results + SassSeparator separator(results.separator()); + // But make the default a comma instead of spaces + if (separator == SASS_UNDEF) separator = SASS_COMMA; + // Create the rest arguments (move remaining stuff) + restargs = SASS_MEMORY_NEW(ArgumentList, pstate, separator, + std::move(superflous), std::move(results.named())); + // Append last parameter (rest arguments) + positional.emplace_back(restargs); + + } + // Function takes rest arguments, so superfluous arguments must + // be passed to the function via the rest argument array + else { + + // Check that all positional arguments are consumed + if (positional.size() > parameters.size()) { + throw Exception::TooManyArguments(logger456, + positional.size(), prototype->maxArgs()); + } + + // Try to get needed function parameters from argument results + for (size_t i = positional.size(); i < parameters.size(); i += 1) { + positional.push_back(getParameter(results, i, parameters[i])); + } + + // Check that all named arguments are consumed + if (results.named().empty() == false) { + throw Exception::TooManyArguments( + logger456, results.named()); + } + + } + + // Now execute the built-in function + ValueObj result = callback(pstate, + positional, compiler, + *this, selfAssign); // 7% + + // If we had no rest arguments, this will be true + if (restargs == nullptr) return result.detach(); + // Check if all keywords have been marked consumed, meaning we + // either don't have any or somebody called `keywords` method + if (restargs->hasAllKeywordsConsumed()) return result.detach(); + + // Throw error since not all named arguments were consumed + throw Exception::TooManyArguments(logger456, restargs->keywords()); + } - Expression* Eval::operator()(Block* b) + //*************************************************// + // Used for user functions and also by + // mixin includes and content includes. + //*************************************************// + Value* Eval::_runUserDefinedCallable( + ArgumentInvocation* arguments, + UserDefinedCallable* callable, + const SourceSpan& pstate) { - Expression* val = 0; - for (size_t i = 0, L = b->length(); i < L; ++i) { - val = b->at(i)->perform(this); - if (val) return val; + + // Here the strategy is to put variables on the current function scope + // Therefore we do not really need to results anymore once we set them + // Therefore we can re-use the positional array for our restargs + + // Get some items from passed parameters + CallableDeclaration* declaration(callable->declaration()); + ArgumentDeclaration* prototype(declaration->arguments()); + if (!prototype) throw std::runtime_error("Mixin declaration has no prototype"); + const sass::vector& parameters(prototype->arguments()); + + ArgumentResults results(_evaluateArguments(arguments)); + + // Get reference to positional arguments in the result object + // Multiple calls to the same function may re-use the object + ValueVector& positional(results.positional()); + + // Create the variable scope to pass args + auto idxs = callable->declaration()->idxs(); + EnvScope scoped(compiler.varRoot, idxs); + + // Try to fetch arguments for all parameters + for (size_t i = 0; i < parameters.size(); i += 1) { + // Errors if argument is missing or given twice + ValueObj value = getParameter(results, i, parameters[i]); + // Set lexical variable on scope + compiler.varRoot.setVariable( + idxs->varFrame, (uint32_t)i, + value->withoutSlash()); + } + + // Needed here for a specific edge case: restargs must be consumed + // Those can be consumed e.g. by passing them to other functions + // Or simply by calling `keywords` on the rest arguments + ArgumentListObj restargs; + + // If the callable accepts rest argument we can pass all unknown args + // Also if we must pass rest args we must pass only the remaining parts + if (prototype->restArg().empty() == false) { + + // Remove consumed items (vars already set) + // This will leave the rest arguments behind + if (positional.size() > parameters.size()) { + positional.erase(positional.begin(), + positional.begin() + parameters.size()); + } + else { + positional.clear(); + } + + // Inherit separator from argument results + SassSeparator separator(results.separator()); + // But make the default a comma instead of spaces + if (separator == SASS_UNDEF) separator = SASS_COMMA; + // Create the rest arguments (move remaining stuff) + restargs = SASS_MEMORY_NEW(ArgumentList, pstate, separator, + std::move(positional), std::move(results.named())); + // Set last lexical variable on scope + compiler.varRoot.setVariable(idxs->varFrame, + (uint32_t)parameters.size(), restargs); + + } + else { + + // Check that all positional arguments are consumed + if (positional.size() > parameters.size()) { + throw Exception::TooManyArguments(logger456, + positional.size(), parameters.size()); + } + + // Check that all named arguments are consumed + if (results.named().empty() == false) { + throw Exception::TooManyArguments( + logger456, results.named()); + } + } - return val; - } - Expression* Eval::operator()(Assignment* a) + ValueObj result; + // Process all statements within user defined function + // Only the `@return` statement must return something! + for (Statement* statement : declaration->elements()) { + result = statement->accept(this); + if (result != nullptr) break; + } + + // If we had no rest arguments, this will be true + if (restargs == nullptr) return result.detach(); + // Check if all keywords have been marked consumed, meaning we + // either don't have any or somebody called `keywords` method + if (restargs->hasAllKeywordsConsumed()) return result.detach(); + + // Throw error since not all named arguments were consumed + throw Exception::TooManyArguments(logger456, restargs->keywords()); + + } + // EO _runUserDefinedCallable + + //*************************************************// + // Call external C-API function + //*************************************************// + Value* Eval::_runExternalCallable( + ArgumentInvocation* arguments, + ExternalCallable* callable, + const SourceSpan& pstate) { - Env* env = environment(); - sass::string var(a->variable()); - if (a->is_global()) { - if (!env->has_global(var)) { - deprecated( - "!global assignments won't be able to declare new variables in future versions.", - "Consider adding `" + var + ": null` at the top level.", - true, a->pstate()); + + // Here the strategy is to put variables into a sass list of Values + + // Get some items from passed parameters + const EnvKey& name(callable->envkey()); + struct SassFunction* callback(callable->function()); + ArgumentDeclaration* prototype(callable->declaration()); + if (!callback) throw std::runtime_error("C-API declaration has no callback"); + if (!prototype) throw std::runtime_error("C-API declaration has no prototype"); + const sass::vector& parameters(prototype->arguments()); + + ArgumentResults results(_evaluateArguments(arguments)); + ValueFlatMap& named(results.named()); + ValueVector& positional(results.positional()); + + // Verify that the passed arguments are valid for this function + prototype->verify(positional.size(), named, pstate, traces); + + // Process all prototype items which are not positional + for (size_t i = positional.size(); i < parameters.size(); i++) { + // Try to find name in passed arguments + Argument* argument = parameters[i]; + const auto& name(argument->name()); + const auto& it(named.find(name)); + // Check if we found the name + if (it != named.end()) { + // Append it to our positional args + positional.emplace_back(named[name]); + named.erase(it); // consume argument } - if (a->is_default()) { - if (env->has_global(var)) { - Expression* e = Cast(env->get_global(var)); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(this)); - } - } - else { - env->set_global(var, a->value()->perform(this)); - } + // Otherwise check if argument has a default value + else if (!argument->defval().isNull()) { + // Evaluate the expression into final value + Value* defval(argument->defval()->accept(this)); + // Append it to our positional args + positional.emplace_back(defval); } else { - env->set_global(var, a->value()->perform(this)); + // This case should never happen due to verification + throw std::runtime_error("Verify did not protect us!"); } } - else if (a->is_default()) { - if (env->has_lexical(var)) { - auto cur = env; - while (cur && cur->is_lexical()) { - if (cur->has_local(var)) { - if (AST_Node_Obj node = cur->get_local(var)) { - Expression* e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - cur->set_local(var, a->value()->perform(this)); - } - } - else { - throw std::runtime_error("Env not in sync"); - } - return 0; - } - cur = cur->parent(); + + // Needed here for a specific edge case: restargs must be consumed + // Those can be consumed e.g. by passing them to other functions + // Or simply by calling `keywords` on the rest arguments + ArgumentListObj restargs; + + // If the callable accepts rest argument we can pass all unknown args + // Also if we must pass rest args we must pass only the remaining parts + if (prototype->restArg().empty() == false) { + // Superfluous function arguments + ValueVector superflous; + // Check if more arguments provided than parameters + if (positional.size() > parameters.size()) { + // Move superfluous arguments into the array + std::move(positional.begin() + parameters.size(), + positional.end(), back_inserter(superflous)); + // Remove the consumed positional arguments + positional.resize(parameters.size()); + } + + SassSeparator separator = results.separator(); + if (separator == SASS_UNDEF) separator = SASS_COMMA; + restargs = SASS_MEMORY_NEW(ArgumentList, + prototype->pstate(), separator, + std::move(superflous), std::move(named)); + positional.emplace_back(restargs); + } + + // Create a new sass list holding parameters to pass to function + struct SassValue* c_args = sass_make_list(SASS_COMMA, false); + // First append all positional parameters to it + for (size_t i = 0; i < positional.size(); i++) { + sass_list_push(c_args, Value::wrap(positional[i])); + } + + // Now invoke the function of the callback object + struct SassValue* c_val = callback->function( + c_args, callback, compiler.wrap()); + // It may not return anything at all + if (c_val == nullptr) return nullptr; + // Unwrap the result into C++ object + ValueObj value(&Value::unwrap(c_val)); + + // Check for some specific return types to handle + // Can't use throw in C code, so this has to do it + if (CustomError* err = value->isaCustomError()) { + sass::string message("C-API function " + + name.orig() + ": " + err->message()); + sass_delete_value(c_args); + sass_delete_value(c_val); + throw Exception::ParserException(traces, message); + } + // This will simply invoke the warning handler + // ToDo: we should have another way to call this + // We might want to warn beside returning a value + else if (CustomWarning* warn = value->isaCustomWarning()) { + sass::string message("C-API function " + + name.orig() + ": " + warn->message()); + // warn->pstate(pstate); + sass_delete_value(c_args); + sass_delete_value(c_val); + logger456.addWarning(message); + } + sass_delete_value(c_val); + sass_delete_value(c_args); + + // If we had no rest arguments, this will be true + if (restargs == nullptr) return value.detach(); + // Check if all keywords have been marked consumed, meaning we + // either don't have any or somebody called `keywords` method + if (restargs->hasAllKeywordsConsumed()) return value.detach(); + + // Throw error since not all named arguments were consumed + throw Exception::TooManyArguments(logger456, restargs->keywords()); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + //*************************************************// + // Call built-in function with no overloads + //*************************************************// + Value* Eval::execute( + BuiltInCallable* callable, + ArgumentInvocation* arguments, + const SourceSpan& pstate, + bool selfAssign) + { + const EnvKey& key(callable->envkey()); + BackTrace trace(pstate, key.orig(), true); + callStackFrame frame(logger456, trace); + ValueObj rv = _runBuiltInCallable( + arguments, callable, pstate, selfAssign); + if (rv.isNull()) { + throw Exception::RuntimeException(logger456, + "Function finished without @return."); + } + rv = rv->withoutSlash(); + return rv.detach(); + } + + //*************************************************// + // Call built-in function with overloads + //*************************************************// + Value* Eval::execute( + BuiltInCallables* callable, + ArgumentInvocation* arguments, + const SourceSpan& pstate, + bool selfAssign) + { + const EnvKey& key(callable->envkey()); + BackTrace trace(pstate, key.orig(), true); + callStackFrame frame(logger456, trace); + ValueObj rv = _runBuiltInCallables(arguments, + callable, pstate, selfAssign); + if (rv.isNull()) { + throw Exception::RuntimeException(logger456, + "Function finished without @return."); + } + rv = rv->withoutSlash(); + return rv.detach(); + } + + //*************************************************// + // Used for user functions and also by + // mixin includes and content includes. + //*************************************************// + Value* Eval::execute( + UserDefinedCallable* callable, + ArgumentInvocation* arguments, + const SourceSpan& pstate, + bool selfAssign) + { + LOCAL_FLAG(inMixin, false); + const EnvKey& key(callable->envkey()); + BackTrace trace(pstate, key.orig(), true); + callStackFrame frame(logger456, trace); + ValueObj rv = _runUserDefinedCallable( + arguments, callable, pstate); + if (rv.isNull()) { + throw Exception::RuntimeException(logger456, + "Function finished without @return."); + } + rv = rv->withoutSlash(); + return rv.detach(); + } + + //*************************************************// + // Call external C-API function + //*************************************************// + Value* Eval::execute( + ExternalCallable* callable, + ArgumentInvocation* arguments, + const SourceSpan& pstate, + bool selfAssign) + { + const EnvKey& key(callable->envkey()); + BackTrace trace(pstate, key.orig(), true); + callStackFrame frame(logger456, trace); + ValueObj rv = _runExternalCallable( + arguments, callable, pstate); + if (rv.isNull()) { + throw Exception::RuntimeException(logger456, + "Function finished without @return."); + } + rv = rv->withoutSlash(); + return rv.detach(); + } + + //*************************************************// + // Return plain css call as string + // Used when function is not known + //*************************************************// + Value* Eval::execute( + PlainCssCallable* callable, + ArgumentInvocation* arguments, + const SourceSpan& pstate, + bool selfAssign) + { + if (!arguments->named().empty()) { + callStackFrame frame(traces, + arguments->pstate()); + throw Exception::RuntimeException(logger456, + "Plain CSS functions don't support keyword arguments."); + } + if (arguments->kwdRest() != nullptr) { + callStackFrame frame(traces, + arguments->kwdRest()->pstate()); + throw Exception::RuntimeException(logger456, + "Plain CSS functions don't support keyword arguments."); + } + bool addComma = false; + sass::string strm; + strm += callable->name(); + strm += "("; + for (Expression* argument : arguments->positional()) { + if (addComma) { strm += ", "; } + else { addComma = true; } + strm += toCss(argument); + } + if (ExpressionObj rest = arguments->restArg()) { + if (addComma) { strm += ", "; } + else { addComma = true; } + strm += toCss(rest); + } + strm += ")"; + return SASS_MEMORY_NEW( + String, pstate, + std::move(strm)); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ArgumentResults Eval::_evaluateArguments( + ArgumentInvocation* arguments) + { + ArgumentResults results; + // Get some items from passed parameters + ValueFlatMap& named(results.named()); + ValueVector& positional(results.positional()); + + // Clear existing array, but + // preserve existing memory + named.clear(); positional.clear(); + // Allocate minimum expected size + + // Collect positional args by evaluating input arguments + positional.reserve(arguments->positional().size()); + for (const auto& arg : arguments->positional()) { + positional.emplace_back(arg->accept(this)); + } + + // Collect named args by evaluating input arguments + for (const auto& kv : arguments->named()) { + named.insert(std::make_pair(kv.first, kv.second->accept(this))); + } + + // Abort if we don't take any restargs + if (arguments->restArg() == nullptr) { + // ToDo: no test case for this!? + results.separator(SASS_UNDEF); + return results; + } + + // Evaluate the variable expression ( + ValueObj rest = arguments->restArg()->accept(this); + + SassSeparator separator = SASS_UNDEF; + + if (Map* restMap = rest->isaMap()) { + _addRestValueMap(named, restMap, arguments->restArg()->pstate()); + } + else if (List* list = rest->isaList()) { + std::copy(list->begin(), list->end(), + std::back_inserter(positional)); + separator = list->separator(); + if (ArgumentList* args = rest->isaArgumentList()) { + auto kwds = args->keywords(); + for (auto kv : kwds) { + named[kv.first] = kv.second; } - throw std::runtime_error("Env not in sync"); } - else if (env->has_global(var)) { - if (AST_Node_Obj node = env->get_global(var)) { - Expression* e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(this)); + } + else { + positional.emplace_back(rest); + } + + if (arguments->kwdRest() == nullptr) { + results.separator(separator); + return results; + } + + // kwdRest already poisened + ValueObj keywordRest = arguments->kwdRest()->accept(this); + + if (Map* restMap = keywordRest->isaMap()) { + _addRestValueMap(named, restMap, arguments->kwdRest()->pstate()); + results.separator(separator); + return results; + } + else { + logger456.addFinalStackTrace(keywordRest->pstate()); + throw Exception::RuntimeException(traces, + "Variable keyword arguments must be a map (was $keywordRest)."); + } + + throw std::runtime_error("thrown before"); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + /// Evaluates [expression] and calls `toCss()`. + sass::string Eval::toCss(Expression* expression, bool quote) + { + ValueObj value = expression->accept(this); + return value->toCss(logger456, quote); + } + + /// Evaluates [interpolation] into a serialized string. + /// + /// If [trim] is `true`, removes whitespace around the result. + /// If [warnForColor] is `true`, this will emit a warning for + /// any named color values passed into the interpolation. + sass::string Eval::acceptInterpolation(InterpolationObj interpolation, bool warnForColor, bool trim) + { + // Needed in loop + ValueObj value; + // Create CSS output options + SassOutputOptionsCpp out({ + SASS_STYLE_TO_CSS, + compiler.precision }); + // Create the emitter + Cssize cssize(out, false); + // Don't quote strings + cssize.quotes = false; + // Process all interpolants in the interpolation + // Items in interpolations are only of three types + // Performance optimized since it's used quite a lot + for (Interpolant* itpl : interpolation->elements()) { + if (itpl == nullptr) continue; + switch (itpl->getType()) { + case Interpolant::LiteralInterpolant: + cssize.append_token( + static_cast(itpl)->text(), + static_cast(itpl)); + break; + case Interpolant::ValueInterpolant: + static_cast(itpl) + ->accept(&cssize); + break; + case Interpolant::ExpressionInterpolant: + value = static_cast(itpl)->accept(this); + value->accept(&cssize); + break; + } + } + // ToDo: check it's using RVO + return cssize.get_buffer(trim); + } + // EO acceptInterpolation + + /// Evaluates [interpolation] and wraps the result in a [SourceData]. + /// + /// If [trim] is `true`, removes whitespace around the result. + /// If [warnForColor] is `true`, this will emit a warning for + /// any named color values passed into the interpolation. + SourceData* Eval::interpolationToSource(InterpolationObj interpolation, bool warnForColor, bool trim) + { + if (interpolation.isNull()) return nullptr; + sass::string result = acceptInterpolation(interpolation, warnForColor, trim); + return SASS_MEMORY_NEW(SourceItpl, interpolation->pstate(), std::move(result)); + } + + /// Evaluates [interpolation] and wraps the result in a [CssValue]. + /// + /// If [trim] is `true`, removes whitespace around the result. + /// If [warnForColor] is `true`, this will emit a warning for + /// any named color values passed into the interpolation. + CssString* Eval::interpolationToCssString(InterpolationObj interpolation, + bool warnForColor, bool trim) + { + if (interpolation.isNull()) return nullptr; + sass::string result = acceptInterpolation(interpolation, warnForColor, trim); + return SASS_MEMORY_NEW(CssString, interpolation->pstate(), std::move(result)); + } + + /// Evaluates [interpolation] and parses the result into a [SelectorList]. + SelectorListObj Eval::interpolationToSelector(Interpolation* itpl, bool plainCss, bool allowParent) + { + // Create a new source data object from the evaluated interpolation + SourceDataObj synthetic = interpolationToSource(itpl, false, true); + // Everything parsed, will be parsed from perspective of local content + // Pass the source-map in for the interpolation, so the scanner can + // update the positions according to previous source-positions + // Is a parser state solely represented by a source map or do we + // need an intermediate format for them? + SelectorParser parser(compiler, synthetic); + parser.allowPlaceholder = plainCss == false; + parser.allowParent = allowParent && plainCss == false; + return parser.parseSelectorList(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void Eval::_evaluateMacroArguments( + CallableInvocation& invocation, + ExpressionVector& positional, + ExpressionFlatMap& named) + { + + ArgumentInvocation* arguments = invocation.arguments(); + + if (arguments->restArg()) { + + ValueObj rest = arguments->restArg()->accept(this); + + if (Map* restMap = rest->isaMap()) { + _addRestExpressionMap(named, restMap, arguments->restArg()->pstate()); + } + else if (List* restList = rest->isaList()) { + for (const ValueObj& value : restList->elements()) { + positional.emplace_back(SASS_MEMORY_NEW( + ValueExpression, value->pstate(), value)); + } + // separator = list->separator(); + if (ArgumentList* args = rest->isaArgumentList()) { + for (auto& kv : args->keywords()) { + named[kv.first] = SASS_MEMORY_NEW(ValueExpression, + kv.second->pstate(), kv.second); } } } - else if (env->is_lexical()) { - env->set_local(var, a->value()->perform(this)); + else { + positional.emplace_back(SASS_MEMORY_NEW( + ValueExpression, rest->pstate(), rest)); + } + + } + + if (arguments->kwdRest() == nullptr) { + return; + } + + ValueObj keywordRest = arguments->kwdRest()->accept(this); + + if (Map* restMap = keywordRest->isaMap()) { + _addRestExpressionMap(named, restMap, arguments->restArg()->pstate()); + return; + } + + throw Exception::RuntimeException(logger456, + "Variable keyword arguments must be a map (was $keywordRest)."); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* Eval::visitBooleanExpression(BooleanExpression* ex) { +#ifdef SASS_ELIDE_COPIES + return ex->value(); +#else + return SASS_MEMORY_COPY(ex->value()); +#endif + } + + Value* Eval::visitColorExpression(ColorExpression* ex) { +#ifdef SASS_ELIDE_COPIES + return ex->value(); +#else + ColorObj color = ex->value(); + ColorObj copy = SASS_MEMORY_COPY(color); + copy->disp(color->disp()); + return copy.detach(); +#endif + } + + Value* Eval::visitNumberExpression(NumberExpression* ex) { +#ifdef SASS_ELIDE_COPIES + return ex->value(); +#else + return SASS_MEMORY_COPY(ex->value()); +#endif + } + + Value* Eval::visitNullExpression(NullExpression* ex) { +#ifdef SASS_ELIDE_COPIES + return ex->value(); +#else + return SASS_MEMORY_COPY(ex->value()); +#endif + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* Eval::visitListExpression(ListExpression* l) + { + // regular case for unevaluated lists + ListObj ll = SASS_MEMORY_NEW(List, l->pstate(), + ValueVector(), l->separator()); + ll->hasBrackets(l->hasBrackets()); + for (size_t i = 0, L = l->size(); i < L; ++i) { + ll->append(l->get(i)->accept(this)); + } + return ll.detach(); + } + + Value* Eval::visitMapExpression(MapExpression* m) + { + ValueObj key; + MapObj map(SASS_MEMORY_NEW(Map, m->pstate())); + const ExpressionVector& kvlist(m->kvlist()); + for (size_t i = 0, L = kvlist.size(); i < L; i += 2) + { + // First evaluate the key + key = kvlist[i]->accept(this); + // Check for key duplication + if (map->has(key)) { + traces.emplace_back(kvlist[i]->pstate()); + throw Exception::DuplicateKeyError(traces, *map, *key); + } + // Second insert the evaluated value for key + map->insertOrSet(key, kvlist[i + 1]->accept(this)); + } + return map.detach(); + } + + Value* Eval::visitStringExpression(StringExpression* node) + { + // Don't use [performInterpolation] here because we need to get + // the raw text from strings, rather than the semantic value. + InterpolationObj itpl = node->text(); + sass::vector strings; + for (const auto& item : itpl->elements()) { + if (ItplString* lit = item->isaItplString()) { + strings.emplace_back(lit->text()); } else { - env->set_local(var, a->value()->perform(this)); + ValueObj result = item->isaValue(); + if (Expression* ex = item->isaExpression()) { + result = ex->accept(this); + } + if (String* lit = result->isaString()) { + strings.emplace_back(lit->value()); + } + else if (!result->isNull()) { + strings.emplace_back(result->toCss(logger456, false)); + } + } + } + + return SASS_MEMORY_NEW(String, node->pstate(), + StringUtils::join(strings, ""), node->hasQuotes()); + } + + Value* Eval::visitFunctionExpression(FunctionExpression* node) + { + // Function Expression might be simple and static, or dynamic CSS call + CallableObj function = node->fidx().isValid() + ? compiler.varRoot.getFunction(node->fidx()).ptr() + : compiler.varRoot.getFunction( + node->name()->getPlainString()); + if (function == nullptr) { + function = SASS_MEMORY_NEW(PlainCssCallable, + node->pstate(), acceptInterpolation(node->name(), false)); + } + LOCAL_FLAG(inFunction, true); + return function->execute(*this, node->arguments(), + node->pstate(), node->selfAssign()); + } + + Value* Eval::visitBinaryOpExpression(BinaryOpExpression* node) + { + ValueObj left, right; + Expression* lhs = node->left(); + Expression* rhs = node->right(); + left = lhs->accept(this); + switch (node->operand()) { + case SassOperator::IESEQ: + right = rhs->accept(this); + return left->singleEquals( + right, logger456, node->pstate()); + case SassOperator::OR: + if (left->isTruthy()) { + return left.detach(); } + return rhs->accept(this); + case SassOperator::AND: + if (!left->isTruthy()) { + return left.detach(); + } + return rhs->accept(this); + case SassOperator::EQ: + right = rhs->accept(this); + return ObjEqualityFn(left, right) + ? bool_true : bool_false; + case SassOperator::NEQ: + right = rhs->accept(this); + return ObjEqualityFn(left, right) + ? bool_false : bool_true; + case SassOperator::GT: + right = rhs->accept(this); + return left->greaterThan(right, + logger456, node->pstate()) + ? bool_true : bool_false; + case SassOperator::GTE: + right = rhs->accept(this); + return left->greaterThanOrEquals(right, + logger456, node->pstate()) + ? bool_true : bool_false; + case SassOperator::LT: + right = rhs->accept(this); + return left->lessThan(right, + logger456, node->pstate()) + ? bool_true : bool_false; + case SassOperator::LTE: + right = rhs->accept(this); + return left->lessThanOrEquals(right, + logger456, node->pstate()) + ? bool_true : bool_false; + case SassOperator::ADD: + right = rhs->accept(this); + return left->plus(right, + logger456, node->pstate()); + case SassOperator::SUB: + right = rhs->accept(this); + return left->minus(right, + logger456, node->pstate()); + case SassOperator::MUL: + right = rhs->accept(this); + return left->times(right, + logger456, node->pstate()); + case SassOperator::DIV: + right = rhs->accept(this); + return doDivision(left, right, + node->allowsSlash(), + logger456, node->pstate()); + case SassOperator::MOD: + right = rhs->accept(this); + return left->modulo(right, + logger456, node->pstate()); + } + // Satisfy compiler + return nullptr; + } + + Value* Eval::visitUnaryOpExpression(UnaryOpExpression* node) + { + ValueObj operand = node->operand()->accept(this); + switch (node->optype()) { + case UnaryOpType::PLUS: + return operand->unaryPlus(logger456, node->pstate()); + case UnaryOpType::MINUS: + return operand->unaryMinus(logger456, node->pstate()); + case UnaryOpType::NOT: + return operand->unaryNot(logger456, node->pstate()); + case UnaryOpType::SLASH: + return operand->unaryDivide(logger456, node->pstate()); + } + // Satisfy compiler + return nullptr; + } + + // This operates similar to a function call + Value* Eval::visitIfExpression(IfExpression* node) + { + ArgumentInvocation* arguments = node->arguments(); + callStackFrame frame(logger456, node->pstate()); + // We need to make copies here to preserve originals + // We could optimize this further, but impact is slim + ExpressionFlatMap named(arguments->named()); + ExpressionVector positional(arguments->positional()); + // Rest arguments must be evaluated in all cases + // evaluateMacroArguments is only used for this + _evaluateMacroArguments(*node, positional, named); + ExpressionObj condition = getArgument(positional, named, 0, Keys::condition); + ExpressionObj ifTrue = getArgument(positional, named, 1, Keys::ifTrue); + ExpressionObj ifFalse = getArgument(positional, named, 2, Keys::ifFalse); + if (positional.size() > 3) { + throw Exception::TooManyArguments( + logger456, positional.size(), 3); + } + if (positional.size() + named.size() > 3) { + throw Exception::TooManyArguments(logger456, named, + { Keys::condition, Keys::ifTrue, Keys::ifFalse }); + } + + ValueObj rv = condition ? condition->accept(this) : nullptr; + Expression* ex = rv && rv->isTruthy() ? ifTrue : ifFalse; + return ex ? ex->accept(this) : nullptr; + } + + Value* Eval::visitParenthesizedExpression(ParenthesizedExpression* ex) + { + // return ex->expression(); + if (ex->expression()) { + return ex->expression()->accept(this); + } + return nullptr; + } + + Value* Eval::visitParentExpression(ParentExpression* p) + { + if (SelectorListObj& parents = original()) { + return parents->toValue(); } else { - env->set_lexical(var, a->value()->perform(this)); + return SASS_MEMORY_NEW(Null, p->pstate()); } - return 0; } - Expression* Eval::operator()(If* i) + Value* Eval::visitValueExpression(ValueExpression* node) { - ExpressionObj rv; - Env env(environment()); - env_stack().push_back(&env); - ExpressionObj cond = i->predicate()->perform(this); - if (!cond->is_false()) { - rv = i->block()->perform(this); + // We have a bug lurking somewhere + // without detach it gets deleted? + ValueObj value = node->value(); + return value.detach(); + } + + Value* Eval::visitVariableExpression(VariableExpression* v) + { + for (auto vidx : v->vidxs()) { + ValueObj& value = compiler.varRoot.getVariable(vidx); + if (value == nullptr) continue; + return value->withoutSlash(); + } + logger456.addFinalStackTrace(v->pstate()); + throw Exception::RuntimeException(traces, + "Undefined variable."); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void Eval::callExternalMessageOverloadFunction(Callable* fn, Value* message) + { + // We know that warn override function can only be external + SASS_ASSERT(fn->isaExternalCallable(), "Custom callable must be external"); + ExternalCallable* def = static_cast(fn); + struct SassFunction* c_function = def->function(); + SassFunctionLambda c_func = sass_function_get_function(c_function); + struct SassValue* c_args = sass_make_list(SASS_COMMA, false); + sass_list_push(c_args, Value::wrap(message)); + struct SassValue* c_val = c_func( + c_args, c_function, compiler.wrap()); + sass_delete_value(c_args); + sass_delete_value(c_val); + } + + Value* Eval::visitDebugRule(DebugRule* node) + { + ValueObj message = node->expression()->accept(this); + if (Callable* fn = compiler.varRoot.getFunction(Keys::debugRule)) { + callExternalMessageOverloadFunction(fn, message); } else { - Block_Obj alt = i->alternative(); - if (alt) rv = alt->perform(this); + logger456.addDebug(message-> + inspect(compiler.precision, false), + node->pstate()); } - env_stack().pop_back(); - return rv.detach(); + return nullptr; } - // For does not create a new env scope - // But iteration vars are reset afterwards - Expression* Eval::operator()(ForRule* f) + Value* Eval::visitWarnRule(WarnRule* node) { - sass::string variable(f->variable()); - ExpressionObj low = f->lower_bound()->perform(this); - if (low->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(low->pstate())); - throw Exception::TypeMismatch(traces, *low, "integer"); + ValueObj message = node->expression()->accept(this); + if (Callable* fn = compiler.varRoot.getFunction(Keys::warnRule)) { + callExternalMessageOverloadFunction(fn, message); } - ExpressionObj high = f->upper_bound()->perform(this); - if (high->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(high->pstate())); - throw Exception::TypeMismatch(traces, *high, "integer"); + else { + sass::string result(message->toCss(logger456, false)); + callStackFrame frame(logger456, BackTrace(node->pstate())); + logger456.addWarning(result); } - Number_Obj sass_start = Cast(low); - Number_Obj sass_end = Cast(high); - // check if units are valid for sequence - if (sass_start->unit() != sass_end->unit()) { - sass::ostream msg; msg << "Incompatible units: '" - << sass_end->unit() << "' and '" - << sass_start->unit() << "'."; - error(msg.str(), low->pstate(), traces); + return nullptr; + } + + Value* Eval::visitErrorRule(ErrorRule* node) + { + ValueObj message = node->expression()->accept(this); + if (Callable* fn = compiler.varRoot.getFunction(Keys::errorRule)) { + callExternalMessageOverloadFunction(fn, message); } - double start = sass_start->value(); - double end = sass_end->value(); - // only create iterator once in this environment - Env env(environment(), true); - env_stack().push_back(&env); - Block_Obj body = f->block(); - Expression* val = 0; - if (start < end) { - if (f->is_inclusive()) ++end; - for (double i = start; - i < end; - ++i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - val = body->perform(this); - if (val) break; - } - } else { - if (f->is_inclusive()) --end; - for (double i = start; - i > end; - --i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - val = body->perform(this); - if (val) break; + else { + sass::string result(message->toCss(logger456, true)); + traces.push_back(BackTrace(node->pstate())); + throw Exception::RuntimeException(traces, result); + } + return nullptr; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* Eval::visitIncludeRule(IncludeRule* node) + { + UserDefinedCallableObj mixin = node->midx().isValid() ? + compiler.varRoot.getMixin(node->midx()).ptr() : + compiler.varRoot.getMixin(node->name()); + + if (mixin == nullptr) { + compiler.addFinalStackTrace(node->pstate()); + throw Exception::RuntimeException( + logger456, "Undefined mixin."); + } + + UserDefinedCallableObj callable; + + if (node->content() != nullptr) { + + callable = SASS_MEMORY_NEW( + UserDefinedCallable, + node->pstate(), node->name(), + node->content(), content88); + + MixinRule* rule = mixin->declaration()->isaMixinRule(); + node->content()->cidx(rule->cidx()); + + if (!rule || !rule->hasContent()) { + SourceSpan span(node->content()->pstate()); + callStackFrame frame(logger456, span); + throw Exception::RuntimeException(logger456, + "Mixin doesn't accept a content block."); } } - env_stack().pop_back(); - return val; + + LOCAL_FLAG(inMixin, true); + + callStackFrame frame(logger456, + BackTrace(node->pstate(), mixin->envkey().orig(), true)); + + LOCAL_PTR(UserDefinedCallable, content88, callable); + + ValueObj qwe = _runUserDefinedCallable( + node->arguments(), mixin, node->pstate()); + + // node->content()->accept(this); + + return nullptr; } - // Eval does not create a new env scope - // But iteration vars are reset afterwards - Expression* Eval::operator()(EachRule* e) + // See visitContentRule and visitIncludeRule + Value* Eval::visitContentBlock(ContentBlock* rule) { - sass::vector variables(e->variables()); - ExpressionObj expr = e->list()->perform(this); - Env env(environment(), true); - env_stack().push_back(&env); - List_Obj list; - Map* map = nullptr; - if (expr->concrete_type() == Expression::MAP) { - map = Cast(expr); + throw std::runtime_error("Evaluation handles " + "@include and its content block together."); + } + + Value* Eval::visitContentRule(ContentRule* c) + { + if (content88 == nullptr) return nullptr; + UserDefinedCallable* content = content88; + LOCAL_FLAG(inMixin, false); + + callStackFrame frame(logger456, + BackTrace(c->pstate(), Strings::contentRule)); + + LOCAL_PTR(UserDefinedCallable, content88, content88->content()); + + ValueObj qwe = _runUserDefinedCallable( + c->arguments(), content, c->pstate()); + + // node->content()->accept(this); + + return nullptr; + + } + + + Value* Eval::visitStyleRule(StyleRule* node) + { + // Create a scope for lexical block variables + EnvScope scope(compiler.varRoot, node->idxs()); + // Keyframe blocks have a specific syntax inside them + // Therefore style rules render a bit different inside them + if (inKeyframes) { + // Find the parent we should append to (bubble up) + auto chroot = current->bubbleThrough(true); + // Create a new keyframe parser from the evaluated interpolation + KeyframeSelectorParser parser(compiler, SASS_MEMORY_NEW(SourceItpl, + node->interpolation()->pstate(), + acceptInterpolation(node->interpolation(), true, true))); + // Invoke the keyframe parser and create a new CssKeyframeBlock + CssKeyframeBlockObj child = SASS_MEMORY_NEW(CssKeyframeBlock, node->pstate(), + chroot, SASS_MEMORY_NEW(CssStringList, node->pstate(), parser.parse())); + // Add child to our parent + chroot->addChildAt(child, false); + // addChildAt(chroot, child); + // Visit the remaining items at new child + acceptChildrenAt(child, node); + } + // Regular style rule + else if (node->interpolation()) { + // Check current importer context + Import* imp = compiler.import_stack.back(); + bool plainCss = imp->syntax == SASS_IMPORT_CSS; + // Evaluate the interpolation and try to parse a selector list + SelectorListObj slist = interpolationToSelector(node->interpolation(), plainCss)-> + resolveParentSelectors(selector(), traces, !atRootExcludingStyleRule); + // Append new selector list to the stack + LOCAL_SELECTOR(selectorStack, slist); + // The copy is needed for parent reference evaluation + // dart-sass stores it as `originalSelector` member + LOCAL_SELECTOR(originalStack, slist->hasExplicitParent() ? + slist.ptr() : SASS_MEMORY_COPY(slist)); // Avoid copy if possible + // Make the new selectors known for the extender + // If previous extend rules match this selector it will + // immediately do the extending, extend rules that occur + // later will apply the extending to the existing ones. + extender.addSelector(slist, mediaStack.back()); + // Find the parent we should append to (bubble up) + auto chroot = current->bubbleThrough(true); + // Create a new style rule at the correct parent + CssStyleRuleObj child = SASS_MEMORY_NEW(CssStyleRule, + node->pstate(), chroot, slist); + // Add child to our parent + chroot->addChildAt(child, true); + // Register new child as style rule + LOCAL_PTR(CssStyleRule, readStyleRule, child); + // Reset specific flag (not in an at-rule) + LOCAL_FLAG(atRootExcludingStyleRule, false); + // Visit the remaining items at child + acceptChildrenAt(child, node); + } + // Consumed node + return nullptr; + } + // EO visitStyleRule + + CssRoot* Eval::acceptRoot(Root* root) + { + CssRootObj css = SASS_MEMORY_NEW(CssRoot, root->pstate()); + LOCAL_PTR(CssParentNode, current, css); + for (const StatementObj& item : root->elements()) { + Value* child = item->accept(this); + if (child) delete child; } - else if (SelectorList * ls = Cast(expr)) { - ExpressionObj rv = Listize::perform(ls); - list = Cast(rv); + return css.detach(); + } + + /* + + /// Whether this node has a visible sibling after it. + bool get hasFollowingSibling { + if (_parent == null) return false; + var siblings = _parent.children; + for (var i = _indexInParent + 1; i < siblings.size; i++) { + var sibling = siblings[i]; + if (!_isInvisible(sibling)) return true; } - else if (expr->concrete_type() != Expression::LIST) { - list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); - list->append(expr); + return false; + } + + */ + + CssParentNode* Eval::hoistStyleRule(CssParentNode* node) + { + if (isInStyleRule()) { + auto outer = SASS_MEMORY_RESECT(readStyleRule); + node->addChildAt(outer, false); + return outer; } else { - list = Cast(expr); + return node; } + } - Block_Obj body = e->block(); - ExpressionObj val; + Value* Eval::visitSupportsRule(SupportsRule* node) + { + ValueObj condition = SASS_MEMORY_NEW( + String, node->condition()->pstate(), + _visitSupportsCondition(node->condition())); + EnvScope scoped(compiler.varRoot, node->idxs()); + auto chroot = current->bubbleThrough(true); + CssSupportsRuleObj css = SASS_MEMORY_NEW(CssSupportsRule, + node->pstate(), chroot, condition); + chroot->addChildAt(css, false); + acceptChildrenAt( + hoistStyleRule(css), + node->elements()); + return nullptr; + } - if (map) { - for (ExpressionObj key : map->keys()) { - ExpressionObj value = map->at(key); + CssParentNode* Eval::_trimIncluded(CssParentVector& nodes) + { - if (variables.size() == 1) { - List* variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); - variable->append(key); - variable->append(value); - env.set_local(variables[0], variable); - } else { - env.set_local(variables[0], key); - env.set_local(variables[1], value); - } + auto _root = getRoot(); + if (nodes.empty()) return _root; - val = body->perform(this); - if (val) break; + auto parent = current; + size_t innermostContiguous = sass::string::npos; + for (size_t i = 0; i < nodes.size(); i++) { + while (parent != nodes[i]) { + innermostContiguous = sass::string::npos; + parent = parent->parent(); + } + if (innermostContiguous == sass::string::npos) { + innermostContiguous = i; } + parent = parent->parent(); + } + + if (parent != _root) return _root; + auto root = nodes[innermostContiguous]; + nodes.resize(innermostContiguous); + return root; + + } + + Value* Eval::visitAtRootRule(AtRootRule* node) + { + EnvScope scoped(compiler.varRoot, node->idxs()); + InterpolationObj itpl = node->query(); + AtRootQueryObj query; + + if (node->query()) { + query = AtRootQuery::parse( + interpolationToSource( + node->query(), true), + compiler); } else { - if (list->length() == 1 && Cast(list)) { - list = Cast(list); + query = AtRootQuery::defaultQuery( + SourceSpan{ node->pstate() }); + } + + LOCAL_FLAG(inKeyframes, false); + LOCAL_FLAG(inUnknownAtRule, false); + LOCAL_FLAG(atRootExcludingStyleRule, + query && query->excludesStyleRules()); + + CssParentNode* parent = current; + CssParentNode* orgParent = current; + CssParentVector included; + + while (parent && parent->parent()) { + // is!CssStylesheet (is!CssRootNode) + if (!query->excludes(parent)) { + included.emplace_back(parent); } - for (size_t i = 0, L = list->length(); i < L; ++i) { - Expression* item = list->at(i); - // unwrap value if the expression is an argument - if (Argument* arg = Cast(item)) item = arg->value(); - // check if we got passed a list of args (investigate) - if (List* scalars = Cast(item)) { - if (variables.size() == 1) { - Expression* var = scalars; - env.set_local(variables[0], var); - } else { - // https://github.com/sass/libsass/issues/3078 - for (size_t j = 0, K = variables.size(); j < K; ++j) { - env.set_local(variables[j], j >= scalars->length() - ? SASS_MEMORY_NEW(Null, expr->pstate()) : scalars->at(j)); - } - } - } else { - if (variables.size() > 0) { - env.set_local(variables.at(0), item); - for (size_t j = 1, K = variables.size(); j < K; ++j) { - // XXX: this is never hit via spec tests - Expression* res = SASS_MEMORY_NEW(Null, expr->pstate()); - env.set_local(variables[j], res); - } - } + parent = parent->parent(); + } + auto root = _trimIncluded(included); + + if (root == orgParent) { + acceptChildrenAt(root, node); + } + else { + CssParentNode* innerCopy = included.empty() ? + nullptr : SASS_MEMORY_RESECT(included.front()); + // if (innerCopy) innerCopy->clear(); + CssParentNode* outerCopy = innerCopy; + auto it = included.begin(); + // Included is not empty + if (it != included.end()) { + if (++it != included.end()) { + auto copy = SASS_MEMORY_RESECT(*it); + copy->addChildAt(outerCopy, false); + outerCopy = copy; } - val = body->perform(this); - if (val) break; } - } - env_stack().pop_back(); - return val.detach(); - } - Expression* Eval::operator()(WhileRule* w) - { - ExpressionObj pred = w->predicate(); - Block_Obj body = w->block(); - Env env(environment(), true); - env_stack().push_back(&env); - ExpressionObj cond = pred->perform(this); - while (!cond->is_false()) { - ExpressionObj val = body->perform(this); - if (val) { - env_stack().pop_back(); - return val.detach(); + if (outerCopy != nullptr) { + root->addChildAt(outerCopy, false); + } + + auto newParent = innerCopy == nullptr ? root : innerCopy; + + LOCAL_FLAG(inKeyframes, inKeyframes); + LOCAL_FLAG(inUnknownAtRule, inUnknownAtRule); + LOCAL_FLAG(atRootExcludingStyleRule, atRootExcludingStyleRule); + auto oldQueries = mediaQueries; + + if (query->excludesStyleRules()) { + atRootExcludingStyleRule = true; } - cond = pred->perform(this); - } - env_stack().pop_back(); - return 0; - } - Expression* Eval::operator()(Return* r) - { - return r->value()->perform(this); - } + if (query->excludesMedia()) { + mediaQueries.clear(); + } - Expression* Eval::operator()(WarningRule* w) - { - Sass_Output_Style outstyle = options().output_style; - options().output_style = NESTED; - ExpressionObj message = w->message()->perform(this); - Env* env = environment(); + if (inKeyframes && query->excludesName("keyframes")) { + inKeyframes = false; + } - // try to use generic function - if (env->has("@warn[f]")) { + bool hasAtRuleInIncluded = false; + for (auto include : included) { + if (include->isaCssAtRule()) { + hasAtRuleInIncluded = true; + break; + } + } - // add call stack entry - callee_stack().push_back({ - "@warn", - w->pstate().getPath(), - w->pstate().getLine(), - w->pstate().getColumn(), - SASS_CALLEE_FUNCTION, - { env } - }); + if (inUnknownAtRule && !hasAtRuleInIncluded) { + inUnknownAtRule = false; + } - Definition* def = Cast((*env)["@warn[f]"]); - // Block_Obj body = def->block(); - // Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - Sass_Function_Fn c_func = sass_function_get_function(c_function); + acceptChildrenAt(newParent, node); - AST2C ast2c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&ast2c)); - union Sass_Value* c_val = c_func(c_args, c_function, compiler()); - options().output_style = outstyle; - callee_stack().pop_back(); - sass_delete_value(c_args); - sass_delete_value(c_val); - return 0; - - } - - sass::string result(unquote(message->to_sass())); - std::cerr << "WARNING: " << result << std::endl; - traces.push_back(Backtrace(w->pstate())); - std::cerr << traces_to_string(traces, " "); - std::cerr << std::endl; - options().output_style = outstyle; - traces.pop_back(); - return 0; - } - - Expression* Eval::operator()(ErrorRule* e) - { - Sass_Output_Style outstyle = options().output_style; - options().output_style = NESTED; - ExpressionObj message = e->message()->perform(this); - Env* env = environment(); - - // try to use generic function - if (env->has("@error[f]")) { - - // add call stack entry - callee_stack().push_back({ - "@error", - e->pstate().getPath(), - e->pstate().getLine(), - e->pstate().getColumn(), - SASS_CALLEE_FUNCTION, - { env } - }); - - Definition* def = Cast((*env)["@error[f]"]); - // Block_Obj body = def->block(); - // Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - Sass_Function_Fn c_func = sass_function_get_function(c_function); - - AST2C ast2c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&ast2c)); - union Sass_Value* c_val = c_func(c_args, c_function, compiler()); - options().output_style = outstyle; - callee_stack().pop_back(); - sass_delete_value(c_args); - sass_delete_value(c_val); - return 0; + mediaQueries = oldQueries; } - sass::string result(unquote(message->to_sass())); - options().output_style = outstyle; - error(result, e->pstate(), traces); - return 0; + return nullptr; } - Expression* Eval::operator()(DebugRule* d) + Value* Eval::visitAtRule(AtRule* node) { - Sass_Output_Style outstyle = options().output_style; - options().output_style = NESTED; - ExpressionObj message = d->value()->perform(this); - Env* env = environment(); + CssStringObj name = interpolationToCssString(node->name(), true, false); + CssStringObj value = interpolationToCssString(node->value(), true, true); - // try to use generic function - if (env->has("@debug[f]")) { + if (node->empty()) { + CssAtRuleObj css = SASS_MEMORY_NEW(CssAtRule, + node->pstate(), current, name, value, node->isChildless()); + current->addChildAt(css, false); + return nullptr; + } - // add call stack entry - callee_stack().push_back({ - "@debug", - d->pstate().getPath(), - d->pstate().getLine(), - d->pstate().getColumn(), - SASS_CALLEE_FUNCTION, - { env } - }); + sass::string normalized(Util::unvendor(name->text())); + bool isKeyframe = normalized == "keyframes"; + LOCAL_FLAG(inUnknownAtRule, !isKeyframe); + LOCAL_FLAG(inKeyframes, isKeyframe); - Definition* def = Cast((*env)["@debug[f]"]); - // Block_Obj body = def->block(); - // Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - Sass_Function_Fn c_func = sass_function_get_function(c_function); - AST2C ast2c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&ast2c)); - union Sass_Value* c_val = c_func(c_args, c_function, compiler()); - options().output_style = outstyle; - callee_stack().pop_back(); - sass_delete_value(c_args); - sass_delete_value(c_val); - return 0; + auto pu = current->bubbleThrough(true); - } + // ModifiableCssKeyframeBlock + CssAtRuleObj css = SASS_MEMORY_NEW(CssAtRule, + node->pstate(), pu, name, value, node->isChildless()); - sass::string result(unquote(message->to_sass())); - sass::string abs_path(Sass::File::rel2abs(d->pstate().getPath(), cwd(), cwd())); - sass::string rel_path(Sass::File::abs2rel(d->pstate().getPath(), cwd(), cwd())); - sass::string output_path(Sass::File::path_for_console(rel_path, abs_path, d->pstate().getPath())); - options().output_style = outstyle; + // Adds new empty atRule to Root! + pu->addChildAt(css, false); - std::cerr << output_path << ":" << d->pstate().getLine() << " DEBUG: " << result; - std::cerr << std::endl; - return 0; - } + auto oldParent = current; + current = css; + if (!(!atRootExcludingStyleRule && readStyleRule != nullptr) || inKeyframes) { - Expression* Eval::operator()(List* l) - { - // special case for unevaluated map - if (l->separator() == SASS_HASH) { - Map_Obj lm = SASS_MEMORY_NEW(Map, - l->pstate(), - l->length() / 2); - for (size_t i = 0, L = l->length(); i < L; i += 2) - { - ExpressionObj key = (*l)[i+0]->perform(this); - ExpressionObj val = (*l)[i+1]->perform(this); - // make sure the color key never displays its real name - key->is_delayed(true); // verified - *lm << std::make_pair(key, val); - } - if (lm->has_duplicate_key()) { - traces.push_back(Backtrace(l->pstate())); - throw Exception::DuplicateKeyError(traces, *lm, *l); + for (const auto& child : node->elements()) { + ValueObj val = child->accept(this); } - lm->is_interpolant(l->is_interpolant()); - return lm->perform(this); + } - // check if we should expand it - if (l->is_expanded()) return l; - // regular case for unevaluated lists - List_Obj ll = SASS_MEMORY_NEW(List, - l->pstate(), - l->length(), - l->separator(), - l->is_arglist(), - l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - ll->append((*l)[i]->perform(this)); - } - ll->is_interpolant(l->is_interpolant()); - ll->from_selector(l->from_selector()); - ll->is_expanded(true); - return ll.detach(); + else { + + // If we're in a style rule, copy it into the at-rule so that + // declarations immediately inside it have somewhere to go. + // For example, "a {@foo {b: c}}" should produce "@foo {a {b: c}}". + CssStyleRule* qwe = SASS_MEMORY_RESECT(readStyleRule); + css->addChildAt(qwe, false); + acceptChildrenAt(qwe, node->elements()); + + } + current = oldParent; + + + return nullptr; } - Expression* Eval::operator()(Map* m) + Value* Eval::visitMediaRule(MediaRule* node) { - if (m->is_expanded()) return m; - // make sure we're not starting with duplicate keys. - // the duplicate key state will have been set in the parser phase. - if (m->has_duplicate_key()) { - traces.push_back(Backtrace(m->pstate())); - throw Exception::DuplicateKeyError(traces, *m, *m); + ExpressionObj mq; + sass::string str_mq; + const SourceSpan& state = node->query() ? + node->query()->pstate() : node->pstate(); + if (node->query()) { + str_mq = acceptInterpolation(node->query(), false); } - Map_Obj mm = SASS_MEMORY_NEW(Map, - m->pstate(), - m->length()); - for (auto key : m->keys()) { - Expression* ex_key = key->perform(this); - Expression* ex_val = m->at(key); - if (ex_val == NULL) continue; - ex_val = ex_val->perform(this); - *mm << std::make_pair(ex_key, ex_val); + + MediaQueryParser parser(compiler, SASS_MEMORY_NEW( + SourceItpl, state, std::move(str_mq))); + CssMediaQueryVector parsed(parser.parse()); + + CssMediaQueryVector mergedQueries + (mergeMediaQueries(mediaQueries, parsed)); + + if (mergedQueries.empty()) { + if (!mediaQueries.empty()) { + return nullptr; + } + mergedQueries = parsed; } - // check the evaluated keys aren't duplicates. - if (mm->has_duplicate_key()) { - traces.push_back(Backtrace(m->pstate())); - throw Exception::DuplicateKeyError(traces, *mm, *m); + // Create a new CSS only representation of the media rule + CssMediaRuleObj css = SASS_MEMORY_NEW(CssMediaRule, + node->pstate(), current, mergedQueries); + auto chroot = current->bubbleThrough(false); + // addChildAt(chroot, css); + chroot->addChildAt(css, false); + + LOCAL_PTR(CssParentNode, current, css); + auto oldMediaQueries(std::move(mediaQueries)); + mediaQueries = mergedQueries; + mediaStack.emplace_back(css); + + if (isInStyleRule()) { + CssStyleRule* copy = SASS_MEMORY_RESECT(readStyleRule); + css->addChildAt(copy, false); + acceptChildrenAt(copy, node->elements()); + } + else { + for (auto& child : node->elements()) { + ValueObj rv = child->accept(this); + } } - mm->is_expanded(true); - return mm.detach(); + mediaQueries = std::move(oldMediaQueries); + + mediaStack.pop_back(); + + return nullptr; } - Expression* Eval::operator()(Binary_Expression* b_in) + Value* Eval::acceptChildren(const Vectorized& children) { + for (const auto& child : children) { + ValueObj val = child->accept(this); + if (val) return val.detach(); + } + return nullptr; + } - ExpressionObj lhs = b_in->left(); - ExpressionObj rhs = b_in->right(); - enum Sass_OP op_type = b_in->optype(); + Value* Eval::acceptChildrenAt(CssParentNode* parent, + const Vectorized& children) + { + LOCAL_PTR(CssParentNode, current, parent); + for (const auto& child : children) { + ValueObj val = child->accept(this); + if (val) return val.detach(); + } + return nullptr; + } - if (op_type == Sass_OP::AND) { - // LOCAL_FLAG(force, true); - lhs = lhs->perform(this); - if (!*lhs) return lhs.detach(); - return rhs->perform(this); + /// Calls `value.toCss()` and wraps a [SassScriptException] to associate +/// it with [nodeWithSpan]'s source span. +/// +/// This takes an [AstNode] rather than a [FileSpan] so it can avoid calling +/// [AstNode.span] if the span isn't required, since some nodes need to do +/// real work to manufacture a source span. +// sass::string Eval::_serialize(Expression* value, bool quote) +// { +// // _addExceptionSpan(nodeWithSpan, () = > value.toCss(quote: quote)); +// return value->to_css(logger456, false); +// } + + +/// parentheses if necessary. +/// +/// If [operator] is passed, it's the operator for the surrounding +/// [SupportsOperation], and is used to determine whether parentheses are +/// necessary if [condition] is also a [SupportsOperation]. + sass::string Eval::_parenthesize(SupportsCondition* condition) { + SupportsNegation* negation = condition->isaSupportsNegation(); + SupportsOperation* operation = condition->isaSupportsOperation(); + if (negation != nullptr || operation != nullptr) { + return "(" + _visitSupportsCondition(condition) + ")"; } - else if (op_type == Sass_OP::OR) { - // LOCAL_FLAG(force, true); - lhs = lhs->perform(this); - if (*lhs) return lhs.detach(); - return rhs->perform(this); + else { + return _visitSupportsCondition(condition); } + } - // Evaluate variables as early o - while (Variable* l_v = Cast(lhs)) { - lhs = operator()(l_v); + sass::string Eval::_parenthesize(SupportsCondition* condition, SupportsOperation::Operand operand) { + SupportsNegation* negation = condition->isaSupportsNegation(); + SupportsOperation* operation = condition->isaSupportsOperation(); + if (negation || (operation && operand != operation->operand())) { + return "(" + _visitSupportsCondition(condition) + ")"; } - while (Variable* r_v = Cast(rhs)) { - rhs = operator()(r_v); + else { + return _visitSupportsCondition(condition); } + } - Binary_ExpressionObj b = b_in; - - // Evaluate sub-expressions early on - while (Binary_Expression* l_b = Cast(lhs)) { - if (!force && l_b->is_delayed()) break; - lhs = operator()(l_b); + /// Evaluates [condition] and converts it to a plain CSS string, with + sass::string Eval::_visitSupportsCondition(SupportsCondition* condition) + { + if (auto operation = condition->isaSupportsOperation()) { + sass::string strm; + SupportsOperation::Operand operand = operation->operand(); + strm += _parenthesize(operation->left(), operand); + strm += (operand == SupportsOperation::AND ? " and " : " or "); + strm += _parenthesize(operation->right(), operand); + return strm; + } + else if (auto negation = condition->isaSupportsNegation()) { + return "not " + _parenthesize(negation->condition()); + } + else if (auto interpolation = condition->isaSupportsInterpolation()) { + return toCss(interpolation->value(), false); + } + else if (auto declaration = condition->isaSupportsDeclaration()) { + sass::string strm("("); + strm += toCss(declaration->feature()); strm += ": "; + strm += toCss(declaration->value()); strm += ")"; + return strm; } - while (Binary_Expression* r_b = Cast(rhs)) { - if (!force && r_b->is_delayed()) break; - rhs = operator()(r_b); + else { + return Strings::empty; } - // don't eval delayed expressions (the '/' when used as a separator) - if (!force && op_type == Sass_OP::DIV && b->is_delayed()) { - b->right(b->right()->perform(this)); - b->left(b->left()->perform(this)); - return b.detach(); - } + } - // specific types we know are final - // handle them early to avoid overhead - if (Number* l_n = Cast(lhs)) { - // lhs is number and rhs is number - if (Number* r_n = Cast(rhs)) { - try { - switch (op_type) { - case Sass_OP::EQ: return *l_n == *r_n ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_n == *r_n ? bool_false : bool_true; - case Sass_OP::LT: return *l_n < *r_n ? bool_true : bool_false; - case Sass_OP::GTE: return *l_n < *r_n ? bool_false : bool_true; - case Sass_OP::LTE: return *l_n < *r_n || *l_n == *r_n ? bool_true : bool_false; - case Sass_OP::GT: return *l_n < *r_n || *l_n == *r_n ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_numbers(op_type, *l_n, *r_n, options(), b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); - } + // String* Eval::operator()(SupportsCondition* condition) + // { + // throw std::runtime_error("to delete"); + // return SASS_MEMORY_NEW(String, condition->pstate(), + // _visitSupportsCondition(condition)); + // } + + /// Adds the values in [map] to [values]. + /// + /// Throws a [RuntimeException] associated with [nodeForSpan]'s source + /// span if any [map] keys aren't strings. + /// + /// If [convert] is passed, that's used to convert the map values to the value + /// type for [values]. Otherwise, the [Value]s are used as-is. + /// + /// This takes an [AstNode] rather than a [FileSpan] so it can avoid calling + /// [AstNode.span] if the span isn't required, since some nodes need to do + /// real work to manufacture a source span. + void Eval::_addRestValueMap(ValueFlatMap& values, Map* map, const SourceSpan& pstate) { + // convert ??= (value) = > value as T; + + for(auto kv : map->elements()) { + if (String* str = kv.first->isaString()) { + values[str->value()] = kv.second; } - // lhs is number and rhs is color - // Todo: allow to work with HSLA colors - else if (Color* r_col = Cast(rhs)) { - Color_RGBA_Obj r_c = r_col->toRGBA(); - try { - switch (op_type) { - case Sass_OP::EQ: return *l_n == *r_c ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_n == *r_c ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_number_color(op_type, *l_n, *r_c, options(), b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); - } + else { + callStackFrame frame(logger456, pstate); + throw Exception::RuntimeException(logger456, + "Variable keyword argument map must have string keys.\n" + + kv.first->inspect() + " is not a string in " + + map->inspect() + "."); } } - else if (Color* l_col = Cast(lhs)) { - Color_RGBA_Obj l_c = l_col->toRGBA(); - // lhs is color and rhs is color - if (Color* r_col = Cast(rhs)) { - Color_RGBA_Obj r_c = r_col->toRGBA(); - try { - switch (op_type) { - case Sass_OP::EQ: return *l_c == *r_c ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_c == *r_c ? bool_false : bool_true; - case Sass_OP::LT: return *l_c < *r_c ? bool_true : bool_false; - case Sass_OP::GTE: return *l_c < *r_c ? bool_false : bool_true; - case Sass_OP::LTE: return *l_c < *r_c || *l_c == *r_c ? bool_true : bool_false; - case Sass_OP::GT: return *l_c < *r_c || *l_c == *r_c ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_colors(op_type, *l_c, *r_c, options(), b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); - } + } + + /// Adds the values in [map] to [values]. + void Eval::_addRestExpressionMap(ExpressionFlatMap& values, Map* map, const SourceSpan& pstate) { + // convert ??= (value) = > value as T; + + for (auto kv : map->elements()) { + if (String* str = kv.first->isaString()) { + values[str->value()] = SASS_MEMORY_NEW( + ValueExpression, map->pstate(), kv.second); } - // lhs is color and rhs is number - else if (Number* r_n = Cast(rhs)) { - try { - switch (op_type) { - case Sass_OP::EQ: return *l_c == *r_n ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_c == *r_n ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_color_number(op_type, *l_c, *r_n, options(), b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); - } + else { + callStackFrame frame(logger456, pstate); + throw Exception::RuntimeException(logger456, + "Variable keyword argument map must have string keys.\n" + + kv.first->inspect() + " is not a string in " + + map->inspect() + "."); } } + } - String_Schema_Obj ret_schema; - // only the last item will be used to eval the binary expression - if (String_Schema* s_l = Cast(b->left())) { - if (!s_l->has_interpolant() && (!s_l->is_right_interpolant())) { - ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); - Binary_ExpressionObj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), - b->op(), s_l->last(), b->right()); - bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // unverified - for (size_t i = 0; i < s_l->length() - 1; ++i) { - ret_schema->append(s_l->at(i)->perform(this)); + CssMediaQueryVector Eval::mergeMediaQueries( + const CssMediaQueryVector& lhs, + const CssMediaQueryVector& rhs) + { + CssMediaQueryVector queries; + for (const CssMediaQueryObj& query1 : lhs) { + for (const CssMediaQueryObj& query2 : rhs) { + CssMediaQueryObj result(query1->merge(query2)); + if (result && !result->empty()) { + queries.emplace_back(result); } - ret_schema->append(bin_ex->perform(this)); - return ret_schema->perform(this); } } - if (String_Schema* s_r = Cast(b->right())) { + return queries; + } - if (!s_r->has_interpolant() && (!s_r->is_left_interpolant() || op_type == Sass_OP::DIV)) { - ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); - Binary_ExpressionObj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), - b->op(), b->left(), s_r->first()); - bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // verified - ret_schema->append(bin_ex->perform(this)); - for (size_t i = 1; i < s_r->length(); ++i) { - ret_schema->append(s_r->at(i)->perform(this)); - } - return ret_schema->perform(this); - } - } + Value* Eval::visitDeclaration(Declaration* node) + { - // fully evaluate their values - if (op_type == Sass_OP::EQ || - op_type == Sass_OP::NEQ || - op_type == Sass_OP::GT || - op_type == Sass_OP::GTE || - op_type == Sass_OP::LT || - op_type == Sass_OP::LTE) - { - LOCAL_FLAG(force, true); - lhs->is_expanded(false); - lhs->set_delayed(false); - lhs = lhs->perform(this); - rhs->is_expanded(false); - rhs->set_delayed(false); - rhs = rhs->perform(this); - } - else { - lhs = lhs->perform(this); + if (!isInStyleRule() && !inUnknownAtRule && !inKeyframes) { + logger456.addFinalStackTrace(node->pstate()); + throw Exception::RuntimeException(traces, + "Declarations may only be used within style rules."); } - // not a logical connective, so go ahead and eval the rhs - rhs = rhs->perform(this); - AST_Node_Obj lu = lhs; - AST_Node_Obj ru = rhs; + CssStringObj name = interpolationToCssString(node->name(), true, false); - Expression::Type l_type; - Expression::Type r_type; + bool is_custom_property = node->is_custom_property(); - // Is one of the operands an interpolant? - String_Schema_Obj s1 = Cast(b->left()); - String_Schema_Obj s2 = Cast(b->right()); - Binary_ExpressionObj b1 = Cast(b->left()); - Binary_ExpressionObj b2 = Cast(b->right()); + if (!declarationName.empty()) { + name->text(declarationName + "-" + name->text()); + } - bool schema_op = false; + ValueObj cssValue; + if (node->value()) { + cssValue = node->value()->accept(this); + } - bool force_delay = (s2 && s2->is_left_interpolant()) || - (s1 && s1->is_right_interpolant()) || - (b1 && b1->is_right_interpolant()) || - (b2 && b2->is_left_interpolant()); + // The parent to add declarations too - if ((s1 && s1->has_interpolants()) || (s2 && s2->has_interpolants()) || force_delay) - { - if (op_type == Sass_OP::DIV || op_type == Sass_OP::MUL || op_type == Sass_OP::MOD || op_type == Sass_OP::ADD || op_type == Sass_OP::SUB || - op_type == Sass_OP::EQ) { - // If possible upgrade LHS to a number (for number to string compare) - if (String_Constant* str = Cast(lhs)) { - sass::string value(str->value()); - const char* start = value.c_str(); - if (Prelexer::sequence < Prelexer::dimension, Prelexer::end_of_file >(start) != 0) { - lhs = Parser::lexed_dimension(b->pstate(), str->value()); - } - } - // If possible upgrade RHS to a number (for string to number compare) - if (String_Constant* str = Cast(rhs)) { - sass::string value(str->value()); - const char* start = value.c_str(); - if (Prelexer::sequence < Prelexer::dimension, Prelexer::number >(start) != 0) { - rhs = Parser::lexed_dimension(b->pstate(), str->value()); - } - } - } + // If the value is an empty list, preserve it, because converting it to CSS + // will throw an error that we want the user to see. + if (cssValue != nullptr && (!cssValue->isBlank() + || cssValue->lengthAsList() == 0)) { + current->addNode(SASS_MEMORY_NEW(CssDeclaration, + node->pstate(), name, cssValue, is_custom_property)); + } + else if (is_custom_property) { + callStackFrame frame(logger456, node->value()->pstate()); + throw Exception::RuntimeException(logger456, + "Custom property values may not be empty."); + } - To_Value to_value(ctx); - ValueObj v_l = Cast(lhs->perform(&to_value)); - ValueObj v_r = Cast(rhs->perform(&to_value)); - - if (force_delay) { - sass::string str(""); - str += v_l->to_string(options()); - if (b->op().ws_before) str += " "; - str += b->separator(); - if (b->op().ws_after) str += " "; - str += v_r->to_string(options()); - String_Constant* val = SASS_MEMORY_NEW(String_Constant, b->pstate(), str); - val->is_interpolant(b->left()->has_interpolant()); - return val; + if (!node->empty()) { + LocalOption ll1(declarationName, name->text()); + for (Statement* child : node->elements()) { + ValueObj result = child->accept(this); } } + return nullptr; + } - // see if it's a relational expression - try { - switch(op_type) { - case Sass_OP::EQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::eq(lhs, rhs)); - case Sass_OP::NEQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::neq(lhs, rhs)); - case Sass_OP::GT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gt(lhs, rhs)); - case Sass_OP::GTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gte(lhs, rhs)); - case Sass_OP::LT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lt(lhs, rhs)); - case Sass_OP::LTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lte(lhs, rhs)); - default: break; + Value* Eval::visitAssignRule(AssignRule* a) + { + + // We always must have at least one variable + SASS_ASSERT(a->vidxs().empty(), "Invalid VIDX"); + // Get the main (local) variable + VarRef vidx(a->vidxs().front()); + // Check if we can overwrite an existing variable + // We create it locally if none other there yet. + for (auto idx : a->vidxs()) { + // Get the value reference (now guaranteed to exist) + ValueObj& value(compiler.varRoot.getVariable(idx)); + // Skip if nothing is stored yet, keep looking + // for another variable with a value set + if (value == nullptr) continue; + // Set variable if default flag is not set or + // if no value has been set yet or that value + // was explicitly set to a SassNull value. + if (!a->is_default() || value->isNull()) { + compiler.varRoot.setVariable(idx, + a->value()->accept(this)); } + // Finished + return nullptr; } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b->pstate())); - throw Exception::SassValueError(traces, b->pstate(), err); - } - - l_type = lhs->concrete_type(); - r_type = rhs->concrete_type(); - - // ToDo: throw error in op functions - // ToDo: then catch and re-throw them - ExpressionObj rv; - try { - SourceSpan pstate(b->pstate()); - if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { - Number* l_n = Cast(lhs); - Number* r_n = Cast(rhs); - l_n->reduce(); r_n->reduce(); - rv = Operators::op_numbers(op_type, *l_n, *r_n, options(), pstate); - } - else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { - Number* l_n = Cast(lhs); - Color_RGBA_Obj r_c = Cast(rhs)->toRGBA(); - rv = Operators::op_number_color(op_type, *l_n, *r_c, options(), pstate); - } - else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { - Color_RGBA_Obj l_c = Cast(lhs)->toRGBA(); - Number* r_n = Cast(rhs); - rv = Operators::op_color_number(op_type, *l_c, *r_n, options(), pstate); - } - else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { - Color_RGBA_Obj l_c = Cast(lhs)->toRGBA(); - Color_RGBA_Obj r_c = Cast(rhs)->toRGBA(); - rv = Operators::op_colors(op_type, *l_c, *r_c, options(), pstate); + + // Emit deprecation for new var with global flag + if (a->is_global()) { + // Check if we are at the global scope + if (compiler.varRoot.isGlobal()) { + logger456.addDeprecation( + "As of LibSass 4.1, !global assignments won't be able to declare new" + " variables. Since this assignment is at the root of the stylesheet," + " the !global flag is unnecessary and can safely be removed.", + a->pstate()); } else { - To_Value to_value(ctx); - // this will leak if perform does not return a value! - ValueObj v_l = Cast(lhs->perform(&to_value)); - ValueObj v_r = Cast(rhs->perform(&to_value)); - bool interpolant = b->is_right_interpolant() || - b->is_left_interpolant() || - b->is_interpolant(); - if (op_type == Sass_OP::SUB) interpolant = false; - // if (op_type == Sass_OP::DIV) interpolant = true; - // check for type violations - if (l_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { - traces.push_back(Backtrace(v_l->pstate())); - throw Exception::InvalidValue(traces, *v_l); - } - if (r_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { - traces.push_back(Backtrace(v_r->pstate())); - throw Exception::InvalidValue(traces, *v_r); - } - Value* ex = Operators::op_strings(b->op(), *v_l, *v_r, options(), pstate, !interpolant); // pass true to compress - if (String_Constant* str = Cast(ex)) - { - if (str->concrete_type() == Expression::STRING) - { - String_Constant* lstr = Cast(lhs); - String_Constant* rstr = Cast(rhs); - if (op_type != Sass_OP::SUB) { - if (String_Constant* org = lstr ? lstr : rstr) - { str->quote_mark(org->quote_mark()); } - } - } - } - ex->is_interpolant(b->is_interpolant()); - rv = ex; + logger456.addDeprecation( + "As of LibSass 4.1, !global assignments won't be able to declare new variables." + " Consider adding `$" + a->variable().orig() + ": null` at the root of the stylesheet.", + a->pstate()); } } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b->pstate())); - // throw Exception::Base(b->pstate(), err.what()); - throw Exception::SassValueError(traces, b->pstate(), err); - } - if (rv) { - if (schema_op) { - // XXX: this is never hit via spec tests - (*s2)[0] = rv; - rv = s2->perform(this); - } - } + // Now create the new variable + compiler.varRoot.setVariable(vidx, + a->value()->accept(this)); - return rv.detach(); + return nullptr; + } + Value* Eval::visitLoudComment(LoudComment* c) + { + if (inFunction) return nullptr; + sass::string text(acceptInterpolation(c->text(), false)); + bool preserve = text[2] == '!'; + current->addNode(SASS_MEMORY_NEW(CssComment, c->pstate(), text, preserve)); + return nullptr; } - Expression* Eval::operator()(Unary_Expression* u) + Value* Eval::visitIfRule(IfRule* i) { - ExpressionObj operand = u->operand()->perform(this); - if (u->optype() == Unary_Expression::NOT) { - Boolean* result = SASS_MEMORY_NEW(Boolean, u->pstate(), (bool)*operand); - result->value(!result->value()); - return result; - } - else if (Number_Obj nr = Cast(operand)) { - // negate value for minus unary expression - if (u->optype() == Unary_Expression::MINUS) { - Number_Obj cpy = SASS_MEMORY_COPY(nr); - cpy->value( - cpy->value() ); // negate value - return cpy.detach(); // return the copy + ValueObj rv; + // Has a condition? + if (i->predicate()) { + // Execute the condition statement + ValueObj condition = i->predicate()->accept(this); + // If true append all children of this clause + if (condition->isTruthy()) { + // Create local variable scope for children + EnvScope scoped(compiler.varRoot, i->idxs()); + rv = acceptChildren(i); } - else if (u->optype() == Unary_Expression::SLASH) { - sass::string str = '/' + nr->to_string(options()); - return SASS_MEMORY_NEW(String_Constant, u->pstate(), str); + else if (i->alternative()) { + // If condition is falsy, execute else blocks + rv = visitIfRule(i->alternative()); } - // nothing for positive - return nr.detach(); } else { - // Special cases: +/- variables which evaluate to null output just +/-, - // but +/- null itself outputs the string - if (operand->concrete_type() == Expression::NULL_VAL && Cast(u->operand())) { - u->operand(SASS_MEMORY_NEW(String_Quoted, u->pstate(), "")); - } - // Never apply unary opertions on colors @see #2140 - else if (Color* color = Cast(operand)) { - // Use the color name if this was eval with one - if (color->disp().length() > 0) { - Unary_ExpressionObj cpy = SASS_MEMORY_COPY(u); - cpy->operand(SASS_MEMORY_NEW(String_Constant, operand->pstate(), color->disp())); - return SASS_MEMORY_NEW(String_Quoted, - cpy->pstate(), - cpy->inspect()); - } - } - else { - Unary_ExpressionObj cpy = SASS_MEMORY_COPY(u); - cpy->operand(operand); - return SASS_MEMORY_NEW(String_Quoted, - cpy->pstate(), - cpy->inspect()); - } - - return SASS_MEMORY_NEW(String_Quoted, - u->pstate(), - u->inspect()); + EnvScope scoped(compiler.varRoot, i->idxs()); + rv = acceptChildren(i); } - // unreachable - return u; + // Is probably nullptr!? + return rv.detach(); } - Expression* Eval::operator()(Function_Call* c) + // For does not create a new env scope + // But iteration vars are reset afterwards + Value* Eval::visitForRule(ForRule* f) { - if (traces.size() > Constants::MaxCallStack) { - // XXX: this is never hit via spec tests - sass::ostream stm; - stm << "Stack depth exceeded max of " << Constants::MaxCallStack; - error(stm.str(), c->pstate(), traces); + BackTrace trace(f->pstate(), Strings::forRule); + EnvScope scoped(compiler.varRoot, f->idxs()); + ValueObj low = f->lower_bound()->accept(this); + ValueObj high = f->upper_bound()->accept(this); + NumberObj sass_start = low->assertNumber(logger456, ""); + NumberObj sass_end = high->assertNumber(logger456, ""); + // check if units are valid for sequence + if (sass_start->unit() != sass_end->unit()) { + sass::sstream msg; msg << "Incompatible units " + << sass_start->unit() << " and " + << sass_end->unit() << "."; + logger456.addFinalStackTrace(low->pstate()); + throw Exception::RuntimeException(traces, msg.str()); } - - if (Cast(c->sname())) { - ExpressionObj evaluated_name = c->sname()->perform(this); - ExpressionObj evaluated_args = c->arguments()->perform(this); - sass::string str(evaluated_name->to_string()); - str += evaluated_args->to_string(); - return SASS_MEMORY_NEW(String_Constant, c->pstate(), str); + double start = sass_start->value(); + double end = sass_end->value(); + // only create iterator once in this environment + ValueObj val; + if (start < end) { + if (f->is_inclusive()) ++end; + for (double i = start; i < end; ++i) { + NumberObj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); + compiler.varRoot.setVariable(f->idxs()->varFrame, 0, it); + val = acceptChildren(f); + if (val) break; + } } - - sass::string name(Util::normalize_underscores(c->name())); - sass::string full_name(name + "[f]"); - - // we make a clone here, need to implement that further - Arguments_Obj args = c->arguments(); - - Env* env = environment(); - if (!env->has(full_name) || (!c->via_call() && Prelexer::re_special_fun(name.c_str()))) { - if (!env->has("*[f]")) { - for (Argument_Obj arg : args->elements()) { - if (List_Obj ls = Cast(arg->value())) { - if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate(), traces); - } - } - args = Cast(args->perform(this)); - Function_Call_Obj lit = SASS_MEMORY_NEW(Function_Call, - c->pstate(), - c->name(), - args); - if (args->has_named_arguments()) { - error("Plain CSS function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces); - } - String_Quoted* str = SASS_MEMORY_NEW(String_Quoted, - c->pstate(), - lit->to_string(options())); - str->is_interpolant(c->is_interpolant()); - return str; - } else { - // call generic function - full_name = "*[f]"; + else { + if (f->is_inclusive()) --end; + for (double i = start; i > end; --i) { + NumberObj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); + compiler.varRoot.setVariable(f->idxs()->varFrame, 0, it); + val = acceptChildren(f); + if (val) break; } } + return val.detach(); + } - // further delay for calls - if (full_name != "call[f]") { - args->set_delayed(false); // verified - } - if (full_name != "if[f]") { - args = Cast(args->perform(this)); + Value* Eval::visitExtendRule(ExtendRule* e) + { + + if (!isInStyleRule() /* || !declarationName.empty() */) { + logger456.addFinalStackTrace(e->pstate()); + throw Exception::RuntimeException(traces, + "@extend may only be used within style rules."); } - Definition* def = Cast((*env)[full_name]); - if (c->func()) def = c->func()->definition(); + SelectorListObj slist = interpolationToSelector( + e->selector(), plainCss, current == nullptr); - if (def->is_overload_stub()) { - sass::ostream ss; - size_t L = args->length(); - // account for rest arguments - if (args->has_rest_argument() && args->length() > 0) { - // get the rest arguments list - List* rest = Cast(args->last()->value()); - // arguments before rest argument plus rest - if (rest) L += rest->length() - 1; - } - ss << full_name << L; - full_name = ss.str(); - sass::string resolved_name(full_name); - if (!env->has(resolved_name)) error("overloaded function `" + sass::string(c->name()) + "` given wrong number of arguments", c->pstate(), traces); - def = Cast((*env)[resolved_name]); - } - - ExpressionObj result = c; - Block_Obj body = def->block(); - Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - - if (c->is_css()) return result.detach(); - - Parameters_Obj params = def->parameters(); - Env fn_env(def->environment()); - env_stack().push_back(&fn_env); - - if (func || body) { - bind(sass::string("Function"), c->name(), params, args, &fn_env, this, traces); - sass::string msg(", in function `" + c->name() + "`"); - traces.push_back(Backtrace(c->pstate(), msg)); - callee_stack().push_back({ - c->name().c_str(), - c->pstate().getPath(), - c->pstate().getLine(), - c->pstate().getColumn(), - SASS_CALLEE_FUNCTION, - { env } - }); - - // eval the body if user-defined or special, invoke underlying CPP function if native - if (body /* && !Prelexer::re_special_fun(name.c_str()) */) { - result = body->perform(this); - } - else if (func) { - result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.getSelectorStack(), exp.originalStack); - } - if (!result) { - error(sass::string("Function ") + c->name() + " finished without @return", c->pstate(), traces); - } - callee_stack().pop_back(); - traces.pop_back(); - } - - // else if it's a user-defined c function - // convert call into C-API compatible form - else if (c_function) { - Sass_Function_Fn c_func = sass_function_get_function(c_function); - if (full_name == "*[f]") { - String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), c->name()); - Arguments_Obj new_args = SASS_MEMORY_NEW(Arguments, c->pstate()); - new_args->append(SASS_MEMORY_NEW(Argument, c->pstate(), str)); - new_args->concat(args); - args = new_args; - } + if (slist) { - // populates env with default values for params - sass::string ff(c->name()); - bind(sass::string("Function"), c->name(), params, args, &fn_env, this, traces); - sass::string msg(", in function `" + c->name() + "`"); - traces.push_back(Backtrace(c->pstate(), msg)); - callee_stack().push_back({ - c->name().c_str(), - c->pstate().getPath(), - c->pstate().getLine(), - c->pstate().getColumn(), - SASS_CALLEE_C_FUNCTION, - { env } - }); - - AST2C ast2c; - union Sass_Value* c_args = sass_make_list(params->length(), SASS_COMMA, false); - for(size_t i = 0; i < params->length(); i++) { - Parameter_Obj param = params->at(i); - sass::string key = param->name(); - AST_Node_Obj node = fn_env.get_local(key); - ExpressionObj arg = Cast(node); - sass_list_set_value(c_args, i, arg->perform(&ast2c)); - } - union Sass_Value* c_val = c_func(c_args, c_function, compiler()); - if (sass_value_get_tag(c_val) == SASS_ERROR) { - sass::string message("error in C function " + c->name() + ": " + sass_error_get_message(c_val)); - sass_delete_value(c_val); - sass_delete_value(c_args); - error(message, c->pstate(), traces); - } else if (sass_value_get_tag(c_val) == SASS_WARNING) { - sass::string message("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val)); - sass_delete_value(c_val); - sass_delete_value(c_args); - error(message, c->pstate(), traces); - } - result = c2ast(c_val, traces, c->pstate()); + for (auto complex : slist->elements()) { - callee_stack().pop_back(); - traces.pop_back(); - sass_delete_value(c_args); - if (c_val != c_args) - sass_delete_value(c_val); - } + if (complex->size() != 1) { + logger456.addFinalStackTrace(complex->pstate()); + throw Exception::RuntimeException(traces, + "complex selectors may not be extended."); + } - // link back to function definition - // only do this for custom functions - if (result->pstate().getSrcId() == sass::string::npos) - result->pstate(c->pstate()); + if (const CompoundSelector * compound = complex->first()->isaCompoundSelector()) { - result = result->perform(this); - result->is_interpolant(c->is_interpolant()); - env_stack().pop_back(); - return result.detach(); - } + if (compound->size() != 1) { - Expression* Eval::operator()(Variable* v) - { - ExpressionObj value; - Env* env = environment(); - const sass::string& name(v->name()); - EnvResult rv(env->find(name)); - if (rv.found) value = static_cast(rv.it->second.ptr()); - else error("Undefined variable: \"" + v->name() + "\".", v->pstate(), traces); - if (Argument* arg = Cast(value)) value = arg->value(); - if (Number* nr = Cast(value)) nr->zero(true); // force flag - value->is_interpolant(v->is_interpolant()); - if (force) value->is_expanded(false); - value->set_delayed(false); // verified - value = value->perform(this); - if(!force) rv.it->second = value; - return value.detach(); - } + sass::sstream sels; bool addComma = false; + sels << "Compound selectors may no longer be extended. Consider `@extend "; + for (auto sel : compound->elements()) { + if (addComma) sels << ", "; + sels << sel->inspect(); + addComma = true; + } + sels << "` instead. See http://bit.ly/ExtendCompound for details."; - Expression* Eval::operator()(Color_RGBA* c) - { - return c; - } + logger456.addDeprecation(sels.str(), compound->pstate()); - Expression* Eval::operator()(Color_HSLA* c) - { - return c; - } + // Make this an error once deprecation is over + for (SimpleSelectorObj simple : compound->elements()) { + // Pass every selector we ever see to extender (to make them findable for extend) + extender.addExtension(selector(), simple, mediaStack.back(), e->is_optional()); + } - Expression* Eval::operator()(Number* n) - { - return n; - } + } + else { + // Pass every selector we ever see to extender (to make them findable for extend) + extender.addExtension(selector(), compound->first(), mediaStack.back(), e->is_optional()); + } - Expression* Eval::operator()(Boolean* b) - { - return b; + } + else { + logger456.addFinalStackTrace(complex->pstate()); + throw Exception::RuntimeException(traces, + "complex selectors may not be extended."); + } + } + } + + return nullptr; } - void Eval::interpolation(Context& ctx, sass::string& res, ExpressionObj ex, bool into_quotes, bool was_itpl) { - bool needs_closing_brace = false; - if (Arguments* args = Cast(ex)) { - List* ll = SASS_MEMORY_NEW(List, args->pstate(), 0, SASS_COMMA); - for(auto arg : args->elements()) { - ll->append(arg->value()); - } - ll->is_interpolant(args->is_interpolant()); - needs_closing_brace = true; - res += "("; - ex = ll; - } - if (Number* nr = Cast(ex)) { - Number reduced(nr); - reduced.reduce(); - if (!reduced.is_valid_css_unit()) { - traces.push_back(Backtrace(nr->pstate())); - throw Exception::InvalidValue(traces, *nr); + Value* Eval::visitEachRule(EachRule* e) + { + const VarRefs* vidx(e->idxs()); + const sass::vector& variables(e->variables()); + EnvScope scoped(compiler.varRoot, e->idxs()); + ValueObj expr = e->expressions()->accept(this); + if (MapObj map = expr->isaMap()) { + Map::ordered_map_type els(map->elements()); + for (auto kv : els) { + ValueObj key = kv.first; + ValueObj value = kv.second; + if (variables.size() == 1) { + List* variable = SASS_MEMORY_NEW(List, + map->pstate(), { key, value }, SASS_SPACE); + compiler.varRoot.setVariable(vidx->varFrame, 0, variable); + } + else { + compiler.varRoot.setVariable(vidx->varFrame, 0, key); + compiler.varRoot.setVariable(vidx->varFrame, 1, value); + } + ValueObj val = acceptChildren(e); + if (val) return val.detach(); } + return nullptr; + } + + ListObj list; + if (List* slist = expr->isaList()) { + list = SASS_MEMORY_NEW(List, expr->pstate(), + slist->elements(), slist->separator()); + list->hasBrackets(slist->hasBrackets()); } - if (Argument* arg = Cast(ex)) { - ex = arg->value(); + else { + list = SASS_MEMORY_NEW(List, expr->pstate(), + { expr }, SASS_COMMA); } - if (String_Quoted* sq = Cast(ex)) { - if (was_itpl) { - bool was_interpolant = ex->is_interpolant(); - ex = SASS_MEMORY_NEW(String_Constant, sq->pstate(), sq->value()); - ex->is_interpolant(was_interpolant); + for (size_t i = 0, L = list->size(); i < L; ++i) { + Value* item = list->get(i); + // check if we got passed a list of args (investigate) + if (List* scalars = item->isaList()) { // Ex + if (variables.size() == 1) { + compiler.varRoot.setVariable(vidx->varFrame, 0, scalars); + } + else { + for (size_t j = 0, K = variables.size(); j < K; ++j) { + compiler.varRoot.setVariable(vidx->varFrame, (uint32_t)j, + j < scalars->size() ? scalars->get(j) + : SASS_MEMORY_NEW(Null, expr->pstate())); + } + } + } + else { + if (variables.size() > 0) { + compiler.varRoot.setVariable(vidx->varFrame, 0, item); + for (size_t j = 1, K = variables.size(); j < K; ++j) { + Value* res = SASS_MEMORY_NEW(Null, expr->pstate()); + compiler.varRoot.setVariable(vidx->varFrame, (uint32_t)j, res); + } + } } + ValueObj val = acceptChildren(e); + if (val) return val.detach(); } - if (Cast(ex)) { return; } + return nullptr; + } - // parent selector needs another go - if (Cast(ex)) { - // XXX: this is never hit via spec tests - ex = ex->perform(this); - } + Value* Eval::visitWhileRule(WhileRule* node) + { - if (List* l = Cast(ex)) { - List_Obj ll = SASS_MEMORY_NEW(List, l->pstate(), 0, l->separator()); - // this fixes an issue with bourbon sample, not really sure why - // if (l->size() && Cast((*l)[0])) { res += ""; } - for(ExpressionObj item : *l) { - item->is_interpolant(l->is_interpolant()); - sass::string rl(""); interpolation(ctx, rl, item, into_quotes, l->is_interpolant()); - bool is_null = Cast(item) != 0; // rl != "" - if (!is_null) ll->append(SASS_MEMORY_NEW(String_Quoted, item->pstate(), rl)); - } - // Check indicates that we probably should not get a list - // here. Normally single list items are already unwrapped. - if (l->size() > 1) { - // string_to_output would fail "#{'_\a' '_\a'}"; - sass::string str(ll->to_string(options())); - str = read_hex_escapes(str); // read escapes - newline_to_space(str); // replace directly - res += str; // append to result string - } else { - res += (ll->to_string(options())); - } - ll->is_interpolant(l->is_interpolant()); - } + // First condition runs outside + EnvScope scoped(compiler.varRoot, node->idxs()); + Expression* condition = node->condition(); + ValueObj result = condition->accept(this); - // Value - // Function_Call - // Selector_List - // String_Quoted - // String_Constant - // Binary_Expression - else { - // ex = ex->perform(this); - if (into_quotes && ex->is_interpolant()) { - res += evacuate_escapes(ex ? ex->to_string(options()) : ""); - } else { - sass::string str(ex ? ex->to_string(options()) : ""); - if (into_quotes) str = read_hex_escapes(str); - res += str; // append to result string - } - } - if (needs_closing_brace) res += ")"; - } + // Evaluate the first run in outer scope + // All successive runs are from inner scope + if (result->isTruthy()) { - Expression* Eval::operator()(String_Schema* s) - { - size_t L = s->length(); - bool into_quotes = false; - if (L > 1) { - if (!Cast((*s)[0]) && !Cast((*s)[L - 1])) { - if (String_Constant* l = Cast((*s)[0])) { - if (String_Constant* r = Cast((*s)[L - 1])) { - if (r->value().size() > 0) { - if (l->value()[0] == '"' && r->value()[r->value().size() - 1] == '"') into_quotes = true; - if (l->value()[0] == '\'' && r->value()[r->value().size() - 1] == '\'') into_quotes = true; - } + while (true) { + result = acceptChildren(node); + if (result) { + return result.detach(); } + result = condition->accept(this); + if (!result->isTruthy()) break; } - } - } - bool was_quoted = false; - bool was_interpolant = false; - sass::string res(""); - for (size_t i = 0; i < L; ++i) { - bool is_quoted = Cast((*s)[i]) != NULL; - if (was_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } - else if (i > 0 && is_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } - ExpressionObj ex = (*s)[i]->perform(this); - interpolation(ctx, res, ex, into_quotes, ex->is_interpolant()); - was_quoted = Cast((*s)[i]) != NULL; - was_interpolant = (*s)[i]->is_interpolant(); } - if (!s->is_interpolant()) { - if (s->length() > 1 && res == "") return SASS_MEMORY_NEW(Null, s->pstate()); - String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res, s->css()); - return str.detach(); - } - // string schema seems to have a special unquoting behavior (also handles "nested" quotes) - String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), res, 0, false, false, false, s->css()); - // if (s->is_interpolant()) str->quote_mark(0); - // String_Constant* str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res); - if (str->quote_mark()) str->quote_mark('*'); - else if (!is_in_comment) str->value(string_to_output(str->value())); - str->is_interpolant(s->is_interpolant()); - return str.detach(); - } - - Expression* Eval::operator()(String_Constant* s) - { - return s; - } + return nullptr; - Expression* Eval::operator()(String_Quoted* s) - { - String_Quoted* str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), ""); - str->value(s->value()); - str->quote_mark(s->quote_mark()); - str->is_interpolant(s->is_interpolant()); - return str; } - Expression* Eval::operator()(SupportsOperation* c) + Value* Eval::visitMixinRule(MixinRule* rule) { - Expression* left = c->left()->perform(this); - Expression* right = c->right()->perform(this); - SupportsOperation* cc = SASS_MEMORY_NEW(SupportsOperation, - c->pstate(), - Cast(left), - Cast(right), - c->operand()); - return cc; + UserDefinedCallableObj callable = + SASS_MEMORY_NEW(UserDefinedCallable, + rule->pstate(), rule->name(), rule, nullptr); + compiler.varRoot.setMixin(rule->midx(), callable); + return nullptr; } - Expression* Eval::operator()(SupportsNegation* c) + Value* Eval::visitReturnRule(ReturnRule* rule) { - Expression* condition = c->condition()->perform(this); - SupportsNegation* cc = SASS_MEMORY_NEW(SupportsNegation, - c->pstate(), - Cast(condition)); - return cc; + return rule->value()->accept(this); } - Expression* Eval::operator()(SupportsDeclaration* c) + Value* Eval::visitFunctionRule(FunctionRule* rule) { - Expression* feature = c->feature()->perform(this); - Expression* value = c->value()->perform(this); - SupportsDeclaration* cc = SASS_MEMORY_NEW(SupportsDeclaration, - c->pstate(), - feature, - value); - return cc; + UserDefinedCallableObj callable = + SASS_MEMORY_NEW(UserDefinedCallable, + rule->pstate(), rule->name(), rule, nullptr); + compiler.varRoot.setFunction(rule->fidx(), callable); + return nullptr; } - Expression* Eval::operator()(Supports_Interpolation* c) + Value* Eval::visitSilentComment(SilentComment* c) { - Expression* value = c->value()->perform(this); - Supports_Interpolation* cc = SASS_MEMORY_NEW(Supports_Interpolation, - c->pstate(), - value); - return cc; + // current->append(c); + return nullptr; } - Expression* Eval::operator()(At_Root_Query* e) + void Eval::acceptIncludeImport(IncludeImport* rule) { - ExpressionObj feature = e->feature(); - feature = (feature ? feature->perform(this) : 0); - ExpressionObj value = e->value(); - value = (value ? value->perform(this) : 0); - Expression* ee = SASS_MEMORY_NEW(At_Root_Query, - e->pstate(), - Cast(feature), - value); - return ee; - } - Media_Query* Eval::operator()(Media_Query* q) - { - String_Obj t = q->media_type(); - t = static_cast(t.isNull() ? 0 : t->perform(this)); - Media_Query_Obj qq = SASS_MEMORY_NEW(Media_Query, - q->pstate(), - t, - q->length(), - q->is_negated(), - q->is_restricted()); - for (size_t i = 0, L = q->length(); i < L; ++i) { - qq->append(static_cast((*q)[i]->perform(this))); - } - return qq.detach(); - } + // Get the include loaded by parser + // const ResolvedImport& include(rule->include()); - Expression* Eval::operator()(Media_Query_Expression* e) - { - ExpressionObj feature = e->feature(); - feature = (feature ? feature->perform(this) : 0); - if (feature && Cast(feature)) { - feature = SASS_MEMORY_NEW(String_Quoted, - feature->pstate(), - Cast(feature)->value()); - } - ExpressionObj value = e->value(); - value = (value ? value->perform(this) : 0); - if (value && Cast(value)) { - // XXX: this is never hit via spec tests - value = SASS_MEMORY_NEW(String_Quoted, - value->pstate(), - Cast(value)->value()); - } - return SASS_MEMORY_NEW(Media_Query_Expression, - e->pstate(), - feature, - value, - e->is_interpolated()); - } + // This will error if the path was not loaded before + // ToDo: Why don't we attach the sheet to include itself? + StyleSheetObj sheet = rule->sheet(); - Expression* Eval::operator()(Null* n) - { - return n; - } + // Create C-API exposed object to query + //struct SassImport import{ + // sheet.syntax, sheet.source, "" + //}; - Expression* Eval::operator()(Argument* a) - { - ExpressionObj val = a->value()->perform(this); - bool is_rest_argument = a->is_rest_argument(); - bool is_keyword_argument = a->is_keyword_argument(); + // Add C-API to stack to expose it + compiler.import_stack.emplace_back(sheet->import); + // compiler.import_stack2.emplace_back(source); - if (a->is_rest_argument()) { - if (val->concrete_type() == Expression::MAP) { - is_rest_argument = false; - is_keyword_argument = true; - } - else if(val->concrete_type() != Expression::LIST) { - List_Obj wrapper = SASS_MEMORY_NEW(List, - val->pstate(), - 0, - SASS_COMMA, - true); - wrapper->append(val); - val = wrapper; - } - } - return SASS_MEMORY_NEW(Argument, - a->pstate(), - val, - a->name(), - is_rest_argument, - is_keyword_argument); - } + callStackFrame frame(traces, + BackTrace(rule->pstate(), Strings::importRule)); - Expression* Eval::operator()(Arguments* a) - { - Arguments_Obj aa = SASS_MEMORY_NEW(Arguments, a->pstate()); - if (a->length() == 0) return aa.detach(); - for (size_t i = 0, L = a->length(); i < L; ++i) { - ExpressionObj rv = (*a)[i]->perform(this); - Argument* arg = Cast(rv); - if (!(arg->is_rest_argument() || arg->is_keyword_argument())) { - aa->append(arg); - } + // Evaluate the included sheet + for (const StatementObj& item : sheet->root2->elements()) { + item->accept(this); } - if (a->has_rest_argument()) { - ExpressionObj rest = a->get_rest_argument()->perform(this); - ExpressionObj splat = Cast(rest)->value()->perform(this); - - Sass_Separator separator = SASS_COMMA; - List* ls = Cast(splat); - Map* ms = Cast(splat); - - List_Obj arglist = SASS_MEMORY_NEW(List, - splat->pstate(), - 0, - ls ? ls->separator() : separator, - true); - - if (ls && ls->is_arglist()) { - arglist->concat(ls); - } else if (ms) { - aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), ms, "", false, true)); - } else if (ls) { - arglist->concat(ls); - } else { - arglist->append(splat); - } - if (arglist->length()) { - aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), arglist, "", true)); - } - } + // These data object have just been borrowed + // sass_import_take_source(compiler.import_stack.back()); + // sass_import_take_srcmap(compiler.import_stack.back()); - if (a->has_keyword_argument()) { - ExpressionObj rv = a->get_keyword_argument()->perform(this); - Argument* rvarg = Cast(rv); - ExpressionObj kwarg = rvarg->value()->perform(this); + // Finally remove if from the stack + compiler.import_stack.pop_back(); - aa->append(SASS_MEMORY_NEW(Argument, kwarg->pstate(), kwarg, "", false, true)); - } - return aa.detach(); } - Expression* Eval::operator()(Comment* c) + CssMediaQueryVector Eval::evalMediaQueries(Interpolation* itpl) { - return 0; + SourceDataObj synthetic = interpolationToSource(itpl, true); + MediaQueryParser parser(compiler, synthetic); + return parser.parse(); } - SelectorList* Eval::operator()(Selector_Schema* s) + void Eval::acceptStaticImport(StaticImport* rule) { - LOCAL_FLAG(is_in_selector_schema, true); - // the parser will look for a brace to end the selector - ExpressionObj sel = s->contents()->perform(this); - sass::string result_str(sel->to_string(options())); - result_str = unquote(Util::rtrim(result_str)); - ItplFile* source = SASS_MEMORY_NEW(ItplFile, - result_str.c_str(), s->pstate()); - Parser p(source, ctx, traces); - - // If a schema contains a reference to parent it is already - // connected to it, so don't connect implicitly anymore - SelectorListObj parsed = p.parseSelectorList(true); - flag_is_in_selector_schema.reset(); - return parsed.detach(); - } + // Create new CssImport object + CssImportObj import = SASS_MEMORY_NEW(CssImport, rule->pstate(), + interpolationToCssString(rule->url(), false, false)); + import->outOfOrder(rule->outOfOrder()); + + if (rule->supports()) { + if (auto supports = rule->supports()->isaSupportsDeclaration()) { + sass::string feature(toCss(supports->feature())); + sass::string value(toCss(supports->value())); + import->supports(SASS_MEMORY_NEW(CssString, + rule->supports()->pstate(), + // Should have a CssSupportsCondition? + // Nope, spaces are even further down + feature + ": " + value)); + } + else { + import->supports(SASS_MEMORY_NEW(CssString, rule->supports()->pstate(), + _visitSupportsCondition(rule->supports()))); + } + // Wrap the resulting condition into a `supports()` clause + import->supports()->text("supports(" + import->supports()->text() + ")"); - Expression* Eval::operator()(Parent_Reference* p) - { - if (SelectorListObj pr = exp.original()) { - return operator()(pr); - } else { - return SASS_MEMORY_NEW(Null, p->pstate()); } - } + if (rule->media()) { + import->media(evalMediaQueries(rule->media())); + } + // append new css import to result + current->addNode(import); - SimpleSelector* Eval::operator()(SimpleSelector* s) - { - return s; } - PseudoSelector* Eval::operator()(PseudoSelector* pseudo) + // Consume all imports in this rule + Value* Eval::visitImportRule(ImportRule* rule) { - // ToDo: should we eval selector? - return pseudo; - }; + for (const ImportBaseObj& import : rule->elements()) { + if (StaticImport* stimp = import->isaStaticImport()) { acceptStaticImport(stimp); } + else if (IncludeImport* stimp = import->isaIncludeImport()) { acceptIncludeImport(stimp); } + else throw std::runtime_error("undefined behavior"); + } + return nullptr; + } } diff --git a/src/eval.hpp b/src/eval.hpp index 2d8f3623e3..51a85e3080 100644 --- a/src/eval.hpp +++ b/src/eval.hpp @@ -1,107 +1,369 @@ -#ifndef SASS_EVAL_H -#define SASS_EVAL_H +#ifndef SASS_EVAL_HPP +#define SASS_EVAL_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include "context.hpp" -#include "listize.hpp" -#include "operation.hpp" -#include "environment.hpp" +// #include "context.hpp" +#include "extender.hpp" +#include "ast_supports.hpp" +#include "ast_callables.hpp" namespace Sass { - class Expand; - class Context; - - class Eval : public Operation_CRTP { - - public: - Expand& exp; - Context& ctx; - Backtraces& traces; - Eval(Expand& exp); - ~Eval(); - - bool force; - bool is_in_comment; - bool is_in_selector_schema; - - Boolean_Obj bool_true; - Boolean_Obj bool_false; - - Env* environment(); - EnvStack& env_stack(); - const sass::string cwd(); - CalleeStack& callee_stack(); - struct Sass_Inspect_Options& options(); - struct Sass_Compiler* compiler(); - - // for evaluating function bodies - Expression* operator()(Block*); - Expression* operator()(Assignment*); - Expression* operator()(If*); - Expression* operator()(ForRule*); - Expression* operator()(EachRule*); - Expression* operator()(WhileRule*); - Expression* operator()(Return*); - Expression* operator()(WarningRule*); - Expression* operator()(ErrorRule*); - Expression* operator()(DebugRule*); - - Expression* operator()(List*); - Expression* operator()(Map*); - Expression* operator()(Binary_Expression*); - Expression* operator()(Unary_Expression*); - Expression* operator()(Function_Call*); - Expression* operator()(Variable*); - Expression* operator()(Number*); - Expression* operator()(Color_RGBA*); - Expression* operator()(Color_HSLA*); - Expression* operator()(Boolean*); - Expression* operator()(String_Schema*); - Expression* operator()(String_Quoted*); - Expression* operator()(String_Constant*); - Media_Query* operator()(Media_Query*); - Expression* operator()(Media_Query_Expression*); - Expression* operator()(At_Root_Query*); - Expression* operator()(SupportsOperation*); - Expression* operator()(SupportsNegation*); - Expression* operator()(SupportsDeclaration*); - Expression* operator()(Supports_Interpolation*); - Expression* operator()(Null*); - Expression* operator()(Argument*); - Expression* operator()(Arguments*); - Expression* operator()(Comment*); - - // these will return selectors - SelectorList* operator()(SelectorList*); - SelectorList* operator()(ComplexSelector*); - CompoundSelector* operator()(CompoundSelector*); - SelectorComponent* operator()(SelectorComponent*); - SimpleSelector* operator()(SimpleSelector* s); - PseudoSelector* operator()(PseudoSelector* s); - - // they don't have any specific implementation (yet) - IDSelector* operator()(IDSelector* s) { return s; }; - ClassSelector* operator()(ClassSelector* s) { return s; }; - TypeSelector* operator()(TypeSelector* s) { return s; }; - AttributeSelector* operator()(AttributeSelector* s) { return s; }; - PlaceholderSelector* operator()(PlaceholderSelector* s) { return s; }; - - // actual evaluated selectors - SelectorList* operator()(Selector_Schema*); - Expression* operator()(Parent_Reference*); - - // generic fallback - template - Expression* fallback(U x) - { return Cast(x); } + /*#####################################################################*/ + /*#####################################################################*/ + + class Eval : + public StatementVisitor, + public ExpressionVisitor { + + private: + + // Some references + Logger& logger456; + Compiler& compiler; + BackTraces& traces; + + // The extend handler + Extender extender; + + CssParentNode* current; + CssMediaVector mediaStack; + SelectorLists originalStack; + SelectorLists selectorStack; + + // The name of the current declaration parent. Used for BEM- + // declaration blocks as in `div { prefix: { suffix: val; } }`; + sass::string declarationName; + + CssMediaQueryVector mediaQueries; + + // The style rule that defines the current parent selector, if any. + CssStyleRule* readStyleRule = nullptr; + + // Current content block + UserDefinedCallable* content88 = nullptr; + + // Whether we're working with plain css. + bool plainCss = false; + + // Whether we're currently executing a mixing. + bool inMixin = false; + + // Whether we're currently executing a function. + bool inFunction = false; + + // Whether we're currently building the output of an unknown at rule. + bool inUnknownAtRule = false; + + // Whether we're directly within an `@at-root` rule excluding style rules. + bool atRootExcludingStyleRule = false; + + // Whether we're currently building the output of a `@keyframes` rule. + bool inKeyframes = false; + + + + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // ToDo: maybe create on demand for better pstate? + // ToDo: do some benchmarks to check implications! + BooleanObj bool_true; + BooleanObj bool_false; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + public: + + // Value constructor + Eval(Compiler& compiler, + Logger& logger, + bool isCss = false); + + // Query if we are in a mixin + bool isInMixin() const { + return inMixin; + } + + // Query if use plain css + bool isPlainCss() const { + return plainCss; + } + + // Query if we have a content block + bool hasContentBlock() const { + return content88 != nullptr; + } + + // Check if there are any unsatisfied extends (will throw) + bool checkForUnsatisfiedExtends(Extension& unsatisfied) const { + return extender.checkForUnsatisfiedExtends(unsatisfied); + } + + // Main entry point to evaluation + CssRoot* acceptRoot(Root* b); + + // Another entry point for the `call` sass-function + Value* acceptFunctionExpression(FunctionExpression* expression) { + return visitFunctionExpression(expression); + } + + // Converts the expression to css representation + sass::string toCss(Expression* expression, bool quote = true); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Call built-in function with no overloads + Value* execute( + BuiltInCallable* callable, + ArgumentInvocation* arguments, + const SourceSpan& pstate, + bool selfAssign); + + // Call built-in function with overloads + Value* execute( + BuiltInCallables* callable, + ArgumentInvocation* arguments, + const SourceSpan& pstate, + bool selfAssign); + + // Used for user functions and also by + // mixin includes and content includes. + Value* execute( + UserDefinedCallable* callable, + ArgumentInvocation* arguments, + const SourceSpan& pstate, + bool selfAssign); + + // Call external C-API function + Value* execute( + ExternalCallable* callable, + ArgumentInvocation* arguments, + const SourceSpan& pstate, + bool selfAssign); + + // Return plain css call as string + // Used when function is not known + Value* execute( + PlainCssCallable* callable, + ArgumentInvocation* arguments, + const SourceSpan& pstate, + bool selfAssign); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + private: + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SelectorListObj& selector() { return selectorStack.back(); } + SelectorListObj& original() { return originalStack.back(); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Return root of current child + CssParentNode* getRoot() + { + auto parent = current; + while (parent->parent()) { + parent = parent->parent(); + } + return parent; + } + + // Check if we currently build + // the output of a style rule. + bool isInStyleRule() const { + return readStyleRule != nullptr && + !atRootExcludingStyleRule; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// private: - void interpolation(Context& ctx, sass::string& res, ExpressionObj ex, bool into_quotes, bool was_itpl = false); + + // Fetch unevaluated positional argument (optionally by name) + // Will error if argument is missing or available both ways + // Note: only needed for lazy evaluation in if expressions + Expression* getArgument( + ExpressionVector& positional, + ExpressionFlatMap& named, + size_t idx, const EnvKey& name); + + // Fetch evaluated positional argument (optionally by name) + // Will error if argument is missing or available both ways + // Named arguments are consumed and removed from the hash + Value* getParameter( + ArgumentResults& evaled, + size_t idx, const Argument* arg); + + // Call built-in function with no overloads + Value* _runBuiltInCallable( + ArgumentInvocation* arguments, + BuiltInCallable* callable, + const SourceSpan& pstate, + bool selfAssign); + + // Call built-in function with overloads + Value* _runBuiltInCallables( + ArgumentInvocation* arguments, + BuiltInCallables* callable, + const SourceSpan& pstate, + bool selfAssign); + + // Helper for _runBuiltInCallable(s) + Value* _callBuiltInCallable( + ArgumentResults& evaluated, + const SassFnPair& function, + const SourceSpan& pstate, + bool selfAssign); + + // Used for user functions and also by + // mixin includes and content includes. + Value* _runUserDefinedCallable( + ArgumentInvocation* evaled, + UserDefinedCallable* callable, + const SourceSpan& pstate); + + // Call external C-API function + Value* _runExternalCallable( + ArgumentInvocation* arguments, + ExternalCallable* callable, + const SourceSpan& pstate); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + private: + ArgumentResults _evaluateArguments(ArgumentInvocation* arguments); + void _addRestValueMap(ValueFlatMap& values, Map* map, const SourceSpan& nodeForSpan); + void _addRestExpressionMap(ExpressionFlatMap& values, Map* map, const SourceSpan& pstate); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + sass::string acceptInterpolation(InterpolationObj interpolation, bool warnForColor, bool trim = false); + SourceData* interpolationToSource(InterpolationObj interpolation, bool warnForColor, bool trim = false); + CssString* interpolationToCssString(InterpolationObj interpolation, bool warnForColor, bool trim = false); + SelectorListObj interpolationToSelector(Interpolation* interpolation, bool plainCss, bool allowParent = true); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void _evaluateMacroArguments(CallableInvocation& invocation, + ExpressionVector& positional, + ExpressionFlatMap& named); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void acceptStaticImport(StaticImport* import); + void acceptIncludeImport(IncludeImport* import); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Evaluate all children at the currently existing block context + Value* acceptChildren(const Vectorized& children); + + // Evaluate all children at a newly established current block context + Value* acceptChildrenAt(CssParentNode* parent, const Vectorized& children); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* visitBinaryOpExpression(BinaryOpExpression*); + Value* visitBooleanExpression(BooleanExpression*); + Value* visitColorExpression(ColorExpression*); + Value* visitFunctionExpression(FunctionExpression*); + Value* visitIfExpression(IfExpression*); + Value* visitListExpression(ListExpression*); + Value* visitMapExpression(MapExpression*); + Value* visitNullExpression(NullExpression*); + Value* visitNumberExpression(NumberExpression*); + Value* visitParenthesizedExpression(ParenthesizedExpression*); + Value* visitParentExpression(ParentExpression*); + Value* visitStringExpression(StringExpression*); + Value* visitUnaryOpExpression(UnaryOpExpression*); + Value* visitValueExpression(ValueExpression*); + Value* visitVariableExpression(VariableExpression*); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* visitAtRootRule(AtRootRule* rule); + Value* visitAtRule(AtRule* rule); + Value* visitContentBlock(ContentBlock* rule); + Value* visitContentRule(ContentRule* rule); + Value* visitDebugRule(DebugRule* rule); + Value* visitDeclaration(Declaration* rule); + Value* visitEachRule(EachRule* rule); + Value* visitErrorRule(ErrorRule* rule); + Value* visitExtendRule(ExtendRule* rule); + Value* visitForRule(ForRule* rule); + // Value* visitForwardRule(ForwardRule* rule); + Value* visitFunctionRule(FunctionRule* rule); + Value* visitIfRule(IfRule* rule); + Value* visitImportRule(ImportRule* rule); + Value* visitIncludeRule(IncludeRule* rule); + Value* visitLoudComment(LoudComment* rule); + Value* visitMediaRule(MediaRule* rule); + Value* visitMixinRule(MixinRule* rule); + Value* visitReturnRule(ReturnRule* rule); + Value* visitSilentComment(SilentComment* rule); + Value* visitStyleRule(StyleRule* rule); + // visitStylesheet + Value* visitSupportsRule(SupportsRule* rule); + // Value* visitUseRule(UseRule* rule); + Value* visitAssignRule(AssignRule* rule); + Value* visitWarnRule(WarnRule* rule); + Value* visitWhileRule(WhileRule* rule); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void callExternalMessageOverloadFunction(Callable* fn, Value* message); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + sass::string _visitSupportsCondition(SupportsCondition* condition); + sass::string _parenthesize(SupportsCondition* condition); + sass::string _parenthesize(SupportsCondition* condition, SupportsOperation::Operand operand); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssParentNode* _trimIncluded(CssParentVector& nodes); + + CssParentNode* hoistStyleRule(CssParentNode* node); + + CssMediaQueryVector mergeMediaQueries(const CssMediaQueryVector& lhs, const CssMediaQueryVector& rhs); + + + + + + + + + + + + + + CssMediaQueryVector evalMediaQueries(Interpolation* itpl); + + }; diff --git a/src/eval_selectors.cpp b/src/eval_selectors.cpp deleted file mode 100644 index 72035b5f56..0000000000 --- a/src/eval_selectors.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "expand.hpp" -#include "eval.hpp" -#include "ast.hpp" - - -namespace Sass { - - SelectorList* Eval::operator()(SelectorList* s) - { - sass::vector rv; - SelectorListObj sl = SASS_MEMORY_NEW(SelectorList, s->pstate()); - for (size_t i = 0, iL = s->length(); i < iL; ++i) { - rv.push_back(operator()(s->get(i))); - } - - // we should actually permutate parent first - // but here we have permutated the selector first - size_t round = 0; - while (round != sass::string::npos) { - bool abort = true; - for (size_t i = 0, iL = rv.size(); i < iL; ++i) { - if (rv[i]->length() > round) { - sl->append((*rv[i])[round]); - abort = false; - } - } - if (abort) { - round = sass::string::npos; - } - else { - ++round; - } - - } - return sl.detach(); - } - - SelectorComponent* Eval::operator()(SelectorComponent* s) - { - return {}; - } - - SelectorList* Eval::operator()(ComplexSelector* s) - { - bool implicit_parent = !exp.old_at_root_without_rule; - if (is_in_selector_schema) exp.pushNullSelector(); - SelectorListObj other = s->resolve_parent_refs( - exp.getOriginalStack(), traces, implicit_parent); - if (is_in_selector_schema) exp.popNullSelector(); - - for (size_t i = 0; i < other->length(); i++) { - ComplexSelectorObj sel = other->at(i); - for (size_t n = 0; n < sel->length(); n++) { - if (CompoundSelectorObj comp = Cast(sel->at(n))) { - sel->at(n) = operator()(comp); - } - } - } - - return other.detach(); - } - - CompoundSelector* Eval::operator()(CompoundSelector* s) - { - for (size_t i = 0; i < s->length(); i++) { - SimpleSelector* ss = s->at(i); - // skip parents here (called via resolve_parent_refs) - s->at(i) = Cast(ss->perform(this)); - } - return s; - } -} diff --git a/src/exceptions.cpp b/src/exceptions.cpp new file mode 100644 index 0000000000..85348fdb04 --- /dev/null +++ b/src/exceptions.cpp @@ -0,0 +1,324 @@ +#include "exceptions.hpp" + +#include "ast_selectors.hpp" +#include "ast_values.hpp" +#include "extension.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + StackTraces convertTraces(BackTraces traces) + { + // This will trigger StackTrace constructor + // Copies necessary stuff from BackTrace + return { traces.begin(), traces.end() }; + } + + StringVector getKeyVector( + const ValueFlatMap& names) + { + StringVector keys; + for (auto it : names) { + keys.push_back(it.first.orig()); + } + return keys; + } + + StringVector getKeyVector( + const ExpressionFlatMap& names) + { + StringVector keys; + for (auto it : names) { + keys.push_back(it.first.orig()); + } + return keys; + } + + sass::string pluralize( + const sass::string& singular, + size_t size, const sass::string& plural) + { + if (size == 1) return singular; + else if (!plural.empty()) return plural; + else return singular + "s"; + } + + sass::string toSentence( + const StringVector& names, + const sass::string& conjunction, + const uint8_t prefix) + { + sass::string buffer; + size_t L = names.size(), i = 0; + auto it = names.begin(); + while (i < L) { + // add conjugation + if (i > 0) { + if (i < L - 1) { + buffer += ", "; + } + else { + buffer += " "; + buffer += conjunction; + buffer += " "; + } + } + if (prefix) buffer += prefix; + buffer += *it; + it++; i++; + } + return buffer; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Exception { + + Base::Base(sass::string msg, BackTraces traces) + : std::runtime_error(msg.c_str()), msg(msg), + traces(traces.begin(), traces.end()) + { } + + Base::Base(sass::string msg, BackTraces traces, SourceSpan pstate) + : Base(msg, traces) + { + Base::traces.push_back(pstate); + } + + ParserException::ParserException(BackTraces traces, sass::string msg) + : Base(msg, traces) + { } + + RuntimeException::RuntimeException( + BackTraces traces, sass::string msg) + : Base(msg, traces) + {} + + RuntimeException::RuntimeException( + sass::string msg, BackTraces traces, SourceSpan pstate) + : Base(msg, traces, pstate) {} + + InvalidParent::InvalidParent(Selector* parent, BackTraces traces, Selector* selector) + : Base(def_msg, traces, selector->pstate()), parent(parent), selector(selector) + { + msg = "Parent " + "\"" + parent->inspect() + "\"" + " is incompatible with this selector."; + } + + + CustomImportError::CustomImportError(BackTraces traces, sass::string msg) + : Base(msg, traces) + { } + + CustomImportNotFound::CustomImportNotFound(BackTraces traces, sass::string file) + : RuntimeException(traces, def_msg) + { + msg = "Can't find stylesheet \"" + file + "\"."; + msg += "\nAs requested by custom importer."; + } + + CustomImportAmbigous::CustomImportAmbigous(BackTraces traces, sass::string file) + : RuntimeException(traces, def_msg) + { + msg = "CustomImportAmbigous \"" + file + "\"."; + msg += "\nAs requested by custom importer."; + } + + CustomImportLoadError::CustomImportLoadError(BackTraces traces, sass::string file) + : RuntimeException(traces, def_msg) + { + msg = "CustomImportLoadError \"" + file + "\"."; + msg += "\nAs requested by custom importer."; + } + + + RecursionLimitError::RecursionLimitError() + : Base(msg_recursion_limit, {}) {} + + DuplicateKeyError::DuplicateKeyError(BackTraces traces, const Map& dup, const Value& org) + : Base(def_msg, traces), dup(dup), org(org) + { + // msg = "Duplicate key " + dup.get_duplicate_key()->inspect() + " in map (" + org.inspect() + ")."; + msg = "Duplicate key."; // dart-sass keeps it simple ... + } + + sass::string formatMixedParamGroups(const sass::string& first, const StringVector& others) + { + // RGB HWB + sass::string msg(first); + msg += " parameters may not be passed along with "; + msg += toSentence(others, Strings::_or_); + msg += " parameters."; + return msg; + } + + sass::string formatUnknownNamedArgument(const StringVector& names) + { + sass::string msg("No "); + msg += pluralize(Strings::argument, names.size()); + msg += " named "; + msg += toSentence(names, Strings::_or_, '$'); + msg += "."; + return msg; + } + + sass::string formatTooManyArguments(size_t given, size_t expected) { + sass::ostream msg; + msg << "Only " << expected << " "; + msg << pluralize("argument", expected); + msg << " allowed, but " << given << " "; + msg << pluralize("was", given, "were"); + msg << " passed."; + return msg.str(); + } + + sass::string formatTooManyArguments(const ExpressionFlatMap& given, const Sass::EnvKeySet& expected) { + StringVector superfluous; + for (auto pair : given) { + if (expected.count(pair.first) == 0) { + superfluous.emplace_back(pair.first.orig()); + } + } + return "No argument named " + + toSentence(superfluous, "or", '$') + "."; + } + + sass::string formatTooManyArguments(const ValueFlatMap& given, const Sass::EnvKeySet& expected) { + StringVector superfluous; + for (auto pair : given) { + if (expected.count(pair.first) == 0) { + superfluous.emplace_back(pair.first.orig()); + } + } + return "No argument named " + + toSentence(superfluous, "or", '$') + "."; + } + + sass::string formatTooManyArguments(const ValueFlatMap& superfluous) { + return "No argument named " + + toSentence(getKeyVector(superfluous), "or", '$') + "."; + } + + TooManyArguments::TooManyArguments(BackTraces traces, size_t given, size_t expected) + : RuntimeException(traces, formatTooManyArguments(given, expected)) + {} + + TooManyArguments::TooManyArguments(BackTraces traces, const ExpressionFlatMap& given, const Sass::EnvKeySet& expected) + : RuntimeException(traces, formatTooManyArguments(given, expected)) + {} + + TooManyArguments::TooManyArguments(BackTraces traces, const ValueFlatMap& superflous) + : RuntimeException(traces, formatTooManyArguments(superflous)) + {} + + MissingArgument::MissingArgument(BackTraces traces, const EnvKey& name) + : RuntimeException(traces, "Missing argument $" + name.norm() + ".") + {} + + ArgumentGivenTwice::ArgumentGivenTwice(BackTraces traces, const EnvKey& name) + : RuntimeException(traces, "Argument $" + name.norm() + " name was passed both by position and by name.") + {} + + UnknownNamedArgument::UnknownNamedArgument(BackTraces traces, ValueFlatMap names) + : RuntimeException(traces, formatUnknownNamedArgument(getKeyVector(names))) + { + } + + MixedParamGroups::MixedParamGroups(BackTraces traces, const sass::string& first, const StringVector others) + : RuntimeException(traces, formatMixedParamGroups(first, others)) + {} + + InvalidCssValue::InvalidCssValue(BackTraces traces, const Value& val) + : Base(val.inspect() + " isn't a valid CSS value.", traces, val.pstate()) + {} + + // Thrown when a parent selector is used without any parent + TopLevelParent::TopLevelParent(BackTraces traces, SourceSpan pstate) + : Base("Top-level selectors may not contain the parent selector \"&\".", traces, pstate) + {} + + // Thrown when a non-optional extend found nothing to extend + UnsatisfiedExtend::UnsatisfiedExtend(BackTraces traces, Extension extension) + : Base("The target selector was not found.\n" + // Calling inspect to the placeholder is visible + "Use \"@extend " + extension.target->inspect() + + " !optional\" to avoid this error.", + traces, extension.target->pstate()) + {} + + // Thrown when we extend across incompatible media contexts + ExtendAcrossMedia::ExtendAcrossMedia(BackTraces traces, Extension extension) + : Base("You may not @extend selectors across media queries.", traces) + {} + + // Thrown when we find an unexpected UTF8 sequence + InvalidUnicode::InvalidUnicode(SourceSpan pstate, BackTraces traces) + : Base("Invalid UTF-8.", traces, pstate) + {} + + SassScriptException::SassScriptException(sass::string msg, + BackTraces traces, SourceSpan pstate, sass::string name) : + Base(name.empty() ? msg : "$" + name + ": " + msg, traces) + {} + + + + + + + + + + + + + + + + + + + + + + + + ///////////////////////////////////////////////////////////////////////// + // Various value operation errors + ///////////////////////////////////////////////////////////////////////// + + ZeroDivisionError::ZeroDivisionError(const Value& lhs, const Value& rhs) + : OperationError("divided by 0") + {} + + IncompatibleUnits::IncompatibleUnits(const Units& lhs, const Units& rhs) + : OperationError("Incompatible units " + + rhs.unit() + " and " + + lhs.unit() + ".") + {} + + + + + + + + + + + + + + + + + + + +} + +} diff --git a/src/exceptions.hpp b/src/exceptions.hpp new file mode 100644 index 0000000000..14db68d21b --- /dev/null +++ b/src/exceptions.hpp @@ -0,0 +1,202 @@ +#ifndef SASS_ERROR_EXCEPTIONS_HPP +#define SASS_ERROR_EXCEPTIONS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include +#include +#include +#include "logger.hpp" +#include "units.hpp" +#include "backtrace.hpp" +#include "ast_fwd_decl.hpp" +#include "environment_cnt.hpp" +#include "sass/functions.h" + +namespace Sass { + + class BackTrace; + + StringVector getKeyVector(const ValueFlatMap& names); + + sass::string pluralize(const sass::string& singular, size_t size, const sass::string& plural = ""); + sass::string toSentence(const StringVector& names, const sass::string& conjunction = "and", const uint8_t prefix = 0); + + namespace Exception { + + const sass::string def_msg("Invalid sass detected"); + const sass::string def_op_null_msg("Invalid null operation"); + const sass::string def_nesting_limit("Code too deeply nested"); + + const sass::string msg_recursion_limit = + "Too deep recursion detected. This can be caused by too deep level nesting.\n" + "LibSass will abort here in order to avoid a possible stack overflow.\n"; + + class Base : public std::runtime_error { + protected: + sass::string msg; + public: + StackTraces traces; + public: + Base(sass::string msg, BackTraces traces); + Base(sass::string msg, BackTraces traces, SourceSpan pstate); + virtual const char* what() const throw() { return msg.c_str(); } + // virtual ~Base() noexcept {}; + }; + + class ParserException : public Base { + public: + ParserException(BackTraces traces, sass::string msg); + }; + + class RuntimeException : public Base { + public: + RuntimeException(BackTraces traces, sass::string msg); + // RuntimeException(sass::string msg, BackTraces traces); + RuntimeException(sass::string msg, BackTraces traces, SourceSpan pstate); + virtual const char* what() const throw() { return msg.c_str(); } + }; + + class InvalidParent : public Base { + protected: + Selector* parent; + Selector* selector; + public: + InvalidParent(Selector* parent, BackTraces traces, Selector* selector); + }; + + class InvalidUnicode : public Base { + public: + InvalidUnicode(SourceSpan pstate, BackTraces traces); + }; + + class CustomImportError : public Base { + public: + CustomImportError(BackTraces traces, sass::string msg); + }; + + class SassScriptException : public Base { + public: + SassScriptException(sass::string msg, + BackTraces traces, SourceSpan pstate, + sass::string name = ""); + }; + + class CustomImportNotFound : public RuntimeException { + public: + CustomImportNotFound(BackTraces traces, sass::string file); + }; + + class CustomImportAmbigous : public RuntimeException { + public: + CustomImportAmbigous(BackTraces traces, sass::string file); + }; + + class CustomImportLoadError : public RuntimeException { + public: + CustomImportLoadError(BackTraces traces, sass::string file); + }; + + class RecursionLimitError : public Base { + public: + RecursionLimitError(); + }; + + class DuplicateKeyError : public Base { + protected: + const Map& dup; + const Value& org; + public: + DuplicateKeyError(BackTraces traces, + const Map& dup, const Value& org); + }; + + + + class TooManyArguments : public RuntimeException { + public: + TooManyArguments(BackTraces traces, size_t given, size_t expected); + TooManyArguments(BackTraces traces, const ExpressionFlatMap& given, const Sass::EnvKeySet& expected); + TooManyArguments(BackTraces traces, const ValueFlatMap& superfluous); + }; + + class MissingArgument : public RuntimeException { + public: + MissingArgument(BackTraces traces, const EnvKey& name); + }; + + class ArgumentGivenTwice : public RuntimeException { + public: + ArgumentGivenTwice(BackTraces traces, const EnvKey& name); + }; + + class UnknownNamedArgument : public RuntimeException { + public: + UnknownNamedArgument(BackTraces traces, ValueFlatMap names); + }; + + class MixedParamGroups : public RuntimeException { + public: + MixedParamGroups(BackTraces traces, const sass::string& first, const StringVector seconds); + }; + + + class InvalidCssValue : public Base { + public: + InvalidCssValue(BackTraces traces, const Value& val); + }; + + + /* common virtual base class (has no pstate or trace) */ + class OperationError : public std::runtime_error { + protected: + sass::string msg; + public: + OperationError(sass::string msg = sass::string("Undefined operation")) + : std::runtime_error(msg.c_str()), msg(msg) + {}; + public: + virtual const char* what() const throw() { return msg.c_str(); } + }; + + class ZeroDivisionError : public OperationError { + public: + ZeroDivisionError(const Value& lhs, const Value& rhs); + }; + + class IncompatibleUnits : public OperationError { + protected: + // const Sass::UnitType lhs; + // const Sass::UnitType rhs; + public: + IncompatibleUnits(const Units& lhs, const Units& rhs); + // virtual ~IncompatibleUnits() noexcept {}; + }; + + + + class TopLevelParent : public Base { + public: + TopLevelParent(BackTraces traces, SourceSpan pstate); + // virtual ~TopLevelParent() noexcept {}; + }; + + class UnsatisfiedExtend : public Base { + public: + UnsatisfiedExtend(BackTraces traces, Extension extension); + // virtual ~UnsatisfiedExtend() noexcept {}; + }; + + class ExtendAcrossMedia : public Base { + public: + ExtendAcrossMedia(BackTraces traces, Extension extension); + // virtual ~ExtendAcrossMedia() noexcept {}; + }; + + } + +} + +#endif diff --git a/src/expand.cpp b/src/expand.cpp deleted file mode 100644 index 89ed4fe132..0000000000 --- a/src/expand.cpp +++ /dev/null @@ -1,875 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include - -#include "ast.hpp" -#include "expand.hpp" -#include "bind.hpp" -#include "eval.hpp" -#include "backtrace.hpp" -#include "context.hpp" -#include "parser.hpp" -#include "sass_functions.hpp" -#include "error_handling.hpp" - -namespace Sass { - - // simple endless recursion protection - const size_t maxRecursion = 500; - - Expand::Expand(Context& ctx, Env* env, SelectorStack* stack, SelectorStack* originals) - : ctx(ctx), - traces(ctx.traces), - eval(Eval(*this)), - recursions(0), - in_keyframes(false), - at_root_without_rule(false), - old_at_root_without_rule(false), - env_stack(), - block_stack(), - call_stack(), - selector_stack(), - originalStack(), - mediaStack() - { - env_stack.push_back(nullptr); - env_stack.push_back(env); - block_stack.push_back(nullptr); - call_stack.push_back({}); - if (stack == NULL) { pushToSelectorStack({}); } - else { - for (auto item : *stack) { - if (item.isNull()) pushToSelectorStack({}); - else pushToSelectorStack(item); - } - } - if (originals == NULL) { pushToOriginalStack({}); } - else { - for (auto item : *stack) { - if (item.isNull()) pushToOriginalStack({}); - else pushToOriginalStack(item); - } - } - mediaStack.push_back({}); - } - - Env* Expand::environment() - { - if (env_stack.size() > 0) - return env_stack.back(); - return 0; - } - - void Expand::pushNullSelector() - { - pushToSelectorStack({}); - pushToOriginalStack({}); - } - - void Expand::popNullSelector() - { - popFromOriginalStack(); - popFromSelectorStack(); - } - - SelectorStack Expand::getOriginalStack() - { - return originalStack; - } - - SelectorStack Expand::getSelectorStack() - { - return selector_stack; - } - - SelectorListObj& Expand::selector() - { - if (selector_stack.size() > 0) { - auto& sel = selector_stack.back(); - if (sel.isNull()) return sel; - return sel; - } - // Avoid the need to return copies - // We always want an empty first item - selector_stack.push_back({}); - return selector_stack.back();; - } - - SelectorListObj& Expand::original() - { - if (originalStack.size() > 0) { - auto& sel = originalStack.back(); - if (sel.isNull()) return sel; - return sel; - } - // Avoid the need to return copies - // We always want an empty first item - originalStack.push_back({}); - return originalStack.back(); - } - - SelectorListObj Expand::popFromSelectorStack() - { - SelectorListObj last = selector_stack.back(); - if (selector_stack.size() > 0) - selector_stack.pop_back(); - if (last.isNull()) return {}; - return last; - } - - void Expand::pushToSelectorStack(SelectorListObj selector) - { - selector_stack.push_back(selector); - } - - SelectorListObj Expand::popFromOriginalStack() - { - SelectorListObj last = originalStack.back(); - if (originalStack.size() > 0) - originalStack.pop_back(); - if (last.isNull()) return {}; - return last; - } - - void Expand::pushToOriginalStack(SelectorListObj selector) - { - originalStack.push_back(selector); - } - - // blocks create new variable scopes - Block* Expand::operator()(Block* b) - { - // create new local environment - // set the current env as parent - Env env(environment()); - // copy the block object (add items later) - Block_Obj bb = SASS_MEMORY_NEW(Block, - b->pstate(), - b->length(), - b->is_root()); - // setup block and env stack - this->block_stack.push_back(bb); - this->env_stack.push_back(&env); - // operate on block - // this may throw up! - this->append_block(b); - // revert block and env stack - this->block_stack.pop_back(); - this->env_stack.pop_back(); - // return copy - return bb.detach(); - } - - Statement* Expand::operator()(StyleRule* r) - { - LOCAL_FLAG(old_at_root_without_rule, at_root_without_rule); - - if (in_keyframes) { - Block* bb = operator()(r->block()); - Keyframe_Rule_Obj k = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), bb); - if (r->schema()) { - pushNullSelector(); - k->name(eval(r->schema())); - popNullSelector(); - } - else if (r->selector()) { - if (SelectorListObj s = r->selector()) { - pushNullSelector(); - k->name(eval(s)); - popNullSelector(); - } - } - - return k.detach(); - } - - if (r->schema()) { - SelectorListObj sel = eval(r->schema()); - r->selector(sel); - for (auto complex : sel->elements()) { - // ToDo: maybe we can get rid of chroots? - complex->chroots(complex->has_real_parent_ref()); - } - - } - - // reset when leaving scope - LOCAL_FLAG(at_root_without_rule, false); - - SelectorListObj evaled = eval(r->selector()); - // do not connect parent again - Env env(environment()); - if (block_stack.back()->is_root()) { - env_stack.push_back(&env); - } - Block_Obj blk; - pushToSelectorStack(evaled); - // The copy is needed for parent reference evaluation - // dart-sass stores it as `originalSelector` member - pushToOriginalStack(SASS_MEMORY_COPY(evaled)); - ctx.extender.addSelector(evaled, mediaStack.back()); - if (r->block()) blk = operator()(r->block()); - popFromOriginalStack(); - popFromSelectorStack(); - StyleRule* rr = SASS_MEMORY_NEW(StyleRule, - r->pstate(), - evaled, - blk); - - if (block_stack.back()->is_root()) { - env_stack.pop_back(); - } - - rr->is_root(r->is_root()); - rr->tabs(r->tabs()); - - return rr; - } - - Statement* Expand::operator()(SupportsRule* f) - { - ExpressionObj condition = f->condition()->perform(&eval); - SupportsRuleObj ff = SASS_MEMORY_NEW(SupportsRule, - f->pstate(), - Cast(condition), - operator()(f->block())); - return ff.detach(); - } - - sass::vector Expand::mergeMediaQueries( - const sass::vector& lhs, - const sass::vector& rhs) - { - sass::vector queries; - for (CssMediaQuery_Obj query1 : lhs) { - for (CssMediaQuery_Obj query2 : rhs) { - CssMediaQuery_Obj result = query1->merge(query2); - if (result && !result->empty()) { - queries.push_back(result); - } - } - } - return queries; - } - - Statement* Expand::operator()(MediaRule* m) - { - ExpressionObj mq = eval(m->schema()); - sass::string str_mq(mq->to_css(ctx.c_options)); - ItplFile* source = SASS_MEMORY_NEW(ItplFile, - str_mq.c_str(), m->pstate()); - Parser parser(source, ctx, traces); - // Create a new CSS only representation of the media rule - CssMediaRuleObj css = SASS_MEMORY_NEW(CssMediaRule, m->pstate(), m->block()); - sass::vector parsed = parser.parseCssMediaQueries(); - if (mediaStack.size() && mediaStack.back()) { - auto& parent = mediaStack.back()->elements(); - css->concat(mergeMediaQueries(parent, parsed)); - } - else { - css->concat(parsed); - } - mediaStack.push_back(css); - css->block(operator()(m->block())); - mediaStack.pop_back(); - return css.detach(); - - } - - Statement* Expand::operator()(AtRootRule* a) - { - Block_Obj ab = a->block(); - ExpressionObj ae = a->expression(); - - if (ae) ae = ae->perform(&eval); - else ae = SASS_MEMORY_NEW(At_Root_Query, a->pstate()); - - LOCAL_FLAG(at_root_without_rule, Cast(ae)->exclude("rule")); - LOCAL_FLAG(in_keyframes, false); - - ; - - Block_Obj bb = ab ? operator()(ab) : NULL; - AtRootRuleObj aa = SASS_MEMORY_NEW(AtRootRule, - a->pstate(), - bb, - Cast(ae)); - return aa.detach(); - } - - Statement* Expand::operator()(AtRule* a) - { - LOCAL_FLAG(in_keyframes, a->is_keyframes()); - Block* ab = a->block(); - SelectorList* as = a->selector(); - Expression* av = a->value(); - pushNullSelector(); - if (av) av = av->perform(&eval); - if (as) as = eval(as); - popNullSelector(); - Block* bb = ab ? operator()(ab) : NULL; - AtRule* aa = SASS_MEMORY_NEW(AtRule, - a->pstate(), - a->keyword(), - as, - bb, - av); - return aa; - } - - Statement* Expand::operator()(Declaration* d) - { - Block_Obj ab = d->block(); - String_Obj old_p = d->property(); - ExpressionObj prop = old_p->perform(&eval); - String_Obj new_p = Cast(prop); - // we might get a color back - if (!new_p) { - sass::string str(prop->to_string(ctx.c_options)); - new_p = SASS_MEMORY_NEW(String_Constant, old_p->pstate(), str); - } - ExpressionObj value = d->value(); - if (value) value = value->perform(&eval); - Block_Obj bb = ab ? operator()(ab) : NULL; - if (!bb) { - if (!value || (value->is_invisible() && !d->is_important())) { - if (d->is_custom_property()) { - error("Custom property values may not be empty.", d->value()->pstate(), traces); - } else { - return nullptr; - } - } - } - Declaration* decl = SASS_MEMORY_NEW(Declaration, - d->pstate(), - new_p, - value, - d->is_important(), - d->is_custom_property(), - bb); - decl->tabs(d->tabs()); - return decl; - } - - Statement* Expand::operator()(Assignment* a) - { - Env* env = environment(); - const sass::string& var(a->variable()); - if (a->is_global()) { - if (!env->has_global(var)) { - deprecated( - "!global assignments won't be able to declare new variables in future versions.", - "Consider adding `" + var + ": null` at the top level.", - true, a->pstate()); - } - if (a->is_default()) { - if (env->has_global(var)) { - ExpressionObj e = Cast(env->get_global(var)); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(&eval)); - } - } - else { - env->set_global(var, a->value()->perform(&eval)); - } - } - else { - env->set_global(var, a->value()->perform(&eval)); - } - } - else if (a->is_default()) { - if (env->has_lexical(var)) { - auto cur = env; - while (cur && cur->is_lexical()) { - if (cur->has_local(var)) { - if (AST_Node_Obj node = cur->get_local(var)) { - ExpressionObj e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - cur->set_local(var, a->value()->perform(&eval)); - } - } - else { - throw std::runtime_error("Env not in sync"); - } - return 0; - } - cur = cur->parent(); - } - throw std::runtime_error("Env not in sync"); - } - else if (env->has_global(var)) { - if (AST_Node_Obj node = env->get_global(var)) { - ExpressionObj e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(&eval)); - } - } - } - else if (env->is_lexical()) { - env->set_local(var, a->value()->perform(&eval)); - } - else { - env->set_local(var, a->value()->perform(&eval)); - } - } - else { - env->set_lexical(var, a->value()->perform(&eval)); - } - return 0; - } - - Statement* Expand::operator()(Import* imp) - { - Import_Obj result = SASS_MEMORY_NEW(Import, imp->pstate()); - if (imp->import_queries() && imp->import_queries()->size()) { - ExpressionObj ex = imp->import_queries()->perform(&eval); - result->import_queries(Cast(ex)); - } - for ( size_t i = 0, S = imp->urls().size(); i < S; ++i) { - result->urls().push_back(imp->urls()[i]->perform(&eval)); - } - // all resources have been dropped for Input_Stubs - // for ( size_t i = 0, S = imp->incs().size(); i < S; ++i) {} - return result.detach(); - } - - Statement* Expand::operator()(Import_Stub* i) - { - traces.push_back(Backtrace(i->pstate())); - // get parent node from call stack - AST_Node_Obj parent = call_stack.back(); - if (Cast(parent) == NULL) { - error("Import directives may not be used within control directives or mixins.", i->pstate(), traces); - } - // we don't seem to need that actually afterall - Sass_Import_Entry import = sass_make_import( - i->imp_path().c_str(), - i->abs_path().c_str(), - 0, 0 - ); - ctx.import_stack.push_back(import); - - Block_Obj trace_block = SASS_MEMORY_NEW(Block, i->pstate()); - Trace_Obj trace = SASS_MEMORY_NEW(Trace, i->pstate(), i->imp_path(), trace_block, 'i'); - block_stack.back()->append(trace); - block_stack.push_back(trace_block); - - const sass::string& abs_path(i->resource().abs_path); - append_block(ctx.sheets.at(abs_path).root); - sass_delete_import(ctx.import_stack.back()); - ctx.import_stack.pop_back(); - block_stack.pop_back(); - traces.pop_back(); - return 0; - } - - Statement* Expand::operator()(WarningRule* w) - { - // eval handles this too, because warnings may occur in functions - w->perform(&eval); - return 0; - } - - Statement* Expand::operator()(ErrorRule* e) - { - // eval handles this too, because errors may occur in functions - e->perform(&eval); - return 0; - } - - Statement* Expand::operator()(DebugRule* d) - { - // eval handles this too, because warnings may occur in functions - d->perform(&eval); - return 0; - } - - Statement* Expand::operator()(Comment* c) - { - if (ctx.output_style() == COMPRESSED) { - // comments should not be evaluated in compact - // https://github.com/sass/libsass/issues/2359 - if (!c->is_important()) return NULL; - } - eval.is_in_comment = true; - Comment* rv = SASS_MEMORY_NEW(Comment, c->pstate(), Cast(c->text()->perform(&eval)), c->is_important()); - eval.is_in_comment = false; - // TODO: eval the text, once we're parsing/storing it as a String_Schema - return rv; - } - - Statement* Expand::operator()(If* i) - { - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(i); - ExpressionObj rv = i->predicate()->perform(&eval); - if (*rv) { - append_block(i->block()); - } - else { - Block* alt = i->alternative(); - if (alt) append_block(alt); - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - // For does not create a new env scope - // But iteration vars are reset afterwards - Statement* Expand::operator()(ForRule* f) - { - sass::string variable(f->variable()); - ExpressionObj low = f->lower_bound()->perform(&eval); - if (low->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(low->pstate())); - throw Exception::TypeMismatch(traces, *low, "integer"); - } - ExpressionObj high = f->upper_bound()->perform(&eval); - if (high->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(high->pstate())); - throw Exception::TypeMismatch(traces, *high, "integer"); - } - Number_Obj sass_start = Cast(low); - Number_Obj sass_end = Cast(high); - // check if units are valid for sequence - if (sass_start->unit() != sass_end->unit()) { - sass::ostream msg; msg << "Incompatible units: '" - << sass_start->unit() << "' and '" - << sass_end->unit() << "'."; - error(msg.str(), low->pstate(), traces); - } - double start = sass_start->value(); - double end = sass_end->value(); - // only create iterator once in this environment - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(f); - Block* body = f->block(); - if (start < end) { - if (f->is_inclusive()) ++end; - for (double i = start; - i < end; - ++i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - append_block(body); - } - } else { - if (f->is_inclusive()) --end; - for (double i = start; - i > end; - --i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - append_block(body); - } - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - // Eval does not create a new env scope - // But iteration vars are reset afterwards - Statement* Expand::operator()(EachRule* e) - { - sass::vector variables(e->variables()); - ExpressionObj expr = e->list()->perform(&eval); - List_Obj list; - Map_Obj map; - if (expr->concrete_type() == Expression::MAP) { - map = Cast(expr); - } - else if (SelectorList * ls = Cast(expr)) { - ExpressionObj rv = Listize::perform(ls); - list = Cast(rv); - } - else if (expr->concrete_type() != Expression::LIST) { - list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); - list->append(expr); - } - else { - list = Cast(expr); - } - // remember variables and then reset them - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(e); - Block* body = e->block(); - - if (map) { - for (auto key : map->keys()) { - ExpressionObj k = key->perform(&eval); - ExpressionObj v = map->at(key)->perform(&eval); - - if (variables.size() == 1) { - List_Obj variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); - variable->append(k); - variable->append(v); - env.set_local(variables[0], variable); - } else { - env.set_local(variables[0], k); - env.set_local(variables[1], v); - } - append_block(body); - } - } - else { - // bool arglist = list->is_arglist(); - if (list->length() == 1 && Cast(list)) { - list = Cast(list); - } - for (size_t i = 0, L = list->length(); i < L; ++i) { - ExpressionObj item = list->at(i); - // unwrap value if the expression is an argument - if (Argument_Obj arg = Cast(item)) item = arg->value(); - // check if we got passed a list of args (investigate) - if (List_Obj scalars = Cast(item)) { - if (variables.size() == 1) { - List_Obj var = scalars; - // if (arglist) var = (*scalars)[0]; - env.set_local(variables[0], var); - } else { - for (size_t j = 0, K = variables.size(); j < K; ++j) { - env.set_local(variables[j], j >= scalars->length() - ? SASS_MEMORY_NEW(Null, expr->pstate()) - : (*scalars)[j]->perform(&eval)); - } - } - } else { - if (variables.size() > 0) { - env.set_local(variables.at(0), item); - for (size_t j = 1, K = variables.size(); j < K; ++j) { - ExpressionObj res = SASS_MEMORY_NEW(Null, expr->pstate()); - env.set_local(variables[j], res); - } - } - } - append_block(body); - } - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - Statement* Expand::operator()(WhileRule* w) - { - ExpressionObj pred = w->predicate(); - Block* body = w->block(); - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(w); - ExpressionObj cond = pred->perform(&eval); - while (!cond->is_false()) { - append_block(body); - cond = pred->perform(&eval); - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - Statement* Expand::operator()(Return* r) - { - error("@return may only be used within a function", r->pstate(), traces); - return 0; - } - - Statement* Expand::operator()(ExtendRule* e) - { - - // evaluate schema first - if (e->schema()) { - e->selector(eval(e->schema())); - e->isOptional(e->selector()->is_optional()); - } - // evaluate the selector - e->selector(eval(e->selector())); - - if (e->selector()) { - - for (auto complex : e->selector()->elements()) { - - if (complex->length() != 1) { - error("complex selectors may not be extended.", complex->pstate(), traces); - } - - if (const CompoundSelector* compound = complex->first()->getCompound()) { - - if (compound->length() != 1) { - - sass::ostream sels; bool addComma = false; - sels << "Compound selectors may no longer be extended.\n"; - sels << "Consider `@extend "; - for (auto sel : compound->elements()) { - if (addComma) sels << ", "; - sels << sel->to_sass(); - addComma = true; - } - sels << "` instead.\n"; - sels << "See http://bit.ly/ExtendCompound for details."; - - warning(sels.str(), compound->pstate()); - - // Make this an error once deprecation is over - for (SimpleSelectorObj simple : compound->elements()) { - // Pass every selector we ever see to extender (to make them findable for extend) - ctx.extender.addExtension(selector(), simple, mediaStack.back(), e->isOptional()); - } - - } - else { - // Pass every selector we ever see to extender (to make them findable for extend) - ctx.extender.addExtension(selector(), compound->first(), mediaStack.back(), e->isOptional()); - } - - } - else { - error("complex selectors may not be extended.", complex->pstate(), traces); - } - } - } - - return nullptr; - - } - - Statement* Expand::operator()(Definition* d) - { - Env* env = environment(); - Definition_Obj dd = SASS_MEMORY_COPY(d); - env->local_frame()[d->name() + - (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = dd; - - if (d->type() == Definition::FUNCTION && ( - Prelexer::calc_fn_call(d->name().c_str()) || - d->name() == "element" || - d->name() == "expression" || - d->name() == "url" - )) { - deprecated( - "Naming a function \"" + d->name() + "\" is disallowed and will be an error in future versions of Sass.", - "This name conflicts with an existing CSS function with special parse rules.", - false, d->pstate() - ); - } - - // set the static link so we can have lexical scoping - dd->environment(env); - return 0; - } - - Statement* Expand::operator()(Mixin_Call* c) - { - - if (recursions > maxRecursion) { - throw Exception::StackError(traces, *c); - } - - recursions ++; - - Env* env = environment(); - sass::string full_name(c->name() + "[m]"); - if (!env->has(full_name)) { - error("no mixin named " + c->name(), c->pstate(), traces); - } - Definition_Obj def = Cast((*env)[full_name]); - Block_Obj body = def->block(); - Parameters_Obj params = def->parameters(); - - if (c->block() && c->name() != "@content" && !body->has_content()) { - error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), traces); - } - ExpressionObj rv = c->arguments()->perform(&eval); - Arguments_Obj args = Cast(rv); - sass::string msg(", in mixin `" + c->name() + "`"); - traces.push_back(Backtrace(c->pstate(), msg)); - ctx.callee_stack.push_back({ - c->name().c_str(), - c->pstate().getPath(), - c->pstate().getLine(), - c->pstate().getColumn(), - SASS_CALLEE_MIXIN, - { env } - }); - - Env new_env(def->environment()); - env_stack.push_back(&new_env); - if (c->block()) { - Parameters_Obj params = c->block_parameters(); - if (!params) params = SASS_MEMORY_NEW(Parameters, c->pstate()); - // represent mixin content blocks as thunks/closures - Definition_Obj thunk = SASS_MEMORY_NEW(Definition, - c->pstate(), - "@content", - params, - c->block(), - Definition::MIXIN); - thunk->environment(env); - new_env.local_frame()["@content[m]"] = thunk; - } - - bind(sass::string("Mixin"), c->name(), params, args, &new_env, &eval, traces); - - Block_Obj trace_block = SASS_MEMORY_NEW(Block, c->pstate()); - Trace_Obj trace = SASS_MEMORY_NEW(Trace, c->pstate(), c->name(), trace_block); - - env->set_global("is_in_mixin", bool_true); - if (Block* pr = block_stack.back()) { - trace_block->is_root(pr->is_root()); - } - block_stack.push_back(trace_block); - for (auto bb : body->elements()) { - if (StyleRule* r = Cast(bb)) { - r->is_root(trace_block->is_root()); - } - Statement_Obj ith = bb->perform(this); - if (ith) trace->block()->append(ith); - } - block_stack.pop_back(); - env->del_global("is_in_mixin"); - - ctx.callee_stack.pop_back(); - env_stack.pop_back(); - traces.pop_back(); - - recursions --; - return trace.detach(); - } - - Statement* Expand::operator()(Content* c) - { - Env* env = environment(); - // convert @content directives into mixin calls to the underlying thunk - if (!env->has("@content[m]")) return 0; - Arguments_Obj args = c->arguments(); - if (!args) args = SASS_MEMORY_NEW(Arguments, c->pstate()); - - Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, - c->pstate(), - "@content", - args); - - Trace_Obj trace = Cast(call->perform(this)); - return trace.detach(); - } - - // process and add to last block on stack - inline void Expand::append_block(Block* b) - { - if (b->is_root()) call_stack.push_back(b); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = b->at(i); - Statement_Obj ith = stm->perform(this); - if (ith) block_stack.back()->append(ith); - } - if (b->is_root()) call_stack.pop_back(); - } - -} diff --git a/src/expand.hpp b/src/expand.hpp deleted file mode 100644 index b1ab7796d9..0000000000 --- a/src/expand.hpp +++ /dev/null @@ -1,98 +0,0 @@ -#ifndef SASS_EXPAND_H -#define SASS_EXPAND_H - -#include - -#include "ast.hpp" -#include "eval.hpp" -#include "operation.hpp" -#include "environment.hpp" - -namespace Sass { - - class Listize; - class Context; - class Eval; - struct Backtrace; - - class Expand : public Operation_CRTP { - public: - - Env* environment(); - SelectorListObj& selector(); - SelectorListObj& original(); - SelectorListObj popFromSelectorStack(); - SelectorStack getOriginalStack(); - SelectorStack getSelectorStack(); - void pushNullSelector(); - void popNullSelector(); - void pushToSelectorStack(SelectorListObj selector); - - SelectorListObj popFromOriginalStack(); - - void pushToOriginalStack(SelectorListObj selector); - - Context& ctx; - Backtraces& traces; - Eval eval; - size_t recursions; - bool in_keyframes; - bool at_root_without_rule; - bool old_at_root_without_rule; - - // it's easier to work with vectors - EnvStack env_stack; - BlockStack block_stack; - CallStack call_stack; - private: - SelectorStack selector_stack; - public: - SelectorStack originalStack; - MediaStack mediaStack; - - Boolean_Obj bool_true; - - private: - - sass::vector mergeMediaQueries(const sass::vector& lhs, const sass::vector& rhs); - - public: - Expand(Context&, Env*, SelectorStack* stack = nullptr, SelectorStack* original = nullptr); - ~Expand() { } - - Block* operator()(Block*); - Statement* operator()(StyleRule*); - - Statement* operator()(MediaRule*); - - // Css StyleRule is already static - // Statement* operator()(CssMediaRule*); - - Statement* operator()(SupportsRule*); - Statement* operator()(AtRootRule*); - Statement* operator()(AtRule*); - Statement* operator()(Declaration*); - Statement* operator()(Assignment*); - Statement* operator()(Import*); - Statement* operator()(Import_Stub*); - Statement* operator()(WarningRule*); - Statement* operator()(ErrorRule*); - Statement* operator()(DebugRule*); - Statement* operator()(Comment*); - Statement* operator()(If*); - Statement* operator()(ForRule*); - Statement* operator()(EachRule*); - Statement* operator()(WhileRule*); - Statement* operator()(Return*); - Statement* operator()(ExtendRule*); - Statement* operator()(Definition*); - Statement* operator()(Mixin_Call*); - Statement* operator()(Content*); - - void append_block(Block*); - - }; - -} - -#endif diff --git a/src/extender.cpp b/src/extender.cpp index 18814185fd..25cbe84d76 100644 --- a/src/extender.cpp +++ b/src/extender.cpp @@ -1,34 +1,17 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" - #include "extender.hpp" + #include "permutate.hpp" +#include "callstack.hpp" #include "dart_helpers.hpp" +#include "ast_selectors.hpp" namespace Sass { - // ########################################################################## - // Constructor without default [mode]. - // [traces] are needed to throw errors. - // ########################################################################## - Extender::Extender(Backtraces& traces) : - mode(NORMAL), - traces(traces), - selectors(), - extensions(), - extensionsByExtender(), - mediaContexts(), - sourceSpecificity(), - originals() - {} - - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Constructor with specific [mode]. // [traces] are needed to throw errors. - // ########################################################################## - Extender::Extender(ExtendMode mode, Backtraces& traces) : + ///////////////////////////////////////////////////////////////////////// + Extender::Extender(ExtendMode mode, BackTraces& traces) : mode(mode), traces(traces), selectors(), @@ -39,50 +22,50 @@ namespace Sass { originals() {} - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [selector] with [source] extender and [targets] extendees. // This works as though `source {@extend target}` were written in the // stylesheet, with the exception that [target] can contain compound // selectors which must be extended as a unit. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// SelectorListObj Extender::extend( SelectorListObj& selector, const SelectorListObj& source, const SelectorListObj& targets, - Backtraces& traces) + BackTraces& traces) { return extendOrReplace(selector, source, targets, ExtendMode::TARGETS, traces); } // EO Extender::extend - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns a copy of [selector] with [targets] replaced by [source]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// SelectorListObj Extender::replace( SelectorListObj& selector, const SelectorListObj& source, const SelectorListObj& targets, - Backtraces& traces) + BackTraces& traces) { return extendOrReplace(selector, source, targets, ExtendMode::REPLACE, traces); } // EO Extender::replace - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // A helper function for [extend] and [replace]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// SelectorListObj Extender::extendOrReplace( SelectorListObj& selector, const SelectorListObj& source, const SelectorListObj& targets, const ExtendMode mode, - Backtraces& traces) + BackTraces& traces) { ExtSelExtMapEntry extenders; for (auto complex : source->elements()) { // Extension.oneOff(complex as ComplexSelector) - extenders.insert(complex, Extension(complex)); + extenders[complex] = Extension(complex); } for (auto complex : targets->elements()) { @@ -92,7 +75,7 @@ namespace Sass { // error("complex selectors may not be extended.", complex->pstate(), traces); // } - if (const CompoundSelector* compound = complex->first()->getCompound()) { + if (const CompoundSelector* compound = complex->first()->isaCompoundSelector()) { ExtSelExtMap extensions; @@ -102,11 +85,11 @@ namespace Sass { Extender extender(mode, traces); - if (!selector->is_invisible()) { - for (auto sel : selector->elements()) { - extender.originals.insert(sel); - } + // if (!selector->hasInvisible()) { + for (auto sel : selector->elements()) { + extender.originals.insert(sel); } + // } selector = extender.extendList(selector, extensions, {}); @@ -119,11 +102,11 @@ namespace Sass { } // EO extendOrReplace - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // The set of all simple selectors in style rules handled // by this extender. This includes simple selectors that // were added because of downstream extensions. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// ExtSmplSelSet Extender::getSimpleSelectors() const { ExtSmplSelSet set; @@ -134,22 +117,21 @@ namespace Sass { } // EO getSimpleSelectors - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Check for extends that have not been satisfied. // Returns true if any non-optional extension did not // extend any selector. Updates the passed reference // to point to that Extension for further analysis. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool Extender::checkForUnsatisfiedExtends(Extension& unsatisfied) const { - if (selectors.empty()) return false; + if (selectors.empty()) return false; // Remove? ExtSmplSelSet originals = getSimpleSelectors(); for (auto target : extensions) { SimpleSelector* key = target.first; ExtSelExtMapEntry& val = target.second; - if (val.empty()) continue; if (originals.find(key) == originals.end()) { - const Extension& extension = val.front().second; + const Extension& extension = val.begin()->second; if (extension.isOptional) continue; unsatisfied = extension; return true; @@ -159,7 +141,7 @@ namespace Sass { } // EO checkUnsatisfiedExtends - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Adds [selector] to this extender, with [selectorSpan] as the span covering // the selector and [ruleSpan] as the span covering the entire style rule. // Extends [selector] using any registered extensions, then returns an empty @@ -167,7 +149,7 @@ namespace Sass { // extensions are added, the returned rule is automatically updated. // The [mediaContext] is the media query context in which the selector was // defined, or `null` if it was defined at the top level of the document. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// void Extender::addSelector( const SelectorListObj& selector, const CssMediaRuleObj& mediaContext) @@ -177,22 +159,22 @@ namespace Sass { // Note: probably why we have originalStack // SelectorListObj original = selector; - if (!selector->isInvisible()) { - for (auto complex : selector->elements()) { - originals.insert(complex); - } + // if (!selector->hasInvisible()) { + for (auto complex : selector->elements()) { + originals.insert(complex); } + // } if (!extensions.empty()) { SelectorListObj res = extendList(selector, extensions, mediaContext); - selector->elements(res->elements()); + selector->elementsM(res->elements()); } if (!mediaContext.isNull()) { - mediaContexts.insert(selector, mediaContext); + mediaContexts[selector] = mediaContext; } registerSelector(selector, selector); @@ -200,10 +182,10 @@ namespace Sass { } // EO addSelector - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Registers the [SimpleSelector]s in [list] // to point to [rule] in [selectors]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// void Extender::registerSelector( const SelectorListObj& list, const SelectorListObj& rule) @@ -211,10 +193,11 @@ namespace Sass { if (list.isNull() || list->empty()) return; for (auto complex : list->elements()) { for (auto component : complex->elements()) { - if (auto compound = component->getCompound()) { - for (SimpleSelector* simple : compound->elements()) { + if (auto compound = component->isaCompoundSelector()) { + for (const SimpleSelectorObj& simple : compound->elements()) { + // Creating this structure can take up to 5% selectors[simple].insert(rule); - if (auto pseudo = simple->getPseudoSelector()) { + if (auto pseudo = simple->isaPseudoSelector()) { if (pseudo->selector()) { auto sel = pseudo->selector(); registerSelector(sel, rule); @@ -227,12 +210,12 @@ namespace Sass { } // EO registerSelector - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns an extension that combines [left] and [right]. Throws // a [SassException] if [left] and [right] have incompatible // media contexts. Throws an [ArgumentError] if [left] // and [right] don't have the same extender and target. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// Extension Extender::mergeExtension( const Extension& lhs, const Extension& rhs) @@ -250,11 +233,11 @@ namespace Sass { } // EO mergeExtension - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function to copy extension between maps - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Seems only relevant for sass 4.0 modules - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// /* void mapCopyExts( ExtSelExtMap& dest, const ExtSelExtMap& source) @@ -278,19 +261,19 @@ namespace Sass { } */ // EO mapCopyExts - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Adds an extension to this extender. The [extender] is the selector for the // style rule in which the extension is defined, and [target] is the selector // passed to `@extend`. The [extend] provides the extend span and indicates // whether the extension is optional. The [mediaContext] defines the media query // context in which the extension is defined. It can only extend selectors // within the same context. A `null` context indicates no media queries. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // ToDo: rename extender to parent, since it is not involved in extending stuff // ToDo: check why dart sass passes the ExtendRule around (is this the main selector?) - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Note: this function could need some logic cleanup - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// void Extender::addExtension( const SelectorListObj& extender, const SimpleSelectorObj& target, @@ -316,7 +299,7 @@ namespace Sass { state.isOptional = is_optional; state.mediaContext = mediaQueryContext; - if (sources.hasKey(complex)) { + if (sources.count(complex) == 1) { // If there's already an extend from [extender] to [target], // we don't need to re-run the extension. We may need to // mark the extension as mandatory, though. @@ -325,12 +308,12 @@ namespace Sass { continue; } - sources.insert(complex, state); + sources[complex] = state; for (auto& component : complex->elements()) { - if (auto compound = component->getCompound()) { + if (auto compound = component->isaCompoundSelector()) { for (auto& simple : compound->elements()) { - extensionsByExtender[simple].push_back(state); + extensionsByExtender[simple].emplace_back(state); if (sourceSpecificity.find(simple) == sourceSpecificity.end()) { // Only source specificity for the original selector is relevant. // Selectors generated by `@extend` don't get new specificity. @@ -341,7 +324,7 @@ namespace Sass { } if (hasRule || hasExistingExtensions) { - newExtensions.insert(complex, state); + newExtensions[complex] = state; } } @@ -374,11 +357,11 @@ namespace Sass { } // EO addExtension - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extend [extensions] using [newExtensions]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Note: dart-sass throws an error in here - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// void Extender::extendExistingStyleRules( const ExtListSelSet& rules, const ExtSelExtMap& newExtensions) @@ -387,19 +370,22 @@ namespace Sass { for (const SelectorListObj& rule : rules) { const SelectorListObj& oldValue = SASS_MEMORY_COPY(rule); CssMediaRuleObj mediaContext; - if (mediaContexts.hasKey(rule)) mediaContext = mediaContexts.get(rule); + auto it = mediaContexts.find(rule); + if (it != mediaContexts.end()) { + mediaContext = it->second; + } SelectorListObj ext = extendList(rule, newExtensions, mediaContext); // If no extends actually happened (for example because unification // failed), we don't need to re-register the selector. if (ObjEqualityFn(oldValue, ext)) continue; - rule->elements(ext->elements()); + rule->elementsM(std::move(ext->elements())); registerSelector(rule, rule); } } // EO extendExistingStyleRules - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extend [extensions] using [newExtensions]. Note that this does duplicate // some work done by [_extendExistingStyleRules], but it's necessary to // expand each extension's extender separately without reference to the full @@ -413,17 +399,17 @@ namespace Sass { // .z.b {@extend .c} // // Returns `null` (Note: empty map) if there are no extensions to add. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Note: maybe refactor to return `bool` (and pass reference) // Note: dart-sass throws an error in here - // ########################################################################## - ExtSelExtMap Extender::extendExistingExtensions( + ///////////////////////////////////////////////////////////////////////// + void Extender::extendExistingExtensions( // Taking in a reference here makes MSVC debug stuck!? const sass::vector& oldExtensions, const ExtSelExtMap& newExtensions) { - ExtSelExtMap additionalExtensions; + // ExtSelExtMap additionalExtensions; // During the loop `oldExtensions` vector might be changed. // Callers normally pass this from `extensionsByExtender` and @@ -455,18 +441,19 @@ namespace Sass { const Extension withExtender = extension.withExtender(complex); - if (sources.hasKey(complex)) { - sources.insert(complex, mergeExtension( - sources.get(complex), withExtender)); + auto it = sources.find(complex); + if (it != sources.end()) { + sources[complex] = mergeExtension( + it->second, withExtender); } else { - sources.insert(complex, withExtender); + sources[complex] = withExtender; /* // Seems only relevant for sass 4.0 modules for (auto& component : complex->elements()) { - if (auto compound = component->getCompound()) { + if (auto compound = component->isaCompoundSelector()) { for (auto& simple : compound->elements()) { - extensionsByExtender[simple].push_back(withExtender); + extensionsByExtender[simple].emplace_back(withExtender); } } } @@ -489,14 +476,14 @@ namespace Sass { } - return additionalExtensions; + // return additionalExtensions; } // EO extendExistingExtensions - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [list] using [extensions]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// SelectorListObj Extender::extendList( const SelectorListObj& list, const ExtSelExtMap& extensions, @@ -506,23 +493,23 @@ namespace Sass { // This could be written more simply using [List.map], but we want to // avoid any allocations in the common case where no extends apply. sass::vector extended; - for (size_t i = 0; i < list->length(); i++) { + for (size_t i = 0; i < list->size(); i++) { const ComplexSelectorObj& complex = list->get(i); sass::vector result = extendComplex(complex, extensions, mediaQueryContext); if (result.empty()) { if (!extended.empty()) { - extended.push_back(complex); + extended.emplace_back(complex); } } else { if (extended.empty()) { for (size_t n = 0; n < i; n += 1) { - extended.push_back(list->get(n)); + extended.emplace_back(list->get(n)); } } - for (auto sel : result) { - extended.push_back(sel); + for (auto& sel : result) { + extended.emplace_back(std::move(sel)); } } } @@ -538,10 +525,10 @@ namespace Sass { } // EO extendList - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [complex] using [extensions], and // returns the contents of a [SelectorList]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector Extender::extendComplex( // Taking in a reference here makes MSVC debug stuck!? const ComplexSelectorObj& complex, @@ -570,9 +557,9 @@ namespace Sass { sass::vector result; sass::vector> extendedNotExpanded; bool isOriginal = originals.find(complex) != originals.end(); - for (size_t i = 0; i < complex->length(); i += 1) { + for (size_t i = 0; i < complex->size(); i += 1) { const SelectorComponentObj& component = complex->get(i); - if (CompoundSelector* compound = Cast(component)) { + if (CompoundSelector* compound = component->isaCompoundSelector()) { sass::vector extended = extendCompound( compound, extensions, mediaQueryContext, isOriginal); if (extended.empty()) { @@ -591,7 +578,7 @@ namespace Sass { }); } } - extendedNotExpanded.push_back(extended); + extendedNotExpanded.emplace_back(extended); } } else { @@ -617,23 +604,24 @@ namespace Sass { for (const sass::vector& path : paths) { // Unpack the inner complex selector to component list - sass::vector> _paths; + sass::vector _paths; for (const ComplexSelectorObj& sel : path) { _paths.insert(_paths.end(), sel->elements()); } - sass::vector> weaved = weave(_paths); + sass::vector weaved = weave(_paths); - for (sass::vector& components : weaved) { + for (SelectorComponentVector& components : weaved) { - ComplexSelectorObj cplx = SASS_MEMORY_NEW(ComplexSelector, "[phony]"); + ComplexSelectorObj cplx = SASS_MEMORY_NEW(ComplexSelector, + SourceSpan::tmp("[phony]")); cplx->hasPreLineFeed(complex->hasPreLineFeed()); for (auto& pp : path) { if (pp->hasPreLineFeed()) { cplx->hasPreLineFeed(true); } } - cplx->elements(components); + cplx->elementsM(std::move(components)); // Make sure that copies of [complex] retain their status // as "original" selectors. This includes selectors that @@ -643,7 +631,7 @@ namespace Sass { } first = false; - result.push_back(cplx); + result.emplace_back(cplx); } @@ -653,10 +641,10 @@ namespace Sass { } // EO extendComplex - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns a one-off [Extension] whose // extender is composed solely of [simple]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// Extension Extender::extensionForSimple( const SimpleSelectorObj& simple) const { @@ -667,29 +655,27 @@ namespace Sass { } // Extender::extensionForSimple - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns a one-off [Extension] whose extender is composed // solely of a compound selector containing [simples]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// Extension Extender::extensionForCompound( // Taking in a reference here makes MSVC debug stuck!? - const sass::vector& simples) const + const CompoundSelectorObj& compound) const { - CompoundSelectorObj compound = SASS_MEMORY_NEW(CompoundSelector, SourceSpan("[ext]")); - compound->concat(simples); Extension extension(compound->wrapInComplex()); - // extension.specificity = sourceSpecificity[simple]; + extension.specificity = maxSourceSpecificity(compound); extension.isOriginal = true; return extension; } // EO extensionForCompound - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [compound] using [extensions], and returns the // contents of a [SelectorList]. The [inOriginal] parameter // indicates whether this is in an original complex selector, // meaning that [compound] should not be trimmed out. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector Extender::extendCompound( const CompoundSelectorObj& compound, const ExtSelExtMap& extensions, @@ -699,21 +685,18 @@ namespace Sass { // If there's more than one target and they all need to // match, we track which targets are actually extended. - ExtSmplSelSet targetsUsed2; - - ExtSmplSelSet* targetsUsed = nullptr; - + std::unique_ptr targetsUsed; if (mode != ExtendMode::NORMAL && extensions.size() > 1) { - targetsUsed = &targetsUsed2; + targetsUsed.reset(new ExtSmplSelSet()); } sass::vector result; // The complex selectors produced from each component of [compound]. sass::vector> options; - - for (size_t i = 0; i < compound->length(); i++) { + for (size_t i = 0; i < compound->size(); i++) { const SimpleSelectorObj& simple = compound->get(i); - auto extended = extendSimple(simple, extensions, mediaQueryContext, targetsUsed); + auto extended = extendSimple(simple, extensions, + mediaQueryContext, targetsUsed.get()); if (extended.empty()) { if (!options.empty()) { options.push_back({ extensionForSimple(simple) }); @@ -722,11 +705,11 @@ namespace Sass { else { if (options.empty()) { if (i != 0) { - sass::vector in; - for (size_t n = 0; n < i; n += 1) { - in.push_back(compound->get(n)); - } - options.push_back({ extensionForCompound(in) }); + sass::vector children; + children.insert(children.begin(), + compound->begin(), compound->begin() + i); + options.push_back({ extensionForCompound(SASS_MEMORY_NEW( + CompoundSelector, compound->pstate(), std::move(children))) }); } } options.insert(options.end(), @@ -754,22 +737,13 @@ namespace Sass { if (options.size() == 1) { sass::vector exts = options[0]; for (size_t n = 0; n < exts.size(); n += 1) { - exts[n].assertCompatibleMediaContext(mediaQueryContext, traces); - // To fix invalid css we need to re-order some - // Therefore we need to make copies for them - if (exts[n].extender->isInvalidCss()) { - exts[n].extender = SASS_MEMORY_COPY(exts[n].extender); - for (SelectorComponentObj& component : exts[n].extender->elements()) { - if (CompoundSelector* compound = component->getCompound()) { - if (compound->isInvalidCss()) { - CompoundSelector* copy = SASS_MEMORY_COPY(compound); - copy->sortChildren(); - component = copy; - } - } - } + if (!exts[n].mediaContext.isNull()) { + SourceSpan span(exts[n].target->pstate()); + callStackFrame outer(traces, BackTrace(span, Strings::extendRule)); + callStackFrame inner(traces, BackTrace(compound->pstate())); + exts[n].assertCompatibleMediaContext(mediaQueryContext, traces); } - result.push_back(exts[n].extender); + result.emplace_back(exts[n].extender); } return result; } @@ -804,7 +778,7 @@ namespace Sass { sass::vector> prePaths = permutate(options); for (size_t i = 0; i < prePaths.size(); i += 1) { - sass::vector> complexes; + sass::vector complexes; const sass::vector& path = prePaths[i]; if (first) { // The first path is always the original selector. We can't just @@ -812,10 +786,11 @@ namespace Sass { // modified, but we don't have to do any unification. first = false; CompoundSelectorObj mergedSelector = - SASS_MEMORY_NEW(CompoundSelector, "[ext]"); + SASS_MEMORY_NEW(CompoundSelector, + compound->pstate()); for (size_t n = 0; n < path.size(); n += 1) { const ComplexSelectorObj& sel = path[n].extender; - if (CompoundSelectorObj compound = Cast(sel->last())) { + if (CompoundSelector* compound = sel->last()->isaCompoundSelector()) { mergedSelector->concat(compound->elements()); } } @@ -823,22 +798,23 @@ namespace Sass { } else { sass::vector originals; - sass::vector> toUnify; + sass::vector toUnify; for (auto& state : path) { if (state.isOriginal) { const ComplexSelectorObj& sel = state.extender; - if (const CompoundSelector* compound = Cast(sel->last())) { + if (CompoundSelector* compound = sel->last()->isaCompoundSelector()) { originals.insert(originals.end(), compound->last()); } } else { - toUnify.push_back(state.extender->elements()); + toUnify.emplace_back(state.extender->elements()); } } if (!originals.empty()) { CompoundSelectorObj merged = - SASS_MEMORY_NEW(CompoundSelector, "[phony]"); + SASS_MEMORY_NEW(CompoundSelector, + compound->pstate()); merged->concat(originals); toUnify.insert(toUnify.begin(), { merged }); } @@ -852,29 +828,21 @@ namespace Sass { bool lineBreak = false; // var specificity = _sourceSpecificityFor(compound); for (const Extension& state : path) { - state.assertCompatibleMediaContext(mediaQueryContext, traces); + if (!state.mediaContext.isNull()) { + SourceSpan span(state.target->pstate()); + callStackFrame outer(traces, BackTrace(span, Strings::extendRule)); + callStackFrame inner(traces, BackTrace(compound->pstate())); + state.assertCompatibleMediaContext(mediaQueryContext, traces); + } lineBreak = lineBreak || state.extender->hasPreLineFeed(); // specificity = math.max(specificity, state.specificity); } - for (sass::vector& components : complexes) { - auto sel = SASS_MEMORY_NEW(ComplexSelector, "[unified]"); + for (SelectorComponentVector& components : complexes) { + auto sel = SASS_MEMORY_NEW(ComplexSelector, compound->pstate()); sel->hasPreLineFeed(lineBreak); - sel->elements(components); - - /* This seems to do too much in regard of previous behavior - for (SelectorComponentObj& component : sel->elements()) { - if (CompoundSelector* compound = component->getCompound()) { - if (compound->isInvalidCss()) { - CompoundSelector* copy = SASS_MEMORY_COPY(compound); - copy->sortChildren(); - component = copy; - } - } - }*/ - - unifiedPaths.push_back(sel); - + sel->elementsM(std::move(components)); + unifiedPaths.emplace_back(sel); } } @@ -883,16 +851,18 @@ namespace Sass { } // EO extendCompound - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [simple] without extending the // contents of any selector pseudos it contains. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector Extender::extendWithoutPseudo( const SimpleSelectorObj& simple, const ExtSelExtMap& extensions, ExtSmplSelSet* targetsUsed) const { + // std::cerr << "EXTEND " << simple->inspect() << "\n"; + auto extension = extensions.find(simple); if (extension == extensions.end()) return {}; const ExtSelExtMapEntry& extenders = extension->second; @@ -900,31 +870,36 @@ namespace Sass { if (targetsUsed != nullptr) { targetsUsed->insert(simple); } + + sass::vector values; + for (auto& kv : extenders) { + // std::cerr << "EMPLACE " << kv.first->inspect() << "\n"; + values.emplace_back(kv.second); + } + if (mode == ExtendMode::REPLACE) { - return extenders.values(); + return values; } - const sass::vector& - values = extenders.values(); sass::vector result; result.reserve(values.size() + 1); - result.push_back(extensionForSimple(simple)); + result.emplace_back(extensionForSimple(simple)); result.insert(result.end(), values.begin(), values.end()); return result; } // EO extendWithoutPseudo - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [simple] and also extending the // contents of any selector pseudos it contains. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector> Extender::extendSimple( const SimpleSelectorObj& simple, const ExtSelExtMap& extensions, const CssMediaRuleObj& mediaQueryContext, ExtSmplSelSet* targetsUsed) { - if (PseudoSelector* pseudo = Cast(simple)) { + if (PseudoSelector* pseudo = simple->isaPseudoSelector()) { if (pseudo->selector()) { sass::vector> merged; sass::vector extended = @@ -934,7 +909,7 @@ namespace Sass { sass::vector result = extendWithoutPseudo(simple, extensions, targetsUsed); if (result.empty()) result = { extensionForSimple(extend) }; - merged.push_back(result); + merged.emplace_back(result); } if (!extended.empty()) { return merged; @@ -948,20 +923,20 @@ namespace Sass { } // extendSimple - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Inner loop helper for [extendPseudo] function - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector Extender::extendPseudoComplex( const ComplexSelectorObj& complex, const PseudoSelectorObj& pseudo, const CssMediaRuleObj& mediaQueryContext) { - if (complex->length() != 1) { return { complex }; } - auto compound = Cast(complex->get(0)); + if (complex->size() != 1) { return { complex }; } + auto compound = complex->get(0)->isaCompoundSelector(); if (compound == nullptr) { return { complex }; } - if (compound->length() != 1) { return { complex }; } - auto innerPseudo = Cast(compound->get(0)); + if (compound->size() != 1) { return { complex }; } + auto innerPseudo = compound->get(0)->isaPseudoSelector(); if (innerPseudo == nullptr) { return { complex }; } if (!innerPseudo->selector()) { return { complex }; } @@ -982,7 +957,7 @@ namespace Sass { // doing so would require this method and its callers to handle much // more complex cases that likely aren't worth the pain. if (innerPseudo->name() != pseudo->name()) return {}; - if (!ObjEquality()(innerPseudo->argument(), pseudo->argument())) return {}; + if (innerPseudo->argument() != pseudo->argument()) return {}; return innerPseudo->selector()->elements(); } else if (name == "has" && name == "host" && name == "host-context" && name == "slotted") { @@ -997,10 +972,10 @@ namespace Sass { } // EO extendPseudoComplex - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [pseudo] using [extensions], and returns // a list of resulting pseudo selectors. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector Extender::extendPseudo( const PseudoSelectorObj& pseudo, const ExtSelExtMap& extensions, @@ -1024,8 +999,8 @@ namespace Sass { if (hasAny(extended->elements(), hasExactlyOne)) { complexes.clear(); for (auto& complex : extended->elements()) { - if (complex->length() <= 1) { - complexes.push_back(complex); + if (complex->size() <= 1) { + complexes.emplace_back(complex); } } } @@ -1039,10 +1014,10 @@ namespace Sass { // In order to support those browsers, we break up the contents of a `:not` // unless it originally contained a selector list. if (pseudo->normalized() == "not") { - if (pseudo->selector()->length() == 1) { + if (pseudo->selector()->size() == 1) { sass::vector pseudos; for (size_t i = 0; i < expanded.size(); i += 1) { - pseudos.push_back(pseudo->withSelector( + pseudos.emplace_back(pseudo->withSelector( expanded[i]->wrapInList() )); } @@ -1050,17 +1025,18 @@ namespace Sass { } } - SelectorListObj list = SASS_MEMORY_NEW(SelectorList, "[phony]"); + SelectorListObj list = SASS_MEMORY_NEW(SelectorList, + pseudo->pstate()); list->concat(complexes); return { pseudo->withSelector(list) }; } // EO extendPseudo - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Rotates the element in list from [start] (inclusive) to [end] (exclusive) // one index higher, looping the final element back to [start]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// void Extender::rotateSlice( sass::vector& list, size_t start, size_t end) @@ -1074,14 +1050,14 @@ namespace Sass { } // EO rotateSlice - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Removes elements from [selectors] if they're subselectors of other // elements. The [isOriginal] callback indicates which selectors are // original to the document, and thus should never be trimmed. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Note: for adaption I pass in the set directly, there is some // code path in selector-trim that might need this special callback - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector Extender::trim( const sass::vector& selectors, const ExtCplxSelSet& existing) const @@ -1101,7 +1077,7 @@ namespace Sass { size_t i = selectors.size(); outer: // Use label to continue loop - while (--i != sass::string::npos) { + while (--i != std::string::npos) { const ComplexSelectorObj& complex1 = selectors[i]; // Check if selector in known in existing "originals" @@ -1126,7 +1102,7 @@ namespace Sass { // that has specificity greater or equal to this. size_t maxSpecificity = 0; for (const SelectorComponentObj& component : complex1->elements()) { - if (const CompoundSelectorObj compound = Cast(component)) { + if (const CompoundSelectorObj compound = component->isaCompoundSelector()) { maxSpecificity = std::max(maxSpecificity, maxSourceSpecificity(compound)); } } @@ -1156,9 +1132,9 @@ namespace Sass { } // EO trim - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns the maximum specificity of the given [simple] source selector. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// size_t Extender::maxSourceSpecificity(const SimpleSelectorObj& simple) const { auto it = sourceSpecificity.find(simple); @@ -1167,9 +1143,9 @@ namespace Sass { } // EO maxSourceSpecificity(SimpleSelectorObj) - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns the maximum specificity for sources that went into producing [compound]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// size_t Extender::maxSourceSpecificity(const CompoundSelectorObj& compound) const { size_t specificity = 0; @@ -1181,34 +1157,35 @@ namespace Sass { } // EO maxSourceSpecificity(CompoundSelectorObj) - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used as callbacks on lists - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool Extender::dontTrimComplex( const ComplexSelector* complex2, const ComplexSelector* complex1, const size_t maxSpecificity) { - if (complex2->minSpecificity() < maxSpecificity) return false; - return complex2->isSuperselectorOf(complex1); + // std::cerr << "IS " << complex2->isSuperselectorOf(complex1) << "\n"; + return complex2->minSpecificity() >= maxSpecificity && + complex2->isSuperselectorOf(complex1); } // EO dontTrimComplex - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used as callbacks on lists - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool Extender::hasExactlyOne(const ComplexSelectorObj& vec) { - return vec->length() == 1; + return vec->size() == 1; } // EO hasExactlyOne - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used as callbacks on lists - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool Extender::hasMoreThanOne(const ComplexSelectorObj& vec) { - return vec->length() > 1; + return vec->size() > 1; } // hasMoreThanOne diff --git a/src/extender.hpp b/src/extender.hpp index 55d6d62340..4d00c75a1a 100644 --- a/src/extender.hpp +++ b/src/extender.hpp @@ -1,73 +1,79 @@ -#ifndef SASS_EXTENDER_H -#define SASS_EXTENDER_H +#ifndef SASS_EXTENDER_HPP +#define SASS_EXTENDER_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include #include -#include -#include "ast_helpers.hpp" -#include "ast_fwd_decl.hpp" -#include "operation.hpp" #include "extension.hpp" -#include "backtrace.hpp" -#include "ordered_map.hpp" namespace Sass { - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Different hash map types used by extender - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // This is special (ptrs!) - typedef std::unordered_set< + typedef UnorderedSet< ComplexSelectorObj, ObjPtrHash, ObjPtrEquality > ExtCplxSelSet; - typedef std::unordered_set< + typedef UnorderedSet< SimpleSelectorObj, ObjHash, - ObjEquality + ObjEquality, + Sass::Allocator > ExtSmplSelSet; - typedef std::unordered_set< + // This is a very busy set + typedef UnorderedSet< SelectorListObj, ObjPtrHash, - ObjPtrEquality + ObjPtrEquality, + Sass::Allocator > ExtListSelSet; - typedef std::unordered_map< + // This is a very busy map + typedef UnorderedMap< SimpleSelectorObj, ExtListSelSet, ObjHash, ObjEquality + // , Sass::Allocator > ExtSelMap; - typedef ordered_map< + typedef OrderedMap< ComplexSelectorObj, Extension, ObjHash, - ObjEquality + ObjEquality, + Sass::Allocator> > ExtSelExtMapEntry; - typedef std::unordered_map< + typedef UnorderedMap< SimpleSelectorObj, ExtSelExtMapEntry, ObjHash, - ObjEquality + ObjEquality, + Sass::Allocator> > ExtSelExtMap; - typedef std::unordered_map < + typedef UnorderedMap< SimpleSelectorObj, sass::vector< Extension >, ObjHash, - ObjEquality + ObjEquality, + Sass::Allocator>> > ExtByExtMap; - - class Extender : public Operation_CRTP { + + class Extender { public: @@ -75,108 +81,112 @@ namespace Sass { private: - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // The mode that controls this extender's behavior. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// ExtendMode mode; - // ########################################################################## - // Shared backtraces with context and expander. Needed the throw + ///////////////////////////////////////////////////////////////////////// + // Shared back-traces with context and expander. Needed the throw // errors when e.g. extending across media query boundaries. - // ########################################################################## - Backtraces& traces; + ///////////////////////////////////////////////////////////////////////// + BackTraces& traces; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // A map from all simple selectors in the stylesheet to the rules that // contain them.This is used to find which rules an `@extend` applies to. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// ExtSelMap selectors; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // A map from all extended simple selectors // to the sources of those extensions. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// ExtSelExtMap extensions; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // A map from all simple selectors in extenders to // the extensions that those extenders define. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// ExtByExtMap extensionsByExtender; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // A map from CSS rules to the media query contexts they're defined in. // This tracks the contexts in which each style rule is defined. // If a rule is defined at the top level, it doesn't have an entry. - // ########################################################################## - ordered_map< + ///////////////////////////////////////////////////////////////////////// + OrderedMap< SelectorListObj, CssMediaRuleObj, ObjPtrHash, - ObjPtrEquality + ObjPtrEquality, + Sass::Allocator> > mediaContexts; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // A map from [SimpleSelector]s to the specificity of their source selectors. // This tracks the maximum specificity of the [ComplexSelector] that originally // contained each [SimpleSelector]. This allows us to ensure we don't trim any // selectors that need to exist to satisfy the [second law that of extend][]. // [second law of extend]: https://github.com/sass/sass/issues/324#issuecomment-4607184 - // ########################################################################## - std::unordered_map< + ///////////////////////////////////////////////////////////////////////// + UnorderedMap< SimpleSelectorObj, size_t, ObjPtrHash, - ObjPtrEquality + ObjPtrEquality, + Sass::Allocator> > sourceSpecificity; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // A set of [ComplexSelector]s that were originally part of their // component [SelectorList]s, as opposed to being added by `@extend`. // This allows us to ensure that we don't trim any selectors // that need to exist to satisfy the [first law of extend][]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// ExtCplxSelSet originals; public: - // Constructor without default [mode]. - // [traces] are needed to throw errors. - Extender(Backtraces& traces); - - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Constructor with specific [mode]. // [traces] are needed to throw errors. - // ########################################################################## - Extender(ExtendMode mode, Backtraces& traces); + ///////////////////////////////////////////////////////////////////////// + Extender(ExtendMode mode, BackTraces& traces); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Empty desctructor - // ########################################################################## - ~Extender() {}; + ///////////////////////////////////////////////////////////////////////// + // ~Extender() {}; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [selector] with [source] extender and [targets] extendees. // This works as though `source {@extend target}` were written in the // stylesheet, with the exception that [target] can contain compound // selectors which must be extended as a unit. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// static SelectorListObj extend( SelectorListObj& selector, const SelectorListObj& source, const SelectorListObj& target, - Backtraces& traces); + BackTraces& traces); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns a copy of [selector] with [targets] replaced by [source]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// static SelectorListObj replace( SelectorListObj& selector, const SelectorListObj& source, const SelectorListObj& target, - Backtraces& traces); + BackTraces& traces); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Adds [selector] to this extender, with [selectorSpan] as the span covering // the selector and [ruleSpan] as the span covering the entire style rule. // Extends [selector] using any registered extensions, then returns an empty @@ -184,211 +194,211 @@ namespace Sass { // extensions are added, the returned rule is automatically updated. // The [mediaContext] is the media query context in which the selector was // defined, or `null` if it was defined at the top level of the document. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// void addSelector( const SelectorListObj& selector, const CssMediaRuleObj& mediaContext); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Registers the [SimpleSelector]s in [list] // to point to [rule] in [selectors]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// void registerSelector( const SelectorListObj& list, const SelectorListObj& rule); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Adds an extension to this extender. The [extender] is the selector for the // style rule in which the extension is defined, and [target] is the selector // passed to `@extend`. The [extend] provides the extend span and indicates // whether the extension is optional. The [mediaContext] defines the media query // context in which the extension is defined. It can only extend selectors // within the same context. A `null` context indicates no media queries. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// void addExtension( const SelectorListObj& extender, const SimpleSelectorObj& target, const CssMediaRuleObj& mediaQueryContext, bool is_optional = false); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // The set of all simple selectors in style rules handled // by this extender. This includes simple selectors that // were added because of downstream extensions. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// ExtSmplSelSet getSimpleSelectors() const; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Check for extends that have not been satisfied. // Returns true if any non-optional extension did not // extend any selector. Updates the passed reference // to point to that Extension for further analysis. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool checkForUnsatisfiedExtends( Extension& unsatisfied) const; private: - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // A helper function for [extend] and [replace]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// static SelectorListObj extendOrReplace( SelectorListObj& selector, const SelectorListObj& source, const SelectorListObj& target, const ExtendMode mode, - Backtraces& traces); + BackTraces& traces); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns an extension that combines [left] and [right]. Throws // a [SassException] if [left] and [right] have incompatible // media contexts. Throws an [ArgumentError] if [left] // and [right] don't have the same extender and target. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// static Extension mergeExtension( const Extension& lhs, const Extension& rhs); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extend [extensions] using [newExtensions]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Note: dart-sass throws an error in here - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// void extendExistingStyleRules( const ExtListSelSet& rules, const ExtSelExtMap& newExtensions); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extend [extensions] using [newExtensions]. Note that this does duplicate // some work done by [_extendExistingStyleRules], but it's necessary to // expand each extension's extender separately without reference to the full // selector list, so that relevant results don't get trimmed too early. // Returns `null` (Note: empty map) if there are no extensions to add. - // ########################################################################## - ExtSelExtMap extendExistingExtensions( + ///////////////////////////////////////////////////////////////////////// + void extendExistingExtensions( // was ExtSelExtMap // Taking in a reference here makes MSVC debug stuck!? const sass::vector& extensions, const ExtSelExtMap& newExtensions); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [list] using [extensions]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// SelectorListObj extendList( const SelectorListObj& list, const ExtSelExtMap& extensions, const CssMediaRuleObj& mediaContext); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [complex] using [extensions], and // returns the contents of a [SelectorList]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector extendComplex( // Taking in a reference here makes MSVC debug stuck!? const ComplexSelectorObj& list, const ExtSelExtMap& extensions, const CssMediaRuleObj& mediaQueryContext); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns a one-off [Extension] whose // extender is composed solely of [simple]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// Extension extensionForSimple( const SimpleSelectorObj& simple) const; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns a one-off [Extension] whose extender is composed // solely of a compound selector containing [simples]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// Extension extensionForCompound( // Taking in a reference here makes MSVC debug stuck!? - const sass::vector& simples) const; + const CompoundSelectorObj& compound) const; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [compound] using [extensions], and returns the // contents of a [SelectorList]. The [inOriginal] parameter // indicates whether this is in an original complex selector, // meaning that [compound] should not be trimmed out. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector extendCompound( const CompoundSelectorObj& compound, const ExtSelExtMap& extensions, const CssMediaRuleObj& mediaQueryContext, bool inOriginal = false); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [simple] without extending the // contents of any selector pseudos it contains. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector extendWithoutPseudo( const SimpleSelectorObj& simple, const ExtSelExtMap& extensions, ExtSmplSelSet* targetsUsed) const; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [simple] and also extending the // contents of any selector pseudos it contains. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector> extendSimple( const SimpleSelectorObj& simple, const ExtSelExtMap& extensions, const CssMediaRuleObj& mediaQueryContext, ExtSmplSelSet* targetsUsed); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Inner loop helper for [extendPseudo] function - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// static sass::vector extendPseudoComplex( const ComplexSelectorObj& complex, const PseudoSelectorObj& pseudo, const CssMediaRuleObj& mediaQueryContext); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [pseudo] using [extensions], and returns // a list of resulting pseudo selectors. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector extendPseudo( const PseudoSelectorObj& pseudo, const ExtSelExtMap& extensions, const CssMediaRuleObj& mediaQueryContext); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Rotates the element in list from [start] (inclusive) to [end] (exclusive) // one index higher, looping the final element back to [start]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// static void rotateSlice( sass::vector& list, size_t start, size_t end); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Removes elements from [selectors] if they're subselectors of other // elements. The [isOriginal] callback indicates which selectors are // original to the document, and thus should never be trimmed. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector trim( const sass::vector& selectors, const ExtCplxSelSet& set) const; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns the maximum specificity of the given [simple] source selector. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// size_t maxSourceSpecificity(const SimpleSelectorObj& simple) const; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns the maximum specificity for sources that went into producing [compound]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// size_t maxSourceSpecificity(const CompoundSelectorObj& compound) const; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used as callbacks on lists - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// static bool dontTrimComplex( const ComplexSelector* complex2, const ComplexSelector* complex1, const size_t maxSpecificity); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used as callbacks on lists - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// static bool hasExactlyOne(const ComplexSelectorObj& vec); static bool hasMoreThanOne(const ComplexSelectorObj& vec); diff --git a/src/extension.cpp b/src/extension.cpp index 80f5f41303..e8331fbaaf 100644 --- a/src/extension.cpp +++ b/src/extension.cpp @@ -1,16 +1,14 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +#include "extension.hpp" +#include "callstack.hpp" #include "ast_helpers.hpp" -#include "extension.hpp" -#include "ast.hpp" +#include "exceptions.hpp" namespace Sass { - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Static function to create a copy with a new extender - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// Extension Extension::withExtender(const ComplexSelectorObj& newExtender) const { Extension extension(newExtender); @@ -20,16 +18,16 @@ namespace Sass { return extension; } - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Asserts that the [mediaContext] for a selector is // compatible with the query context for this extender. - // ########################################################################## - void Extension::assertCompatibleMediaContext(CssMediaRuleObj mediaQueryContext, Backtraces& traces) const + ///////////////////////////////////////////////////////////////////////// + void Extension::assertCompatibleMediaContext(CssMediaRuleObj mediaQueryContext, BackTraces& traces) const { if (this->mediaContext.isNull()) return; - if (mediaQueryContext && ObjPtrEqualityFn(mediaContext->block(), mediaQueryContext->block())) return; + if (mediaQueryContext && mediaContext == mediaQueryContext) return; if (ObjEqualityFn(mediaQueryContext, mediaContext)) return; @@ -37,7 +35,7 @@ namespace Sass { } - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/extension.hpp b/src/extension.hpp index 58fd5f8214..b207d20538 100644 --- a/src/extension.hpp +++ b/src/extension.hpp @@ -1,14 +1,11 @@ -#ifndef SASS_EXTENSION_H -#define SASS_EXTENSION_H +#ifndef SASS_EXTENSION_HPP +#define SASS_EXTENSION_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include -#include -#include "ast_fwd_decl.hpp" -#include "backtrace.hpp" +#include "ast_css.hpp" namespace Sass { @@ -34,8 +31,6 @@ namespace Sass { // originally in the document, rather than one defined with `@extend`. bool isOriginal; - bool isSatisfied; - // The media query context to which this extend is restricted, // or `null` if it can apply within any context. CssMediaRuleObj mediaContext; @@ -48,10 +43,8 @@ namespace Sass { specificity(0), isOptional(true), isOriginal(false), - isSatisfied(false), - mediaContext({}) { - - } + mediaContext({}) + {} // Copy constructor Extension(const Extension& extension) : @@ -60,10 +53,8 @@ namespace Sass { specificity(extension.specificity), isOptional(extension.isOptional), isOriginal(extension.isOriginal), - isSatisfied(extension.isSatisfied), - mediaContext(extension.mediaContext) { - - } + mediaContext(extension.mediaContext) + {} // Default constructor Extension() : @@ -72,13 +63,12 @@ namespace Sass { specificity(0), isOptional(false), isOriginal(false), - isSatisfied(false), - mediaContext({}) { - } + mediaContext({}) + {} // Asserts that the [mediaContext] for a selector is // compatible with the query context for this extender. - void assertCompatibleMediaContext(CssMediaRuleObj mediaContext, Backtraces& traces) const; + void assertCompatibleMediaContext(CssMediaRuleObj mediaContext, BackTraces& traces) const; Extension withExtender(const ComplexSelectorObj& newExtender) const; diff --git a/src/file.cpp b/src/file.cpp index 163abd427b..5509ac3af8 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -1,6 +1,14 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +#include "file.hpp" + +// Some functions are heavily inspired by Perl +// https://perldoc.perl.org/File/Basename.html +// https://perldoc.perl.org/File/Spec.html + +#if defined (_MSC_VER) // Visual studio +#define thread_local __declspec( thread ) +#elif defined (__GCC__) // GCC +#define thread_local __thread +#endif #ifdef _WIN32 # ifdef __MINGW32__ @@ -13,35 +21,29 @@ #else # include #endif -#include -#include -#include + #include -#include "file.hpp" -#include "context.hpp" -#include "prelexer.hpp" -#include "utf8_string.hpp" -#include "sass_functions.hpp" -#include "error_handling.hpp" -#include "util.hpp" -#include "util_string.hpp" -#include "sass2scss.h" +#include "source.hpp" +#include "unicode.hpp" +#include "character.hpp" +#include "exceptions.hpp" +#include "string_utils.hpp" #ifdef _WIN32 # include # ifdef _MSC_VER # include -inline static Sass::sass::string wstring_to_string(const std::wstring& wstr) +inline static sass::string wstring_to_string(const std::wstring& wstr) { std::wstring_convert, wchar_t> wchar_converter; - return wchar_converter.to_bytes(wstr); + return wchar_converter.to_bytes(wstr).c_str(); } # else // mingw(/gcc) does not support C++11's codecvt yet. -inline static Sass::sass::string wstring_to_string(const std::wstring &wstr) +inline static sass::string wstring_to_string(const std::wstring &wstr) { int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); - Sass::sass::string strTo(size_needed, 0); + sass::string strTo(size_needed, 0); WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); return strTo; } @@ -54,7 +56,7 @@ namespace Sass { // return the current directory // always with forward slashes // always with trailing slash - sass::string get_cwd() + extern sass::string get_cwd() { const size_t wd_len = 4096; #ifndef _WIN32 @@ -76,28 +78,39 @@ namespace Sass { return cwd; } + // test if path exists and is a file - bool file_exists(const sass::string& path) + // takes a cache map to improve performance + bool file_exists(const sass::string& path, const sass::string& CWD, std::unordered_map& cache) { #ifdef _WIN32 wchar_t resolved[32768]; - // windows unicode filepaths are encoded in utf16 - sass::string abspath(join_paths(get_cwd(), path)); + // windows unicode file-paths are encoded in utf16 + sass::string abspath(join_paths(CWD, path)); if (!(abspath[0] == '/' && abspath[1] == '/')) { abspath = "//?/" + abspath; } - std::wstring wpath(UTF_8::convert_to_utf16(abspath)); + auto it = cache.find(abspath); + if (it != cache.end()) { + return it->second; + } + sass::wstring wpath(Unicode::utf8to16(abspath)); std::replace(wpath.begin(), wpath.end(), '/', '\\'); DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); if (rv > 32767) throw Exception::OperationError("Path is too long"); if (rv == 0) throw Exception::OperationError("Path could not be resolved"); - DWORD dwAttrib = GetFileAttributesW(resolved); - return (dwAttrib != INVALID_FILE_ATTRIBUTES && - (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))); + DWORD dwAttrib = GetFileAttributesW(resolved); // was 3% + bool result = (dwAttrib != INVALID_FILE_ATTRIBUTES + && (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))); + cache[abspath] = result; + return result; #else struct stat st_buf; - return (stat (path.c_str(), &st_buf) == 0) && - (!S_ISDIR (st_buf.st_mode)); + sass::string abspath(join_paths(CWD, path)); + bool result = (stat (abspath.c_str(), &st_buf) == 0) + && (!S_ISDIR (st_buf.st_mode)); + cache[abspath] = result; + return result; #endif } @@ -106,17 +119,18 @@ namespace Sass { bool is_absolute_path(const sass::string& path) { #ifdef _WIN32 - if (path.length() >= 2 && Util::ascii_isalpha(path[0]) && path[1] == ':') return true; + if (path.length() >= 3 && Character::isAlphabetic(path[0]) && path[1] == ':') return true; #endif size_t i = 0; // check if we have a protocol - if (path[i] && Util::ascii_isalpha(static_cast(path[i]))) { + if (path[i] && Character::isAlphabetic(static_cast(path[i]))) { // skip over all alphanumeric characters - while (path[i] && Util::ascii_isalnum(static_cast(path[i]))) ++i; + while (path[i] && Character::isAlphanumeric(static_cast(path[i]))) ++i; i = i && path[i] == ':' ? i + 1 : 0; } return path[i] == '/'; } + // EO is_absolute_path // helper function to find the last directory separator inline size_t find_last_folder_separator(const sass::string& path, size_t limit = sass::string::npos) @@ -139,6 +153,7 @@ namespace Sass { } return pos; } + // EO find_last_folder_separator // return only the directory part of path sass::string dir_name(const sass::string& path) @@ -147,6 +162,7 @@ namespace Sass { if (pos == sass::string::npos) return ""; else return path.substr(0, pos+1); } + // EO dir_name // return only the filename part of path sass::string base_name(const sass::string& path) @@ -155,9 +171,10 @@ namespace Sass { if (pos == sass::string::npos) return path; else return path.substr(pos+1); } + // EO base_name // do a logical clean up of the path - // no physical check on the filesystem + // no physical check on the file-system sass::string make_canonical_path (sass::string path) { @@ -169,19 +186,18 @@ namespace Sass { replace(path.begin(), path.end(), '\\', '/'); #endif - pos = 0; // remove all self references inside the path string + pos = 0; // remove all self references inside the path string (`/./`) while((pos = path.find("/./", pos)) != sass::string::npos) path.erase(pos, 2); // remove all leading and trailing self references while(path.size() >= 2 && path[0] == '.' && path[1] == '/') path.erase(0, 2); while((pos = path.length()) > 1 && path[pos - 2] == '/' && path[pos - 1] == '.') path.erase(pos - 2); - size_t proto = 0; // check if we have a protocol - if (path[proto] && Util::ascii_isalpha(static_cast(path[proto]))) { + if (path[proto] && Character::isAlphabetic(path[proto])) { // skip over all alphanumeric characters - while (path[proto] && Util::ascii_isalnum(static_cast(path[proto++]))) {} + while (path[proto] && Character::isAlphanumeric(path[proto++])) {} // then skip over the mandatory colon if (proto && path[proto] == ':') ++ proto; } @@ -195,9 +211,12 @@ namespace Sass { return path; } + // EO make_canonical_path // join two path segments cleanly together // but only if right side is not absolute yet + // Can we avoid the two string copies? + // OK, one copy is needed anyway sass::string join_paths(sass::string l, sass::string r) { @@ -231,46 +250,35 @@ namespace Sass { return l + r; } + // EO join_paths - sass::string path_for_console(const sass::string& rel_path, const sass::string& abs_path, const sass::string& orig_path) + sass::string rel2dbg(const sass::string& rel_path, const sass::string& orig_path) { - // magic algorithm goes here!! - // if the file is outside this directory show the absolute path - if (rel_path.substr(0, 3) == "../") { - return orig_path; - } - // this seems to work most of the time - return abs_path == orig_path ? abs_path : rel_path; + return rel_path.substr(0, 3) == "../" ? orig_path : rel_path; } + // EO rel2dbg // create an absolute path by resolving relative paths with cwd - sass::string rel2abs(const sass::string& path, const sass::string& base, const sass::string& cwd) + sass::string rel2abs(const sass::string& path, const sass::string& base, const sass::string& CWD) { - sass::string rv = make_canonical_path(join_paths(join_paths(cwd + "/", base + "/"), path)); - #ifdef _WIN32 - // On windows we may get an absolute path without directory - // In that case we should prepend the directory from the root - if (rv[0] == '/' && rv[1] != '/') { - rv.insert(0, cwd, 0, 2); - } - #endif - return rv; + return make_canonical_path(join_paths(join_paths(CWD + "/", base + "/"), path)); } + // EO rel2abs // create a path that is relative to the given base directory // path and base will first be resolved against cwd to make them absolute - sass::string abs2rel(const sass::string& path, const sass::string& base, const sass::string& cwd) + sass::string abs2rel(const sass::string& path, const sass::string& base, const sass::string& CWD) { - sass::string abs_path = rel2abs(path, cwd); - sass::string abs_base = rel2abs(base, cwd); + sass::string abs_path = rel2abs(path, CWD, CWD); + sass::string abs_base = rel2abs(base, CWD, CWD); size_t proto = 0; // check if we have a protocol - if (path[proto] && Util::ascii_isalpha(static_cast(path[proto]))) { + if (path[proto] && Character::isAlphabetic(static_cast(path[proto]))) { // skip over all alphanumeric characters - while (path[proto] && Util::ascii_isalnum(static_cast(path[proto++]))) {} + while (path[proto] && Character::isAlphanumeric(static_cast(path[proto++]))) {} // then skip over the mandatory colon if (proto && path[proto] == ':') ++ proto; } @@ -294,10 +302,9 @@ namespace Sass { #ifdef FS_CASE_SENSITIVE if (abs_path[i] != abs_base[i]) break; #else - // compare the charactes in a case insensitive manner - // windows fs is only case insensitive in ascii ranges - if (Util::ascii_tolower(static_cast(abs_path[i])) != - Util::ascii_tolower(static_cast(abs_base[i]))) break; + // compare the characters in a case insensitive manner + // windows FS is only case insensitive in ASCII ranges + if (!Character::characterEqualsIgnoreCase(abs_path[i], abs_base[i])) break; #endif if (abs_path[i] == '/') index = i + 1; } @@ -333,7 +340,68 @@ namespace Sass { return result; } + // EO abs2rel + + + + // Resolution order for ambiguous imports: + // (1) filename as given + // (2) underscore + given + // (3) underscore + given + extension + // (4) given + extension + // (5) given + _index.scss + // (6) given + _index.sass + void findFileOrPartial( + const sass::string& root, + const sass::string& dirname, + const sass::string& basename, + const sass::string& CWD, + std::unordered_map& cache, + const std::vector& exts, + sass::vector& candidates) + { + sass::string relPath(join_paths(dirname, "_" + basename)); + sass::string absPath(join_paths(root, relPath)); + if (file_exists(join_paths(root, relPath), CWD, cache)) { + ImportRequest request(relPath, root); + ResolvedImport import(request, absPath, SASS_IMPORT_AUTO); + candidates.push_back(import); + } + relPath = join_paths(dirname, basename); + absPath = join_paths(root, relPath); + if (file_exists(join_paths(root, relPath), CWD, cache)) { + ImportRequest request(relPath, root); + ResolvedImport import(request, absPath, SASS_IMPORT_AUTO); + candidates.push_back(import); + } + for (auto ext : exts) { + if (ext == ".css" && candidates.size()) return; + relPath = join_paths(dirname, "_" + basename + ext); + absPath = join_paths(root, relPath); + if (file_exists(join_paths(root, relPath), CWD, cache)) { + ImportRequest request(relPath, root); + ResolvedImport import(request, absPath, SASS_IMPORT_AUTO); + candidates.push_back(import); + } + relPath = join_paths(dirname, basename + ext); + absPath = join_paths(root, relPath); + if (file_exists(join_paths(root, relPath), CWD, cache)) { + ImportRequest request(relPath, root); + ResolvedImport import(request, absPath, SASS_IMPORT_AUTO); + candidates.push_back(import); + } + } + // if (candidates.size() > 1) { + // sass::sstream msg_stream; + // msg_stream << "It's not clear which file to import. Found:\n"; + // for (size_t i = 0, L = candidates.size(); i < L; ++i) + // { msg_stream << " " << candidates[i].imp_path << "\n"; } + // throw Exception::ParserException(logger, msg_stream.str()); + // } + // return candidates; + } + // Resolution order for ambiguous imports: // (1) filename as given // (2) underscore + given @@ -341,96 +409,79 @@ namespace Sass { // (4) given + extension // (5) given + _index.scss // (6) given + _index.sass - sass::vector resolve_includes(const sass::string& root, const sass::string& file, const sass::vector& exts) + sass::vector resolve_includes( + const sass::string& root, + const sass::string& file, + const sass::string& CWD, + std::unordered_map& cache, + const std::vector& exts) { sass::string filename = join_paths(root, file); // split the filename sass::string base(dir_name(file)); sass::string name(base_name(file)); - sass::vector includes; + sass::vector includes; // create full path (maybe relative) sass::string rel_path(join_paths(base, name)); sass::string abs_path(join_paths(root, rel_path)); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); - // next test variation with underscore - rel_path = join_paths(base, "_" + name); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); - // next test exts plus underscore - for(auto ext : exts) { - rel_path = join_paths(base, "_" + name + ext); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); - } - // next test plain name with exts - for(auto ext : exts) { - rel_path = join_paths(base, name + ext); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); - } - // index files - if (includes.size() == 0) { - // ignore directories that look like @import'able filename - for(auto ext : exts) { - if (ends_with(name, ext)) return includes; - } - // next test underscore index exts - for(auto ext : exts) { - rel_path = join_paths(base, join_paths(name, "_index" + ext)); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); - } - // next test plain index exts - for(auto ext : exts) { - rel_path = join_paths(base, join_paths(name, "index" + ext)); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + + sass::string suffix; + for (auto ext : exts) { + if (StringUtils::endsWithIgnoreCase(name, ext)) { + name.resize(name.size() - ext.size()); + suffix = ext; + break; } } - // nothing found + + findFileOrPartial(root, base, name + ".import" + suffix, CWD, cache, exts, includes); + if (includes.size()) return includes; + + findFileOrPartial(root, base, name + suffix, CWD, cache, exts, includes); + if (includes.size()) return includes; + + // if (!suffix.empty()) return includes; + + sass::string subdir(join_paths(base, name)); + + findFileOrPartial(root, subdir, "index.import", CWD, cache, exts, includes); + if (includes.size()) return includes; + + findFileOrPartial(root, subdir, "index", CWD, cache, exts, includes); + if (includes.size()) return includes; + return includes; } + // EO resolve_includes - sass::vector find_files(const sass::string& file, const sass::vector paths) + // Private helper function for find_file + StringVector _find_file(const sass::string& file, const sass::string& CWD, const StringVector paths, std::unordered_map& cache) { - sass::vector includes; - for (sass::string path : paths) { + StringVector includes; + for (const sass::string& path : paths) { sass::string abs_path(join_paths(path, file)); - if (file_exists(abs_path)) includes.push_back(abs_path); + if (file_exists(abs_path, CWD, cache)) includes.emplace_back(abs_path); } return includes; } - - sass::vector find_files(const sass::string& file, struct Sass_Compiler* compiler) - { - // get the last import entry to get current base directory - // struct Sass_Options* options = sass_compiler_get_options(compiler); - Sass_Import_Entry import = sass_compiler_get_last_import(compiler); - const sass::vector& incs = compiler->cpp_ctx->include_paths; - // create the vector with paths to lookup - sass::vector paths(1 + incs.size()); - paths.push_back(dir_name(import->abs_path)); - paths.insert(paths.end(), incs.begin(), incs.end()); - // dispatch to find files in paths - return find_files(file, paths); - } + // EO find_files // helper function to search one file in all include paths // this is normally not used internally by libsass (C-API sugar) - sass::string find_file(const sass::string& file, const sass::vector paths) + sass::string find_file(const sass::string& file, const sass::string& CWD, const StringVector paths, std::unordered_map& cache) { if (file.empty()) return file; - auto res = find_files(file, paths); + auto res = _find_file(file, CWD, paths, cache); return res.empty() ? "" : res.front(); } // helper function to resolve a filename - sass::string find_include(const sass::string& file, const sass::vector paths) + sass::string find_include(const sass::string& file, const sass::string& CWD, const StringVector paths, std::unordered_map& cache) { // search in every include path for a match for (size_t i = 0, S = paths.size(); i < S; ++i) { - sass::vector resolved(resolve_includes(paths[i], file)); + sass::vector resolved(resolve_includes(paths[i], file, CWD, cache)); if (resolved.size()) return resolved[0].abs_path; } // nothing found @@ -440,35 +491,33 @@ namespace Sass { // try to load the given filename // returned memory must be freed // will auto convert .sass files - char* read_file(const sass::string& path) + char* slurp_file(const sass::string& path, const sass::string& CWD) { #ifdef _WIN32 - BYTE* pBuffer; + char* contents; DWORD dwBytes; wchar_t resolved[32768]; - // windows unicode filepaths are encoded in utf16 - sass::string abspath(join_paths(get_cwd(), path)); + // windows unicode file-paths are encoded in utf16 + sass::string abspath(join_paths(CWD, path)); if (!(abspath[0] == '/' && abspath[1] == '/')) { abspath = "//?/" + abspath; } - std::wstring wpath(UTF_8::convert_to_utf16(abspath)); + sass::wstring wpath(Unicode::utf8to16(abspath)); std::replace(wpath.begin(), wpath.end(), '/', '\\'); DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); if (rv > 32767) throw Exception::OperationError("Path is too long"); if (rv == 0) throw Exception::OperationError("Path could not be resolved"); HANDLE hFile = CreateFileW(resolved, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) return 0; + // ToDo: do some file locking here!? DWORD dwFileLength = GetFileSize(hFile, NULL); if (dwFileLength == INVALID_FILE_SIZE) return 0; - // allocate an extra byte for the null char - // and another one for edge-cases in lexer - pBuffer = (BYTE*)malloc((dwFileLength+2)*sizeof(BYTE)); - ReadFile(hFile, pBuffer, dwFileLength, &dwBytes, NULL); - pBuffer[dwFileLength+0] = '\0'; - pBuffer[dwFileLength+1] = '\0'; + // allocate an extra byte for the null terminator + contents = (char*) sass_alloc_memory(size_t(dwFileLength) + 1); + if (!ReadFile(hFile, contents, dwFileLength, &dwBytes, NULL)) + throw Exception::OperationError("Could not read file"); + contents[dwFileLength] = '\0'; // ensure null terminator CloseHandle(hFile); - // just convert from unsigned char* - char* contents = (char*) pBuffer; #else // Read the file using `` instead of `` for better portability. // The `` header initializes `` and this buggy in GCC4/5 with static linking. @@ -480,7 +529,7 @@ namespace Sass { FILE* fd = std::fopen(path.c_str(), "rb"); if (fd == nullptr) return nullptr; const std::size_t size = st.st_size; - char* contents = static_cast(malloc(st.st_size + 2 * sizeof(char))); + char* contents = static_cast(sass_alloc_memory(st.st_size + 1 * sizeof(char))); if (std::fread(static_cast(contents), 1, size, fd) != size) { free(contents); std::fclose(fd); @@ -491,41 +540,83 @@ namespace Sass { return nullptr; } contents[size] = '\0'; - contents[size + 1] = '\0'; #endif - sass::string extension; - if (path.length() > 5) { - extension = path.substr(path.length() - 5, 5); - } - Util::ascii_str_tolower(&extension); - if (extension == ".sass" && contents != 0) { - char * converted = sass2scss(contents, SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT); - free(contents); // free the indented contents - return converted; // should be freed by caller - } else { - return contents; - } + return contents; } + // EO slurp_file - // split a path string delimited by semicolons or colons (OS dependent) - sass::vector split_path_list(const char* str) + Import* read_file(const ResolvedImport& import) { - sass::vector paths; - if (str == NULL) return paths; - // find delimiter via prelexer (return zero at end) - const char* end = Prelexer::find_first(str); - // search until null delimiter - while (end) { - // add path from current position to delimiter - paths.push_back(sass::string(str, end - str)); - str = end + 1; // skip delimiter - end = Prelexer::find_first(str); + // try to read the content of the resolved file entry + // the memory buffer returned to us must be freed by us! + if (char* contents = slurp_file(import.abs_path, CWD)) { + // Return LoadedImport object + // ToDo: Add sourcemap parsing + return SASS_MEMORY_NEW(Import, + SASS_MEMORY_NEW(SourceFile, + import.imp_path.c_str(), + import.abs_path.c_str(), + contents, nullptr + ), import.syntax); } - // add path from current position to end - paths.push_back(sass::string(str)); - // return back - return paths; + // Nothing was found + return nullptr; } + // EO slurp_file + + } + // Entry point for top level file import + // Don't load like other includes, we do not + // check inside include paths for this file! + void Import::loadIfNeeded() + { + // Only load once + if (isLoaded()) return; + // Check if entry file-path is given + // Use err string of LoadedImport + if (getAbsPath() == nullptr) { + throw std::runtime_error( + "No file path given to be loaded."); + } + // try to read the content of the resolved file entry + // the memory buffer returned to us must be freed by us! + if (char* contents = File::slurp_file(getAbsPath(), CWD)) { + // Upgrade to a source file + // ToDo: Add sourcemap parsing + source = SASS_MEMORY_NEW(SourceFile, + source->getImpPath(), + source->getAbsPath(), + contents, nullptr + ); + } + else { + // Throw error if read has failed + throw Exception::OperationError( + "File to read not found or unreadable."); + } } + + const char* Import::getImpPath() const + { + return source->getImpPath(); + } + + const char* Import::getAbsPath() const + { + return source->getAbsPath(); + } + + Import::Import( + SourceData* source, + SassImportFormat syntax) : + source(source), + syntax(syntax) + {} + + bool Import::isLoaded() const + { + return source && source->content(); + } + } diff --git a/src/file.hpp b/src/file.hpp index 78b5244408..58ce16d4ff 100644 --- a/src/file.hpp +++ b/src/file.hpp @@ -1,26 +1,36 @@ -#ifndef SASS_FILE_H -#define SASS_FILE_H +#ifndef SASS_FILE_HPP +#define SASS_FILE_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include #include #include "sass/context.h" +#include "sass/functions.h" #include "ast_fwd_decl.hpp" +#include "ast_def_macros.hpp" namespace Sass { + extern sass::string CWD; + + + class ResolvedImport; + + namespace File { + // return the current directory // always with forward slashes - sass::string get_cwd(); + extern sass::string get_cwd(); // test if path exists and is a file - bool file_exists(const sass::string& file); + bool file_exists(const sass::string& file, const sass::string& CWD, + std::unordered_map& cache); // return if given path is absolute // works with *nix and windows paths @@ -32,46 +42,47 @@ namespace Sass { // return only the filename part of path sass::string base_name(const sass::string&); - // do a locigal clean up of the path - // no physical check on the filesystem + // do a logical clean up of the path + // no physical check on the file-system sass::string make_canonical_path (sass::string path); // join two path segments cleanly together // but only if right side is not absolute yet sass::string join_paths(sass::string root, sass::string name); - // if the relative path is outside of the cwd we want want to - // show the absolute path in console messages - sass::string path_for_console(const sass::string& rel_path, const sass::string& abs_path, const sass::string& orig_path); + // if the relative path is outside of the cwd we want + // to show the absolute path in console messages + sass::string rel2dbg(const sass::string& rel_path, const sass::string& orig_path); // create an absolute path by resolving relative paths with cwd - sass::string rel2abs(const sass::string& path, const sass::string& base = ".", const sass::string& cwd = get_cwd()); + sass::string rel2abs(const sass::string& path, const sass::string& base = Sass::CWD, const sass::string& CWD = Sass::CWD); // create a path that is relative to the given base directory // path and base will first be resolved against cwd to make them absolute - sass::string abs2rel(const sass::string& path, const sass::string& base = ".", const sass::string& cwd = get_cwd()); + sass::string abs2rel(const sass::string& path, const sass::string& base = Sass::CWD, const sass::string& CWD = Sass::CWD); // helper function to resolve a filename // searching without variations in all paths - sass::string find_file(const sass::string& file, struct Sass_Compiler* options); - sass::string find_file(const sass::string& file, const sass::vector paths); + // sass::string find_file(const sass::string& file, const sass::string& CWD, struct SassCompilerCpp* options); + sass::string find_file(const sass::string& file, const sass::string& CWD, const StringVector paths, std::unordered_map& cache); // helper function to resolve a include filename // this has the original resolve logic for sass include - sass::string find_include(const sass::string& file, const sass::vector paths); + sass::string find_include(const sass::string& file, const sass::string& CWD, const StringVector paths, std::unordered_map& cache); // split a path string delimited by semicolons or colons (OS dependent) - sass::vector split_path_list(const char* paths); + // StringVector split_path_list(sass::string paths); // try to load the given filename // returned memory must be freed - // will auto convert .sass files - char* read_file(const sass::string& file); + char* slurp_file(const sass::string& file, const sass::string& CWD); + + Import* read_file(const ResolvedImport& import); } // requested import - class Importer { + class ImportRequest { public: // requested import path sass::string imp_path; @@ -81,7 +92,7 @@ namespace Sass { // this really just acts as a cache sass::string base_path; public: - Importer(sass::string imp_path, sass::string ctx_path) + ImportRequest(sass::string imp_path, sass::string ctx_path) : imp_path(File::make_canonical_path(imp_path)), ctx_path(File::make_canonical_path(ctx_path)), base_path(File::dir_name(ctx_path)) @@ -89,34 +100,59 @@ namespace Sass { }; // a resolved include (final import) - class Include : public Importer { + class ResolvedImport : public ImportRequest { public: // resolved absolute path sass::string abs_path; + // which importer to use + SassImportFormat syntax; public: - Include(const Importer& imp, sass::string abs_path) - : Importer(imp), abs_path(abs_path) + ResolvedImport( + const ImportRequest& imp, + sass::string abs_path, + SassImportFormat syntax) + : ImportRequest(imp), abs_path(abs_path), syntax(syntax) { } }; - // a loaded resource - class Resource { - public: - // the file contents - char* contents; - // connected sourcemap - char* srcmap; - public: - Resource(char* contents, char* srcmap) - : contents(contents), srcmap(srcmap) - { } + + // Base class for entry points + class Import : public SharedObj { + public: + SourceDataObj source; + SassImportFormat syntax; + void loadIfNeeded(); + bool isLoaded() const; + const char* getImpPath() const; + const char* getAbsPath() const; + Import(SassImportFormat syntax = SASS_IMPORT_AUTO) : + syntax(syntax) + {} + + Import( + SourceData* source, + SassImportFormat syntax); + + CAPI_WRAPPER(Import, SassImport); }; - namespace File { + // Error thrown by certain file functions + class AmbiguousImport : public std::exception { + public: + sass::vector imports; + AmbiguousImport(sass::vector imports) + : imports(imports) + {} + }; + + // Error thrown by certain file functions + class ImportNotFound : public std::exception {}; + class ImportReadFailed : public std::exception {}; - sass::vector resolve_includes(const sass::string& root, const sass::string& file, - const sass::vector& exts = { ".scss", ".sass", ".css" }); + namespace File { + sass::vector resolve_includes(const sass::string& root, const sass::string& file, const sass::string& CWD, + std::unordered_map& cache, const std::vector& exts = { ".sass", ".scss", ".css" }); } } diff --git a/src/flat_map.hpp b/src/flat_map.hpp new file mode 100644 index 0000000000..2bd2f71870 --- /dev/null +++ b/src/flat_map.hpp @@ -0,0 +1,217 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FLAT_MAP_HPP +#define SASS_FLAT_MAP_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + // A flat map is an optimized map when you know to expect minimal + // amount of items in the map. In this case we rather should use a + // vector, which is what this implementation does. It implements the + // interface of a regular map (partially) by utilizing a vector. + // My own tests indicate that hash maps start to win when 10 or more + // items are present. We can assume that e.g. functions typically don't + // have that many named arguments, probably most often not more than + // three or four. The interface matches std::unordered_map mostly. + // Having a compatible interface means we can exchange the + // implementations easily for performance benchmarks. + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + template class FlatMap + { + private: + + // Define base types + using PAIR = std::pair; + using TYPE = sass::vector; + + // Main key/value pair vector + TYPE items; + + public: + + // Some convenient iterator type aliases + using iterator = typename TYPE::iterator; + using const_iterator = typename TYPE::const_iterator; + using reverse_iterator = typename TYPE::reverse_iterator; + using const_reverse_iterator = typename TYPE::const_reverse_iterator; + + // Returns number of key/value pairs + size_t size() const + { + return items.size(); + } + // EO size + + // Returns if map is empty + bool empty() const + { + return items.empty(); + } + // EO empty + + // Erases all items + void clear() + { + items.clear(); + } + // EO clear + + // Returns the number of elements matching specific key + size_t count(const K& key) const + { + const_iterator cur = items.begin(); + const_iterator end = items.end(); + while (cur != end) { + // Compare the normalized keys + if (cur->first == key) { + return 1; + } + cur++; + } + return 0; + } + // EO count + + // Removes item with specific key from the map + void erase(const K& key) + { + const_iterator cur = items.begin(); + const_iterator end = items.end(); + while (cur != end) { + // Compare the normalized keys + if (cur->first == key) { + items.erase(cur); + return; + } + cur++; + } + } + // EO erase(key) + + // Removes iterator's item from the map + void erase(iterator it) + { + items.erase(it); + } + // EO erase(it) + + // Reserves space for at least the specified number of elements. + void reserve(size_t size) + { + items.reserve(size); + } + // EO reserve + + // Finds element with specific key + iterator find(const K& key) + { + iterator cur = items.begin(); + iterator end = items.end(); + while (cur != end) { + if (cur->first == key) { + return cur; + } + cur++; + } + return end; + } + // EO find + + // Finds element with specific key + const_iterator find(const K& key) const + { + const_iterator cur = items.begin(); + const_iterator end = items.end(); + while (cur != end) { + // Compare the normalized keys + if (cur->first == key) { + return cur; + } + cur++; + } + return end; + } + // EO const find + + // Access or insert specified element + V& operator[](const K& key) + { + iterator cur = items.begin(); + iterator end = items.end(); + while (cur != end) { + // Compare the normalized keys + if (cur->first == key) { + return cur->second; + } + cur++; + } + // Append empty object + items.push_back( + std::make_pair( + key, V{})); + // Returns newly added value + return items.back().second; + } + // EO array[] + + // Insert passed key/value pair + bool insert(const PAIR& kv) + { + if (count(kv.first) == 0) { + // Append the pair + items.push_back(kv); + // Returns success + return true; + } + // Nothing inserted + return false; + } + // EO insert + + // Access element at specific key + // Throws of key is not known in map + const V& at(const K& key) const + { + const_iterator cur = items.begin(); + const_iterator end = items.end(); + while (cur != end) { + if (cur->first == key) { + return cur->second; + } + cur++; + } + // Throw an error if the item does not exist + throw std::runtime_error( + "Key does not exist"); + } + // EO at + + // Equality comparison operator + bool operator==(FlatMap rhs) const { + return items == rhs.items; + } + + // Returns an iterator for the begin or end position + const_iterator begin() const { return items.begin(); } + const_iterator end() const { return items.end(); } + // iterator begin() { return items.begin(); } + // iterator end() { return items.end(); } + + }; + // EO Class FlatMap + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +}; + +#endif diff --git a/src/fn_colors.cpp b/src/fn_colors.cpp index aab277e3c1..17d49b41e0 100644 --- a/src/fn_colors.cpp +++ b/src/fn_colors.cpp @@ -1,594 +1,1252 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +#include "fn_colors.hpp" #include -#include "ast.hpp" -#include "fn_utils.hpp" -#include "fn_colors.hpp" -#include "util.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" #include "util_string.hpp" namespace Sass { namespace Functions { - bool string_argument(AST_Node_Obj obj) { - String_Constant* s = Cast(obj); - if (s == nullptr) return false; - const sass::string& str = s->value(); - return starts_with(str, "calc(") || - starts_with(str, "var("); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - void hsla_alpha_percent_deprecation(const SourceSpan& pstate, const sass::string val) - { + // Import string utility functions + using namespace StringUtils; - sass::string msg("Passing a percentage as the alpha value to hsla() will be interpreted"); - sass::string tail("differently in future versions of Sass. For now, use " + val + " instead."); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - deprecated(msg, tail, false, pstate); + // Create typedef for value function callback + typedef Value* (*colFn)( + const sass::string& name, + const ValueVector& arguments, + const SourceSpan& pstate, + Logger& logger); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - Signature rgb_sig = "rgb($red, $green, $blue)"; - BUILT_IN(rgb) + // Returns whether [value] is an unquoted string + // that start with `var(` and contains `/`. + bool isVarSlash(Value* value) { - if ( - string_argument(env["$red"]) || - string_argument(env["$green"]) || - string_argument(env["$blue"]) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "rgb(" - + env["$red"]->to_string() - + ", " - + env["$green"]->to_string() - + ", " - + env["$blue"]->to_string() - + ")" - ); - } - - return SASS_MEMORY_NEW(Color_RGBA, - pstate, - COLOR_NUM("$red"), - COLOR_NUM("$green"), - COLOR_NUM("$blue")); + if (value == nullptr) return false; + const String* str = value->isaString(); + if (str == nullptr) return false; + if (str->hasQuotes()) return false; + return startsWith(str->value(), "var(") && + str->value().find('/') != NPOS; } + // EO isVarSlash - Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)"; - BUILT_IN(rgba_4) + // Returns whether [value] is an unquoted + // string that start with `var(`. + bool isVar(const Value* value) { - if ( - string_argument(env["$red"]) || - string_argument(env["$green"]) || - string_argument(env["$blue"]) || - string_argument(env["$alpha"]) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" - + env["$red"]->to_string() - + ", " - + env["$green"]->to_string() - + ", " - + env["$blue"]->to_string() - + ", " - + env["$alpha"]->to_string() - + ")" - ); - } - - return SASS_MEMORY_NEW(Color_RGBA, - pstate, - COLOR_NUM("$red"), - COLOR_NUM("$green"), - COLOR_NUM("$blue"), - ALPHA_NUM("$alpha")); + if (value == nullptr) return false; + const String* str = value->isaString(); + if (str == nullptr) return false; + if (str->hasQuotes()) return false; + return startsWith(str->value(), "var("); } + // EO isVar - Signature rgba_2_sig = "rgba($color, $alpha)"; - BUILT_IN(rgba_2) + // Returns whether [value] is an unquoted + // string that start either with `calc(`, + // "var(", "env(", "min(" or "max(". + bool isSpecialNumber(const Value* value) { - if ( - string_argument(env["$color"]) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" - + env["$color"]->to_string() - + ", " - + env["$alpha"]->to_string() - + ")" - ); - } - - Color_RGBA_Obj c_arg = ARG("$color", Color)->toRGBA(); - - if ( - string_argument(env["$alpha"]) - ) { - sass::ostream strm; - strm << "rgba(" - << (int)c_arg->r() << ", " - << (int)c_arg->g() << ", " - << (int)c_arg->b() << ", " - << env["$alpha"]->to_string() - << ")"; - return SASS_MEMORY_NEW(String_Constant, pstate, strm.str()); - } - - Color_RGBA_Obj new_c = SASS_MEMORY_COPY(c_arg); - new_c->a(ALPHA_NUM("$alpha")); - new_c->disp(""); - return new_c.detach(); + if (value == nullptr) return false; + const String* str = value->isaString(); + if (str == nullptr) return false; + if (str->hasQuotes()) return false; + if (str->value().size() < 6) return false; + return startsWith(str->value(), "calc(", 5) + || startsWith(str->value(), "var(", 4) + || startsWith(str->value(), "env(", 4) + || startsWith(str->value(), "min(", 4) + || startsWith(str->value(), "max(", 4); } + // EO isSpecialNumber - //////////////// - // RGB FUNCTIONS - //////////////// - - Signature red_sig = "red($color)"; - BUILT_IN(red) + // Implements regex check against /^[a-zA-Z]+\s*=/ + bool isMsFilterStart(const sass::string& text) { - Color_RGBA_Obj color = ARG("$color", Color)->toRGBA(); - return SASS_MEMORY_NEW(Number, pstate, color->r()); + auto it = text.begin(); + // The filter must start with alpha + if (!Character::isAlphabetic(*it)) return false; + while (it != text.end() && Character::isAlphabetic(*it)) ++it; + while (it != text.end() && Character::isWhitespace(*it)) ++it; + return it != text.end() && *it == '='; } + // EO isMsFilterStart - Signature green_sig = "green($color)"; - BUILT_IN(green) + // Helper function for debugging + // ToDo return EnvKey? + const sass::string& getColorArgName( + size_t idx, const sass::string& name) { - Color_RGBA_Obj color = ARG("$color", Color)->toRGBA(); - return SASS_MEMORY_NEW(Number, pstate, color->g()); + switch (idx) { + case 0: return name[0] == Character::$h ? Strings::hue : Strings::red; + case 1: return name[0] == Character::$h ? name[1] == Character::$s ? Strings::saturation : Strings::whiteness : Strings::green; + case 2: return name[0] == Character::$h ? name[1] == Character::$s ? Strings::lightness : Strings::blackness : Strings::blue; + default: throw std::runtime_error("Invalid input argument"); + } } - - Signature blue_sig = "blue($color)"; - BUILT_IN(blue) + // EO getColorArgName + + // Return value that will render as-is in css + String* getFunctionString( + const sass::string& name, + const SourceSpan& pstate, + const ValueVector& arguments = {}, + SassSeparator separator = SASS_COMMA) { - Color_RGBA_Obj color = ARG("$color", Color)->toRGBA(); - return SASS_MEMORY_NEW(Number, pstate, color->b()); + bool addComma = false; + sass::sstream fncall; + fncall << name << "("; + sass::string sep(" "); + if (separator == SASS_COMMA) sep = ", "; + for (Value* arg : arguments) { + if (addComma) fncall << sep; + fncall << arg->inspect(); + addComma = true; + } + fncall << ")"; + return SASS_MEMORY_NEW(String, + pstate, fncall.str()); } + // EO getFunctionString - Color_RGBA* colormix(Context& ctx, SourceSpan& pstate, Color* color1, Color* color2, double weight) { - Color_RGBA_Obj c1 = color1->toRGBA(); - Color_RGBA_Obj c2 = color2->toRGBA(); - double p = weight/100; - double w = 2*p - 1; - double a = c1->a() - c2->a(); - - double w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0; - double w2 = 1 - w1; - - return SASS_MEMORY_NEW(Color_RGBA, - pstate, - Sass::round(w1*c1->r() + w2*c2->r(), ctx.c_options.precision), - Sass::round(w1*c1->g() + w2*c2->g(), ctx.c_options.precision), - Sass::round(w1*c1->b() + w2*c2->b(), ctx.c_options.precision), - c1->a()*p + c2->a()*(1-p)); + Value* parseColorChannels( + const sass::string& name, + Value* channels, + const SourceSpan& pstate, + Compiler& compiler) + { + // Check for css var + if (isVar(channels)) { + return SASS_MEMORY_NEW( + String, pstate, name + "(" + + channels->inspect() + ")"); + } + // Check if argument is already a list + ListObj list = channels->isaList(); + // If not create one and wrap value in it + if (!list) { + list = SASS_MEMORY_NEW(List, + pstate, { channels }); + } + // Check for invalid input arguments + bool isBracketed = list->hasBrackets(); + bool isCommaSeparated = list->hasCommaSeparator(); + if (isCommaSeparated || isBracketed) { + sass::sstream msg; + msg << "$channels must be"; + if (isBracketed) msg << " an unbracketed"; + if (isCommaSeparated) { + msg << (isBracketed ? "," : " a"); + msg << " space-separated"; + } + msg << " list."; + compiler.addFinalStackTrace(list->pstate()); + throw Exception::RuntimeException(compiler, msg.str()); + } + // Check if we have too many arguments + if (list->size() > 3) { + compiler.addFinalStackTrace(list->pstate()); + throw Exception::TooManyArguments(compiler, list->size(), 3); + } + // Check for not enough arguments + if (list->size() < 3) { + // Check if we have any css vars + bool hasVar = false; + for (Value* item : list->elements()) { + if (isVar(item)) { + hasVar = true; + break; + } + } + // Return function as-is back to be rendered as css + if (hasVar || (!list->empty() && isVarSlash(list->last()))) { + return getFunctionString(name, pstate, list->elements(), list->separator()); + } + // Throw error for missing argument + throw Exception::MissingArgument(compiler, + getColorArgName(list->size(), name)); + } + // Check for the second argument + Number* secondNumber = list->get(2)->isaNumber(); + String* secondString = list->get(2)->isaString(); + if (secondNumber && secondNumber->hasAsSlash()) { + return SASS_MEMORY_NEW(List, pstate, { + list->get(0), list->get(1), + secondNumber->lhsAsSlash(), + secondNumber->rhsAsSlash() + }); + } + if (secondString && !secondString->hasQuotes() + && secondString->value().find('/') != NPOS) { + return getFunctionString(name, pstate, + list->elements(), list->separator()); + } + // Return arguments + return list.detach(); } - - Signature mix_sig = "mix($color1, $color2, $weight: 50%)"; - BUILT_IN(mix) + // EO parseColorChannels + + // Handle one argument function invocation + // Used by color functions rgb, hsl and hwb + Value* handleOneArgColorFn( + const sass::string& name, + Value* argument, + colFn function, + Compiler& compiler, + SourceSpan pstate) { - Color_Obj color1 = ARG("$color1", Color); - Color_Obj color2 = ARG("$color2", Color); - double weight = DARG_U_PRCT("$weight"); - return colormix(ctx, pstate, color1, color2, weight); - + // Parse the color channel arguments + ValueObj parsed = parseColorChannels( + name, argument, pstate, compiler); + // Return if it is a string + if (parsed->isaString()) { + return parsed.detach(); + } + // Execute function with list of arguments + if (const List* list = parsed->isaList()) { + return (*function)(name, list->elements(), pstate, compiler); + } + // Otherwise return + return argument; + // Not sure if we must stringify + // return SASS_MEMORY_NEW(String, + // pstate, argument->inspect()); } - - //////////////// - // HSL FUNCTIONS - //////////////// - - Signature hsl_sig = "hsl($hue, $saturation, $lightness)"; - BUILT_IN(hsl) + // EO handleOneArgColorFn + + /// Returns [color1] and [color2], mixed + // together and weighted by [weight]. + ColorRgba* mixColors( + const Color* color1, + const Color* color2, + const Number* weight, + const SourceSpan& pstate, + Logger& logger) { - if ( - string_argument(env["$hue"]) || - string_argument(env["$saturation"]) || - string_argument(env["$lightness"]) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "hsl(" - + env["$hue"]->to_string() - + ", " - + env["$saturation"]->to_string() - + ", " - + env["$lightness"]->to_string() - + ")" - ); - } - - return SASS_MEMORY_NEW(Color_HSLA, - pstate, - ARGVAL("$hue"), - ARGVAL("$saturation"), - ARGVAL("$lightness"), - 1.0); - + ColorRgbaObj lhs(color1->toRGBA()); + ColorRgbaObj rhs(color2->toRGBA()); + // This algorithm factors in both the user-provided weight (w) and the + // difference between the alpha values of the two colors (a) to decide how + // to perform the weighted average of the two RGB values. + // It works by first normalizing both parameters to be within [-1, 1], where + // 1 indicates "only use color1", -1 indicates "only use color2", and all + // values in between indicated a proportionately weighted average. + // Once we have the normalized variables w and a, we apply the formula + // (w + a)/(1 + w*a) to get the combined weight (in [-1, 1]) of color1. This + // formula has two especially nice properties: + // * When either w or a are -1 or 1, the combined weight is also that + // number (cases where w * a == -1 are undefined, and handled as a + // special case). + // * When a is 0, the combined weight is w, and vice versa. + // Finally, the weight of color1 is renormalized to be within [0, 1] and the + // weight of color2 is given by 1 minus the weight of color1. + double weightScale = weight->assertRange( + 0.0, 100.0, logger, "weight") / 100.0; + double normalizedWeight = weightScale * 2.0 - 1.0; + double alphaDistance = lhs->a() - color2->a(); + double combinedWeight1 = normalizedWeight * alphaDistance == -1 + ? normalizedWeight : (normalizedWeight + alphaDistance) / + (1.0 + normalizedWeight * alphaDistance); + double weight1 = (combinedWeight1 + 1.0) / 2.0; + double weight2 = 1.0 - weight1; + return SASS_MEMORY_NEW(ColorRgba, pstate, + fuzzyRound(lhs->r() * weight1 + rhs->r() * weight2, logger.epsilon), + fuzzyRound(lhs->g() * weight1 + rhs->g() * weight2, logger.epsilon), + fuzzyRound(lhs->b() * weight1 + rhs->b() * weight2, logger.epsilon), + lhs->a() * weightScale + rhs->a() * (1 - weightScale)); } + // EO mixColor - Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)"; - BUILT_IN(hsla) + double scaleValue( + double current, + double scale, + double max) { - if ( - string_argument(env["$hue"]) || - string_argument(env["$saturation"]) || - string_argument(env["$lightness"]) || - string_argument(env["$alpha"]) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "hsla(" - + env["$hue"]->to_string() - + ", " - + env["$saturation"]->to_string() - + ", " - + env["$lightness"]->to_string() - + ", " - + env["$alpha"]->to_string() - + ")" - ); - } - - Number* alpha = ARG("$alpha", Number); - if (alpha && alpha->unit() == "%") { - Number_Obj val = SASS_MEMORY_COPY(alpha); - val->numerators.clear(); // convert - val->value(val->value() / 100.0); - sass::string nr(val->to_string(ctx.c_options)); - hsla_alpha_percent_deprecation(pstate, nr); - } - - return SASS_MEMORY_NEW(Color_HSLA, - pstate, - ARGVAL("$hue"), - ARGVAL("$saturation"), - ARGVAL("$lightness"), - ARGVAL("$alpha")); - + return current + (scale > 0.0 ? max - current : current) * scale; } - ///////////////////////////////////////////////////////////////////////// - // Query functions - ///////////////////////////////////////////////////////////////////////// - - Signature hue_sig = "hue($color)"; - BUILT_IN(hue) + // Asserts that [number] is a percentage or has no units, and normalizes the + // value. If [number] has no units, its value is clamped to be greater than `0` + // or less than [max] and returned. If [number] is a percentage, it's scaled to + // be within `0` and [max]. Otherwise, this throws a [SassScriptException]. + // [name] is used to identify the argument in the error message. + double _percentageOrUnitless( + const Number* number, double max, + const sass::string& name, + Logger& traces) { - Color_HSLA_Obj col = ARG("$color", Color)->toHSLA(); - return SASS_MEMORY_NEW(Number, pstate, col->h(), "deg"); + double value = 0.0; + if (!number->hasUnits()) { + value = number->value(); + } + else if (number->hasUnit(Strings::percent)) { + value = max * number->value() / 100; + } + else { + traces.addFinalStackTrace(number->pstate()); + throw Exception::RuntimeException(traces, + name + ": Expected " + number->inspect() + + " to have no units or \"%\"."); + } + if (value < 0.0) return 0.0; + if (value > max) return max; + return value; } - Signature saturation_sig = "saturation($color)"; - BUILT_IN(saturation) + String* _functionRgbString(sass::string name, ColorRgba* color, Value* alpha, const SourceSpan& pstate) { - Color_HSLA_Obj col = ARG("$color", Color)->toHSLA(); - return SASS_MEMORY_NEW(Number, pstate, col->s(), "%"); + sass::sstream fncall; + fncall << name << "("; + fncall << color->r() << ", "; + fncall << color->g() << ", "; + fncall << color->b() << ", "; + fncall << alpha->inspect() << ")"; + return SASS_MEMORY_NEW(String, + pstate, fncall.str()); } - Signature lightness_sig = "lightness($color)"; - BUILT_IN(lightness) + Value* handleTwoArgRgb(sass::string name, ValueVector arguments, const SourceSpan& pstate, Logger& logger) { - Color_HSLA_Obj col = ARG("$color", Color)->toHSLA(); - return SASS_MEMORY_NEW(Number, pstate, col->l(), "%"); + // Check if any `calc()` or `var()` are passed + if (isVar(arguments[0])) { + return getFunctionString( + name, pstate, arguments); + } + else if (isVar(arguments[1])) { + if (const Color* first = arguments[0]->isaColor()) { + ColorRgbaObj rgba = first->toRGBA(); + return _functionRgbString(name, + rgba, arguments[1], pstate); + } + else { + return getFunctionString( + name, pstate, arguments); + } + } + else if (isSpecialNumber(arguments[1])) { + if (const Color* color = arguments[0]->assertColor(logger, Strings::color)) { + ColorRgbaObj rgba = color->toRGBA(); + return _functionRgbString(name, + rgba, arguments[1], pstate); + } + } + + const Color* color = arguments[0]->assertColor(logger, Strings::color); + const Number* alpha = arguments[1]->assertNumber(logger, Strings::alpha); + ColorObj copy = SASS_MEMORY_COPY(color); + copy->a(_percentageOrUnitless( + alpha, 1.0, "$alpha", logger)); + return copy.detach(); } ///////////////////////////////////////////////////////////////////////// - // HSL manipulation functions ///////////////////////////////////////////////////////////////////////// - Signature adjust_hue_sig = "adjust-hue($color, $degrees)"; - BUILT_IN(adjust_hue) - { - Color* col = ARG("$color", Color); - double degrees = ARGVAL("$degrees"); - Color_HSLA_Obj copy = col->copyAsHSLA(); - copy->h(absmod(copy->h() + degrees, 360.0)); - return copy.detach(); - } + namespace Colors { - Signature lighten_sig = "lighten($color, $amount)"; - BUILT_IN(lighten) - { - Color* col = ARG("$color", Color); - double amount = DARG_U_PRCT("$amount"); - Color_HSLA_Obj copy = col->copyAsHSLA(); - copy->l(clip(copy->l() + amount, 0.0, 100.0)); - return copy.detach(); + /*******************************************************************/ - } + BUILT_IN_FN(rgb4arg) + { + return rgbFn(Strings::rgb, + arguments, pstate, compiler); + } - Signature darken_sig = "darken($color, $amount)"; - BUILT_IN(darken) - { - Color* col = ARG("$color", Color); - double amount = DARG_U_PRCT("$amount"); - Color_HSLA_Obj copy = col->copyAsHSLA(); - copy->l(clip(copy->l() - amount, 0.0, 100.0)); - return copy.detach(); - } + BUILT_IN_FN(rgb3arg) + { + return rgbFn(Strings::rgb, + arguments, pstate, compiler); + } - Signature saturate_sig = "saturate($color, $amount: false)"; - BUILT_IN(saturate) - { - // CSS3 filter function overload: pass literal through directly - if (!Cast(env["$amount"])) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "saturate(" + env["$color"]->to_string(ctx.c_options) + ")"); + BUILT_IN_FN(rgb2arg) + { + return handleTwoArgRgb(Strings::rgb, + arguments, pstate, compiler); } - Color* col = ARG("$color", Color); - double amount = DARG_U_PRCT("$amount"); - Color_HSLA_Obj copy = col->copyAsHSLA(); - copy->s(clip(copy->s() + amount, 0.0, 100.0)); - return copy.detach(); - } + BUILT_IN_FN(rgb1arg) + { + return handleOneArgColorFn(Strings::rgb, + arguments[0], &rgbFn, compiler, pstate); + } - Signature desaturate_sig = "desaturate($color, $amount)"; - BUILT_IN(desaturate) - { - Color* col = ARG("$color", Color); - double amount = DARG_U_PRCT("$amount"); - Color_HSLA_Obj copy = col->copyAsHSLA(); - copy->s(clip(copy->s() - amount, 0.0, 100.0)); - return copy.detach(); - } + /*******************************************************************/ - Signature grayscale_sig = "grayscale($color)"; - BUILT_IN(grayscale) - { - // CSS3 filter function overload: pass literal through directly - Number* amount = Cast(env["$color"]); - if (amount) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "grayscale(" + amount->to_string(ctx.c_options) + ")"); + BUILT_IN_FN(rgba4arg) + { + return rgbFn(Strings::rgba, + arguments, pstate, compiler); } - Color* col = ARG("$color", Color); - Color_HSLA_Obj copy = col->copyAsHSLA(); - copy->s(0.0); // just reset saturation - return copy.detach(); - } + BUILT_IN_FN(rgba3arg) + { + return rgbFn(Strings::rgba, + arguments, pstate, compiler); + } - ///////////////////////////////////////////////////////////////////////// - // Misc manipulation functions - ///////////////////////////////////////////////////////////////////////// + BUILT_IN_FN(rgba2arg) + { + return handleTwoArgRgb(Strings::rgba, + arguments, pstate, compiler); + } - Signature complement_sig = "complement($color)"; - BUILT_IN(complement) - { - Color* col = ARG("$color", Color); - Color_HSLA_Obj copy = col->copyAsHSLA(); - copy->h(absmod(copy->h() - 180.0, 360.0)); - return copy.detach(); - } + BUILT_IN_FN(rgba1arg) + { + return handleOneArgColorFn(Strings::rgba, + arguments[0], &rgbFn, compiler, pstate); + } - Signature invert_sig = "invert($color, $weight: 100%)"; - BUILT_IN(invert) - { - // CSS3 filter function overload: pass literal through directly - Number* amount = Cast(env["$color"]); - double weight = DARG_U_PRCT("$weight"); - if (amount) { - // TODO: does not throw on 100% manually passed as value - if (weight < 100.0) { - error("Only one argument may be passed to the plain-CSS invert() function.", pstate, traces); - } - return SASS_MEMORY_NEW(String_Quoted, pstate, "invert(" + amount->to_string(ctx.c_options) + ")"); - } - - Color* col = ARG("$color", Color); - Color_RGBA_Obj inv = col->copyAsRGBA(); - inv->r(clip(255.0 - inv->r(), 0.0, 255.0)); - inv->g(clip(255.0 - inv->g(), 0.0, 255.0)); - inv->b(clip(255.0 - inv->b(), 0.0, 255.0)); - return colormix(ctx, pstate, inv, col, weight); - } + /*******************************************************************/ - ///////////////////////////////////////////////////////////////////////// - // Opacity functions - ///////////////////////////////////////////////////////////////////////// + BUILT_IN_FN(hsl4arg) + { + return hslFn(Strings::hsl, + arguments, pstate, compiler); + } - Signature alpha_sig = "alpha($color)"; - Signature opacity_sig = "opacity($color)"; - BUILT_IN(alpha) - { - String_Constant* ie_kwd = Cast(env["$color"]); - if (ie_kwd) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "alpha(" + ie_kwd->value() + ")"); + + BUILT_IN_FN(hsl3arg) + { + return hslFn(Strings::hsl, + arguments, pstate, compiler); } - // CSS3 filter function overload: pass literal through directly - Number* amount = Cast(env["$color"]); - if (amount) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "opacity(" + amount->to_string(ctx.c_options) + ")"); + BUILT_IN_FN(hsl2arg) + { + // hsl(123, var(--foo)) is valid CSS because --foo might be `10%, 20%` + // and functions are parsed after variable substitution. + if (isVar(arguments[0]) || isVar(arguments[1])) { + return getFunctionString(Strings::hsl, pstate, arguments); + } + // Otherwise throw error for missing argument + throw Exception::MissingArgument(compiler, Keys::lightness); } - return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->a()); - } + BUILT_IN_FN(hsl1arg) + { + return handleOneArgColorFn(Strings::hsl, + arguments[0], &hslFn, compiler, pstate); + } - Signature opacify_sig = "opacify($color, $amount)"; - Signature fade_in_sig = "fade-in($color, $amount)"; - BUILT_IN(opacify) - { - Color* col = ARG("$color", Color); - double amount = DARG_U_FACT("$amount"); - Color_Obj copy = SASS_MEMORY_COPY(col); - copy->a(clip(col->a() + amount, 0.0, 1.0)); - return copy.detach(); - } + /*******************************************************************/ - Signature transparentize_sig = "transparentize($color, $amount)"; - Signature fade_out_sig = "fade-out($color, $amount)"; - BUILT_IN(transparentize) - { - Color* col = ARG("$color", Color); - double amount = DARG_U_FACT("$amount"); - Color_Obj copy = SASS_MEMORY_COPY(col); - copy->a(std::max(col->a() - amount, 0.0)); - return copy.detach(); - } + BUILT_IN_FN(hsla4arg) + { + return hslFn(Strings::hsla, arguments, pstate, compiler); + } + + BUILT_IN_FN(hsla3arg) + { + return hslFn(Strings::hsla, arguments, pstate, compiler); + } - //////////////////////// - // OTHER COLOR FUNCTIONS - //////////////////////// + BUILT_IN_FN(hsla2arg) + { + // hsl(123, var(--foo)) is valid CSS because --foo might be `10%, 20%` + // and functions are parsed after variable substitution. + if (isVar(arguments[0]) || isVar(arguments[1])) { + return getFunctionString(Strings::hsla, pstate, arguments); + } + // Otherwise throw error for missing argument + throw Exception::MissingArgument(compiler, Keys::lightness); + } + + BUILT_IN_FN(hsla1arg) + { + return handleOneArgColorFn(Strings::hsla, + arguments[0], &hslFn, compiler, pstate); + } + + /*******************************************************************/ + + BUILT_IN_FN(hwb4arg) + { + return hwbFn(Strings::hwb, + arguments, pstate, compiler); + } + + + BUILT_IN_FN(hwb3arg) + { + return hwbFn(Strings::hwb, + arguments, pstate, compiler); + } + + BUILT_IN_FN(hwb2arg) + { + // hwb(123, var(--foo)) is valid CSS because --foo might be `10%, 20%` + // and functions are parsed after variable substitution. + if (isVar(arguments[0]) || isVar(arguments[1])) { + return getFunctionString(Strings::hwb, pstate, arguments); + } + // Otherwise throw error for missing argument + throw Exception::MissingArgument(compiler, Keys::lightness); + } + + BUILT_IN_FN(hwb1arg) + { + return handleOneArgColorFn(Strings::hwb, + arguments[0], &hwbFn, compiler, pstate); + } + + /*******************************************************************/ + + BUILT_IN_FN(hwba4arg) + { + return hwbFn(Strings::hwba, arguments, pstate, compiler); + } + + BUILT_IN_FN(hwba3arg) + { + return hwbFn(Strings::hwba, arguments, pstate, compiler); + } + + BUILT_IN_FN(hwba2arg) + { + // hwb(123, var(--foo)) is valid CSS because --foo might be `10%, 20%` + // and functions are parsed after variable substitution. + if (isVar(arguments[0]) || isVar(arguments[1])) { + return getFunctionString(Strings::hwba, pstate, arguments); + } + // Otherwise throw error for missing argument + throw Exception::MissingArgument(compiler, Keys::lightness); + } + + BUILT_IN_FN(hwba1arg) + { + return handleOneArgColorFn(Strings::hwba, + arguments[0], &hwbFn, compiler, pstate); + } + + /*******************************************************************/ + + BUILT_IN_FN(red) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorRgbaObj rgba(color->toRGBA()); // This might create a copy + return SASS_MEMORY_NEW(Number, pstate, Sass::round32(rgba->r())); + } + + BUILT_IN_FN(green) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorRgbaObj rgba(color->toRGBA()); // This might create a copy + return SASS_MEMORY_NEW(Number, pstate, Sass::round32(rgba->g())); + } + + BUILT_IN_FN(blue) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorRgbaObj rgba(color->toRGBA()); // This might create a copy + return SASS_MEMORY_NEW(Number, pstate, Sass::round32(rgba->b())); + } + + /*******************************************************************/ + + BUILT_IN_FN(invert) + { + if (arguments[0]->isaNumber()) { + // Allow only the value `100` or a percentage (unit == `% `) + const Number* weight = arguments[1]->assertNumber(compiler, Strings::weight); + if (weight->value() != 100 || !weight->hasUnit(Strings::percent)) { + throw Exception::RuntimeException(compiler, + "Only one argument may be passed " + "to the plain-CSS invert() function."); + } + // Return function string since first argument was a number + // Need to remove the weight argument as it has a default value + return getFunctionString(Strings::invert, pstate, { arguments[0] }); + } + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* weight = arguments[1]->assertNumber(compiler, Strings::weight); + ColorRgbaObj inverse(color->copyAsRGBA()); // Make a copy! + inverse->r(clamp(255.0 - inverse->r(), 0.0, 255.0)); + inverse->g(clamp(255.0 - inverse->g(), 0.0, 255.0)); + inverse->b(clamp(255.0 - inverse->b(), 0.0, 255.0)); + // Note: mixColors will create another unnecessary copy! + return mixColors(inverse, color, weight, pstate, compiler); + } + + /*******************************************************************/ + + BUILT_IN_FN(hue) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorHslaObj hsla(color->toHSLA()); // This probably creates a copy + return SASS_MEMORY_NEW(Number, pstate, hsla->h(), Strings::deg); + } + + BUILT_IN_FN(saturation) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorHslaObj hsla(color->toHSLA()); // This probably creates a copy + return SASS_MEMORY_NEW(Number, pstate, hsla->s(), Strings::percent); + } + + BUILT_IN_FN(lightness) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorHslaObj hsla(color->toHSLA()); // This probably creates a copy + return SASS_MEMORY_NEW(Number, pstate, hsla->l(), Strings::percent); + } + + BUILT_IN_FN(whiteness) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::whiteness); + ColorHwbaObj hwba(color->toHWBA()); // This probably creates a copy + return SASS_MEMORY_NEW(Number, pstate, hwba->w(), Strings::percent); + } + + BUILT_IN_FN(blackness) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::blackness); + ColorHwbaObj hwba(color->toHWBA()); // This probably creates a copy + return SASS_MEMORY_NEW(Number, pstate, hwba->b(), Strings::percent); + } + + /*******************************************************************/ + + BUILT_IN_FN(adjustHue) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* degrees = arguments[1]->assertNumber(compiler, Strings::degrees); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->h(absmod(copy->h() + degrees->value(), 360.0)); + return copy.detach(); + } + + BUILT_IN_FN(complement) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->h(absmod(copy->h() + 180.0, 360.0)); + return copy.detach(); + } + + /*******************************************************************/ + + BUILT_IN_FN(grayscale) + { + // Gracefully handle if number is passed + if (arguments[0]->isaNumber()) { + return getFunctionString( + Strings::grayscale, + pstate, arguments); + } + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->s(0.0); // Simply reset the saturation + return copy.detach(); // Return HSLA + } + + BUILT_IN_FN(lighten) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* amount = arguments[1]->assertNumber(compiler, Strings::amount); + double nr = amount->assertRange(0.0, 100.0, compiler, Strings::amount); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->l(clamp(copy->l() + nr, 0.0, 100.0)); + return copy.detach(); // Return HSLA + } + + BUILT_IN_FN(darken) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* amount = arguments[1]->assertNumber(compiler, Strings::amount); + double nr = amount->assertRange(0.0, 100.0, compiler, Strings::amount); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->l(clamp(copy->l() - nr, 0.0, 100.0)); + return copy.detach(); // Return HSLA + } + + /*******************************************************************/ + + BUILT_IN_FN(saturate2arg) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* amount = arguments[1]->assertNumber(compiler, Strings::amount); + double nr = amount->assertRange(0.0, 100.0, compiler, Strings::amount); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + if (copy->h() == 0 && nr > 0.0) copy->h(100.0); + copy->s(clamp(copy->s() + nr, 0.0, 100.0)); + return copy.detach(); // Return HSLA + } + + BUILT_IN_FN(saturate1arg) + { + arguments[0]->assertNumber(compiler, Strings::amount); + return getFunctionString(Strings::saturate, pstate, { arguments[0] }); + } + + BUILT_IN_FN(desaturate) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* amount = arguments[1]->assertNumber(compiler, Strings::amount); + double nr = amount->assertRange(0.0, 100.0, compiler, Strings::amount); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->s(clamp(copy->s() - nr, 0.0, 100.0)); + return copy.detach(); // Return HSLA + } + + /*******************************************************************/ + + BUILT_IN_FN(opacify) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* amount = arguments[1]->assertNumber(compiler, Strings::amount); + double nr = amount->assertRange(0.0, 1.0, compiler, Strings::amount); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->a(clamp(copy->a() + nr, 0.0, 1.0)); + return copy.detach(); // Return HSLA + } + + BUILT_IN_FN(transparentize) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* amount = arguments[1]->assertNumber(compiler, Strings::amount); + double nr = amount->assertRange(0.0, 1.0, compiler, Strings::amount); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->a(clamp(copy->a() - nr, 0.0, 1.0)); + return copy.detach(); // Return HSLA + } + + /*******************************************************************/ + + BUILT_IN_FN(alphaOne) + { + if (String * string = arguments[0]->isaString()) { + if (!string->hasQuotes() && isMsFilterStart(string->value())) { + return getFunctionString(Strings::alpha, pstate, arguments); + } + } + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + return SASS_MEMORY_NEW(Number, pstate, color->a()); + } + + BUILT_IN_FN(alphaAny) + { + size_t size = arguments[0]->lengthAsList(); + + if (size == 0) { + throw Exception::MissingArgument(compiler, Keys::color); + } + + bool isOnlyIeFilters = true; + for (Value* value : arguments[0]->iterator()) { + if (String* string = value->isaString()) { + if (!isMsFilterStart(string->value())) { + isOnlyIeFilters = false; + break; + } + } + else { + isOnlyIeFilters = false; + break; + } + } + if (isOnlyIeFilters) { + // Support the proprietary Microsoft alpha() function. + return getFunctionString(Strings::alpha, pstate, arguments); + } + compiler.addFinalStackTrace(arguments[0]->pstate()); + throw Exception::TooManyArguments(compiler, size, 1); + } + + + BUILT_IN_FN(opacity) + { + // Gracefully handle if number is passed + if (arguments[0]->isaNumber()) { + return getFunctionString("opacity", + pstate, arguments); + } + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + return SASS_MEMORY_NEW(Number, pstate, color->a()); + } + + + BUILT_IN_FN(ieHexStr) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorRgbaObj rgba = color->toRGBA(); // This might create a copy + // clamp should not be needed here + double r = clamp(rgba->r(), 0.0, 255.0); + double g = clamp(rgba->g(), 0.0, 255.0); + double b = clamp(rgba->b(), 0.0, 255.0); + double a = clamp(rgba->a(), 0.0, 1.0) * 255.0; + sass::sstream ss; + ss << '#' << std::setw(2) << std::setfill('0') << std::uppercase; + ss << std::hex << std::setw(2) << fuzzyRound(a, compiler.epsilon); + ss << std::hex << std::setw(2) << fuzzyRound(r, compiler.epsilon); + ss << std::hex << std::setw(2) << fuzzyRound(g, compiler.epsilon); + ss << std::hex << std::setw(2) << fuzzyRound(b, compiler.epsilon); + return SASS_MEMORY_NEW(String, pstate, ss.str()); + } + + Number* getKwdArg(ValueFlatMap& keywords, const EnvKey& name, Logger& logger) + { + EnvKey variable(name.norm()); + auto kv = keywords.find(variable); + // Return null since args are optional + if (kv == keywords.end()) return nullptr; + // Get the number object from found keyword + Number* num = kv->second->assertNumber(logger, name.orig()); + // Only consume keyword once + keywords.erase(kv); + // Return the number + return num; + } + + BUILT_IN_FN(adjust) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ArgumentList* argumentList = arguments[1] + ->assertArgumentList(compiler, "kwargs"); + if (!argumentList->empty()) { + SourceSpan span(color->pstate()); + callStackFrame frame(compiler, BackTrace( + span, Strings::colorAdjust)); + throw Exception::RuntimeException(compiler, + "Only one positional argument is allowed. All " + "other arguments must be passed by name."); + } + + // ToDo: solve without erase ... + ValueFlatMap& keywords(argumentList->keywords()); + + Number* nr_r = getKwdArg(keywords, Keys::red, compiler); + Number* nr_g = getKwdArg(keywords, Keys::green, compiler); + Number* nr_b = getKwdArg(keywords, Keys::blue, compiler); + Number* nr_h = getKwdArg(keywords, Keys::hue, compiler); + Number* nr_s = getKwdArg(keywords, Keys::saturation, compiler); + Number* nr_l = getKwdArg(keywords, Keys::lightness, compiler); + Number* nr_a = getKwdArg(keywords, Keys::alpha, compiler); + Number* nr_wn = getKwdArg(keywords, Keys::whiteness, compiler); + Number* nr_bn = getKwdArg(keywords, Keys::blackness, compiler); + + double r = nr_r ? nr_r->assertRange(-255.0, 255.0, compiler, Strings::red) : 0.0; + double g = nr_g ? nr_g->assertRange(-255.0, 255.0, compiler, Strings::green) : 0.0; + double b = nr_b ? nr_b->assertRange(-255.0, 255.0, compiler, Strings::blue) : 0.0; + double s = nr_s ? nr_s->assertRange(-100.0, 100.0, compiler, Strings::saturation) : 0.0; + double l = nr_l ? nr_l->assertRange(-100.0, 100.0, compiler, Strings::lightness) : 0.0; + + double wn = nr_wn ? nr_wn->assertHasUnits(compiler, Strings::percent, Strings::whiteness)->assertRange(-100.0, 100.0, compiler, Strings::whiteness) : 0.0; + double bn = nr_bn ? nr_bn->assertHasUnits(compiler, Strings::percent, Strings::blackness)->assertRange(-100.0, 100.0, compiler, Strings::blackness) : 0.0; + + double a = nr_a ? nr_a->assertRange(-1.0, 1.0, compiler, Strings::alpha) : 0.0; + + double h = nr_h ? nr_h->value() : 0.0; // Hue is a very special case + + if (!keywords.empty()) { + throw Exception::UnknownNamedArgument(compiler, keywords); + } + + bool hasRgb = nr_r || nr_g || nr_b; + bool hasHsl = nr_s || nr_l; + bool hasHwb = nr_wn || nr_bn; + bool hasHue = nr_h; + + if (hasRgb && hasHsl && hasHwb) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL", "HWB" }); + else if (hasRgb && hasHue) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL/HWB" }); + else if (hasRgb && hasHsl) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL" }); + else if (hasRgb && hasHwb) throw Exception::MixedParamGroups(compiler, "RGB", { "HWB" }); + else if (hasHsl && hasHwb) throw Exception::MixedParamGroups(compiler, "HSL", { "HWB" }); + else if (hasHwb && hasHsl) throw Exception::MixedParamGroups(compiler, "HSL", { "HWB" }); + + if (hasRgb) { + ColorRgbaObj rgba = color->copyAsRGBA(); + if (nr_r) rgba->r(clamp(rgba->r() + r, 0.0, 255.0)); + if (nr_g) rgba->g(clamp(rgba->g() + g, 0.0, 255.0)); + if (nr_b) rgba->b(clamp(rgba->b() + b, 0.0, 255.0)); + if (nr_a) rgba->a(clamp(rgba->a() + a, 0.0, 1.0)); + return rgba.detach(); + } + else if (hasHsl) { + ColorHslaObj hsla = color->copyAsHSLA(); + if (nr_h) hsla->h(absmod(hsla->h() + h, 360.0)); + if (nr_s) hsla->s(clamp(hsla->s() + s, 0.0, 100.0)); + if (nr_l) hsla->l(clamp(hsla->l() + l, 0.0, 100.0)); + if (nr_a) hsla->a(clamp(hsla->a() + a, 0.0, 1.0)); + return hsla.detach(); + } else if (hasHwb || nr_h) { // hue can be shared! + ColorHwbaObj hwba = color->copyAsHWBA(); + if (nr_h) hwba->h(absmod(hwba->h() + h, 360.0)); + if (nr_wn) hwba->w(clamp(hwba->w() + wn, 0.0, 100.0)); + if (nr_bn) hwba->b(clamp(hwba->b() + bn, 0.0, 100.0)); + if (nr_a) hwba->a(clamp(hwba->a() + a, 0.0, 1.0)); + return hwba.detach(); + } + else if (nr_a) { + ColorObj copy = SASS_MEMORY_COPY(color); + if (nr_a) copy->a(clamp(copy->a() + a, 0.0, 1.0)); + return copy.detach(); + } + return arguments[0]; + } + + BUILT_IN_FN(change) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ArgumentList* argumentList = arguments[1] + ->assertArgumentList(compiler, "kwargs"); + if (!argumentList->empty()) { + SourceSpan span(color->pstate()); + callStackFrame frame(compiler, BackTrace( + span, Strings::colorChange)); + throw Exception::RuntimeException(compiler, + "Only one positional argument is allowed. All " + "other arguments must be passed by name."); + } + + // ToDo: solve without erase ... + ValueFlatMap& keywords(argumentList->keywords()); + + Number* nr_r = getKwdArg(keywords, Keys::red, compiler); + Number* nr_g = getKwdArg(keywords, Keys::green, compiler); + Number* nr_b = getKwdArg(keywords, Keys::blue, compiler); + Number* nr_h = getKwdArg(keywords, Keys::hue, compiler); + Number* nr_s = getKwdArg(keywords, Keys::saturation, compiler); + Number* nr_l = getKwdArg(keywords, Keys::lightness, compiler); + Number* nr_a = getKwdArg(keywords, Keys::alpha, compiler); + Number* nr_wn = getKwdArg(keywords, Keys::whiteness, compiler); + Number* nr_bn = getKwdArg(keywords, Keys::blackness, compiler); + + double r = nr_r ? nr_r->assertRange(0.0, 255.0, compiler, Strings::red) : 0.0; + double g = nr_g ? nr_g->assertRange(0.0, 255.0, compiler, Strings::green) : 0.0; + double b = nr_b ? nr_b->assertRange(0.0, 255.0, compiler, Strings::blue) : 0.0; + double s = nr_s ? nr_s->assertRange(0.0, 100.0, compiler, Strings::saturation) : 0.0; + double l = nr_l ? nr_l->assertRange(0.0, 100.0, compiler, Strings::lightness) : 0.0; + double a = nr_a ? nr_a->assertRange(0.0, 1.0, compiler, Strings::alpha) : 0.0; + double wn = nr_wn ? nr_wn->assertHasUnits(compiler, Strings::percent, Strings::whiteness)->assertRange(0.0, 100.0, compiler, Strings::whiteness) : 0.0; + double bn = nr_bn ? nr_bn->assertHasUnits(compiler, Strings::percent, Strings::blackness)->assertRange( 0.0, 100.0, compiler, Strings::blackness) : 0.0; + double h = nr_h ? nr_h->value() : 0.0; // Hue is a very special case + + if (!keywords.empty()) { + throw Exception::UnknownNamedArgument(compiler, keywords); + } + + bool hasRgb = nr_r || nr_g || nr_b; + bool hasHsl = nr_s || nr_l; + bool hasHwb = nr_wn || nr_bn; + bool hasHue = nr_h; + + if (hasRgb && hasHsl && hasHwb) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL", "HWB" }); + else if (hasRgb && hasHue) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL/HWB" }); + else if (hasRgb && hasHsl) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL" }); + else if (hasRgb && hasHwb) throw Exception::MixedParamGroups(compiler, "RGB", { "HWB" }); + else if (hasHsl && hasHwb) throw Exception::MixedParamGroups(compiler, "HSL", { "HWB" }); + else if (hasHwb && hasHsl) throw Exception::MixedParamGroups(compiler, "HSL", { "HWB" }); + + if (hasRgb) { + ColorRgbaObj rgba = color->copyAsRGBA(); + if (nr_r) rgba->r(clamp(r, 0.0, 255.0)); + if (nr_g) rgba->g(clamp(g, 0.0, 255.0)); + if (nr_b) rgba->b(clamp(b, 0.0, 255.0)); + if (nr_a) rgba->a(clamp(a, 0.0, 1.0)); + return rgba.detach(); + } + else if (hasHsl) { + ColorHslaObj hsla = color->copyAsHSLA(); + if (nr_h) hsla->h(absmod(h, 360.0)); + if (nr_s) hsla->s(clamp(s, 0.0, 100.0)); + if (nr_l) hsla->l(clamp(l, 0.0, 100.0)); + if (nr_a) hsla->a(clamp(a, 0.0, 1.0)); + return hsla.detach(); + } + else if (hasHwb || nr_h) { // hue can be shared! + ColorHwbaObj hwba = color->copyAsHWBA(); + if (nr_h) hwba->h(absmod(h, 360.0)); + if (nr_wn) hwba->w(clamp(wn, 0.0, 100.0)); + if (nr_bn) hwba->b(clamp(bn, 0.0, 100.0)); + if (nr_a) hwba->a(clamp(a, 0.0, 1.0)); + return hwba.detach(); + } + else if (nr_a) { + ColorObj copy = SASS_MEMORY_COPY(color); + if (nr_a) copy->a(clamp(a, 0.0, 1.0)); + return copy.detach(); + } + return arguments[0]; + } + + BUILT_IN_FN(scale) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ArgumentList* argumentList = arguments[1] + ->assertArgumentList(compiler, "kwargs"); + if (!argumentList->empty()) { + SourceSpan span(color->pstate()); + callStackFrame frame(compiler, BackTrace( + span, Strings::scaleColor)); + throw Exception::RuntimeException(compiler, + "Only one positional argument is allowed. All " + "other arguments must be passed by name."); + } + + // ToDo: solve without erase ... + ValueFlatMap& keywords(argumentList->keywords()); + + Number* nr_r = getKwdArg(keywords, Keys::red, compiler); + Number* nr_g = getKwdArg(keywords, Keys::green, compiler); + Number* nr_b = getKwdArg(keywords, Keys::blue, compiler); + Number* nr_s = getKwdArg(keywords, Keys::saturation, compiler); + Number* nr_l = getKwdArg(keywords, Keys::lightness, compiler); + Number* nr_wn = getKwdArg(keywords, Keys::whiteness, compiler); + Number* nr_bn = getKwdArg(keywords, Keys::blackness, compiler); + Number* nr_a = getKwdArg(keywords, Keys::alpha, compiler); + + double r = nr_r ? nr_r->assertHasUnits(compiler, Strings::percent, Strings::red)->assertRange(-100.0, 100.0, compiler, Strings::red) / 100.0 : 0.0; + double g = nr_g ? nr_g->assertHasUnits(compiler, Strings::percent, Strings::green)->assertRange(-100.0, 100.0, compiler, Strings::green) / 100.0 : 0.0; + double b = nr_b ? nr_b->assertHasUnits(compiler, Strings::percent, Strings::blue)->assertRange(-100.0, 100.0, compiler, Strings::blue) / 100.0 : 0.0; + double s = nr_s ? nr_s->assertHasUnits(compiler, Strings::percent, Strings::saturation)->assertRange(-100.0, 100.0, compiler, Strings::saturation) / 100.0 : 0.0; + double l = nr_l ? nr_l->assertHasUnits(compiler, Strings::percent, Strings::lightness)->assertRange(-100.0, 100.0, compiler, Strings::lightness) / 100.0 : 0.0; + double wn = nr_wn ? nr_wn->assertHasUnits(compiler, Strings::percent, Strings::whiteness)->assertRange(-100.0, 100.0, compiler, Strings::whiteness) / 100.0 : 0.0; + double bn = nr_bn ? nr_bn->assertHasUnits(compiler, Strings::percent, Strings::blackness)->assertRange(-100.0, 100.0, compiler, Strings::blackness) / 100.0 : 0.0; + double a = nr_a ? nr_a->assertHasUnits(compiler, Strings::percent, Strings::alpha)->assertRange(-100.0, 100.0, compiler, Strings::alpha) / 100.0 : 0.0; + + if (!keywords.empty()) { + throw Exception::UnknownNamedArgument(compiler, keywords); + } + + bool hasRgb = nr_r || nr_g || nr_b; + bool hasHsl = nr_s || nr_l; + bool hasHwb = nr_wn || nr_bn; + + if (hasRgb && hasHsl && hasHwb) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL", "HWB" }); + else if (hasRgb && hasHsl) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL" }); + else if (hasRgb && hasHwb) throw Exception::MixedParamGroups(compiler, "RGB", { "HWB" }); + else if (hasHsl && hasHwb) throw Exception::MixedParamGroups(compiler, "HSL", { "HWB" }); + else if (hasHwb && hasHsl) throw Exception::MixedParamGroups(compiler, "HSL", { "HWB" }); + + if (hasRgb) { + ColorRgbaObj rgba = color->copyAsRGBA(); + if (nr_r) rgba->r(scaleValue(rgba->r(), r, 255.0)); + if (nr_g) rgba->g(scaleValue(rgba->g(), g, 255.0)); + if (nr_b) rgba->b(scaleValue(rgba->b(), b, 255.0)); + if (nr_a) rgba->a(scaleValue(rgba->a(), a, 1.0)); + return rgba.detach(); + } + else if (hasHsl) { + ColorHslaObj hsla = color->copyAsHSLA(); + if (nr_s) hsla->s(scaleValue(hsla->s(), s, 100.0)); + if (nr_l) hsla->l(scaleValue(hsla->l(), l, 100.0)); + if (nr_a) hsla->a(scaleValue(hsla->a(), a, 1.0)); + return hsla.detach(); + } + else if (hasHwb) { // hue can be shared! + ColorHwbaObj hwba = color->copyAsHWBA(); + if (nr_wn) hwba->w(scaleValue(hwba->w(), wn, 100.0)); + if (nr_bn) hwba->b(scaleValue(hwba->b(), bn, 100.0)); + if (nr_a) hwba->a(scaleValue(hwba->a(), a, 1.0)); + return hwba.detach(); + } + else if (nr_a) { + ColorObj copy = SASS_MEMORY_COPY(color); + if (nr_a) copy->a(scaleValue(copy->a(), a, 1.0)); + return copy.detach(); + } + return arguments[0]; + } + + BUILT_IN_FN(mix) + { + const Color* color1 = arguments[0]->assertColor(compiler, "color1"); + const Color* color2 = arguments[1]->assertColor(compiler, "color2"); + const Number* weight = arguments[2]->assertNumber(compiler, "weight"); + return mixColors(color1, color2, weight, pstate, compiler); + } + + void registerFunctions(Compiler& ctx) + { + + ctx.registerBuiltInOverloadFns("rgb", { + std::make_pair("$red, $green, $blue, $alpha", rgb4arg), + std::make_pair("$red, $green, $blue", rgb3arg), + std::make_pair("$color, $alpha", rgb2arg), + std::make_pair("$channels", rgb1arg), + }); + ctx.registerBuiltInOverloadFns("rgba", { + std::make_pair("$red, $green, $blue, $alpha", rgba4arg), + std::make_pair("$red, $green, $blue", rgba3arg), + std::make_pair("$color, $alpha", rgba2arg), + std::make_pair("$channels", rgba1arg), + }); + ctx.registerBuiltInOverloadFns("hsl", { + std::make_pair("$hue, $saturation, $lightness, $alpha", hsl4arg), + std::make_pair("$hue, $saturation, $lightness", hsl3arg), + std::make_pair("$color, $alpha", hsl2arg), + std::make_pair("$channels", hsl1arg), + }); + ctx.registerBuiltInOverloadFns("hsla", { + std::make_pair("$hue, $saturation, $lightness, $alpha", hsla4arg), + std::make_pair("$hue, $saturation, $lightness", hsla3arg), + std::make_pair("$color, $alpha", hsla2arg), + std::make_pair("$channels", hsla1arg), + }); + ctx.registerBuiltInOverloadFns("hwb", { + std::make_pair("$hue, $whiteness, $blackness, $alpha", hwb4arg), + std::make_pair("$hue, $whiteness, $blackness", hwb3arg), + std::make_pair("$color, $alpha", hwb2arg), + std::make_pair("$channels", hwb1arg), + }); + ctx.registerBuiltInOverloadFns("hwba", { + std::make_pair("$hue, $whiteness, $blackness, $alpha", hwba4arg), + std::make_pair("$hue, $whiteness, $blackness", hwba3arg), + std::make_pair("$color, $alpha", hwba2arg), + std::make_pair("$channels", hwba1arg), + }); + + ctx.registerBuiltInFunction("red", "$color", red); + ctx.registerBuiltInFunction("green", "$color", green); + ctx.registerBuiltInFunction("blue", "$color", blue); + ctx.registerBuiltInFunction("hue", "$color", hue); + ctx.registerBuiltInFunction("lightness", "$color", lightness); + ctx.registerBuiltInFunction("saturation", "$color", saturation); + //ctx.registerBuiltInFunction("blackness", "$color", blackness); + //ctx.registerBuiltInFunction("whiteness", "$color", whiteness); + ctx.registerBuiltInFunction("invert", "$color, $weight: 100%", invert); + ctx.registerBuiltInFunction("grayscale", "$color", grayscale); + ctx.registerBuiltInFunction("complement", "$color", complement); + ctx.registerBuiltInFunction("lighten", "$color, $amount", lighten); + ctx.registerBuiltInFunction("darken", "$color, $amount", darken); + ctx.registerBuiltInFunction("desaturate", "$color, $amount", desaturate); + ctx.registerBuiltInOverloadFns("saturate", { + std::make_pair("$amount", saturate1arg), + std::make_pair("$color, $amount", saturate2arg), + }); + + ctx.registerBuiltInFunction("adjust-hue", "$color, $degrees", adjustHue); + ctx.registerBuiltInFunction("adjust-color", "$color, $kwargs...", adjust); + ctx.registerBuiltInFunction("change-color", "$color, $kwargs...", change); + ctx.registerBuiltInFunction("scale-color", "$color, $kwargs...", scale); + ctx.registerBuiltInFunction("mix", "$color1, $color2, $weight: 50%", mix); + + ctx.registerBuiltInFunction("opacify", "$color, $amount", opacify); + ctx.registerBuiltInFunction("fade-in", "$color, $amount", opacify); + ctx.registerBuiltInFunction("fade-out", "$color, $amount", transparentize); + ctx.registerBuiltInFunction("transparentize", "$color, $amount", transparentize); + ctx.registerBuiltInFunction("ie-hex-str", "$color", ieHexStr); + ctx.registerBuiltInOverloadFns("alpha", { + std::make_pair("$color", alphaOne), + std::make_pair("$args...", alphaAny), + }); + ctx.registerBuiltInFunction("opacity", "$color", opacity); + + } - Signature adjust_color_sig = "adjust-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; - BUILT_IN(adjust_color) - { - Color* col = ARG("$color", Color); - Number* r = Cast(env["$red"]); - Number* g = Cast(env["$green"]); - Number* b = Cast(env["$blue"]); - Number* h = Cast(env["$hue"]); - Number* s = Cast(env["$saturation"]); - Number* l = Cast(env["$lightness"]); - Number* a = Cast(env["$alpha"]); - - bool rgb = r || g || b; - bool hsl = h || s || l; - - if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate, traces); - } - else if (rgb) { - Color_RGBA_Obj c = col->copyAsRGBA(); - if (r) c->r(c->r() + DARG_R_BYTE("$red")); - if (g) c->g(c->g() + DARG_R_BYTE("$green")); - if (b) c->b(c->b() + DARG_R_BYTE("$blue")); - if (a) c->a(c->a() + DARG_R_FACT("$alpha")); - return c.detach(); - } - else if (hsl) { - Color_HSLA_Obj c = col->copyAsHSLA(); - if (h) c->h(c->h() + absmod(h->value(), 360.0)); - if (s) c->s(c->s() + DARG_R_PRCT("$saturation")); - if (l) c->l(c->l() + DARG_R_PRCT("$lightness")); - if (a) c->a(c->a() + DARG_R_FACT("$alpha")); - return c.detach(); - } - else if (a) { - Color_Obj c = SASS_MEMORY_COPY(col); - c->a(c->a() + DARG_R_FACT("$alpha")); - c->a(clip(c->a(), 0.0, 1.0)); - return c.detach(); - } - error("not enough arguments for `adjust-color'", pstate, traces); - // unreachable - return col; } - Signature scale_color_sig = "scale-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; - BUILT_IN(scale_color) + Value* rgbFn(const sass::string& name, const ValueVector& arguments, const SourceSpan& pstate, Logger& logger) { - Color* col = ARG("$color", Color); - Number* r = Cast(env["$red"]); - Number* g = Cast(env["$green"]); - Number* b = Cast(env["$blue"]); - Number* h = Cast(env["$hue"]); - Number* s = Cast(env["$saturation"]); - Number* l = Cast(env["$lightness"]); - Number* a = Cast(env["$alpha"]); - - bool rgb = r || g || b; - bool hsl = h || s || l; - - if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate, traces); - } - else if (rgb) { - Color_RGBA_Obj c = col->copyAsRGBA(); - double rscale = (r ? DARG_R_PRCT("$red") : 0.0) / 100.0; - double gscale = (g ? DARG_R_PRCT("$green") : 0.0) / 100.0; - double bscale = (b ? DARG_R_PRCT("$blue") : 0.0) / 100.0; - double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; - if (rscale) c->r(c->r() + rscale * (rscale > 0.0 ? 255.0 - c->r() : c->r())); - if (gscale) c->g(c->g() + gscale * (gscale > 0.0 ? 255.0 - c->g() : c->g())); - if (bscale) c->b(c->b() + bscale * (bscale > 0.0 ? 255.0 - c->b() : c->b())); - if (ascale) c->a(c->a() + ascale * (ascale > 0.0 ? 1.0 - c->a() : c->a())); - return c.detach(); - } - else if (hsl) { - Color_HSLA_Obj c = col->copyAsHSLA(); - double hscale = (h ? DARG_R_PRCT("$hue") : 0.0) / 100.0; - double sscale = (s ? DARG_R_PRCT("$saturation") : 0.0) / 100.0; - double lscale = (l ? DARG_R_PRCT("$lightness") : 0.0) / 100.0; - double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; - if (hscale) c->h(c->h() + hscale * (hscale > 0.0 ? 360.0 - c->h() : c->h())); - if (sscale) c->s(c->s() + sscale * (sscale > 0.0 ? 100.0 - c->s() : c->s())); - if (lscale) c->l(c->l() + lscale * (lscale > 0.0 ? 100.0 - c->l() : c->l())); - if (ascale) c->a(c->a() + ascale * (ascale > 0.0 ? 1.0 - c->a() : c->a())); - return c.detach(); - } - else if (a) { - Color_Obj c = SASS_MEMORY_COPY(col); - double ascale = DARG_R_PRCT("$alpha") / 100.0; - c->a(c->a() + ascale * (ascale > 0.0 ? 1.0 - c->a() : c->a())); - c->a(clip(c->a(), 0.0, 1.0)); - return c.detach(); - } - error("not enough arguments for `scale-color'", pstate, traces); - // unreachable - return col; + Value* _r = arguments[0]; + Value* _g = arguments[1]; + Value* _b = arguments[2]; + Value* _a = nullptr; + if (arguments.size() > 3) { + _a = arguments[3]; + } + // Check if any `calc()` or `var()` are passed + if (isSpecialNumber(_r) || isSpecialNumber(_g) || isSpecialNumber(_b) || isSpecialNumber(_a)) { + sass::sstream fncall; + fncall << name << "("; + fncall << _r->inspect() << ", "; + fncall << _g->inspect() << ", "; + fncall << _b->inspect(); + if (_a) { fncall << ", " << _a->inspect(); } + fncall << ")"; + return SASS_MEMORY_NEW(String, pstate, fncall.str()); + } + + Number* r = _r->assertNumber(logger, Strings::red); + Number* g = _g->assertNumber(logger, Strings::green); + Number* b = _b->assertNumber(logger, Strings::blue); + Number* a = _a ? _a->assertNumber(logger, Strings::alpha) : nullptr; + + return SASS_MEMORY_NEW(ColorRgba, pstate, + fuzzyRound(_percentageOrUnitless(r, 255, "$red", logger), logger.epsilon), + fuzzyRound(_percentageOrUnitless(g, 255, "$green", logger), logger.epsilon), + fuzzyRound(_percentageOrUnitless(b, 255, "$blue", logger), logger.epsilon), + _a ? _percentageOrUnitless(a, 1.0, "$alpha", logger) : 1.0); + } - Signature change_color_sig = "change-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; - BUILT_IN(change_color) + Value* hwbFn(const sass::string& name, const ValueVector& arguments, const SourceSpan& pstate, Logger& logger) { - Color* col = ARG("$color", Color); - Number* r = Cast(env["$red"]); - Number* g = Cast(env["$green"]); - Number* b = Cast(env["$blue"]); - Number* h = Cast(env["$hue"]); - Number* s = Cast(env["$saturation"]); - Number* l = Cast(env["$lightness"]); - Number* a = Cast(env["$alpha"]); - - bool rgb = r || g || b; - bool hsl = h || s || l; - - if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `change-color'", pstate, traces); - } - else if (rgb) { - Color_RGBA_Obj c = col->copyAsRGBA(); - if (r) c->r(DARG_U_BYTE("$red")); - if (g) c->g(DARG_U_BYTE("$green")); - if (b) c->b(DARG_U_BYTE("$blue")); - if (a) c->a(DARG_U_FACT("$alpha")); - return c.detach(); - } - else if (hsl) { - Color_HSLA_Obj c = col->copyAsHSLA(); - if (h) c->h(absmod(h->value(), 360.0)); - if (s) c->s(DARG_U_PRCT("$saturation")); - if (l) c->l(DARG_U_PRCT("$lightness")); - if (a) c->a(DARG_U_FACT("$alpha")); - return c.detach(); - } - else if (a) { - Color_Obj c = SASS_MEMORY_COPY(col); - c->a(clip(DARG_U_FACT("$alpha"), 0.0, 1.0)); - return c.detach(); - } - error("not enough arguments for `change-color'", pstate, traces); - // unreachable - return col; + Value* _h = arguments[0]; + Value* _w = arguments[1]; + Value* _b = arguments[2]; + Value* _a = nullptr; + if (arguments.size() > 3) { + _a = arguments[3]; + } + // Check if any `calc()` or `var()` are passed + if (isSpecialNumber(_h) || isSpecialNumber(_w) || isSpecialNumber(_b) || isSpecialNumber(_a)) { + sass::sstream fncall; + fncall << name << "("; + fncall << _h->inspect() << ", "; + fncall << _w->inspect() << ", "; + fncall << _b->inspect(); + if (_a) { fncall << ", " << _a->inspect(); } + fncall << ")"; + return SASS_MEMORY_NEW(String, pstate, fncall.str()); + } + + Number* h = _h->assertNumber(logger, Strings::hue); + Number* w = _w->assertNumber(logger, Strings::whiteness); + Number* b = _b->assertNumber(logger, Strings::blackness); + Number* a = _a ? _a->assertNumber(logger, Strings::alpha) : nullptr; + + return SASS_MEMORY_NEW(ColorHwba, pstate, + h->value(), + clamp(w->value(), 0.0, 100.0), + clamp(b->value(), 0.0, 100.0), + _a ? _percentageOrUnitless(a, 1.0, "$alpha", logger) : 1.0); + } - Signature ie_hex_str_sig = "ie-hex-str($color)"; - BUILT_IN(ie_hex_str) + Value* hslFn(const sass::string& name, const ValueVector& arguments, const SourceSpan& pstate, Logger& logger) { - Color* col = ARG("$color", Color); - Color_RGBA_Obj c = col->toRGBA(); - double r = clip(c->r(), 0.0, 255.0); - double g = clip(c->g(), 0.0, 255.0); - double b = clip(c->b(), 0.0, 255.0); - double a = clip(c->a(), 0.0, 1.0) * 255.0; - - sass::ostream ss; - ss << '#' << std::setw(2) << std::setfill('0'); - ss << std::hex << std::setw(2) << static_cast(Sass::round(a, ctx.c_options.precision)); - ss << std::hex << std::setw(2) << static_cast(Sass::round(r, ctx.c_options.precision)); - ss << std::hex << std::setw(2) << static_cast(Sass::round(g, ctx.c_options.precision)); - ss << std::hex << std::setw(2) << static_cast(Sass::round(b, ctx.c_options.precision)); - - sass::string result = ss.str(); - Util::ascii_str_toupper(&result); - return SASS_MEMORY_NEW(String_Quoted, pstate, result); + Value* _h = arguments[0]; + Value* _s = arguments[1]; + Value* _l = arguments[2]; + Value* _a = nullptr; + if (arguments.size() > 3) { + _a = arguments[3]; + } + // Check if any `calc()` or `var()` are passed + if (isSpecialNumber(_h) || isSpecialNumber(_s) || isSpecialNumber(_l) || isSpecialNumber(_a)) { + sass::sstream fncall; + fncall << name << "("; + fncall << _h->inspect() << ", "; + fncall << _s->inspect() << ", "; + fncall << _l->inspect(); + if (_a) { fncall << ", " << _a->inspect(); } + fncall << ")"; + return SASS_MEMORY_NEW(String, pstate, fncall.str()); + } + + Number* h = _h->assertNumber(logger, Strings::hue); + Number* s = _s->assertNumber(logger, Strings::saturation); + Number* l = _l->assertNumber(logger, Strings::lightness); + Number* a = _a ? _a->assertNumber(logger, Strings::alpha) : nullptr; + + return SASS_MEMORY_NEW(ColorHsla, pstate, + h->value(), + clamp(s->value(), 0.0, 100.0), + clamp(l->value(), 0.0, 100.0), + _a ? _percentageOrUnitless(a, 1.0, "$alpha", logger) : 1.0); } } diff --git a/src/fn_colors.hpp b/src/fn_colors.hpp index a474c64b5b..cf67effd81 100644 --- a/src/fn_colors.hpp +++ b/src/fn_colors.hpp @@ -1,5 +1,12 @@ -#ifndef SASS_FN_COLORS_H -#define SASS_FN_COLORS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_COLORS_HPP +#define SASS_FN_COLORS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include "fn_utils.hpp" @@ -7,76 +14,39 @@ namespace Sass { namespace Functions { - // macros for common ranges (u mean unsigned or upper, r for full range) - #define DARG_U_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 1.0) // double - #define DARG_R_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 1.0, 1.0) // double - #define DARG_U_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 255.0) // double - #define DARG_R_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 255.0, 255.0) // double - #define DARG_U_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 100.0) // double - #define DARG_R_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 100.0, 100.0) // double - - // macros for color related inputs (rbg and alpha/opacity values) - #define COLOR_NUM(argname) color_num(argname, env, sig, pstate, traces) // double - #define ALPHA_NUM(argname) alpha_num(argname, env, sig, pstate, traces) // double - - extern Signature rgb_sig; - extern Signature rgba_4_sig; - extern Signature rgba_2_sig; - extern Signature red_sig; - extern Signature green_sig; - extern Signature blue_sig; - extern Signature mix_sig; - extern Signature hsl_sig; - extern Signature hsla_sig; - extern Signature hue_sig; - extern Signature saturation_sig; - extern Signature lightness_sig; - extern Signature adjust_hue_sig; - extern Signature lighten_sig; - extern Signature darken_sig; - extern Signature saturate_sig; - extern Signature desaturate_sig; - extern Signature grayscale_sig; - extern Signature complement_sig; - extern Signature invert_sig; - extern Signature alpha_sig; - extern Signature opacity_sig; - extern Signature opacify_sig; - extern Signature fade_in_sig; - extern Signature transparentize_sig; - extern Signature fade_out_sig; - extern Signature adjust_color_sig; - extern Signature scale_color_sig; - extern Signature change_color_sig; - extern Signature ie_hex_str_sig; - - BUILT_IN(rgb); - BUILT_IN(rgba_4); - BUILT_IN(rgba_2); - BUILT_IN(red); - BUILT_IN(green); - BUILT_IN(blue); - BUILT_IN(mix); - BUILT_IN(hsl); - BUILT_IN(hsla); - BUILT_IN(hue); - BUILT_IN(saturation); - BUILT_IN(lightness); - BUILT_IN(adjust_hue); - BUILT_IN(lighten); - BUILT_IN(darken); - BUILT_IN(saturate); - BUILT_IN(desaturate); - BUILT_IN(grayscale); - BUILT_IN(complement); - BUILT_IN(invert); - BUILT_IN(alpha); - BUILT_IN(opacify); - BUILT_IN(transparentize); - BUILT_IN(adjust_color); - BUILT_IN(scale_color); - BUILT_IN(change_color); - BUILT_IN(ie_hex_str); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* rgbFn( + const sass::string& name, + const ValueVector& arguments, + const SourceSpan& pstate, + Logger& logger); + + Value* hslFn( + const sass::string& name, + const ValueVector& arguments, + const SourceSpan& pstate, + Logger& logger); + + Value* hwbFn( + const sass::string& name, + const ValueVector& arguments, + const SourceSpan& pstate, + Logger& logger); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Colors { + + // Register all functions at compiler + void registerFunctions(Compiler& ctx); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/fn_lists.cpp b/src/fn_lists.cpp index 1d47d5789a..a2e928f9f8 100644 --- a/src/fn_lists.cpp +++ b/src/fn_lists.cpp @@ -1,285 +1,254 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "listize.hpp" -#include "operators.hpp" -#include "fn_utils.hpp" #include "fn_lists.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" + namespace Sass { namespace Functions { - ///////////////// - // LIST FUNCTIONS - ///////////////// - - Signature keywords_sig = "keywords($args)"; - BUILT_IN(keywords) - { - List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); // copy - Map_Obj result = SASS_MEMORY_NEW(Map, pstate, 1); - for (size_t i = arglist->size(), L = arglist->length(); i < L; ++i) { - ExpressionObj obj = arglist->at(i); - Argument_Obj arg = (Argument*) obj.ptr(); // XXX - sass::string name = sass::string(arg->name()); - name = name.erase(0, 1); // sanitize name (remove dollar sign) - *result << std::make_pair(SASS_MEMORY_NEW(String_Quoted, - pstate, name), - arg->value()); - } - return result.detach(); - } + namespace Lists { - Signature length_sig = "length($list)"; - BUILT_IN(length) - { - if (SelectorList * sl = Cast(env["$list"])) { - return SASS_MEMORY_NEW(Number, pstate, (double) sl->length()); - } - Expression* v = ARG("$list", Expression); - if (v->concrete_type() == Expression::MAP) { - Map* map = Cast(env["$list"]); - return SASS_MEMORY_NEW(Number, pstate, (double)(map ? map->length() : 1)); - } - if (v->concrete_type() == Expression::SELECTOR) { - if (CompoundSelector * h = Cast(v)) { - return SASS_MEMORY_NEW(Number, pstate, (double)h->length()); - } else if (SelectorList * ls = Cast(v)) { - return SASS_MEMORY_NEW(Number, pstate, (double)ls->length()); - } else { - return SASS_MEMORY_NEW(Number, pstate, 1); - } + /*******************************************************************/ + + BUILT_IN_FN(length) + { + return SASS_MEMORY_NEW(Number, + arguments[0]->pstate(), + (double) arguments[0]->lengthAsList()); } - List* list = Cast(env["$list"]); - return SASS_MEMORY_NEW(Number, - pstate, - (double)(list ? list->size() : 1)); - } + /*******************************************************************/ - Signature nth_sig = "nth($list, $n)"; - BUILT_IN(nth) - { - double nr = ARGVAL("$n"); - Map* m = Cast(env["$list"]); - if (SelectorList * sl = Cast(env["$list"])) { - size_t len = m ? m->length() : sl->length(); - bool empty = m ? m->empty() : sl->empty(); - if (empty) error("argument `$list` of `" + sass::string(sig) + "` must not be empty", pstate, traces); - double index = std::floor(nr < 0 ? len + nr : nr - 1); - if (index < 0 || index > len - 1) error("index out of bounds for `" + sass::string(sig) + "`", pstate, traces); - return Cast(Listize::perform(sl->get(static_cast(index)))); + BUILT_IN_FN(nth) + { + Value* list = arguments[0]; + Value* index = arguments[1]; + return list->getValueAt(index, compiler); } - List_Obj l = Cast(env["$list"]); - if (nr == 0) error("argument `$n` of `" + sass::string(sig) + "` must be non-zero", pstate, traces); - // if the argument isn't a list, then wrap it in a singleton list - if (!m && !l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - size_t len = m ? m->length() : l->length(); - bool empty = m ? m->empty() : l->empty(); - if (empty) error("argument `$list` of `" + sass::string(sig) + "` must not be empty", pstate, traces); - double index = std::floor(nr < 0 ? len + nr : nr - 1); - if (index < 0 || index > len - 1) error("index out of bounds for `" + sass::string(sig) + "`", pstate, traces); - - if (m) { - l = SASS_MEMORY_NEW(List, pstate, 2); - l->append(m->keys()[static_cast(index)]); - l->append(m->at(m->keys()[static_cast(index)])); - return l.detach(); - } - else { - ValueObj rv = l->value_at_index(static_cast(index)); - rv->set_delayed(false); - return rv.detach(); - } - } - Signature set_nth_sig = "set-nth($list, $n, $value)"; - BUILT_IN(set_nth) - { - Map_Obj m = Cast(env["$list"]); - List_Obj l = Cast(env["$list"]); - Number_Obj n = ARG("$n", Number); - ExpressionObj v = ARG("$value", Expression); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - if (m) { - l = m->to_list(pstate); - } - if (l->empty()) error("argument `$list` of `" + sass::string(sig) + "` must not be empty", pstate, traces); - double index = std::floor(n->value() < 0 ? l->length() + n->value() : n->value() - 1); - if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + sass::string(sig) + "`", pstate, traces); - List* result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator(), false, l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - result->append(((i == index) ? v : (*l)[i])); - } - return result; - } + /*******************************************************************/ - Signature index_sig = "index($list, $value)"; - BUILT_IN(index) - { - Map_Obj m = Cast(env["$list"]); - List_Obj l = Cast(env["$list"]); - ExpressionObj v = ARG("$value", Expression); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - if (m) { - l = m->to_list(pstate); - } - for (size_t i = 0, L = l->length(); i < L; ++i) { - if (Operators::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1)); - } - return SASS_MEMORY_NEW(Null, pstate); - } + BUILT_IN_FN(setNth) + { + Value* input = arguments[0]; + Value* index = arguments[1]; - Signature join_sig = "join($list1, $list2, $separator: auto, $bracketed: auto)"; - BUILT_IN(join) - { - Map_Obj m1 = Cast(env["$list1"]); - Map_Obj m2 = Cast(env["$list2"]); - List_Obj l1 = Cast(env["$list1"]); - List_Obj l2 = Cast(env["$list2"]); - String_Constant_Obj sep = ARG("$separator", String_Constant); - enum Sass_Separator sep_val = (l1 ? l1->separator() : SASS_SPACE); - Value* bracketed = ARG("$bracketed", Value); - bool is_bracketed = (l1 ? l1->is_bracketed() : false); - if (!l1) { - l1 = SASS_MEMORY_NEW(List, pstate, 1); - l1->append(ARG("$list1", Expression)); - sep_val = (l2 ? l2->separator() : SASS_SPACE); - is_bracketed = (l2 ? l2->is_bracketed() : false); - } - if (!l2) { - l2 = SASS_MEMORY_NEW(List, pstate, 1); - l2->append(ARG("$list2", Expression)); - } - if (m1) { - l1 = m1->to_list(pstate); - sep_val = SASS_COMMA; - } - if (m2) { - l2 = m2->to_list(pstate); - } - size_t len = l1->length() + l2->length(); - sass::string sep_str = unquote(sep->value()); - if (sep_str == "space") sep_val = SASS_SPACE; - else if (sep_str == "comma") sep_val = SASS_COMMA; - else if (sep_str != "auto") error("argument `$separator` of `" + sass::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); - String_Constant_Obj bracketed_as_str = Cast(bracketed); - bool bracketed_is_auto = bracketed_as_str && unquote(bracketed_as_str->value()) == "auto"; - if (!bracketed_is_auto) { - is_bracketed = !bracketed->is_false(); - } - List_Obj result = SASS_MEMORY_NEW(List, pstate, len, sep_val, false, is_bracketed); - result->concat(l1); - result->concat(l2); - return result.detach(); - } + #ifdef SASS_OPTIMIZE_SELF_ASSIGN + if (selfAssign && input->refcount < AssignableRefCount) { + if (List* lst = input->isaList()) { + size_t idx = input->sassIndexToListIndex(index, compiler, "n"); + lst->at(idx) = arguments[2]; + return lst; + } + } + #endif + + auto it = input->iterator(); + size_t idx = input-> + sassIndexToListIndex( + index, compiler, "n"); + ValueVector values(it.begin(), it.end()); + values[idx] = arguments[2]; + return SASS_MEMORY_NEW(List, + input->pstate(), std::move(values), + input->separator(), input->hasBrackets()); + } + + /*******************************************************************/ + + BUILT_IN_FN(join) + { + Value* list1 = arguments[0]; + Value* list2 = arguments[1]; + String* separatorParam = arguments[2]->assertString(compiler, "separator"); + Value* bracketedParam = arguments[3]; + + SassSeparator separator = SASS_UNDEF; + if (separatorParam->value() == "auto") { + if (list1->separator() != SASS_UNDEF) { + separator = list1->separator(); + } + else if (list2->separator() != SASS_UNDEF) { + separator = list2->separator(); + } + else { + separator = SASS_SPACE; + } + } + else if (separatorParam->value() == "space") { + separator = SASS_SPACE; + } + else if (separatorParam->value() == "comma") { + separator = SASS_COMMA; + } + else { + throw Exception::SassScriptException( + "$separator: Must be \"space\", \"comma\", or \"auto\".", + compiler, pstate); + } - Signature append_sig = "append($list, $val, $separator: auto)"; - BUILT_IN(append) - { - Map_Obj m = Cast(env["$list"]); - List_Obj l = Cast(env["$list"]); - ExpressionObj v = ARG("$val", Expression); - if (SelectorList * sl = Cast(env["$list"])) { - l = Cast(Listize::perform(sl)); - } - String_Constant_Obj sep = ARG("$separator", String_Constant); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - if (m) { - l = m->to_list(pstate); - } - List* result = SASS_MEMORY_COPY(l); - sass::string sep_str(unquote(sep->value())); - if (sep_str != "auto") { // check default first - if (sep_str == "space") result->separator(SASS_SPACE); - else if (sep_str == "comma") result->separator(SASS_COMMA); - else error("argument `$separator` of `" + sass::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); - } - if (l->is_arglist()) { - result->append(SASS_MEMORY_NEW(Argument, - v->pstate(), - v, - "", - false, - false)); - - } else { - result->append(v); - } - return result; - } + bool bracketed = bracketedParam->isTruthy(); + if (String * str = bracketedParam->isaString()) { + if (str->value() == "auto") { + bracketed = list1->hasBrackets(); + } + } - Signature zip_sig = "zip($lists...)"; - BUILT_IN(zip) - { - List_Obj arglist = SASS_MEMORY_COPY(ARG("$lists", List)); - size_t shortest = 0; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - List_Obj ith = Cast(arglist->value_at_index(i)); - Map_Obj mith = Cast(arglist->value_at_index(i)); - if (!ith) { - if (mith) { - ith = mith->to_list(pstate); - } else { - ith = SASS_MEMORY_NEW(List, pstate, 1); - ith->append(arglist->value_at_index(i)); + #ifdef SASS_OPTIMIZE_SELF_ASSIGN + if (selfAssign && list2->refcount < AssignableRefCount) { + if (List* lst = list2->isaList()) { + ValueVector& values(lst->elements()); + lst->separator(separator); + lst->hasBrackets(bracketed); + auto it2 = list2->iterator(); + values.insert(values.end(), + it2.begin(), it2.end()); + return lst; } - if (arglist->is_arglist()) { - Argument_Obj arg = (Argument*)(arglist->at(i).ptr()); // XXX - arg->value(ith); - } else { - (*arglist)[i] = ith; + } + #endif + + ValueVector values; + auto it1 = list1->iterator(); + values.insert(values.end(), + it1.begin(), it1.end()); + auto it2 = list2->iterator(); + values.insert(values.end(), + it2.begin(), it2.end()); + return SASS_MEMORY_NEW(List, pstate, + std::move(values), separator, bracketed); + } + + /*******************************************************************/ + + BUILT_IN_FN(append) + { + Value* list = arguments[0]->assertValue(compiler, "list"); + Value* value = arguments[1]->assertValue(compiler, "val"); + String* separatorParam = arguments[2]->assertString(compiler, "separator"); + SassSeparator separator = SASS_UNDEF; + if (separatorParam->value() == "auto") { + separator = list->separator() == SASS_UNDEF + ? SASS_SPACE : list->separator(); + } + else if (separatorParam->value() == "space") { + separator = SASS_SPACE; + } + else if (separatorParam->value() == "comma") { + separator = SASS_COMMA; + } + else { + throw Exception::SassScriptException( + "$separator: Must be \"space\", \"comma\", or \"auto\".", + compiler, pstate); + } + + #ifdef SASS_OPTIMIZE_SELF_ASSIGN + if (selfAssign && list->refcount < AssignableRefCount) { + if (List* lst = list->isaList()) { + lst->separator(separator); + lst->append(value); + return lst; } } - shortest = (i ? std::min(shortest, ith->length()) : ith->length()); + #endif + + ValueVector values; + auto it = list->iterator(); + values.insert(values.end(), + it.begin(), it.end()); + values.emplace_back(value); + return SASS_MEMORY_NEW(List, + list->pstate(), std::move(values), + separator, list->hasBrackets()); + } + + /*******************************************************************/ + + BUILT_IN_FN(zip) + { + size_t shortest = sass::string::npos; + sass::vector lists; + for (Value* arg : arguments[0]->iterator()) { + Values it = arg->iterator(); + ValueVector inner; + inner.reserve(arg->lengthAsList()); + std::copy(it.begin(), it.end(), + std::back_inserter(inner)); + shortest = std::min(shortest, inner.size()); + lists.emplace_back(inner); + } + if (lists.empty()) { + return SASS_MEMORY_NEW(List, + pstate, {}, SASS_COMMA); + } + ValueVector result; + if (!lists.empty()) { + for (size_t i = 0; i < shortest; i++) { + ValueVector inner; + inner.reserve(lists.size()); + for (ValueVector& arg : lists) { + inner.push_back(arg[i]); + } + result.emplace_back(SASS_MEMORY_NEW( + List, pstate, inner, SASS_SPACE)); + } + } + return SASS_MEMORY_NEW( + List, pstate, result, SASS_COMMA); } - List* zippers = SASS_MEMORY_NEW(List, pstate, shortest, SASS_COMMA); - size_t L = arglist->length(); - for (size_t i = 0; i < shortest; ++i) { - List* zipper = SASS_MEMORY_NEW(List, pstate, L); - for (size_t j = 0; j < L; ++j) { - zipper->append(Cast(arglist->value_at_index(j))->at(i)); + + /*******************************************************************/ + + BUILT_IN_FN(index) + { + Value* value = arguments[1]; + size_t index = arguments[0]->indexOf(value); + if (index == NPOS) { + return SASS_MEMORY_NEW(Null, + arguments[0]->pstate()); } - zippers->append(zipper); + return SASS_MEMORY_NEW(Number, + arguments[0]->pstate(), + (double) index + 1); } - return zippers; - } - Signature list_separator_sig = "list_separator($list)"; - BUILT_IN(list_separator) - { - List_Obj l = Cast(env["$list"]); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); + /*******************************************************************/ + + BUILT_IN_FN(separator) + { + return SASS_MEMORY_NEW(String, arguments[0]->pstate(), + std::move(arguments[0]->separator() == SASS_COMMA ? "comma" : "space")); } - return SASS_MEMORY_NEW(String_Quoted, - pstate, - l->separator() == SASS_COMMA ? "comma" : "space"); - } - Signature is_bracketed_sig = "is-bracketed($list)"; - BUILT_IN(is_bracketed) - { - ValueObj value = ARG("$list", Value); - List_Obj list = Cast(value); - return SASS_MEMORY_NEW(Boolean, pstate, list && list->is_bracketed()); - } + /*******************************************************************/ + + BUILT_IN_FN(isBracketed) + { + return SASS_MEMORY_NEW(Boolean, pstate, + arguments[0]->hasBrackets()); + } + /*******************************************************************/ + + void registerFunctions(Compiler& ctx) + { + ctx.registerBuiltInFunction("length", "$list", length); + ctx.registerBuiltInFunction("nth", "$list, $n", nth); + ctx.registerBuiltInFunction("set-nth", "$list, $n, $value", setNth); + ctx.registerBuiltInFunction("join", "$list1, $list2, $separator: auto, $bracketed: auto", join); + ctx.registerBuiltInFunction("append", "$list, $val, $separator: auto", append); + ctx.registerBuiltInFunction("zip", "$lists...", zip); + ctx.registerBuiltInFunction("index", "$list, $value", index); + ctx.registerBuiltInFunction("list-separator", "$list", separator); + ctx.registerBuiltInFunction("is-bracketed", "$list", isBracketed); + } + + /*******************************************************************/ + + } } } diff --git a/src/fn_lists.hpp b/src/fn_lists.hpp index 260023aeb7..d8e4622261 100644 --- a/src/fn_lists.hpp +++ b/src/fn_lists.hpp @@ -1,5 +1,12 @@ -#ifndef SASS_FN_LISTS_H -#define SASS_FN_LISTS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_LISTS_HPP +#define SASS_FN_LISTS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include "fn_utils.hpp" @@ -7,25 +14,18 @@ namespace Sass { namespace Functions { - extern Signature length_sig; - extern Signature nth_sig; - extern Signature index_sig; - extern Signature join_sig; - extern Signature append_sig; - extern Signature zip_sig; - extern Signature list_separator_sig; - extern Signature is_bracketed_sig; - extern Signature keywords_sig; - - BUILT_IN(length); - BUILT_IN(nth); - BUILT_IN(index); - BUILT_IN(join); - BUILT_IN(append); - BUILT_IN(zip); - BUILT_IN(list_separator); - BUILT_IN(is_bracketed); - BUILT_IN(keywords); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Lists { + + // Register all functions at compiler + void registerFunctions(Compiler& ctx); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/fn_maps.cpp b/src/fn_maps.cpp index 0db5acab76..d8f11ffceb 100644 --- a/src/fn_maps.cpp +++ b/src/fn_maps.cpp @@ -1,94 +1,134 @@ -#include "operators.hpp" -#include "fn_utils.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "fn_maps.hpp" +#include "compiler.hpp" +#include "ast_values.hpp" + namespace Sass { namespace Functions { - ///////////////// - // MAP FUNCTIONS - ///////////////// - - Signature map_get_sig = "map-get($map, $key)"; - BUILT_IN(map_get) - { - // leaks for "map-get((), foo)" if not Obj - // investigate why this is (unexpected) - Map_Obj m = ARGM("$map", Map); - ExpressionObj v = ARG("$key", Expression); - try { - ValueObj val = m->at(v); - if (!val) return SASS_MEMORY_NEW(Null, pstate); - val->set_delayed(false); - return val.detach(); - } catch (const std::out_of_range&) { + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Maps { + + /*******************************************************************/ + + BUILT_IN_FN(get) + { + MapObj map = arguments[0]->assertMap(compiler, Strings::map); + Value* key = arguments[1]->assertValue(compiler, Strings::key); + auto it = map->find(key); + if (it != map->end()) return it->second; return SASS_MEMORY_NEW(Null, pstate); } - catch (...) { throw; } - } - Signature map_has_key_sig = "map-has-key($map, $key)"; - BUILT_IN(map_has_key) - { - Map_Obj m = ARGM("$map", Map); - ExpressionObj v = ARG("$key", Expression); - return SASS_MEMORY_NEW(Boolean, pstate, m->has(v)); - } + /*******************************************************************/ - Signature map_keys_sig = "map-keys($map)"; - BUILT_IN(map_keys) - { - Map_Obj m = ARGM("$map", Map); - List* result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); - for ( auto key : m->keys()) { - result->append(key); + BUILT_IN_FN(merge) + { + MapObj map1 = arguments[0]->assertMap(compiler, Strings::map1); + MapObj map2 = arguments[1]->assertMap(compiler, Strings::map2); + // We assign to ourself, so we can optimize this + // This can shave off a few percent of run-time + #ifdef SASS_OPTIMIZE_SELF_ASSIGN + if (selfAssign && map1->refcount < AssignableRefCount + 1) { + for (auto kv : map2->elements()) { map1->insertOrSet(kv); } + return map1.detach(); + } + #endif + Map* copy = SASS_MEMORY_COPY(map1); + for (auto kv : map2->elements()) { + copy->insertOrSet(kv); } + return copy; } - return result; - } - Signature map_values_sig = "map-values($map)"; - BUILT_IN(map_values) - { - Map_Obj m = ARGM("$map", Map); - List* result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); - for ( auto key : m->keys()) { - result->append(m->at(key)); + /*******************************************************************/ + + // Because the signature below has an explicit `$key` argument, it doesn't + // allow zero keys to be passed. We want to allow that case, so we add an + // explicit overload for it. + BUILT_IN_FN(remove_one) + { + return arguments[0]->assertMap(compiler, Strings::map); } - return result; - } - Signature map_merge_sig = "map-merge($map1, $map2)"; - BUILT_IN(map_merge) - { - Map_Obj m1 = ARGM("$map1", Map); - Map_Obj m2 = ARGM("$map2", Map); - - size_t len = m1->length() + m2->length(); - Map* result = SASS_MEMORY_NEW(Map, pstate, len); - // concat not implemented for maps - *result += m1; - *result += m2; - return result; - } + /*******************************************************************/ + + BUILT_IN_FN(remove_many) + { + MapObj map = arguments[0]->assertMap(compiler, Strings::map); + + #ifdef SASS_OPTIMIZE_SELF_ASSIGN + if (selfAssign && map->refcount < AssignableRefCount + 1) { + map->erase(arguments[1]); + for (Value* key : arguments[2]->iterator()) { + map->erase(key); + } + return map.detach(); + } + #endif - Signature map_remove_sig = "map-remove($map, $keys...)"; - BUILT_IN(map_remove) - { - bool remove; - Map_Obj m = ARGM("$map", Map); - List_Obj arglist = ARG("$keys", List); - Map* result = SASS_MEMORY_NEW(Map, pstate, 1); - for (auto key : m->keys()) { - remove = false; - for (size_t j = 0, K = arglist->length(); j < K && !remove; ++j) { - remove = Operators::eq(key, arglist->value_at_index(j)); + MapObj copy = SASS_MEMORY_COPY(map); + copy->erase(arguments[1]); + for (Value* key : arguments[2]->iterator()) { + copy->erase(key); } - if (!remove) *result << std::make_pair(key, m->at(key)); + return copy.detach(); } - return result; + + /*******************************************************************/ + + BUILT_IN_FN(keys) + { + MapObj map = arguments[0]->assertMap(compiler, Strings::map); + return SASS_MEMORY_NEW(List, + pstate, map->keys(), SASS_COMMA); + } + + /*******************************************************************/ + + BUILT_IN_FN(values) + { + MapObj map = arguments[0]->assertMap(compiler, Strings::map); + return SASS_MEMORY_NEW(List, + pstate, map->values(), SASS_COMMA); + } + + /*******************************************************************/ + + BUILT_IN_FN(hasKey) + { + MapObj map = arguments[0]->assertMap(compiler, Strings::map); + Value* key = arguments[1]->assertValue(compiler, Strings::key); + return SASS_MEMORY_NEW(Boolean, pstate, map->has(key)); + } + + /*******************************************************************/ + + void registerFunctions(Compiler& ctx) + { + ctx.registerBuiltInFunction("map-get", "$map, $key", get); + ctx.registerBuiltInFunction("map-merge", "$map1, $map2", merge); + ctx.registerBuiltInOverloadFns("map-remove", { + std::make_pair("$map", remove_one), + std::make_pair("$map, $key, $keys...", remove_many) + }); + ctx.registerBuiltInFunction("map-keys", "$map", keys); + ctx.registerBuiltInFunction("map-values", "$map", values); + ctx.registerBuiltInFunction("map-has-key", "$map, $key", hasKey); + } + + /*******************************************************************/ + } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } -} \ No newline at end of file +} diff --git a/src/fn_maps.hpp b/src/fn_maps.hpp index 9551ec33ec..191bbb84a7 100644 --- a/src/fn_maps.hpp +++ b/src/fn_maps.hpp @@ -1,5 +1,12 @@ -#ifndef SASS_FN_MAPS_H -#define SASS_FN_MAPS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_MAPS_HPP +#define SASS_FN_MAPS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include "fn_utils.hpp" @@ -7,21 +14,18 @@ namespace Sass { namespace Functions { - #define ARGM(argname, argtype) get_arg_m(argname, env, sig, pstate, traces) - - extern Signature map_get_sig; - extern Signature map_merge_sig; - extern Signature map_remove_sig; - extern Signature map_keys_sig; - extern Signature map_values_sig; - extern Signature map_has_key_sig; - - BUILT_IN(map_get); - BUILT_IN(map_merge); - BUILT_IN(map_remove); - BUILT_IN(map_keys); - BUILT_IN(map_values); - BUILT_IN(map_has_key); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Maps { + + // Register all functions at compiler + void registerFunctions(Compiler& ctx); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/fn_meta.cpp b/src/fn_meta.cpp new file mode 100644 index 0000000000..281d9f3232 --- /dev/null +++ b/src/fn_meta.cpp @@ -0,0 +1,257 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "fn_meta.hpp" + +#include "eval.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" +#include "ast_callables.hpp" +#include "ast_expressions.hpp" + +namespace Sass { + + namespace Functions { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Meta { + + /*******************************************************************/ + + BUILT_IN_FN(typeOf) + { + sass::string copy(arguments[0]->type()); + return SASS_MEMORY_NEW(String, + pstate, std::move(copy)); + } + + /*******************************************************************/ + + BUILT_IN_FN(inspect) + { + if (arguments[0] == nullptr) { + return SASS_MEMORY_NEW( + String, pstate, "null"); + } + return SASS_MEMORY_NEW(String, + pstate, arguments[0]->inspect()); + } + + /*******************************************************************/ + + BUILT_IN_FN(keywords) + { + ArgumentList* argumentList = arguments[0]->assertArgumentList(compiler, Sass::Strings::args); + const ValueFlatMap& keywords = argumentList->keywords(); + MapObj map = SASS_MEMORY_NEW(Map, arguments[0]->pstate()); + for (auto kv : keywords) { + sass::string key = kv.first.norm(); // .substr(1); + // Util::ascii_normalize_underscore(key); + // Wrap string key into a sass value + map->insert(SASS_MEMORY_NEW(String, + kv.second->pstate(), std::move(key) + ), kv.second); + } + return map.detach(); + } + + /*******************************************************************/ + + BUILT_IN_FN(featureExists) + { + String* feature = arguments[0]->assertString(compiler, "feature"); + static const auto* const features = + new std::unordered_set{ + "global-variable-shadowing", + "extend-selector-pseudoclass", + "at-error", + "units-level-3", + "custom-property" + }; + sass::string name(feature->value()); + return SASS_MEMORY_NEW(Boolean, + pstate, features->count(name) == 1); + } + + /*******************************************************************/ + + BUILT_IN_FN(globalVariableExists) + { + String* variable = arguments[0]->assertString(compiler, Sass::Strings::name); + String* plugin = arguments[1]->assertStringOrNull(compiler, Sass::Strings::module); + if (plugin != nullptr) { + throw Exception::RuntimeException(compiler, + "Modules are not supported yet"); + } + return SASS_MEMORY_NEW(Boolean, pstate, + compiler.getVariable(variable->value(), true)); + + } + + /*******************************************************************/ + + BUILT_IN_FN(variableExists) + { + String* variable = arguments[0]->assertString(compiler, Sass::Strings::name); + ValueObj ex = compiler.getVariable(variable->value()); + return SASS_MEMORY_NEW(Boolean, pstate, !ex.isNull()); + } + + BUILT_IN_FN(functionExists) + { + String* variable = arguments[0]->assertString(compiler, Sass::Strings::name); + String* plugin = arguments[1]->assertStringOrNull(compiler, Sass::Strings::module); + if (plugin != nullptr) { + throw Exception::RuntimeException(compiler, + "Modules are not supported yet"); + } + CallableObj fn = compiler.getFunction(variable->value()); + return SASS_MEMORY_NEW(Boolean, pstate, !fn.isNull()); + } + + BUILT_IN_FN(mixinExists) + { + String* variable = arguments[0]->assertString(compiler, Sass::Strings::name); + String* plugin = arguments[1]->assertStringOrNull(compiler, Sass::Strings::module); + if (plugin != nullptr) { + throw Exception::RuntimeException(compiler, + "Modules are not supported yet"); + } + CallableObj fn = compiler.getMixin(variable->value()); + return SASS_MEMORY_NEW(Boolean, pstate, !fn.isNull()); + } + + BUILT_IN_FN(contentExists) + { + if (!eval.isInMixin()) { + throw Exception::RuntimeException(compiler, + "content-exists() may only be called within a mixin."); + } + return SASS_MEMORY_NEW(Boolean, pstate, + eval.hasContentBlock()); + } + + BUILT_IN_FN(moduleVariables) + { + // String* variable = arguments[0]->assertString(Sass::Strings::name); + // String* plugin = arguments[1]->assertStringOrNull(Sass::Strings::module); + throw Exception::RuntimeException(compiler, + "Modules are not supported yet"); + } + + BUILT_IN_FN(moduleFunctions) + { + // String* variable = arguments[0]->assertString(Sass::Strings::name); + // String* plugin = arguments[1]->assertStringOrNull(Sass::Strings::module); + throw Exception::RuntimeException(compiler, + "Modules are not supported yet"); + } + + /// Like `_environment.getFunction`, but also returns built-in + /// globally-available functions. + Callable* _getFunction(const EnvKey& name, Context& ctx, const sass::string& ns = "") { + return ctx.getFunction(name); // no detach, is a reference anyway + } + + BUILT_IN_FN(getFunction) + { + + String* name = arguments[0]->assertString(compiler, Sass::Strings::name); + bool css = arguments[1]->isTruthy(); // supports all values + String* plugin = arguments[2]->assertStringOrNull(compiler, Sass::Strings::module); + + if (css && plugin != nullptr) { + throw Exception::RuntimeException(compiler, + "$css and $module may not both be passed at once."); + } + + CallableObj callable = css + ? SASS_MEMORY_NEW(PlainCssCallable, pstate, name->value()) + : _getFunction(name->value(), compiler); + + if (callable == nullptr) throw + Exception::RuntimeException(compiler, + "Function not found: " + name->value()); + + return SASS_MEMORY_NEW(Function, pstate, callable); + + } + + BUILT_IN_FN(call) + { + + Value* function = arguments[0]->assertValue(compiler, "function"); + ArgumentList* args = arguments[1]->assertArgumentList(compiler, Sass::Strings::args); + + // ExpressionVector positional, + // EnvKeyMap named, + // Expression* restArgs = nullptr, + // Expression* kwdRest = nullptr); + + ValueExpression* restArg = SASS_MEMORY_NEW( + ValueExpression, args->pstate(), args); + + ValueExpression* kwdRest = nullptr; + if (!args->keywords().empty()) { + Map* map = args->keywordsAsSassMap(); + kwdRest = SASS_MEMORY_NEW( + ValueExpression, map->pstate(), map); + } + + ArgumentInvocationObj invocation = SASS_MEMORY_NEW( + ArgumentInvocation, pstate, ExpressionVector{}, {}, restArg, kwdRest); + + if (String * str = function->isaString()) { + sass::string name = str->value(); + compiler.addDeprecation( + "Passing a string to call() is deprecated and will be illegal in LibSass " + "4.1.0. Use call(get-function(" + str->inspect() + ")) instead.", + str->pstate()); + + InterpolationObj itpl = SASS_MEMORY_NEW(Interpolation, pstate); + itpl->append(SASS_MEMORY_NEW(String, pstate, sass::string(str->value()))); + FunctionExpressionObj expression = SASS_MEMORY_NEW( + FunctionExpression, pstate, itpl, invocation); + return eval.acceptFunctionExpression(expression); + // return expression->accept(&eval); + + } + + Callable* callable = function->assertFunction(compiler, "function")->callable(); + return callable->execute(eval, invocation, pstate, false); + + + } + + void registerFunctions(Compiler& compiler) + { + + // Meta functions + compiler.registerBuiltInFunction("feature-exists", "$feature", featureExists); + compiler.registerBuiltInFunction("type-of", "$value", typeOf); + compiler.registerBuiltInFunction("inspect", "$value", inspect); + compiler.registerBuiltInFunction("keywords", "$args", keywords); + + // ToDo: dart-sass keeps them on the local environment scope, see below: + // These functions are defined in the context of the evaluator because + // they need access to the [_environment] or other local state. + compiler.registerBuiltInFunction("global-variable-exists", "$name, $module: null", globalVariableExists); + compiler.registerBuiltInFunction("variable-exists", "$name", variableExists); + compiler.registerBuiltInFunction("function-exists", "$name, $module: null", functionExists); + compiler.registerBuiltInFunction("mixin-exists", "$name, $module: null", mixinExists); + compiler.registerBuiltInFunction("content-exists", "", contentExists); + // compiler.registerBuiltInFunction("module-variables", "$module", moduleVariables); + // compiler.registerBuiltInFunction("module-functions", "$module", moduleFunctions); + compiler.registerBuiltInFunction("get-function", "$name, $css: false, $module: null", getFunction); + compiler.registerBuiltInFunction("call", "$function, $args...", call); + + } + + } + + } + +} diff --git a/src/fn_meta.hpp b/src/fn_meta.hpp new file mode 100644 index 0000000000..8902507f9a --- /dev/null +++ b/src/fn_meta.hpp @@ -0,0 +1,34 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_META_HPP +#define SASS_FN_META_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "fn_utils.hpp" + +namespace Sass { + + namespace Functions { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Meta { + + // Register all functions at compiler + void registerFunctions(Compiler& ctx); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + } + +} + +#endif diff --git a/src/fn_miscs.cpp b/src/fn_miscs.cpp deleted file mode 100644 index 38e8d2a820..0000000000 --- a/src/fn_miscs.cpp +++ /dev/null @@ -1,244 +0,0 @@ -#include "ast.hpp" -#include "expand.hpp" -#include "fn_utils.hpp" -#include "fn_miscs.hpp" -#include "util_string.hpp" - -namespace Sass { - - namespace Functions { - - ////////////////////////// - // INTROSPECTION FUNCTIONS - ////////////////////////// - - Signature type_of_sig = "type-of($value)"; - BUILT_IN(type_of) - { - Expression* v = ARG("$value", Expression); - return SASS_MEMORY_NEW(String_Quoted, pstate, v->type()); - } - - Signature variable_exists_sig = "variable-exists($name)"; - BUILT_IN(variable_exists) - { - sass::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); - - if(d_env.has("$"+s)) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature global_variable_exists_sig = "global-variable-exists($name)"; - BUILT_IN(global_variable_exists) - { - sass::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); - - if(d_env.has_global("$"+s)) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature function_exists_sig = "function-exists($name)"; - BUILT_IN(function_exists) - { - String_Constant* ss = Cast(env["$name"]); - if (!ss) { - error("$name: " + (env["$name"]->to_string()) + " is not a string for `function-exists'", pstate, traces); - } - - sass::string name = Util::normalize_underscores(unquote(ss->value())); - - if(d_env.has(name+"[f]")) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature mixin_exists_sig = "mixin-exists($name)"; - BUILT_IN(mixin_exists) - { - sass::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); - - if(d_env.has(s+"[m]")) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature feature_exists_sig = "feature-exists($feature)"; - BUILT_IN(feature_exists) - { - sass::string s = unquote(ARG("$feature", String_Constant)->value()); - - static const auto *const features = new std::unordered_set { - "global-variable-shadowing", - "extend-selector-pseudoclass", - "at-error", - "units-level-3", - "custom-property" - }; - return SASS_MEMORY_NEW(Boolean, pstate, features->find(s) != features->end()); - } - - Signature call_sig = "call($function, $args...)"; - BUILT_IN(call) - { - sass::string function; - Function* ff = Cast(env["$function"]); - String_Constant* ss = Cast(env["$function"]); - - if (ss) { - function = Util::normalize_underscores(unquote(ss->value())); - std::cerr << "DEPRECATION WARNING: "; - std::cerr << "Passing a string to call() is deprecated and will be illegal" << std::endl; - std::cerr << "in Sass 4.0. Use call(get-function(" + quote(function) + ")) instead." << std::endl; - std::cerr << std::endl; - } else if (ff) { - function = ff->name(); - } - - List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); - - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - // sass::string full_name(name + "[f]"); - // Definition* def = d_env.has(full_name) ? Cast((d_env)[full_name]) : 0; - // Parameters* params = def ? def->parameters() : 0; - // size_t param_size = params ? params->length() : 0; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - ExpressionObj expr = arglist->value_at_index(i); - // if (params && params->has_rest_parameter()) { - // Parameter_Obj p = param_size > i ? (*params)[i] : 0; - // List* list = Cast(expr); - // if (list && p && !p->is_rest_parameter()) expr = (*list)[0]; - // } - if (arglist->is_arglist()) { - ExpressionObj obj = arglist->at(i); - Argument_Obj arg = (Argument*) obj.ptr(); // XXX - args->append(SASS_MEMORY_NEW(Argument, - pstate, - expr, - arg ? arg->name() : "", - arg ? arg->is_rest_argument() : false, - arg ? arg->is_keyword_argument() : false)); - } else { - args->append(SASS_MEMORY_NEW(Argument, pstate, expr)); - } - } - Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, function, args); - - Expand expand(ctx, &d_env, &selector_stack, &original_stack); - func->via_call(true); // calc invoke is allowed - if (ff) func->func(ff); - return Cast(func->perform(&expand.eval)); - } - - //////////////////// - // BOOLEAN FUNCTIONS - //////////////////// - - Signature not_sig = "not($value)"; - BUILT_IN(sass_not) - { - return SASS_MEMORY_NEW(Boolean, pstate, ARG("$value", Expression)->is_false()); - } - - Signature if_sig = "if($condition, $if-true, $if-false)"; - BUILT_IN(sass_if) - { - Expand expand(ctx, &d_env, &selector_stack, &original_stack); - ExpressionObj cond = ARG("$condition", Expression)->perform(&expand.eval); - bool is_true = !cond->is_false(); - ExpressionObj res = ARG(is_true ? "$if-true" : "$if-false", Expression); - ValueObj qwe = Cast(res->perform(&expand.eval)); - // res = res->perform(&expand.eval.val_eval); - qwe->set_delayed(false); // clone? - return qwe.detach(); - } - - ////////////////////////// - // MISCELLANEOUS FUNCTIONS - ////////////////////////// - - Signature inspect_sig = "inspect($value)"; - BUILT_IN(inspect) - { - Expression* v = ARG("$value", Expression); - if (v->concrete_type() == Expression::NULL_VAL) { - return SASS_MEMORY_NEW(String_Constant, pstate, "null"); - } else if (v->concrete_type() == Expression::BOOLEAN && v->is_false()) { - return SASS_MEMORY_NEW(String_Constant, pstate, "false"); - } else if (v->concrete_type() == Expression::STRING) { - String_Constant *s = Cast(v); - if (s->quote_mark()) { - return SASS_MEMORY_NEW(String_Constant, pstate, quote(s->value(), s->quote_mark())); - } else { - return s; - } - } else { - // ToDo: fix to_sass for nested parentheses - Sass_Output_Style old_style; - old_style = ctx.c_options.output_style; - ctx.c_options.output_style = TO_SASS; - Emitter emitter(ctx.c_options); - Inspect i(emitter); - i.in_declaration = false; - v->perform(&i); - ctx.c_options.output_style = old_style; - return SASS_MEMORY_NEW(String_Quoted, pstate, i.get_buffer()); - } - } - - Signature content_exists_sig = "content-exists()"; - BUILT_IN(content_exists) - { - if (!d_env.has_global("is_in_mixin")) { - error("Cannot call content-exists() except within a mixin.", pstate, traces); - } - return SASS_MEMORY_NEW(Boolean, pstate, d_env.has_lexical("@content[m]")); - } - - Signature get_function_sig = "get-function($name, $css: false)"; - BUILT_IN(get_function) - { - String_Constant* ss = Cast(env["$name"]); - if (!ss) { - error("$name: " + (env["$name"]->to_string()) + " is not a string for `get-function'", pstate, traces); - } - - sass::string name = Util::normalize_underscores(unquote(ss->value())); - sass::string full_name = name + "[f]"; - - Boolean_Obj css = ARG("$css", Boolean); - if (!css->is_false()) { - Definition* def = SASS_MEMORY_NEW(Definition, - pstate, - name, - SASS_MEMORY_NEW(Parameters, pstate), - SASS_MEMORY_NEW(Block, pstate, 0, false), - Definition::FUNCTION); - return SASS_MEMORY_NEW(Function, pstate, def, true); - } - - - if (!d_env.has_global(full_name)) { - error("Function not found: " + name, pstate, traces); - } - - Definition* def = Cast(d_env[full_name]); - return SASS_MEMORY_NEW(Function, pstate, def, false); - } - - } - -} diff --git a/src/fn_miscs.hpp b/src/fn_miscs.hpp deleted file mode 100644 index aec693e92d..0000000000 --- a/src/fn_miscs.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef SASS_FN_MISCS_H -#define SASS_FN_MISCS_H - -#include "fn_utils.hpp" - -namespace Sass { - - namespace Functions { - - extern Signature type_of_sig; - extern Signature variable_exists_sig; - extern Signature global_variable_exists_sig; - extern Signature function_exists_sig; - extern Signature mixin_exists_sig; - extern Signature feature_exists_sig; - extern Signature call_sig; - extern Signature not_sig; - extern Signature if_sig; - extern Signature set_nth_sig; - extern Signature content_exists_sig; - extern Signature get_function_sig; - - BUILT_IN(type_of); - BUILT_IN(variable_exists); - BUILT_IN(global_variable_exists); - BUILT_IN(function_exists); - BUILT_IN(mixin_exists); - BUILT_IN(feature_exists); - BUILT_IN(call); - BUILT_IN(sass_not); - BUILT_IN(sass_if); - BUILT_IN(set_nth); - BUILT_IN(content_exists); - BUILT_IN(get_function); - - } - -} - -#endif diff --git a/src/fn_numbers.cpp b/src/fn_numbers.cpp index 01ac604996..95fc0c473f 100644 --- a/src/fn_numbers.cpp +++ b/src/fn_numbers.cpp @@ -1,227 +1,184 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "ast.hpp" -#include "units.hpp" -#include "fn_utils.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "fn_numbers.hpp" -#ifdef __MINGW32__ -#include "windows.h" -#include "wincrypt.h" -#endif +#include "compiler.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" namespace Sass { namespace Functions { - #ifdef __MINGW32__ - uint64_t GetSeed() - { - HCRYPTPROV hp = 0; - BYTE rb[8]; - CryptAcquireContext(&hp, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); - CryptGenRandom(hp, sizeof(rb), rb); - CryptReleaseContext(hp, 0); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - uint64_t seed; - memcpy(&seed, &rb[0], sizeof(seed)); + namespace Math { - return seed; - } - #else - uint64_t GetSeed() + /*******************************************************************/ + + BUILT_IN_FN(round) { - std::random_device rd; - return rd(); + Number* number = arguments[0]->assertNumber(compiler, "number"); + return SASS_MEMORY_NEW(Number, pstate, + fuzzyRound(number->value(), compiler.epsilon), + number->unit()); } - #endif - - // note: the performance of many implementations of - // random_device degrades sharply once the entropy pool - // is exhausted. For practical use, random_device is - // generally only used to seed a PRNG such as mt19937. - static std::mt19937 rand(static_cast(GetSeed())); - - /////////////////// - // NUMBER FUNCTIONS - /////////////////// - - Signature percentage_sig = "percentage($number)"; - BUILT_IN(percentage) - { - Number_Obj n = ARGN("$number"); - if (!n->is_unitless()) error("argument $number of `" + sass::string(sig) + "` must be unitless", pstate, traces); - return SASS_MEMORY_NEW(Number, pstate, n->value() * 100, "%"); - } - Signature round_sig = "round($number)"; - BUILT_IN(round) - { - Number_Obj r = ARGN("$number"); - r->value(Sass::round(r->value(), ctx.c_options.precision)); - r->pstate(pstate); - return r.detach(); - } + /*******************************************************************/ - Signature ceil_sig = "ceil($number)"; - BUILT_IN(ceil) - { - Number_Obj r = ARGN("$number"); - r->value(std::ceil(r->value())); - r->pstate(pstate); - return r.detach(); - } + BUILT_IN_FN(ceil) + { + Number* number = arguments[0]->assertNumber(compiler, "number"); + return SASS_MEMORY_NEW(Number, pstate, + std::ceil(number->value()), + number->unit()); + } - Signature floor_sig = "floor($number)"; - BUILT_IN(floor) - { - Number_Obj r = ARGN("$number"); - r->value(std::floor(r->value())); - r->pstate(pstate); - return r.detach(); - } + /*******************************************************************/ - Signature abs_sig = "abs($number)"; - BUILT_IN(abs) - { - Number_Obj r = ARGN("$number"); - r->value(std::abs(r->value())); - r->pstate(pstate); - return r.detach(); - } + BUILT_IN_FN(floor) + { + Number* number = arguments[0]->assertNumber(compiler, "number"); + return SASS_MEMORY_NEW(Number, pstate, + std::floor(number->value()), + number->unit()); + } + + /*******************************************************************/ - Signature min_sig = "min($numbers...)"; - BUILT_IN(min) - { - List* arglist = ARG("$numbers", List); - Number_Obj least; - size_t L = arglist->length(); - if (L == 0) { - error("At least one argument must be passed.", pstate, traces); + BUILT_IN_FN(abs) + { + Number* number = arguments[0]->assertNumber(compiler, "number"); + return SASS_MEMORY_NEW(Number, pstate, + std::abs(number->value()), + number->unit()); } - for (size_t i = 0; i < L; ++i) { - ExpressionObj val = arglist->value_at_index(i); - Number_Obj xi = Cast(val); - if (!xi) { - error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate, traces); + + /*******************************************************************/ + + BUILT_IN_FN(max) + { + Number* max = nullptr; + for (Value* value : arguments[0]->iterator()) { + Number* number = value->assertNumber(compiler, ""); + if (max == nullptr || max->lessThan(number, compiler, pstate)) { + max = number; + } } - if (least) { - if (*xi < *least) least = xi; - } else least = xi; + if (max != nullptr) return max; + // Report invalid arguments error + throw Exception::SassScriptException( + "At least one argument must be passed.", + compiler, pstate); } - return least.detach(); - } - Signature max_sig = "max($numbers...)"; - BUILT_IN(max) - { - List* arglist = ARG("$numbers", List); - Number_Obj greatest; - size_t L = arglist->length(); - if (L == 0) { - error("At least one argument must be passed.", pstate, traces); - } - for (size_t i = 0; i < L; ++i) { - ExpressionObj val = arglist->value_at_index(i); - Number_Obj xi = Cast(val); - if (!xi) { - error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate, traces); + /*******************************************************************/ + + BUILT_IN_FN(min) + { + Number* min = nullptr; + for (Value* value : arguments[0]->iterator()) { + Number* number = value->assertNumber(compiler, ""); + if (min == nullptr || min->greaterThan(number, compiler, pstate)) { + min = number; + } } - if (greatest) { - if (*greatest < *xi) greatest = xi; - } else greatest = xi; + if (min != nullptr) return min; + // Report invalid arguments error + throw Exception::SassScriptException( + "At least one argument must be passed.", + compiler, pstate); } - return greatest.detach(); - } - Signature random_sig = "random($limit:false)"; - BUILT_IN(random) - { - AST_Node_Obj arg = env["$limit"]; - Value* v = Cast(arg); - Number* l = Cast(arg); - Boolean* b = Cast(arg); - if (l) { - double lv = l->value(); - if (lv < 1) { - sass::ostream err; - err << "$limit " << lv << " must be greater than or equal to 1 for `random'"; - error(err.str(), pstate, traces); + /*******************************************************************/ + + BUILT_IN_FN(random) + { + if (arguments[0]->isNull()) { + return SASS_MEMORY_NEW(Number, pstate, + getRandomDouble(0, 1)); } - bool eq_int = std::fabs(trunc(lv) - lv) < NUMBER_EPSILON; - if (!eq_int) { - sass::ostream err; - err << "Expected $limit to be an integer but got " << lv << " for `random'"; - error(err.str(), pstate, traces); + Number* nr = arguments[0]->assertNumber(compiler, "limit"); + long limit = nr->assertInt(compiler, "limit"); + if (limit >= 1) { + return SASS_MEMORY_NEW(Number, pstate, + (long) getRandomDouble(1, double(limit) + 1)); } - std::uniform_real_distribution<> distributor(1, lv + 1); - uint_fast32_t distributed = static_cast(distributor(rand)); - return SASS_MEMORY_NEW(Number, pstate, (double)distributed); + // Report invalid arguments error + sass::sstream strm; + strm << "$limit: Must be greater than 0, was " << limit << "."; + throw Exception::SassScriptException(strm.str(), compiler, pstate); } - else if (b) { - std::uniform_real_distribution<> distributor(0, 1); - double distributed = static_cast(distributor(rand)); - return SASS_MEMORY_NEW(Number, pstate, distributed); - } else if (v) { - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number", v); - } else { - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number"); + + /*******************************************************************/ + + BUILT_IN_FN(unit) + { + Number* number = arguments[0]->assertNumber(compiler, "number"); + sass::string copy(number->unit()); + return SASS_MEMORY_NEW(String, pstate, std::move(copy), true); } - } - Signature unique_id_sig = "unique-id()"; - BUILT_IN(unique_id) - { - sass::ostream ss; - std::uniform_real_distribution<> distributor(0, 4294967296); // 16^8 - uint_fast32_t distributed = static_cast(distributor(rand)); - ss << "u" << std::setfill('0') << std::setw(8) << std::hex << distributed; - return SASS_MEMORY_NEW(String_Quoted, pstate, ss.str()); - } + /*******************************************************************/ - Signature unit_sig = "unit($number)"; - BUILT_IN(unit) - { - Number_Obj arg = ARGN("$number"); - sass::string str(quote(arg->unit(), '"')); - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } + BUILT_IN_FN(isUnitless) + { + Number* number = arguments[0]->assertNumber(compiler, "number"); + return SASS_MEMORY_NEW(Boolean, pstate, !number->hasUnits()); + } - Signature unitless_sig = "unitless($number)"; - BUILT_IN(unitless) - { - Number_Obj arg = ARGN("$number"); - bool unitless = arg->is_unitless(); - return SASS_MEMORY_NEW(Boolean, pstate, unitless); - } + /*******************************************************************/ + + BUILT_IN_FN(percentage) + { + Number* number = arguments[0]->assertNumber(compiler, "number"); + number->assertUnitless(compiler, "number"); + return SASS_MEMORY_NEW(Number, pstate, + number->value() * 100, "%"); + } + + /*******************************************************************/ - Signature comparable_sig = "comparable($number1, $number2)"; - BUILT_IN(comparable) - { - Number_Obj n1 = ARGN("$number1"); - Number_Obj n2 = ARGN("$number2"); - if (n1->is_unitless() || n2->is_unitless()) { - return SASS_MEMORY_NEW(Boolean, pstate, true); + BUILT_IN_FN(compatible) + { + Number* n1 = arguments[0]->assertNumber(compiler, "number1"); + Number* n2 = arguments[1]->assertNumber(compiler, "number2"); + if (n1->isUnitless() || n2->isUnitless()) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + // normalize into main units + n1->normalize(); n2->normalize(); + Units& lhs_unit = *n1, & rhs_unit = *n2; + bool is_comparable = (lhs_unit == rhs_unit); + return SASS_MEMORY_NEW(Boolean, pstate, is_comparable); } - // normalize into main units - n1->normalize(); n2->normalize(); - Units &lhs_unit = *n1, &rhs_unit = *n2; - bool is_comparable = (lhs_unit == rhs_unit); - return SASS_MEMORY_NEW(Boolean, pstate, is_comparable); + + /*******************************************************************/ + + void registerFunctions(Compiler& ctx) + { + ctx.registerBuiltInFunction("round", "$number", round); + ctx.registerBuiltInFunction("ceil", "$number", ceil); + ctx.registerBuiltInFunction("floor", "$number", floor); + ctx.registerBuiltInFunction("abs", "$number", abs); + ctx.registerBuiltInFunction("max", "$numbers...", max); + ctx.registerBuiltInFunction("min", "$numbers...", min); + ctx.registerBuiltInFunction("random", "$limit: null", random); + ctx.registerBuiltInFunction("unit", "$number", unit); + ctx.registerBuiltInFunction("percentage", "$number", percentage); + ctx.registerBuiltInFunction("unitless", "$number", isUnitless); + ctx.registerBuiltInFunction("comparable", "$number1, $number2", compatible); + } + + /*******************************************************************/ + } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } } diff --git a/src/fn_numbers.hpp b/src/fn_numbers.hpp index dba96be0b4..eb9920e3bd 100644 --- a/src/fn_numbers.hpp +++ b/src/fn_numbers.hpp @@ -1,5 +1,12 @@ -#ifndef SASS_FN_NUMBERS_H -#define SASS_FN_NUMBERS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_NUMBERS_HPP +#define SASS_FN_NUMBERS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include "fn_utils.hpp" @@ -7,39 +14,21 @@ namespace Sass { namespace Functions { - // return a number object (copied since we want to have reduced units) - #define ARGN(argname) get_arg_n(argname, env, sig, pstate, traces) // Number copy - - extern Signature percentage_sig; - extern Signature round_sig; - extern Signature ceil_sig; - extern Signature floor_sig; - extern Signature abs_sig; - extern Signature min_sig; - extern Signature max_sig; - extern Signature inspect_sig; - extern Signature random_sig; - extern Signature unique_id_sig; - extern Signature unit_sig; - extern Signature unitless_sig; - extern Signature comparable_sig; - - BUILT_IN(percentage); - BUILT_IN(round); - BUILT_IN(ceil); - BUILT_IN(floor); - BUILT_IN(abs); - BUILT_IN(min); - BUILT_IN(max); - BUILT_IN(inspect); - BUILT_IN(random); - BUILT_IN(unique_id); - BUILT_IN(unit); - BUILT_IN(unitless); - BUILT_IN(comparable); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Math { + + // Register all functions at compiler + void registerFunctions(Compiler& ctx); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } } -#endif \ No newline at end of file +#endif diff --git a/src/fn_selectors.cpp b/src/fn_selectors.cpp index 7494451e0e..5143ffb25a 100644 --- a/src/fn_selectors.cpp +++ b/src/fn_selectors.cpp @@ -1,204 +1,213 @@ -#include +#include "fn_selectors.hpp" -#include "parser.hpp" #include "extender.hpp" -#include "listize.hpp" -#include "fn_utils.hpp" -#include "fn_selectors.hpp" +#include "source.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" +#include "ast_selectors.hpp" +#include "parser_selector.hpp" namespace Sass { namespace Functions { - Signature selector_nest_sig = "selector-nest($selectors...)"; - BUILT_IN(selector_nest) - { - List* arglist = ARG("$selectors", List); - - // Not enough parameters - if (arglist->length() == 0) { - error( - "$selectors: At least one selector must be passed for `selector-nest'", - pstate, traces); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Adds a [ParentSelector] to the beginning of [compound], + // or returns `null` if that wouldn't produce a valid selector. + CompoundSelector* prependParent(CompoundSelector* compound) { + SimpleSelector* first = compound->first(); + if (first->isUniversal()) return nullptr; + if (TypeSelector * type = first->isaTypeSelector()) { + if (type->hasNs()) return nullptr; + CompoundSelector* copy = SASS_MEMORY_COPY(compound); + copy->withExplicitParent(true); + return copy; } - - // Parse args into vector of selectors - SelectorStack parsedSelectors; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - ExpressionObj exp = Cast(arglist->value_at_index(i)); - if (exp->concrete_type() == Expression::NULL_VAL) { - error( - "$selectors: null is not a valid selector: it must be a string,\n" - "a list of strings, or a list of lists of strings for 'selector-nest'", - pstate, traces); - } - if (String_Constant_Obj str = Cast(exp)) { - str->quote_mark(0); - } - sass::string exp_src = exp->to_string(ctx.c_options); - ItplFile* source = SASS_MEMORY_NEW(ItplFile, exp_src.c_str(), exp->pstate()); - SelectorListObj sel = Parser::parse_selector(source, ctx, traces); - parsedSelectors.push_back(sel); + else { + CompoundSelector* copy = SASS_MEMORY_COPY(compound); + copy->withExplicitParent(true); + return copy; } + } - // Nothing to do - if( parsedSelectors.empty() ) { - return SASS_MEMORY_NEW(Null, pstate); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - // Set the first element as the `result`, keep - // appending to as we go down the parsedSelector vector. - SelectorStack::iterator itr = parsedSelectors.begin(); - SelectorListObj& result = *itr; - ++itr; - - for(;itr != parsedSelectors.end(); ++itr) { - SelectorListObj& child = *itr; - original_stack.push_back(result); - SelectorListObj rv = child->resolve_parent_refs(original_stack, traces); - result->elements(rv->elements()); - original_stack.pop_back(); - } + namespace Selectors { - return Cast(Listize::perform(result)); - } - - Signature selector_append_sig = "selector-append($selectors...)"; - BUILT_IN(selector_append) - { - List* arglist = ARG("$selectors", List); - - // Not enough parameters - if (arglist->empty()) { - error( - "$selectors: At least one selector must be " - "passed for `selector-append'", - pstate, traces); - } + /*******************************************************************/ - // Parse args into vector of selectors - SelectorStack parsedSelectors; - parsedSelectors.push_back({}); - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression* exp = Cast(arglist->value_at_index(i)); - if (exp->concrete_type() == Expression::NULL_VAL) { - error( - "$selectors: null is not a valid selector: it must be a string,\n" - "a list of strings, or a list of lists of strings for 'selector-append'", - pstate, traces); - } - if (String_Constant* str = Cast(exp)) { - str->quote_mark(0); + BUILT_IN_FN(nest) + { + // Not enough parameters + if (arguments[0]->lengthAsList() == 0) { + throw Exception::RuntimeException(compiler, + "$selectors: At least one selector must be passed."); } - sass::string exp_src = exp->to_string(); - ItplFile* source = SASS_MEMORY_NEW(ItplFile, exp_src.c_str(), exp->pstate()); - SelectorListObj sel = Parser::parse_selector(source, ctx, traces, true); - - for (auto& complex : sel->elements()) { - if (complex->empty()) { - complex->append(SASS_MEMORY_NEW(CompoundSelector, "[phony]")); - } - if (CompoundSelector* comp = Cast(complex->first())) { - comp->hasRealParent(true); - complex->chroots(true); + SelectorListObj result; + // Iterate over the rest argument list + for (Value* arg : arguments[0]->iterator()) { + if (arg->isNull()) { + compiler.addFinalStackTrace(arg->pstate()); + throw Exception::RuntimeException(compiler, // "$selectors: " + "null is not a valid selector: it must be a string,\n" + "a list of strings, or a list of lists of strings."); } + // Read and parse argument into selectors + SelectorListObj slist(arg->assertSelector( + compiler, Strings::empty, !result.isNull())); + // First item is just taken as it is + if (result.isNull()) { result = slist; continue; } + // Otherwise resolve it with the previous selector + result = slist->resolveParentSelectors(result, compiler); } + // Convert and return value + return result->toValue(); + } + + /*******************************************************************/ - if (parsedSelectors.size() > 1) { + BUILT_IN_FN(append) + { + // Not enough parameters + if (arguments[0]->lengthAsList() == 0) { + throw Exception::RuntimeException(compiler, + "$selectors: At least one selector must be passed."); + } - if (!sel->has_real_parent_ref()) { - auto parent = parsedSelectors.back(); - for (auto& complex : parent->elements()) { - if (CompoundSelector* comp = Cast(complex->first())) { - comp->hasRealParent(false); + SelectorListObj reduced; + for (Value* arg : arguments[0]->iterator()) { + if (arg->isNull()) { + throw Exception::RuntimeException( // "$selectors: " + "null is not a valid selector: it must be a string,\n" + "a list of strings, or a list of lists of strings.", + compiler, arg->pstate()); + } + SelectorListObj slist(arg->assertSelector( + compiler, Strings::empty, false)); + // First item is just taken as it is + if (reduced.isNull()) { reduced = slist; continue; } + + // Combine selector list with parent + SelectorListObj cp = SASS_MEMORY_COPY(slist); + for (ComplexSelector* complex : slist->elements()) { + SelectorComponent* component = complex->first(); + if (CompoundSelector* compound = component->isaCompoundSelector()) { + compound = prependParent(compound); + if (compound == nullptr) { + throw Exception::RuntimeException(compiler, + "Can't append " + slist->inspect() + " to " + + reduced->inspect() + "."); } + complex->at(0) = compound; + } + else { + throw Exception::RuntimeException(compiler, + "Can't append " + slist->inspect() + " to " + + reduced->inspect() + "."); } - error("Can't append \"" + sel->to_string() + "\" to \"" + - parent->to_string() + "\" for `selector-append'", - pstate, traces); } - // Build the resolved stack from the left. It's cheaper to directly - // calculate and update each resolved selcted from the left, than to - // recursively calculate them from the right side, as we would need - // to go through the whole stack depth to build the final results. - // E.g. 'a', 'b', 'x, y' => 'a' => 'a b' => 'a b x, a b y' - // vs 'a', 'b', 'x, y' => 'x' => 'b x' => 'a b x', 'y' ... - parsedSelectors.push_back(sel->resolve_parent_refs(parsedSelectors, traces, true)); - } - else { - parsedSelectors.push_back(sel); + // Otherwise resolve it with the previous selector + reduced = cp->resolveParentSelectors(reduced, compiler, false); + } + + return reduced->toValue(); } - // Nothing to do - if( parsedSelectors.empty() ) { - return SASS_MEMORY_NEW(Null, pstate); + /*******************************************************************/ + + BUILT_IN_FN(extend) + { + SelectorListObj selector = arguments[0]-> + assertSelector(compiler, "selector"); + SelectorListObj target = arguments[1]-> + assertSelector(compiler, "extendee"); + SelectorListObj source = arguments[2]-> + assertSelector(compiler, "extender"); + SelectorListObj result = Extender::extend(selector, source, target, compiler); + return result->toValue(); } - return Cast(Listize::perform(parsedSelectors.back())); - } + BUILT_IN_FN(replace) + { + SelectorListObj selector = arguments[0]-> + assertSelector(compiler, "selector"); + SelectorListObj target = arguments[1]-> + assertSelector(compiler, "original"); + SelectorListObj source = arguments[2]-> + assertSelector(compiler, "replacement"); + SelectorListObj result = Extender::replace(selector, source, target, compiler); + return result->toValue(); + } - Signature selector_unify_sig = "selector-unify($selector1, $selector2)"; - BUILT_IN(selector_unify) - { - SelectorListObj selector1 = ARGSELS("$selector1"); - SelectorListObj selector2 = ARGSELS("$selector2"); - SelectorListObj result = selector1->unifyWith(selector2); - return Cast(Listize::perform(result)); - } + BUILT_IN_FN(unify) + { + SelectorListObj selector1 = arguments[0]-> + assertSelector(compiler, "selector1"); + SelectorListObj selector2 = arguments[1]-> + assertSelector(compiler, "selector2"); + SelectorListObj result = selector1->unifyWith(selector2); + return result->toValue(); + } - Signature simple_selectors_sig = "simple-selectors($selector)"; - BUILT_IN(simple_selectors) - { - CompoundSelectorObj sel = ARGSEL("$selector"); + BUILT_IN_FN(isSuper) + { + SelectorListObj sel_sup = arguments[0]-> + assertSelector(compiler, "super"); + SelectorListObj sel_sub = arguments[1]-> + assertSelector(compiler, "sub"); + bool result = sel_sup->isSuperselectorOf(sel_sub); + return SASS_MEMORY_NEW(Boolean, pstate, result); + } - List* l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); + BUILT_IN_FN(simple) + { + CompoundSelectorObj selector = arguments[0]-> + assertCompoundSelector(compiler, "selector"); + ValueVector results; + for (auto child : selector->elements()) { + results.emplace_back(SASS_MEMORY_NEW(String, + child->pstate(), child->inspect())); + } + // Return new value list + return SASS_MEMORY_NEW(List, + selector->pstate(), + std::move(results), + SASS_COMMA); + } - for (size_t i = 0, L = sel->length(); i < L; ++i) { - const SimpleSelectorObj& ss = sel->get(i); - sass::string ss_string = ss->to_string() ; - l->append(SASS_MEMORY_NEW(String_Quoted, ss->pstate(), ss_string)); + BUILT_IN_FN(parse) + { + SelectorListObj selector = arguments[0]-> + assertSelector(compiler, "selector"); + return selector->toValue(); } - return l; - } + /*******************************************************************/ - Signature selector_extend_sig = "selector-extend($selector, $extendee, $extender)"; - BUILT_IN(selector_extend) - { - SelectorListObj selector = ARGSELS("$selector"); - SelectorListObj target = ARGSELS("$extendee"); - SelectorListObj source = ARGSELS("$extender"); - SelectorListObj result = Extender::extend(selector, source, target, traces); - return Cast(Listize::perform(result)); - } + void registerFunctions(Compiler& ctx) + { + ctx.registerBuiltInFunction("selector-nest", "$selectors...", nest); + ctx.registerBuiltInFunction("selector-append", "$selectors...", append); + ctx.registerBuiltInFunction("selector-extend", "$selector, $extendee, $extender", extend); + ctx.registerBuiltInFunction("selector-replace", "$selector, $original, $replacement", replace); + ctx.registerBuiltInFunction("selector-unify", "$selector1, $selector2", unify); + ctx.registerBuiltInFunction("is-superselector", "$super, $sub", isSuper); + ctx.registerBuiltInFunction("simple-selectors", "$selector", simple); + ctx.registerBuiltInFunction("selector-parse", "$selector", parse); + } - Signature selector_replace_sig = "selector-replace($selector, $original, $replacement)"; - BUILT_IN(selector_replace) - { - SelectorListObj selector = ARGSELS("$selector"); - SelectorListObj target = ARGSELS("$original"); - SelectorListObj source = ARGSELS("$replacement"); - SelectorListObj result = Extender::replace(selector, source, target, traces); - return Cast(Listize::perform(result)); - } + /*******************************************************************/ - Signature selector_parse_sig = "selector-parse($selector)"; - BUILT_IN(selector_parse) - { - SelectorListObj selector = ARGSELS("$selector"); - return Cast(Listize::perform(selector)); } - Signature is_superselector_sig = "is-superselector($super, $sub)"; - BUILT_IN(is_superselector) - { - SelectorListObj sel_sup = ARGSELS("$super"); - SelectorListObj sel_sub = ARGSELS("$sub"); - bool result = sel_sup->isSuperselectorOf(sel_sub); - return SASS_MEMORY_NEW(Boolean, pstate, result); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/fn_selectors.hpp b/src/fn_selectors.hpp index d5c106cd2b..95c9e8a444 100644 --- a/src/fn_selectors.hpp +++ b/src/fn_selectors.hpp @@ -1,5 +1,12 @@ -#ifndef SASS_FN_SELECTORS_H -#define SASS_FN_SELECTORS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_SELECTORS_HPP +#define SASS_FN_SELECTORS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include "fn_utils.hpp" @@ -7,26 +14,18 @@ namespace Sass { namespace Functions { - #define ARGSEL(argname) get_arg_sel(argname, env, sig, pstate, traces, ctx) - #define ARGSELS(argname) get_arg_sels(argname, env, sig, pstate, traces, ctx) - - BUILT_IN(selector_nest); - BUILT_IN(selector_append); - BUILT_IN(selector_extend); - BUILT_IN(selector_replace); - BUILT_IN(selector_unify); - BUILT_IN(is_superselector); - BUILT_IN(simple_selectors); - BUILT_IN(selector_parse); - - extern Signature selector_nest_sig; - extern Signature selector_append_sig; - extern Signature selector_extend_sig; - extern Signature selector_replace_sig; - extern Signature selector_unify_sig; - extern Signature is_superselector_sig; - extern Signature simple_selectors_sig; - extern Signature selector_parse_sig; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Selectors { + + // Register all functions at compiler + void registerFunctions(Compiler& ctx); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/fn_strings.cpp b/src/fn_strings.cpp deleted file mode 100644 index 58bf60733d..0000000000 --- a/src/fn_strings.cpp +++ /dev/null @@ -1,268 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "utf8.h" -#include "ast.hpp" -#include "fn_utils.hpp" -#include "fn_strings.hpp" -#include "util_string.hpp" - -namespace Sass { - - namespace Functions { - - void handle_utf8_error (const SourceSpan& pstate, Backtraces traces) - { - try { - throw; - } - catch (utf8::invalid_code_point&) { - sass::string msg("utf8::invalid_code_point"); - error(msg, pstate, traces); - } - catch (utf8::not_enough_room&) { - sass::string msg("utf8::not_enough_room"); - error(msg, pstate, traces); - } - catch (utf8::invalid_utf8&) { - sass::string msg("utf8::invalid_utf8"); - error(msg, pstate, traces); - } - catch (...) { throw; } - } - - /////////////////// - // STRING FUNCTIONS - /////////////////// - - Signature unquote_sig = "unquote($string)"; - BUILT_IN(sass_unquote) - { - AST_Node_Obj arg = env["$string"]; - if (String_Quoted* string_quoted = Cast(arg)) { - String_Constant* result = SASS_MEMORY_NEW(String_Constant, pstate, string_quoted->value()); - // remember if the string was quoted (color tokens) - result->is_delayed(true); // delay colors - return result; - } - else if (String_Constant* str = Cast(arg)) { - return str; - } - else if (Value* ex = Cast(arg)) { - Sass_Output_Style oldstyle = ctx.c_options.output_style; - ctx.c_options.output_style = SASS_STYLE_NESTED; - sass::string val(arg->to_string(ctx.c_options)); - val = Cast(arg) ? "null" : val; - ctx.c_options.output_style = oldstyle; - - deprecated_function("Passing " + val + ", a non-string value, to unquote()", pstate); - return ex; - } - throw std::runtime_error("Invalid Data Type for unquote"); - } - - Signature quote_sig = "quote($string)"; - BUILT_IN(sass_quote) - { - const String_Constant* s = ARG("$string", String_Constant); - String_Quoted *result = SASS_MEMORY_NEW( - String_Quoted, pstate, s->value(), - /*q=*/'\0', /*keep_utf8_escapes=*/false, /*skip_unquoting=*/true); - result->quote_mark('*'); - return result; - } - - Signature str_length_sig = "str-length($string)"; - BUILT_IN(str_length) - { - size_t len = sass::string::npos; - try { - String_Constant* s = ARG("$string", String_Constant); - len = UTF_8::code_point_count(s->value(), 0, s->value().size()); - - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - // return something even if we had an error (-1) - return SASS_MEMORY_NEW(Number, pstate, (double)len); - } - - Signature str_insert_sig = "str-insert($string, $insert, $index)"; - BUILT_IN(str_insert) - { - sass::string str; - try { - String_Constant* s = ARG("$string", String_Constant); - str = s->value(); - String_Constant* i = ARG("$insert", String_Constant); - sass::string ins = i->value(); - double index = ARGVAL("$index"); - if (index != (int)index) { - sass::ostream strm; - strm << "$index: "; - strm << std::to_string(index); - strm << " is not an int"; - error(strm.str(), pstate, traces); - } - size_t len = UTF_8::code_point_count(str, 0, str.size()); - - if (index > 0 && index <= len) { - // positive and within string length - str.insert(UTF_8::offset_at_position(str, static_cast(index) - 1), ins); - } - else if (index > len) { - // positive and past string length - str += ins; - } - else if (index == 0) { - str = ins + str; - } - else if (std::abs(index) <= len) { - // negative and within string length - index += len + 1; - str.insert(UTF_8::offset_at_position(str, static_cast(index)), ins); - } - else { - // negative and past string length - str = ins + str; - } - - if (String_Quoted* ss = Cast(s)) { - if (ss->quote_mark()) str = quote(str); - } - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - - Signature str_index_sig = "str-index($string, $substring)"; - BUILT_IN(str_index) - { - size_t index = sass::string::npos; - try { - String_Constant* s = ARG("$string", String_Constant); - String_Constant* t = ARG("$substring", String_Constant); - sass::string str = s->value(); - sass::string substr = t->value(); - - size_t c_index = str.find(substr); - if(c_index == sass::string::npos) { - return SASS_MEMORY_NEW(Null, pstate); - } - index = UTF_8::code_point_count(str, 0, c_index) + 1; - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - // return something even if we had an error (-1) - return SASS_MEMORY_NEW(Number, pstate, (double)index); - } - - Signature str_slice_sig = "str-slice($string, $start-at, $end-at:-1)"; - BUILT_IN(str_slice) - { - sass::string newstr; - try { - String_Constant* s = ARG("$string", String_Constant); - double start_at = ARGVAL("$start-at"); - double end_at = ARGVAL("$end-at"); - - if (start_at != (int)start_at) { - sass::ostream strm; - strm << "$start-at: "; - strm << std::to_string(start_at); - strm << " is not an int"; - error(strm.str(), pstate, traces); - } - - String_Quoted* ss = Cast(s); - - sass::string str(s->value()); - - size_t size = utf8::distance(str.begin(), str.end()); - - if (!Cast(env["$end-at"])) { - end_at = -1; - } - - if (end_at != (int)end_at) { - sass::ostream strm; - strm << "$end-at: "; - strm << std::to_string(end_at); - strm << " is not an int"; - error(strm.str(), pstate, traces); - } - - if (end_at == 0 || (end_at + size) < 0) { - if (ss && ss->quote_mark()) newstr = quote(""); - return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); - } - - if (end_at < 0) { - end_at += size + 1; - if (end_at == 0) end_at = 1; - } - if (end_at > size) { end_at = (double)size; } - if (start_at < 0) { - start_at += size + 1; - if (start_at <= 0) start_at = 1; - } - else if (start_at == 0) { ++ start_at; } - - if (start_at <= end_at) - { - sass::string::iterator start = str.begin(); - utf8::advance(start, start_at - 1, str.end()); - sass::string::iterator end = start; - utf8::advance(end, end_at - start_at + 1, str.end()); - newstr = sass::string(start, end); - } - if (ss) { - if(ss->quote_mark()) newstr = quote(newstr); - } - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); - } - - Signature to_upper_case_sig = "to-upper-case($string)"; - BUILT_IN(to_upper_case) - { - String_Constant* s = ARG("$string", String_Constant); - sass::string str = s->value(); - Util::ascii_str_toupper(&str); - - if (String_Quoted* ss = Cast(s)) { - String_Quoted* cpy = SASS_MEMORY_COPY(ss); - cpy->value(str); - return cpy; - } else { - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - } - - Signature to_lower_case_sig = "to-lower-case($string)"; - BUILT_IN(to_lower_case) - { - String_Constant* s = ARG("$string", String_Constant); - sass::string str = s->value(); - Util::ascii_str_tolower(&str); - - if (String_Quoted* ss = Cast(s)) { - String_Quoted* cpy = SASS_MEMORY_COPY(ss); - cpy->value(str); - return cpy; - } else { - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - } - - } - -} diff --git a/src/fn_strings.hpp b/src/fn_strings.hpp deleted file mode 100644 index 4a1ed19009..0000000000 --- a/src/fn_strings.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef SASS_FN_STRINGS_H -#define SASS_FN_STRINGS_H - -#include "fn_utils.hpp" - -namespace Sass { - - namespace Functions { - - extern Signature unquote_sig; - extern Signature quote_sig; - extern Signature str_length_sig; - extern Signature str_insert_sig; - extern Signature str_index_sig; - extern Signature str_slice_sig; - extern Signature to_upper_case_sig; - extern Signature to_lower_case_sig; - extern Signature length_sig; - - BUILT_IN(sass_unquote); - BUILT_IN(sass_quote); - BUILT_IN(str_length); - BUILT_IN(str_insert); - BUILT_IN(str_index); - BUILT_IN(str_slice); - BUILT_IN(to_upper_case); - BUILT_IN(to_lower_case); - BUILT_IN(length); - - } - -} - -#endif diff --git a/src/fn_texts.cpp b/src/fn_texts.cpp new file mode 100644 index 0000000000..e4dd32a73e --- /dev/null +++ b/src/fn_texts.cpp @@ -0,0 +1,215 @@ +#include "fn_texts.hpp" + +#include +#include "unicode.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" + +#ifdef __MINGW32__ +#include "windows.h" +#include "wincrypt.h" +#endif + +namespace Sass { + + namespace Functions { + + namespace Texts { + + long _codepointForIndex(long index, long lengthInCodepoints, bool allowNegative = false) { + if (index == 0) return 0; + if (index > 0) return std::min(index - 1, lengthInCodepoints); + long result = lengthInCodepoints + index; + if (result < 0 && !allowNegative) return 0; + return result; + } + + BUILT_IN_FN(unquote) + { + String* string = arguments[0]->assertString(compiler, "string"); + if (!string->hasQuotes()) return string; + sass::string copy(string->value()); + return SASS_MEMORY_NEW(String, + string->pstate(), std::move(copy), false); + } + + BUILT_IN_FN(quote) + { + if (Color* col = arguments[0]->isaColor()) { + if (!col->disp().empty()) { + sass::string copy(col->disp()); + return SASS_MEMORY_NEW(String, + arguments[0]->pstate(), std::move(copy), true); + } + } + String* string = arguments[0]->assertString(compiler, "string"); + if (string->hasQuotes()) return string; + sass::string copy(string->value()); + return SASS_MEMORY_NEW(String, + string->pstate(), std::move(copy), true); + } + + BUILT_IN_FN(toUpperCase) + { + String* string = arguments[0]->assertString(compiler, "string"); + return SASS_MEMORY_NEW(String, pstate, + StringUtils::toUpperCase(string->value()), + string->hasQuotes()); + } + + BUILT_IN_FN(toLowerCase) + { + String* string = arguments[0]->assertString(compiler, "string"); + return SASS_MEMORY_NEW(String, pstate, + StringUtils::toLowerCase(string->value()), + string->hasQuotes()); + } + + BUILT_IN_FN(length) + { + String* string = arguments[0]->assertString(compiler, "string"); + size_t len = Unicode::codePointCount(string->value()); + return SASS_MEMORY_NEW(Number, pstate, (double)len); + } + + BUILT_IN_FN(insert) + { + String* string = arguments[0]->assertString(compiler, "string"); + String* insert = arguments[1]->assertString(compiler, "insert"); + size_t len = Unicode::codePointCount(string->value()); + long index = arguments[2]->assertNumber(compiler, "index") + ->assertUnitless(compiler, "index") + ->assertInt(compiler, "index"); + + // str-insert has unusual behavior for negative inputs. It guarantees that + // the `$insert` string is at `$index` in the result, which means that we + // want to insert before `$index` if it's positive and after if it's + // negative. + if (index < 0) { + // +1 because negative indexes start counting from -1 rather than 0, and + // another +1 because we want to insert *after* that index. + index = (long)len + index + 2; + } + + index = (long) _codepointForIndex(index, (long)len); + + sass::string str(string->value()); + str.insert(Unicode::byteOffsetAtPosition( + str, index), insert->value()); + + return SASS_MEMORY_NEW(String, + pstate, std::move(str), + string->hasQuotes()); + } + + BUILT_IN_FN(index) + { + String* string = arguments[0]->assertString(compiler, "string"); + String* substring = arguments[1]->assertString(compiler, "substring"); + + sass::string str(string->value()); + sass::string substr(substring->value()); + + size_t c_index = str.find(substr); + if (c_index == sass::string::npos) { + return SASS_MEMORY_NEW(Null, pstate); + } + + return SASS_MEMORY_NEW(Number, pstate, + (double)Unicode::codePointCount(str, c_index) + 1); + } + + BUILT_IN_FN(slice) + { + String* string = arguments[0]->assertString(compiler, "string"); + Number* beg = arguments[1]->assertNumber(compiler, "start-at"); + Number* end = arguments[2]->assertNumber(compiler, "end-at"); + size_t len = Unicode::codePointCount(string->value()); + beg = beg->assertUnitless(compiler, "start"); + end = end->assertUnitless(compiler, "end"); + long begInt = beg->assertInt(compiler, "start"); + long endInt = end->assertInt(compiler, "end"); + + // No matter what the start index is, an end + // index of 0 will produce an empty string. + if (endInt == 0) { + return SASS_MEMORY_NEW(String, + pstate, "", string->hasQuotes()); + } + + begInt = (long)_codepointForIndex(begInt, (long)len, false); + endInt = (long)_codepointForIndex(endInt, (long)len, true); + + if (endInt == (long)len) endInt = (long)len - 1; + if (endInt < begInt) { + return SASS_MEMORY_NEW(String, + pstate, "", string->hasQuotes()); + } + + const sass::string& value(string->value()); + sass::string::const_iterator begIt = value.begin(); + sass::string::const_iterator endIt = value.begin(); + utf8::advance(begIt, begInt + 0, value.end()); + utf8::advance(endIt, endInt + 1, value.end()); + + return SASS_MEMORY_NEW( + String, pstate, + sass::string(begIt, endIt), + string->hasQuotes()); + + } + + BUILT_IN_FN(uniqueId) + { + sass::sstream ss; ss << "u" + << std::setfill('0') << std::setw(8) + << std::hex << getRandomUint32(); + return SASS_MEMORY_NEW(String, + pstate, ss.str(), true); + } + + void registerFunctions(Compiler& ctx) + { + + ctx.registerBuiltInFunction("unquote", "$string", unquote); + ctx.registerBuiltInFunction("quote", "$string", quote); + ctx.registerBuiltInFunction("to-upper-case", "$string", toUpperCase); + ctx.registerBuiltInFunction("to-lower-case", "$string", toLowerCase); + ctx.registerBuiltInFunction("str-length", "$string", length); + ctx.registerBuiltInFunction("str-insert", "$string, $insert, $index", insert); + ctx.registerBuiltInFunction("str-index", "$string, $substring", index); + ctx.registerBuiltInFunction("str-slice", "$string, $start-at, $end-at: -1", slice); + ctx.registerBuiltInFunction("unique-id", "", uniqueId); + + } + + } + + + void handle_utf8_error (const SourceSpan& pstate, BackTraces traces) + { + try { + throw; + } + catch (utf8::invalid_code_point&) { + traces.push_back(pstate); + throw Exception::RuntimeException(traces, + "utf8::invalid_code_point"); + } + catch (utf8::not_enough_room&) { + traces.push_back(pstate); + throw Exception::RuntimeException(traces, + "utf8::not_enough_room"); + } + catch (utf8::invalid_utf8&) { + traces.push_back(pstate); + throw Exception::RuntimeException(traces, + "utf8::invalid_utf8"); + } + catch (...) { throw; } + } + + } + +} diff --git a/src/fn_texts.hpp b/src/fn_texts.hpp new file mode 100644 index 0000000000..fe6f310638 --- /dev/null +++ b/src/fn_texts.hpp @@ -0,0 +1,34 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_TEXTS_HPP +#define SASS_FN_TEXTS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "fn_utils.hpp" + +namespace Sass { + + namespace Functions { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Texts { + + // Register all functions at compiler + void registerFunctions(Compiler& ctx); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + } + +} + +#endif diff --git a/src/fn_utils.cpp b/src/fn_utils.cpp deleted file mode 100644 index bcf5363973..0000000000 --- a/src/fn_utils.cpp +++ /dev/null @@ -1,158 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "parser.hpp" -#include "fn_utils.hpp" -#include "util_string.hpp" - -namespace Sass { - - Definition* make_native_function(Signature sig, Native_Function func, Context& ctx) - { - SourceFile* source = SASS_MEMORY_NEW(SourceFile, "[built-in function]", sig, std::string::npos); - Parser sig_parser(source, ctx, ctx.traces); - sig_parser.lex(); - sass::string name(Util::normalize_underscores(sig_parser.lexed)); - Parameters_Obj params = sig_parser.parse_parameters(); - return SASS_MEMORY_NEW(Definition, - SourceSpan(source), - sig, - name, - params, - func, - false); - } - - Definition* make_c_function(Sass_Function_Entry c_func, Context& ctx) - { - using namespace Prelexer; - const char* sig = sass_function_get_signature(c_func); - SourceFile* source = SASS_MEMORY_NEW(SourceFile, "[c function]", sig, std::string::npos); - Parser sig_parser(source, ctx, ctx.traces); - // allow to overload generic callback plus @warn, @error and @debug with custom functions - sig_parser.lex < alternatives < identifier, exactly <'*'>, - exactly < Constants::warn_kwd >, - exactly < Constants::error_kwd >, - exactly < Constants::debug_kwd > - > >(); - sass::string name(Util::normalize_underscores(sig_parser.lexed)); - Parameters_Obj params = sig_parser.parse_parameters(); - return SASS_MEMORY_NEW(Definition, - SourceSpan(source), - sig, - name, - params, - c_func); - } - - namespace Functions { - - sass::string function_name(Signature sig) - { - sass::string str(sig); - return str.substr(0, str.find('(')); - } - - Map* get_arg_m(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces) - { - AST_Node* value = env[argname]; - if (Map* map = Cast(value)) return map; - List* list = Cast(value); - if (list && list->length() == 0) { - return SASS_MEMORY_NEW(Map, pstate, 0); - } - return get_arg(argname, env, sig, pstate, traces); - } - - double get_arg_r(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces, double lo, double hi) - { - Number* val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - double v = tmpnr.value(); - if (!(lo <= v && v <= hi)) { - sass::ostream msg; - msg << "argument `" << argname << "` of `" << sig << "` must be between "; - msg << lo << " and " << hi; - error(msg.str(), pstate, traces); - } - return v; - } - - Number* get_arg_n(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces) - { - Number* val = get_arg(argname, env, sig, pstate, traces); - val = SASS_MEMORY_COPY(val); - val->reduce(); - return val; - } - - double get_arg_val(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces) - { - Number* val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - return tmpnr.value(); - } - - double color_num(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces) - { - Number* val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - if (tmpnr.unit() == "%") { - return std::min(std::max(tmpnr.value() * 255 / 100.0, 0.0), 255.0); - } else { - return std::min(std::max(tmpnr.value(), 0.0), 255.0); - } - } - - double alpha_num(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces) { - Number* val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - if (tmpnr.unit() == "%") { - return std::min(std::max(tmpnr.value(), 0.0), 100.0); - } else { - return std::min(std::max(tmpnr.value(), 0.0), 1.0); - } - } - - SelectorListObj get_arg_sels(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces, Context& ctx) { - ExpressionObj exp = ARG(argname, Expression); - if (exp->concrete_type() == Expression::NULL_VAL) { - sass::ostream msg; - msg << argname << ": null is not a valid selector: it must be a string,\n"; - msg << "a list of strings, or a list of lists of strings for `" << function_name(sig) << "'"; - error(msg.str(), exp->pstate(), traces); - } - if (String_Constant* str = Cast(exp)) { - str->quote_mark(0); - } - sass::string exp_src = exp->to_string(ctx.c_options); - ItplFile* source = SASS_MEMORY_NEW(ItplFile, exp_src.c_str(), exp->pstate()); - return Parser::parse_selector(source, ctx, traces, false); - } - - CompoundSelectorObj get_arg_sel(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces, Context& ctx) { - ExpressionObj exp = ARG(argname, Expression); - if (exp->concrete_type() == Expression::NULL_VAL) { - sass::ostream msg; - msg << argname << ": null is not a string for `" << function_name(sig) << "'"; - error(msg.str(), exp->pstate(), traces); - } - if (String_Constant* str = Cast(exp)) { - str->quote_mark(0); - } - sass::string exp_src = exp->to_string(ctx.c_options); - ItplFile* source = SASS_MEMORY_NEW(ItplFile, exp_src.c_str(), exp->pstate()); - SelectorListObj sel_list = Parser::parse_selector(source, ctx, traces, false); - if (sel_list->length() == 0) return {}; - return sel_list->first()->first(); - } - - - } - -} diff --git a/src/fn_utils.hpp b/src/fn_utils.hpp index 7f9a354f40..7013228d17 100644 --- a/src/fn_utils.hpp +++ b/src/fn_utils.hpp @@ -1,62 +1,87 @@ -#ifndef SASS_FN_UTILS_H -#define SASS_FN_UTILS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_UTILS_HPP +#define SASS_FN_UTILS_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include "units.hpp" -#include "backtrace.hpp" -#include "environment.hpp" -#include "ast_fwd_decl.hpp" -#include "error_handling.hpp" +// Make some macros available +#include "ast_fwd_decl.hpp" namespace Sass { - #define FN_PROTOTYPE \ - Env& env, \ - Env& d_env, \ - Context& ctx, \ - Signature sig, \ - SourceSpan pstate, \ - Backtraces& traces, \ - SelectorStack selector_stack, \ - SelectorStack original_stack \ - - typedef const char* Signature; - typedef PreValue* (*Native_Function)(FN_PROTOTYPE); - #define BUILT_IN(name) PreValue* name(FN_PROTOTYPE) - - #define ARG(argname, argtype) get_arg(argname, env, sig, pstate, traces) - // special function for weird hsla percent (10px == 10% == 10 != 0.1) - #define ARGVAL(argname) get_arg_val(argname, env, sig, pstate, traces) // double - - Definition* make_native_function(Signature, Native_Function, Context& ctx); - Definition* make_c_function(Sass_Function_Entry c_func, Context& ctx); - - namespace Functions { - - template - T* get_arg(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces) - { - T* val = Cast(env[argname]); - if (!val) { - error("argument `" + argname + "` of `" + sig + "` must be a " + T::type_name(), pstate, traces); - } - return val; - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Returns whether [lhs] and [rhs] are equal within [epsilon]. + inline bool fuzzyEquals(double lhs, double rhs, double epsilon) { + return fabs(lhs - rhs) < epsilon; + } + + // Returns whether [lhs] is less than [rhs], and not [fuzzyEquals]. + inline bool fuzzyLessThan(double lhs, double rhs, double epsilon) { + return lhs < rhs && !fuzzyEquals(lhs, rhs, epsilon); + } + + // Returns whether [lhs] is less than [rhs], or [fuzzyEquals]. + inline bool fuzzyLessThanOrEquals(double lhs, double rhs, double epsilon) { + return lhs < rhs || fuzzyEquals(lhs, rhs, epsilon); + } + + // Returns whether [lhs] is greater than [rhs], and not [fuzzyEquals]. + inline bool fuzzyGreaterThan(double lhs, double rhs, double epsilon) { + return lhs > rhs && !fuzzyEquals(lhs, rhs, epsilon); + } - Map* get_arg_m(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces); // maps only - Number* get_arg_n(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces); // numbers only - double alpha_num(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces); // colors only - double color_num(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces); // colors only - double get_arg_r(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces, double lo, double hi); // colors only - double get_arg_val(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces); // shared - SelectorListObj get_arg_sels(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces, Context& ctx); // selectors only - CompoundSelectorObj get_arg_sel(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces, Context& ctx); // selectors only + // Returns whether [lhs] is greater than [rhs], or [fuzzyEquals]. + inline bool fuzzyGreaterThanOrEquals(double lhs, double rhs, double epsilon) { + return lhs > rhs || fuzzyEquals(lhs, rhs, epsilon); + } + // Returns whether [number] is [fuzzyEquals] to an integer. + inline bool fuzzyIsInt(double number, double epsilon) { + // Check against 0.5 rather than 0.0 so that we catch numbers that + // are both very slightly above an integer, and very slightly below. + double _fabs_ = fabs(number - 0.5); + double _fmod_ = fmod(_fabs_, 1.0); + return fuzzyEquals(_fmod_, 0.5, epsilon); } + // Rounds [number] to the nearest integer. + // This rounds up numbers that are [fuzzyEquals] to `X.5`. + inline long fuzzyRound(double number, double epsilon) { + // If the number is within epsilon of X.5, + // round up (or down for negative numbers). + if (number > 0) { + return lround(fuzzyLessThan( + fmod(number, 1.0), 0.5, epsilon) + ? floor(number) : ceill(number)); + } + return lround(fuzzyLessThanOrEquals( + fmod(number, 1.0), - 0.5, epsilon) + ? floorl(number) : ceill(number)); + } + + // Returns `true` if it's within [min] and [max], + // or [number] is [fuzzyEquals] to [min] or [max]. + inline bool fuzzyCheckRange(double number, double min, double max, double epsilon) + { + return (number > min&& number < max) + || fuzzyEquals(number, min, epsilon) + || fuzzyEquals(number, max, epsilon); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + #define BUILT_IN_FN(name) Value* name(FN_PROTOTYPE2) + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } #endif diff --git a/src/highlighter.cpp b/src/highlighter.cpp new file mode 100644 index 0000000000..722270ed95 --- /dev/null +++ b/src/highlighter.cpp @@ -0,0 +1,19 @@ +#include "highlighter.hpp" + +namespace Sass { + + Hightlighter::Hightlighter( + ParserState pstate, + std::string color) : + pstate(pstate), + color(color), + multiline(false), + paddingBeforeSidebar(0), + spacesPerTab(4) + { + // auto newSpan = _normalizeContext(span); + // newSpan = _normalizeNewlines(newSpan); + // newSpan = _normalizeTrailingNewline(newSpan); + // newSpan = _normalizeEndOfLine(newSpan); + } +} diff --git a/src/highlighter.hpp b/src/highlighter.hpp new file mode 100644 index 0000000000..f7ff27d559 --- /dev/null +++ b/src/highlighter.hpp @@ -0,0 +1,47 @@ +#ifndef SASS_HIGHLIGHTER_H +#define SASS_HIGHLIGHTER_H + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include "ast.hpp" + +#include +#include + +namespace Sass { + + class Hightlighter { + + private: + + ParserState pstate; + + // The color to highlight [_span] within its context, + // or empty if the span should not be colored. + std::string color; + + // Whether [_span] covers multiple lines. + bool multiline; + + // The number of characters before the bar in the sidebar. + size_t paddingBeforeSidebar; + + // The buffer to which to write the result. + std::stringstream buffer; + + /// The number of spaces to render for hard tabs that appear + /// in `_span.text`. We don't want to render raw tabs, because + /// they'll mess up our character alignment. + size_t spacesPerTab = 4; + + Hightlighter(ParserState pstate, + std::string color /* = default */); + + // static SourceSpanWithContext _normalizeContext(SourceSpan span); + }; + + +} + +#endif diff --git a/src/inspect.cpp b/src/inspect.cpp index 4d079bed8b..5a5c90946f 100644 --- a/src/inspect.cpp +++ b/src/inspect.cpp @@ -1,108 +1,242 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +#include "inspect.hpp" -#include -#include -#include #include -#include -#include - -#include "ast.hpp" -#include "inspect.hpp" -#include "context.hpp" -#include "listize.hpp" -#include "color_maps.hpp" -#include "utf8/checked.h" +#include "file.hpp" +#include "ast_css.hpp" +#include "charcode.hpp" +#include "character.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" +#include "ast_selectors.hpp" namespace Sass { - Inspect::Inspect(const Emitter& emi) - : Emitter(emi) - { } - Inspect::~Inspect() { } + // Import some namespaces + using namespace Charcode; + using namespace Character; - // statements - void Inspect::operator()(Block* block) + Inspect::Inspect(SassOutputOptionsCpp& opt, bool srcmap_enabled) + : Emitter(opt, srcmap_enabled), quotes(true), inspect(false) { - if (!block->is_root()) { - add_open_mapping(block); - append_scope_opener(); - } - if (output_style() == NESTED) indentation += block->tabs(); - for (size_t i = 0, L = block->length(); i < L; ++i) { - (*block)[i]->perform(this); - } - if (output_style() == NESTED) indentation -= block->tabs(); - if (!block->is_root()) { - append_scope_closer(); - add_close_mapping(block); - } - } - void Inspect::operator()(StyleRule* ruleset) + Inspect::Inspect(Logger& logger, SassOutputOptionsCpp& opt, bool srcmap_enabled) + : Emitter(opt, srcmap_enabled), quotes(true), inspect(false) { - if (ruleset->selector()) { - ruleset->selector()->perform(this); - } - if (ruleset->block()) { - ruleset->block()->perform(this); - } } - void Inspect::operator()(Keyframe_Rule* rule) + void Inspect::acceptCssString(CssString* node) { - if (rule->name()) rule->name()->perform(this); - if (rule->block()) rule->block()->perform(this); + append_token(node->text(), node); } - void Inspect::operator()(Bubble* bubble) + void Inspect::visitBlockStatements(CssNodeVector children) { - append_indentation(); - append_token("::BUBBLE", bubble); append_scope_opener(); - bubble->node()->perform(this); + for (CssNode* stmt : children) { + stmt->accept(this); + } append_scope_closer(); } - void Inspect::operator()(MediaRule* rule) + void Inspect::renderUnquotedString(const sass::string& text) { - append_indentation(); - append_token("@media", rule); - append_mandatory_space(); - if (rule->block()) { - rule->block()->perform(this); + bool afterNewline = false; + for (size_t i = 0; i < text.size(); i++) { + uint8_t chr = text[i]; + switch (chr) { + case $lf: + append_char($space); + afterNewline = true; + break; + + case $space: + if (!afterNewline) { + append_char($space); + } + break; + + default: + append_char(chr); + afterNewline = false; + break; + } } } - void Inspect::operator()(CssMediaRule* rule) + void Inspect::renderQuotedString(const sass::string& text, uint8_t quotes) + { + + // Scan the string first, dart-sass seems to do some fancy + // trick by calling itself recursively when it encounters a + // conflicting quote during the output, throwing away buffers. + bool includesSingleQuote = text.find($single_quote) != sass::string::npos; + bool includesDoubleQuote = text.find($double_quote) != sass::string::npos; + + // If both quotes are encountered + if (quotes == $nul) { + if (includesSingleQuote) quotes = $double_quote; + else if (includesDoubleQuote) quotes = $single_quote; + else quotes = $double_quote; + } + + append_char(quotes); + + uint8_t chr, next; + for (size_t i = 0, iL = text.size(); i < iL; i++) { + chr = text[i]; + switch (chr) { + case $single_quote: + if (quotes == $single_quote) { + append_char($backslash); + } + append_char($single_quote); + break; + case $double_quote: + if (quotes == $double_quote) { + append_char($backslash); + } + append_char($double_quote); + break; + // Write newline characters and unprintable ASCII characters as escapes. + case $nul: + case $soh: + case $stx: + case $etx: + case $eot: + case $enq: + case $ack: + case $bel: + case $bs: + case $lf: + case $vt: + case $ff: + case $cr: + case $so: + case $si: + case $dle: + case $dc1: + case $dc2: + case $dc3: + case $dc4: + case $nak: + case $syn: + case $etb: + case $can: + case $em: + case $sub: + case $esc: + case $fs: + case $gs: + case $rs: + case $us: + append_char($backslash); + if (chr > 0xF) append_char(hexCharFor(chr >> 4)); + append_char(hexCharFor(chr & 0xF)); + if (iL == i + 1) break; + next = text[i+1]; + if (isHex(next) || next == $space || next == $tab) { + append_char($space); + } + break; + case $backslash: + append_char($backslash); + append_char($backslash); + break; + default: + append_char(chr); + break; + } + } + + append_char(quotes); + + } + // EO renderQuotedString + + + void Inspect::visitCssMediaRule(CssMediaRule* node) { - if (output_style() == NESTED) - indentation += rule->tabs(); append_indentation(); - append_token("@media", rule); + append_token("@media", node); append_mandatory_space(); - in_media_block = true; bool joinIt = false; - for (auto query : rule->elements()) { + for (auto query : node->queries()) { if (joinIt) { append_comma_separator(); append_optional_space(); } - operator()(query); + acceptCssMediaQuery(query); joinIt = true; } - if (rule->block()) { - rule->block()->perform(this); + visitBlockStatements(node->elements()); + } + // EO visitCssMediaRule + + void Inspect::visitCssStyleRule(CssStyleRule* node) + { + SelectorListObj s = node->selector(); + + if (!s || s->empty()) return; + if (!node || node->isInvisibleCss()) return; + + // if (output_style() == SASS_STYLE_NESTED) { + // indentation += node->tabs(); + // } + + if (opt.source_comments) { + sass::sstream ss; + append_indentation(); + sass::string path(File::abs2rel(node->pstate().getAbsPath(), ".", CWD)); // ToDo: optimize + ss << "/* line " << node->pstate().getLine() << ", " << path << " */"; + append_string(ss.str()); + append_optional_linefeed(); + } + + scheduled_crutch = s; + if (s) visitSelectorList(s); + append_scope_opener(node); + + for (size_t i = 0, L = node->size(); i < L; ++i) { + node->get(i)->accept(this); // XX } - in_media_block = false; - if (output_style() == NESTED) - indentation -= rule->tabs(); + + // if (output_style() == SASS_STYLE_NESTED) { + // indentation -= node->tabs(); + // } + append_scope_closer(node); } + // EO visitCssStyleRule - void Inspect::operator()(CssMediaQuery* query) + void Inspect::visitCssSupportsRule(CssSupportsRule* rule) + { + if (rule == nullptr) return; + if (rule->isInvisibleCss()) return; + + // if (output_style() == SASS_STYLE_NESTED) { + // indentation += rule->tabs(); + // } + + append_indentation(); + append_token("@supports", rule); + append_mandatory_space(); + rule->condition()->accept(this); + append_scope_opener(); + + size_t L = rule->size(); + for (size_t i = 0; i < L; ++i) { + rule->get(i)->accept(this); + if (i < L - 1) append_special_linefeed(); + } + + // if (output_style() == SASS_STYLE_NESTED) { + // indentation -= rule->tabs(); + // } + + append_scope_closer(); + } + + void Inspect::acceptCssMediaQuery(CssMediaQuery* query) { bool joinIt = false; if (!query->modifier().empty()) { @@ -124,510 +258,520 @@ namespace Sass { } } - void Inspect::operator()(SupportsRule* feature_block) - { - append_indentation(); - append_token("@supports", feature_block); - append_mandatory_space(); - feature_block->condition()->perform(this); - feature_block->block()->perform(this); - } - - void Inspect::operator()(AtRootRule* at_root_block) - { - append_indentation(); - append_token("@at-root ", at_root_block); - append_mandatory_space(); - if(at_root_block->expression()) at_root_block->expression()->perform(this); - if(at_root_block->block()) at_root_block->block()->perform(this); - } - - void Inspect::operator()(AtRule* at_rule) + void Inspect::visitCssComment(CssComment* c) { - append_indentation(); - append_token(at_rule->keyword(), at_rule); - if (at_rule->selector()) { - append_mandatory_space(); - bool was_wrapped = in_wrapped; - in_wrapped = true; - at_rule->selector()->perform(this); - in_wrapped = was_wrapped; - } - if (at_rule->value()) { - append_mandatory_space(); - at_rule->value()->perform(this); + bool important = c->isPreserved(); + if (output_style() == SASS_STYLE_COMPRESSED || output_style() == SASS_STYLE_COMPACT) { + if (!important) return; } - if (at_rule->block()) { - at_rule->block()->perform(this); - } - else { - append_delimiter(); + if (output_style() != SASS_STYLE_COMPRESSED || important) { + append_indentation(); + append_string(c->text()); + if (indentation == 0) { + append_mandatory_linefeed(); + } + else { + append_optional_linefeed(); + } } } + // EO visitCssComment - void Inspect::operator()(Declaration* dec) + void Inspect::visitCssDeclaration(CssDeclaration* node) { - if (dec->value()->concrete_type() == Expression::NULL_VAL) return; - bool was_decl = in_declaration; - in_declaration = true; - LOCAL_FLAG(in_custom_property, dec->is_custom_property()); - - if (output_style() == NESTED) - indentation += dec->tabs(); + LOCAL_FLAG(in_declaration, true); + LOCAL_FLAG(in_custom_property, + node->is_custom_property()); + // if (output_style() == SASS_STYLE_NESTED) + // indentation += node->tabs(); append_indentation(); - if (dec->property()) - dec->property()->perform(this); - append_colon_separator(); - - if (dec->value()->concrete_type() == Expression::SELECTOR) { - ExpressionObj ls = Listize::perform(dec->value()); - ls->perform(this); - } else { - dec->value()->perform(this); + if (node->name()) { + acceptCssString(node->name()); } - - if (dec->is_important()) { - append_optional_space(); - append_string("!important"); + append_colon_separator(); + if (node->value()) { + node->value()->accept(this); } append_delimiter(); - if (output_style() == NESTED) - indentation -= dec->tabs(); - in_declaration = was_decl; + // if (output_style() == SASS_STYLE_NESTED) + // indentation -= node->tabs(); } + // EO visitCssDeclaration - void Inspect::operator()(Assignment* assn) + // statements + void Inspect::visitCssRoot(CssRoot* block) { - append_token(assn->variable(), assn); - append_colon_separator(); - assn->value()->perform(this); - if (assn->is_default()) { - append_optional_space(); - append_string("!default"); + for (size_t i = 0, L = block->size(); i < L; ++i) { + (*block)[i]->accept(this); // XX } - append_delimiter(); + } - void Inspect::operator()(Import* import) + void Inspect::visitCssKeyframeBlock(CssKeyframeBlock* node) { - if (!import->urls().empty()) { - append_token("@import", import); - append_mandatory_space(); + if (node->selector()) { - import->urls().front()->perform(this); - if (import->urls().size() == 1) { - if (import->import_queries()) { - append_mandatory_space(); - import->import_queries()->perform(this); - } - } - append_delimiter(); - for (size_t i = 1, S = import->urls().size(); i < S; ++i) { - append_mandatory_linefeed(); - append_token("@import", import); - append_mandatory_space(); + const sass::vector& selector + = node->selector()->texts(); - import->urls()[i]->perform(this); - if (import->urls().size() - 1 == i) { - if (import->import_queries()) { - append_mandatory_space(); - import->import_queries()->perform(this); + if (!selector.empty()) { + append_indentation(); + bool addComma = false; + for (sass::string sel : selector) { + if (addComma) { + append_comma_separator(); } + append_string(sel); + addComma = true; } - append_delimiter(); } + } - } + // StringLiteralObj v2 = node->name2(); + // + // if (!v2.isNull()) { + // append_indentation(); + // v2->accept(this); + // } + // + // append_scope_opener(); + // for (size_t i = 0, L = r->size(); i < L; ++i) { + // Statement_Obj stm = r->get(i); + // stm->accept(this); + // if (i < L - 1) append_special_linefeed(); + // } + // append_scope_closer(); + if (!node->isInvisibleCss()) { + append_scope_opener(); + for (CssNode* child : node->elements()) { + child->accept(this); + } + append_scope_closer(); + } + } - void Inspect::operator()(Import_Stub* import) + void Inspect::visitCssAtRule(CssAtRule* node) { append_indentation(); - append_token("@import", import); - append_mandatory_space(); - append_string(import->imp_path()); - append_delimiter(); + if (node->name()) { + append_char($at); + acceptCssString(node->name()); + } + if (node->value()) { + append_mandatory_space(); + acceptCssString(node->value()); + } + if (node->isChildless()) { + append_delimiter(); + } + else { + visitBlockStatements(node->elements()); + } } - void Inspect::operator()(WarningRule* warning) + void Inspect::visitCssImport(CssImport* import) { append_indentation(); - append_token("@warn", warning); + add_open_mapping(import); + append_string("@import"); append_mandatory_space(); - warning->message()->perform(this); + CssString* url(import->url()); + append_token(url->text(), url); + if (import->supports()) { + append_mandatory_space(); + CssString* text(import->supports()); + append_token(text->text(), text); + } + if (!import->media().empty()) { + bool first = true; + append_mandatory_space(); + for (CssMediaQueryObj query : import->media()) { + if (first == false) { + append_char($comma); + append_optional_space(); + } + acceptCssMediaQuery(query); + first = false; + } + } + add_close_mapping(import); append_delimiter(); } - void Inspect::operator()(ErrorRule* error) + void Inspect::_writeMapElement(Interpolant* itpl) { - append_indentation(); - append_token("@error", error); - append_mandatory_space(); - error->message()->perform(this); - append_delimiter(); + if (Value * value = itpl->isaValue()) { + bool needsParens = false; + if (List * list = value->isaList()) { + needsParens = list->separator() == SASS_COMMA; + if (list->hasBrackets()) needsParens = false; + } + if (needsParens) { + append_char($lparen); + value->accept(this); + append_char($rparen); + } + else { + value->accept(this); + } + } + else if (ItplString* str = itpl->isaItplString()) { + append_token(str->text(), str); + } + else { + throw std::runtime_error("Expression not evaluated"); + } } - void Inspect::operator()(DebugRule* debug) + void Inspect::acceptNameSpaceSelector(NameSpaceSelector* selector) { - append_indentation(); - append_token("@debug", debug); - append_mandatory_space(); - debug->value()->perform(this); - append_delimiter(); + flush_schedules(); + add_open_mapping(selector); + if (selector->hasNs()) { + write_string(selector->ns()); + write_char($pipe); + } + write_string(selector->name()); + add_close_mapping(selector); } - void Inspect::operator()(Comment* comment) + void Inspect::visitAttributeSelector(AttributeSelector* attribute) { - in_comment = true; - comment->text()->perform(this); - in_comment = false; + append_string("["); + add_open_mapping(attribute); + acceptNameSpaceSelector(attribute); + if (!attribute->op().empty()) { + append_string(attribute->op()); + if (attribute->isIdentifier() && !StringUtils::startsWith(attribute->value(), "--", 2)) { + append_string(attribute->value()); + if (attribute->modifier() != 0) { + append_optional_space(); + } + } + else { + renderQuotedString(attribute->value()); + if (attribute->modifier() != 0) { + append_optional_space(); + } + } + } + add_close_mapping(attribute); + if (attribute->modifier() != 0) { + append_mandatory_space(); + append_char(attribute->modifier()); + } + append_string("]"); } - void Inspect::operator()(If* cond) + void Inspect::visitClassSelector(ClassSelector* klass) { - append_indentation(); - append_token("@if", cond); - append_mandatory_space(); - cond->predicate()->perform(this); - cond->block()->perform(this); - if (cond->alternative()) { - append_optional_linefeed(); - append_indentation(); - append_string("else"); - cond->alternative()->perform(this); + flush_schedules(); + add_open_mapping(klass); + // hotfix for browser issues + // this is pretty ugly indeed + if (scheduled_crutch) { + add_open_mapping(scheduled_crutch); + scheduled_crutch = 0; } + append_string(klass->name()); + add_close_mapping(klass); } - void Inspect::operator()(ForRule* loop) + void Inspect::visitComplexSelector(ComplexSelector* complex) { - append_indentation(); - append_token("@for", loop); - append_mandatory_space(); - append_string(loop->variable()); - append_string(" from "); - loop->lower_bound()->perform(this); - append_string(loop->is_inclusive() ? " through " : " to "); - loop->upper_bound()->perform(this); - loop->block()->perform(this); + bool many = false; + if (complex->hasPreLineFeed()) { + append_optional_linefeed(); + } + for (SelectorComponentObj& item : complex->elements()) { + if (many) append_optional_space(); + if (SelectorCombinator* combinator = item->isaSelectorCombinator()) { + visitSelectorCombinator(combinator); + } + else if (CompoundSelector* compound = item->isaCompoundSelector()) { + visitCompoundSelector(compound); + } + many = true; + } } - void Inspect::operator()(EachRule* loop) + void Inspect::visitCompoundSelector(CompoundSelector* compound) { - append_indentation(); - append_token("@each", loop); - append_mandatory_space(); - append_string(loop->variables()[0]); - for (size_t i = 1, L = loop->variables().size(); i < L; ++i) { - append_comma_separator(); - append_string(loop->variables()[i]); - } - append_string(" in "); - loop->list()->perform(this); - loop->block()->perform(this); - } - void Inspect::operator()(WhileRule* loop) - { - append_indentation(); - append_token("@while", loop); - append_mandatory_space(); - loop->predicate()->perform(this); - loop->block()->perform(this); - } + size_t position = wbuf.buffer.size(); - void Inspect::operator()(Return* ret) - { - append_indentation(); - append_token("@return", ret); - append_mandatory_space(); - ret->value()->perform(this); - append_delimiter(); - } + if (compound->withExplicitParent()) { + if (inspect == true) { + append_string("&"); + } + } - void Inspect::operator()(ExtendRule* extend) - { - append_indentation(); - append_token("@extend", extend); - append_mandatory_space(); - extend->selector()->perform(this); - append_delimiter(); - } + for (SimpleSelectorObj& item : compound->elements()) { + item->accept(this); + } - void Inspect::operator()(Definition* def) - { - append_indentation(); - if (def->type() == Definition::MIXIN) { - append_token("@mixin", def); - append_mandatory_space(); - } else { - append_token("@function", def); - append_mandatory_space(); + // If we emit an empty compound, it's because all of the + // components got optimized out because they match all + // selectors, so we just emit the universal selector. + if (position == wbuf.buffer.size()) { + write_char($asterisk); + } + + // Add the post line break (from ruby sass) + // Dart sass uses another logic for newlines + if (compound->hasPostLineBreak()) { + if (output_style() != SASS_STYLE_COMPACT) { + append_optional_linefeed(); + } } - append_string(def->name()); - def->parameters()->perform(this); - def->block()->perform(this); } - void Inspect::operator()(Mixin_Call* call) + void Inspect::visitSelectorCombinator(SelectorCombinator* combinator) { - append_indentation(); - append_token("@include", call); - append_mandatory_space(); - append_string(call->name()); - if (call->arguments()) { - call->arguments()->perform(this); - } - if (call->block()) { - append_optional_space(); - call->block()->perform(this); + append_optional_space(); + switch (combinator->combinator()) { + case SelectorCombinator::Combinator::CHILD: append_string(">"); break; + case SelectorCombinator::Combinator::GENERAL: append_string("~"); break; + case SelectorCombinator::Combinator::ADJACENT: append_string("+"); break; } - if (!call->block()) append_delimiter(); + append_optional_space(); + // Add the post line break (from ruby sass) + // Dart sass uses another logic for newlines + // if (combinator->hasPostLineBreak()) { + // if (output_style() != COMPACT) { + // // append_optional_linefeed(); + // } + // } } - void Inspect::operator()(Content* content) + void Inspect::visitIDSelector(IDSelector* id) { - append_indentation(); - append_token("@content", content); - append_delimiter(); + append_token(id->name(), id); } - void Inspect::operator()(Map* map) + void Inspect::visitPlaceholderSelector(PlaceholderSelector* placeholder) { - if (output_style() == TO_SASS && map->empty()) { - append_string("()"); - return; - } - if (map->empty()) return; - if (map->is_invisible()) return; - bool items_output = false; - append_string("("); - for (auto key : map->keys()) { - if (items_output) append_comma_separator(); - key->perform(this); - append_colon_separator(); - LOCAL_FLAG(in_space_array, true); - LOCAL_FLAG(in_comma_array, true); - map->at(key)->perform(this); - items_output = true; - } - append_string(")"); + append_token(placeholder->name(), placeholder); } - sass::string Inspect::lbracket(List* list) { - return list->is_bracketed() ? "[" : "("; - } + void Inspect::visitPseudoSelector(PseudoSelector* pseudo) + { + + if (auto sel = pseudo->selector()) { + if (pseudo->name() == "not") { + if (sel->empty()) { + return; + } + } + } - sass::string Inspect::rbracket(List* list) { - return list->is_bracketed() ? "]" : ")"; + if (pseudo->name() != "") { + append_string(":"); + if (pseudo->isSyntacticElement()) { + append_string(":"); + } + append_token(pseudo->name(), pseudo); + // this whole logic can be done simpler!? copy object? + if (pseudo->selector() || !pseudo->argument().empty()) { + append_string("("); + append_string(pseudo->argument()); + if (pseudo->selector() && !pseudo->argument().empty()) { + if (!pseudo->selector()->empty()) { + append_mandatory_space(); + } + } + bool was_comma_array = in_comma_array; + in_comma_array = false; + if (pseudo->selector()) { + visitSelectorList(pseudo->selector()); + } + in_comma_array = was_comma_array; + append_string(")"); + } + } } - void Inspect::operator()(List* list) + void Inspect::visitSelectorList(SelectorList* list) { - if (list->empty() && (output_style() == TO_SASS || list->is_bracketed())) { - append_string(lbracket(list)); - append_string(rbracket(list)); + if (list->empty()) { return; } - sass::string sep(list->separator() == SASS_SPACE ? " " : ","); - if ((output_style() != COMPRESSED) && sep == ",") sep += " "; - else if (in_media_block && sep != " ") sep += " "; // verified - if (list->empty()) return; - bool items_output = false; - bool was_space_array = in_space_array; bool was_comma_array = in_comma_array; - // if the list is bracketed, always include the left bracket - if (list->is_bracketed()) { - append_string(lbracket(list)); - } // probably ruby sass equivalent of element_needs_parens - else if (output_style() == TO_SASS && - list->length() == 1 && - !list->from_selector() && - !Cast(list->at(0)) && - !Cast(list->at(0)) - ) { - append_string(lbracket(list)); - } - else if (!in_declaration && (list->separator() == SASS_HASH || - (list->separator() == SASS_SPACE && in_space_array) || - (list->separator() == SASS_COMMA && in_comma_array) - )) { - append_string(lbracket(list)); + if (!in_declaration && in_comma_array) { + append_string("("); } - if (list->separator() == SASS_SPACE) in_space_array = true; - else if (list->separator() == SASS_COMMA) in_comma_array = true; + if (in_declaration) in_comma_array = true; for (size_t i = 0, L = list->size(); i < L; ++i) { - if (list->separator() == SASS_HASH) - { sep[0] = i % 2 ? ':' : ','; } - ExpressionObj list_item = list->at(i); - if (output_style() != TO_SASS) { - if (list_item->is_invisible()) { - // this fixes an issue with "" in a list - if (!Cast(list_item)) { - continue; - } - } - } - if (items_output) { - append_string(sep); + + if (i == 0) append_indentation(); + if ((*list)[i] == nullptr) continue; + schedule_mapping(list->get(i)->last()); + // add_open_mapping(list->get(i)->last()); + visitComplexSelector(list->get(i)); + // add_close_mapping(list->get(i)->last()); + if (i < L - 1) { + scheduled_space = 0; + append_comma_separator(); } - if (items_output && sep != " ") - append_optional_space(); - list_item->perform(this); - items_output = true; } in_comma_array = was_comma_array; - in_space_array = was_space_array; - - // if the list is bracketed, always include the right bracket - if (list->is_bracketed()) { - if (list->separator() == SASS_COMMA && list->size() == 1) { - append_string(","); - } - append_string(rbracket(list)); - } // probably ruby sass equivalent of element_needs_parens - else if (output_style() == TO_SASS && - list->length() == 1 && - !list->from_selector() && - !Cast(list->at(0)) && - !Cast(list->at(0)) - ) { - append_string(","); - append_string(rbracket(list)); - } - else if (!in_declaration && (list->separator() == SASS_HASH || - (list->separator() == SASS_SPACE && in_space_array) || - (list->separator() == SASS_COMMA && in_comma_array) - )) { - append_string(rbracket(list)); + if (!in_declaration && in_comma_array) { + append_string(")"); } - } - void Inspect::operator()(Binary_Expression* expr) + void Inspect::visitTypeSelector(TypeSelector* type) { - expr->left()->perform(this); - if ( in_media_block || - (output_style() == INSPECT) || ( - expr->op().ws_before - && (!expr->is_interpolant()) - && (expr->is_left_interpolant() || - expr->is_right_interpolant()) - - )) append_string(" "); - switch (expr->optype()) { - case Sass_OP::AND: append_string("&&"); break; - case Sass_OP::OR: append_string("||"); break; - case Sass_OP::EQ: append_string("=="); break; - case Sass_OP::NEQ: append_string("!="); break; - case Sass_OP::GT: append_string(">"); break; - case Sass_OP::GTE: append_string(">="); break; - case Sass_OP::LT: append_string("<"); break; - case Sass_OP::LTE: append_string("<="); break; - case Sass_OP::ADD: append_string("+"); break; - case Sass_OP::SUB: append_string("-"); break; - case Sass_OP::MUL: append_string("*"); break; - case Sass_OP::DIV: append_string("/"); break; - case Sass_OP::MOD: append_string("%"); break; - default: break; // shouldn't get here - } - if ( in_media_block || - (output_style() == INSPECT) || ( - expr->op().ws_after - && (!expr->is_interpolant()) - && (expr->is_left_interpolant() || - expr->is_right_interpolant()) - )) append_string(" "); - expr->right()->perform(this); + acceptNameSpaceSelector(type); } - void Inspect::operator()(Unary_Expression* expr) - { - if (expr->optype() == Unary_Expression::PLUS) append_string("+"); - else if (expr->optype() == Unary_Expression::SLASH) append_string("/"); - else append_string("-"); - expr->operand()->perform(this); + // Returns whether [value] needs parentheses as an + // element in a list with the given [separator]. + bool _elementNeedsParens(SassSeparator separator, const Value* value) { + if (const List * list = value->isaList()) { + if (list->size() < 2) return false; + if (list->hasBrackets()) return false; + return separator == SASS_COMMA + ? list->separator() == SASS_COMMA + : list->separator() != SASS_UNDEF; + } + return false; } - void Inspect::operator()(Function_Call* call) + void Inspect::visitList(List* list) { - append_token(call->name(), call); - call->arguments()->perform(this); - } + // Handle empty case + if (list->empty()) { + if (list->hasBrackets()) { + append_char($lbracket); + append_char($rbracket); + } + else { + append_char($lparen); + append_char($rparen); + } + return; + } - void Inspect::operator()(Variable* var) - { - append_token(var->name(), var); - } + bool preserveComma = inspect && + list->size() == 1 && + list->separator() == SASS_COMMA; - void Inspect::operator()(Number* n) - { + if (list->hasBrackets()) { + append_char($lbracket); + } + else if (preserveComma) { + append_char($lparen); + } - // reduce units - n->reduce(); + add_open_mapping(list); - sass::ostream ss; - ss.precision(opt.precision); - ss << std::fixed << n->value(); + const sass::vector& values(list->elements()); - sass::string res = ss.str(); - size_t s = res.length(); + bool first = true; + sass::string joiner = + list->separator() == SASS_SPACE ? " " : + output_style() == SASS_STYLE_COMPRESSED ? "," : ", "; - // delete trailing zeros - for(s = s - 1; s > 0; --s) - { - if(res[s] == '0') { - res.erase(s, 1); + for (Value* value : values) { + // Only print `null` when inspecting + if (!inspect && value->isBlank()) continue; + if (first == false) { + append_string(joiner); + } + else { + first = false; + } + if (inspect) { + bool needsParens = _elementNeedsParens( + list->separator(), value); + if (needsParens) { + append_char($lparen); + } + value->accept(this); + if (needsParens) { + append_char($rparen); } - else break; + } + else { + value->accept(this); + } } - // delete trailing decimal separator - if(res[s] == '.') res.erase(s, 1); + add_close_mapping(list); - // some final cosmetics - if (res == "0.0") res = "0"; - else if (res == "") res = "0"; - else if (res == "-0") res = "0"; - else if (res == "-0.0") res = "0"; - else if (opt.output_style == COMPRESSED) - { - if (n->zero()) { - // check if handling negative nr - size_t off = res[0] == '-' ? 1 : 0; - // remove leading zero from floating point in compressed mode - if (res[off] == '0' && res[off+1] == '.') res.erase(off, 1); + if (preserveComma) { + append_char($comma); + if (!list->hasBrackets()) { + append_char($rparen); } } - // add unit now - res += n->unit(); + if (list->hasBrackets()) { + append_char($rbracket); + } + } - if (opt.output_style == TO_CSS && !n->is_valid_css_unit()) { - // traces.push_back(Backtrace(nr->pstate())); - throw Exception::InvalidValue({}, *n); + void Inspect::acceptInterpolation(Interpolation* node) + { + // throw std::runtime_error("Interpolation"); + for (Interpolant* itpl : node->elements()) { + if (ItplString* str = itpl->isaItplString()) { + append_token(str->text(), str); + } + else if (Value* value = itpl->isaValue()) { + value->accept(this); + } + else { + throw std::runtime_error("Expression not evaluated"); + } } + } + + void Inspect::visitFunction(Function* value) + { + append_token("get-function", value); + append_string("("); + Callable* fn = value->callable(); + // Function names are safe to quote! + append_token("\""+ fn->name() + "\"", fn); + append_string(")"); + } + + // T visitBoolean(Boolean value); + void Inspect::visitBoolean(Boolean* value) + { // output the final token - append_token(res, n); + append_token(value->value() ? "true" : "false", value); } - // helper function for serializing colors - template - static double cap_channel(double c) { - if (c > range) return range; - else if (c < 0) return 0; - else return c; + bool is_hex_doublet(double n) + { + return n == 0x00 || n == 0x11 || n == 0x22 || n == 0x33 || + n == 0x44 || n == 0x55 || n == 0x66 || n == 0x77 || + n == 0x88 || n == 0x99 || n == 0xAA || n == 0xBB || + n == 0xCC || n == 0xDD || n == 0xEE || n == 0xFF; + } + + bool is_color_doublet(double r, double g, double b) + { + return is_hex_doublet(r) && is_hex_doublet(g) && is_hex_doublet(b); } - void Inspect::operator()(Color_RGBA* c) + // T visitColorRGBA(SassColor value); + void Inspect::visitColor(Color* color) { // output the final token - sass::ostream ss; + sass::sstream ss; + + ColorRgbaObj c = color->toRGBA(); // original color name // maybe an unknown token @@ -636,18 +780,18 @@ namespace Sass { // resolved color sass::string res_name = name; - double r = Sass::round(cap_channel<0xff>(c->r()), opt.precision); - double g = Sass::round(cap_channel<0xff>(c->g()), opt.precision); - double b = Sass::round(cap_channel<0xff>(c->b()), opt.precision); - double a = cap_channel<1> (c->a()); + double r = round32(clamp(c->r(), 0.0, 255.0), opt.precision); + double g = round32(clamp(c->g(), 0.0, 255.0), opt.precision); + double b = round32(clamp(c->b(), 0.0, 255.0), opt.precision); + double a = clamp(c->a(), 0, 1); // get color from given name (if one was given at all) if (name != "" && name_to_color(name)) { - const Color_RGBA* n = name_to_color(name); - r = Sass::round(cap_channel<0xff>(n->r()), opt.precision); - g = Sass::round(cap_channel<0xff>(n->g()), opt.precision); - b = Sass::round(cap_channel<0xff>(n->b()), opt.precision); - a = cap_channel<1> (n->a()); + const ColorRgba* n = name_to_color(name); + r = round32(clamp(n->r(), 0.0, 255.0), opt.precision); + g = round32(clamp(n->g(), 0.0, 255.0), opt.precision); + b = round32(clamp(n->b(), 0.0, 255.0), opt.precision); + a = clamp(n->a(), 0.0, 1.0); } // otherwise get the possible resolved color name else { @@ -656,38 +800,38 @@ namespace Sass { res_name = color_to_name(numval); } - sass::ostream hexlet; + sass::sstream hexlet; // dart sass compressed all colors in regular css always // ruby sass and libsass does it only when not delayed // since color math is going to be removed, this can go too - bool compressed = opt.output_style == COMPRESSED; + bool compressed = opt.output_style == SASS_STYLE_COMPRESSED; hexlet << '#' << std::setw(1) << std::setfill('0'); // create a short color hexlet if there is any need for it - if (compressed && is_color_doublet(r, g, b) && a == 1) { + if (compressed && is_color_doublet(r, g, b) && a >= 1.0) { hexlet << std::hex << std::setw(1) << (static_cast(r) >> 4); hexlet << std::hex << std::setw(1) << (static_cast(g) >> 4); hexlet << std::hex << std::setw(1) << (static_cast(b) >> 4); - } else { + if (a != 1) hexlet << std::hex << std::setw(1) << (static_cast(a * 255) >> 4); + } + else { hexlet << std::hex << std::setw(2) << static_cast(r); hexlet << std::hex << std::setw(2) << static_cast(g); hexlet << std::hex << std::setw(2) << static_cast(b); + if (a != 1) hexlet << std::hex << std::setw(2) << (static_cast(a * 255) >> 4); } - if (compressed && !c->is_delayed()) name = ""; - if (opt.output_style == INSPECT && a >= 1) { - append_token(hexlet.str(), c); - return; - } + if (compressed) name = ""; // retain the originally specified color definition if unchanged if (name != "") { ss << name; } - else if (a >= 1) { + else if (a >= 1.0) { if (res_name != "") { if (compressed && hexlet.str().size() < res_name.size()) { ss << hexlet.str(); - } else { + } + else { ss << res_name; } } @@ -695,6 +839,7 @@ namespace Sass { ss << hexlet.str(); } } + else { ss << "rgba("; ss << static_cast(r) << ","; @@ -707,418 +852,118 @@ namespace Sass { } append_token(ss.str(), c); - } + // EO visitColorRGBA - void Inspect::operator()(Color_HSLA* c) - { - Color_RGBA_Obj rgba = c->toRGBA(); - operator()(rgba); - } + // T visitFunction(Function value); - void Inspect::operator()(Boolean* b) + // T visitMap(Map value); + void Inspect::visitMap(Map* value) { - // output the final token - append_token(b->value() ? "true" : "false", b); - } - - void Inspect::operator()(String_Schema* ss) - { - // Evaluation should turn these into String_Constants, - // so this method is only for inspection purposes. - for (size_t i = 0, L = ss->length(); i < L; ++i) { - if ((*ss)[i]->is_interpolant()) append_string("#{"); - (*ss)[i]->perform(this); - if ((*ss)[i]->is_interpolant()) append_string("}"); + if (value->empty()) { + append_string("()"); + return; } - } - - void Inspect::operator()(String_Constant* s) - { - append_token(s->value(), s); - } - - void Inspect::operator()(String_Quoted* s) - { - if (const char q = s->quote_mark()) { - append_token(quote(s->value(), q), s); - } else { - append_token(s->value(), s); + bool items_output = false; + append_string("("); + for (auto kv : value->elements()) { + if (items_output) append_comma_separator(); + _writeMapElement(kv.first); + append_colon_separator(); + _writeMapElement(kv.second); + items_output = true; } + append_string(")"); } - void Inspect::operator()(Custom_Error* e) + void Inspect::visitNull(Null* value) { - append_token(e->message(), e); + if (output_style() == SASS_STYLE_TO_CSS) return; + // output the final token + append_token("null", value); } - void Inspect::operator()(Custom_Warning* w) - { - append_token(w->message(), w); - } - void Inspect::operator()(SupportsOperation* so) + void Inspect::visitNumber(Number* value) { - - if (so->needs_parens(so->left())) append_string("("); - so->left()->perform(this); - if (so->needs_parens(so->left())) append_string(")"); - - if (so->operand() == SupportsOperation::AND) { - append_mandatory_space(); - append_token("and", so); - append_mandatory_space(); - } else if (so->operand() == SupportsOperation::OR) { - append_mandatory_space(); - append_token("or", so); - append_mandatory_space(); + if (value->lhsAsSlash() && value->rhsAsSlash()) { + visitNumber(value->lhsAsSlash()); + append_string("/"); + visitNumber(value->rhsAsSlash()); + return; } - if (so->needs_parens(so->right())) append_string("("); - so->right()->perform(this); - if (so->needs_parens(so->right())) append_string(")"); - } - - void Inspect::operator()(SupportsNegation* sn) - { - append_token("not", sn); - append_mandatory_space(); - if (sn->needs_parens(sn->condition())) append_string("("); - sn->condition()->perform(this); - if (sn->needs_parens(sn->condition())) append_string(")"); - } - - void Inspect::operator()(SupportsDeclaration* sd) - { - append_string("("); - sd->feature()->perform(this); - append_string(": "); - sd->value()->perform(this); - append_string(")"); - } - - void Inspect::operator()(Supports_Interpolation* sd) - { - sd->value()->perform(this); - } - - void Inspect::operator()(Media_Query* mq) - { - size_t i = 0; - if (mq->media_type()) { - if (mq->is_negated()) append_string("not "); - else if (mq->is_restricted()) append_string("only "); - mq->media_type()->perform(this); - } - else { - (*mq)[i++]->perform(this); - } - for (size_t L = mq->length(); i < L; ++i) { - append_string(" and "); - (*mq)[i]->perform(this); + if (std::isnan(value->value())) { + append_string("NaN"); + return; } - } - void Inspect::operator()(Media_Query_Expression* mqe) - { - if (mqe->is_interpolated()) { - mqe->feature()->perform(this); - } - else { - append_string("("); - mqe->feature()->perform(this); - if (mqe->value()) { - append_string(": "); // verified - mqe->value()->perform(this); - } - append_string(")"); + if (std::isinf(value->value())) { + append_string("Infinity"); + return; } - } - void Inspect::operator()(At_Root_Query* ae) - { - if (ae->feature()) { - append_string("("); - ae->feature()->perform(this); - if (ae->value()) { - append_colon_separator(); - ae->value()->perform(this); + sass::sstream ss; + ss.precision(opt.precision); + // This can be a bottleneck + ss << std::fixed << value->value(); + + sass::string res = ss.str(); + size_t s = res.size(); + + // delete trailing zeros + for (s = s - 1; s > 0; --s) + { + if (res[s] == '0') { + res.erase(s, 1); } - append_string(")"); + else break; } - } - void Inspect::operator()(Function* f) - { - append_token("get-function", f); - append_string("("); - append_string(quote(f->name())); - append_string(")"); - } + // delete trailing decimal separator + if (res[s] == '.') res.erase(s, 1); - void Inspect::operator()(Null* n) - { - // output the final token - append_token("null", n); - } + // some final cosmetics + if (res == "0.0") res = "0"; + else if (res == "") res = "0"; + else if (res == "-0") res = "0"; + else if (res == "-0.0") res = "0"; - // parameters and arguments - void Inspect::operator()(Parameter* p) - { - append_token(p->name(), p); - if (p->default_value()) { - append_colon_separator(); - p->default_value()->perform(this); - } - else if (p->is_rest_parameter()) { - append_string("..."); - } - } + // add unit now + res += value->unit(); - void Inspect::operator()(Parameters* p) - { - append_string("("); - if (!p->empty()) { - (*p)[0]->perform(this); - for (size_t i = 1, L = p->length(); i < L; ++i) { - append_comma_separator(); - (*p)[i]->perform(this); - } - } - append_string(")"); + // output the final token + append_token(res, value); } - void Inspect::operator()(Argument* a) + void Inspect::visitString(String* value) { - if (!a->name().empty()) { - append_token(a->name(), a); - append_colon_separator(); - } - if (!a->value()) return; - // Special case: argument nulls can be ignored - if (a->value()->concrete_type() == Expression::NULL_VAL) { - return; - } - if (a->value()->concrete_type() == Expression::STRING) { - String_Constant* s = Cast(a->value()); - if (s) s->perform(this); - } else { - a->value()->perform(this); + if (quotes && value->hasQuotes()) { + renderQuotedString(value->value()); } - if (a->is_rest_argument()) { - append_string("..."); - } - } - - void Inspect::operator()(Arguments* a) - { - append_string("("); - if (!a->empty()) { - (*a)[0]->perform(this); - for (size_t i = 1, L = a->length(); i < L; ++i) { - append_string(", "); // verified - // Sass Bug? append_comma_separator(); - (*a)[i]->perform(this); - } + else { + renderUnquotedString(value->value()); + // append_token(s->value(), s); } - append_string(")"); } - void Inspect::operator()(Selector_Schema* s) - { - s->contents()->perform(this); - } - void Inspect::operator()(Parent_Reference* p) - { - append_string("&"); - } - void Inspect::operator()(PlaceholderSelector* s) - { - append_token(s->name(), s); - } - void Inspect::operator()(TypeSelector* s) - { - append_token(s->ns_name(), s); - } - void Inspect::operator()(ClassSelector* s) - { - append_token(s->ns_name(), s); - } - void Inspect::operator()(IDSelector* s) - { - append_token(s->ns_name(), s); - } - - void Inspect::operator()(AttributeSelector* s) - { - append_string("["); - add_open_mapping(s); - append_token(s->ns_name(), s); - if (!s->matcher().empty()) { - append_string(s->matcher()); - if (s->value() && *s->value()) { - s->value()->perform(this); - } - } - add_close_mapping(s); - if (s->modifier() != 0) { - append_mandatory_space(); - append_char(s->modifier()); - } - append_string("]"); - } - - void Inspect::operator()(PseudoSelector* s) - { - - if (s->name() != "") { - append_string(":"); - if (s->isSyntacticElement()) { - append_string(":"); - } - append_token(s->ns_name(), s); - if (s->selector() || s->argument()) { - bool was = in_wrapped; - in_wrapped = true; - append_string("("); - if (s->argument()) { - s->argument()->perform(this); - } - if (s->selector() && s->argument()) { - append_mandatory_space(); - } - bool was_comma_array = in_comma_array; - in_comma_array = false; - if (s->selector()) { - s->selector()->perform(this); - } - in_comma_array = was_comma_array; - append_string(")"); - in_wrapped = was; - } - } - } - void Inspect::operator()(SelectorList* g) - { - if (g->empty()) { - if (output_style() == TO_SASS) { - append_token("()", g); - } - return; - } - bool was_comma_array = in_comma_array; - // probably ruby sass equivalent of element_needs_parens - if (output_style() == TO_SASS && g->length() == 1 && - (!Cast((*g)[0]) && - !Cast((*g)[0]))) { - append_string("("); - } - else if (!in_declaration && in_comma_array) { - append_string("("); - } - if (in_declaration) in_comma_array = true; - for (size_t i = 0, L = g->length(); i < L; ++i) { - if (!in_wrapped && i == 0) append_indentation(); - if ((*g)[i] == nullptr) continue; - if (g->at(i)->length() == 0) continue; - schedule_mapping(g->at(i)->last()); - // add_open_mapping((*g)[i]->last()); - (*g)[i]->perform(this); - // add_close_mapping((*g)[i]->last()); - if (i < L - 1) { - scheduled_space = 0; - append_comma_separator(); - } - } - in_comma_array = was_comma_array; - // probably ruby sass equivalent of element_needs_parens - if (output_style() == TO_SASS && g->length() == 1 && - (!Cast((*g)[0]) && - !Cast((*g)[0]))) { - append_string(",)"); - } - else if (!in_declaration && in_comma_array) { - append_string(")"); - } - } - void Inspect::operator()(ComplexSelector* sel) - { - if (sel->hasPreLineFeed()) { - append_optional_linefeed(); - if (!in_wrapped && output_style() == NESTED) { - append_indentation(); - } - } - const SelectorComponent* prev = nullptr; - for (auto& item : sel->elements()) { - if (prev != nullptr) { - if (item->getCombinator() || prev->getCombinator()) { - append_optional_space(); - } else { - append_mandatory_space(); - } - } - item->perform(this); - prev = item.ptr(); - } - } - void Inspect::operator()(SelectorComponent* sel) - { - // You should probably never call this method directly - // But in case anyone does, we will do the up-casting - if (auto comp = Cast(sel)) operator()(comp); - if (auto comb = Cast(sel)) operator()(comb); - } - void Inspect::operator()(CompoundSelector* sel) - { - if (sel->hasRealParent()) { - append_string("&"); - } - for (auto& item : sel->elements()) { - item->perform(this); - } - // Add the post line break (from ruby sass) - // Dart sass uses another logic for newlines - if (sel->hasPostLineBreak()) { - if (output_style() != COMPACT) { - append_optional_linefeed(); - } - } - } - void Inspect::operator()(SelectorCombinator* sel) - { - append_optional_space(); - switch (sel->combinator()) { - case SelectorCombinator::Combinator::CHILD: append_string(">"); break; - case SelectorCombinator::Combinator::GENERAL: append_string("~"); break; - case SelectorCombinator::Combinator::ADJACENT: append_string("+"); break; - } - append_optional_space(); - // Add the post line break (from ruby sass) - // Dart sass uses another logic for newlines - if (sel->hasPostLineBreak()) { - if (output_style() != COMPACT) { - // append_optional_linefeed(); - } - } - } } diff --git a/src/inspect.hpp b/src/inspect.hpp index 2755b3b510..faae82adbc 100644 --- a/src/inspect.hpp +++ b/src/inspect.hpp @@ -1,99 +1,102 @@ -#ifndef SASS_INSPECT_H -#define SASS_INSPECT_H +#ifndef SASS_INSPECT_HPP +#define SASS_INSPECT_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include "position.hpp" -#include "operation.hpp" #include "emitter.hpp" +#include "color_maps.hpp" +#include "visitor_css.hpp" +#include "visitor_value.hpp" +#include "visitor_selector.hpp" namespace Sass { class Context; - class Inspect : public Operation_CRTP, public Emitter { - protected: - // import all the class-specific methods and override as desired - using Operation_CRTP::operator(); + // public SelectorVisitor + + // Inspect does roughly the same as serialize.dart + class Inspect : + public SelectorVisitor, + public ValueVisitor, + public CssVisitor, + public Emitter { public: - Inspect(const Emitter& emi); - virtual ~Inspect(); - - // statements - virtual void operator()(Block*); - virtual void operator()(StyleRule*); - virtual void operator()(Bubble*); - virtual void operator()(SupportsRule*); - virtual void operator()(AtRootRule*); - virtual void operator()(AtRule*); - virtual void operator()(Keyframe_Rule*); - virtual void operator()(Declaration*); - virtual void operator()(Assignment*); - virtual void operator()(Import*); - virtual void operator()(Import_Stub*); - virtual void operator()(WarningRule*); - virtual void operator()(ErrorRule*); - virtual void operator()(DebugRule*); - virtual void operator()(Comment*); - virtual void operator()(If*); - virtual void operator()(ForRule*); - virtual void operator()(EachRule*); - virtual void operator()(WhileRule*); - virtual void operator()(Return*); - virtual void operator()(ExtendRule*); - virtual void operator()(Definition*); - virtual void operator()(Mixin_Call*); - virtual void operator()(Content*); - // expressions - virtual void operator()(Map*); - virtual void operator()(Function*); - virtual void operator()(List*); - virtual void operator()(Binary_Expression*); - virtual void operator()(Unary_Expression*); - virtual void operator()(Function_Call*); - // virtual void operator()(Custom_Warning*); - // virtual void operator()(Custom_Error*); - virtual void operator()(Variable*); - virtual void operator()(Number*); - virtual void operator()(Color_RGBA*); - virtual void operator()(Color_HSLA*); - virtual void operator()(Boolean*); - virtual void operator()(String_Schema*); - virtual void operator()(String_Constant*); - virtual void operator()(String_Quoted*); - virtual void operator()(Custom_Error*); - virtual void operator()(Custom_Warning*); - virtual void operator()(SupportsOperation*); - virtual void operator()(SupportsNegation*); - virtual void operator()(SupportsDeclaration*); - virtual void operator()(Supports_Interpolation*); - virtual void operator()(MediaRule*); - virtual void operator()(CssMediaRule*); - virtual void operator()(CssMediaQuery*); - virtual void operator()(Media_Query*); - virtual void operator()(Media_Query_Expression*); - virtual void operator()(At_Root_Query*); - virtual void operator()(Null*); - virtual void operator()(Parent_Reference* p); - // parameters and arguments - virtual void operator()(Parameter*); - virtual void operator()(Parameters*); - virtual void operator()(Argument*); - virtual void operator()(Arguments*); - // selectors - virtual void operator()(Selector_Schema*); - virtual void operator()(PlaceholderSelector*); - virtual void operator()(TypeSelector*); - virtual void operator()(ClassSelector*); - virtual void operator()(IDSelector*); - virtual void operator()(AttributeSelector*); - virtual void operator()(PseudoSelector*); - virtual void operator()(SelectorComponent*); - virtual void operator()(SelectorCombinator*); - virtual void operator()(CompoundSelector*); - virtual void operator()(ComplexSelector*); - virtual void operator()(SelectorList*); - virtual sass::string lbracket(List*); - virtual sass::string rbracket(List*); + // Whether quoted strings should be emitted with quotes. + bool quotes; + + // So far this only influences how list are rendered. + // If the separator is known to be comma, we append + // a trailing comma for lists with a single item. + bool inspect; + + // We should probably pass an emitter, so we can switch implementation? + Inspect(struct SassOutputOptionsCpp& opt, bool srcmap_enabled); + Inspect(Logger& logger, struct SassOutputOptionsCpp& opt, bool srcmap_enabled); + + void visitBlockStatements(CssNodeVector children); + + void renderQuotedString(const sass::string& text, uint8_t quotes = 0); + void renderUnquotedString(const sass::string& text); + + ///////////////////////////////////////////////////////////////////////// + // Implement Selector Visitors + ///////////////////////////////////////////////////////////////////////// + + virtual void visitAttributeSelector(AttributeSelector* sel) override; + virtual void visitClassSelector(ClassSelector* sel) override; + virtual void visitComplexSelector(ComplexSelector* sel) override; + virtual void visitCompoundSelector(CompoundSelector* sel) override; + virtual void visitIDSelector(IDSelector* sel) override; + virtual void visitPlaceholderSelector(PlaceholderSelector* sel) override; + virtual void visitPseudoSelector(PseudoSelector* sel) override; + virtual void visitSelectorCombinator(SelectorCombinator* sel) override; // LibSass only + virtual void visitSelectorList(SelectorList* sel) override; + virtual void visitTypeSelector(TypeSelector* sel) override; + + ///////////////////////////////////////////////////////////////////////// + // Implement Value Visitors + ///////////////////////////////////////////////////////////////////////// + + virtual void visitBoolean(Boolean* value) override; + virtual void visitColor(Color* value) override; + virtual void visitFunction(Function* value) override; + virtual void visitList(List* value) override; + virtual void visitMap(Map* value) override; + virtual void visitNull(Null* value) override; + virtual void visitNumber(Number* value) override; + virtual void visitString(String* value) override; + + ///////////////////////////////////////////////////////////////////////// + // Implement CSS Visitors + ///////////////////////////////////////////////////////////////////////// + + virtual void visitCssAtRule(CssAtRule* css) override; + virtual void visitCssComment(CssComment* css) override; + virtual void visitCssDeclaration(CssDeclaration* css) override; + virtual void visitCssImport(CssImport* css) override; + virtual void visitCssKeyframeBlock(CssKeyframeBlock* css) override; + virtual void visitCssMediaRule(CssMediaRule* css) override; + virtual void visitCssRoot(CssRoot* css) override; // LibSass only + virtual void visitCssStyleRule(CssStyleRule* css) override; + virtual void visitCssSupportsRule(CssSupportsRule* css) override; + + ///////////////////////////////////////////////////////////////////////// + // Not part of visitors (used internally as entry points) + ///////////////////////////////////////////////////////////////////////// + + virtual void acceptCssString(CssString*); + virtual void acceptCssMediaQuery(CssMediaQuery*); + virtual void acceptInterpolation(Interpolation*); + virtual void acceptNameSpaceSelector(NameSpaceSelector*); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void _writeMapElement(Interpolant* value); }; diff --git a/src/interpolation.cpp b/src/interpolation.cpp new file mode 100644 index 0000000000..c57f8a6acc --- /dev/null +++ b/src/interpolation.cpp @@ -0,0 +1,94 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "interpolation.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create new interpolation object from the interpolation buffer + Interpolation* InterpolationBuffer::getInterpolation(const SourceSpan& pstate, bool rtrim) + { + InterpolationObj itpl = SASS_MEMORY_NEW(Interpolation, pstate); + // Append all content Expressions + for (ValueObj item : contents) { + itpl->append(std::move(item)); + } + if (!text.empty()) { + // Appends a ItplString from the remaining text in the string buffer + if (rtrim) StringUtils::makeRightTrimmed(text.buffer); + itpl->append(SASS_MEMORY_NEW(ItplString, pstate, text.buffer)); + } + // ToDo: get rid of detach? + return itpl.detach(); + } + + // Flushes [text] to [contents] if necessary. + void InterpolationBuffer::flushText() + { + // Do nothing if text is empty + if (text.empty()) return; + // Create and add string constant to container + contents.emplace_back(SASS_MEMORY_NEW( + ItplString, pstate, text.buffer)); + // Clear buffer now + text.clear(); + } + // EO flushText + + // Add an interpolation to the buffer. + void InterpolationBuffer::addInterpolation(const InterpolationObj schema) + { + if (schema->empty()) return; + + auto& elements = schema->elements(); + auto addStart = elements.begin(); + auto addEnd = elements.end(); + + // The schema to add start with a plain string + if (ItplString* str = elements[0]->isaItplString()) { + // Append the plain string + text.write(str); + // First item is consumed + addStart += 1; + } + + // Flush plain text to contents (as + // ItplString) if any text is defined. + flushText(); + + // Is there an item at the end? + if (addStart != addEnd) { + + auto& last = *(addEnd - 1); + if (auto str = last->isaItplString()) { + // Add the rest if some is left + contents.insert(contents.end(), + addStart, addEnd - 1); + text.write( + str->text(), + str->pstate()); + } + else { + // Add the rest if some is left + contents.insert(contents.end(), + addStart, addEnd); + } + } + + if (auto str = contents.back()->isaItplString()) { + text.write( + str->text(), + str->pstate()); + contents.pop_back(); + } + + } + // EO addInterpolation + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/interpolation.hpp b/src/interpolation.hpp new file mode 100644 index 0000000000..2ffdd3697a --- /dev/null +++ b/src/interpolation.hpp @@ -0,0 +1,200 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_INTERPOLATION_H +#define SASS_INTERPOLATION_H + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" +#include "string_utils.hpp" +#include "scanner_string.hpp" +#include "utf8/checked.h" +#include "ast_values.hpp" + +namespace Sass { + + class StringBuffer { + + public: + + sass::string buffer; + + StringBuffer() : + buffer() + {} + + void writeCharCode(uint32_t character) + { + utf8::append(character, std::back_inserter(buffer)); + } + + void write(const ItplString* string) + { + buffer += string->text(); + } + + void write(unsigned char character) + { + buffer.push_back(character); + } + + void write(char character) + { + buffer.push_back(character); + } + + void write(const sass::string& text) + { + buffer.append(text); + } + + void write(const sass::string& text, const SourceSpan& pstate) + { + buffer.append(text); + } + + void write(sass::string&& text) + { + buffer.append(std::move(text)); + } + + void write(sass::string&& text, const SourceSpan& pstate) + { + buffer.append(std::move(text)); + } + + bool empty() const + { + return buffer.empty(); + } + + void clear() + { + buffer.clear(); + } + + }; + // EO StringBuffer + + class InterpolationBuffer { + + private: + + SourceSpan pstate; + + sass::vector contents; + + public: + + StringBuffer text; + + InterpolationBuffer(const SourceSpan& pstate) : + pstate(pstate), text() + {} + + InterpolationBuffer(const StringScanner& scanner) : + pstate(scanner.rawSpan()), text() + {} + + // Create new interpolation object from the interpolation buffer + Interpolation* getInterpolation(const SourceSpan& pstate, bool rtrim = false); + + bool empty() const + { + return contents.empty() && text.empty(); + } + + // Empties this buffer. + void clear() { + contents.clear(); + } + + private: + + // Flushes [_text] to [_contents] if necessary. + void flushText(); + + public: + + // Add an interpolation to the buffer. + void addInterpolation(const InterpolationObj schema); + + void writeCharCode(uint32_t character) + { + text.writeCharCode(character); + } + + void write(unsigned char character) + { + text.write(character); + } + + void write(char character) + { + text.write(character); + } + + void write(const char str[]) + { + text.write(sass::string(str)); + } + + void write(const sass::string& str) + { + text.write(str); + } + + void write(const sass::string& str, SourceSpan pstate) + { + text.write(str, pstate); + } + + void write(sass::string&& str) + { + text.write(std::move(str)); + } + + void write(sass::string&& str, SourceSpan pstate) + { + text.write(std::move(str), pstate); + } + + void write(const ItplString* str) + { + text.write(str->text(), str->pstate()); + } + + void write(const InterpolantObj& expression) + { + flushText(); + contents.emplace_back(expression); + } + + void add(const InterpolantObj& expression) + { + flushText(); + contents.emplace_back(expression); + } + + sass::string trailingString() + { + return text.buffer; + } + + bool trailingStringEndsWith(const sass::string& cmp) + { + sass::string tail(text.buffer); + // ToDo: trim shouldn't affect srcmap? + StringUtils::makeRightTrimmed(tail); + return StringUtils::endsWith(tail, cmp); + } + + }; + // EO InterpolationBuffer + +} + +#endif diff --git a/src/json.cpp b/src/json.cpp index f7d06e4c75..c3ccbbc89f 100644 --- a/src/json.cpp +++ b/src/json.cpp @@ -30,13 +30,13 @@ // include utf8 library used by libsass // ToDo: replace internal json utf8 code -#include "utf8.h" +#include "unicode.hpp" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #if defined(_MSC_VER) && _MSC_VER < 1900 #include @@ -93,7 +93,9 @@ static void sb_grow(SB *sb, int need) do { alloc *= 2; } while (alloc < length + need); - + #ifdef _MSC_VER + #pragma warning(disable : 6308) + #endif sb->start = (char*) realloc(sb->start, alloc + 1); if (sb->start == NULL) out_of_memory(); diff --git a/src/json.hpp b/src/json.hpp index 05b35cd940..be8518d9cc 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -24,8 +24,8 @@ #ifndef CCAN_JSON_H #define CCAN_JSON_H -#include -#include +#include +#include typedef enum { JSON_NULL, diff --git a/src/lexer.cpp b/src/lexer.cpp deleted file mode 100644 index e8c3ba4ba7..0000000000 --- a/src/lexer.cpp +++ /dev/null @@ -1,122 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include "lexer.hpp" -#include "constants.hpp" -#include "util_string.hpp" - - -namespace Sass { - using namespace Constants; - - namespace Prelexer { - - //#################################### - // BASIC CHARACTER MATCHERS - //#################################### - - // Match standard control chars - const char* kwd_at(const char* src) { return exactly<'@'>(src); } - const char* kwd_dot(const char* src) { return exactly<'.'>(src); } - const char* kwd_comma(const char* src) { return exactly<','>(src); }; - const char* kwd_colon(const char* src) { return exactly<':'>(src); }; - const char* kwd_star(const char* src) { return exactly<'*'>(src); }; - const char* kwd_plus(const char* src) { return exactly<'+'>(src); }; - const char* kwd_minus(const char* src) { return exactly<'-'>(src); }; - const char* kwd_slash(const char* src) { return exactly<'/'>(src); }; - - bool is_number(char chr) { - return Util::ascii_isdigit(static_cast(chr)) || - chr == '-' || chr == '+'; - } - - // check if char is within a reduced ascii range - // valid in a uri (copied from Ruby Sass) - bool is_uri_character(char chr) - { - unsigned int cmp = unsigned(chr); - return (cmp > 41 && cmp < 127) || - cmp == ':' || cmp == '/'; - } - - // check if char is within a reduced ascii range - // valid for escaping (copied from Ruby Sass) - bool is_escapable_character(char chr) - { - unsigned int cmp = unsigned(chr); - return cmp > 31 && cmp < 127; - } - - // Match word character (look ahead) - bool is_character(char chr) - { - // valid alpha, numeric or unicode char (plus hyphen) - return Util::ascii_isalnum(static_cast(chr)) || - !Util::ascii_isascii(static_cast(chr)) || - chr == '-'; - } - - //#################################### - // BASIC CLASS MATCHERS - //#################################### - - // create matchers that advance the position - const char* space(const char* src) { return Util::ascii_isspace(static_cast(*src)) ? src + 1 : nullptr; } - const char* alpha(const char* src) { return Util::ascii_isalpha(static_cast(*src)) ? src + 1 : nullptr; } - const char* nonascii(const char* src) { return Util::ascii_isascii(static_cast(*src)) ? nullptr : src + 1; } - const char* digit(const char* src) { return Util::ascii_isdigit(static_cast(*src)) ? src + 1 : nullptr; } - const char* xdigit(const char* src) { return Util::ascii_isxdigit(static_cast(*src)) ? src + 1 : nullptr; } - const char* alnum(const char* src) { return Util::ascii_isalnum(static_cast(*src)) ? src + 1 : nullptr; } - const char* hyphen(const char* src) { return *src == '-' ? src + 1 : 0; } - const char* uri_character(const char* src) { return is_uri_character(*src) ? src + 1 : 0; } - const char* escapable_character(const char* src) { return is_escapable_character(*src) ? src + 1 : 0; } - - // Match multiple ctype characters. - const char* spaces(const char* src) { return one_plus(src); } - const char* digits(const char* src) { return one_plus(src); } - const char* hyphens(const char* src) { return one_plus(src); } - - // Whitespace handling. - const char* no_spaces(const char* src) { return negate< space >(src); } - const char* optional_spaces(const char* src) { return zero_plus< space >(src); } - - // Match any single character. - const char* any_char(const char* src) { return *src ? src + 1 : src; } - - // Match word boundary (zero-width lookahead). - const char* word_boundary(const char* src) { return is_character(*src) || *src == '#' ? 0 : src; } - - // Match linefeed /(?:\n|\r\n?|\f)/ - const char* re_linebreak(const char* src) - { - // end of file or unix linefeed return here - if (*src == 0) return src; - // end of file or unix linefeed return here - if (*src == '\n' || *src == '\f') return src + 1; - // a carriage return may optionally be followed by a linefeed - if (*src == '\r') return *(src + 1) == '\n' ? src + 2 : src + 1; - // no linefeed - return 0; - } - - // Assert string boundaries (/\Z|\z|\A/) - // This is a zero-width positive lookahead - const char* end_of_line(const char* src) - { - // end of file or unix linefeed return here - return *src == 0 || *src == '\n' || *src == '\r' || *src == '\f' ? src : 0; - } - - // Assert end_of_file boundary (/\z/) - // This is a zero-width positive lookahead - const char* end_of_file(const char* src) - { - // end of file or unix linefeed return here - return *src == 0 ? src : 0; - } - - } -} diff --git a/src/lexer.hpp b/src/lexer.hpp deleted file mode 100644 index 360ed22694..0000000000 --- a/src/lexer.hpp +++ /dev/null @@ -1,304 +0,0 @@ -#ifndef SASS_LEXER_H -#define SASS_LEXER_H - -#include - -namespace Sass { - namespace Prelexer { - - //#################################### - // BASIC CHARACTER MATCHERS - //#################################### - - // Match standard control chars - const char* kwd_at(const char* src); - const char* kwd_dot(const char* src); - const char* kwd_comma(const char* src); - const char* kwd_colon(const char* src); - const char* kwd_star(const char* src); - const char* kwd_plus(const char* src); - const char* kwd_minus(const char* src); - const char* kwd_slash(const char* src); - - //#################################### - // BASIC CLASS MATCHERS - //#################################### - - // Matches ASCII digits, +, and -. - bool is_number(char src); - - bool is_uri_character(char src); - bool escapable_character(char src); - - // Match a single ctype predicate. - const char* space(const char* src); - const char* alpha(const char* src); - const char* digit(const char* src); - const char* xdigit(const char* src); - const char* alnum(const char* src); - const char* hyphen(const char* src); - const char* nonascii(const char* src); - const char* uri_character(const char* src); - const char* escapable_character(const char* src); - - // Match multiple ctype characters. - const char* spaces(const char* src); - const char* digits(const char* src); - const char* hyphens(const char* src); - - // Whitespace handling. - const char* no_spaces(const char* src); - const char* optional_spaces(const char* src); - - // Match any single character (/./). - const char* any_char(const char* src); - - // Assert word boundary (/\b/) - // Is a zero-width positive lookaheads - const char* word_boundary(const char* src); - - // Match a single linebreak (/(?:\n|\r\n?)/). - const char* re_linebreak(const char* src); - - // Assert string boundaries (/\Z|\z|\A/) - // There are zero-width positive lookaheads - const char* end_of_line(const char* src); - - // Assert end_of_file boundary (/\z/) - const char* end_of_file(const char* src); - // const char* start_of_string(const char* src); - - // Type definition for prelexer functions - typedef const char* (*prelexer)(const char*); - - //#################################### - // BASIC "REGEX" CONSTRUCTORS - //#################################### - - // Match a single character literal. - // Regex equivalent: /(?:x)/ - template - const char* exactly(const char* src) { - return *src == chr ? src + 1 : 0; - } - - // Match the full string literal. - // Regex equivalent: /(?:literal)/ - template - const char* exactly(const char* src) { - if (str == NULL) return 0; - const char* pre = str; - if (src == NULL) return 0; - // there is a small chance that the search string - // is longer than the rest of the string to look at - while (*pre && *src == *pre) { - ++src, ++pre; - } - // did the matcher finish? - return *pre == 0 ? src : 0; - } - - - // Match a single character literal. - // Regex equivalent: /(?:x)/i - // only define lower case alpha chars - template - const char* insensitive(const char* src) { - return *src == chr || *src+32 == chr ? src + 1 : 0; - } - - // Match the full string literal. - // Regex equivalent: /(?:literal)/i - // only define lower case alpha chars - template - const char* insensitive(const char* src) { - if (str == NULL) return 0; - const char* pre = str; - if (src == NULL) return 0; - // there is a small chance that the search string - // is longer than the rest of the string to look at - while (*pre && (*src == *pre || *src+32 == *pre)) { - ++src, ++pre; - } - // did the matcher finish? - return *pre == 0 ? src : 0; - } - - // Match for members of char class. - // Regex equivalent: /[axy]/ - template - const char* class_char(const char* src) { - const char* cc = char_class; - while (*cc && *src != *cc) ++cc; - return *cc ? src + 1 : 0; - } - - // Match for members of char class. - // Regex equivalent: /[axy]+/ - template - const char* class_chars(const char* src) { - const char* p = src; - while (class_char(p)) ++p; - return p == src ? 0 : p; - } - - // Match for members of char class. - // Regex equivalent: /[^axy]/ - template - const char* neg_class_char(const char* src) { - if (*src == 0) return 0; - const char* cc = neg_char_class; - while (*cc && *src != *cc) ++cc; - return *cc ? 0 : src + 1; - } - - // Match for members of char class. - // Regex equivalent: /[^axy]+/ - template - const char* neg_class_chars(const char* src) { - const char* p = src; - while (neg_class_char(p)) ++p; - return p == src ? 0 : p; - } - - // Match all except the supplied one. - // Regex equivalent: /[^x]/ - template - const char* any_char_but(const char* src) { - return (*src && *src != chr) ? src + 1 : 0; - } - - // Succeeds if the matcher fails. - // Aka. zero-width negative lookahead. - // Regex equivalent: /(?!literal)/ - template - const char* negate(const char* src) { - return mx(src) ? 0 : src; - } - - // Succeeds if the matcher succeeds. - // Aka. zero-width positive lookahead. - // Regex equivalent: /(?=literal)/ - // just hangs around until we need it - template - const char* lookahead(const char* src) { - return mx(src) ? src : 0; - } - - // Tries supplied matchers in order. - // Succeeds if one of them succeeds. - // Regex equivalent: /(?:FOO|BAR)/ - template - const char* alternatives(const char* src) { - const char* rslt; - if ((rslt = mx(src))) return rslt; - return 0; - } - template - const char* alternatives(const char* src) { - const char* rslt; - if ((rslt = mx1(src))) return rslt; - return alternatives(src); - } - - // Tries supplied matchers in order. - // Succeeds if all of them succeeds. - // Regex equivalent: /(?:FOO)(?:BAR)/ - template - const char* sequence(const char* src) { - const char* rslt = src; - if (!(rslt = mx1(rslt))) return 0; - return rslt; - } - template - const char* sequence(const char* src) { - const char* rslt = src; - if (!(rslt = mx1(rslt))) return 0; - return sequence(rslt); - } - - - // Match a pattern or not. Always succeeds. - // Regex equivalent: /(?:literal)?/ - template - const char* optional(const char* src) { - const char* p = mx(src); - return p ? p : src; - } - - // Match zero or more of the patterns. - // Regex equivalent: /(?:literal)*/ - template - const char* zero_plus(const char* src) { - const char* p = mx(src); - while (p) src = p, p = mx(src); - return src; - } - - // Match one or more of the patterns. - // Regex equivalent: /(?:literal)+/ - template - const char* one_plus(const char* src) { - const char* p = mx(src); - if (!p) return 0; - while (p) src = p, p = mx(src); - return src; - } - - // Match mx non-greedy until delimiter. - // Other prelexers are greedy by default. - // Regex equivalent: /(?:$mx)*?(?=$delim)\b/ - template - const char* non_greedy(const char* src) { - while (!delim(src)) { - const char* p = mx(src); - if (p == src) return 0; - if (p == 0) return 0; - src = p; - } - return src; - } - - //#################################### - // ADVANCED "REGEX" CONSTRUCTORS - //#################################### - - // Match with word boundary rule. - // Regex equivalent: /(?:$mx)\b/i - template - const char* keyword(const char* src) { - return sequence < - insensitive < str >, - word_boundary - >(src); - } - - // Match with word boundary rule. - // Regex equivalent: /(?:$mx)\b/ - template - const char* word(const char* src) { - return sequence < - exactly < str >, - word_boundary - >(src); - } - - template - const char* loosely(const char* src) { - return sequence < - optional_spaces, - exactly < chr > - >(src); - } - template - const char* loosely(const char* src) { - return sequence < - optional_spaces, - exactly < str > - >(src); - } - - } -} - -#endif diff --git a/src/listize.cpp b/src/listize.cpp deleted file mode 100644 index d12eb0d2e9..0000000000 --- a/src/listize.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include - -#include "listize.hpp" -#include "context.hpp" -#include "backtrace.hpp" -#include "error_handling.hpp" - -namespace Sass { - - Listize::Listize() - { } - - Expression* Listize::perform(AST_Node* node) - { - Listize listize; - return node->perform(&listize); - } - - Expression* Listize::operator()(SelectorList* sel) - { - List_Obj l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); - l->from_selector(true); - for (size_t i = 0, L = sel->length(); i < L; ++i) { - if (!sel->at(i)) continue; - l->append(sel->at(i)->perform(this)); - } - if (l->length()) return l.detach(); - return SASS_MEMORY_NEW(Null, l->pstate()); - } - - Expression* Listize::operator()(CompoundSelector* sel) - { - sass::string str; - for (size_t i = 0, L = sel->length(); i < L; ++i) { - Expression* e = (*sel)[i]->perform(this); - if (e) str += e->to_string(); - } - return SASS_MEMORY_NEW(String_Quoted, sel->pstate(), str); - } - - Expression* Listize::operator()(ComplexSelector* sel) - { - List_Obj l = SASS_MEMORY_NEW(List, sel->pstate()); - // ToDo: investigate what this does - // Note: seems reated to parent ref - l->from_selector(true); - - for (auto component : sel->elements()) { - if (CompoundSelectorObj compound = Cast(component)) { - if (!compound->empty()) { - ExpressionObj hh = compound->perform(this); - if (hh) l->append(hh); - } - } - else if (component) { - l->append(SASS_MEMORY_NEW(String_Quoted, component->pstate(), component->to_string())); - } - } - - if (l->length() == 0) return 0; - return l.detach(); - } - -} diff --git a/src/listize.hpp b/src/listize.hpp deleted file mode 100644 index 5da094df97..0000000000 --- a/src/listize.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef SASS_LISTIZE_H -#define SASS_LISTIZE_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "ast_fwd_decl.hpp" -#include "operation.hpp" - -namespace Sass { - - struct Backtrace; - - class Listize : public Operation_CRTP { - - public: - - static Expression* perform(AST_Node* node); - - public: - Listize(); - ~Listize() { } - - Expression* operator()(SelectorList*); - Expression* operator()(ComplexSelector*); - Expression* operator()(CompoundSelector*); - - // generic fallback - template - Expression* fallback(U x) - { return Cast(x); } - }; - -} - -#endif diff --git a/src/logger.cpp b/src/logger.cpp new file mode 100644 index 0000000000..19702cf2b1 --- /dev/null +++ b/src/logger.cpp @@ -0,0 +1,633 @@ +#include "logger.hpp" + +#include +#include "source.hpp" +#include "utf8/checked.h" +#include "string_utils.hpp" + +namespace Sass { + + Logger::Logger(enum SassLoggerStyle style, int precision, size_t columns) : + epsilon(std::pow(0.1, precision + 1)), + columns(columns), + style(style) + { + setLogStyle(style); + } + + void Logger::setLogStyle(enum SassLoggerStyle style) + { + // This auto-detection is experimental + // We do our best but hard to make portable + if (this->style == SASS_LOGGER_AUTO) { + auto colors = Terminal::hasColorSupport(true); + bool unicode = Terminal::hasUnicodeSupport(true); + if (colors && unicode) { this->style = SASS_LOGGER_UNICODE_COLOR; } + else if (unicode) { this->style = SASS_LOGGER_UNICODE_MONO; } + else if (colors) { this->style = SASS_LOGGER_ASCII_COLOR; } + else { this->style = SASS_LOGGER_ASCII_MONO; } + } + // Auto-detect available columns + if (columns == NPOS) { + this->columns = Terminal::getColumns(true); + } + // Clamp into a sensible range + if (columns > 800) { columns = 800; } + else if (columns < 40) { columns = 40; } + } + + void Logger::setLogPrecision(int precision) + { + epsilon = std::pow(0.1, precision + 1); + } + + // Write warning header to error stream + void Logger::writeWarnHead(bool deprecation) + { + if (style & SASS_LOGGER_COLOR) { + logstrm << getColor(Terminal::yellow); + if (!deprecation) logstrm << "Warning"; + else logstrm << "Deprecation Warning"; + logstrm << getColor(Terminal::reset); + } + else { + if (!deprecation) logstrm << "WARNING"; + else logstrm << "DEPRECATION WARNING"; + } + } + + // Convert back-traces which only hold references + // to e.g. the source content to stack-traces which + // manages copies of the temporary string references. + StackTraces convertBackTraces(BackTraces traces) + { + // They convert implicitly, so simply assign them + return StackTraces(traces.begin(), traces.end()); + } + + // Print the `input` string onto the output stream `os` and + // wrap words around to fit into the given column `width`. + void wrap(sass::string const& input, size_t width, sass::ostream& os) + { + sass::istream in(input); + + size_t current = 0; + sass::string word; + + while (in >> word) { + if (current + word.size() > width) { + os << STRMLF; + current = 0; + } + os << word << ' '; + current += word.size() + 1; + } + if (current != 0) { + os << STRMLF; + } + } + + // Print a warning without any SourceSpan (used by @warn) + void Logger::addWarning(const sass::string& message) + { + writeWarnHead(false); + logstrm << ": "; + + wrap(message, 80, logstrm); + StackTraces stack(callStack.begin(), callStack.end()); + writeStackTraces(logstrm, stack, " ", true, 0); + } + + // Print a debug message without any SourceSpan (used by @debug) + void Logger::addDebug(const sass::string& message, const SourceSpan& pstate) + { + logstrm << pstate.getDebugPath() << ":" << + pstate.getLine() << " DEBUG: " << message; + logstrm << STRMLF; + } + + + // Print a regular warning or deprecation + void Logger::printWarning(const sass::string& message, const SourceSpan& pstate, bool deprecation) + { + + callStackFrame frame(*this, pstate); + + writeWarnHead(deprecation); + logstrm << " on line " << pstate.getLine(); + logstrm << ", column " << pstate.getColumn(); + logstrm << " of " << pstate.getDebugPath() << ':' << STRMLF; + + // Might be expensive to call? + // size_t cols = Terminal::getWidth(); + // size_t width = std::min(cols, 80); + wrap(message, 80, logstrm); + + logstrm << STRMLF; + + StackTraces stack(callStack.begin(), callStack.end()); + writeStackTraces(logstrm, stack, " ", true); + + } + + /* + StdLogger::StdLogger(int precision, enum SassLoggerStyle style) : + Logger(precision, style) + { + } + + void StdLogger::warn(sass::string message) const + { + } + + void StdLogger::debug(sass::string message) const + { + } + + void StdLogger::error(sass::string message) const + { + } + */ + + + + + + + void Logger::printSourceSpan(SourceSpan pstate, sass::ostream& stream, enum SassLoggerStyle logstyle) + { + + // ASCII reporting + sass::string top(","); + sass::string upper("/"); + sass::string middle("|"); + sass::string lower("\\"); + sass::string runin("-"); + sass::string bottom("'"); + + // Or use Unicode versions + if (logstyle & SASS_LOGGER_UNICODE) { + top = "\xE2\x95\xB7"; + upper = "\xE2\x94\x8C"; + middle = "\xE2\x94\x82"; + lower = "\xE2\x94\x94"; + runin = "\xE2\x94\x80"; + bottom = "\xE2\x95\xB5"; + } + + // now create the code trace (ToDo: maybe have utility functions?) + if (pstate.getContent() == nullptr) return; + + // Calculate offset positions + Offset beg = pstate.position; + Offset end = beg + pstate.span; + + // Dart-sass claims this might fail due to float errors + size_t padding = (size_t)floor(log10(end.line + 1)) + 1; + + // Do multi-line reporting + SourceData* source = pstate.getSource(); + if (pstate.span.line > 0 && source != nullptr) { + + sass::vector lines; + + // Fetch all lines we need to print the state //XOXOXO + for (size_t i = 0; i <= pstate.span.line; i++) { + sass::string line(source->getLine(pstate.position.line + i)); + lines.emplace_back(line); + } + + // Write intro line + stream + << getColor(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << ' ' + << ' ' << top + << getColor(Terminal::reset) + << STRMLF; + + for (size_t i = 0; i < lines.size(); i++) { + + // Right trim the line to report + StringUtils::makeRightTrimmed(lines[i]); + + // Write the line number and the code + stream + << getColor(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << (beg.line + i + 1) + << ' ' << middle + << getColor(Terminal::reset) + << ' '; + + // Report the first line + // Gets a line underneath + if (i == 0) { + + // Rewind position to last relevant to report cleaner if possible + while (beg.column > 0 && Character::isWhitespace(lines[i][beg.column - 1])) beg.column--; + + // Only need a line if not at start + if (beg.column > 0) { + + // Print the initial code line + auto line_beg = lines[i].begin(); + utf8::advance(line_beg, beg.column, lines[i].end()); + lines[i].insert(line_beg - lines[i].begin(), getColor(Terminal::red)); + stream << " " << lines[i] << getColor(Terminal::reset) << STRMLF; + + // Print the line beneath + stream + << getColor(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << ' ' + << ' ' << middle << ' ' + << getColor(Terminal::reset) + << getColor(Terminal::red) + << upper; + // This needs a loop unfortunately + size_t cols = beg.column; + for (size_t n = 0; n < cols + 2; n++) { + stream << runin; + } + // Final indicator + stream + << '^' + << getColor(Terminal::reset) + << STRMLF; + } + // Just print the code line + else { + stream + << getColor(Terminal::red) + << upper << ' ' << lines[i] + << getColor(Terminal::reset) + << STRMLF; + } + } + // Last line might get another indicator line + else if (i == lines.size() - 1) { + + if (end.column < lines[i].size()) { + + // Print the final code line + auto line_beg = lines[i].begin(); + utf8::advance(line_beg, end.column, lines[i].end()); + lines[i].insert(line_beg - lines[i].begin(), getColor(Terminal::reset)); + stream << getColor(Terminal::red) << middle << ' ' << lines[i] << STRMLF; + + // Print the line beneath + stream + << getColor(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << ' ' + << ' ' << middle << ' ' + << getColor(Terminal::reset) + << getColor(Terminal::red) + << lower; + // This needs a loop unfortunately + for (size_t n = 0; n < end.column; n++) { + stream << runin; + } + // Final indicator + stream + << '^' + << getColor(Terminal::reset) + << STRMLF; + } + else { + // Just print the code line + stream + << getColor(Terminal::red) + << lower << ' ' << lines[i] + << getColor(Terminal::reset) + << STRMLF; + } + } + else { + // Just print the code line + stream + << getColor(Terminal::red) + << middle << ' ' << lines[i] + << getColor(Terminal::reset) + << STRMLF; + } + } + + // Write outro line + stream + << getColor(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << ' ' + << ' ' << bottom + << getColor(Terminal::reset) + << STRMLF; + + } + // Single line reporting + else { + + sass::string raw = pstate.getSource()->getLine(pstate.position.line); + sass::string line; utf8::replace_invalid(raw.begin(), raw.end(), + std::back_inserter(line), logstyle & SASS_LOGGER_UNICODE ? 0xfffd : '?'); + + // Convert to ASCII only string + if (logstyle & SASS_LOGGER_ASCII) { + std::replace_if(line.begin(), line.end(), + Character::isUtf8StartByte, '?'); + line.erase(std::remove_if(line.begin(), line.end(), + Character::isUtf8Continuation), line.end()); + } + + size_t lhs_len, mid_len; //, rhs_len; + // Get the sizes (characters) for each part + mid_len = pstate.span.column; + lhs_len = pstate.position.column; + + // Normalize tab characters to spaces for better counting + for (size_t i = line.length(); i != std::string::npos; i -= 1) { + if (line[i] == '\t') { + // Adjust highlight positions + if (i < lhs_len) lhs_len += 3; + else if (i < lhs_len + mid_len) mid_len += 3; + // Replace tab with spaces + line.replace(i, 1, " "); + } + } + + // Split line into parts to report + // They will be shortened if needed + sass::string lhs, mid, rhs; + splitLine(line, lhs_len, mid_len, + columns - 4 - padding, lhs, mid, rhs); + + // Get character length of each part + lhs_len = utf8::distance(lhs.begin(), lhs.end()); + mid_len = utf8::distance(mid.begin(), mid.end()); + // rhs_len = utf8::distance(rhs.begin(), rhs.end()); + + // Report the trace + stream + + // Write the leading line + << getColor(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << ' ' + << ' ' << top + << getColor(Terminal::reset) + + << STRMLF + + // Write the line number + << getColor(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << (beg.line + 1) + << ' ' << middle + << getColor(Terminal::reset) + + << ' ' + + // Write the parts + << lhs + << getColor(Terminal::red) + << mid + << getColor(Terminal::reset) + << rhs + + << STRMLF + + // Write left part of marker line + << getColor(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << ' ' + << ' ' << middle + << getColor(Terminal::reset) + + << ' ' + + // Write the actual marker + << sass::string(lhs_len, ' ') + << getColor(Terminal::red) + << sass::string(mid_len ? mid_len : 1, '^') + << getColor(Terminal::reset) + + << STRMLF + + // Write the trailing line + << getColor(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << ' ' + << ' ' << bottom + << getColor(Terminal::reset) + + << STRMLF; + + } + + } + + void Logger::splitLine(sass::string line, size_t lhs_len, size_t mid_len, + size_t columns, sass::string& lhs, sass::string& mid, sass::string& rhs) + { + + // Get the ellipsis character(s) either in unicode or ASCII + size_t ellipsis_len = style & SASS_LOGGER_UNICODE ? 1 : 3; + sass::string ellipsis(style & SASS_LOGGER_UNICODE ? "\xE2\x80\xA6": "..."); + + // Normalize tab characters to spaces for better counting + for (size_t i = line.length(); i != std::string::npos; i -= 1) { + if (line[i] == '\t') line.replace(i, 1, " "); + } + + // Get line iterators + auto line_beg = line.begin(); + auto line_end = line.end(); + + // Get the sizes (characters) for each part + size_t line_len = utf8::distance(line_beg, line_end); + size_t rhs_len = line_len - lhs_len - mid_len; + + // Prepare iterators for left side of highlighted string + auto lhs_beg = line.begin(), lhs_end = lhs_beg; + utf8::advance(lhs_end, lhs_len, line_end); + // Prepare iterators for right side of highlighted string + auto rhs_beg = lhs_end, rhs_end = line.end(); + utf8::advance(rhs_beg, mid_len, line_end); + + // Create substring of each part + lhs.append(lhs_beg, lhs_end); + rhs.append(rhs_beg, rhs_end); + mid.append(lhs_end, rhs_beg); + + // Trim trailing spaces + StringUtils::makeRightTrimmed(rhs); + + // Re-count characters after trimming is done + lhs_len = utf8::distance(lhs.begin(), lhs.end()); + rhs_len = utf8::distance(rhs.begin(), rhs.end()); + + // Get how much we want to show at least + size_t min_left = 12, min_right = 12; + // But we can't show more than we have + min_left = std::min(min_left, lhs_len); + min_right = std::min(min_right, rhs_len); + + // Calculate available size for highlighted part + size_t mid_max = columns - min_left - min_right; + + // Check if middle parts needs shortening + if (mid_len > mid_max) { + // Calculate how much we must shorten + size_t shorten = mid_len - mid_max + ellipsis_len; + // Get the sizes for left and right parts and adjust for epsilon + size_t lhs_size = size_t(std::ceil(0.5 * (mid_len - shorten))); + size_t rhs_size = size_t(std::floor(0.5 * (mid_len - shorten))); + // Prepare iterators for later substring operation + auto lhs_start = mid.begin(), rhs_stop = mid.end(); + auto lhs_stop = lhs_start; utf8::advance(lhs_stop, lhs_size, rhs_stop); + auto rhs_start = lhs_stop; utf8::advance(rhs_start, shorten, rhs_stop); + // Recreate shortened middle (highlight) part + mid = sass::string(lhs_start, lhs_stop) + + ellipsis + sass::string(rhs_start, rhs_stop); + // Update middle size (should be mid_max) + mid_len = lhs_size + rhs_size + ellipsis_len; + } + else { + // We can give some space back + size_t leftover = mid_max - mid_len; + // Distribute the leftovers + // ToDo: do it with math only + while (leftover) { + if (min_left < lhs_len) { + min_left += 1; + leftover -= 1; + if (min_right < rhs_len) { + if (leftover > 0) { + min_right += 1; + leftover -= 1; + } + } + } + else if (min_right < rhs_len) { + min_right += 1; + leftover -= 1; + } + else { + break; + } + } + } + + // Shorten left side + if (min_left < lhs_len) { + min_left = min_left - ellipsis_len; + auto beg = lhs.begin(), end = lhs.end(); + utf8::advance(beg, lhs_len - min_left, end); + lhs = sass::string(beg, end); + StringUtils::makeLeftTrimmed(lhs); + lhs = ellipsis + lhs; + } + + // Shorten right side + if (min_right < rhs_len) { + min_right = min_right - ellipsis_len; + auto beg = rhs.begin(), end = rhs.begin(); + utf8::advance(end, min_right, rhs.end()); + rhs = sass::string(beg, end); + StringUtils::makeRightTrimmed(rhs); + rhs = rhs + ellipsis; + } + + } + // EO splitLine + + // Print `amount` of `traces` to output stream `os`. + void Logger::writeStackTraces(sass::ostream& os, StackTraces traces, + sass::string indent, bool showPos, size_t amount) + { + sass::sstream strm; + + // bool first = true; + size_t max = 0; + size_t i_beg = traces.size() - 1; + size_t i_end = sass::string::npos; + + + sass::string last("root stylesheet"); + sass::vector> traced; + for (size_t i = 0; i < traces.size(); i++) { + + const StackTrace& trace = traces[i]; + + // make path relative to the current directory + sass::string rel_path(File::abs2rel(trace.pstate.getAbsPath(), CWD, CWD)); + + strm.str(sass::string()); + strm << rel_path << ' '; + strm << trace.pstate.getLine(); + strm << ":" << trace.pstate.getColumn(); + + sass::string str(strm.str()); + max = std::max(max, str.length()); + + if (i == 0) { + traced.emplace_back(std::make_pair( + str, last)); + } + else if (!traces[i - 1].name.empty()) { + last = traces[i - 1].name; + if (traces[i - 1].fn) last += "()"; + traced.emplace_back(std::make_pair(str, last)); + } + else { + traced.emplace_back(std::make_pair( + str, last)); + } + + } + + i_beg = traces.size() - 1; + i_end = sass::string::npos; + for (size_t i = i_beg; i != i_end; i--) { + + const StackTrace& trace = traces[i]; + + // make path relative to the current directory + sass::string rel_path(File::abs2rel(trace.pstate.getAbsPath(), CWD, CWD)); + + // skip functions on error cases (unsure why ruby sass does this) + // if (trace.caller.substr(0, 6) == ", in f") continue; + + if (amount == sass::string::npos || amount > 0) { + printSourceSpan(trace.pstate, os, style); + if (amount > 0) --amount; + } + + if (showPos) { + os << indent; + os << std::left << std::setfill(' ') + << std::setw(max + 2) << traced[i].first; + os << traced[i].second; + os << STRMLF; + } + + } + + os << STRMLF; + + } + + + + + + + + + + + + + + +} diff --git a/src/logger.hpp b/src/logger.hpp new file mode 100644 index 0000000000..2f0eb5c88e --- /dev/null +++ b/src/logger.hpp @@ -0,0 +1,165 @@ +#ifndef SASS_LOGGER_HPP +#define SASS_LOGGER_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include +#include "strings.hpp" +#include "constants.hpp" +#include "backtrace.hpp" +#include "callstack.hpp" +#include "terminal.hpp" + +namespace Sass { + + // The logger belongs to context + class Logger { + + public: + + // Epsilon for precision + double epsilon; + + // warning buffers + sass::ostream logstrm; + + // The current callstack + BackTraces callStack; + + // Available columns on tty + size_t columns; + + // Flag for unicode and/or color + enum SassLoggerStyle style; + + // Append the pstate if not already there + // Only call this right before throwing errors + void addFinalStackTrace(const SourceSpan& pstate) + { + if (callStack.empty()) { + callStack.push_back(pstate); + } + else { + BackTrace& trace(callStack.back()); + if (!(trace.pstate == pstate)) { + callStack.push_back(pstate); + } + } + } + // EO addFinalStackTrace + + private: + + // Split the line to three parts for error reporting. + // The `lhs_len` is the position where the error reporting + // should start and `mid_len` how many characters should be + // highlighted. It will fill the strings `lhs`, `mid` and `rhs` + // accordingly so the total length is not bigger than `columns`. + // Strings might be shortened and ellipsis are added as needed. + void splitLine( + sass::string line, + size_t lhs_len, + size_t mid_len, + size_t columns, + sass::string& lhs, + sass::string& mid, + sass::string& rhs); + + // Helper function to ease color output. Returns the + // passed color if color output is enable, otherwise + // it will simply return an empty string. + inline const char* getColor(const char* color) { + if (style & SASS_LOGGER_COLOR) { + return color; + } + return Constants::String::empty; + } + + // Write warning header to error stream + void writeWarnHead( + bool deprecation = false); + + // Print to stderr stream + void printWarning( + const sass::string& message, + const SourceSpan& pstate, + bool deprecation = false); + + void printSourceSpan( + SourceSpan pstate, + sass::ostream& stream, + enum SassLoggerStyle logstyle); + + public: + + void writeStackTraces(sass::ostream& os, + StackTraces traces, + sass::string indent = " ", + bool showPos = true, + size_t showTraces = sass::string::npos); + + public: + + Logger(enum SassLoggerStyle style = SASS_LOGGER_ASCII_MONO, + int precision = SassDefaultPrecision, size_t columns = NPOS); + + void setLogPrecision(int precision); + + void setLogStyle(enum SassLoggerStyle style); + + void addWarning(const sass::string& message); + + void addDebug(const sass::string& message, const SourceSpan& pstate); + + void addWarning(const sass::string& message, const SourceSpan& pstate) + { + printWarning(message, pstate, false); + } + + void addDeprecation(const sass::string& message, const SourceSpan& pstate) + { + printWarning(message, pstate, true); + } + + public: + + operator BackTraces&() { + return callStack; } + + // virtual ~Logger() {}; + + }; + /* + class StdLogger : + public Logger { + + public: + + StdLogger(int precision, enum SassLoggerStyle style = SASS_LOGGER_AUTO); + + void warn(sass::string message) const override final; + void debug(sass::string message) const override final; + void error(sass::string message) const override final; + + ~StdLogger() override final {}; + + }; + + class NullLogger : + public Logger { + + public: + + void warn(sass::string message) const override final {}; + void debug(sass::string message) const override final {}; + void error(sass::string message) const override final {}; + + ~NullLogger() override final {}; + + }; + */ +} + +#endif diff --git a/src/mapping.hpp b/src/mapping.hpp index d6a3336aa7..5681414b8b 100644 --- a/src/mapping.hpp +++ b/src/mapping.hpp @@ -1,19 +1,30 @@ #ifndef SASS_MAPPING_H #define SASS_MAPPING_H -#include "position.hpp" -#include "backtrace.hpp" +#include +#include "memory.hpp" +#include "offset.hpp" namespace Sass { - struct Mapping { - Position original_position; - Position generated_position; + class Mapping { + + public: + // Source Index for the origin + size_t srcidx; // uint32_t + // Position of original occurrence + Offset origin; + // Position where it was rendered. + Offset target; + + // Base copy constructor + Mapping(size_t srcidx, const Offset& origin, const Offset& target) + : srcidx(srcidx), origin(origin), target(target) { } - Mapping(const Position& original_position, const Position& generated_position) - : original_position(original_position), generated_position(generated_position) { } }; + typedef sass::vector Mappings; + } #endif diff --git a/src/memory.hpp b/src/memory.hpp index 14a5541b6d..68a19eb442 100644 --- a/src/memory.hpp +++ b/src/memory.hpp @@ -1,5 +1,8 @@ -#ifndef SASS_MEMORY_H -#define SASS_MEMORY_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_MEMORY_HPP +#define SASS_MEMORY_HPP #include "settings.hpp" diff --git a/src/memory/allocator.cpp b/src/memory/allocator.cpp index 7ad4a542ab..0e3ee0f01d 100644 --- a/src/memory/allocator.cpp +++ b/src/memory/allocator.cpp @@ -1,31 +1,34 @@ -#include "../sass.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "allocator.hpp" -#include "memory_pool.hpp" -#if defined (_MSC_VER) // Visual studio -#define thread_local __declspec( thread ) -#elif defined (__GCC__) // GCC -#define thread_local __thread +#ifdef SASS_CUSTOM_ALLOCATOR +#include "memory_pool.hpp" #endif namespace Sass { #ifdef SASS_CUSTOM_ALLOCATOR - // Only use PODs for thread_local - // Objects get unpredictable init order + // You must only use PODs for thread_local. + // Objects get very unpredictable init order. static thread_local MemoryPool* pool; static thread_local size_t allocations; + // Allocate memory from the memory pool. + // Memory pool is allocated on first call. void* allocateMem(size_t size) { if (pool == nullptr) { pool = new MemoryPool(); } - allocations++; + ++allocations; return pool->allocate(size); } + // Release the memory from the pool. + // Destroys the pool when it is emptied. void deallocateMem(void* ptr, size_t size) { diff --git a/src/memory/allocator.hpp b/src/memory/allocator.hpp index a830457107..f13dfc08cb 100644 --- a/src/memory/allocator.hpp +++ b/src/memory/allocator.hpp @@ -1,10 +1,15 @@ -#ifndef SASS_ALLOCATOR_H -#define SASS_ALLOCATOR_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_ALLOCATOR_HPP +#define SASS_ALLOCATOR_HPP #include "config.hpp" #include "../settings.hpp" #include "../MurmurHash2.hpp" +#include "../randomize.hpp" +#include #include #include #include @@ -13,14 +18,17 @@ namespace Sass { -#ifndef SASS_CUSTOM_ALLOCATOR - + // Fallback to standard allocator + #ifndef SASS_CUSTOM_ALLOCATOR template using Allocator = std::allocator; + #else -#else - + // Allocate memory from the memory pool. + // Memory pool is allocated on first call. void* allocateMem(size_t size); + // Release the memory from the pool. + // Destroys the pool when it is emptied. void deallocateMem(void* ptr, size_t size = 1); template @@ -38,16 +46,17 @@ namespace Sass { typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; + // Not really sure what this does!? template struct rebind { typedef Allocator other; }; - // Constructor + // Default constructor Allocator(void) {} - // Copy Constructor + // Copy constructor template Allocator(Allocator const&) {} @@ -90,6 +99,7 @@ namespace Sass { }; + // Allocators are equal, don't care for type! template bool operator==(Allocator const& left, Allocator const& right) @@ -97,6 +107,7 @@ namespace Sass { return true; } + // Allocators are equal, don't care for type! template bool operator!=(Allocator const& left, Allocator const& right) @@ -104,31 +115,36 @@ namespace Sass { return !(left == right); } -#endif + // EO custom allocator + #endif - namespace sass { - template using vector = std::vector>; - using string = std::basic_string, Sass::Allocator>; - using sstream = std::basic_stringstream, Sass::Allocator>; - using ostream = std::basic_ostringstream, Sass::Allocator>; - using istream = std::basic_istringstream, Sass::Allocator>; - } +} +// Make them available on the global scope +// Easier for global structs needed for C linkage +namespace sass { // Note the lower-case notation + template using deque = std::deque>; + template using vector = std::vector>; + using string = std::basic_string, Sass::Allocator>; + using wstring = std::basic_string, Sass::Allocator>; + using sstream = std::basic_stringstream, Sass::Allocator>; + using ostream = std::basic_ostringstream, Sass::Allocator>; + using istream = std::basic_istringstream, Sass::Allocator>; } #ifdef SASS_CUSTOM_ALLOCATOR namespace std { // Only GCC seems to need this specialization!? - template <> struct hash { + template <> struct hash { public: inline size_t operator()( - const Sass::sass::string& name) const + const sass::string& name) const { return MurmurHash2( (void*)name.c_str(), (int)name.size(), - 0x73617373); + Sass::getHashSeed()); } }; } diff --git a/src/memory/config.hpp b/src/memory/config.hpp index 9d666b2e6b..9b9934cb7f 100644 --- a/src/memory/config.hpp +++ b/src/memory/config.hpp @@ -1,6 +1,15 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #ifndef SASS_MEMORY_CONFIG_H #define SASS_MEMORY_CONFIG_H +#include "../settings.hpp" + +///////////////////////////////////////////////////////////////////////// +// Memory allocator configurations +///////////////////////////////////////////////////////////////////////// + // Define memory alignment requirements #define SASS_MEM_ALIGN sizeof(unsigned int) @@ -17,4 +26,22 @@ // Currently unused and for later optimization #define SassAllocatorArenaHeadSize 0 -#endif \ No newline at end of file +///////////////////////////////////////////////////////////////////////// +// Below settings should only be changed if you know what you do! +///////////////////////////////////////////////////////////////////////// + +// Detail settings for pool allocator +#ifdef SASS_CUSTOM_ALLOCATOR + + // How many buckets should we have for the free-list + // Determines when allocations go directly to malloc/free + // For maximum size of managed items multiply by alignment + #define SassAllocatorBuckets 640 + + // The size of the memory pool arenas in bytes. + #define SassAllocatorArenaSize (1024 * 640) + +#endif +// EO SASS_CUSTOM_ALLOCATOR + +#endif diff --git a/src/memory/memory_pool.hpp b/src/memory/memory_pool.hpp index d2cef9930d..b41978bcbd 100644 --- a/src/memory/memory_pool.hpp +++ b/src/memory/memory_pool.hpp @@ -1,15 +1,23 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #ifndef SASS_MEMORY_POOL_H #define SASS_MEMORY_POOL_H -#include -#include -#include -#include #include +#include +#include +#include + +#include "config.hpp" namespace Sass { +#ifdef SASS_CUSTOM_ALLOCATOR + + ///////////////////////////////////////////////////////////////////////// // SIMPLE MEMORY-POOL ALLOCATOR WITH FREE-LIST ON TOP + ///////////////////////////////////////////////////////////////////////// // This is a memory pool allocator with a free list on top. // We only allocate memory arenas from the system in specific @@ -30,10 +38,13 @@ namespace Sass { // https://en.wikipedia.org/wiki/Free_list // On allocation calls we first check if there is any suitable - // item on the free-list. If there is we pop it from the stack + // item on the free-list. If there is, we pop it from the stack // and return it to the caller. Otherwise we have to take out // a new slice from the current `arena` and increase `offset`. + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + // Note that this is not thread safe. This is on purpose as we // want to use the memory pool in a thread local usage. In order // to get this thread safe you need to only allocate one pool @@ -43,6 +54,9 @@ namespace Sass { // static thread_local size_t allocations; // static thread_local MemoryPool* pool; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + class MemoryPool { // Current arena we fill up @@ -75,8 +89,7 @@ namespace Sass { // Set to maximum value in order to // make an allocation on the first run offset(std::string::npos) - { - } + {} // Destructor ~MemoryPool() { @@ -86,7 +99,6 @@ namespace Sass { } // Delete current arena free(arena); - } // Allocate a slice of the memory pool @@ -154,6 +166,7 @@ namespace Sass { } // EO allocate + // Deallocate the pointer void deallocate(void* ptr) { @@ -181,6 +194,8 @@ namespace Sass { }; +#endif + } #endif diff --git a/src/memory/shared_ptr.cpp b/src/memory/shared_ptr.cpp index faa5796fab..a4ce1d2faa 100644 --- a/src/memory/shared_ptr.cpp +++ b/src/memory/shared_ptr.cpp @@ -1,14 +1,13 @@ -#include "../sass.hpp" -#include -#include - #include "shared_ptr.hpp" -#include "../ast_fwd_decl.hpp" #ifdef DEBUG_SHARED_PTR #include "../debugger.hpp" +#include +#include #endif +#include "../source.hpp" + namespace Sass { #ifdef DEBUG_SHARED_PTR @@ -18,16 +17,29 @@ namespace Sass { std::cerr << "# REPORTING MISSING DEALLOCATIONS #\n"; std::cerr << "###################################\n"; for (SharedObj* var : all) { - if (AST_Node* ast = dynamic_cast(var)) { + if (AstNode* ast = dynamic_cast(var)) { + std::cerr << "LEAKED AST " << ast->getDbgFile() << ":" << ast->getDbgLine() << "\n"; debug_ast(ast); - } else { + } + else if (SourceData* ast = dynamic_cast(var)) { + std::cerr << "LEAKED SOURCE " << ast->getDbgFile() << ":" << ast->getDbgLine() << "\n"; + } + else { std::cerr << "LEAKED " << var << "\n"; } } + all.clear(); + deleted.clear(); + objCount = 0; } } + size_t SharedObj::objCount = 0; sass::vector SharedObj::all; - #endif + std::unordered_set SharedObj::deleted; + size_t SharedObj::maxRefCount = 0; +#endif bool SharedObj::taint = false; + // size_t SharedObj::moves = 0; + // size_t SharedObj::copies = 0; } diff --git a/src/memory/shared_ptr.hpp b/src/memory/shared_ptr.hpp index 09d263afb5..f54d72438b 100644 --- a/src/memory/shared_ptr.hpp +++ b/src/memory/shared_ptr.hpp @@ -1,51 +1,85 @@ -#ifndef SASS_MEMORY_SHARED_PTR_H -#define SASS_MEMORY_SHARED_PTR_H +#ifndef SASS_MEMORY_SHARED_PTR_HPP +#define SASS_MEMORY_SHARED_PTR_HPP -#include "sass/base.h" - -#include "../sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "../capi_sass.hpp" #include "allocator.hpp" + #include #include #include #include #include +#ifdef DEBUG_SHARED_PTR +#include +#endif + // https://lokiastari.com/blog/2014/12/30/c-plus-plus-by-example-smart-pointer/index.html // https://lokiastari.com/blog/2015/01/15/c-plus-plus-by-example-smart-pointer-part-ii/index.html // https://lokiastari.com/blog/2015/01/23/c-plus-plus-by-example-smart-pointer-part-iii/index.html +// https://www.youtube.com/watch?v=LIb3L4vKZ7U - allocator composition (freelist and bucketizer) namespace Sass { - // Forward declaration class SharedPtr; - /////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// // Use macros for the allocation task, since overloading operator `new` - // has been proven to be flaky under certain compilers (see comment below). - /////////////////////////////////////////////////////////////////////////////// + // has been proven to be flaky under certain compilers. This allows us + // to track memory issues better by adding the source location for every + // memory allocation, so once it leaks we know where it was created. + /////////////////////////////////////////////////////////////////////////// #ifdef DEBUG_SHARED_PTR + // Macro to call the constructor + // Attach file/line in debug mode #define SASS_MEMORY_NEW(Class, ...) \ ((Class*)(new Class(__VA_ARGS__))->trace(__FILE__, __LINE__)) \ - #define SASS_MEMORY_COPY(obj) \ - ((obj)->copy(__FILE__, __LINE__)) \ + // Macro to call the constructor + // Attach file/line in debug mode + #define SASS_MEMORY_NEW_DBG(Class, ...) \ + ((Class*)(new Class(__VA_ARGS__))->trace(file, line)) \ - #define SASS_MEMORY_CLONE(obj) \ - ((obj)->clone(__FILE__, __LINE__)) \ + // Macro to call the constructor + // Attach file/line in debug mode + #define SASS_MEMORY_NEW(Class, ...) \ + ((Class*)(new Class(__VA_ARGS__))->trace(__FILE__, __LINE__)) \ + + // Copy object and zero children + // The children array is empty + #define SASS_MEMORY_RESECT(obj) \ + ((obj)->copy(__FILE__, __LINE__, true)) \ + + // Full copy but same children + // Children are the same reference + #define SASS_MEMORY_COPY(obj) \ + ((obj)->copy(__FILE__, __LINE__, false)) \ #else + // Macro to call the constructor + // Attach file/line in debug mode #define SASS_MEMORY_NEW(Class, ...) \ new Class(__VA_ARGS__) \ - #define SASS_MEMORY_COPY(obj) \ - ((obj)->copy()) \ + // Macro to call the constructor + // Attach file/line in debug mode + #define SASS_MEMORY_NEW_DBG(Class, ...) \ + new Class(__VA_ARGS__) \ - #define SASS_MEMORY_CLONE(obj) \ - ((obj)->clone()) \ + // Copy object and zero children + // The children array is empty + #define SASS_MEMORY_RESECT(obj) \ + ((obj)->copy(true)) \ + + // Full copy but same children + // Children are the same reference + #define SASS_MEMORY_COPY(obj) \ + ((obj)->copy(false)) \ #endif @@ -60,10 +94,14 @@ namespace Sass { // too by using `std::make_shared` (where the control block and the actual // object are allocated in one continuous memory block via one single call). class SharedObj { + public: - SharedObj() : refcount(0), detached(false) { + SharedObj() : refcount(0) { #ifdef DEBUG_SHARED_PTR - if (taint) all.push_back(this); + this->objId = ++objCount; + if (taint) { + all.emplace_back(this); + } #endif } virtual ~SharedObj() { @@ -74,10 +112,16 @@ namespace Sass { break; } } + erased = true; #endif } + #ifdef DEBUG_SHARED_PTR + static void reportRefCounts() { + std::cerr << "Max refcount: " << + SharedObj::maxRefCount << "\n"; + } static void dumpMemLeaks(); SharedObj* trace(sass::string file, size_t line) { this->file = file; @@ -101,42 +145,74 @@ namespace Sass { } #endif - virtual sass::string to_string() const = 0; protected: friend class SharedPtr; - friend class Memory_Manager; - size_t refcount; - bool detached; + friend class MemoryPool; + public: + public: + uint32_t refcount; + public: static bool taint; - #ifdef DEBUG_SHARED_PTR +#ifdef DEBUG_SHARED_PTR + static size_t maxRefCount; sass::string file; size_t line; + public: + size_t objId; + protected: bool dbg = false; + bool erased = false; + static size_t objCount; static sass::vector all; - #endif + static std::unordered_set deleted; +#endif }; // SharedPtr is a intermediate (template-less) base class for SharedImpl. // ToDo: there should be a way to include this in SharedImpl and to get // ToDo: rid of all the static_cast that are now needed in SharedImpl. class SharedPtr { - public: + + protected: + + SharedObj* node; + + private: + + static const uint32_t SET_DETACHED_BITMASK = (uint32_t(1) << (sizeof(uint32_t) * 8 - 1)); + static const uint32_t UNSET_DETACHED_BITMASK = ~(uint32_t(1) << (sizeof(uint32_t) * 8 - 1)); + + public: SharedPtr() : node(nullptr) {} - SharedPtr(SharedObj* ptr) : node(ptr) { - incRefCount(); - } + SharedPtr(SharedObj* ptr) : node(ptr) { incRefCount(); } SharedPtr(const SharedPtr& obj) : SharedPtr(obj.node) {} - ~SharedPtr() { + SharedPtr(SharedPtr&& obj) noexcept : node(std::move(obj.node)) { + obj.node = nullptr; // reset old node pointer + } + virtual ~SharedPtr() { decRefCount(); } SharedPtr& operator=(SharedObj* other_node) { if (node != other_node) { - decRefCount(); + if (node) decRefCount(); node = other_node; incRefCount(); } else if (node != nullptr) { - node->detached = false; + node->refcount &= UNSET_DETACHED_BITMASK; + } + return *this; + } + + SharedPtr& operator=(SharedPtr&& obj) noexcept + { + if (node != obj.node) { + if (node) decRefCount(); + node = obj.node; + obj.node = nullptr; + } + else if (node != nullptr) { + node->refcount &= UNSET_DETACHED_BITMASK; } return *this; } @@ -147,52 +223,88 @@ namespace Sass { // Prevents all SharedPtrs from freeing this node until it is assigned to another SharedPtr. SharedObj* detach() { - if (node != nullptr) node->detached = true; + if (node != nullptr) { + node->refcount |= SET_DETACHED_BITMASK; + } #ifdef DEBUG_SHARED_PTR - if (node->dbg) { + if (node && node->dbg) { std::cerr << "DETACHING NODE\n"; } - #endif + #endif return node; } - SharedObj* obj() const { return node; } - SharedObj* operator->() const { return node; } + void clear() { + if (node != nullptr) { + decRefCount(); + node = nullptr; + } + } + + SharedObj* obj() const { + #ifdef DEBUG_SHARED_PTR + if (node && node->deleted.count(node->objId) == 1) { + std::cerr << "ACCESSING DELETED " << node << "\n"; + } + #endif + return node; + } + SharedObj* operator->() const { + #ifdef DEBUG_SHARED_PTR + if (node && node->deleted.count(node->objId) == 1) { + std::cerr << "ACCESSING DELETED " << node << "\n"; + } + #endif + return node; + } bool isNull() const { return node == nullptr; } operator bool() const { return node != nullptr; } - protected: - SharedObj* node; - void decRefCount() { + protected: + + // ##__declspec(noinline) + inline void decRefCount() noexcept { if (node == nullptr) return; - --node->refcount; #ifdef DEBUG_SHARED_PTR - if (node->dbg) std::cerr << "- " << node << " X " << node->refcount << " (" << this << ") " << "\n"; + if (node->dbg) { + std::cerr << "- " << node << " X " << ((node->refcount & SET_DETACHED_BITMASK) ? "detached " : "") + << (node->refcount - (node->refcount & SET_DETACHED_BITMASK)) << " (" << this << ") " << "\n"; + } #endif - if (node->refcount == 0 && !node->detached) { + if (--node->refcount == 0) { #ifdef DEBUG_SHARED_PTR - if (node->dbg) std::cerr << "DELETE NODE " << node << "\n"; + if (node->dbg) { + std::cerr << "DELETE NODE " << node << "\n"; + } + // node->deleted.insert(node->objId); #endif delete node; } - else if (node->refcount == 0) { - #ifdef DEBUG_SHARED_PTR - if (node->dbg) std::cerr << "NODE EVAEDED DELETE " << node << "\n"; - #endif + #ifdef DEBUG_SHARED_PTR + else if (node->refcount & SET_DETACHED_BITMASK) { + if (node->dbg) { + std::cerr << "NODE EVADED DELETE " << node << "\n"; + } } + #endif } - void incRefCount() { + void incRefCount() noexcept { if (node == nullptr) return; - node->detached = false; + node->refcount &= UNSET_DETACHED_BITMASK; ++node->refcount; #ifdef DEBUG_SHARED_PTR - if (node->dbg) std::cerr << "+ " << node << " X " << node->refcount << " (" << this << ") " << "\n"; + if (SharedObj::maxRefCount < node->refcount) { + SharedObj::maxRefCount = node->refcount; + } + if (node->dbg) { + std::cerr << "+ " << node << " X " << node->refcount << " (" << this << ") " << "\n"; + } #endif } }; template - class SharedImpl : private SharedPtr { + class SharedImpl final : private SharedPtr { public: SharedImpl() : SharedPtr(nullptr) {} @@ -212,14 +324,15 @@ namespace Sass { } template - SharedImpl& operator=(const SharedImpl& rhs) { + SharedImpl& operator=(SharedImpl&& rhs) { return static_cast&>( - SharedPtr::operator=(static_cast&>(rhs))); + SharedPtr::operator=(std::move(static_cast&>(rhs)))); } - operator sass::string() const { - if (node) return node->to_string(); - return "null"; + template + SharedImpl& operator=(const SharedImpl& rhs) { + return static_cast&>( + SharedPtr::operator=(static_cast&>(rhs))); } using SharedPtr::isNull; @@ -230,7 +343,7 @@ namespace Sass { T* operator-> () const { return static_cast(this->obj()); }; T* ptr () const { return static_cast(this->obj()); }; T* detach() { return static_cast(SharedPtr::detach()); } - + void clear() { return SharedPtr::clear(); } }; // Comparison operators, based on: diff --git a/src/offset.cpp b/src/offset.cpp new file mode 100644 index 0000000000..fd029acdb9 --- /dev/null +++ b/src/offset.cpp @@ -0,0 +1,170 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "offset.hpp" + +#include "charcode.hpp" +#include "character.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + using namespace Charcode; + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Default constructor + Offset::Offset() {} + + // Create an Offset from the given string + // Will use `plus` internally on all chars + Offset::Offset(uint8_t character) + { + plus(character); + } + + // Create an Offset from the given string + // Will use `plus` internally on all chars + Offset::Offset(const sass::string& text) + { + for (uint8_t character : text) { + plus(character); + } + } + + // Create an Offset from the given char star + // Will use `plus` internally on all chars + Offset::Offset(const char* text, const char* end) + { + if (end == nullptr) { + while (*text != 0) { + plus(*text++); + } + } + else { + while (text < end) { + plus(*text++); + } + } + } + + // Append [character] to increment offset + void Offset::plus(uint8_t character) + { + switch (character) { + case $lf: + line += 1; + column = 0; + break; + case $space: + case $tab: + case $vt: + case $ff: + case $cr: + column += 1; + break; + default: + // skip over 10xxxxxx and 01xxxxxx + // count ASCII and initial utf8 bytes + if (Character::isCharacter(character)) { + // 64 => initial utf8 byte + // 128 => regular ASCII char + column += 1; + } + break; + } + } + + // Append [text] to increment offset + void Offset::plus(const sass::string& text) + { + for (uint8_t character : text) { + plus(character); + } + } + + // Create offset with given [line] and [column] + // Needs static constructor to avoid ambiguity + Offset Offset::init(size_t line, size_t column) + { + Offset offset; + offset.line = line == NPOS ? -1 : (uint32_t) line; + offset.column = column == NPOS ? -1 : (uint32_t) column; + return offset; + } + // EO Offset::init + + // Return the `distance` between [start[ and [end] + // Gives the solution to the equation `end = start + x` + Offset Offset::distance(const Offset& start, const Offset& end) + { + Offset rv(end); + // Both are on the same line + if (start.line == end.line) { + // Get distance between columns + rv.column -= start.column; + // Line distance is zero + rv.line = 0; + } + else { + // Get distance between lines + rv.line -= start.line; + // Columns don't need to be changed + // Since we land on another line, we + // will reach the same end column + } + return rv; + } + // EO Offset::distance + + // Implement equality operators + bool Offset::operator== (const Offset& rhs) const + { + return line == rhs.line + && column == rhs.column; + } + // EO Offset::operator== + + // Implement assign and addition operator + void Offset::operator+= (const Offset& rhs) + { + // lines are always summed up + line += rhs.line; + // columns may need to be reset + if (rhs.line == 0) { + column += rhs.column; + } + else { + column = rhs.column; + } + } + // EO Offset::operator+= + + // Implement addition operator (returns new Offset) + Offset Offset::operator+ (const Offset& rhs) const + { + Offset rv(*this); + rv += rhs; + return rv; + } + // EO Offset::operator+ + + // Implement multiply operator (returns new Offset) + Offset Offset::operator* (uint32_t mul) const + { + Offset rv(*this); + if (rv.line == 0) { + rv.column *= mul; + } + else { + rv.line *= mul; + } + return rv; + } + // EO Offset::operator* + +} diff --git a/src/offset.hpp b/src/offset.hpp new file mode 100644 index 0000000000..f1706549c0 --- /dev/null +++ b/src/offset.hpp @@ -0,0 +1,86 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_OFFSET_HPP +#define SASS_OFFSET_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_def_macros.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Basic class for text file positions + // The logic how to count characters and + // to add/subtract are implemented here. + class Offset + { + public: + + // All properties are public (zero based) + // Getters return human-readable form (+1) + uint32_t line = 0; + uint32_t column = 0; + + // Default constructor + Offset(); + + // Create an Offset from the given string + // Will use `plus` internally on all chars + Offset(uint8_t character); + + // Create an Offset from the given string + // Will use `plus` internally on all chars + Offset(const sass::string& text); + + // Create an Offset from the given char star + // Will use `plus` internally on all chars + Offset(const char* beg, const char* end = 0); + + // Append [character] to increment offset + void plus(uint8_t character); + + // Append [text] to increment offset + void plus(const sass::string& text); + + // Create offset with given [line] and [column] + // Needs static constructor to avoid ambiguity + static Offset init(size_t line, size_t column); + + // Return the `distance` between [start[ and [end] + // Gives the solution to the equation `end = start + x` + static Offset distance(const Offset& start, const Offset& end); + + // Assign and increment operator + void operator+= (const Offset& pos); + + // Plus operator (returns new Offset) + Offset operator+ (const Offset& off) const; + + // Multiply operator (returns new Offset) + Offset operator* (uint32_t mul) const; + + // Implement equal and derive unequal + bool operator==(const Offset& rhs) const; + + // Delete other operators to make implementation more clear + // Helps us spot cases where we use undefined implementations + // bool operator!=(const Offset& rhs) const = delete; + // bool operator>=(const Offset& rhs) const = delete; + // bool operator<=(const Offset& rhs) const = delete; + // bool operator>(const Offset& rhs) const = delete; + // bool operator<(const Offset& rhs) const = delete; + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/operation.hpp b/src/operation.hpp deleted file mode 100644 index fafd2c73be..0000000000 --- a/src/operation.hpp +++ /dev/null @@ -1,223 +0,0 @@ -#ifndef SASS_OPERATION_H -#define SASS_OPERATION_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -// base classes to implement curiously recurring template pattern (CRTP) -// https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern - -#include -#include - -#include "ast_fwd_decl.hpp" -#include "ast_def_macros.hpp" - -namespace Sass { - - #define ATTACH_ABSTRACT_CRTP_PERFORM_METHODS()\ - virtual void perform(Operation* op) = 0; \ - virtual Value* perform(Operation* op) = 0; \ - virtual sass::string perform(Operation* op) = 0; \ - virtual AST_Node* perform(Operation* op) = 0; \ - virtual Selector* perform(Operation* op) = 0; \ - virtual Statement* perform(Operation* op) = 0; \ - virtual Expression* perform(Operation* op) = 0; \ - virtual union Sass_Value* perform(Operation* op) = 0; \ - virtual SupportsCondition* perform(Operation* op) = 0; \ - - // you must add operators to every class - // ensures `this` of actual instance type - // we therefore call the specific operator - // they are virtual so most specific is used - #define ATTACH_CRTP_PERFORM_METHODS()\ - virtual void perform(Operation* op) override { return (*op)(this); } \ - virtual Value* perform(Operation* op) override { return (*op)(this); } \ - virtual sass::string perform(Operation* op) override { return (*op)(this); } \ - virtual AST_Node* perform(Operation* op) override { return (*op)(this); } \ - virtual Selector* perform(Operation* op) override { return (*op)(this); } \ - virtual Statement* perform(Operation* op) override { return (*op)(this); } \ - virtual Expression* perform(Operation* op) override { return (*op)(this); } \ - virtual union Sass_Value* perform(Operation* op) override { return (*op)(this); } \ - virtual SupportsCondition* perform(Operation* op) override { return (*op)(this); } \ - - template - class Operation { - public: - virtual T operator()(AST_Node* x) = 0; - // statements - virtual T operator()(Block* x) = 0; - virtual T operator()(StyleRule* x) = 0; - virtual T operator()(Bubble* x) = 0; - virtual T operator()(Trace* x) = 0; - virtual T operator()(SupportsRule* x) = 0; - virtual T operator()(MediaRule* x) = 0; - virtual T operator()(CssMediaRule* x) = 0; - virtual T operator()(CssMediaQuery* x) = 0; - virtual T operator()(AtRootRule* x) = 0; - virtual T operator()(AtRule* x) = 0; - virtual T operator()(Keyframe_Rule* x) = 0; - virtual T operator()(Declaration* x) = 0; - virtual T operator()(Assignment* x) = 0; - virtual T operator()(Import* x) = 0; - virtual T operator()(Import_Stub* x) = 0; - virtual T operator()(WarningRule* x) = 0; - virtual T operator()(ErrorRule* x) = 0; - virtual T operator()(DebugRule* x) = 0; - virtual T operator()(Comment* x) = 0; - virtual T operator()(If* x) = 0; - virtual T operator()(ForRule* x) = 0; - virtual T operator()(EachRule* x) = 0; - virtual T operator()(WhileRule* x) = 0; - virtual T operator()(Return* x) = 0; - virtual T operator()(Content* x) = 0; - virtual T operator()(ExtendRule* x) = 0; - virtual T operator()(Definition* x) = 0; - virtual T operator()(Mixin_Call* x) = 0; - // expressions - virtual T operator()(Null* x) = 0; - virtual T operator()(List* x) = 0; - virtual T operator()(Map* x) = 0; - virtual T operator()(Function* x) = 0; - virtual T operator()(Binary_Expression* x) = 0; - virtual T operator()(Unary_Expression* x) = 0; - virtual T operator()(Function_Call* x) = 0; - virtual T operator()(Custom_Warning* x) = 0; - virtual T operator()(Custom_Error* x) = 0; - virtual T operator()(Variable* x) = 0; - virtual T operator()(Number* x) = 0; - virtual T operator()(Color* x) = 0; - virtual T operator()(Color_RGBA* x) = 0; - virtual T operator()(Color_HSLA* x) = 0; - virtual T operator()(Boolean* x) = 0; - virtual T operator()(String_Schema* x) = 0; - virtual T operator()(String_Quoted* x) = 0; - virtual T operator()(String_Constant* x) = 0; - virtual T operator()(SupportsCondition* x) = 0; - virtual T operator()(SupportsOperation* x) = 0; - virtual T operator()(SupportsNegation* x) = 0; - virtual T operator()(SupportsDeclaration* x) = 0; - virtual T operator()(Supports_Interpolation* x) = 0; - virtual T operator()(Media_Query* x) = 0; - virtual T operator()(Media_Query_Expression* x) = 0; - virtual T operator()(At_Root_Query* x) = 0; - virtual T operator()(Parent_Reference* x) = 0; - // parameters and arguments - virtual T operator()(Parameter* x) = 0; - virtual T operator()(Parameters* x) = 0; - virtual T operator()(Argument* x) = 0; - virtual T operator()(Arguments* x) = 0; - // selectors - virtual T operator()(Selector_Schema* x) = 0; - virtual T operator()(PlaceholderSelector* x) = 0; - virtual T operator()(TypeSelector* x) = 0; - virtual T operator()(ClassSelector* x) = 0; - virtual T operator()(IDSelector* x) = 0; - virtual T operator()(AttributeSelector* x) = 0; - virtual T operator()(PseudoSelector* x) = 0; - virtual T operator()(SelectorComponent* x) = 0; - virtual T operator()(SelectorCombinator* x) = 0; - virtual T operator()(CompoundSelector* x) = 0; - virtual T operator()(ComplexSelector* x) = 0; - virtual T operator()(SelectorList* x) = 0; - - }; - - // example: Operation_CRTP - // T is the base return type of all visitors - // D is the class derived visitor class - // normally you want to implement all operators - template - class Operation_CRTP : public Operation { - public: - T operator()(AST_Node* x) { return static_cast(this)->fallback(x); } - // statements - T operator()(Block* x) { return static_cast(this)->fallback(x); } - T operator()(StyleRule* x) { return static_cast(this)->fallback(x); } - T operator()(Bubble* x) { return static_cast(this)->fallback(x); } - T operator()(Trace* x) { return static_cast(this)->fallback(x); } - T operator()(SupportsRule* x) { return static_cast(this)->fallback(x); } - T operator()(MediaRule* x) { return static_cast(this)->fallback(x); } - T operator()(CssMediaRule* x) { return static_cast(this)->fallback(x); } - T operator()(CssMediaQuery* x) { return static_cast(this)->fallback(x); } - T operator()(AtRootRule* x) { return static_cast(this)->fallback(x); } - T operator()(AtRule* x) { return static_cast(this)->fallback(x); } - T operator()(Keyframe_Rule* x) { return static_cast(this)->fallback(x); } - T operator()(Declaration* x) { return static_cast(this)->fallback(x); } - T operator()(Assignment* x) { return static_cast(this)->fallback(x); } - T operator()(Import* x) { return static_cast(this)->fallback(x); } - T operator()(Import_Stub* x) { return static_cast(this)->fallback(x); } - T operator()(WarningRule* x) { return static_cast(this)->fallback(x); } - T operator()(ErrorRule* x) { return static_cast(this)->fallback(x); } - T operator()(DebugRule* x) { return static_cast(this)->fallback(x); } - T operator()(Comment* x) { return static_cast(this)->fallback(x); } - T operator()(If* x) { return static_cast(this)->fallback(x); } - T operator()(ForRule* x) { return static_cast(this)->fallback(x); } - T operator()(EachRule* x) { return static_cast(this)->fallback(x); } - T operator()(WhileRule* x) { return static_cast(this)->fallback(x); } - T operator()(Return* x) { return static_cast(this)->fallback(x); } - T operator()(Content* x) { return static_cast(this)->fallback(x); } - T operator()(ExtendRule* x) { return static_cast(this)->fallback(x); } - T operator()(Definition* x) { return static_cast(this)->fallback(x); } - T operator()(Mixin_Call* x) { return static_cast(this)->fallback(x); } - // expressions - T operator()(Null* x) { return static_cast(this)->fallback(x); } - T operator()(List* x) { return static_cast(this)->fallback(x); } - T operator()(Map* x) { return static_cast(this)->fallback(x); } - T operator()(Function* x) { return static_cast(this)->fallback(x); } - T operator()(Binary_Expression* x) { return static_cast(this)->fallback(x); } - T operator()(Unary_Expression* x) { return static_cast(this)->fallback(x); } - T operator()(Function_Call* x) { return static_cast(this)->fallback(x); } - T operator()(Custom_Warning* x) { return static_cast(this)->fallback(x); } - T operator()(Custom_Error* x) { return static_cast(this)->fallback(x); } - T operator()(Variable* x) { return static_cast(this)->fallback(x); } - T operator()(Number* x) { return static_cast(this)->fallback(x); } - T operator()(Color* x) { return static_cast(this)->fallback(x); } - T operator()(Color_RGBA* x) { return static_cast(this)->fallback(x); } - T operator()(Color_HSLA* x) { return static_cast(this)->fallback(x); } - T operator()(Boolean* x) { return static_cast(this)->fallback(x); } - T operator()(String_Schema* x) { return static_cast(this)->fallback(x); } - T operator()(String_Constant* x) { return static_cast(this)->fallback(x); } - T operator()(String_Quoted* x) { return static_cast(this)->fallback(x); } - T operator()(SupportsCondition* x) { return static_cast(this)->fallback(x); } - T operator()(SupportsOperation* x) { return static_cast(this)->fallback(x); } - T operator()(SupportsNegation* x) { return static_cast(this)->fallback(x); } - T operator()(SupportsDeclaration* x) { return static_cast(this)->fallback(x); } - T operator()(Supports_Interpolation* x) { return static_cast(this)->fallback(x); } - T operator()(Media_Query* x) { return static_cast(this)->fallback(x); } - T operator()(Media_Query_Expression* x) { return static_cast(this)->fallback(x); } - T operator()(At_Root_Query* x) { return static_cast(this)->fallback(x); } - T operator()(Parent_Reference* x) { return static_cast(this)->fallback(x); } - // parameters and arguments - T operator()(Parameter* x) { return static_cast(this)->fallback(x); } - T operator()(Parameters* x) { return static_cast(this)->fallback(x); } - T operator()(Argument* x) { return static_cast(this)->fallback(x); } - T operator()(Arguments* x) { return static_cast(this)->fallback(x); } - // selectors - T operator()(Selector_Schema* x) { return static_cast(this)->fallback(x); } - T operator()(PlaceholderSelector* x) { return static_cast(this)->fallback(x); } - T operator()(TypeSelector* x) { return static_cast(this)->fallback(x); } - T operator()(ClassSelector* x) { return static_cast(this)->fallback(x); } - T operator()(IDSelector* x) { return static_cast(this)->fallback(x); } - T operator()(AttributeSelector* x) { return static_cast(this)->fallback(x); } - T operator()(PseudoSelector* x) { return static_cast(this)->fallback(x); } - T operator()(SelectorComponent* x) { return static_cast(this)->fallback(x); } - T operator()(SelectorCombinator* x) { return static_cast(this)->fallback(x); } - T operator()(CompoundSelector* x) { return static_cast(this)->fallback(x); } - T operator()(ComplexSelector* x) { return static_cast(this)->fallback(x); } - T operator()(SelectorList* x) { return static_cast(this)->fallback(x); } - - // fallback with specific type U - // will be called if not overloaded - template inline T fallback(U x) - { - throw std::runtime_error( - std::string(typeid(*this).name()) + ": CRTP not implemented for " + typeid(x).name()); - } - - }; - -} - -#endif diff --git a/src/operators.cpp b/src/operators.cpp deleted file mode 100644 index f163f3dcff..0000000000 --- a/src/operators.cpp +++ /dev/null @@ -1,267 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include "operators.hpp" - -namespace Sass { - - namespace Operators { - - inline double add(double x, double y) { return x + y; } - inline double sub(double x, double y) { return x - y; } - inline double mul(double x, double y) { return x * y; } - inline double div(double x, double y) { return x / y; } // x/0 checked by caller - - inline double mod(double x, double y) { // x/0 checked by caller - if ((x > 0 && y < 0) || (x < 0 && y > 0)) { - double ret = std::fmod(x, y); - return ret ? ret + y : ret; - } else { - return std::fmod(x, y); - } - } - - typedef double (*bop)(double, double); - bop ops[Sass_OP::NUM_OPS] = { - 0, 0, // and, or - 0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte - add, sub, mul, div, mod - }; - - /* static function, has no pstate or traces */ - bool eq(ExpressionObj lhs, ExpressionObj rhs) - { - // operation is undefined if one is not a number - if (!lhs || !rhs) throw Exception::UndefinedOperation(lhs, rhs, Sass_OP::EQ); - // use compare operator from ast node - return *lhs == *rhs; - } - - /* static function, throws OperationError, has no pstate or traces */ - bool cmp(ExpressionObj lhs, ExpressionObj rhs, const Sass_OP op) - { - // can only compare numbers!? - Number_Obj l = Cast(lhs); - Number_Obj r = Cast(rhs); - // operation is undefined if one is not a number - if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); - // use compare operator from ast node - return *l < *r; - } - - /* static functions, throws OperationError, has no pstate or traces */ - bool lt(ExpressionObj lhs, ExpressionObj rhs) { return cmp(lhs, rhs, Sass_OP::LT); } - bool neq(ExpressionObj lhs, ExpressionObj rhs) { return eq(lhs, rhs) == false; } - bool gt(ExpressionObj lhs, ExpressionObj rhs) { return !cmp(lhs, rhs, Sass_OP::GT) && neq(lhs, rhs); } - bool lte(ExpressionObj lhs, ExpressionObj rhs) { return cmp(lhs, rhs, Sass_OP::LTE) || eq(lhs, rhs); } - bool gte(ExpressionObj lhs, ExpressionObj rhs) { return !cmp(lhs, rhs, Sass_OP::GTE) || eq(lhs, rhs); } - - /* colour math deprecation warning */ - void op_color_deprecation(enum Sass_OP op, sass::string lsh, sass::string rhs, const SourceSpan& pstate) - { - deprecated( - "The operation `" + lsh + " " + sass_op_to_name(op) + " " + rhs + - "` is deprecated and will be an error in future versions.", - "Consider using Sass's color functions instead.\n" - "https://sass-lang.com/documentation/Sass/Script/Functions.html#other_color_functions", - /*with_column=*/false, pstate); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value* op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) - { - enum Sass_OP op = operand.operand; - - String_Quoted* lqstr = Cast(&lhs); - String_Quoted* rqstr = Cast(&rhs); - - sass::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); - sass::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); - - if (Cast(&lhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); - if (Cast(&rhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); - - sass::string sep; - switch (op) { - case Sass_OP::ADD: sep = ""; break; - case Sass_OP::SUB: sep = "-"; break; - case Sass_OP::DIV: sep = "/"; break; - case Sass_OP::EQ: sep = "=="; break; - case Sass_OP::NEQ: sep = "!="; break; - case Sass_OP::LT: sep = "<"; break; - case Sass_OP::GT: sep = ">"; break; - case Sass_OP::LTE: sep = "<="; break; - case Sass_OP::GTE: sep = ">="; break; - default: - throw Exception::UndefinedOperation(&lhs, &rhs, op); - break; - } - - if (op == Sass_OP::ADD) { - // create string that might be quoted on output (but do not unquote what we pass) - return SASS_MEMORY_NEW(String_Quoted, pstate, lstr + rstr, 0, false, true); - } - - // add whitespace around operator - // but only if result is not delayed - if (sep != "" && delayed == false) { - if (operand.ws_before) sep = " " + sep; - if (operand.ws_after) sep = sep + " "; - } - - if (op == Sass_OP::SUB || op == Sass_OP::DIV) { - if (lqstr && lqstr->quote_mark()) lstr = quote(lstr); - if (rqstr && rqstr->quote_mark()) rstr = quote(rstr); - } - - return SASS_MEMORY_NEW(String_Constant, pstate, lstr + sep + rstr); - } - - /* ToDo: allow to operate also with hsla colors */ - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value* op_colors(enum Sass_OP op, const Color_RGBA& lhs, const Color_RGBA& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) - { - - if (lhs.a() != rhs.a()) { - throw Exception::AlphaChannelsNotEqual(&lhs, &rhs, op); - } - if ((op == Sass_OP::DIV || op == Sass_OP::MOD) && (!rhs.r() || !rhs.g() || !rhs.b())) { - throw Exception::ZeroDivisionError(lhs, rhs); - } - - op_color_deprecation(op, lhs.to_string(), rhs.to_string(), pstate); - - return SASS_MEMORY_NEW(Color_RGBA, - pstate, - ops[op](lhs.r(), rhs.r()), - ops[op](lhs.g(), rhs.g()), - ops[op](lhs.b(), rhs.b()), - lhs.a()); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value* op_numbers(enum Sass_OP op, const Number& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) - { - double lval = lhs.value(); - double rval = rhs.value(); - - if (op == Sass_OP::MOD && rval == 0) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "NaN"); - } - - if (op == Sass_OP::DIV && rval == 0) { - sass::string result(lval ? "Infinity" : "NaN"); - return SASS_MEMORY_NEW(String_Quoted, pstate, result); - } - - size_t l_n_units = lhs.numerators.size(); - size_t l_d_units = lhs.numerators.size(); - size_t r_n_units = rhs.denominators.size(); - size_t r_d_units = rhs.denominators.size(); - // optimize out the most common and simplest case - if (l_n_units == r_n_units && l_d_units == r_d_units) { - if (l_n_units + l_d_units <= 1 && r_n_units + r_d_units <= 1) { - if (lhs.numerators == rhs.numerators) { - if (lhs.denominators == rhs.denominators) { - Number* v = SASS_MEMORY_COPY(&lhs); - v->value(ops[op](lval, rval)); - return v; - } - } - } - } - - Number_Obj v = SASS_MEMORY_COPY(&lhs); - - if (lhs.is_unitless() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { - v->numerators = rhs.numerators; - v->denominators = rhs.denominators; - } - - if (op == Sass_OP::MUL) { - v->value(ops[op](lval, rval)); - v->numerators.insert(v->numerators.end(), - rhs.numerators.begin(), rhs.numerators.end() - ); - v->denominators.insert(v->denominators.end(), - rhs.denominators.begin(), rhs.denominators.end() - ); - v->reduce(); - } - else if (op == Sass_OP::DIV) { - v->value(ops[op](lval, rval)); - v->numerators.insert(v->numerators.end(), - rhs.denominators.begin(), rhs.denominators.end() - ); - v->denominators.insert(v->denominators.end(), - rhs.numerators.begin(), rhs.numerators.end() - ); - v->reduce(); - } - else { - Number ln(lhs), rn(rhs); - ln.reduce(); rn.reduce(); - double f(rn.convert_factor(ln)); - v->value(ops[op](lval, rn.value() * f)); - } - - v->pstate(pstate); - return v.detach(); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value* op_number_color(enum Sass_OP op, const Number& lhs, const Color_RGBA& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) - { - double lval = lhs.value(); - - switch (op) { - case Sass_OP::ADD: - case Sass_OP::MUL: { - op_color_deprecation(op, lhs.to_string(), rhs.to_string(opt), pstate); - return SASS_MEMORY_NEW(Color_RGBA, - pstate, - ops[op](lval, rhs.r()), - ops[op](lval, rhs.g()), - ops[op](lval, rhs.b()), - rhs.a()); - } - case Sass_OP::SUB: - case Sass_OP::DIV: { - sass::string color(rhs.to_string(opt)); - op_color_deprecation(op, lhs.to_string(), color, pstate); - return SASS_MEMORY_NEW(String_Quoted, - pstate, - lhs.to_string(opt) - + sass_op_separator(op) - + color); - } - default: break; - } - throw Exception::UndefinedOperation(&lhs, &rhs, op); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value* op_color_number(enum Sass_OP op, const Color_RGBA& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) - { - double rval = rhs.value(); - - if ((op == Sass_OP::DIV || op == Sass_OP::DIV) && rval == 0) { - // comparison of Fixnum with Float failed? - throw Exception::ZeroDivisionError(lhs, rhs); - } - - op_color_deprecation(op, lhs.to_string(), rhs.to_string(), pstate); - - return SASS_MEMORY_NEW(Color_RGBA, - pstate, - ops[op](lhs.r(), rval), - ops[op](lhs.g(), rval), - ops[op](lhs.b(), rval), - lhs.a()); - } - - } - -} diff --git a/src/operators.hpp b/src/operators.hpp deleted file mode 100644 index 42616320e5..0000000000 --- a/src/operators.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SASS_OPERATORS_H -#define SASS_OPERATORS_H - -#include "values.hpp" -#include "sass/values.h" - -namespace Sass { - - namespace Operators { - - // equality operator using AST Node operator== - bool eq(ExpressionObj, ExpressionObj); - bool neq(ExpressionObj, ExpressionObj); - // specific operators based on cmp and eq - bool lt(ExpressionObj, ExpressionObj); - bool gt(ExpressionObj, ExpressionObj); - bool lte(ExpressionObj, ExpressionObj); - bool gte(ExpressionObj, ExpressionObj); - // arithmetic for all the combinations that matter - Value* op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed = false); - Value* op_colors(enum Sass_OP, const Color_RGBA&, const Color_RGBA&, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed = false); - Value* op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed = false); - Value* op_number_color(enum Sass_OP, const Number&, const Color_RGBA&, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed = false); - Value* op_color_number(enum Sass_OP, const Color_RGBA&, const Number&, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed = false); - - }; - -} - -#endif diff --git a/src/ordered_map.hpp b/src/ordered_map.hpp index 6b70f80536..df3a9b6d31 100644 --- a/src/ordered_map.hpp +++ b/src/ordered_map.hpp @@ -1,111 +1,333 @@ #ifndef SASS_ORDERED_MAP_H #define SASS_ORDERED_MAP_H +#include +#include +#include "memory.hpp" + +// #include "../../hopscotch-map/include/tsl/hopscotch_map.h" +// #include "../../hopscotch-map/include/tsl/hopscotch_set.h" + namespace Sass { - // ########################################################################## - // Very simple and limited container for insert ordered hash map. - // Piggy-back implementation on std::unordered_map and sass::vector - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + // Very simple and limited container for insertion ordered hash map. + // Piggy-back implementation on std::unordered_map and std::vector. + ///////////////////////////////////////////////////////////////////////// + // In order to support assignable value references, we can only store the + // value in one container. We can't reference values from one container in + // another, since the pointer would be invalidated, once a container needs + // re-allocation. To fix this we need a soft reference. Therefore we only + // store the index into the list vector on the hash-map. This makes all + // access operations constant, but makes erasing of items, or insertion + // not at the end (which is not yet supported) in the worst case O(n), as + // we need to adjust the indexes on the hash-map after the modified item. + ///////////////////////////////////////////////////////////////////////// template< - class Key, - class T, - class Hash = std::hash, - class KeyEqual = std::equal_to, - class Allocator = std::allocator> + class TKey, + class TVal, + class Hash = std::hash, + class KeyEqual = std::equal_to, + class ListAllocator = Sass::Allocator>, + class MapAllocator = Sass::Allocator> > class ordered_map { - private: - - using map_type = typename std::unordered_map< Key, T, Hash, KeyEqual, Allocator>; - using map_iterator = typename std::unordered_map< Key, T, Hash, KeyEqual, Allocator>::iterator; - using map_const_iterator = typename std::unordered_map< Key, T, Hash, KeyEqual, Allocator>::const_iterator; - - // The main unordered map - map_type _map; - - // Keep insertion order - sass::vector _keys; - sass::vector _values; - - const KeyEqual _keyEqual; - - public: - - ordered_map() : - _keyEqual(KeyEqual()) - { - } - - ordered_map& operator= (const ordered_map& other) { - _map = other._map; - _keys = other._keys; - _values = other._values; - return *this; - } - - std::pair front() { - return std::make_pair( - _keys.front(), - _values.front() - ); - } - - bool empty() const { - return _keys.empty(); - } - - void insert(const Key& key, const T& val) { - if (!hasKey(key)) { - _values.push_back(val); - _keys.push_back(key); - } - _map[key] = val; - } - - bool hasKey(const Key& key) const { - return _map.find(key) != _map.end(); - } - - bool erase(const Key& key) { - _map.erase(key); - // find the position in the array - for (size_t i = 0; i < _keys.size(); i += 1) { - if (_keyEqual(key, _keys[i])) { - _keys.erase(_keys.begin() + i); - _values.erase(_values.begin() + i); - return true; + using map_type = typename std::unordered_map < + TKey, size_t, Hash, KeyEqual, MapAllocator >; + using pair_type = typename std::pair; + using list_type = typename std::vector; + + private: + + // The main unordered map for key access + map_type _map; + + // The insertion ordered list of kv-pairs + list_type _list; + + // Instance of equality functor + const KeyEqual _keyEqual; + + public: + + // Default constructor + ordered_map() : + _map(map_type()), + _list(list_type()), + _keyEqual(KeyEqual()) + {} + + // Explicit destructor for rule of three + // ~ordered_map() {} + + // The copy constructor for rule of three + ordered_map(const ordered_map* ptr) : + _map(ptr->_map), + _list(ptr->_list), + _keyEqual(ptr->_keyEqual) + {} + + // The copy constructor for rule of three + ordered_map(const ordered_map& other) : + _map(other._map), + _list(other._list), + _keyEqual(other._keyEqual) + {} + + // The move constructor for rule of three + ordered_map(ordered_map&& other) : + _map(std::move(other._map)), + _list(std::move(other._list)), + _keyEqual(other._keyEqual) + {} + + // The copy assignment operator for rule of three + ordered_map& operator= (const ordered_map& other) + { + _map = other._map; + _list = other._list; + // _keyEqual = other._keyEqual; + return *this; + } + + // The move assignment operator for rule of three + ordered_map& operator= (ordered_map&& other) + { + _map = std::move(other._map); + _list = std::move(other._list); + // _keyEqual = other._keyEqual; + return *this; + } + + ///////////////////////////////////////////////////////////////////////// + // Implement vector API partially (return pairs) + ///////////////////////////////////////////////////////////////////////// + + // Returns number of pairs (or keys). + // Normal maps report double the size. + size_t size() const + { + return _list.size(); + } + // EO size + + // Returns true if map is empty + bool empty() const + { + return _list.empty(); + } + // EO empty + + // Get kv_pair for last item. + // Throws out of boundary exception. + pair_type& front() + { + return _list.front(); + } + // EO front + + // Get kv_pair for last item. + // Throws out of boundary exception. + pair_type& back() + { + return _list.back(); + } + // EO back + + ///////////////////////////////////////////////////////////////////////// + // Implement unordered_map API partially + ///////////////////////////////////////////////////////////////////////// + + // Returns 1 if the key exists + size_t count(const TKey& key) const { + return _map.count(key); + } + // EO count(key) + + // Find key and return ordered list iterator + typename list_type::const_iterator find(const TKey& key) const { + typename map_type::const_iterator existing = _map.find(key); + if (existing == _map.end()) return _list.end(); + return _list.begin() + existing->second; + } + // EO find(key) const + + // Find key and return ordered list iterator + typename list_type::iterator find(const TKey& key) { + typename map_type::iterator existing = _map.find(key); + if (existing == _map.end()) return _list.end(); + return _list.begin() + existing->second; + } + // EO find(key) + + ///////////////////////////////////////////////////////////////////////// + // Implement mixed manipulation API + ///////////////////////////////////////////////////////////////////////// + + // Append a new key/value pair + void push_back(const pair_type& kv, bool overwrite = false) + { + typename map_type::iterator existing = _map.find(kv.first); + // Check if key is already existing + if (existing != _map.end()) { + if (overwrite) { + size_t idx = existing->second; + _list[idx].second = kv.second; + } + else { + throw std::logic_error("Key already exists"); + } + } + // Append new key and value + else { + _map[kv.first] = size(); + _list.emplace_back(kv); + } + } + // EO push_back(kv_pair) + + // Append a new key/value pair + void push_back(const TKey& key, const TVal& value, bool overwrite = false) + { + push_back(std::make_pair(key, value), overwrite); + } + // EO push_back(key, value) + + // Overwrite existing item or append it + void set(const TKey& key, const TVal& value) + { + push_back(key, value, true); + } + // EO set(key, value) + + // Remove item by key + bool erase(const TKey& key) { + // Remove from map + _map.erase(key); + // Find the position in the array + for (size_t i = 0; i < size(); i += 1) { + if (_keyEqual(key, _list[i].first)) { + _list.erase(_list.begin() + i); + // Adjust all indexes after the removal + for (size_t j = i; j < size(); j += 1) { + --_map[_list[j].first]; // reduce by one + } + return true; + } + } + return false; + } + // EO erase(key) + + // Remove item by index + bool erase(size_t idx) { + // Gracefully handle out of bound + if (idx < 0 || idx >= size()) return false; + // Remove key to index + _map.erase(_list[idx].first); + // Remove key / value pair + _list.erase(_list.begin() + idx); + // Adjust all indexes after the removal + for (size_t j = idx; j < size(); j += 1) { + --_map[_list[j].first]; // reduce by one + } + return true; + } + // EO erase(index) + + // Get item from map, if missing it will + // be created and default initialized. + TVal& operator[](const TKey& key) { + typename map_type::iterator existing = _map.find(key); + // Check if key is already existing + if (existing != _map.end()) { + return _list[existing->second].second; } + _map[key] = _list.size(); + // We must default initialize the value! + _list.emplace_back(std::make_pair(key, TVal())); + return _list.back().second; + } + // EO operator[](key) + + // Get item from list by index. + pair_type& operator[](size_t idx) { + return _list[idx]; } - return false; - } + // EO operator[](index) - const sass::vector& keys() const { return _keys; } - const sass::vector& values() const { return _values; } + // Get item from list by index. + // Throws out of boundary error. + pair_type& at(size_t idx) { + return _list.at(idx); + } + // EO at(index) + + ///////////////////////////////////////////////////////////////////////// + // Some additional stuff + ///////////////////////////////////////////////////////////////////////// - const T& get(const Key& key) { - if (hasKey(key)) { - return _map[key]; + // Reserve memory + void reserve(size_t size) + { + _map.reserve(size); + _list.reserve(size); } - throw std::runtime_error("Key does not exist"); - } + // EO reserve + + ///////////////////////////////////////////////////////////////////////// + // Some syntax sugar API + ///////////////////////////////////////////////////////////////////////// + + // Note that this creates a new array every time. + // Only call it if you really want to have a copy. + sass::vector keys() const { + sass::vector keys; + keys.reserve(size()); + for (const pair_type& kv : _list) { + keys.emplace_back(kv.first); + } + return keys; + } + // EO keys + + // Note that this creates a new array every time. + // Only call it if you really want to have a copy. + sass::vector values() const { + sass::vector values; + values.reserve(size()); + for (const pair_type& kv : _list) { + values.emplace_back(kv.second); + } + return values; + } + // EO values + + ///////////////////////////////////////////////////////////////////////// + // Create iterator aliases for ourself + ///////////////////////////////////////////////////////////////////////// + + using iterator = typename list_type::iterator; + using const_iterator = typename list_type::const_iterator; + using reverse_iterator = typename list_type::reverse_iterator; + using const_reverse_iterator = typename list_type::const_reverse_iterator; - using iterator = typename sass::vector::iterator; - using const_iterator = typename sass::vector::const_iterator; - using reverse_iterator = typename sass::vector::reverse_iterator; - using const_reverse_iterator = typename sass::vector::const_reverse_iterator; + ///////////////////////////////////////////////////////////////////////// + // Implement iterator functions + ///////////////////////////////////////////////////////////////////////// - typename sass::vector::iterator end() { return _keys.end(); } - typename sass::vector::iterator begin() { return _keys.begin(); } - typename sass::vector::reverse_iterator rend() { return _keys.rend(); } - typename sass::vector::reverse_iterator rbegin() { return _keys.rbegin(); } - typename sass::vector::const_iterator end() const { return _keys.end(); } - typename sass::vector::const_iterator begin() const { return _keys.begin(); } - typename sass::vector::const_reverse_iterator rend() const { return _keys.rend(); } - typename sass::vector::const_reverse_iterator rbegin() const { return _keys.rbegin(); } + iterator end() { return _list.end(); } + iterator begin() { return _list.begin(); } + reverse_iterator rend() { return _list.rend(); } + reverse_iterator rbegin() { return _list.rbegin(); } + const_iterator end() const { return _list.end(); } + const_iterator begin() const { return _list.begin(); } + const_reverse_iterator rend() const { return _list.rend(); } + const_reverse_iterator rbegin() const { return _list.rbegin(); } }; + // EO ordered_map } diff --git a/src/output.cpp b/src/output.cpp index 0748cd4643..290d0f2116 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -1,60 +1,65 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "ast.hpp" #include "output.hpp" -#include "util.hpp" + +#include "ast_css.hpp" +#include "ast_values.hpp" +#include "charcode.hpp" +#include "character.hpp" +#include "exceptions.hpp" namespace Sass { - Output::Output(Sass_Output_Options& opt) - : Inspect(Emitter(opt)), - charset(""), - top_nodes(0) - {} + // Import some namespaces + using namespace Charcode; + using namespace Character; - Output::~Output() { } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - void Output::fallback_impl(AST_Node* n) - { - return n->perform(this); - } + // Value constructor + Output::Output( + SassOutputOptionsCpp& opt, + bool srcmap_enabled) : + Cssize(opt, srcmap_enabled) + {} - void Output::operator()(Number* n) - { - // check for a valid unit here - // includes result for reporting - if (!n->is_valid_css_unit()) { - // should be handle in check_expression - throw Exception::InvalidValue({}, *n); - } - // use values to_string facility - sass::string res = n->to_string(opt); - // output the final token - append_token(res, n); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - void Output::operator()(Import* imp) + // local helper function + void trim_trailing_lines(sass::string& text) { - top_nodes.push_back(imp); + auto start = text.begin(); + auto lastlf = text.end(); + auto end = lastlf - 1; + while (end != start) { + if (Character::isNewline(*end)) { + lastlf = end; + end--; + } + else if (Character::isWhitespace(*end)) { + end--; + } + else { + break; + } + } + if (lastlf != text.end()) { + text = sass::string(start, lastlf) + " "; + } } + // EO string_trim_trailing_lines - void Output::operator()(Map* m) - { - // should be handle in check_expression - throw Exception::InvalidValue({}, *m); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - OutputBuffer Output::get_buffer(void) + OutputBuffer Output::getBuffer(void) { - - Emitter emitter(opt); - Inspect inspect(emitter); + // This needs saving + Inspect inspect(opt, wbuf.smap ? true : false); size_t size_nodes = top_nodes.size(); for (size_t i = 0; i < size_nodes; i++) { - top_nodes[i]->perform(&inspect); + top_nodes[i]->accept(&inspect); inspect.append_mandatory_linefeed(); } @@ -64,20 +69,19 @@ namespace Sass { // prepend buffer on top prepend_output(inspect.output()); // make sure we end with a linefeed - if (!ends_with(wbuf.buffer, opt.linefeed)) { + if (!StringUtils::endsWith(wbuf.buffer, opt.linefeed)) { // if the output is not completely empty if (!wbuf.buffer.empty()) append_string(opt.linefeed); } // search for unicode char - for(const char& chr : wbuf.buffer) { - // skip all ascii chars - // static cast to unsigned to handle `char` being signed / unsigned + for (const char& chr : wbuf.buffer) { + // skip all ASCII chars if (static_cast(chr) < 128) continue; // declare the charset - if (output_style() != COMPRESSED) + if (output_style() != SASS_STYLE_COMPRESSED) charset = "@charset \"UTF-8\";" - + sass::string(opt.linefeed); + + sass::string(opt.linefeed); else charset = "\xEF\xBB\xBF"; // abort search break; @@ -86,235 +90,86 @@ namespace Sass { // add charset as first line, before comments and imports if (!charset.empty()) prepend_string(charset); - return wbuf; + return std::move(wbuf); } + // EO getBuffer - void Output::operator()(Comment* c) - { - // if (indentation && txt == "/**/") return; - bool important = c->is_important(); - if (output_style() != COMPRESSED || important) { - if (buffer().size() == 0) { - top_nodes.push_back(c); - } else { - in_comment = true; - append_indentation(); - c->text()->perform(this); - in_comment = false; - if (indentation == 0) { - append_mandatory_linefeed(); - } else { - append_optional_linefeed(); - } - } - } - } - - void Output::operator()(StyleRule* r) - { - Block_Obj b = r->block(); - SelectorListObj s = r->selector(); - - if (!s || s->empty()) return; - - // Filter out rulesets that aren't printable (process its children though) - if (!Util::isPrintable(r, output_style())) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - const Statement_Obj& stm = b->get(i); - if (Cast(stm)) { - if (!Cast(stm)) { - stm->perform(this); - } - } - } - return; - } - - if (output_style() == NESTED) { - indentation += r->tabs(); - } - if (opt.source_comments) { - sass::ostream ss; - append_indentation(); - sass::string path(File::abs2rel(r->pstate().getPath())); - ss << "/* line " << r->pstate().getLine() << ", " << path << " */"; - append_string(ss.str()); - append_optional_linefeed(); - } - scheduled_crutch = s; - if (s) s->perform(this); - append_scope_opener(b); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->get(i); - bool bPrintExpression = true; - // Check print conditions - if (Declaration* dec = Cast(stm)) { - if (const String_Constant* valConst = Cast(dec->value())) { - const sass::string& val = valConst->value(); - if (const String_Quoted* qstr = Cast(valConst)) { - if (!qstr->quote_mark() && val.empty()) { - bPrintExpression = false; - } - } - } - else if (List* list = Cast(dec->value())) { - bool all_invisible = true; - for (size_t list_i = 0, list_L = list->length(); list_i < list_L; ++list_i) { - Expression* item = list->get(list_i); - if (!item->is_invisible()) all_invisible = false; - } - if (all_invisible && !list->is_bracketed()) bPrintExpression = false; - } - } - // Print if OK - if (bPrintExpression) { - stm->perform(this); - } - } - if (output_style() == NESTED) indentation -= r->tabs(); - append_scope_closer(b); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - } - void Output::operator()(Keyframe_Rule* r) + void Output::visitCssImport(CssImport* imp) { - Block_Obj b = r->block(); - Selector_Obj v = r->name(); - - if (!v.isNull()) { - v->perform(this); + if (imp->outOfOrder()) { + top_nodes.emplace_back(imp); } - - if (!b) { - append_colon_separator(); - return; - } - - append_scope_opener(); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->get(i); - stm->perform(this); - if (i < L - 1) append_special_linefeed(); + else { + Cssize::visitCssImport(imp); } - append_scope_closer(); } + // EO visitCssImport - void Output::operator()(SupportsRule* f) + void Output::visitCssComment(CssComment* c) { - if (f->is_invisible()) return; - - SupportsConditionObj c = f->condition(); - Block_Obj b = f->block(); - - // Filter out feature blocks that aren't printable (process its children though) - if (!Util::isPrintable(f, output_style())) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->get(i); - if (Cast(stm)) { - stm->perform(this); + bool important = c->isPreserved(); + if (output_style() == SASS_STYLE_COMPRESSED || output_style() == SASS_STYLE_COMPACT) { + if (!important) return; + } + if (output_style() != SASS_STYLE_COMPRESSED || important) { + if (wbuf.buffer.size() == 0) { + top_nodes.emplace_back(c); + } + else { + append_indentation(); + append_string(c->text()); + if (indentation == 0) { + append_mandatory_linefeed(); + } + else { + append_optional_linefeed(); } } - return; - } - - if (output_style() == NESTED) indentation += f->tabs(); - append_indentation(); - append_token("@supports", f); - append_mandatory_space(); - c->perform(this); - append_scope_opener(); - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->get(i); - stm->perform(this); - if (i < L - 1) append_special_linefeed(); } - - if (output_style() == NESTED) indentation -= f->tabs(); - - append_scope_closer(); - } + // EO visitCssComment - void Output::operator()(CssMediaRule* rule) + void Output::visitCssMediaRule(CssMediaRule* rule) { // Avoid null pointer exception if (rule == nullptr) return; - // Skip empty/invisible rule - if (rule->isInvisible()) return; - // Avoid null pointer exception - if (rule->block() == nullptr) return; - // Skip empty/invisible rule - if (rule->block()->isInvisible()) return; - // Skip if block is empty/invisible - if (Util::isPrintable(rule, output_style())) { - // Let inspect do its magic - Inspect::operator()(rule); - } + // Skip empty or invisible rule + if (rule->isInvisibleCss()) return; + // Let inspect do its magic + Cssize::visitCssMediaRule(rule); } + // EO visitCssMediaRule - void Output::operator()(AtRule* a) + void Output::visitMap(Map* m) { - sass::string kwd = a->keyword(); - Selector_Obj s = a->selector(); - ExpressionObj v = a->value(); - Block_Obj b = a->block(); - - append_indentation(); - append_token(kwd, a); - if (s) { - append_mandatory_space(); - in_wrapped = true; - s->perform(this); - in_wrapped = false; - } - if (v) { - append_mandatory_space(); - // ruby sass bug? should use options? - append_token(v->to_string(/* opt */), v); - } - if (!b) { - append_delimiter(); - return; - } - - if (b->is_invisible() || b->length() == 0) { - append_optional_space(); - return append_string("{}"); - } - - append_scope_opener(); - - bool format = kwd != "@font-face";; - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->get(i); - if (stm) stm->perform(this); - if (i < L - 1 && format) append_special_linefeed(); - } - - append_scope_closer(); + // should be handle in check_expression + throw Exception::InvalidCssValue({}, *m); } + // EO visitMap - void Output::operator()(String_Quoted* s) + void Output::visitString(String* s) { - if (s->quote_mark()) { - append_token(quote(s->value(), s->quote_mark()), s); - } else if (!in_comment) { - append_token(string_to_output(s->value()), s); - } else { - append_token(s->value(), s); + if (!in_custom_property) { + if (s->hasQuotes()) { + renderQuotedString(s->value()); + } + else { + renderUnquotedString(s->value()); + } } - } - - void Output::operator()(String_Constant* s) - { - sass::string value(s->value()); - if (!in_comment && !in_custom_property) { - append_token(string_to_output(value), s); - } else { + else { + sass::string value(s->value()); + trim_trailing_lines(value); append_token(value, s); } } + // EO visitString + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/output.hpp b/src/output.hpp index b05ff3c807..d57bcc6cad 100644 --- a/src/output.hpp +++ b/src/output.hpp @@ -1,44 +1,55 @@ -#ifndef SASS_OUTPUT_H -#define SASS_OUTPUT_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_OUTPUT_HPP +#define SASS_OUTPUT_HPP -#include -#include +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include "util.hpp" -#include "inspect.hpp" -#include "operation.hpp" +#include "cssize.hpp" namespace Sass { - class Context; - class Output : public Inspect { + class Output : public Cssize + { protected: - using Inspect::operator(); - public: - Output(Sass_Output_Options& opt); - virtual ~Output(); - - protected: sass::string charset; - sass::vector top_nodes; + CssNodeVector top_nodes; public: - OutputBuffer get_buffer(void); - - virtual void operator()(Map*); - virtual void operator()(StyleRule*); - virtual void operator()(SupportsRule*); - virtual void operator()(CssMediaRule*); - virtual void operator()(AtRule*); - virtual void operator()(Keyframe_Rule*); - virtual void operator()(Import*); - virtual void operator()(Comment*); - virtual void operator()(Number*); - virtual void operator()(String_Quoted*); - virtual void operator()(String_Constant*); - - void fallback_impl(AST_Node* n); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + Output( + SassOutputOptionsCpp& opt, + bool srcmap_enabled); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Return buffer to compiler + OutputBuffer getBuffer(void); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + virtual void visitCssImport(CssImport*) override; + virtual void visitCssComment(CssComment*) override; + virtual void visitCssMediaRule(CssMediaRule*) override; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + virtual void visitMap(Map* value) override; + virtual void visitString(String* value) override; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// }; diff --git a/src/parser.cpp b/src/parser.cpp index 201a536ef6..ccd922f9d6 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1,2965 +1,662 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "parser.hpp" -#include "color_maps.hpp" -#include "util_string.hpp" - -// Notes about delayed: some ast nodes can have delayed evaluation so -// they can preserve their original semantics if needed. This is most -// prominently exhibited by the division operation, since it is not -// only a valid operation, but also a valid css statement (i.e. for -// fonts, as in `16px/24px`). When parsing lists and expression we -// unwrap single items from lists and other operations. A nested list -// must not be delayed, only the items of the first level sometimes -// are delayed (as with argument lists). To achieve this we need to -// pass status to the list parser, so this can be set correctly. -// Another case with delayed values are colors. In compressed mode -// only processed values get compressed (other are left as written). +#include "compiler.hpp" +#include "charcode.hpp" +#include "character.hpp" +#include "exceptions.hpp" +#include "ast_statements.hpp" namespace Sass { - using namespace Constants; - using namespace Prelexer; - - - Parser::Parser(SourceData* source, Context& ctx, Backtraces traces, bool allow_parent) : - SourceSpan(source), - ctx(ctx), - source(source), - begin(source->begin()), - position(source->begin()), - end(source->end()), - before_token(0, 0), - after_token(0, 0), - pstate(source->getSourceSpan()), - traces(traces), - indentation(0), - nestings(0), - allow_parent(allow_parent) - { - Block_Obj root = SASS_MEMORY_NEW(Block, pstate); - stack.push_back(Scope::Root); - block_stack.push_back(root); - root->is_root(true); - } - void Parser::advanceToNextToken() { - lex < css_comments >(false); - // advance to position - pstate.position += pstate.offset; - pstate.offset.column = 0; - pstate.offset.line = 0; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Constructor + Parser::Parser( + Compiler& context, + SourceDataObj source) : + context(context), + scanner(context, source), + varStack(context.varStack), + lastSilentComment() + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // ToDo: implement without the try clause + // ToDo: measure if this brings any speed? + bool Parser::isIdentifier(sass::string text) + { + try { + auto src = SASS_MEMORY_NEW(SourceString, + "sass:://identifier", std::move(text)); + Parser parser(context, src); + sass::string id(parser.readIdentifier()); + return parser.scanner.isDone(); + } + catch (Exception::ParserException&) { + return false; } - - SelectorListObj Parser::parse_selector(SourceData* source, Context& ctx, Backtraces traces, bool allow_parent) - { - Parser p(source, ctx, traces, allow_parent); - // ToDo: remap the source-map entries somehow - return p.parseSelectorList(false); } - bool Parser::peek_newline(const char* start) + // Consumes and ignores a comment if possible. + // Returns whether the comment was consumed. + bool Parser::scanComment() { - return peek_linefeed(start ? start : position) - && ! peek_css>(start); + if (scanner.peekChar() != $slash) return false; + auto next = scanner.peekChar(1); + if (next == $slash) { + lastSilentComment = readSilentComment(); + return true; + } + else if (next == $asterisk) { + loudComment(); + return true; + } + else { + return false; + } } - /* main entry point to parse root block */ - Block_Obj Parser::parse() + // Consumes and ignores a silent (Sass-style) comment. + SilentComment* Parser::readSilentComment() { - - // consume unicode BOM - read_bom(); - - // scan the input to find invalid utf8 sequences - const char* it = utf8::find_invalid(position, end); - - // report invalid utf8 - if (it != end) { - pstate.position += Offset::init(position, it); - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidSass(pstate, traces, "Invalid UTF-8 sequence"); - } - - // create a block AST node to hold children - Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true); - - // check seems a bit esoteric but works - if (ctx.resources.size() == 1) { - // apply headers only on very first include - ctx.apply_custom_headers(root, getPath(), pstate); + scanner.expect("//"); + while (!scanner.isDone() && !isNewline(scanner.peekChar())) { + scanner.readChar(); } + return nullptr; + } - // parse children nodes - block_stack.push_back(root); - parse_block_nodes(true); - block_stack.pop_back(); - - // update final position - root->update_pstate(pstate); + // Consumes and ignores a loud (CSS-style) comment. + void Parser::loudComment() + { + scanner.expect("/*"); + while (true) { + auto next = scanner.readChar(); + if (next != $asterisk) continue; - if (position != end) { - css_error("Invalid CSS", " after ", ": expected selector or at-rule, was "); + do { + next = scanner.readChar(); + } while (next == $asterisk); + if (next == $slash) break; } - - return root; } - - // convenience function for block parsing - // will create a new block ad-hoc for you - // this is the base block parsing function - Block_Obj Parser::parse_css_block(bool is_root) + // Consumes a plain CSS identifier. If [unit] is `true`, this + // doesn't parse a `-` followed by a digit. This ensures that + // `1px-2px` parses as subtraction rather than the unit `px-2px`. + sass::string Parser::readIdentifier(bool unit) { - // parse comments before block - // lex < optional_css_comments >(); + // NOTE: this logic is largely duplicated in StylesheetParser._interpolatedIdentifier + // and isIdentifier in utils.dart. Most changes here should be mirrored there. - // lex mandatory opener or error out - if (!lex_css < exactly<'{'> >()) { - css_error("Invalid CSS", " after ", ": expected \"{\", was "); + Offset start(scanner.offset); + StringBuffer text; + if (scanner.scanChar($dash)) { + text.write($dash); + if (scanner.scanChar($dash)) { + text.writeCharCode($dash); + consumeidentifierBody(text, unit); + return text.buffer; + } } - // create new block and push to the selector stack - Block_Obj block = SASS_MEMORY_NEW(Block, pstate, 0, is_root); - block_stack.push_back(block); - if (!parse_block_nodes(is_root)) css_error("Invalid CSS", " after ", ": expected \"}\", was "); - - if (!lex_css < exactly<'}'> >()) { - css_error("Invalid CSS", " after ", ": expected \"}\", was "); + auto first = scanner.peekChar(); + if (first == $nul) { + error("Expected identifier.", + scanner.rawSpanFrom(start)); + } + else if (isNameStart(first)) { + text.write(scanner.readChar()); + } + else if (first == $backslash) { + escape(text, /*identifierStart*/true); + } + else { + error("Expected identifier.", + scanner.rawSpanFrom(start)); } - // update for end position - // this seems to be done somewhere else - // but that fixed selector schema issue - // block->update_pstate(pstate); - - // parse comments after block - // lex < optional_css_comments >(); - - block_stack.pop_back(); + consumeidentifierBody(text, unit); - return block; - } + return std::move(text.buffer); - // convenience function for block parsing - // will create a new block ad-hoc for you - // also updates the `in_at_root` flag - Block_Obj Parser::parse_block(bool is_root) - { - return parse_css_block(is_root); } - // the main block parsing function - // parses stuff between `{` and `}` - bool Parser::parse_block_nodes(bool is_root) + // Consumes a chunk of a plain CSS identifier after the name start. + sass::string Parser::identifierBody() { - - // loop until end of string - while (position < end) { - - // we should be able to refactor this - parse_block_comments(); - lex < css_whitespace >(); - - if (lex < exactly<';'> >()) continue; - if (peek < end_of_file >()) return true; - if (peek < exactly<'}'> >()) return true; - - if (parse_block_node(is_root)) continue; - - parse_block_comments(); - - if (lex_css < exactly<';'> >()) continue; - if (peek_css < end_of_file >()) return true; - if (peek_css < exactly<'}'> >()) return true; - - // illegal sass - return false; + StringBuffer text; + consumeidentifierBody(text); + if (text.empty()) { + error( + "Expected identifier body.", + scanner.rawSpan()); } - // return success - return true; + return text.buffer; } - // parser for a single node in a block - // semicolons must be lexed beforehand - bool Parser::parse_block_node(bool is_root) { - - Block_Obj block = block_stack.back(); - - parse_block_comments(); - - // throw away white-space - // includes line comments - lex < css_whitespace >(); - - Lookahead lookahead_result; - - // also parse block comments - - // first parse everything that is allowed in functions - if (lex < variable >(true)) { block->append(parse_assignment()); } - else if (lex < kwd_err >(true)) { block->append(parse_error()); } - else if (lex < kwd_dbg >(true)) { block->append(parse_debug()); } - else if (lex < kwd_warn >(true)) { block->append(parse_warning()); } - else if (lex < kwd_if_directive >(true)) { block->append(parse_if_directive()); } - else if (lex < kwd_for_directive >(true)) { block->append(parse_for_directive()); } - else if (lex < kwd_each_directive >(true)) { block->append(parse_each_directive()); } - else if (lex < kwd_while_directive >(true)) { block->append(parse_while_directive()); } - else if (lex < kwd_return_directive >(true)) { block->append(parse_return_directive()); } - - // parse imports to process later - else if (lex < kwd_import >(true)) { - Scope parent = stack.empty() ? Scope::Rules : stack.back(); - if (parent != Scope::Function && parent != Scope::Root && parent != Scope::Rules && parent != Scope::Media) { - if (! peek_css< uri_prefix >(position)) { // this seems to go in ruby sass 3.4.20 - error("Import directives may not be used within control directives or mixins."); - } + // Like [consumeidentifierBody], but parses the body into the [text] buffer. + void Parser::consumeidentifierBody(StringBuffer& text, bool unit) + { + while (true) { + uint8_t next = scanner.peekChar(); + if (next == $nul) { + break; } - // this puts the parsed doc into sheets - // import stub will fetch this in expand - Import_Obj imp = parse_import(); - // if it is a url, we only add the statement - if (!imp->urls().empty()) block->append(imp); - // process all resources now (add Import_Stub nodes) - for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { - block->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); + else if (unit && next == $dash) { + // Disallow `-` followed by a dot or a digit in units. + uint8_t second = scanner.peekChar(1); + if (second != $nul && (second == $dot || isDigit(second))) break; + text.write(scanner.readChar()); } - } - - else if (lex < kwd_extend >(true)) { - Lookahead lookahead = lookahead_for_include(position); - if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was "); - SelectorListObj target; - if (!lookahead.has_interpolants) { - LOCAL_FLAG(allow_parent, false); - auto selector = parseSelectorList(true); - auto extender = SASS_MEMORY_NEW(ExtendRule, pstate, selector); - extender->isOptional(selector && selector->is_optional()); - block->append(extender); + else if (isName(next)) { + text.write(scanner.readChar()); + } + else if (next == $backslash) { + escape(text); } else { - LOCAL_FLAG(allow_parent, false); - auto selector = parse_selector_schema(lookahead.found, true); - auto extender = SASS_MEMORY_NEW(ExtendRule, pstate, selector); - // A schema is not optional yet, check once it is evaluated - // extender->isOptional(selector && selector->is_optional()); - block->append(extender); + break; } - - } - - // selector may contain interpolations which need delayed evaluation - else if ( - !(lookahead_result = lookahead_for_selector(position)).error && - !lookahead_result.is_custom_property - ) - { - block->append(parse_ruleset(lookahead_result)); } + } - // parse multiple specific keyword directives - else if (lex < kwd_media >(true)) { block->append(parseMediaRule()); } - else if (lex < kwd_at_root >(true)) { block->append(parse_at_root_block()); } - else if (lex < kwd_include_directive >(true)) { block->append(parse_include_directive()); } - else if (lex < kwd_content_directive >(true)) { block->append(parse_content_directive()); } - else if (lex < kwd_supports_directive >(true)) { block->append(parse_supports_directive()); } - else if (lex < kwd_mixin >(true)) { block->append(parse_definition(Definition::MIXIN)); } - else if (lex < kwd_function >(true)) { block->append(parse_definition(Definition::FUNCTION)); } - - // ignore the @charset directive for now - else if (lex< kwd_charset_directive >(true)) { parse_charset_directive(); } - - else if (lex < exactly < else_kwd >>(true)) { error("Invalid CSS: @else must come after @if"); } - - // generic at keyword (keep last) - else if (lex< at_keyword >(true)) { block->append(parse_directive()); } + // Consumes a plain CSS string. This returns the parsed contents of the + // string—that is, it doesn't include quotes and its escapes are resolved. + sass::string Parser::string() + { + // NOTE: this logic is largely duplicated in ScssParser._interpolatedString. + // Most changes here should be mirrored there. - else if (is_root && stack.back() != Scope::AtRoot /* && block->is_root() */) { - lex< css_whitespace >(); - if (position >= end) return true; - css_error("Invalid CSS", " after ", ": expected 1 selector or at-rule, was "); - } - // parse a declaration - else - { - // ToDo: how does it handle parse errors? - // maybe we are expected to parse something? - Declaration_Obj decl = parse_declaration(); - decl->tabs(indentation); - block->append(decl); - // maybe we have a "sub-block" - if (peek< exactly<'{'> >()) { - if (decl->is_indented()) ++ indentation; - // parse a propset that rides on the declaration's property - stack.push_back(Scope::Properties); - decl->block(parse_block()); - stack.pop_back(); - if (decl->is_indented()) -- indentation; - } + uint8_t quote = scanner.readChar(); + if (quote != $single_quote && quote != $double_quote) { + error("Expected string.", + scanner.rawSpan()); + /*, + position: quote == null ? scanner.position : scanner.position - 1*/ } - // something matched - return true; - } - // EO parse_block_nodes - // parse imports inside the - Import_Obj Parser::parse_import() - { - Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); - sass::vector> to_import; - bool first = true; - do { - while (lex< block_comment >()); - if (lex< quoted_string >()) { - to_import.push_back(std::pair(sass::string(lexed), {})); + StringBuffer buffer; + while (true) { + uint8_t next = scanner.peekChar(); + if (next == quote) { + scanner.readChar(); + break; } - else if (lex< uri_prefix >()) { - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, sass::string("url"), args); - - if (lex< quoted_string >()) { - ExpressionObj quoted_url = parse_string(); - args->append(SASS_MEMORY_NEW(Argument, quoted_url->pstate(), quoted_url)); - } - else if (String_Obj string_url = parse_url_function_argument()) { - args->append(SASS_MEMORY_NEW(Argument, string_url->pstate(), string_url)); - } - else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(position)) { - ExpressionObj braced_url = parse_list(); // parse_interpolated_chunk(lexed); - args->append(SASS_MEMORY_NEW(Argument, braced_url->pstate(), braced_url)); + else if (next == $nul || isNewline(next)) { + sass::sstream strm; + strm << "Expected " << quote << "."; + error(strm.str(), + scanner.rawSpan()); + } + else if (next == $backslash) { + if (isNewline(scanner.peekChar(1))) { + scanner.readChar(); + scanner.readChar(); } else { - error("malformed URL"); + buffer.writeCharCode(escapeCharacter()); } - if (!lex< exactly<')'> >()) error("URI is missing ')'"); - to_import.push_back(std::pair("", result)); } else { - if (first) error("@import directive requires a url or quoted path"); - else error("expecting another url or quoted path in @import list"); - } - first = false; - } while (lex_css< exactly<','> >()); - - if (!peek_css< alternatives< exactly<';'>, exactly<'}'>, end_of_file > >()) { - List_Obj import_queries = parse_media_queries(); - imp->import_queries(import_queries); - } - - for(auto location : to_import) { - if (location.second) { - imp->urls().push_back(location.second); - } - // check if custom importers want to take over the handling - else if (!ctx.call_importers(unquote(location.first), getPath(), pstate, imp)) { - // nobody wants it, so we do our import - ctx.import_url(imp, location.first, getPath()); + buffer.write(scanner.readChar()); } } - return imp; - } - - Definition_Obj Parser::parse_definition(Definition::Type which_type) - { - sass::string which_str(lexed); - if (!lex< identifier >()) error("invalid name in " + which_str + " definition"); - sass::string name(Util::normalize_underscores(lexed)); - if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not")) - { error("Invalid function name \"" + name + "\"."); } - SourceSpan source_position_of_def = pstate; - Parameters_Obj params = parse_parameters(); - if (which_type == Definition::MIXIN) stack.push_back(Scope::Mixin); - else stack.push_back(Scope::Function); - Block_Obj body = parse_block(); - stack.pop_back(); - return SASS_MEMORY_NEW(Definition, source_position_of_def, name, params, body, which_type); - } - - Parameters_Obj Parser::parse_parameters() - { - Parameters_Obj params = SASS_MEMORY_NEW(Parameters, pstate); - if (lex_css< exactly<'('> >()) { - // if there's anything there at all - if (!peek_css< exactly<')'> >()) { - do { - if (peek< exactly<')'> >()) break; - params->append(parse_parameter()); - } while (lex_css< exactly<','> >()); - } - if (!lex_css< exactly<')'> >()) { - css_error("Invalid CSS", " after ", ": expected \")\", was "); - } - } - return params; + return buffer.buffer; } - Parameter_Obj Parser::parse_parameter() + // Consumes and returns a natural number. + // That is, a non - negative integer. + // Doesn't support scientific notation. + double Parser::naturalNumber() { - if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { - css_error("Invalid CSS", " after ", ": expected variable (e.g. $foo), was "); - } - while (lex< alternatives < spaces, block_comment > >()); - lex < variable >(); - sass::string name(Util::normalize_underscores(lexed)); - SourceSpan pos = pstate; - ExpressionObj val; - 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(); - } - else if (lex< exactly< ellipsis > >()) { - is_rest = true; + if (!isDigit(scanner.peekChar())) { + error("Expected digit.", + scanner.rawSpan()); } - return SASS_MEMORY_NEW(Parameter, pos, name, val, is_rest); - } - - Arguments_Obj Parser::parse_arguments() - { - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - if (lex_css< exactly<'('> >()) { - // if there's anything there at all - if (!peek_css< exactly<')'> >()) { - do { - if (peek< exactly<')'> >()) break; - args->append(parse_argument()); - } while (lex_css< exactly<','> >()); - } - if (!lex_css< exactly<')'> >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } + uint8_t first = scanner.readChar(); + double number = asDecimal(first); + while (isDigit(scanner.peekChar())) { + number = asDecimal(scanner.readChar()) + + number * 10; } - return args; + return number; } + // EO naturalNumber - Argument_Obj Parser::parse_argument() + // Consumes tokens until it reaches a top-level `";"`, `")"`, `"]"`, + // or `"}"` and returns their contents as a string. If [allowEmpty] + // is `false` (the default), this requires at least one token. + sass::string Parser::declarationValue(bool allowEmpty) { - if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { - css_error("Invalid CSS", " after ", ": expected \")\", was "); - } - if (peek_css< sequence < exactly< hash_lbrace >, exactly< rbrace > > >()) { - position += 2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } + // NOTE: this logic is largely duplicated in + // StylesheetParser.parseInterpolatedDeclarationValue. + // Most changes here should be mirrored there. - Argument_Obj arg; - if (peek_css< sequence < variable, optional_css_comments, exactly<':'> > >()) { - lex_css< variable >(); - sass::string name(Util::normalize_underscores(lexed)); - SourceSpan p = pstate; - lex_css< exactly<':'> >(); - ExpressionObj val = parse_space_list(); - arg = SASS_MEMORY_NEW(Argument, p, val, name); - } - else { - bool is_arglist = false; - bool is_keyword = false; - ExpressionObj val = parse_space_list(); - List* l = Cast(val); - if (lex_css< exactly< ellipsis > >()) { - if (val->concrete_type() == Expression::MAP || ( - (l != NULL && l->separator() == SASS_HASH) - )) is_keyword = true; - else is_arglist = true; - } - arg = SASS_MEMORY_NEW(Argument, pstate, val, "", is_arglist, is_keyword); - } - return arg; - } + sass::string url; + StringBuffer buffer; + bool wroteNewline = false; + sass::vector brackets; - Assignment_Obj Parser::parse_assignment() - { - sass::string name(Util::normalize_underscores(lexed)); - SourceSpan var_source_position = pstate; - if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement"); - if (peek_css< alternatives < exactly<';'>, end_of_file > >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - ExpressionObj val; - Lookahead lookahead = lookahead_for_value(position); - if (lookahead.has_interpolants && lookahead.found) { - val = parse_value_schema(lookahead.found); - } else { - val = parse_list(); - } - bool is_default = false; - bool is_global = false; - while (peek< alternatives < default_flag, global_flag > >()) { - if (lex< default_flag >()) is_default = true; - else if (lex< global_flag >()) is_global = true; - } - return SASS_MEMORY_NEW(Assignment, var_source_position, name, val, is_default, is_global); - } + while (true) { + uint8_t next = scanner.peekChar(); + switch (next) { + case $backslash: + escape(buffer, true); + wroteNewline = false; + break; - // a ruleset connects a selector and a block - StyleRuleObj Parser::parse_ruleset(Lookahead lookahead) - { - NESTING_GUARD(nestings); - // inherit is_root from parent block - Block_Obj parent = block_stack.back(); - bool is_root = parent && parent->is_root(); - // make sure to move up the the last position - lex < optional_css_whitespace >(false, true); - // create the connector object (add parts later) - StyleRuleObj ruleset = SASS_MEMORY_NEW(StyleRule, pstate); - // parse selector static or as schema to be evaluated later - if (lookahead.parsable) { - ruleset->selector(parseSelectorList(false)); - } - else { - SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate); - auto sc = parse_selector_schema(lookahead.position, false); - ruleset->schema(sc); - ruleset->selector(list); - } - // then parse the inner block - stack.push_back(Scope::Rules); - ruleset->block(parse_block()); - stack.pop_back(); - // update for end position - ruleset->update_pstate(pstate); - ruleset->block()->update_pstate(pstate); - // need this info for coherence checks - ruleset->is_root(is_root); - // return AST Node - return ruleset; - } + case $double_quote: + case $single_quote: + buffer.write(rawText(&Parser::string)); + wroteNewline = false; + break; - // parse a selector schema that will be evaluated in the eval stage - // uses a string schema internally to do the actual schema handling - // in the eval stage we will be re-parse it into an actual selector - Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector, bool chroot) - { - NESTING_GUARD(nestings); - // move up to the start - lex< optional_spaces >(); - const char* i = position; - // selector schema re-uses string schema implementation - String_Schema* schema = SASS_MEMORY_NEW(String_Schema, pstate); - // the selector schema is pretty much just a wrapper for the string schema - Selector_Schema_Obj selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema); - selector_schema->connect_parent(chroot == false); - - // process until end - while (i < end_of_selector) { - // try to parse multiple interpolants - if (const char* p = find_first_in_interval< exactly, block_comment >(i, end_of_selector)) { - // accumulate the preceding segment if the position has advanced - if (i < p) { - sass::string parsed(i, p); - String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); - pstate.position += Offset(parsed); - str->update_pstate(pstate); - schema->append(str); + case $slash: + if (scanner.peekChar(1) == $asterisk) { + buffer.write(rawText(&Parser::loudComment)); + } + else { + buffer.write(scanner.readChar()); } + wroteNewline = false; + break; - // skip over all nested inner interpolations up to our own delimiter - const char* j = skip_over_scopes< exactly, exactly >(p + 2, end_of_selector); - // check if the interpolation never ends of only contains white-space (error out) - if (!j || peek < sequence < optional_spaces, exactly > >(p+2)) { - position = p+2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + case $space: + case $tab: + if (wroteNewline || !isWhitespace(scanner.peekChar(1))) { + buffer.write($space); } - // pass inner expression to the parser to resolve nested interpolations - LocalOption partEnd(end, j); - LocalOption partBeg(position, p + 2); - ExpressionObj interpolant = parse_list(); - // set status on the list expression - interpolant->is_interpolant(true); - // schema->has_interpolants(true); - // add to the string schema - schema->append(interpolant); - // advance parser state - pstate.position.add(p+2, j); - // advance position - i = j; - } - // no more interpolants have been found - // add the last segment if there is one - else { - // make sure to add the last bits of the string up to the end (if any) - if (i < end_of_selector) { - sass::string parsed(i, end_of_selector); - String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); - pstate.position += Offset(parsed); - str->update_pstate(pstate); - i = end_of_selector; - schema->append(str); + scanner.readChar(); + break; + + case $lf: + case $cr: + case $ff: + if (!isNewline(scanner.peekChar(-1))) { + buffer.write("\n"); } - // exit loop - } - } - // EO until eos + scanner.readChar(); + wroteNewline = true; + break; - // update position - position = i; + case $lparen: + case $lbrace: + case $lbracket: + buffer.write(next); + brackets.emplace_back(opposite(scanner.readChar())); + wroteNewline = false; + break; - // update for end position - selector_schema->update_pstate(pstate); - schema->update_pstate(pstate); + case $rparen: + case $rbrace: + case $rbracket: + if (brackets.empty()) goto outOfLoop; + buffer.write(next); + scanner.expectChar(brackets.back()); + wroteNewline = false; + brackets.pop_back(); + break; - after_token = before_token = pstate.position; + case $semicolon: + if (brackets.empty()) goto outOfLoop; + buffer.write(scanner.readChar()); + break; - // return parsed result - return selector_schema.detach(); - } - // EO parse_selector_schema + case $u: + case $U: + url = tryUrl() ; + if (!url.empty()) { + buffer.write(url); + } + else { + buffer.write(scanner.readChar()); + } + wroteNewline = false; + break; - void Parser::parse_charset_directive() - { - lex < - sequence < - quoted_string, - optional_spaces, - exactly <';'> - > - >(); - } + default: + if (next == $nul) goto outOfLoop; - // called after parsing `kwd_include_directive` - Mixin_Call_Obj Parser::parse_include_directive() - { - // lex identifier into `lexed` var - lex_identifier(); // may error out - // normalize underscores to hyphens - sass::string name(Util::normalize_underscores(lexed)); - // create the initial mixin call object - Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, Arguments_Obj{}); - // parse mandatory arguments - call->arguments(parse_arguments()); - // parse using and optional block parameters - bool has_parameters = lex< kwd_using >() != nullptr; - - if (has_parameters) { - if (!peek< exactly<'('> >()) css_error("Invalid CSS", " after ", ": expected \"(\", was "); - } else { - if (peek< exactly<'('> >()) css_error("Invalid CSS", " after ", ": expected \";\", was "); + if (lookingAtIdentifier()) { + buffer.write(readIdentifier()); + } + else { + buffer.write(scanner.readChar()); + } + wroteNewline = false; + break; + } } - if (has_parameters) call->block_parameters(parse_parameters()); - - // parse optional block - if (peek < exactly <'{'> >()) { - call->block(parse_block()); - } - else if (has_parameters) { - css_error("Invalid CSS", " after ", ": expected \"{\", was "); - } - // return ast node - return call.detach(); - } - // EO parse_include_directive + outOfLoop: + if (!brackets.empty()) scanner.expectChar(brackets.back()); + if (!allowEmpty && buffer.empty()) error( + "Expected token.", scanner.rawSpan()); + return buffer.buffer; - SimpleSelectorObj Parser::parse_simple_selector() - { - lex < css_comments >(false); - if (lex< class_name >()) { - return SASS_MEMORY_NEW(ClassSelector, pstate, lexed); - } - else if (lex< id_name >()) { - return SASS_MEMORY_NEW(IDSelector, pstate, lexed); - } - else if (lex< alternatives < variable, number, static_reference_combinator > >()) { - return SASS_MEMORY_NEW(TypeSelector, pstate, lexed); - } - else if (peek< pseudo_not >()) { - return parse_negated_selector2(); - } - else if (peek< re_pseudo_selector >()) { - return parse_pseudo_selector(); - } - else if (peek< exactly<':'> >()) { - return parse_pseudo_selector(); - } - else if (lex < exactly<'['> >()) { - return parse_attribute_selector(); - } - else if (lex< placeholder >()) { - return SASS_MEMORY_NEW(PlaceholderSelector, pstate, lexed); - } - else { - css_error("Invalid CSS", " after ", ": expected selector, was "); - } - // failed - return {}; } + // EO declarationValue - PseudoSelectorObj Parser::parse_negated_selector2() + // Consumes a `url()` token if possible, and returns `null` otherwise. + sass::string Parser::tryUrl() { - lex< pseudo_not >(); - sass::string name(lexed); - SourceSpan nsource_position = pstate; - SelectorListObj negated = parseSelectorList(true); - if (!lex< exactly<')'> >()) { - error("negated selector is missing ')'"); - } - name.erase(name.size() - 1); - PseudoSelector* sel = SASS_MEMORY_NEW(PseudoSelector, nsource_position, name.substr(1)); - sel->selector(negated); - return sel; - } + // NOTE: this logic is largely duplicated in ScssParser.tryUrlContents. + // Most changes here should be mirrored there. - // Helper to clean binominal string - bool BothAreSpaces(char lhs, char rhs) { return isspace(lhs) && isspace(rhs); } - - // a pseudo selector often starts with one or two colons - // it can contain more selectors inside parentheses - SimpleSelectorObj Parser::parse_pseudo_selector() { - - // Lex one or two colon characters - if (lex()) { - sass::string colons(lexed); - // Check if it is a pseudo element - bool element = colons.size() == 2; - - if (lex< sequence< - // we keep the space within the name, strange enough - // ToDo: refactor output to schedule the space for it - // or do we really want to keep the real white-space? - sequence< identifier, optional < block_comment >, exactly<'('> > - > >()) - { - - sass::string name(lexed); - name.erase(name.size() - 1); - SourceSpan p = pstate; - - // specially parse nth-child pseudo selectors - if (lex_css < sequence < binomial, word_boundary >>()) { - sass::string parsed(lexed); // always compacting binominals (as dart-sass) - parsed.erase(std::unique(parsed.begin(), parsed.end(), BothAreSpaces), parsed.end()); - String_Constant_Obj arg = SASS_MEMORY_NEW(String_Constant, pstate, parsed); - PseudoSelector* pseudo = SASS_MEMORY_NEW(PseudoSelector, p, name, element); - if (lex < sequence < css_whitespace, insensitive < of_kwd >>>(false)) { - pseudo->selector(parseSelectorList(true)); - } - pseudo->argument(arg); - if (lex_css< exactly<')'> >()) { - return pseudo; - } - } - else { - if (peek_css< exactly<')'>>() && Util::equalsLiteral("nth-", name.substr(0, 4))) { - css_error("Invalid CSS", " after ", ": expected An+B expression, was "); - } - - sass::string unvendored = Util::unvendor(name); - - if (unvendored == "not" || unvendored == "matches" || unvendored == "current" || unvendored == "any" || unvendored == "has" || unvendored == "host" || unvendored == "host-context" || unvendored == "slotted") { - if (SelectorListObj wrapped = parseSelectorList(true)) { - if (wrapped && lex_css< exactly<')'> >()) { - PseudoSelector* pseudo = SASS_MEMORY_NEW(PseudoSelector, p, name, element); - pseudo->selector(wrapped); - return pseudo; - } - } - } else { - String_Schema_Obj arg = parse_css_variable_value(); - PseudoSelector* pseudo = SASS_MEMORY_NEW(PseudoSelector, p, name, element); - pseudo->argument(arg); - - if (lex_css< exactly<')'> >()) { - return pseudo; - } - } - } + StringScannerState state = scanner.state(); + if (!scanIdentifier("url")) return ""; - } - // EO if pseudo selector + if (!scanner.scanChar($lparen)) { + scanner.backtrack(state); + return ""; + } + + scanWhitespace(); - else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) { - return SASS_MEMORY_NEW(PseudoSelector, pstate, lexed, element); + // Match Ruby Sass's behavior: parse a raw URL() if possible, + // and if not backtrack and re-parse as a function expression. + uint8_t next; + StringBuffer buffer; + buffer.write("url("); + while (true) { + if (!scanner.peekChar(next)) { + break; } - else if (lex < pseudo_prefix >()) { - css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was "); + if (next == $percent || + next == $ampersand || + next == $hash || + (next >= $asterisk && next <= $tilde) || + next >= 0x0080) { + buffer.write(scanner.readChar()); + } + else if (next == $backslash) { + escape(buffer); + } + else if (isWhitespace(next)) { + scanWhitespace(); + if (scanner.peekChar() != $rparen) break; + } + else if (next == $rparen) { + buffer.write(scanner.readChar()); + return buffer.buffer; + } + else { + break; } - - } - else { - lex < identifier >(); // needed for error message? - css_error("Invalid CSS", " after ", ": expected selector, was "); } - - css_error("Invalid CSS", " after ", ": expected \")\", was "); - - // unreachable statement - return {}; - } - - const char* Parser::re_attr_sensitive_close(const char* src) - { - return alternatives < exactly<']'>, exactly<'/'> >(src); + scanner.backtrack(state); + return ""; } + // EO tryUrl - const char* Parser::re_attr_insensitive_close(const char* src) + // Consumes a Sass variable name, and returns + // its name without the dollar sign. + sass::string Parser::variableName() { - return sequence < insensitive<'i'>, re_attr_sensitive_close >(src); + scanner.expectChar($dollar); + // dart sass removes the dollar + return readIdentifier(); } - AttributeSelectorObj Parser::parse_attribute_selector() + // Consumes an escape sequence and returns the text that defines it. + // If [identifierStart] is true, this normalizes the escape sequence + // as though it were at the beginning of an identifier. + void Parser::escape(StringBuffer& buffer, bool identifierStart) { - SourceSpan p = pstate; - if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector"); - sass::string name(lexed); - if (lex_css< re_attr_sensitive_close >()) { - return SASS_MEMORY_NEW(AttributeSelector, p, name, "", String_Obj{}); + Offset start(scanner.offset); + scanner.expectChar($backslash); + uint32_t value = 0; + uint8_t first, next; + if (!scanner.peekChar(first)) { + return; } - else if (lex_css< re_attr_insensitive_close >()) { - char modifier = lexed.begin[0]; - return SASS_MEMORY_NEW(AttributeSelector, p, name, "", String_Obj{}, modifier); + else if (isNewline(first)) { + error("Expected escape sequence.", + scanner.rawSpan()); + return; } - if (!lex_css< alternatives< exact_match, class_match, dash_match, - prefix_match, suffix_match, substring_match > >()) { - error("invalid operator in attribute selector for " + name); - } - sass::string matcher(lexed); + else if (isHex(first)) { + for (uint8_t i = 0; i < 6; i++) { + scanner.peekChar(next); + if (next == $nul || !isHex(next)) break; + value *= 16; + value += asHex(scanner.readChar()); + } - String_Obj value; - if (lex_css< identifier >()) { - value = SASS_MEMORY_NEW(String_Constant, p, lexed); - } - else if (lex_css< quoted_string >()) { - value = parse_interpolated_chunk(lexed, true); // needed! + scanCharIf(isWhitespace); } else { - error("expected a string constant or identifier in attribute selector for " + name); + value = scanner.readChar(); } - if (lex_css< re_attr_sensitive_close >()) { - return SASS_MEMORY_NEW(AttributeSelector, p, name, matcher, value, 0); + if (identifierStart ? isNameStart(value) : isName(value)) { + if (!utf8::internal::is_code_point_valid(value)) { + error("Invalid Unicode code point.", + scanner.relevantSpanFrom(start)); + } + buffer.writeCharCode(value); + return; } - else if (lex_css< re_attr_insensitive_close >()) { - char modifier = lexed.begin[0]; - return SASS_MEMORY_NEW(AttributeSelector, p, name, matcher, value, modifier); + else if (value <= 0x1F || + value == 0x7F || + (identifierStart && isDigit(value))) { + buffer.write($backslash); + if (value > 0xF) buffer.write(hexCharFor(value >> 4)); + buffer.write(hexCharFor(value & 0xF)); + buffer.write($space); + return; + } + else { + buffer.write($backslash); + buffer.writeCharCode(value); + return; } - error("unterminated attribute selector for " + name); - return {}; // to satisfy compilers (error must not return) } + // EO tryUrl - /* parse block comment and add to block */ - void Parser::parse_block_comments(bool store) + // Consumes an escape sequence and returns the character it represents. + uint32_t Parser::escapeCharacter() { - Block_Obj block = block_stack.back(); - - while (lex< block_comment >()) { - bool is_important = lexed.begin[2] == '!'; - // flag on second param is to skip loosely over comments - String_Obj contents = parse_interpolated_chunk(lexed, true, false); - if (store) block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); - } - } + // See https://drafts.csswg.org/css-syntax-3/#consume-escaped-code-point. - Declaration_Obj Parser::parse_declaration() { - String_Obj prop; - bool is_custom_property = false; - if (lex< sequence< optional< exactly<'*'> >, identifier_schema > >()) { - const sass::string property(lexed); - is_custom_property = property.compare(0, 2, "--") == 0; - prop = parse_identifier_schema(); - } - else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) { - const sass::string property(lexed); - is_custom_property = property.compare(0, 2, "--") == 0; - prop = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - else { - css_error("Invalid CSS", " after ", ": expected \"}\", was "); + uint8_t first, next; + scanner.expectChar($backslash); + if (!scanner.peekChar(first)) { + return 0xFFFD; } - bool is_indented = true; - const sass::string property(lexed); - if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + escape_string(property) + "\" must be followed by a ':'"); - if (!is_custom_property && match< sequence< optional_css_comments, exactly<';'> > >()) error("style declaration must contain a value"); - if (match< sequence< optional_css_comments, exactly<'{'> > >()) is_indented = false; // don't indent if value is empty - if (is_custom_property) { - return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_css_variable_value(), false, true); + else if (isNewline(first)) { + error("Expected escape sequence.", + scanner.rawSpan()); + return 0; } - lex < css_comments >(false); - if (peek_css< static_value >()) { - return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_static_value()/*, lex()*/); - } - else { - ExpressionObj value; - Lookahead lookahead = lookahead_for_value(position); - if (lookahead.found) { - if (lookahead.has_interpolants) { - value = parse_value_schema(lookahead.found); - } else { - value = parse_list(DELAYED); - } + else if (isHex(first)) { + uint32_t value = 0; + for (uint8_t i = 0; i < 6; i++) { + if (!scanner.peekChar(next) || !isHex(next)) break; + value = (value << 4) + asHex(scanner.readChar()); + } + if (isWhitespace(scanner.peekChar())) { + scanner.readChar(); + } + if (value == 0 || + (value >= 0xD800 && value <= 0xDFFF) || + value >= 0x10FFFF) { + return 0xFFFD; } else { - value = parse_list(DELAYED); - if (List* list = Cast(value)) { - if (!list->is_bracketed() && list->length() == 0 && !peek< exactly <'{'> >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - } + return value; } - lex < css_comments >(false); - Declaration_Obj decl = SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, value/*, lex()*/); - decl->is_indented(is_indented); - decl->update_pstate(pstate); - return decl; + } + else { + return scanner.readChar(); } } - ExpressionObj Parser::parse_map() + // Consumes the next character if it matches [condition]. + // Returns whether or not the character was consumed. + bool Parser::scanCharIf(bool(*condition)(uint8_t character)) { - NESTING_GUARD(nestings); - ExpressionObj key = parse_list(); - List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH); - - // it's not a map so return the lexed value as a list value - if (!lex_css< exactly<':'> >()) - { return key; } - - List_Obj l = Cast(key); - if (l && l->separator() == SASS_COMMA) { - css_error("Invalid CSS", " after ", ": expected \")\", was "); - } - - ExpressionObj value = parse_space_list(); - - map->append(key); - map->append(value); - - while (lex_css< exactly<','> >()) - { - // allow trailing commas - #495 - if (peek_css< exactly<')'> >(position)) - { break; } - - key = parse_space_list(); - - if (!(lex< exactly<':'> >())) - { css_error("Invalid CSS", " after ", ": expected \":\", was "); } - - value = parse_space_list(); - - map->append(key); - map->append(value); - } - - SourceSpan ps = map->pstate(); - ps.offset = pstate.position - ps.position + pstate.offset; - map->pstate(ps); - - return map; + uint8_t next = scanner.peekChar(); + if (!condition(next)) return false; + scanner.readChar(); + return true; } - ExpressionObj Parser::parse_bracket_list() + // Consumes the next character if it's equal + // to [letter], ignoring ASCII case. + bool Parser::scanCharIgnoreCase(uint8_t letter) { - NESTING_GUARD(nestings); - // check if we have an empty list - // return the empty list as such - if (peek_css< list_terminator >(position)) - { - // return an empty list (nothing to delay) - return SASS_MEMORY_NEW(List, pstate, 0, SASS_SPACE, false, true); - } - - bool has_paren = peek_css< exactly<'('> >() != NULL; - - // now try to parse a space list - ExpressionObj list = parse_space_list(); - // if it's a singleton, return it (don't wrap it) - if (!peek_css< exactly<','> >(position)) { - List_Obj l = Cast(list); - if (!l || l->is_bracketed() || has_paren) { - List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 1, SASS_SPACE, false, true); - bracketed_list->append(list); - return bracketed_list; - } - l->is_bracketed(true); - return l; - } - - // if we got so far, we actually do have a comma list - List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA, false, true); - // wrap the first expression - bracketed_list->append(list); - - while (lex_css< exactly<','> >()) - { - // check for abort condition - if (peek_css< list_terminator >(position) - ) { break; } - // otherwise add another expression - bracketed_list->append(parse_space_list()); - } - // return the list - return bracketed_list; + if (!equalsLetterIgnoreCase(letter, scanner.peekChar())) return false; + scanner.readChar(); + return true; } - // parse list returns either a space separated list, - // a comma separated list or any bare expression found. - // so to speak: we unwrap items from lists if possible here! - ExpressionObj Parser::parse_list(bool delayed) + // Consumes the next character and asserts that + // it's equal to [letter], ignoring ASCII case. + void Parser::expectCharIgnoreCase(uint8_t letter) { - NESTING_GUARD(nestings); - return parse_comma_list(delayed); + Offset start(scanner.offset); + uint8_t actual(scanner.readChar()); + if (!equalsLetterIgnoreCase(letter, actual)) { + sass::string msg = "Expected \""; + msg += letter; msg += "\"."; + scanner.offset = start; + error(msg, scanner.rawSpan()); + } } - // will return singletons unwrapped - ExpressionObj Parser::parse_comma_list(bool delayed) + // Returns whether the scanner is immediately before a number. This follows [the CSS algorithm]. + // [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#starts-with-a-number + bool Parser::lookingAtNumber() const { - NESTING_GUARD(nestings); - // check if we have an empty list - // return the empty list as such - if (peek_css< list_terminator >(position)) - { - // return an empty list (nothing to delay) - return SASS_MEMORY_NEW(List, pstate, 0); - } + uint8_t first, second, third; + if (!scanner.peekChar(first)) return false; + if (isDigit(first)) return true; - // now try to parse a space list - ExpressionObj list = parse_space_list(); - // if it's a singleton, return it (don't wrap it) - if (!peek_css< exactly<','> >(position)) { - // set_delay doesn't apply to list children - // so this will only undelay single values - if (!delayed) list->set_delayed(false); - return list; + if (first == $dot) { + return scanner.peekChar(second, 1) + && isDigit(second); } + else if (first == $plus || first == $minus) { + if (!scanner.peekChar(second, 1)) return false; + if (isDigit(second)) return true; + if (second != $dot) return false; - // if we got so far, we actually do have a comma list - List_Obj comma_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA); - // wrap the first expression - comma_list->append(list); - - while (lex_css< exactly<','> >()) - { - // check for abort condition - if (peek_css< list_terminator >(position) - ) { break; } - // otherwise add another expression - comma_list->append(parse_space_list()); + return scanner.peekChar(third, 2) + && isDigit(third); } - // return the list - return comma_list; - } - // EO parse_comma_list - - // will return singletons unwrapped - ExpressionObj Parser::parse_space_list() - { - NESTING_GUARD(nestings); - ExpressionObj disj1 = parse_disjunction(); - // if it's a singleton, return it (don't wrap it) - if (peek_css< space_list_terminator >(position) - ) { - return disj1; } - - List_Obj space_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_SPACE); - space_list->append(disj1); - - while ( - !(peek_css< space_list_terminator >(position)) && - peek_css< optional_css_whitespace >() != end - ) { - // the space is parsed implicitly? - space_list->append(parse_disjunction()); + else { + return false; } - // return the list - return space_list; } - // EO parse_space_list + // EO lookingAtNumber - // parse logical OR operation - ExpressionObj Parser::parse_disjunction() + // Returns whether the scanner is immediately before a plain CSS identifier. + // If [forward] is passed, this looks that many characters forward instead. + // This is based on [the CSS algorithm][], but it assumes all backslashes start escapes. + // [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier + bool Parser::lookingAtIdentifier(size_t forward) const { - NESTING_GUARD(nestings); - advanceToNextToken(); - SourceSpan state(pstate); - // parse the left hand side conjunction - ExpressionObj conj = parse_conjunction(); - // parse multiple right hand sides - sass::vector operands; - while (lex_css< kwd_or >()) - operands.push_back(parse_conjunction()); - // if it's a singleton, return it directly - if (operands.size() == 0) return conj; - // fold all operands into one binary expression - ExpressionObj ex = fold_operands(conj, operands, { Sass_OP::OR }); - state.offset = pstate.position - state.position + pstate.offset; - ex->pstate(state); - return ex; + // See also [ScssParser.lookingAtInterpolatedIdentifier]. + uint8_t first, second; + if (!scanner.peekChar(first, forward)) return false; + if (isNameStart(first) || first == $backslash) return true; + if (first != $dash) return false; + + if (!scanner.peekChar(second, forward + 1)) return false; + + return isNameStart(second) + || second == $backslash + || second == $dash; } - // EO parse_disjunction + // EO lookingAtIdentifier - // parse logical AND operation - ExpressionObj Parser::parse_conjunction() + // Returns whether the scanner is immediately before a sequence + // of characters that could be part of a plain CSS identifier body. + bool Parser::lookingAtIdentifierBody() { - NESTING_GUARD(nestings); - advanceToNextToken(); - SourceSpan state(pstate); - // parse the left hand side relation - ExpressionObj rel = parse_relation(); - // parse multiple right hand sides - sass::vector operands; - while (lex_css< kwd_and >()) { - operands.push_back(parse_relation()); - } - // if it's a singleton, return it directly - if (operands.size() == 0) return rel; - // fold all operands into one binary expression - ExpressionObj ex = fold_operands(rel, operands, { Sass_OP::AND }); - state.offset = pstate.position - state.position + pstate.offset; - ex->pstate(state); - return ex; + uint8_t next = scanner.peekChar(); + return next && (isName(next) || next == $backslash); } - // EO parse_conjunction + // EO lookingAtIdentifierBody - // parse comparison operations - ExpressionObj Parser::parse_relation() + // Consumes an identifier if its name exactly matches [text]. + bool Parser::scanIdentifier(const char* text) { - NESTING_GUARD(nestings); - advanceToNextToken(); - SourceSpan state(pstate); - // parse the left hand side expression - ExpressionObj lhs = parse_expression(); - sass::vector operands; - sass::vector operators; - // if it's a singleton, return it (don't wrap it) - while (peek< alternatives < - kwd_eq, - kwd_neq, - kwd_gte, - kwd_gt, - kwd_lte, - kwd_lt - > >(position)) - { - // is directly adjancent to expression? - bool left_ws = peek < css_comments >() != NULL; - // parse the operator - enum Sass_OP op - = lex() ? Sass_OP::EQ - : lex() ? Sass_OP::NEQ - : lex() ? Sass_OP::GTE - : lex() ? Sass_OP::LTE - : lex() ? Sass_OP::GT - : lex() ? Sass_OP::LT - // we checked the possibilities on top of fn - : Sass_OP::EQ; - // is directly adjacent to expression? - bool right_ws = peek < css_comments >() != NULL; - operators.push_back({ op, left_ws, right_ws }); - operands.push_back(parse_expression()); + if (!lookingAtIdentifier()) return false; + + StringScannerState state = scanner.state(); + for (size_t i = 0; text[i]; i++) { + if (scanCharIgnoreCase(text[i])) continue; + scanner.backtrack(state); + return false; } - // we are called recursively for list, so we first - // fold inner binary expression which has delayed - // correctly set to zero. After folding we also unwrap - // single nested items. So we cannot set delay on the - // returned result here, as we have lost nestings ... - ExpressionObj ex = fold_operands(lhs, operands, operators); - state.offset = pstate.position - state.position + pstate.offset; - ex->pstate(state); - return ex; + + if (!lookingAtIdentifierBody()) return true; + scanner.backtrack(state); + return false; } - // parse_relation - - // parse expression valid for operations - // called from parse_relation - // called from parse_for_directive - // called from parse_media_expression - // parse addition and subtraction operations - ExpressionObj Parser::parse_expression() + // EO scanIdentifier + + // Consumes an identifier if its name exactly matches [text]. + bool Parser::scanIdentifier(sass::string text) { - NESTING_GUARD(nestings); - advanceToNextToken(); - SourceSpan state(pstate); - // parses multiple add and subtract operations - // NOTE: make sure that identifiers starting with - // NOTE: dashes do NOT count as subtract operation - ExpressionObj lhs = parse_operators(); - // if it's a singleton, return it (don't wrap it) - if (!(peek_css< exactly<'+'> >(position) || - // condition is a bit mysterious, but some combinations should not be counted as operations - (peek< no_spaces >(position) && peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< space > > >(position)) || - (peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< unsigned_number > > >(position))) || - peek< sequence < zero_plus < exactly <'-' > >, identifier > >(position)) - { return lhs; } - - sass::vector operands; - sass::vector operators; - bool left_ws = peek < css_comments >() != NULL; - while ( - lex_css< exactly<'+'> >() || - - ( - ! peek_css< sequence < zero_plus < exactly <'-' > >, identifier > >(position) - && lex_css< sequence< negate< digit >, exactly<'-'> > >() - ) - - ) { - - bool right_ws = peek < css_comments >() != NULL; - operators.push_back({ lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws, right_ws }); - operands.push_back(parse_operators()); - left_ws = peek < css_comments >() != NULL; + if (!lookingAtIdentifier()) return false; + + StringScannerState state = scanner.state(); + for (size_t i = 0; text[i]; i++) { + // uint8_t next = text[i]; // cast needed + if (scanCharIgnoreCase(text[i])) continue; + scanner.backtrack(state); + return false; } - if (operands.size() == 0) return lhs; - ExpressionObj ex = fold_operands(lhs, operands, operators); - state.offset = pstate.position - state.position + pstate.offset; - ex->pstate(state); - return ex; + if (!lookingAtIdentifierBody()) return true; + scanner.backtrack(state); + return false; } + // EO scanIdentifier - // parse addition and subtraction operations - ExpressionObj Parser::parse_operators() + // Consumes an identifier and asserts that its name exactly matches [text]. + void Parser::expectIdentifier(const char* text, sass::string name) { - NESTING_GUARD(nestings); - advanceToNextToken(); - SourceSpan state(pstate); - ExpressionObj factor = parse_factor(); - // if it's a singleton, return it (don't wrap it) - sass::vector operands; // factors - sass::vector operators; // ops - // lex operations to apply to lhs - const char* left_ws = peek < css_comments >(); - while (lex_css< class_char< static_ops > >()) { - const char* right_ws = peek < css_comments >(); - switch(*lexed.begin) { - case '*': operators.push_back({ Sass_OP::MUL, left_ws != 0, right_ws != 0 }); break; - case '/': operators.push_back({ Sass_OP::DIV, left_ws != 0, right_ws != 0 }); break; - case '%': operators.push_back({ Sass_OP::MOD, left_ws != 0, right_ws != 0 }); break; - default: throw std::runtime_error("unknown static op parsed"); - } - operands.push_back(parse_factor()); - left_ws = peek < css_comments >(); + Offset start(scanner.offset); + for (uint8_t i = 0; text[i]; i++) { + if (scanCharIgnoreCase(text[i])) continue; + error("Expected " + name + ".", + scanner.rawSpanFrom(start)); } - // operands and operators to binary expression - ExpressionObj ex = fold_operands(factor, operands, operators); - state.offset = pstate.position - state.position + pstate.offset; - ex->pstate(state); - return ex; + if (!lookingAtIdentifierBody()) return; + error("Expected " + name + ".", + scanner.rawSpanFrom(start)); + } + + // Throws an error associated with [pstate]. + void Parser::error(sass::string message, SourceSpan pstate) { + callStackFrame frame(context, BackTrace(pstate)); + throw Exception::ParserException(context, message); } - // EO parse_operators - - // called from parse_operators - // called from parse_value_schema - ExpressionObj Parser::parse_factor() - { - NESTING_GUARD(nestings); - lex < css_comments >(false); - if (lex_css< exactly<'('> >()) { - // parse_map may return a list - ExpressionObj value = parse_map(); - // lex the expected closing parenthesis - if (!lex_css< exactly<')'> >()) error("unclosed parenthesis"); - // expression can be evaluated - return value; - } - else if (lex_css< exactly<'['> >()) { - // explicit bracketed - ExpressionObj value = parse_bracket_list(); - // lex the expected closing square bracket - if (!lex_css< exactly<']'> >()) error("unclosed squared bracket"); - return value; - } - // string may be interpolated - // if (lex< quoted_string >()) { - // return &parse_string(); - // } - else if (peek< ie_property >()) { - return parse_ie_property(); - } - else if (peek< ie_keyword_arg >()) { - return parse_ie_keyword_arg(); - } - else if (peek< sequence < calc_fn_call, exactly <'('> > >()) { - return parse_calc_function(); - } - else if (lex < functional_schema >()) { - return parse_function_call_schema(); - } - else if (lex< identifier_schema >()) { - String_Obj string = parse_identifier_schema(); - if (String_Schema* schema = Cast(string)) { - if (lex < exactly < '(' > >()) { - schema->append(parse_list()); - lex < exactly < ')' > >(); - } - } - return string; - } - else if (peek< sequence< uri_prefix, W, real_uri_value > >()) { - return parse_url_function_string(); - } - else if (peek< re_functional >()) { - return parse_function_call(); - } - else if (lex< exactly<'+'> >()) { - Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else if (lex< exactly<'-'> >()) { - Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else if (lex< exactly<'/'> >()) { - Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::SLASH, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else if (lex< sequence< kwd_not > >()) { - Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else { - return parse_value(); - } - } - - bool number_has_zero(const sass::string& parsed) - { - size_t L = parsed.length(); - return !( (L > 0 && parsed.substr(0, 1) == ".") || - (L > 1 && parsed.substr(0, 2) == "0.") || - (L > 1 && parsed.substr(0, 2) == "-.") || - (L > 2 && parsed.substr(0, 3) == "-0.") ); - } - - Number* Parser::lexed_number(const SourceSpan& pstate, const sass::string& parsed) - { - Number* nr = SASS_MEMORY_NEW(Number, - pstate, - sass_strtod(parsed.c_str()), - "", - number_has_zero(parsed)); - nr->is_interpolant(false); - nr->is_delayed(true); - return nr; - } - - Number* Parser::lexed_percentage(const SourceSpan& pstate, const sass::string& parsed) - { - Number* nr = SASS_MEMORY_NEW(Number, - pstate, - sass_strtod(parsed.c_str()), - "%", - true); - nr->is_interpolant(false); - nr->is_delayed(true); - return nr; - } - - Number* Parser::lexed_dimension(const SourceSpan& pstate, const sass::string& parsed) - { - size_t L = parsed.length(); - size_t num_pos = parsed.find_first_not_of(" \n\r\t"); - if (num_pos == sass::string::npos) num_pos = L; - size_t unit_pos = parsed.find_first_not_of("-+0123456789.", num_pos); - if (parsed[unit_pos] == 'e' && is_number(parsed[unit_pos+1]) ) { - unit_pos = parsed.find_first_not_of("-+0123456789.", ++ unit_pos); - } - if (unit_pos == sass::string::npos) unit_pos = L; - const sass::string& num = parsed.substr(num_pos, unit_pos - num_pos); - Number* nr = SASS_MEMORY_NEW(Number, - pstate, - sass_strtod(num.c_str()), - Token(number(parsed.c_str())), - number_has_zero(parsed)); - nr->is_interpolant(false); - nr->is_delayed(true); - return nr; - } - - Value* Parser::lexed_hex_color(const SourceSpan& pstate, const sass::string& parsed) - { - Color_RGBA* color = NULL; - if (parsed[0] != '#') { - return SASS_MEMORY_NEW(String_Quoted, pstate, parsed); - } - // chop off the '#' - sass::string hext(parsed.substr(1)); - if (parsed.length() == 4) { - sass::string r(2, parsed[1]); - sass::string g(2, parsed[2]); - sass::string b(2, parsed[3]); - color = SASS_MEMORY_NEW(Color_RGBA, - pstate, - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - 1, // alpha channel - parsed); - } - else if (parsed.length() == 5) { - sass::string r(2, parsed[1]); - sass::string g(2, parsed[2]); - sass::string b(2, parsed[3]); - sass::string a(2, parsed[4]); - color = SASS_MEMORY_NEW(Color_RGBA, - pstate, - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - static_cast(strtol(a.c_str(), NULL, 16)) / 255, - parsed); - } - else if (parsed.length() == 7) { - sass::string r(parsed.substr(1,2)); - sass::string g(parsed.substr(3,2)); - sass::string b(parsed.substr(5,2)); - color = SASS_MEMORY_NEW(Color_RGBA, - pstate, - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - 1, // alpha channel - parsed); - } - else if (parsed.length() == 9) { - sass::string r(parsed.substr(1,2)); - sass::string g(parsed.substr(3,2)); - sass::string b(parsed.substr(5,2)); - sass::string a(parsed.substr(7,2)); - color = SASS_MEMORY_NEW(Color_RGBA, - pstate, - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - static_cast(strtol(a.c_str(), NULL, 16)) / 255, - parsed); - } - color->is_interpolant(false); - color->is_delayed(false); - return color; - } - - Value* Parser::color_or_string(const sass::string& lexed) const - { - if (auto color = name_to_color(lexed)) { - auto c = SASS_MEMORY_NEW(Color_RGBA, color); - c->is_delayed(true); - c->pstate(pstate); - c->disp(lexed); - return c; - } else { - return SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - } - - // parse one value for a list - ExpressionObj Parser::parse_value() - { - lex< css_comments >(false); - if (lex< ampersand >()) - { - if (match< ampersand >()) { - warning("In Sass, \"&&\" means two copies of the parent selector. You probably want to use \"and\" instead.", pstate); - } - return SASS_MEMORY_NEW(Parent_Reference, pstate); } - - if (lex< kwd_important >()) - { return SASS_MEMORY_NEW(String_Constant, pstate, "!important"); } - - // parse `10%4px` into separated items and not a schema - if (lex< sequence < percentage, lookahead < number > > >()) - { return lexed_percentage(lexed); } - - if (lex< sequence < number, lookahead< sequence < op, number > > > >()) - { return lexed_number(lexed); } - - // string may be interpolated - if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >()) - { return parse_string(); } - - if (const char* stop = peek< value_schema >()) - { return parse_value_schema(stop); } - - // string may be interpolated - if (lex< quoted_string >()) - { return parse_string(); } - - if (lex< kwd_true >()) - { return SASS_MEMORY_NEW(Boolean, pstate, true); } - - if (lex< kwd_false >()) - { return SASS_MEMORY_NEW(Boolean, pstate, false); } - - if (lex< kwd_null >()) - { return SASS_MEMORY_NEW(Null, pstate); } - - if (lex< identifier >()) { - return color_or_string(lexed); - } - - if (lex< percentage >()) - { return lexed_percentage(lexed); } - - // match hex number first because 0x000 looks like a number followed by an identifier - if (lex< sequence < alternatives< hex, hex0 >, negate < exactly<'-'> > > >()) - { return lexed_hex_color(lexed); } - - if (lex< hexa >()) - { return lexed_hex_color(lexed); } - - if (lex< sequence < exactly <'#'>, identifier > >()) - { return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); } - - // also handle the 10em- foo special case - // alternatives < exactly < '.' >, .. > -- `1.5em-.75em` is split into a list, not a binary expression - if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < space > > > > > >()) - { return lexed_dimension(lexed); } - - if (lex< sequence< static_component, one_plus< strict_identifier > > >()) - { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } - - if (lex< number >()) - { return lexed_number(lexed); } - - if (lex< variable >()) - { return SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)); } - - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - - // unreachable statement - return {}; - } - - // this parses interpolation inside other strings - // means the result should later be quoted again - String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant, bool css) - { - const char* i = chunk.begin; - // see if there any interpolants - const char* p = constant ? find_first_in_interval< exactly >(i, chunk.end) : - find_first_in_interval< exactly, block_comment >(i, chunk.end); - - if (!p) { - String_Quoted* str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, sass::string(i, chunk.end), 0, false, false, true, css); - if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); - return str_quoted; - } - - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate, 0, css); - schema->is_interpolant(true); - while (i < chunk.end) { - p = constant ? find_first_in_interval< exactly >(i, chunk.end) : - find_first_in_interval< exactly, block_comment >(i, chunk.end); - if (p) { - if (i < p) { - // accumulate the preceding segment if it's nonempty - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, p), css)); - } - // we need to skip anything inside strings - // create a new target in parser/prelexer - if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - const char* j = skip_over_scopes< exactly, exactly >(p + 2, chunk.end); // find the closing brace - if (j) { --j; - // parse the interpolant and accumulate it - LocalOption partEnd(end, j); - LocalOption partBeg(position, p + 2); - ExpressionObj interp_node = parse_list(); - interp_node->is_interpolant(true); - schema->append(interp_node); - i = j; - } - else { - // throw an error if the interpolant is unterminated - error("unterminated interpolant inside string constant " + chunk.to_string()); - } - } - else { // no interpolants left; add the last segment if nonempty - // check if we need quotes here (was not sure after merge) - if (i < chunk.end) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, chunk.end), css)); - break; - } - ++ i; - } - - return schema.detach(); - } - - String_Schema_Obj Parser::parse_css_variable_value() - { - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - sass::vector brackets; - while (true) { - if ( - (brackets.empty() && lex< css_variable_top_level_value >(false)) || - (!brackets.empty() && lex< css_variable_value >(false)) - ) { - Token str(lexed); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, str)); - } else if (ExpressionObj tok = lex_interpolation()) { - if (String_Schema* s = Cast(tok)) { - if (s->empty()) break; - schema->concat(s); - } else { - schema->append(tok); - } - } else if (lex< quoted_string >()) { - ExpressionObj tok = parse_string(); - if (tok.isNull()) break; - if (String_Schema* s = Cast(tok)) { - if (s->empty()) break; - schema->concat(s); - } else { - schema->append(tok); - } - } else if (lex< alternatives< exactly<'('>, exactly<'['>, exactly<'{'> > >()) { - const char opening_bracket = *(position - 1); - brackets.push_back(opening_bracket); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(1, opening_bracket))); - } else if (const char *match = peek< alternatives< exactly<')'>, exactly<']'>, exactly<'}'> > >()) { - if (brackets.empty()) break; - const char closing_bracket = *(match - 1); - if (brackets.back() != Util::opening_bracket_for(closing_bracket)) { - sass::string message = ": expected \""; - message += Util::closing_bracket_for(brackets.back()); - message += "\", was "; - css_error("Invalid CSS", " after ", message); - } - lex< alternatives< exactly<')'>, exactly<']'>, exactly<'}'> > >(); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(1, closing_bracket))); - brackets.pop_back(); - } else { - break; - } - } - - if (!brackets.empty()) { - sass::string message = ": expected \""; - message += Util::closing_bracket_for(brackets.back()); - message += "\", was "; - css_error("Invalid CSS", " after ", message); - } - - if (schema->empty()) error("Custom property values may not be empty."); - return schema.detach(); - } - - ValueObj Parser::parse_static_value() - { - lex< static_value >(); - Token str(lexed); - // static values always have trailing white- - // space and end delimiter (\s*[;]$) included - --pstate.offset.column; - --after_token.column; - --str.end; - --position; - - return color_or_string(str.time_wspace());; - } - - String_Obj Parser::parse_string() - { - return parse_interpolated_chunk(Token(lexed)); - } - - String_Obj Parser::parse_ie_property() - { - lex< ie_property >(); - Token str(lexed); - const char* i = str.begin; - // see if there any interpolants - const char* p = find_first_in_interval< exactly, block_comment >(str.begin, str.end); - if (!p) { - return SASS_MEMORY_NEW(String_Quoted, pstate, sass::string(str.begin, str.end)); - } - - String_Schema* schema = SASS_MEMORY_NEW(String_Schema, pstate); - while (i < str.end) { - p = find_first_in_interval< exactly, block_comment >(i, str.end); - if (p) { - if (i < p) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, p))); // accumulate the preceding segment if it's nonempty - } - if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - const char* j = skip_over_scopes< exactly, exactly >(p+2, str.end); // find the closing brace - if (j) { - // parse the interpolant and accumulate it - LocalOption partEnd(end, j); - LocalOption partBeg(position, p + 2); - ExpressionObj interp_node = parse_list(); - interp_node->is_interpolant(true); - schema->append(interp_node); - i = j; - } - else { - // throw an error if the interpolant is unterminated - error("unterminated interpolant inside IE function " + str.to_string()); - } - } - else { // no interpolants left; add the last segment if nonempty - if (i < str.end) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, str.end))); - } - break; - } - } - return schema; - } - - String_Obj Parser::parse_ie_keyword_arg() - { - String_Schema_Obj kwd_arg = SASS_MEMORY_NEW(String_Schema, pstate, 3); - if (lex< variable >()) { - kwd_arg->append(SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed))); - } else { - lex< alternatives< identifier_schema, identifier > >(); - kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - } - lex< exactly<'='> >(); - kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if (peek< variable >()) kwd_arg->append(parse_list()); - else if (lex< number >()) { - sass::string parsed(lexed); - Util::normalize_decimals(parsed); - kwd_arg->append(lexed_number(parsed)); - } - else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(parse_list()); } - return kwd_arg; - } - - String_Schema_Obj Parser::parse_value_schema(const char* stop) - { - // initialize the string schema object to add tokens - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - - if (peek>()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - - const char* e; - const char* ee = end; - end = stop; - size_t num_items = 0; - bool need_space = false; - while (position < stop) { - // parse space between tokens - if (lex< spaces >() && num_items) { - need_space = true; - } - if (need_space) { - need_space = false; - // schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); - } - if ((e = peek< re_functional >()) && e < stop) { - schema->append(parse_function_call()); - } - // lex an interpolant /#{...}/ - else if (lex< exactly < hash_lbrace > >()) { - // Try to lex static expression first - if (peek< exactly< rbrace > >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - ExpressionObj ex; - if (lex< re_static_expression >()) { - ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } else { - ex = parse_list(true); - } - ex->is_interpolant(true); - schema->append(ex); - if (!lex < exactly < rbrace > >()) { - css_error("Invalid CSS", " after ", ": expected \"}\", was "); - } - } - // lex some string constants or other valid token - // Note: [-+] chars are left over from i.e. `#{3}+3` - else if (lex< alternatives < exactly<'%'>, exactly < '-' >, exactly < '+' > > >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - } - // lex a quoted string - else if (lex< quoted_string >()) { - // need_space = true; - // if (schema->length()) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); - // else need_space = true; - schema->append(parse_string()); - if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { - // need_space = true; - } - if (peek < exactly < '-' > >()) break; - } - else if (lex< identifier >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { - // need_space = true; - } - } - // lex (normalized) variable - else if (lex< variable >()) { - sass::string name(Util::normalize_underscores(lexed)); - schema->append(SASS_MEMORY_NEW(Variable, pstate, name)); - } - // lex percentage value - else if (lex< percentage >()) { - schema->append(lexed_percentage(lexed)); - } - // lex dimension value - else if (lex< dimension >()) { - schema->append(lexed_dimension(lexed)); - } - // lex number value - else if (lex< number >()) { - schema->append(lexed_number(lexed)); - } - // lex hex color value - else if (lex< sequence < hex, negate < exactly < '-' > > > >()) { - schema->append(lexed_hex_color(lexed)); - } - else if (lex< sequence < exactly <'#'>, identifier > >()) { - schema->append(SASS_MEMORY_NEW(String_Quoted, pstate, lexed)); - } - // lex a value in parentheses - else if (peek< parenthese_scope >()) { - schema->append(parse_factor()); - } - else { - break; - } - ++num_items; - } - if (position != stop) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(position, stop))); - position = stop; - } - end = ee; - return schema; - } - - // this parses interpolation outside other strings - // means the result must not be quoted again later - String_Obj Parser::parse_identifier_schema() - { - Token id(lexed); - const char* i = id.begin; - // see if there any interpolants - const char* p = find_first_in_interval< exactly, block_comment >(id.begin, id.end); - if (!p) { - return SASS_MEMORY_NEW(String_Constant, pstate, sass::string(id.begin, id.end)); - } - - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - while (i < id.end) { - p = find_first_in_interval< exactly, block_comment >(i, id.end); - if (p) { - if (i < p) { - // accumulate the preceding segment if it's nonempty - const char* o = position; position = i; - schema->append(parse_value_schema(p)); - position = o; - } - // we need to skip anything inside strings - // create a new target in parser/prelexer - if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - const char* j = skip_over_scopes< exactly, exactly >(p+2, id.end); // find the closing brace - if (j) { - // parse the interpolant and accumulate it - LocalOption partEnd(end, j); - LocalOption partBeg(position, p + 2); - ExpressionObj interp_node = parse_list(DELAYED); - interp_node->is_interpolant(true); - schema->append(interp_node); - // schema->has_interpolants(true); - i = j; - } - else { - // throw an error if the interpolant is unterminated - error("unterminated interpolant inside interpolated identifier " + id.to_string()); - } - } - else { // no interpolants left; add the last segment if nonempty - if (i < end) { - const char* o = position; position = i; - schema->append(parse_value_schema(id.end)); - position = o; - } - break; - } - } - return schema ? schema.detach() : 0; - } - - // calc functions should preserve arguments - Function_Call_Obj Parser::parse_calc_function() - { - lex< identifier >(); - sass::string name(lexed); - SourceSpan call_pos = pstate; - lex< exactly<'('> >(); - SourceSpan arg_pos = pstate; - const char* arg_beg = position; - parse_list(); - const char* arg_end = position; - lex< skip_over_scopes < - exactly < '(' >, - exactly < ')' > - > >(); - - Argument_Obj arg = SASS_MEMORY_NEW(Argument, arg_pos, parse_interpolated_chunk(Token(arg_beg, arg_end))); - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, arg_pos); - args->append(arg); - return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); - } - - String_Obj Parser::parse_url_function_string() - { - sass::string prefix(""); - if (lex< uri_prefix >()) { - prefix = sass::string(lexed); - } - - lex < optional_spaces >(); - String_Obj url_string = parse_url_function_argument(); - - sass::string suffix(""); - if (lex< real_uri_suffix >()) { - suffix = sass::string(lexed); - } - - sass::string uri(""); - if (url_string) { - uri = url_string->to_string({ NESTED, 5 }); - } - - if (String_Schema* schema = Cast(url_string)) { - String_Schema_Obj res = SASS_MEMORY_NEW(String_Schema, pstate); - res->append(SASS_MEMORY_NEW(String_Constant, pstate, prefix)); - res->append(schema); - res->append(SASS_MEMORY_NEW(String_Constant, pstate, suffix)); - return res; - } else { - sass::string res = prefix + uri + suffix; - return SASS_MEMORY_NEW(String_Constant, pstate, res); - } - } - - String_Obj Parser::parse_url_function_argument() - { - const char* p = position; - - sass::string uri(""); - if (lex< real_uri_value >(false)) { - uri = lexed.to_string(); - } - - if (peek< exactly< hash_lbrace > >()) { - const char* pp = position; - // TODO: error checking for unclosed interpolants - while (pp && peek< exactly< hash_lbrace > >(pp)) { - pp = sequence< interpolant, real_uri_value >(pp); - } - if (!pp) return {}; - position = pp; - return parse_interpolated_chunk(Token(p, position)); - } - else if (uri != "") { - sass::string res = Util::rtrim(uri); - return SASS_MEMORY_NEW(String_Constant, pstate, res); - } - - return {}; - } - - Function_Call_Obj Parser::parse_function_call() - { - lex< identifier >(); - sass::string name(lexed); - - if (Util::normalize_underscores(name) == "content-exists" && stack.back() != Scope::Mixin) - { error("Cannot call content-exists() except within a mixin."); } - - SourceSpan call_pos = pstate; - Arguments_Obj args = parse_arguments(); - return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); - } - - Function_Call_Obj Parser::parse_function_call_schema() - { - String_Obj name = parse_identifier_schema(); - SourceSpan source_position_of_call = pstate; - Arguments_Obj args = parse_arguments(); - - return SASS_MEMORY_NEW(Function_Call, source_position_of_call, name, args); - } - - Content_Obj Parser::parse_content_directive() - { - SourceSpan call_pos = pstate; - Arguments_Obj args = parse_arguments(); - - return SASS_MEMORY_NEW(Content, call_pos, args); - } - - If_Obj Parser::parse_if_directive(bool else_if) - { - stack.push_back(Scope::Control); - SourceSpan if_source_position = pstate; - bool root = block_stack.back()->is_root(); - ExpressionObj predicate = parse_list(); - Block_Obj block = parse_block(root); - Block_Obj alternative; - - // only throw away comment if we parse a case - // we want all other comments to be parsed - if (lex_css< elseif_directive >()) { - alternative = SASS_MEMORY_NEW(Block, pstate); - alternative->append(parse_if_directive(true)); - } - else if (lex_css< kwd_else_directive >()) { - alternative = parse_block(root); - } - stack.pop_back(); - return SASS_MEMORY_NEW(If, if_source_position, predicate, block, alternative); - } - - ForRuleObj Parser::parse_for_directive() - { - stack.push_back(Scope::Control); - SourceSpan for_source_position = pstate; - bool root = block_stack.back()->is_root(); - lex_variable(); - sass::string var(Util::normalize_underscores(lexed)); - if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive"); - ExpressionObj lower_bound = parse_expression(); - bool inclusive = false; - if (lex< kwd_through >()) inclusive = true; - else if (lex< kwd_to >()) inclusive = false; - else error("expected 'through' or 'to' keyword in @for directive"); - ExpressionObj upper_bound = parse_expression(); - Block_Obj body = parse_block(root); - stack.pop_back(); - return SASS_MEMORY_NEW(ForRule, for_source_position, var, lower_bound, upper_bound, body, inclusive); - } - - // helper to parse a var token - Token Parser::lex_variable() - { - // peek for dollar sign first - if (!peek< exactly <'$'> >()) { - css_error("Invalid CSS", " after ", ": expected \"$\", was "); - } - // we expect a simple identifier as the call name - if (!lex< sequence < exactly <'$'>, identifier > >()) { - lex< exactly <'$'> >(); // move pstate and position up - css_error("Invalid CSS", " after ", ": expected identifier, was "); - } - // return object - return lexed; - } - // helper to parse identifier - Token Parser::lex_identifier() - { - // we expect a simple identifier as the call name - if (!lex< identifier >()) { // ToDo: pstate wrong? - css_error("Invalid CSS", " after ", ": expected identifier, was "); - } - // return object - return lexed; - } - - EachRuleObj Parser::parse_each_directive() - { - stack.push_back(Scope::Control); - SourceSpan each_source_position = pstate; - bool root = block_stack.back()->is_root(); - sass::vector vars; - lex_variable(); - vars.push_back(Util::normalize_underscores(lexed)); - while (lex< exactly<','> >()) { - if (!lex< variable >()) error("@each directive requires an iteration variable"); - vars.push_back(Util::normalize_underscores(lexed)); - } - if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive"); - ExpressionObj list = parse_list(); - Block_Obj body = parse_block(root); - stack.pop_back(); - return SASS_MEMORY_NEW(EachRule, each_source_position, vars, list, body); - } - - // called after parsing `kwd_while_directive` - WhileRuleObj Parser::parse_while_directive() - { - stack.push_back(Scope::Control); - bool root = block_stack.back()->is_root(); - // create the initial while call object - WhileRuleObj call = SASS_MEMORY_NEW(WhileRule, pstate, ExpressionObj{}, Block_Obj{}); - // parse mandatory predicate - ExpressionObj predicate = parse_list(); - List_Obj l = Cast(predicate); - if (!predicate || (l && !l->length())) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ", false); - } - call->predicate(predicate); - // parse mandatory block - call->block(parse_block(root)); - // return ast node - stack.pop_back(); - // return ast node - return call.detach(); - } - - - sass::vector Parser::parseCssMediaQueries() - { - sass::vector result; - do { - if (auto query = parseCssMediaQuery()) { - result.push_back(query); - } - } while (lex>()); - return result; - } - - sass::string Parser::parseIdentifier() - { - if (lex < identifier >(false)) { - return sass::string(lexed); - } - return sass::string(); - } - - CssMediaQuery_Obj Parser::parseCssMediaQuery() - { - CssMediaQuery_Obj result = SASS_MEMORY_NEW(CssMediaQuery, pstate); - lex(false); - - // Check if any tokens are to parse - if (!peek_css>()) { - - sass::string token1(parseIdentifier()); - lex(false); - - if (token1.empty()) { - return {}; - } - - sass::string token2(parseIdentifier()); - lex(false); - - if (Util::equalsLiteral("and", token2)) { - result->type(token1); - } - else { - if (token2.empty()) { - result->type(token1); - } - else { - result->modifier(token1); - result->type(token2); - } - - if (lex < kwd_and >()) { - lex(false); - } - else { - return result; - } - - } - - } - - sass::vector queries; - - do { - lex(false); - - if (lex>()) { - // In dart sass parser returns a pure string - if (lex < skip_over_scopes < exactly < '(' >, exactly < ')' > > >()) { - sass::string decl("(" + sass::string(lexed)); - queries.push_back(decl); - } - // Should be: parseDeclarationValue; - if (!lex>()) { - // Should we throw an error here? - } - } - } while (lex < kwd_and >()); - - result->features(queries); - - if (result->features().empty()) { - if (result->type().empty()) { - return {}; - } - } - - return result; - } - - - // EO parse_while_directive - MediaRule_Obj Parser::parseMediaRule() - { - MediaRule_Obj rule = SASS_MEMORY_NEW(MediaRule, pstate); - stack.push_back(Scope::Media); - rule->schema(parse_media_queries()); - parse_block_comments(false); - rule->block(parse_css_block()); - stack.pop_back(); - return rule; - } - - List_Obj Parser::parse_media_queries() - { - advanceToNextToken(); - List_Obj queries = SASS_MEMORY_NEW(List, pstate, 0, SASS_COMMA); - if (!peek_css < exactly <'{'> >()) queries->append(parse_media_query()); - while (lex_css < exactly <','> >()) queries->append(parse_media_query()); - queries->update_pstate(pstate); - return queries.detach(); - } - - // Expression* Parser::parse_media_query() - Media_Query_Obj Parser::parse_media_query() - { - advanceToNextToken(); - Media_Query_Obj media_query = SASS_MEMORY_NEW(Media_Query, pstate); - if (lex < kwd_not >()) { media_query->is_negated(true); lex < css_comments >(false); } - else if (lex < kwd_only >()) { media_query->is_restricted(true); lex < css_comments >(false); } - - if (lex < identifier_schema >()) media_query->media_type(parse_identifier_schema()); - else if (lex < identifier >()) media_query->media_type(parse_interpolated_chunk(lexed)); - else media_query->append(parse_media_expression()); - - while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); - if (lex < identifier_schema >()) { - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - if (media_query->media_type()) { - schema->append(media_query->media_type()); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); - } - schema->append(parse_identifier_schema()); - media_query->media_type(schema); - } - while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); - - media_query->update_pstate(pstate); - - return media_query; - } - - Media_Query_ExpressionObj Parser::parse_media_expression() - { - if (lex < identifier_schema >()) { - String_Obj ss = parse_identifier_schema(); - return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, ExpressionObj{}, true); - } - if (!lex_css< exactly<'('> >()) { - error("media query expression must begin with '('"); - } - ExpressionObj feature; - if (peek_css< exactly<')'> >()) { - error("media feature required in media query expression"); - } - feature = parse_expression(); - ExpressionObj expression; - if (lex_css< exactly<':'> >()) { - expression = parse_list(DELAYED); - } - if (!lex_css< exactly<')'> >()) { - error("unclosed parenthesis in media query expression"); - } - return SASS_MEMORY_NEW(Media_Query_Expression, feature->pstate(), feature, expression); - } - - // lexed after `kwd_supports_directive` - // these are very similar to media blocks - SupportsRuleObj Parser::parse_supports_directive() - { - SupportsConditionObj cond = parse_supports_condition(/*top_level=*/true); - // create the ast node object for the support queries - SupportsRuleObj query = SASS_MEMORY_NEW(SupportsRule, pstate, cond); - // additional block is mandatory - // parse inner block - query->block(parse_block()); - // return ast node - return query; - } - - // parse one query operation - // may encounter nested queries - SupportsConditionObj Parser::parse_supports_condition(bool top_level) - { - lex < css_whitespace >(); - SupportsConditionObj cond; - if ((cond = parse_supports_negation())) return cond; - if ((cond = parse_supports_operator(top_level))) return cond; - if ((cond = parse_supports_interpolation())) return cond; - return cond; - } - - SupportsConditionObj Parser::parse_supports_negation() - { - if (!lex < kwd_not >()) return {}; - SupportsConditionObj cond = parse_supports_condition_in_parens(/*parens_required=*/true); - return SASS_MEMORY_NEW(SupportsNegation, pstate, cond); - } - - SupportsConditionObj Parser::parse_supports_operator(bool top_level) - { - SupportsConditionObj cond = parse_supports_condition_in_parens(/*parens_required=*/top_level); - if (cond.isNull()) return {}; - - while (true) { - SupportsOperation::Operand op = SupportsOperation::OR; - if (lex < kwd_and >()) { op = SupportsOperation::AND; } - else if(!lex < kwd_or >()) { break; } - - lex < css_whitespace >(); - SupportsConditionObj right = parse_supports_condition_in_parens(/*parens_required=*/true); - - // SupportsCondition* cc = SASS_MEMORY_NEW(SupportsCondition, *static_cast(cond)); - cond = SASS_MEMORY_NEW(SupportsOperation, pstate, cond, right, op); - } - return cond; - } - - SupportsConditionObj Parser::parse_supports_interpolation() - { - if (!lex < interpolant >()) return {}; - - String_Obj interp = parse_interpolated_chunk(lexed); - if (!interp) return {}; - - return SASS_MEMORY_NEW(Supports_Interpolation, pstate, interp); - } - - // TODO: This needs some major work. Although feature conditions - // look like declarations their semantics differ significantly - SupportsConditionObj Parser::parse_supports_declaration() - { - SupportsCondition* cond; - // parse something declaration like - ExpressionObj feature = parse_expression(); - ExpressionObj expression; - if (lex_css< exactly<':'> >()) { - expression = parse_list(DELAYED); - } - if (!feature || !expression) error("@supports condition expected declaration"); - cond = SASS_MEMORY_NEW(SupportsDeclaration, - feature->pstate(), - feature, - expression); - // ToDo: maybe we need an additional error condition? - return cond; - } - - SupportsConditionObj Parser::parse_supports_condition_in_parens(bool parens_required) - { - SupportsConditionObj interp = parse_supports_interpolation(); - if (interp != nullptr) return interp; - - if (!lex < exactly <'('> >()) { - if (parens_required) { - css_error("Invalid CSS", " after ", ": expected @supports condition (e.g. (display: flexbox)), was ", /*trim=*/false); - } else { - return {}; - } - } - lex < css_whitespace >(); - - SupportsConditionObj cond = parse_supports_condition(/*top_level=*/false); - if (cond.isNull()) cond = parse_supports_declaration(); - if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); - - lex < css_whitespace >(); - return cond; - } - - AtRootRuleObj Parser::parse_at_root_block() - { - stack.push_back(Scope::AtRoot); - SourceSpan at_source_position = pstate; - Block_Obj body; - At_Root_Query_Obj expr; - Lookahead lookahead_result; - if (lex_css< exactly<'('> >()) { - expr = parse_at_root_query(); - } - if (peek_css < exactly<'{'> >()) { - lex (); - body = parse_block(true); - } - else if ((lookahead_result = lookahead_for_selector(position)).found) { - StyleRuleObj r = parse_ruleset(lookahead_result); - body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true); - body->append(r); - } - AtRootRuleObj at_root = SASS_MEMORY_NEW(AtRootRule, at_source_position, body); - if (!expr.isNull()) at_root->expression(expr); - stack.pop_back(); - return at_root; - } - - At_Root_Query_Obj Parser::parse_at_root_query() - { - if (peek< exactly<')'> >()) error("at-root feature required in at-root expression"); - - if (!peek< alternatives< kwd_with_directive, kwd_without_directive > >()) { - css_error("Invalid CSS", " after ", ": expected \"with\" or \"without\", was "); - } - - ExpressionObj feature = parse_list(); - if (!lex_css< exactly<':'> >()) error("style declaration must contain a value"); - ExpressionObj expression = parse_list(); - List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1); - - if (expression->concrete_type() == Expression::LIST) { - value = Cast(expression); - } - else value->append(expression); - - At_Root_Query_Obj cond = SASS_MEMORY_NEW(At_Root_Query, - value->pstate(), - feature, - value); - if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression"); - return cond; - } - - AtRuleObj Parser::parse_directive() - { - AtRuleObj directive = SASS_MEMORY_NEW(AtRule, pstate, lexed); - String_Schema_Obj val = parse_almost_any_value(); - // strip left and right if they are of type string - directive->value(val); - if (peek< exactly<'{'> >()) { - directive->block(parse_block()); - } - return directive; - } - - ExpressionObj Parser::lex_interpolation() - { - if (lex < interpolant >(true) != NULL) { - return parse_interpolated_chunk(lexed, true); - } - return {}; - } - - ExpressionObj Parser::lex_interp_uri() - { - // create a string schema by lexing optional interpolations - return lex_interp< re_string_uri_open, re_string_uri_close >(); - } - - ExpressionObj Parser::lex_interp_string() - { - ExpressionObj rv; - if ((rv = lex_interp< re_string_double_open, re_string_double_close >())) return rv; - if ((rv = lex_interp< re_string_single_open, re_string_single_close >())) return rv; - return rv; - } - - ExpressionObj Parser::lex_almost_any_value_chars() - { - const char* match = - lex < - one_plus < - alternatives < - exactly <'>'>, - sequence < - exactly <'\\'>, - any_char - >, - sequence < - negate < - sequence < - exactly < url_kwd >, - exactly <'('> - > - >, - neg_class_char < - almost_any_value_class - > - >, - sequence < - exactly <'/'>, - negate < - alternatives < - exactly <'/'>, - exactly <'*'> - > - > - >, - sequence < - exactly <'\\'>, - exactly <'#'>, - negate < - exactly <'{'> - > - >, - sequence < - exactly <'!'>, - negate < - alpha - > - > - > - > - >(false); - if (match) { - return SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - return {}; - } - - ExpressionObj Parser::lex_almost_any_value_token() - { - ExpressionObj rv; - if (*position == 0) return {}; - if ((rv = lex_almost_any_value_chars())) return rv; - // if ((rv = lex_block_comment())) return rv; - // if ((rv = lex_single_line_comment())) return rv; - if ((rv = lex_interp_string())) return rv; - if ((rv = lex_interp_uri())) return rv; - if ((rv = lex_interpolation())) return rv; - if (lex< alternatives< hex, hex0 > >()) - { return lexed_hex_color(lexed); } - return rv; - } - - String_Schema_Obj Parser::parse_almost_any_value() - { - - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - if (*position == 0) return {}; - lex < spaces >(false); - ExpressionObj token = lex_almost_any_value_token(); - if (!token) return {}; - schema->append(token); - if (*position == 0) { - schema->rtrim(); - return schema.detach(); - } - - while ((token = lex_almost_any_value_token())) { - schema->append(token); - } - - lex < css_whitespace >(); - - schema->rtrim(); - - return schema.detach(); - } - - WarningRuleObj Parser::parse_warning() - { - if (stack.back() != Scope::Root && - stack.back() != Scope::Function && - stack.back() != Scope::Mixin && - stack.back() != Scope::Control && - stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties."); - } - return SASS_MEMORY_NEW(WarningRule, pstate, parse_list(DELAYED)); - } - - ErrorRuleObj Parser::parse_error() - { - if (stack.back() != Scope::Root && - stack.back() != Scope::Function && - stack.back() != Scope::Mixin && - stack.back() != Scope::Control && - stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties."); - } - return SASS_MEMORY_NEW(ErrorRule, pstate, parse_list(DELAYED)); - } - - DebugRuleObj Parser::parse_debug() - { - if (stack.back() != Scope::Root && - stack.back() != Scope::Function && - stack.back() != Scope::Mixin && - stack.back() != Scope::Control && - stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties."); - } - return SASS_MEMORY_NEW(DebugRule, pstate, parse_list(DELAYED)); - } - - Return_Obj Parser::parse_return_directive() - { - // check that we do not have an empty list (ToDo: check if we got all cases) - if (peek_css < alternatives < exactly < ';' >, exactly < '}' >, end_of_file > >()) - { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } - return SASS_MEMORY_NEW(Return, pstate, parse_list()); - } - - Lookahead Parser::lookahead_for_selector(const char* start) - { - // init result struct - Lookahead rv = Lookahead(); - // get start position - const char* p = start ? start : position; - // match in one big "regex" - rv.error = p; - if (const char* q = - peek < - re_selector_list - >(p) - ) { - bool could_be_property = peek< sequence< exactly<'-'>, exactly<'-'> > >(p) != 0; - bool could_be_escaped = false; - while (p < q) { - // did we have interpolations? - if (*p == '#' && *(p+1) == '{') { - rv.has_interpolants = true; - p = q; break; - } - // A property that's ambiguous with a nested selector is interpreted as a - // custom property. - if (*p == ':' && !could_be_escaped) { - rv.is_custom_property = could_be_property || p+1 == q || peek< space >(p+1); - } - could_be_escaped = *p == '\\'; - ++ p; - } - // store anyway } - - - // ToDo: remove - rv.error = q; - rv.position = q; - // check expected opening bracket - // only after successful matching - if (peek < exactly<'{'> >(q)) rv.found = q; - // else if (peek < end_of_file >(q)) rv.found = q; - else if (peek < exactly<'('> >(q)) rv.found = q; - // else if (peek < exactly<';'> >(q)) rv.found = q; - // else if (peek < exactly<'}'> >(q)) rv.found = q; - if (rv.found || *p == 0) rv.error = 0; - } - - rv.parsable = ! rv.has_interpolants; - - // return result - return rv; - - } - // EO lookahead_for_selector - - // used in parse_block_nodes and parse_special_directive - // ToDo: actual usage is still not really clear to me? - Lookahead Parser::lookahead_for_include(const char* start) - { - // we actually just lookahead for a selector - Lookahead rv = lookahead_for_selector(start); - // but the "found" rules are different - if (const char* p = rv.position) { - // check for additional abort condition - if (peek < exactly<';'> >(p)) rv.found = p; - else if (peek < exactly<'}'> >(p)) rv.found = p; - } - // return result - return rv; - } - // EO lookahead_for_include - - // look ahead for a token with interpolation in it - // we mostly use the result if there is an interpolation - // everything that passes here gets parsed as one schema - // meaning it will not be parsed as a space separated list - Lookahead Parser::lookahead_for_value(const char* start) - { - // init result struct - Lookahead rv = Lookahead(); - // get start position - const char* p = start ? start : position; - // match in one big "regex" - if (const char* q = - peek < - non_greedy < - alternatives < - // consume whitespace - block_comment, // spaces, - // main tokens - sequence < - interpolant, - optional < - quoted_string - > - >, - identifier, - variable, - // issue #442 - sequence < - parenthese_scope, - interpolant, - optional < - quoted_string - > - > - >, - sequence < - // optional_spaces, - alternatives < - // end_of_file, - exactly<'{'>, - exactly<'}'>, - exactly<';'> - > - > - > - >(p) - ) { - if (p == q) return rv; - while (p < q) { - // did we have interpolations? - if (*p == '#' && *(p+1) == '{') { - rv.has_interpolants = true; - p = q; break; - } - ++ p; - } - // store anyway - // ToDo: remove - rv.position = q; - // check expected opening bracket - // only after successful matching - if (peek < exactly<'{'> >(q)) rv.found = q; - else if (peek < exactly<';'> >(q)) rv.found = q; - else if (peek < exactly<'}'> >(q)) rv.found = q; - } - - // return result - return rv; - } - // EO lookahead_for_value - - void Parser::read_bom() - { - size_t skip = 0; - sass::string encoding; - bool utf_8 = false; - switch ((unsigned char)position[0]) { - case 0xEF: - skip = check_bom_chars(position, end, utf_8_bom, 3); - encoding = "UTF-8"; - utf_8 = true; - break; - case 0xFE: - skip = check_bom_chars(position, end, utf_16_bom_be, 2); - encoding = "UTF-16 (big endian)"; - break; - case 0xFF: - skip = check_bom_chars(position, end, utf_16_bom_le, 2); - skip += (skip ? check_bom_chars(position, end, utf_32_bom_le, 4) : 0); - encoding = (skip == 2 ? "UTF-16 (little endian)" : "UTF-32 (little endian)"); - break; - case 0x00: - skip = check_bom_chars(position, end, utf_32_bom_be, 4); - encoding = "UTF-32 (big endian)"; - break; - case 0x2B: - skip = check_bom_chars(position, end, utf_7_bom_1, 4) - | check_bom_chars(position, end, utf_7_bom_2, 4) - | check_bom_chars(position, end, utf_7_bom_3, 4) - | check_bom_chars(position, end, utf_7_bom_4, 4) - | check_bom_chars(position, end, utf_7_bom_5, 5); - encoding = "UTF-7"; - break; - case 0xF7: - skip = check_bom_chars(position, end, utf_1_bom, 3); - encoding = "UTF-1"; - break; - case 0xDD: - skip = check_bom_chars(position, end, utf_ebcdic_bom, 4); - encoding = "UTF-EBCDIC"; - break; - case 0x0E: - skip = check_bom_chars(position, end, scsu_bom, 3); - encoding = "SCSU"; - break; - case 0xFB: - skip = check_bom_chars(position, end, bocu_1_bom, 3); - encoding = "BOCU-1"; - break; - case 0x84: - skip = check_bom_chars(position, end, gb_18030_bom, 4); - encoding = "GB-18030"; - break; - default: break; - } - if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding); - position += skip; - } - - size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len) - { - size_t skip = 0; - if (src + len > end) return 0; - for (size_t i = 0; i < len; ++i, ++skip) { - if ((unsigned char) src[i] != bom[i]) return 0; - } - return skip; - } - - - ExpressionObj Parser::fold_operands(ExpressionObj base, sass::vector& operands, Operand op) - { - for (size_t i = 0, S = operands.size(); i < S; ++i) { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), op, base, operands[i]); - } - return base; - } - - ExpressionObj Parser::fold_operands(ExpressionObj base, sass::vector& operands, sass::vector& ops, size_t i) - { - if (String_Schema* schema = Cast(base)) { - // return schema; - if (schema->has_interpolants()) { - if (i + 1 < operands.size() && ( - (ops[0].operand == Sass_OP::EQ) - || (ops[0].operand == Sass_OP::ADD) - || (ops[0].operand == Sass_OP::DIV) - || (ops[0].operand == Sass_OP::MUL) - || (ops[0].operand == Sass_OP::NEQ) - || (ops[0].operand == Sass_OP::LT) - || (ops[0].operand == Sass_OP::GT) - || (ops[0].operand == Sass_OP::LTE) - || (ops[0].operand == Sass_OP::GTE) - )) { - ExpressionObj rhs = fold_operands(operands[i], operands, ops, i + 1); - rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[0], schema, rhs); - return rhs; - } - // return schema; - } - } - - if (operands.size() > Constants::MaxCallStack) { - // XXX: this is never hit via spec tests - sass::ostream stm; - stm << "Stack depth exceeded max of " << Constants::MaxCallStack; - error(stm.str()); - } - - for (size_t S = operands.size(); i < S; ++i) { - if (String_Schema* schema = Cast(operands[i])) { - if (schema->has_interpolants()) { - if (i + 1 < S) { - // this whole branch is never hit via spec tests - ExpressionObj rhs = fold_operands(operands[i+1], operands, ops, i + 2); - rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], schema, rhs); - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, rhs); - return base; - } - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); - return base; - } else { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); - } - } else { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); - } - Binary_Expression* b = Cast(base.ptr()); - if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { - base->is_delayed(true); - } - } - // nested binary expression are never to be delayed - if (Binary_Expression* b = Cast(base)) { - if (Cast(b->left())) base->set_delayed(false); - if (Cast(b->right())) base->set_delayed(false); - } - return base; - } - - void Parser::error(sass::string msg) - { - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidSass(pstate, traces, msg); - } - - // print a css parsing error with actual context information from parsed source - void Parser::css_error(const sass::string& msg, const sass::string& prefix, const sass::string& middle, const bool trim) - { - int max_len = 18; - const char* end = this->end; - while (*end != 0) ++ end; - const char* pos = peek < optional_spaces >(); - if (!pos) pos = position; - - const char* last_pos(pos); - if (last_pos > begin) { - utf8::prior(last_pos, begin); - } - // backup position to last significant char - while (trim && last_pos > begin&& last_pos < end) { - if (!Util::ascii_isspace(static_cast(*last_pos))) break; - utf8::prior(last_pos, begin); - } - - bool ellipsis_left = false; - const char* pos_left(last_pos); - const char* end_left(last_pos); - - if (*pos_left) utf8::next(pos_left, end); - if (*end_left) utf8::next(end_left, end); - while (pos_left > begin) { - if (utf8::distance(pos_left, end_left) >= max_len) { - utf8::prior(pos_left, begin); - ellipsis_left = *(pos_left) != '\n' && - *(pos_left) != '\r'; - utf8::next(pos_left, end); - break; - } - - const char* prev = pos_left; - utf8::prior(prev, begin); - if (*prev == '\r') break; - if (*prev == '\n') break; - pos_left = prev; - } - if (pos_left < begin) { - pos_left = begin; - } - - bool ellipsis_right = false; - const char* end_right(pos); - const char* pos_right(pos); - while (end_right < end) { - if (utf8::distance(pos_right, end_right) > max_len) { - ellipsis_left = *(pos_right) != '\n' && - *(pos_right) != '\r'; - break; - } - if (*end_right == '\r') break; - if (*end_right == '\n') break; - utf8::next(end_right, end); - } - // if (*end_right == 0) end_right ++; - - sass::string left(pos_left, end_left); - sass::string right(pos_right, end_right); - size_t left_subpos = left.size() > 15 ? left.size() - 15 : 0; - size_t right_subpos = right.size() > 15 ? right.size() - 15 : 0; - if (left_subpos && ellipsis_left) left = ellipsis + left.substr(left_subpos); - if (right_subpos && ellipsis_right) right = right.substr(right_subpos) + ellipsis; - // now pass new message to the more generic error function - error(msg + prefix + quote(left) + middle + quote(right)); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/parser.hpp b/src/parser.hpp index 25c39b968f..c102c04952 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -1,394 +1,188 @@ -#ifndef SASS_PARSER_H -#define SASS_PARSER_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include - -#include "ast.hpp" -#include "position.hpp" -#include "context.hpp" -#include "position.hpp" -#include "prelexer.hpp" -#include "source.hpp" - -#ifndef MAX_NESTING -// Note that this limit is not an exact science -// it depends on various factors, which some are -// not under our control (compile time or even OS -// dependent settings on the available stack size) -// It should fix most common segfault cases though. -#define MAX_NESTING 512 -#endif - -struct Lookahead { - const char* found; - const char* error; - const char* position; - bool parsable; - bool has_interpolants; - bool is_custom_property; -}; +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_HPP +#define SASS_PARSER_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +// Parser has a great chance for stack overflow +// To fix this once and for all we need a different approach +// http://lambda-the-ultimate.org/node/1599 +// Basically we should not call into recursion, but rather +// return some continuation bit, of course we still need to +// add our ast nodes somewhere (instead of returning as now) + +#include "interpolation.hpp" +#include "scanner_string.hpp" namespace Sass { - class Parser : public SourceSpan { + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class Parser + { public: - enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules, AtRoot }; + // Compiler context + Compiler& context; - Context& ctx; - sass::vector block_stack; - sass::vector stack; - SourceDataObj source; - const char* begin; - const char* position; - const char* end; - Offset before_token; - Offset after_token; - SourceSpan pstate; - Backtraces traces; - size_t indentation; - size_t nestings; - bool allow_parent; - Token lexed; + // The scanner that scans through the text being parsed. + StringScanner scanner; - Parser(SourceData* source, Context& ctx, Backtraces, bool allow_parent = true); + protected: - // special static parsers to convert strings into certain selectors - static SelectorListObj parse_selector(SourceData* source, Context& ctx, Backtraces, bool allow_parent = true); + // Value constructor + Parser( + Compiler& context, + SourceDataObj source); -#ifdef __clang__ + friend class ExpressionParser; - // lex and peak uses the template parameter to branch on the action, which - // triggers clangs tautological comparison on the single-comparison - // branches. This is not a bug, just a merging of behaviour into - // one function + // Original source mappings. If defined, SourceSpans will + // point to where the original source has come from. + // sass::vector srcmap; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-compare" + // Points to context.varStack + sass::vector& varStack; -#endif + // The silent comment this parser encountered previously. + SilentCommentObj lastSilentComment; + protected: - // skip current token and next whitespace - // moves SourceSpan right before next token - void advanceToNextToken(); + // Returns whether [text] is a valid CSS identifier. + bool isIdentifier(sass::string text); - bool peek_newline(const char* start = 0); + // Consumes whitespace, including any comments. + virtual void scanWhitespace() + { + do { + scanWhitespaceWithoutComments(); + } while (scanComment()); + } - // skip over spaces, tabs and line comments - template - const char* sneak(const char* start = 0) + // Consumes whitespace, but not comments. + inline void scanWhitespaceWithoutComments() { - using namespace Prelexer; - - // maybe use optional start position from arguments? - const char* it_position = start ? start : position; - - // skip white-space? - if (mx == spaces || - mx == no_spaces || - mx == css_comments || - mx == css_whitespace || - mx == optional_spaces || - mx == optional_css_comments || - mx == optional_css_whitespace - ) { - return it_position; + while (!scanner.isDone() && Character::isWhitespace(scanner.peekChar())) { + scanner.readChar(); } - - // skip over spaces, tabs and sass line comments - const char* pos = optional_css_whitespace(it_position); - // always return a valid position - return pos ? pos : it_position; - } - // match will not skip over space, tabs and line comment - // return the position where the lexer match will occur - template - const char* match(const char* start = 0) + // Consumes spaces and tabs. + inline void scanSpaces() { - // match the given prelexer - return mx(position); + while (!scanner.isDone() && Character::isSpaceOrTab(scanner.peekChar())) { + scanner.readChar(); + } } - // peek will only skip over space, tabs and line comment - // return the position where the lexer match will occur - template - const char* peek(const char* start = 0) - { + // Consumes and ignores a comment if possible. + // Returns whether the comment was consumed. + bool scanComment(); - // sneak up to the actual token we want to lex - // this should skip over white-space if desired - const char* it_before_token = sneak < mx >(start); + // Consumes and ignores a silent (Sass-style) comment. + virtual SilentComment* readSilentComment(); - // match the given prelexer - const char* match = mx(it_before_token); + // Consumes and ignores a loud (CSS-style) comment. + void loudComment(); - // check if match is in valid range - return match <= end ? match : 0; + // Consumes a plain CSS identifier. If [unit] is `true`, this + // doesn't parse a `-` followed by a digit. This ensures that + // `1px-2px` parses as subtraction rather than the unit `px-2px`. + sass::string readIdentifier(bool unit = false); - } + // Consumes a chunk of a plain CSS identifier after the name start. + sass::string identifierBody(); - // white-space handling is built into the lexer - // this way you do not need to parse it yourself - // some matchers don't accept certain white-space - // we do not support start arg, since we manipulate - // sourcemap offset and we modify the position pointer! - // lex will only skip over space, tabs and line comment - template - const char* lex(bool lazy = true, bool force = false) - { + // Like [consumeidentifierBody], but parses the body into the [text] buffer. + void consumeidentifierBody(StringBuffer& text, bool unit = false); - if (*position == 0) return 0; + // Consumes a plain CSS string. This returns the parsed contents of the + // string—that is, it doesn't include quotes and its escapes are resolved. + sass::string string(); - // position considered before lexed token - // we can skip whitespace or comments for - // lazy developers (but we need control) - const char* it_before_token = position; + // Consumes and returns a natural number. + // That is, a non - negative integer. + // Doesn't support scientific notation. + double naturalNumber(); - // sneak up to the actual token we want to lex - // this should skip over white-space if desired - if (lazy) it_before_token = sneak < mx >(position); + // Consumes tokens until it reaches a top-level `";"`, `")"`, `"]"`, + // or `"}"` and returns their contents as a string. If [allowEmpty] + // is `false` (the default), this requires at least one token. + sass::string declarationValue(bool allowEmpty = false); - // now call matcher to get position after token - const char* it_after_token = mx(it_before_token); + // Consumes a `url()` token if possible, and returns `null` otherwise. + sass::string tryUrl(); - // check if match is in valid range - if (it_after_token > end) return 0; + // Consumes a Sass variable name, and returns + // its name without the dollar sign. + sass::string variableName(); - // maybe we want to update the parser state anyway? - if (force == false) { - // assertion that we got a valid match - if (it_after_token == 0) return 0; - // assertion that we actually lexed something - if (it_after_token == it_before_token) return 0; - } + // Consumes an escape sequence and returns the text that defines it. + // If [identifierStart] is true, this normalizes the escape sequence + // as though it were at the beginning of an identifier. + void escape(StringBuffer& buffer, bool identifierStart = false); - // create new lexed token object (holds the parse results) - lexed = Token(position, it_before_token, it_after_token); + // Consumes an escape sequence and returns the character it represents. + uint32_t escapeCharacter(); - // advance position (add whitespace before current token) - before_token = after_token.add(position, it_before_token); + // Consumes the next character if it matches [condition]. + // Returns whether or not the character was consumed. + bool scanCharIf(bool (*condition)(uint8_t character)); - // update after_token position for current token - after_token.add(it_before_token, it_after_token); + // Consumes the next character if it's equal + // to [letter], ignoring ASCII case. + bool scanCharIgnoreCase(uint8_t letter); - // ToDo: could probably do this incremental on original object (API wants offset?) - pstate = SourceSpan(source, before_token, after_token - before_token); + // Consumes the next character and asserts that + // it's equal to [letter], ignoring ASCII case. + void expectCharIgnoreCase(uint8_t letter); - // advance internal char iterator - return position = it_after_token; + // Returns whether the scanner is immediately before a number. This follows [the CSS algorithm]. + // [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#starts-with-a-number + bool lookingAtNumber() const; - } + // Returns whether the scanner is immediately before a plain CSS identifier. + // If [forward] is passed, this looks that many characters forward instead. + // This is based on [the CSS algorithm][], but it assumes all backslashes start escapes. + // [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier + bool lookingAtIdentifier(size_t forward = 0) const; - // lex_css skips over space, tabs, line and block comment - // all block comments will be consumed and thrown away - // source-map position will point to token after the comment - template - const char* lex_css() - { - // copy old token - Token prev = lexed; - // store previous pointer - const char* oldpos = position; - Offset bt = before_token; - Offset at = after_token; - SourceSpan op = pstate; - // throw away comments - // update srcmap position - lex < Prelexer::css_comments >(); - // now lex a new token - const char* pos = lex< mx >(); - // maybe restore prev state - if (pos == 0) { - pstate = op; - lexed = prev; - position = oldpos; - after_token = at; - before_token = bt; - } - // return match - return pos; - } - - // all block comments will be skipped and thrown away - template - const char* peek_css(const char* start = 0) - { - // now peek a token (skip comments first) - return peek< mx >(peek < Prelexer::css_comments >(start)); - } - -#ifdef __clang__ + // Returns whether the scanner is immediately before a sequence + // of characters that could be part of a plain CSS identifier body. + bool lookingAtIdentifierBody(); -#pragma clang diagnostic pop + // Consumes an identifier if its name exactly matches [text]. + bool scanIdentifier(const char* text); + bool scanIdentifier(sass::string text); -#endif - - void error(sass::string msg); - // generate message with given and expected sample - // text before and in the middle are configurable - void css_error(const sass::string& msg, - const sass::string& prefix = " after ", - const sass::string& middle = ", was: ", - const bool trim = true); - void read_bom(); - - Block_Obj parse(); - Import_Obj parse_import(); - Definition_Obj parse_definition(Definition::Type which_type); - Parameters_Obj parse_parameters(); - Parameter_Obj parse_parameter(); - Mixin_Call_Obj parse_include_directive(); - Arguments_Obj parse_arguments(); - Argument_Obj parse_argument(); - Assignment_Obj parse_assignment(); - StyleRuleObj parse_ruleset(Lookahead lookahead); - SelectorListObj parseSelectorList(bool chroot); - ComplexSelectorObj parseComplexSelector(bool chroot); - Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot); - CompoundSelectorObj parseCompoundSelector(); - SimpleSelectorObj parse_simple_selector(); - PseudoSelectorObj parse_negated_selector2(); - Expression* parse_binominal(); - SimpleSelectorObj parse_pseudo_selector(); - AttributeSelectorObj parse_attribute_selector(); - Block_Obj parse_block(bool is_root = false); - Block_Obj parse_css_block(bool is_root = false); - bool parse_block_nodes(bool is_root = false); - bool parse_block_node(bool is_root = false); - - Declaration_Obj parse_declaration(); - ExpressionObj parse_map(); - ExpressionObj parse_bracket_list(); - ExpressionObj parse_list(bool delayed = false); - ExpressionObj parse_comma_list(bool delayed = false); - ExpressionObj parse_space_list(); - ExpressionObj parse_disjunction(); - ExpressionObj parse_conjunction(); - ExpressionObj parse_relation(); - ExpressionObj parse_expression(); - ExpressionObj parse_operators(); - ExpressionObj parse_factor(); - ExpressionObj parse_value(); - Function_Call_Obj parse_calc_function(); - Function_Call_Obj parse_function_call(); - Function_Call_Obj parse_function_call_schema(); - String_Obj parse_url_function_string(); - String_Obj parse_url_function_argument(); - String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true); - String_Obj parse_string(); - ValueObj parse_static_value(); - String_Schema_Obj parse_css_variable_value(); - String_Obj parse_ie_property(); - String_Obj parse_ie_keyword_arg(); - String_Schema_Obj parse_value_schema(const char* stop); - String_Obj parse_identifier_schema(); - If_Obj parse_if_directive(bool else_if = false); - ForRuleObj parse_for_directive(); - EachRuleObj parse_each_directive(); - WhileRuleObj parse_while_directive(); - MediaRule_Obj parseMediaRule(); - sass::vector parseCssMediaQueries(); - sass::string parseIdentifier(); - CssMediaQuery_Obj parseCssMediaQuery(); - Return_Obj parse_return_directive(); - Content_Obj parse_content_directive(); - void parse_charset_directive(); - List_Obj parse_media_queries(); - Media_Query_Obj parse_media_query(); - Media_Query_ExpressionObj parse_media_expression(); - SupportsRuleObj parse_supports_directive(); - SupportsConditionObj parse_supports_condition(bool top_level); - SupportsConditionObj parse_supports_negation(); - SupportsConditionObj parse_supports_operator(bool top_level); - SupportsConditionObj parse_supports_interpolation(); - SupportsConditionObj parse_supports_declaration(); - SupportsConditionObj parse_supports_condition_in_parens(bool parens_required); - AtRootRuleObj parse_at_root_block(); - At_Root_Query_Obj parse_at_root_query(); - String_Schema_Obj parse_almost_any_value(); - AtRuleObj parse_directive(); - WarningRuleObj parse_warning(); - ErrorRuleObj parse_error(); - DebugRuleObj parse_debug(); - - Value* color_or_string(const sass::string& lexed) const; - - // be more like ruby sass - ExpressionObj lex_almost_any_value_token(); - ExpressionObj lex_almost_any_value_chars(); - ExpressionObj lex_interp_string(); - ExpressionObj lex_interp_uri(); - ExpressionObj lex_interpolation(); - - // these will throw errors - Token lex_variable(); - Token lex_identifier(); - - void parse_block_comments(bool store = true); - - Lookahead lookahead_for_value(const char* start = 0); - Lookahead lookahead_for_selector(const char* start = 0); - Lookahead lookahead_for_include(const char* start = 0); - - ExpressionObj fold_operands(ExpressionObj base, sass::vector& operands, Operand op); - ExpressionObj fold_operands(ExpressionObj base, sass::vector& operands, sass::vector& ops, size_t i = 0); - - void throw_syntax_error(sass::string message, size_t ln = 0); - void throw_read_error(sass::string message, size_t ln = 0); - - - template - ExpressionObj lex_interp() + // ToDo: make template to ignore return type + template + sass::string rawText(T(X::* consumer)()) { - if (lex < open >(false)) { - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - // std::cerr << "LEX [[" << sass::string(lexed) << "]]\n"; - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if (position[0] == '#' && position[1] == '{') { - ExpressionObj itpl = lex_interpolation(); - if (!itpl.isNull()) schema->append(itpl); - while (lex < close >(false)) { - // std::cerr << "LEX [[" << sass::string(lexed) << "]]\n"; - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if (position[0] == '#' && position[1] == '{') { - ExpressionObj itpl = lex_interpolation(); - if (!itpl.isNull()) schema->append(itpl); - } else { - return schema; - } - } - } else { - return SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - } - return {}; + const char* start = scanner.position; + // We need to clean up after ourself + // This has a chance to leak memory! + (static_cast(this)->*consumer)(); + return scanner.substring(start); } - public: - static Number* lexed_number(const SourceSpan& pstate, const sass::string& parsed); - static Number* lexed_dimension(const SourceSpan& pstate, const sass::string& parsed); - static Number* lexed_percentage(const SourceSpan& pstate, const sass::string& parsed); - static Value* lexed_hex_color(const SourceSpan& pstate, const sass::string& parsed); - private: - Number* lexed_number(const sass::string& parsed) { return lexed_number(pstate, parsed); }; - Number* lexed_dimension(const sass::string& parsed) { return lexed_dimension(pstate, parsed); }; - Number* lexed_percentage(const sass::string& parsed) { return lexed_percentage(pstate, parsed); }; - Value* lexed_hex_color(const sass::string& parsed) { return lexed_hex_color(pstate, parsed); }; - - static const char* re_attr_sensitive_close(const char* src); - static const char* re_attr_insensitive_close(const char* src); + // Consumes an identifier and asserts that its name exactly matches [text]. + void expectIdentifier(const char* text, sass::string name); + + // Throws an error associated with [span]. + void error(sass::string message, SourceSpan pstate /*, FileSpan span */); }; - size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } #endif diff --git a/src/parser/helper.hpp b/src/parser/helper.hpp new file mode 100644 index 0000000000..790def099d --- /dev/null +++ b/src/parser/helper.hpp @@ -0,0 +1,67 @@ +#ifndef SASS_PARSER_HELPER_H +#define SASS_PARSER_HELPER_H + +namespace Sass { + + namespace ParserHelper { + + + // Returns whether [character] is an ASCII newline. + inline bool isNewline(unsigned char character) + { + return character == 0x0A + || character == 0x0C + || character == 0x0D; + } + + // Returns whether [character] is a space or a tab character. + inline bool isSpaceOrTab(unsigned char character) { + return character == 0x20 + || character == 0x09; + } + + // Returns whether [character] is an ASCII whitespace character. + inline bool isWhitespace(unsigned char character) { + return isSpaceOrTab(character) + || isNewline(character); + } + + // Returns whether [character] is a letter or number. + inline bool isAlphanumeric(unsigned char character) { + return Util::ascii_isalnum(character); + } + + // Returns whether [character] is a letter. + inline bool isAlphabetic(unsigned char character) { + return Util::ascii_isalpha(character); + } + + // Returns whether [character] is a number. + inline bool isDigit(unsigned char character) { + return Util::ascii_isdigit(character); + } + + // Returns whether [character] is legal as the start of a Sass identifier. + inline bool isNameStart(uint32_t character) { + return character == '_' + || isAlphabetic(character) + || character >= 0x0080; + } + + // Returns whether [character] is legal in the body of a Sass identifier. + inline bool isName(unsigned char character) { + return isNameStart(character) + || isDigit(character) + || character == '-'; + } + + // Returns whether [character] is a hexadeicmal digit. + inline bool isHex(unsigned char character) { + return Util::ascii_isxdigit(character); + } + + } + +} + +#endif diff --git a/src/parser_at_root_query.cpp b/src/parser_at_root_query.cpp new file mode 100644 index 0000000000..d43d69d64f --- /dev/null +++ b/src/parser_at_root_query.cpp @@ -0,0 +1,55 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "parser_at_root_query.hpp" + +#include "character.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + AtRootQuery* AtRootQueryParser::parse() + { + Offset start(scanner.offset); + scanner.expectChar($lparen); + scanWhitespace(); + + bool include = scanIdentifier("with"); + if (!include) expectIdentifier("without", + "\"with\" or \"without\""); + + scanWhitespace(); + scanner.expectChar($colon); + scanWhitespace(); + + StringSet atRules; + do { + sass::string ident(readIdentifier()); + StringUtils::makeLowerCase(ident); + atRules.insert(ident); + scanWhitespace(); + } + while (lookingAtIdentifier()); + + scanner.expectChar($rparen); + scanner.expectDone(); + + return SASS_MEMORY_NEW(AtRootQuery, + scanner.rawSpanFrom(start), + std::move(atRules), + include); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/parser_at_root_query.hpp b/src/parser_at_root_query.hpp new file mode 100644 index 0000000000..bf16d95a5f --- /dev/null +++ b/src/parser_at_root_query.hpp @@ -0,0 +1,39 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_AT_ROOT_QUERY_HPP +#define SASS_PARSER_AT_ROOT_QUERY_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class AtRootQueryParser : public Parser + { + public: + + // Value constructor + AtRootQueryParser( + Compiler& context, + SourceDataObj source) : + Parser(context, source) + {} + + // Main entry function + AtRootQuery* parse(); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/parser_base.cpp b/src/parser_base.cpp new file mode 100644 index 0000000000..bf51ac744b --- /dev/null +++ b/src/parser_base.cpp @@ -0,0 +1,8 @@ +#include "parser_base.hpp" + +#include "character.hpp" +#include "utf8/checked.h" + +namespace Sass { + +} diff --git a/src/parser_base.hpp b/src/parser_base.hpp new file mode 100644 index 0000000000..627f12227f --- /dev/null +++ b/src/parser_base.hpp @@ -0,0 +1,36 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_BASE_HPP +#define SASS_PARSER_BASE_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class BaseParser : public Parser + { + public: + + // Value constructor + BaseParser( + Compiler& context, + SourceDataObj source) : + Parser(context, source) + {} + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/parser_css.cpp b/src/parser_css.cpp new file mode 100644 index 0000000000..c2eb331e2b --- /dev/null +++ b/src/parser_css.cpp @@ -0,0 +1,247 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "parser_css.hpp" + +#include "character.hpp" +#include "ast_expressions.hpp" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + // Consumes a plain-CSS `@import` rule that disallows + // interpolation. [start] should point before the `@`. + ImportRule* CssParser::readImportRule(Offset start) + { + uint8_t next = scanner.peekChar(); + ExpressionObj url; + if (next == $u || next == $U) { + url = readFunctionOrStringExpression(); + } + else { + StringExpressionObj ex(readInterpolatedString()); + InterpolationObj itpl(ex->getAsInterpolation()); + url = SASS_MEMORY_NEW(StringExpression, ex->pstate(), itpl); + } + scanWhitespace(); + Offset beforeQuery(scanner.offset); + auto queries(tryImportQueries()); + expectStatementSeparator("@import rule"); + SourceSpan span(scanner.relevantSpanFrom(beforeQuery)); + ImportRuleObj imp(SASS_MEMORY_NEW(ImportRule, span)); + StaticImportObj entry(SASS_MEMORY_NEW(StaticImport, span, + SASS_MEMORY_NEW(Interpolation, span, url), + queries.first, queries.second)); + entry->outOfOrder(false); + imp->append(entry); + return imp.detach(); + } + // EO readImportRule + + // Consume a silent comment and throws error + SilentComment* CssParser::readSilentComment() + { + Offset start(scanner.offset); + lastSilentComment = ScssParser::readSilentComment(); + error("Silent comments aren't allowed in plain CSS.", + scanner.relevantSpanFrom(start)); + return lastSilentComment.detach(); + } + // EO readSilentComment + + // Helper to declare all forbidden at-rules + bool isForbiddenCssAtRule(const sass::string& name) + { + return name == "at-root" + || name == "content" + || name == "debug" + || name == "each" + || name == "error" + || name == "extend" + || name == "for" + || name == "function" + || name == "if" + || name == "include" + || name == "mixin" + || name == "return" + || name == "warn" + || name == "while"; + } + // EO isForbiddenCssAtRule + + // Parse allowed at-rule statement and parse children via [child_parser] parser function + Statement* CssParser::readAtRule(Statement* (StylesheetParser::* child)(), bool root) + { + // NOTE: logic is largely duplicated in CssParser.atRule. + // Most changes here should be mirrored there. + + Offset start(scanner.offset); + scanner.expectChar($at); + InterpolationObj name = readInterpolatedIdentifier(); + scanWhitespace(); + + sass::string plain(name->getPlainString()); + + if (isForbiddenCssAtRule(plain)) { + InterpolationObj value = readAlmostAnyValue(); + error("This at-rule isn't allowed in plain CSS.", + scanner.relevantSpanFrom(start)); + } + else if (plain == "charset") { + sass::string charset(string()); + if (!root) { + error("This at-rule is not allowed here.", + scanner.relevantSpanFrom(start)); + } + } + else if (plain == "import") { + return readImportRule(start); + } + else if (plain == "media") { + return readMediaRule(start); + } + else if (plain == "-moz-document") { + return readMozDocumentRule(start, name); + } + else if (plain == "supports") { + return readSupportsRule(start); + } + else { + return readAnyAtRule(start, name); + } + + return nullptr; + } + // EO readAtRule + + // Helper to declare all forbidden functions + bool isDisallowedFunction(sass::string name) + { + return name == "red" + || name == "green" + || name == "blue" + || name == "mix" + || name == "hue" + || name == "saturation" + || name == "lightness" + || name == "adjust-hue" + || name == "lighten" + || name == "darken" + || name == "desaturate" + || name == "complement" + || name == "opacify" + || name == "fade-in" + || name == "transparentize" + || name == "fade-out" + || name == "adjust-color" + || name == "scale-color" + || name == "change-color" + || name == "ie-hex-str" + || name == "unquote" + || name == "quote" + || name == "str-length" + || name == "str-insert" + || name == "str-index" + || name == "str-slice" + || name == "to-upper-case" + || name == "to-lower-case" + || name == "percentage" + || name == "round" + || name == "ceil" + || name == "floor" + || name == "abs" + || name == "max" + || name == "min" + || name == "random" + || name == "length" + || name == "nth" + || name == "set-nth" + || name == "join" + || name == "append" + || name == "zip" + || name == "index" + || name == "list-separator" + || name == "is-bracketed" + || name == "map-get" + || name == "map-merge" + || name == "map-remove" + || name == "map-keys" + || name == "map-values" + || name == "map-has-key" + || name == "keywords" + || name == "selector-nest" + || name == "selector-append" + || name == "selector-extend" + || name == "selector-replace" + || name == "selector-unify" + || name == "is-superselector" + || name == "simple-selectors" + || name == "selector-parse" + || name == "feature-exists" + || name == "inspect" + || name == "type-of" + || name == "unit" + || name == "unitless" + || name == "comparable" + || name == "whiteness" + || name == "blackness" + || name == "if" + || name == "unique-id"; + } + // EO isDisallowedFunction + + // Consumes an expression that starts like an identifier. + Expression* CssParser::readIdentifierLike() + { + Offset start(scanner.offset); + InterpolationObj identifier = readInterpolatedIdentifier(); + sass::string plain(identifier->getPlainString()); + StringExpressionObj specialFunction = trySpecialFunction(plain, start); + + if (specialFunction != nullptr) { + return specialFunction; + } + + // Offset beforeArguments(scanner.offset); + + if (!scanner.scanChar($lparen)) { + return SASS_MEMORY_NEW(StringExpression, + scanner.rawSpanFrom(start), identifier); + } + + ExpressionVector arguments; + + if (!scanner.scanChar($rparen)) { + do { + scanWhitespace(); + Expression* argument = readExpression(false, true); + if (argument) arguments.emplace_back(argument); + scanWhitespace(); + } while (scanner.scanChar($comma)); + scanner.expectChar($rparen); + } + + // arguments->pstate(scanner.relevantSpan(start)); + + if (isDisallowedFunction(plain)) { + error( + "This function isn't allowed in plain CSS.", + scanner.relevantSpanFrom(start)); + } + + Interpolation* name = SASS_MEMORY_NEW(Interpolation, identifier->pstate()); + name->append(SASS_MEMORY_NEW(StringExpression, identifier->pstate(), identifier)); + + ArgumentInvocation* args = SASS_MEMORY_NEW(ArgumentInvocation, + scanner.rawSpanFrom(start), std::move(arguments), {}); + + return SASS_MEMORY_NEW(FunctionExpression, + scanner.rawSpanFrom(start), name, args); + + } + // EO readIdentifierLike + +} diff --git a/src/parser_css.hpp b/src/parser_css.hpp new file mode 100644 index 0000000000..439d66d5e2 --- /dev/null +++ b/src/parser_css.hpp @@ -0,0 +1,54 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_CSS_HPP +#define SASS_PARSER_CSS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser_scss.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssParser : public ScssParser + { + public: + + // Value constructor + CssParser( + Compiler& context, + SourceDataObj source) : + ScssParser(context, source) + {} + + protected: + + // Whether this is a plain CSS stylesheet. + bool plainCss() const override final { return true; } + + // Consumes a plain-CSS `@import` rule that disallows + // interpolation. [start] should point before the `@`. + ImportRule* readImportRule(Offset start); + + // Consumes an expression that starts like an identifier. + Expression* readIdentifierLike() override final; + + // Consume a silent comment and throws error + SilentComment* readSilentComment() override final; + + // Parse allowed at-rule statement and parse children via [child_parser] parser function + Statement* readAtRule(Statement* (StylesheetParser::* child_parser)(), bool root = false) override final; + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/parser_expression.cpp b/src/parser_expression.cpp new file mode 100644 index 0000000000..ed1174eb2e --- /dev/null +++ b/src/parser_expression.cpp @@ -0,0 +1,157 @@ +#include "parser_expression.hpp" + +#include "character.hpp" +#include "utf8/checked.h" +#include "ast_values.hpp" +#include "ast_expressions.hpp" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + void ExpressionParser::addOperator(SassOperator op, Offset& start) { + if (parser.plainCss() && op != SassOperator::DIV) { + parser.error("Operators aren't allowed in plain CSS.", + parser.scanner.relevantSpanFrom(start)); + /* , + position: scanner.position - operator.operator.length, + length : operator.operator.length */ + } + + allowSlash = allowSlash && op == SassOperator::DIV; + while ((!operators.empty()) && + (sass_op_to_precedence(operators.back()) + >= sass_op_to_precedence(op))) { + resolveOneOperation(); + } + operators.emplace_back(op); + + // We started parsing with an operator + if (singleExpression == nullptr) { + SourceSpan pstate(parser.scanner.relevantSpanFrom(start)); + singleExpression = SASS_MEMORY_NEW(NullExpression, + pstate, SASS_MEMORY_NEW(Null, pstate)); + } + + // assert(singleExpression != null); + + operands.emplace_back(singleExpression); + parser.scanWhitespace(); + allowSlash = allowSlash && parser.lookingAtNumber(); + singleExpression = parser.readSingleExpression(); + allowSlash = allowSlash && singleExpression->isaNumberExpression(); + } + + ExpressionParser::ExpressionParser(StylesheetParser& parser) : + start(parser.scanner.state()), + commaExpressions(), + singleEqualsOperand(), + spaceExpressions(), + operators(), + operands(), + allowSlash(false), + singleExpression(), + parser(parser) + { + allowSlash = parser.lookingAtNumber(); + singleExpression = parser.readSingleExpression(); + } + + void ExpressionParser::resetState() + { + commaExpressions.clear(); + spaceExpressions.clear(); + operators.clear(); + operands.clear(); + parser.scanner.backtrack(start); + allowSlash = parser.lookingAtNumber(); + singleExpression = parser.readSingleExpression(); + } + + void ExpressionParser::resolveOneOperation() + { + enum SassOperator op = operators.back(); + operators.pop_back(); + if (op != SassOperator::DIV) allowSlash = false; + if (allowSlash && parser.inParentheses) allowSlash = false; + Expression* lhs = operands.back(); + singleExpression = SASS_MEMORY_NEW(BinaryOpExpression, + SourceSpan::delta(lhs->pstate(), + singleExpression->pstate()), + op, lhs, singleExpression, + allowSlash); + operands.pop_back(); + } + + void ExpressionParser::resolveOperations() + { + if (operators.empty()) return; + while (!operators.empty()) { + resolveOneOperation(); + } + } + + void ExpressionParser::addSingleExpression(ExpressionObj expression, bool number) + { + if (singleExpression != nullptr) { + // If we discover we're parsing a list whose first element is a division + // operation, and we're in parentheses, re-parse outside of a parent + // context. This ensures that `(1/2 1)` doesn't perform division on its + // first element. + if (parser.inParentheses) { + parser.inParentheses = false; + if (allowSlash) { + resetState(); + return; + } + } + + resolveOperations(); + spaceExpressions.emplace_back(singleExpression); + allowSlash = number; + } + else if (!number) { + allowSlash = false; + } + singleExpression = expression; + + } + + void ExpressionParser::resolveSpaceExpressions() { + resolveOperations(); + + if (!spaceExpressions.empty()) { + spaceExpressions.emplace_back(singleExpression); + SourceSpan span = SourceSpan::delta( + spaceExpressions.front()->pstate(), + spaceExpressions.back()->pstate()); + ListExpression* list = SASS_MEMORY_NEW( + ListExpression, std::move(span), SASS_SPACE); + list->concat(std::move(spaceExpressions)); + singleExpression = list; + spaceExpressions.clear(); + } + + if (singleEqualsOperand && singleExpression) { + singleExpression = SASS_MEMORY_NEW(BinaryOpExpression, + SourceSpan::delta(singleEqualsOperand->pstate(), singleExpression->pstate()), + SassOperator::IESEQ, singleEqualsOperand, singleExpression); + singleEqualsOperand = {}; + + } + /* + // Seem to be for ms stuff + singleExpression = SASS_MEMORY_NEW(BinaryOpExpression, + "[pstateS2]", ) + + BinaryOperationExpression( + BinaryOperator.singleEquals, singleEqualsOperand, singleExpression); + singleEqualsOperand = null; + } + */ + + } + +} diff --git a/src/parser_expression.hpp b/src/parser_expression.hpp new file mode 100644 index 0000000000..a2fb76750b --- /dev/null +++ b/src/parser_expression.hpp @@ -0,0 +1,79 @@ +#ifndef SASS_PARSER_EXPRESSION_HPP +#define SASS_PARSER_EXPRESSION_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "scanner_string.hpp" +#include "parser_expression.hpp" +#include "parser_stylesheet.hpp" + +namespace Sass { + + // Helper class for stylesheet parser + class ExpressionParser final { + + friend class StylesheetParser; + friend class ScssParser; + friend class CssParser; + + public: + + // Value constructor + ExpressionParser( + StylesheetParser& parser); + + protected: + + StringScannerState start; + + ExpressionVector commaExpressions; + + ExpressionObj singleEqualsOperand; + + ExpressionVector spaceExpressions; + + // Operators whose right-hand operands are not fully parsed yet, in order of + // appearance in the document. Because a low-precedence operator will cause + // parsing to finish for all preceding higher-precedence operators, this is + // naturally ordered from lowest to highest precedence. + sass::vector operators; + + // The left-hand sides of [operators]. `operands[n]` + // is the left-hand side of `operators[n]`. + ExpressionVector operands; + + // Whether the single expression parsed so far + // may be interpreted as slash-separated numbers. + bool allowSlash = false; + + /// The leftmost expression that's been fully-parsed. Never `null`. + ExpressionObj singleExpression; + + // The associated parser + StylesheetParser& parser; + + // Resets the scanner state to the state it was at the + // beginning of the expression, except for [_inParentheses]. + void resetState(); + + void resolveOneOperation(); + + void resolveOperations(); + + void addSingleExpression( + ExpressionObj expression, + bool number = false); + + void addOperator( + SassOperator op, + Offset& start); + + void resolveSpaceExpressions(); + + }; + +} + +#endif diff --git a/src/parser_keyframe_selector.cpp b/src/parser_keyframe_selector.cpp new file mode 100644 index 0000000000..87ea171883 --- /dev/null +++ b/src/parser_keyframe_selector.cpp @@ -0,0 +1,92 @@ +#include "parser_keyframe_selector.hpp" + +#include "character.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + StringVector KeyframeSelectorParser::parse() { + + StringVector selectors; + do { + scanWhitespace(); + if (lookingAtIdentifier()) { + if (scanIdentifier("from")) { + selectors.emplace_back("from"); + } + else { + expectIdentifier("to", + "\"to\" or \"from\""); + selectors.emplace_back("to"); + } + } + else { + selectors.emplace_back(readPercentage()); + } + scanWhitespace(); + } + while (scanner.scanChar($comma)); + + scanner.expectDone(); + return selectors; + } + + sass::string KeyframeSelectorParser::readPercentage() + { + + StringBuffer buffer; + if (scanner.scanChar($plus)) { + buffer.writeCharCode($plus); + } + + uint8_t second = scanner.peekChar(); + if (!isDigit(second) && second != $dot) { + error("Expected number.", + scanner.rawSpan()); + } + + while (isDigit(scanner.peekChar())) { + buffer.writeCharCode(scanner.readChar()); + } + + if (scanner.peekChar() == $dot) { + buffer.writeCharCode(scanner.readChar()); + + while (isDigit(scanner.peekChar())) { + buffer.writeCharCode(scanner.readChar()); + } + } + + if (scanIdentifier("e")) { + buffer.write(scanner.readChar()); + uint8_t next = scanner.peekChar(); + if (next == $plus || next == $minus) buffer.write(scanner.readChar()); + if (!isDigit(scanner.peekChar())) { + error("Expected digit.", + scanner.rawSpan()); + } + + while (isDigit(scanner.peekChar())) { + buffer.writeCharCode(scanner.readChar()); + } + } + + scanner.expectChar($percent); + buffer.writeCharCode($percent); + return buffer.buffer; + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/parser_keyframe_selector.hpp b/src/parser_keyframe_selector.hpp new file mode 100644 index 0000000000..cc3daa3e39 --- /dev/null +++ b/src/parser_keyframe_selector.hpp @@ -0,0 +1,40 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_KEYFRAME_SELECTOR_HPP +#define SASS_PARSER_KEYFRAME_SELECTOR_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // A parser for `@keyframes` block selectors. + class KeyframeSelectorParser final : public Parser + { + public: + + KeyframeSelectorParser( + Compiler& context, + SourceDataObj source) : + Parser(context, source) + {} + + StringVector parse(); + + sass::string readPercentage(); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/parser_media_query.cpp b/src/parser_media_query.cpp new file mode 100644 index 0000000000..ba19dec110 --- /dev/null +++ b/src/parser_media_query.cpp @@ -0,0 +1,97 @@ +#include "parser_media_query.hpp" + +#include "ast_css.hpp" +#include "charcode.hpp" +#include "character.hpp" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + // Consume multiple media queries delimited by commas. + CssMediaQueryVector MediaQueryParser::parse() + { + CssMediaQueryVector queries; + do { + scanWhitespace(); + queries.emplace_back(readMediaQuery()); + } while (scanner.scanChar($comma)); + scanner.expectDone(); + return queries; + } + + // Consumes a single media query. + CssMediaQuery* MediaQueryParser::readMediaQuery() + { + // This is somewhat duplicated in StylesheetParser.readMediaQuery. + sass::string type; + sass::string modifier; + Offset start(scanner.offset); + if (scanner.peekChar() != $lparen) { + sass::string identifier1 = readIdentifier(); + scanWhitespace(); + + if (!lookingAtIdentifier()) { + // For example, "@media screen {" + return SASS_MEMORY_NEW(CssMediaQuery, + scanner.rawSpanFrom(start), + std::move(identifier1)); + } + + sass::string identifier2 = readIdentifier(); + scanWhitespace(); + + if (StringUtils::equalsIgnoreCase(identifier2, "and", 3)) { + // For example, "@media screen and ..." + type = identifier1; + } + else { + modifier = identifier1; + type = identifier2; + if (scanIdentifier("and")) { + // For example, "@media only screen and ..." + scanWhitespace(); + } + else { + // For example, "@media only screen {" + return SASS_MEMORY_NEW(CssMediaQuery, + scanner.rawSpanFrom(start), + std::move(type), + std::move(modifier)); + } + } + + } + + // We've consumed either `IDENTIFIER "and"`, `IDENTIFIER IDENTIFIER "and"`, + // or no text. + + StringVector features; + do { + scanWhitespace(); + scanner.expectChar($lparen); + auto decl = declarationValue(); + features.emplace_back("(" + decl + ")"); + scanner.expectChar($rparen); + scanWhitespace(); + } while (scanIdentifier("and")); + + if (type.empty()) { + return SASS_MEMORY_NEW(CssMediaQuery, + scanner.rawSpanFrom(start), + "", "", std::move(features)); + } + else { + return SASS_MEMORY_NEW(CssMediaQuery, + scanner.rawSpanFrom(start), + std::move(type), + std::move(modifier), + std::move(features)); + } + + } + // EO readMediaQuery + +} diff --git a/src/parser_media_query.hpp b/src/parser_media_query.hpp new file mode 100644 index 0000000000..f467dc9cc6 --- /dev/null +++ b/src/parser_media_query.hpp @@ -0,0 +1,44 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_MEDIA_QUERY_HPP +#define SASS_PARSER_MEDIA_QUERY_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class MediaQueryParser final : public Parser + { + public: + + // Value constructor + MediaQueryParser( + Compiler& context, + SourceDataObj source) : + Parser(context, source) + {} + + // Consume multiple media queries delimited by commas. + CssMediaQueryVector parse(); + + private: + + // Consumes a single media query. + CssMediaQuery* readMediaQuery(); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/parser_sass.cpp b/src/parser_sass.cpp new file mode 100644 index 0000000000..d56a6098a8 --- /dev/null +++ b/src/parser_sass.cpp @@ -0,0 +1,499 @@ +#include "parser_sass.hpp" + +#include "character.hpp" +#include "utf8/checked.h" +#include "interpolation.hpp" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + Interpolation* SassParser::styleRuleSelector() + { + Offset start(scanner.offset); + InterpolationBuffer buffer(scanner); + do { + buffer.addInterpolation(readAlmostAnyValue()); + buffer.writeCharCode($lf); + } while (buffer.trailingStringEndsWith(",") && + scanCharIf(isNewline)); + + return buffer.getInterpolation(scanner.rawSpanFrom(start)); + } + + void SassParser::expectStatementSeparator(sass::string name) { + if (!atEndOfStatement()) expectNewline(); + if (peekIndentation() <= currentIndentation) return; + sass::sstream strm; + strm << "Nothing may be indented "; + if (name.empty()) { strm << "here."; } + else { strm << "beneath a " + name + "."; } + scanWhitespaceWithoutComments(); + error(strm.str(), scanner.rawSpan()); + + /*, + position: nextIndentationEnd.position*/ + } + + bool SassParser::atEndOfStatement() + { + uint8_t next; + if (scanner.peekChar(next)) { + return isNewline(next); + } + return true; + } + + bool SassParser::lookingAtChildren() + { + return atEndOfStatement() && + peekIndentation() > currentIndentation; + } + + void SassParser::scanImportArgument(ImportRule* rule) + { + uint8_t next = scanner.peekChar(); + StringScannerState state(scanner.state()); + switch (next) { + case $u: + case $U: + if (scanIdentifier("url")) { + if (scanner.scanChar($lparen)) { + scanner.backtrack(state); + StylesheetParser::scanImportArgument(rule); + return; + } + else { + scanner.backtrack(state); + } + } + break; + + case $single_quote: + case $double_quote: + StylesheetParser::scanImportArgument(rule); + return; + } + + Offset start(scanner.offset); + StringScannerState state2(scanner.state()); + while (scanner.peekChar(next) && + next != $comma && + next != $semicolon && + !isNewline(next)) { + scanner.readChar(); + next = scanner.peekChar(); + } + + sass::string url = scanner.substring(state2.position); + + if (isPlainImportUrl(url)) { + InterpolationObj itpl = SASS_MEMORY_NEW( + Interpolation, scanner.relevantSpanFrom(start)); + auto str = SASS_MEMORY_NEW(String, + scanner.relevantSpanFrom(start), + std::move(url), true); + // Must be an easier way to get quotes? + str->value(str->inspect()); + itpl->append(str); + rule->append(SASS_MEMORY_NEW(StaticImport, + scanner.relevantSpanFrom(start), itpl)); + } + else { + + resolveDynamicImport(rule, start, url); + + } + + } + + bool SassParser::scanElse(size_t ifIndentation) + { + if (peekIndentation() != ifIndentation) return false; + StringScannerState state(scanner.state()); + size_t startIndentation = currentIndentation; + size_t startNextIndentation = nextIndentation; + StringScannerState startNextIndentationEnd = nextIndentationEnd; + + readIndentation(); + if (scanner.scanChar($at) && scanIdentifier("else")) return true; + + scanner.backtrack(state); + currentIndentation = startIndentation; + nextIndentation = startNextIndentation; + nextIndentationEnd = startNextIndentationEnd; + return false; + } + + StatementVector SassParser::readChildren(Statement* (StylesheetParser::* parser)()) + { + StatementVector children; + whileIndentedLower(parser, children); + return children; + } + + StatementVector SassParser::readStatements(Statement* (StylesheetParser::* parser)()) + { + uint8_t first = scanner.peekChar(); + if (first == $tab || first == $space) { + error("Indenting at the beginning of the document is illegal.", + scanner.rawSpan()); + /*position: 0, length : scanner.position*/ + } + + StatementVector statements; + while (!scanner.isDone()) { + Statement* child = parseChild(parser); + if (child != nullptr) statements.emplace_back(child); + size_t indentation = readIndentation(); + if (indentation != 0) { + error( + "Inconsistent indentation, expected 0 spaces.", + scanner.rawSpan()); + } + } + return statements; + } + + Statement* SassParser::parseChild(Statement* (StylesheetParser::* child)()) + { + switch (scanner.peekChar()) { + // Ignore empty lines. + case $cr: + case $lf: + case $ff: + return nullptr; + + case $dollar: + return readVariableDeclaration(); + break; + + case $slash: + switch (scanner.peekChar(1)) { + case $slash: + lastSilentComment = readSilentComment(); + return lastSilentComment.ptr(); + break; + case $asterisk: + return loudComment(); + break; + default: + return (this->*child)(); + break; + } + break; + + default: + return (this->*child)(); + break; + } + } + + SilentComment* SassParser::readSilentComment() + { + Offset start(scanner.offset); + scanner.expect("//"); + StringBuffer buffer; + size_t parentIndentation = currentIndentation; + + do { + sass::string commentPrefix = scanner.scanChar($slash) ? "///" : "//"; + + while (true) { + buffer.write(commentPrefix); + + // Skip the initial characters because we're already writing the + // slashes. + for (size_t i = commentPrefix.length(); + i < currentIndentation - parentIndentation; + i++) { + buffer.writeCharCode($space); + } + + while (!scanner.isDone() && !isNewline(scanner.peekChar())) { + buffer.writeCharCode(scanner.readChar()); + } + buffer.write("\n"); + + if (peekIndentation() < parentIndentation) goto endOfLoop; + + if (peekIndentation() == parentIndentation) { + // Look ahead to the next line to see if it starts another comment. + if (scanner.peekChar(1 + parentIndentation) == $slash && + scanner.peekChar(2 + parentIndentation) == $slash) { + readIndentation(); + } + break; + } + readIndentation(); + } + } while (scanner.scan("//")); + + endOfLoop: + + lastSilentComment = SASS_MEMORY_NEW(SilentComment, + scanner.rawSpanFrom(start), std::move(buffer.buffer)); + return lastSilentComment; + } + + LoudComment* SassParser::loudComment() + { + Offset start(scanner.offset); + scanner.expect("/*"); + + bool first = true; + InterpolationBuffer buffer(scanner); + buffer.write("/*"); + size_t parentIndentation = currentIndentation; + while (true) { + if (first) { + // If the first line is empty, ignore it. + const char* beginningOfComment = scanner.position; + scanSpaces(); + if (isNewline(scanner.peekChar())) { + readIndentation(); + buffer.writeCharCode($space); + } + else { + buffer.write(scanner.substring(beginningOfComment)); + } + } + else { + buffer.write("\n"); + buffer.write(" * "); + } + first = false; + + for (size_t i = 3; i < currentIndentation - parentIndentation; i++) { + buffer.writeCharCode($space); + } + + while (!scanner.isDone()) { + uint8_t next = scanner.peekChar(); + switch (next) { + case $lf: + case $cr: + case $ff: + goto endOfLoop; + + case $hash: + if (scanner.peekChar(1) == $lbrace) { + buffer.add(readSingleInterpolation()); + } + else { + buffer.writeCharCode(scanner.readChar()); + } + break; + + default: + buffer.writeCharCode(scanner.readChar()); + break; + } + } + + endOfLoop: + + if (peekIndentation() <= parentIndentation) break; + + // Preserve empty lines. + while (lookingAtDoubleNewline()) { + expectNewline(); + buffer.write("\n"); + buffer.write(" *"); + } + + readIndentation(); + } + + if (!buffer.trailingStringEndsWith("*/")) { + buffer.write(" */"); + } + + SourceSpan pstate(scanner.rawSpanFrom(start)); + InterpolationObj itpl = buffer.getInterpolation(pstate); + return SASS_MEMORY_NEW(LoudComment, std::move(pstate), itpl); + } + + void SassParser::scanWhitespace() + { + // This overrides whitespace consumption so that + // it doesn't consume newlines or loud comments. + while (!scanner.isDone()) { + uint8_t next = scanner.peekChar(); + if (next != $tab && next != $space) break; + scanner.readChar(); + } + + if (scanner.peekChar() == $slash && scanner.peekChar(1) == $slash) { + lastSilentComment = readSilentComment(); + } + } + + void SassParser::expectNewline() + { + switch (scanner.peekChar()) { + case $semicolon: + error("semicolons aren't allowed in the indented syntax.", + scanner.rawSpan()); + return; + case $cr: + scanner.readChar(); + if (scanner.peekChar() == $lf) scanner.readChar(); + return; + case $lf: + case $ff: + scanner.readChar(); + return; + default: + error("expected newline.", + scanner.rawSpan()); + } + } + + bool SassParser::lookingAtDoubleNewline() + { + uint8_t next = scanner.peekChar(); + uint8_t nextChar = scanner.peekChar(1); + switch (next) { + case $cr: + if (nextChar == $lf) return isNewline(scanner.peekChar(2)); + return nextChar == $cr || nextChar == $ff; + case $lf: + case $ff: + return isNewline(scanner.peekChar(1)); + default: + return false; + } + } + + void SassParser::whileIndentedLower(Statement* (StylesheetParser::* child)(), StatementVector& children) + { + size_t parentIndentation = currentIndentation; + size_t childIndentation = sass::string::npos; + while (peekIndentation() > parentIndentation) { + size_t indentation = readIndentation(); + if (childIndentation == sass::string::npos) { + childIndentation = indentation; + } + if (childIndentation != indentation) { + sass::sstream msg; + msg << "Inconsistent indentation, expected " + << childIndentation << " spaces."; + error(msg.str(), + scanner.rawSpan()); + + /*, + position: scanner.position - scanner.column, + length : scanner.column*/ + } + children.emplace_back(parseChild(child)); + } + + } + + size_t SassParser::readIndentation() + { + if (nextIndentation == sass::string::npos) { + peekIndentation(); + } + currentIndentation = nextIndentation; + scanner.backtrack(nextIndentationEnd); + nextIndentation = sass::string::npos; + // What does this mean, where is it used? + // nextIndentationEnd = null; ToDo + return currentIndentation; + + } + + size_t SassParser::peekIndentation() + { + if (nextIndentation != sass::string::npos) { + return nextIndentation; + } + + if (scanner.isDone()) { + nextIndentation = 0; + nextIndentationEnd = scanner.state(); + return 0; + } + + StringScannerState start = scanner.state(); + if (!scanCharIf(isNewline)) { + error("Expected newline.", + scanner.rawSpan()); + /* position: scanner.position*/ + } + + bool containsTab; + bool containsSpace; + do { + containsTab = false; + containsSpace = false; + nextIndentation = 0; + + while (true) { + uint8_t next = scanner.peekChar(); + if (next == $space) { + containsSpace = true; + } + else if (next == $tab) { + containsTab = true; + } + else { + break; + } + nextIndentation++; + scanner.readChar(); + } + + if (scanner.isDone()) { + nextIndentation = 0; + nextIndentationEnd = scanner.state(); + scanner.backtrack(start); + return 0; + } + } while (scanCharIf(isNewline)); + + checkIndentationConsistency(containsTab, containsSpace); + + if (nextIndentation > 0) { + if (indentType == SassIndentType::AUTO) { + indentType = containsSpace ? + SassIndentType::SPACES : + SassIndentType::TABS; + } + } + nextIndentationEnd = scanner.state(); + scanner.backtrack(start); + return nextIndentation; + } + + // Ensures that the document uses consistent characters for indentation. + // The [containsTab] and [containsSpace] parameters refer to + // a single line of indentation that has just been parsed. + void SassParser::checkIndentationConsistency(bool containsTab, bool containsSpace) + { + if (containsTab) { + if (containsSpace) { + error("Tabs and spaces may not be mixed.", + scanner.rawSpan()); + /* position: scanner.position - scanner.column, + length : scanner.column*/ + } + else if (useSpaceIndentation()) { + error("Expected spaces, was tabs.", + scanner.rawSpan()); + /* position: scanner.position - scanner.column, + length : scanner.column*/ + } + } + else if (containsSpace && useTabIndentation()) { + error("Expected tabs, was spaces.", + scanner.rawSpan()); + /* position: scanner.position - scanner.column, length : scanner.column*/ + } + } + +} diff --git a/src/parser_sass.hpp b/src/parser_sass.hpp new file mode 100644 index 0000000000..413a871a63 --- /dev/null +++ b/src/parser_sass.hpp @@ -0,0 +1,150 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_SASS_HPP +#define SASS_PARSER_SASS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser_stylesheet.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class SassParser final : public StylesheetParser + { + public: + + enum SassIndentType { + AUTO, TABS, SPACES, + }; + + SassParser( + Compiler& context, + SourceDataObj source) : + StylesheetParser( + context, source), + currentIndentation(0), + nextIndentation(NPOS), + nextIndentationEnd({ + source->content(), + Offset() }), + indentType(SassIndentType::AUTO) + {} + + protected: + + // The current indentation level + size_t currentIndentation = 0; + + // The indentation level of the next source line after the scanner's + // position, or `null` if that hasn't been computed yet. + // A source line is any line that's not entirely whitespace. + size_t nextIndentation; + + // The beginning of the next source line after the scanner's + // position, or `null` if that hasn't been computed yet. + // A source line is any line that's not entirely whitespace. + StringScannerState nextIndentationEnd; + + // Whether the document is indented using spaces or tabs. + // If this is `true`, the document is indented using spaces. If it's `false`, + // the document is indented using tabs. If it's `null`, we haven't yet seen + // the indentation character used by the document. + SassIndentType indentType = SassIndentType::AUTO; + + // Some helper function to do the most generic queries + bool useTabIndentation() { return indentType == SassIndentType::TABS; } + bool useSpaceIndentation() { return indentType == SassIndentType::SPACES; } + + // Whether this is a plain CSS stylesheet. + bool plainCss() const override final { return false; } + + // Whether this is parsing the indented syntax. + bool isIndented() const override final { return true; }; + + // Parses and returns a selector used in a style rule. + Interpolation* styleRuleSelector() override final; + + // Asserts that the scanner is positioned before a statement separator, + // or at the end of a list of statements. If the [name] of the parent + // rule is passed, it's used for error reporting. This consumes + // whitespace, but nothing else, including comments. + void expectStatementSeparator(sass::string name) override final; + + // Whether the scanner is positioned at the end of a statement. + bool atEndOfStatement() override final; + + // Whether the scanner is positioned before a block of + // children that can be parsed with [children]. + bool lookingAtChildren() override final; + + // Consumes an argument to an `@import` rule. + // If anything is found it will be added to [rule]. + void scanImportArgument(ImportRule* rule) override final; + + // Tries to scan an `@else` rule after an `@if` block. Returns whether + // that succeeded. This should just scan the rule name, not anything + // afterwards. [ifIndentation] is the result of [currentIndentation] + // from before the corresponding `@if` was parsed. + bool scanElse(size_t ifIndentation) override final; + + // Consumes a block of child statements. Unlike most production consumers, + // this does *not* consume trailing whitespace. This is necessary to ensure + // that the source span for the parent rule doesn't cover whitespace after the rule. + StatementVector readChildren(Statement* (StylesheetParser::* child)()) override final; + + // Consumes top-level statements. The [statement] callback may return `nullptr`, + // indicating that a statement was consumed that shouldn't be added to the AST. + StatementVector readStatements(Statement* (StylesheetParser::* statement)()) override final; + + // Consumes a child of the current statement. This consumes + // children that are allowed at all levels of the document; + // the [child] parameter is called to consume any children + // that are specifically allowed in the caller's context. + Statement* parseChild(Statement* (StylesheetParser::* statement)()); + + // Consumes an indented-style silent comment. + SilentComment* readSilentComment() override final; + + // Consumes an indented-style loud context. + // This overrides loud comment consumption so + // that it doesn't consume multi-line comments. + LoudComment* loudComment(); + + void scanWhitespace() override final; + + // Expect and consume a single newline character. + void expectNewline(); + + // Returns whether the scanner is immediately before *two* newlines. + bool lookingAtDoubleNewline(); + + // As long as the scanner's position is indented beneath the + // starting line, runs [body] to consume the next statement. + void whileIndentedLower(Statement* (StylesheetParser::* child)(), StatementVector& children); + + // Consumes indentation whitespace and returns + // the indentation level of the next line. + size_t readIndentation(); + + // Returns the indentation level of the next line. + size_t peekIndentation(); + + // Ensures that the document uses consistent characters for indentation. + // The [containsTab] and [containsSpace] parameters refer to + // a single line of indentation that has just been parsed. + void checkIndentationConsistency(bool containsTab, bool containsSpace); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/parser_scss.cpp b/src/parser_scss.cpp new file mode 100644 index 0000000000..50b494a392 --- /dev/null +++ b/src/parser_scss.cpp @@ -0,0 +1,259 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "parser_scss.hpp" + +#include "character.hpp" +#include "utf8/checked.h" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Parses and returns a selector used in a style rule. + Interpolation* ScssParser::styleRuleSelector() + { + return readAlmostAnyValue(); + } + // EO styleRuleSelector + + // Asserts that the scanner is positioned before a statement separator, + // or at the end of a list of statements. If the [name] of the parent + // rule is passed, it's used for error reporting. This consumes + // whitespace, but nothing else, including comments. + void ScssParser::expectStatementSeparator(sass::string name) + { + scanWhitespaceWithoutComments(); + if (scanner.isDone()) return; + uint8_t next = scanner.peekChar(); + if (next == $semicolon || next == $rbrace) return; + scanner.expectChar($semicolon); + } + // EO expectStatementSeparator + + // Whether the scanner is positioned at the end of a statement. + bool ScssParser::atEndOfStatement() + { + if (scanner.isDone()) return true; + uint8_t next = scanner.peekChar(); + return next == $semicolon + || next == $rbrace + || next == $lbrace; + } + // EO atEndOfStatement + + // Whether the scanner is positioned before a block of + // children that can be parsed with [children]. + bool ScssParser::lookingAtChildren() + { + return scanner.peekChar() == $lbrace; + } + // EO lookingAtChildren + + // Tries to scan an `@else` rule after an `@if` block, and returns whether + // that succeeded. This should just scan the rule name, not anything + // afterwards. [ifIndentation] is the result of [currentIndentation] + // from before the corresponding `@if` was parsed. + bool ScssParser::scanElse(size_t) + { + StringScannerState start = scanner.state(); + scanWhitespace(); + // StringScannerState beforeAt = scanner.state(); + if (scanner.scanChar($at)) { + if (scanIdentifier("else")) return true; + if (scanIdentifier("elseif")) { + /* + logger123.warn( + "@elseif is deprecated and will not be supported in future Sass versions.\n" + "Use "@else if" instead.", + span: scanner.spanFrom(beforeAt), + deprecation : true); + */ + scanner.offset.column -= 2; + scanner.position -= 2; + return true; + } + } + scanner.backtrack(start); + return false; + } + // EO scanElse + + // Consumes a block of child statements. Unlike most production consumers, + // this does *not* consume trailing whitespace. This is necessary to ensure + // that the source span for the parent rule doesn't cover whitespace after the rule. + StatementVector ScssParser::readChildren( + Statement* (StylesheetParser::* child)()) + { + scanner.expectChar($lbrace); + scanWhitespaceWithoutComments(); + StatementVector children; + while (true) { + switch (scanner.peekChar()) { + case $dollar: + children.emplace_back(readVariableDeclaration()); + break; + + case $slash: + switch (scanner.peekChar(1)) { + case $slash: + lastSilentComment = readSilentComment(); + scanWhitespaceWithoutComments(); + break; + case $asterisk: + children.emplace_back(loudComment()); + scanWhitespaceWithoutComments(); + break; + default: + children.emplace_back((this->*child)()); + break; + } + break; + + case $semicolon: + scanner.readChar(); + scanWhitespaceWithoutComments(); + break; + + case $rbrace: + scanner.expectChar($rbrace); + return children; + + default: + children.emplace_back((this->*child)()); + break; + } + } + } + // EO children + + // Consumes top-level statements. The [statement] callback may return `null`, + // indicating that a statement was consumed that shouldn't be added to the AST. + StatementVector ScssParser::readStatements( + Statement* (StylesheetParser::* statement)()) + { + scanWhitespaceWithoutComments(); + StatementVector statements; + while (!scanner.isDone()) { + switch (scanner.peekChar()) { + case $dollar: + statements.emplace_back(readVariableDeclaration()); + break; + + case $slash: + switch (scanner.peekChar(1)) { + case $slash: + lastSilentComment = readSilentComment(); + scanWhitespaceWithoutComments(); + break; + case $asterisk: + statements.emplace_back(loudComment()); + scanWhitespaceWithoutComments(); + break; + default: + StatementObj child = (this->*statement)(); + if (child != nullptr) statements.emplace_back(child); + break; + } + break; + + case $semicolon: + scanner.readChar(); + scanWhitespaceWithoutComments(); + break; + + default: + StatementObj child = (this->*statement)(); + if (child != nullptr) statements.emplace_back(child); + break; + } + } + return statements; + } + // EO statements + + // Consumes a statement-level silent comment block. + SilentComment* ScssParser::readSilentComment() + { + StringScannerState start = scanner.state(); + scanner.expect("//"); + + do { + while (!scanner.isDone() && + !isNewline(scanner.readChar())) {} + if (scanner.isDone()) break; + scanWhitespaceWithoutComments(); + } + while (scanner.scan("//")); + + if (plainCss()) { + error("Silent comments aren't allowed in plain CSS.", + scanner.relevantSpanFrom(start.offset)); + } + + return SASS_MEMORY_NEW(SilentComment, scanner.rawSpanFrom(start.offset), + scanner.substring(start.position, scanner.position)); + } + // EO readSilentComment + + // Consumes a statement-level loud comment block. + LoudComment* ScssParser::loudComment() + { + InterpolationBuffer buffer(scanner); + Offset start(scanner.offset); + scanner.expect("/*"); + buffer.write("/*"); + while (true) { + switch (scanner.peekChar()) { + case $hash: + if (scanner.peekChar(1) == $lbrace) { + buffer.add(readSingleInterpolation()); + } + else { + buffer.write(scanner.readChar()); + } + break; + + case $asterisk: + buffer.write(scanner.readChar()); + if (scanner.peekChar() != $slash) break; + buffer.write(scanner.readChar()); + return SASS_MEMORY_NEW(LoudComment, + scanner.rawSpanFrom(start), + buffer.getInterpolation( + scanner.rawSpanFrom(start))); + + case $cr: + scanner.readChar(); + if (scanner.peekChar() != $lf) { + buffer.write($lf); + } + break; + + case $ff: + scanner.readChar(); + buffer.write($lf); + break; + + default: + buffer.write(scanner.readChar()); + break; + } + } + + return nullptr; + } + // EO loudComment + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/parser_scss.hpp b/src/parser_scss.hpp new file mode 100644 index 0000000000..1820796373 --- /dev/null +++ b/src/parser_scss.hpp @@ -0,0 +1,93 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_SCSS_HPP +#define SASS_PARSER_SCSS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser_stylesheet.hpp" + +namespace Sass { + + class ScssParser : public StylesheetParser + { + public: + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ScssParser( + Compiler& context, + SourceDataObj source) : + StylesheetParser( + context, source) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // So far we are not parsing css + virtual bool plainCss() const override { + return false; + } + + // We are sure scss is not indented syntax + bool isIndented() const override final { + return false; + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + protected: + + // Parses and returns a selector used in a style rule. + virtual Interpolation* styleRuleSelector() override; + + // Asserts that the scanner is positioned before a statement separator, + // or at the end of a list of statements. If the [name] of the parent + // rule is passed, it's used for error reporting. This consumes + // whitespace, but nothing else, including comments. + virtual void expectStatementSeparator(sass::string name) override; + + // Whether the scanner is positioned at the end of a statement. + virtual bool atEndOfStatement() override; + + // Whether the scanner is positioned before a block of + // children that can be parsed with [children]. + virtual bool lookingAtChildren() override; + + // Tries to scan an `@else` rule after an `@if` block, and returns whether + // that succeeded. This should just scan the rule name, not anything + // afterwards. [ifIndentation] is the result of [currentIndentation] + // from before the corresponding `@if` was parsed. + virtual bool scanElse(size_t ifIndentation) override; + + // Consumes a block of child statements. Unlike most production consumers, + // this does *not* consume trailing whitespace. This is necessary to ensure + // that the source span for the parent rule doesn't cover whitespace after the rule. + virtual StatementVector readChildren( + Statement* (StylesheetParser::* child)()) override; + + // Consumes top-level statements. The [statement] callback may return `null`, + // indicating that a statement was consumed that shouldn't be added to the AST. + virtual StatementVector readStatements( + Statement* (StylesheetParser::* statement)()) override; + + // Consumes a statement-level silent comment block. + virtual SilentComment* readSilentComment() override; + + // Consumes a statement-level loud comment block. + virtual LoudComment* loudComment(); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + }; + +} + +#endif diff --git a/src/parser_selector.cpp b/src/parser_selector.cpp new file mode 100644 index 0000000000..44d1aedc26 --- /dev/null +++ b/src/parser_selector.cpp @@ -0,0 +1,555 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "parser_selector.hpp" + +#include "charcode.hpp" +#include "character.hpp" +#include "ast_selectors.hpp" +#include "scanner_string.hpp" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + // Parse content into selector list + // Throws if not everything is consumed + SelectorList* SelectorParser::parseSelectorList() + { + SelectorListObj selector(readSelectorList()); + if (!scanner.isDone()) { + error("expected selector.", + scanner.rawSpan()); + } + return selector.detach(); + } + + // Parse content into compound selector + // Throws if not everything is consumed + CompoundSelector* SelectorParser::parseCompoundSelector() + { + CompoundSelectorObj compound(readCompoundSelector()); + if (!scanner.isDone()) { + error("expected selector.", + scanner.rawSpan()); + } + return compound.detach(); + } + + // Parse content into simple selector + // Throws if not everything is consumed + SimpleSelector* SelectorParser::parseSimpleSelector() + { + SimpleSelectorObj simple(readSimpleSelector(allowParent)); + if (!scanner.isDone()) { + error("unexpected token.", + scanner.relevantSpan()); + } + return simple.detach(); + } + + // Consumes a selector list. + SelectorList* SelectorParser::readSelectorList() + { + Offset start(scanner.offset); + const char* previousLine = scanner.position; + sass::vector items; + items.emplace_back(readComplexSelector()); + + scanWhitespace(); + while (scanner.scanChar($comma)) { + scanWhitespace(); + uint8_t next = scanner.peekChar(); + if (next == $comma) continue; + if (scanner.isDone()) break; + + bool lineBreak = scanner.hasLineBreak(previousLine); // ToDo + // var lineBreak = scanner.line != previousLine; + // if (lineBreak) previousLine = scanner.line; + items.emplace_back(readComplexSelector(lineBreak)); + } + + return SASS_MEMORY_NEW(SelectorList, + scanner.relevantSpanFrom(start), std::move(items)); + } + + // Consumes a complex selector. + ComplexSelector* SelectorParser::readComplexSelector(bool lineBreak) + { + + uint8_t next; + Offset start(scanner.offset); + SelectorComponentVector complex; + + while (true) { + scanWhitespace(); + + Offset before(scanner.offset); + if (!scanner.peekChar(next)) { + goto endOfLoop; + } + switch (next) { + case $plus: + scanner.readChar(); + complex.emplace_back(SASS_MEMORY_NEW(SelectorCombinator, + scanner.rawSpanFrom(before), SelectorCombinator::ADJACENT)); + break; + + case $gt: + scanner.readChar(); + complex.emplace_back(SASS_MEMORY_NEW(SelectorCombinator, + scanner.rawSpanFrom(before), SelectorCombinator::CHILD)); + break; + + case $tilde: + scanner.readChar(); + complex.emplace_back(SASS_MEMORY_NEW(SelectorCombinator, + scanner.rawSpanFrom(before), SelectorCombinator::GENERAL)); + break; + + case $lbracket: + case $dot: + case $hash: + case $percent: + case $colon: + case $ampersand: + case $asterisk: + case $pipe: + complex.emplace_back(readCompoundSelector()); + if (scanner.peekChar() == $ampersand) { + error( + "\"&\" may only used at the beginning of a compound selector.", + scanner.rawSpan()); + } + break; + + default: + if (!lookingAtIdentifier()) goto endOfLoop; + complex.emplace_back(readCompoundSelector()); + if (scanner.peekChar() == $ampersand) { + error( + "\"&\" may only used at the beginning of a compound selector.", + scanner.rawSpan()); + } + break; + } + } + + endOfLoop: + + if (complex.empty()) { + error("expected selector.", + scanner.rawSpan()); + } + + ComplexSelector* selector = SASS_MEMORY_NEW(ComplexSelector, + scanner.rawSpanFrom(start), std::move(complex)); + selector->hasPreLineFeed(lineBreak); + return selector; + + } + // EO readComplexSelector + + // Consumes a compound selector. + CompoundSelector* SelectorParser::readCompoundSelector() + { + // Note: libsass uses a flag on the compound selector to + // signal that it contains a real parent reference. + // dart-sass uses ParentSelector with a suffix. + Offset start(scanner.offset); + CompoundSelectorObj compound = SASS_MEMORY_NEW(CompoundSelector, + scanner.relevantSpan()); + + if (scanner.scanChar($amp)) { + if (!allowParent) { + error( + "Parent selectors aren't allowed here.", + scanner.rawSpanFrom(start)); + } + compound->withExplicitParent(true); + if (lookingAtIdentifierBody()) { + Offset before(scanner.offset); + sass::string body(identifierBody()); + SimpleSelectorObj simple = SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(before), std::move(body), "", false); + if (!simple.isNull()) compound->append(simple); + } + } + else { + SimpleSelectorObj simple = readSimpleSelector(false); + if (!simple.isNull()) compound->append(simple); + } + + while (isSimpleSelectorStart(scanner.peekChar())) { + SimpleSelectorObj simple = readSimpleSelector(false); + if (!simple.isNull()) compound->append(simple); + } + + compound->pstate(scanner.rawSpanFrom(start)); + return compound.detach(); + + } + // EO readCompoundSelector + + // Consumes a simple selector. + SimpleSelector* SelectorParser::readSimpleSelector(bool allowParent) + { + + Offset start(scanner.offset); + uint8_t next = scanner.peekChar(); + if (next == $lbracket) { + return readAttributeSelector(); + } + else if (next == $dot) { + return readClassSelector(); + } + else if (next == $hash) { + return readIdSelector(); + } + else if (next == $percent) + { + PlaceholderSelectorObj selector(readPlaceholderSelector()); + if (!allowPlaceholder) { + error("Placeholder selectors aren't allowed here.", + scanner.rawSpanFrom(start)); + } + return selector.detach(); + } + else if (next == $colon) { + return readPseudoSelector(); + } + else if (next == $ampersand) + { + if (!allowParent) { + error( + "Parent selectors aren't allowed here.", + scanner.rawSpanFrom(start)); + } + return {}; + } + else { + return readTypeOrUniversalSelector(); + } + + } + // EO readSimpleSelector + + // Consumes an attribute selector. + AttributeSelector* SelectorParser::readAttributeSelector() + { + + scanner.expectChar($lbracket); + + scanWhitespace(); + Offset start(scanner.offset); + struct QualifiedName name(readAttributeName()); + SourceSpan span(scanner.relevantSpanFrom(start)); + scanWhitespace(); + + if (scanner.scanChar($rbracket)) { + return SASS_MEMORY_NEW(AttributeSelector, + span, std::move(name)); + } + + sass::string op(readAttributeOperator()); + + scanWhitespace(); + + bool isIdent = true; + sass::string value; + uint8_t next = scanner.peekChar(); + // Check if we are looking at an unquoted text + if (next != $single_quote && next != $double_quote) { + value = readIdentifier(); + } + else { + value = string(); + isIdent = isIdentifier(value); + } + + scanWhitespace(); + uint8_t modifier = 0; + if (isAlphabetic(scanner.peekChar())) { + modifier = scanner.readChar(); + scanWhitespace(); + } + + span = scanner.relevantSpanFrom(start); + + scanner.expectChar($rbracket); + + return SASS_MEMORY_NEW(AttributeSelector, span, + std::move(name), std::move(op), + std::move(value), isIdent, + modifier); + + } + // EO readAttributeSelector + + struct QualifiedName SelectorParser::readAttributeName() + { + + if (scanner.scanChar($asterisk)) { + scanner.expectChar($pipe); + return { readIdentifier(), "*", true }; + } + + sass::string nameOrNamespace = readIdentifier(); + if (scanner.peekChar() != $pipe || scanner.peekChar(1) == $equal) { + return { std::move(nameOrNamespace), "", false }; + } + + scanner.readChar(); + return { readIdentifier(), std::move(nameOrNamespace), true }; + + } + // EO readAttributeName + + // Consumes an attribute operator. + sass::string SelectorParser::readAttributeOperator() + { + Offset start(scanner.offset); + switch (scanner.readChar()) { + case $equal: + return "="; // AttributeOperator.equal; + + case $tilde: + scanner.expectChar($equal); + return "~="; // AttributeOperator.include; + + case $pipe: + scanner.expectChar($equal); + return "|="; // AttributeOperator.dash; + + case $caret: + scanner.expectChar($equal); + return "^="; // AttributeOperator.prefix; + + case $dollar: + scanner.expectChar($equal); + return "$="; // AttributeOperator.suffix; + + case $asterisk: + scanner.expectChar($equal); + return "*="; // AttributeOperator.substring; + + default: + error("Expected \"]\".", + scanner.rawSpanFrom(start)); + throw "Unreachable"; + } + } + // EO readAttributeOperator + + // Consumes a class operator. + ClassSelector* SelectorParser::readClassSelector() + { + Offset start(scanner.offset); + scanner.expectChar($dot); + sass::string name = readIdentifier(); + return SASS_MEMORY_NEW(ClassSelector, + scanner.rawSpanFrom(start), "." + name); + } + // EO readClassSelector + + // Consumes an in operator. + IDSelector* SelectorParser::readIdSelector() + { + Offset start(scanner.offset); + scanner.expectChar($hash); + sass::string name = readIdentifier(); + return SASS_MEMORY_NEW(IDSelector, + scanner.rawSpanFrom(start), "#" + name); + } + // EO readIdSelector + + // Consumes a placeholder operator. + PlaceholderSelector* SelectorParser::readPlaceholderSelector() + { + Offset start(scanner.offset); + scanner.expectChar($percent); + sass::string name = readIdentifier(); + return SASS_MEMORY_NEW(PlaceholderSelector, + scanner.rawSpanFrom(start), "%" + name); + } + // EO readPlaceholderSelector + + // Consumes a pseudo operator. + PseudoSelector* SelectorParser::readPseudoSelector() + { + Offset start(scanner.offset); + scanner.expectChar($colon); + bool element = scanner.scanChar($colon); + sass::string name = readIdentifier(); + + if (!scanner.scanChar($lparen)) { + return SASS_MEMORY_NEW(PseudoSelector, + scanner.rawSpanFrom(start), name, element); + } + scanWhitespace(); + + sass::string unvendored(name); + unvendored = Util::unvendor(unvendored); + + sass::string argument; + // Offset beforeArgument(scanner.offset); + SelectorListObj selector = SASS_MEMORY_NEW(SelectorList, scanner.relevantSpan()); + if (element) { + if (isSelectorPseudoElement(unvendored)) { + selector = readSelectorList(); + for (auto complex : selector->elements()) { + complex->chroots(true); + } + } + else { + argument = declarationValue(true); + } + } + else if (isSelectorPseudoClass(unvendored)) { + LOCAL_FLAG(allowParent, true); + selector = readSelectorList(); + for (auto complex : selector->elements()) { + complex->chroots(true); + } + } + else if (unvendored == "nth-child" || unvendored == "nth-last-child") { + argument = readAnPlusB(); + scanWhitespace(); + if (isWhitespace(scanner.peekChar(-1)) && scanner.peekChar() != $rparen) { + expectIdentifier("of", "\"of\""); + argument += " of"; + scanWhitespace(); + selector = readSelectorList(); + } + } + else { + argument = declarationValue(true); + StringUtils::makeRightTrimmed(argument); + } + scanner.expectChar($rparen); + + auto pseudo = SASS_MEMORY_NEW(PseudoSelector, + scanner.rawSpanFrom(start), + std::move(name), element != 0); + if (!selector->empty()) pseudo->selector(selector); + pseudo->argument(std::move(argument)); + return pseudo; + + } + // EO readPlaceholderSelector + + // Consumes an `an+b` expression. + sass::string SelectorParser::readAnPlusB() + { + + StringBuffer buffer; + uint8_t first, next, last; + switch (scanner.peekChar()) { + case $e: + case $E: + expectIdentifier("even", "\"even\""); + return "even"; + + case $o: + case $O: + expectIdentifier("odd", "\"odd\""); + return "odd"; + + case $plus: + case $minus: + buffer.write(scanner.readChar()); + break; + } + + if (scanner.peekChar(first) && isDigit(first)) { + while (isDigit(scanner.peekChar())) { + buffer.write(scanner.readChar()); + } + scanWhitespace(); + if (!scanCharIgnoreCase($n)) return buffer.buffer; + } + else { + expectCharIgnoreCase($n); + } + buffer.write($n); + scanWhitespace(); + + scanner.peekChar(next); + if (next != $plus && next != $minus) return buffer.buffer; + buffer.write(scanner.readChar()); + scanWhitespace(); + + if (!scanner.peekChar(last) || !isDigit(last)) { + error("Expected a number.", + scanner.rawSpan()); + } + while (isDigit(scanner.peekChar())) { + buffer.write(scanner.readChar()); + } + return buffer.buffer; + } + // readAnPlusB + + // Consumes a type of universal (simple) selector. + SimpleSelector* SelectorParser::readTypeOrUniversalSelector() + { + // Note: libsass has no explicit UniversalSelector, + // we use a regular type selector with name == "*". + Offset start(scanner.offset); + uint8_t first = scanner.peekChar(); + if (first == $asterisk) { + scanner.readChar(); + if (!scanner.scanChar($pipe)) { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), + "*", "", false); + } + if (scanner.scanChar($asterisk)) { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), + "*", "*", true); + } + else { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), + readIdentifier(), "*", true); + } + } + else if (first == $pipe) { + scanner.readChar(); + if (scanner.scanChar($asterisk)) { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), + "*", "", true); + } + else { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), + readIdentifier(), "", true); + } + } + + sass::string nameOrNamespace = readIdentifier(); + if (!scanner.scanChar($pipe)) { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), + std::move(nameOrNamespace), + "", false); + } + else if (scanner.scanChar($asterisk)) { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), "*", + std::move(nameOrNamespace), true); + } + else { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), readIdentifier(), + std::move(nameOrNamespace), true); + } + + } + // EO readTypeOrUniversalSelector + +} diff --git a/src/parser_selector.hpp b/src/parser_selector.hpp new file mode 100644 index 0000000000..9b14a8f699 --- /dev/null +++ b/src/parser_selector.hpp @@ -0,0 +1,99 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_SELECTOR_HPP +#define SASS_PARSER_SELECTOR_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class SelectorParser final : public Parser + { + public: + + // Whether this parser allows the parent selector `&`. + bool allowParent; + + // Whether this parser allows placeholder selectors beginning with `%`. + bool allowPlaceholder; + + // Value constructor + SelectorParser( + Compiler& context, + SourceDataObj source, + bool allowParent = true, + bool allowPlaceholder = true) : + Parser(context, source), + allowParent(allowParent), + allowPlaceholder(allowPlaceholder) + {} + + // Parse content into selector list + // Throws if not everything is consumed + SelectorList* parseSelectorList(); + + // Parse content into compound selector + // Throws if not everything is consumed + CompoundSelector* parseCompoundSelector(); + + // Parse content into simple selector + // Throws if not everything is consumed + SimpleSelector* parseSimpleSelector(); + + private: + + // Consumes a selector list. + SelectorList* readSelectorList(); + + // Consumes a complex selector. + ComplexSelector* readComplexSelector(bool lineBreak = false); + + // Consumes a compound selector. + CompoundSelector* readCompoundSelector(); + + // Consumes a simple selector. + SimpleSelector* readSimpleSelector(bool allowParent); + + // Consumes an attribute selector. + AttributeSelector* readAttributeSelector(); + + // Consumes an attribute name. + struct QualifiedName readAttributeName(); + + // Consumes an attribute operator. + sass::string readAttributeOperator(); + + // Consumes a class operator. + ClassSelector* readClassSelector(); + + // Consumes an in operator. + IDSelector* readIdSelector(); + + // Consumes a placeholder operator. + PlaceholderSelector* readPlaceholderSelector(); + + // Consumes a pseudo operator. + PseudoSelector* readPseudoSelector(); + + // Consumes an `an+b` expression. + sass::string readAnPlusB(); + + // Consumes a type of universal (simple) selector. + SimpleSelector* readTypeOrUniversalSelector(); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/parser_selectors.cpp b/src/parser_selectors.cpp deleted file mode 100644 index 6f60e616c4..0000000000 --- a/src/parser_selectors.cpp +++ /dev/null @@ -1,189 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "parser.hpp" - -namespace Sass { - - using namespace Prelexer; - using namespace Constants; - - ComplexSelectorObj Parser::parseComplexSelector(bool chroot) - { - - NESTING_GUARD(nestings); - - lex < block_comment >(); - advanceToNextToken(); - - ComplexSelectorObj sel = SASS_MEMORY_NEW(ComplexSelector, pstate); - - if (peek < end_of_file >()) return sel; - - while (true) { - - lex < block_comment >(); - advanceToNextToken(); - - // check for child (+) combinator - if (lex < exactly < selector_combinator_child > >()) { - sel->append(SASS_MEMORY_NEW(SelectorCombinator, pstate, SelectorCombinator::CHILD, peek_newline())); - } - // check for general sibling (~) combinator - else if (lex < exactly < selector_combinator_general > >()) { - sel->append(SASS_MEMORY_NEW(SelectorCombinator, pstate, SelectorCombinator::GENERAL, peek_newline())); - } - // check for adjecant sibling (+) combinator - else if (lex < exactly < selector_combinator_adjacent > >()) { - sel->append(SASS_MEMORY_NEW(SelectorCombinator, pstate, SelectorCombinator::ADJACENT, peek_newline())); - } - // check if we can parse a compound selector - else if (CompoundSelectorObj compound = parseCompoundSelector()) { - sel->append(compound); - } - else { - break; - } - } - - if (sel->empty()) return {}; - - // check if we parsed any parent references - sel->chroots(sel->has_real_parent_ref() || chroot); - - sel->update_pstate(pstate); - - return sel; - - } - - SelectorListObj Parser::parseSelectorList(bool chroot) - { - - bool reloop; - bool had_linefeed = false; - NESTING_GUARD(nestings); - SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate); - - if (peek_css< alternatives < end_of_file, exactly <'{'>, exactly <','> > >()) { - css_error("Invalid CSS", " after ", ": expected selector, was "); - } - - do { - reloop = false; - - had_linefeed = had_linefeed || peek_newline(); - - if (peek_css< alternatives < class_char < selector_list_delims > > >()) - break; // in case there are superfluous commas at the end - - // now parse the complex selector - ComplexSelectorObj complex = parseComplexSelector(chroot); - if (complex.isNull()) return list.detach(); - complex->hasPreLineFeed(had_linefeed); - - had_linefeed = false; - - while (peek_css< exactly<','> >()) - { - lex< css_comments >(false); - // consume everything up and including the comma separator - reloop = lex< exactly<','> >() != 0; - // remember line break (also between some commas) - had_linefeed = had_linefeed || peek_newline(); - // remember line break (also between some commas) - } - list->append(complex); - - } while (reloop); - - while (lex_css< kwd_optional >()) { - list->is_optional(true); - } - - // update for end position - list->update_pstate(pstate); - - return list.detach(); - } - - // parse one compound selector, which is basically - // a list of simple selectors (directly adjacent) - // lex them exactly (without skipping white-space) - CompoundSelectorObj Parser::parseCompoundSelector() - { - // init an empty compound selector wrapper - CompoundSelectorObj seq = SASS_MEMORY_NEW(CompoundSelector, pstate); - - // skip initial white-space - lex < block_comment >(); - advanceToNextToken(); - - if (lex< exactly<'&'> >(false)) - { - // ToDo: check the conditions and try to simplify flag passing - if (!allow_parent) error("Parent selectors aren't allowed here."); - // Create and append a new parent selector object - seq->hasRealParent(true); - } - - // parse list - while (true) - { - // remove all block comments - // leaves trailing white-space - lex < block_comment >(); - // parse parent selector - if (lex< exactly<'&'> >(false)) - { - // parent selector only allowed at start - // upcoming Sass may allow also trailing - SourceSpan state(pstate); - sass::string found("&"); - if (lex < identifier >()) { - found += sass::string(lexed); - } - sass::string sel(seq->hasRealParent() ? "&" : ""); - if (!seq->empty()) { sel = seq->last()->to_string({ NESTED, 5 }); } - // ToDo: parser should throw parser exceptions - error("Invalid CSS after \"" + sel + "\": expected \"{\", was \"" + found + "\"\n\n" - "\"" + found + "\" may only be used at the beginning of a compound selector."); - } - // parse functional - else if (match < re_functional >()) - { - seq->append(parse_simple_selector()); - } - - // parse type selector - else if (lex< re_type_selector >(false)) - { - seq->append(SASS_MEMORY_NEW(TypeSelector, pstate, lexed)); - } - // peek for abort conditions - else if (peek< spaces >()) break; - else if (peek< end_of_file >()) { break; } - else if (peek_css < class_char < selector_combinator_ops > >()) break; - else if (peek_css < class_char < complex_selector_delims > >()) break; - // otherwise parse another simple selector - else { - SimpleSelectorObj sel = parse_simple_selector(); - if (!sel) return {}; - seq->append(sel); - } - } - // EO while true - - if (seq && !peek_css>>()) { - seq->hasPostLineBreak(peek_newline()); - } - - // We may have set hasRealParent - if (seq && seq->empty() && !seq->hasRealParent()) return {}; - - return seq; - } - - -} diff --git a/src/parser_stylesheet.cpp b/src/parser_stylesheet.cpp new file mode 100644 index 0000000000..6e8ce48616 --- /dev/null +++ b/src/parser_stylesheet.cpp @@ -0,0 +1,3767 @@ +#include "parser_stylesheet.hpp" + +#include +#include "compiler.hpp" +#include "charcode.hpp" +#include "charcode.hpp" +#include "character.hpp" +#include "color_maps.hpp" +#include "exceptions.hpp" +#include "source_span.hpp" +#include "ast_supports.hpp" +#include "ast_statements.hpp" +#include "ast_expressions.hpp" +#include "parser_expression.hpp" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + using namespace StringUtils; + + ExternalCallable* StylesheetParser::parseExternalCallable() + { + // LibSass specials functions start with an `@` + bool hasAt = scanner.scanChar(Character::$at); + // Return new external callable object + ExternalCallableObj callable = + SASS_MEMORY_NEW(ExternalCallable, hasAt ? + "@" + readIdentifier() : readIdentifier(), + parseArgumentDeclaration(), nullptr); + if (!scanner.isDone()) { + error("expected selector.", + scanner.rawSpan()); + } + return callable.detach(); + } + + + // Parse stylesheet root block + Root* StylesheetParser::parseRoot() + { + + // skip over optional utf8 bom + // ToDo: check influence on count + scanner.scan(Strings::utf8bom); + + // Create initial states + Offset start(scanner.offset); + + // The parsed children + StatementVector children; + + // Check seems a bit esoteric but works + if (context.included_sources.size() == 1) { + // Apply headers only on very first include + context.applyCustomHeaders(children, + scanner.relevantSpanFrom(start)); + } + + // Parse nested root statements + StatementVector parsed(readStatements( + &StylesheetParser::readRootStatement)); + + // Move parsed children into our array + children.insert(children.end(), + std::make_move_iterator(parsed.begin()), + std::make_move_iterator(parsed.end())); + + // make sure everything is parsed + scanner.expectDone(); + + // Finalize variable scopes + context.varRoot.finalizeScopes(); + + // Return the new root object + return SASS_MEMORY_NEW(Root, + scanner.relevantSpanFrom(start), + std::move(children)); + } + // EO parseRoot + + // Consumes a variable declaration. + Statement* StylesheetParser::readVariableDeclaration() + { + Offset start(scanner.offset); + + sass::string ns; + sass::string id = variableName(); + if (scanner.scanChar($dot)) { + ns = id; + id = readPublicIdentifier(); + } + + // Create EnvKey from id + EnvKey name(std::move(id)); + + if (plainCss()) { + error("Sass variables aren't allowed in plain CSS.", + scanner.relevantSpanFrom(start)); + } + + scanWhitespace(); + scanner.expectChar($colon); + scanWhitespace(); + + ExpressionObj value = readExpression(); + + bool guarded = false; + bool global = false; + + Offset flagStart(scanner.offset); + while (scanner.scanChar($exclamation)) { + sass::string flag = readIdentifier(); + if (flag == "default") { + guarded = true; + } + else if (flag == "global") { + if (!ns.empty()) { + error("!global isn't allowed for variables in other modules.", + scanner.relevantSpanFrom(flagStart)); + } + global = true; + } + else { + error("Invalid flag name.", + scanner.relevantSpanFrom(flagStart)); + } + + scanWhitespace(); + flagStart = scanner.offset; + } + + expectStatementSeparator("variable declaration"); + + bool has_local = false; + // Skip to optional global scope + EnvFrame* frame = global ? + context.varStack.front() : + context.varStack.back(); + // As long as we are not in a loop construct, we + // can utilize full static variable optimizations. + VarRef vidx(inLoopDirective ? + frame->getLocalVariableIdx(name) : + frame->getVariableIdx(name)); + // Check if we found a local variable to use + if (vidx.isValid()) has_local = true; + // Otherwise we must create a new local variable + else vidx = frame->createVariable(name); + + // JIT self assignments + #ifdef SASS_OPTIMIZE_SELF_ASSIGN + // Optimization for cases where functions manipulate same variable + // We detect these cases here in order for the function to optimize + // self-assignment case (e.g. map-merge). It can then manipulate + // the value passed as the first argument directly in-place. + if (has_local && !global) { + // Certainly looks a bit like some poor man's JIT + if (auto fn = value->isaFunctionExpression()) { + auto& pos(fn->arguments()->positional()); + if (pos.size() > 0) { + if (auto var = pos[0]->isaVariableExpression()) { + if (var->name() == name) { // same name + fn->selfAssign(true); // Up to 15% faster + } + } + } + } + } + #endif + + AssignRule* declaration = SASS_MEMORY_NEW(AssignRule, + scanner.relevantSpanFrom(start), name, vidx, value, guarded, global); + if (inLoopDirective) frame->assignments.push_back(declaration); + return declaration; + } + // EO variableDeclaration + + // Consumes a statement that's allowed at the top level of the stylesheet or + // within nested style and at rules. If [root] is `true`, this parses at-rules + // that are allowed only at the root of the stylesheet. + Statement* StylesheetParser::readStatement(bool root) + { + Offset start(scanner.offset); + switch (scanner.peekChar()) { + + case $at: + return readAtRule(&StylesheetParser::readChildStatement, root); + + case $plus: + if (!isIndented() || !lookingAtIdentifier(1)) { + return readStyleRule(); + } + isUseAllowed = false; + start = scanner.offset; + scanner.readChar(); + return readIncludeRule(start); + + case $equal: + if (!isIndented()) return readStyleRule(); + isUseAllowed = false; + start = scanner.offset; + scanner.readChar(); + scanWhitespace(); + return readMixinRule(start); + + default: + isUseAllowed = false; + if (inStyleRule || inUnknownAtRule || inMixin || inContentBlock) { + return readDeclarationOrStyleRule(); + } + else { + return readStyleRule(); + } + + } + return nullptr; + } + // EO readStatement + + // Consumes a style rule. + StyleRule* StylesheetParser::readStyleRule() + { + LOCAL_FLAG(inStyleRule, true); + + // The indented syntax allows a single backslash to distinguish a style rule + // from old-style property syntax. We don't support old property syntax, but + // we do support the backslash because it's easy to do. + if (isIndented()) scanner.scanChar($backslash); + InterpolationObj readStyleRule(styleRuleSelector()); + EnvFrame local(context.varStack, false); + + Offset start(scanner.offset); + return withChildren( + &StylesheetParser::readChildStatement, + start, readStyleRule.ptr(), local.idxs); + + } + // EO readStyleRule + + // Consumes a [Declaration] or a [StyleRule]. + // + // When parsing the contents of a style rule, it can be difficult to tell + // declarations apart from nested style rules. Since we don't thoroughly + // parse selectors until after resolving interpolation, we can share a bunch + // of the parsing of the two, but we need to disambiguate them first. We use + // the following criteria: + // + // * If the entity doesn't start with an identifier followed by a colon, + // it's a selector. There are some additional mostly-unimportant cases + // here to support various declaration hacks. + // + // * If the colon is followed by another colon, it's a selector. + // + // * Otherwise, if the colon is followed by anything other than + // interpolation or a character that's valid as the beginning of an + // identifier, it's a declaration. + // + // * If the colon is followed by interpolation or a valid identifier, try + // parsing it as a declaration value. If this fails, backtrack and parse + // it as a selector. + // + // * If the declaration value is valid but is followed by "{", backtrack and + // parse it as a selector anyway. This ensures that ".foo:bar {" is always + // parsed as a selector and never as a property with nested properties + // beneath it. + Statement* StylesheetParser::readDeclarationOrStyleRule() + { + + if (plainCss() && inStyleRule && !inUnknownAtRule) { + // _propertyOrVariableDeclaration + return readDeclaration(); + } + + // The indented syntax allows a single backslash to distinguish a style rule + // from old-style property syntax. We don't support old property syntax, but + // we do support the backslash because it's easy to do. + if (isIndented() && scanner.scanChar($backslash)) { + return readStyleRule(); + } + + Offset start(scanner.offset); + InterpolationBuffer buffer(scanner); + Declaration* declaration = tryDeclarationOrBuffer(buffer); + + if (declaration != nullptr) { + return declaration; + } + + buffer.addInterpolation(styleRuleSelector()); + SourceSpan selectorPstate(scanner.relevantSpanFrom(start)); + + LOCAL_FLAG(inStyleRule, true); + + if (buffer.empty()) { + error("expected \"}\".", + scanner.relevantSpan()); + } + + EnvFrame local(context.varStack, true); + InterpolationObj itpl = buffer.getInterpolation( + scanner.relevantSpanFrom(start)); + StyleRuleObj rule = withChildren( + &StylesheetParser::readChildStatement, + start, itpl.ptr(), local.idxs); + if (isIndented() && rule->empty()) { + context.addWarning( + "This selector doesn't have any properties and won't be rendered.", + selectorPstate); + } + return rule.detach(); + } + // readDeclarationOrStyleRule + + + // Tries to parse a declaration, and returns the value parsed so + // far if it fails. This can return either an [InterpolationBuffer], + // indicating that it couldn't consume a declaration and that selector + // parsing should be attempted; or it can return a [Declaration], + // indicating that it successfully consumed a declaration. + Declaration* StylesheetParser::tryDeclarationOrBuffer(InterpolationBuffer& nameBuffer) + { + Offset start(scanner.offset); + + // Allow the "*prop: val", ":prop: val", + // "#prop: val", and ".prop: val" hacks. + uint8_t first = scanner.peekChar(); + if (first == $colon || first == $asterisk || first == $dot || + (first == $hash && scanner.peekChar(1) != $lbrace)) { + sass::sstream strm; + strm << scanner.readChar(); + strm << rawText(&StylesheetParser::scanWhitespace); + nameBuffer.write(strm.str(), scanner.relevantSpanFrom(start)); + } + + if (!lookingAtInterpolatedIdentifier()) { + return nullptr; + } + + nameBuffer.addInterpolation(readInterpolatedIdentifier()); + if (scanner.matches("/*")) nameBuffer.write(rawText(&StylesheetParser::loudComment)); + + StringBuffer midBuffer; + midBuffer.write(rawText(&StylesheetParser::scanWhitespace)); + SourceSpan beforeColon(scanner.relevantSpanFrom(start)); + if (!scanner.scanChar($colon)) { + if (!midBuffer.empty()) { + nameBuffer.write($space); + } + return nullptr; + } + midBuffer.write($colon); + + // Parse custom properties as declarations no matter what. + InterpolationObj name = nameBuffer.getInterpolation(beforeColon); + if (startsWith(name->getInitialPlain(), "--")) { + InterpolationObj value(readInterpolatedDeclarationValue()); + expectStatementSeparator("custom property"); + return SASS_MEMORY_NEW(Declaration, + scanner.relevantSpanFrom(start), name, + value->wrapInStringExpression(), true); + } + + if (scanner.scanChar($colon)) { + nameBuffer.write(midBuffer.buffer); + nameBuffer.write($colon); + return nullptr; + } + else if (isIndented() && lookingAtInterpolatedIdentifier()) { + // In the indented syntax, `foo:bar` is always + // considered a selector rather than a property. + nameBuffer.write(midBuffer.buffer); + return nullptr; + } + + sass::string postColonWhitespace = rawText(&StylesheetParser::scanWhitespace); + if (lookingAtChildren()) { + return withChildren( + &StylesheetParser::readDeclarationOrAtRule, + start, name, nullptr, false); + } + + midBuffer.write(postColonWhitespace); + bool couldBeSelector = postColonWhitespace.empty() + && lookingAtInterpolatedIdentifier(); + + StringScannerState beforeDeclaration = scanner.state(); + ExpressionObj value; + + try { + + if (lookingAtChildren()) { + SourceSpan pstate = scanner.relevantSpanFrom(scanner.offset); + Interpolation* itpl = SASS_MEMORY_NEW(Interpolation, pstate); + value = SASS_MEMORY_NEW(StringExpression, pstate, itpl, true); + } + else { + value = readExpression(); + } + + if (lookingAtChildren()) { + // Properties that are ambiguous with selectors can't have additional + // properties nested beneath them, so we force an error. This will be + // caught below and cause the text to be re-parsed as a selector. + if (couldBeSelector) { + expectStatementSeparator(); + } + } + else if (!atEndOfStatement()) { + // Force an exception if there isn't a valid end-of-property character but + // don't consume that character. This will also cause text to be re-parsed. + expectStatementSeparator(); + } + + } + catch (Exception::ParserException&) { + if (!couldBeSelector) throw; + + // If the value would be followed by a semicolon, it's + // definitely supposed to be a property, not a selector. + scanner.backtrack(beforeDeclaration); + InterpolationObj additional = readAlmostAnyValue(); + if (!isIndented() && scanner.peekChar() == $semicolon) throw; + + nameBuffer.write(midBuffer.buffer); + nameBuffer.addInterpolation(additional); + return nullptr; + } + + if (lookingAtChildren()) { + // Offset start(scanner.offset); + return withChildren( + &StylesheetParser::readDeclarationOrAtRule, + start, name, value, false); + } + else { + expectStatementSeparator(); + return SASS_MEMORY_NEW(Declaration, + scanner.relevantSpanFrom(start), name, value); + } + + } + // EO tryDeclarationOrBuffer + + // Consumes a property declaration. This is only used in contexts where + // declarations are allowed but style rules are not, such as nested + // declarations. Otherwise, [readDeclarationOrStyleRule] is used instead. + Declaration* StylesheetParser::readDeclaration(bool parseCustomProperties) + { + + Offset start(scanner.offset); + + InterpolationObj name; + // Allow the "*prop: val", ":prop: val", + // "#prop: val", and ".prop: val" hacks. + uint8_t first = scanner.peekChar(); + if (first == $colon || + first == $asterisk || + first == $dot || + (first == $hash && scanner.peekChar(1) != $lbrace)) { + InterpolationBuffer nameBuffer(scanner); + nameBuffer.write(scanner.readChar()); + nameBuffer.write(rawText(&StylesheetParser::scanWhitespace)); + nameBuffer.addInterpolation(readInterpolatedIdentifier()); + name = nameBuffer.getInterpolation(scanner.relevantSpanFrom(start)); + } + else { + name = readInterpolatedIdentifier(); + } + + scanWhitespace(); + scanner.expectChar($colon); + scanWhitespace(); + + if (parseCustomProperties && startsWith(name->getInitialPlain(), "--", 2)) { + InterpolationObj value = readInterpolatedDeclarationValue(); + expectStatementSeparator("custom property"); + return SASS_MEMORY_NEW(Declaration, + scanner.relevantSpanFrom(start), + name, value->wrapInStringExpression()); + } + + if (lookingAtChildren()) { + if (plainCss()) { + error("Nested declarations aren't allowed in plain CSS.", + scanner.rawSpan()); + } + return withChildren( + &StylesheetParser::readDeclarationOrAtRule, + start, name, nullptr, false); + } + + ExpressionObj value = readExpression(); + if (lookingAtChildren()) { + if (plainCss()) { + error("Nested declarations aren't allowed in plain CSS.", + scanner.rawSpan()); + } + // only without children; + return withChildren( + &StylesheetParser::readDeclarationOrAtRule, + start, name, value, false); + } + else { + expectStatementSeparator(); + return SASS_MEMORY_NEW(Declaration, + scanner.relevantSpanFrom(start), name, value); + } + + } + // EO readDeclaration + + // Consumes a statement that's allowed within a declaration. + Statement* StylesheetParser::readDeclarationOrAtRule() + { + if (scanner.peekChar() == $at) { + return readDeclarationAtRule(); + } + return readDeclaration(false); + } + // EO readDeclarationOrAtRule + + // Consumes an at-rule. This consumes at-rules that are allowed at all levels + // of the document; the [child] parameter is called to consume any at-rules + // that are specifically allowed in the caller's context. If [root] is `true`, + // this parses at-rules that are allowed only at the root of the stylesheet. + Statement* StylesheetParser::readAtRule(Statement* (StylesheetParser::* child)(), bool root) + { + // NOTE: this logic is largely duplicated in CssParser.atRule. + // Most changes here should be mirrored there. + + Offset start(scanner.offset); + scanner.expectChar($at, "@-rule"); + InterpolationObj name = readInterpolatedIdentifier(); + scanWhitespace(); + + // We want to set [isUseAllowed] to `false` *unless* we're parsing + // `@charset`, `@forward`, or `@use`. To avoid double-comparing the rule + // name, we always set it to `false` and then set it back to its previous + // value if we're parsing an allowed rule. + bool wasUseAllowed = isUseAllowed; + LOCAL_FLAG(isUseAllowed, false); + + sass::string plain(name->getPlainString()); + if (plain == "at-root") { + return readAtRootRule(start); + } + else if (plain == "charset") { + isUseAllowed = wasUseAllowed; + if (!root) throwDisallowedAtRule(start); + sass::string throwaway(string()); + return nullptr; + } + else if (plain == "content") { + return readContentRule(start); + } + else if (plain == "debug") { + return readDebugRule(start); + } + else if (plain == "each") { + return readEachRule(start, child); + } + else if (plain == "else") { + return throwDisallowedAtRule(start); + } + else if (plain == "error") { + return readErrorRule(start); + } + else if (plain == "extend") { + return readExtendRule(start); + } + else if (plain == "for") { + return readForRule(start, child); + } + else if (plain == "function") { + return readFunctionRule(start); + } + else if (plain == "if") { + return readIfRule(start, child); + } + else if (plain == "import") { + return readImportRule(start); + } + else if (plain == "include") { + return readIncludeRule(start); + } + else if (plain == "media") { + return readMediaRule(start); + } + else if (plain == "mixin") { + return readMixinRule(start); + } + else if (plain == "-moz-document") { + return readMozDocumentRule(start, name); + } + else if (plain == "return") { + return throwDisallowedAtRule(start); + } + else if (plain == "supports") { + return readSupportsRule(start); + } + else if (plain == "use") { + return readAnyAtRule(start, name); + // isUseAllowed = wasUseAllowed; + // if (!root) throwDisallowedAtRule(start); + // return parseUseRule(start); + } + else if (plain == "warn") { + return readWarnRule(start); + } + else if (plain == "while") { + return readWhileRule(start, child); + } + else { + return readAnyAtRule(start, name); + } + + } + // EO atRule + + // Consumes an at-rule allowed within a property declaration. + Statement* StylesheetParser::readDeclarationAtRule() + { + Offset start(scanner.offset); + sass::string name = readPlainAtRuleName(); + + if (name == "content") { + return readContentRule(start); + } + else if (name == "debug") { + return readDebugRule(start); + } + else if (name == "each") { + return readEachRule(start, + &StylesheetParser::readDeclarationOrAtRule); + } + else if (name == "else") { + return throwDisallowedAtRule(start); + } + else if (name == "error") { + return readErrorRule(start); + } + else if (name == "for") { + return readForRule(start, + &StylesheetParser::readDeclarationAtRule); + } + else if (name == "if") { + return readIfRule(start, + &StylesheetParser::readDeclarationOrAtRule); + } + else if (name == "include") { + return readIncludeRule(start); + } + else if (name == "warn") { + return readWarnRule(start); + } + else if (name == "while") { + return readWhileRule(start, + &StylesheetParser::readDeclarationOrAtRule); + } + else { + return throwDisallowedAtRule(start); + } + } + + // Consumes a statement allowed within a function. + Statement* StylesheetParser::readFunctionAtRule() + { + if (scanner.peekChar() != $at) { + // If a variable declaration failed to parse, it's possible the user + // thought they could write a style rule or property declaration in a + // function. If so, throw a more helpful error message. + StatementObj statement(readDeclarationOrStyleRule()); + // ToDo: dart-sass has a try/catch clause here!? + bool isStyleRule = statement->isaStyleRule(); + error( + sass::string("@function rules may not contain ") + + (isStyleRule ? "style rules." : "declarations."), + statement->pstate()); + } + + Offset start(scanner.offset); + sass::string name(readPlainAtRuleName()); + if (name == "debug") { + return readDebugRule(start); + } + else if (name == "each") { + return readEachRule(start, + &StylesheetParser::readFunctionAtRule); + } + else if (name == "else") { + return throwDisallowedAtRule(start); + } + else if (name == "error") { + return readErrorRule(start); + } + else if (name == "for") { + return readForRule(start, + &StylesheetParser::readFunctionAtRule); + } + else if (name == "if") { + return readIfRule(start, + &StylesheetParser::readFunctionAtRule); + } + else if (name == "return") { + return readReturnRule(start); + } + else if (name == "warn") { + return readWarnRule(start); + } + else if (name == "while") { + return readWhileRule(start, + &StylesheetParser::readFunctionAtRule); + } + else { + return throwDisallowedAtRule(start); + } + } + + // Consumes an at-rule's name, with interpolation disallowed. + sass::string StylesheetParser::readPlainAtRuleName() + { + scanner.expectChar($at, "@-rule"); + sass::string name = readIdentifier(); + scanWhitespace(); + return name; + } + + // Consumes an `@at-root` rule. + // [start] should point before the `@`. + AtRootRule* StylesheetParser::readAtRootRule(Offset start) + { + + EnvFrame local(context.varStack, false); + + if (scanner.peekChar() == $lparen) { + InterpolationObj query = readAtRootQuery(); + scanWhitespace(); + return withChildren( + &StylesheetParser::readChildStatement, + start, query, local.idxs); + } + else if (lookingAtChildren()) { + return withChildren( + &StylesheetParser::readChildStatement, + start, nullptr, local.idxs); + } + StyleRule* child = readStyleRule(); + return SASS_MEMORY_NEW(AtRootRule, + scanner.relevantSpanFrom(start), + nullptr, local.idxs, { child }); + } + // EO readAtRootRule + + // Consumes a query expression of the form `(foo: bar)`. + Interpolation* StylesheetParser::readAtRootQuery() + { + if (scanner.peekChar() == $hash) { + Expression* interpolation(readSingleInterpolation()); + return SASS_MEMORY_NEW(Interpolation, + interpolation->pstate(), interpolation); + } + + Offset start(scanner.offset); + InterpolationBuffer buffer(scanner); + scanner.expectChar($lparen); + buffer.writeCharCode($lparen); + scanWhitespace(); + + buffer.add(readExpression()); + if (scanner.scanChar($colon)) { + scanWhitespace(); + buffer.writeCharCode($colon); + buffer.writeCharCode($space); + buffer.add(readExpression()); + } + + scanner.expectChar($rparen); + scanWhitespace(); + buffer.writeCharCode($rparen); + + return buffer.getInterpolation( + scanner.relevantSpanFrom(start)); + + } + + // Consumes a `@content` rule. + // [start] should point before the `@`. + ContentRule* StylesheetParser::readContentRule(Offset start) + { + if (!inMixin) { + error("@content is only allowed within mixin declarations.", + scanner.relevantSpanFrom(start)); + } + + scanWhitespace(); + + ArgumentInvocationObj args; + if (scanner.peekChar() == $lparen) { + args = readArgumentInvocation(true); + } + else { + args = SASS_MEMORY_NEW(ArgumentInvocation, + scanner.relevantSpan(), ExpressionVector(), {}); + } + + LOCAL_FLAG(mixinHasContent, true); + expectStatementSeparator("@content rule"); + // ToDo: ContentRule + return SASS_MEMORY_NEW(ContentRule, + scanner.relevantSpanFrom(start), args); + + } + // EO readContentRule + + // Try to parse either `to` or `through`, if successful + // we will return `true`. The boolean passed via [inclusive] + // will be set to `true` if we parsed `through`. We return + // `false` if neither of the tokens could be parsed. + bool StylesheetParser::tryForRuleOperator(bool& inclusive) + { + if (!lookingAtIdentifier()) return false; + if (scanIdentifier("to")) { + inclusive = false; + return true; + } + else if (scanIdentifier("through")) { + inclusive = true; + return true; + } + else { + return false; + } + } + + + // Consumes an `@each` rule. + // [start] should point before the `@`. [child] is called to consume any + // children that are specifically allowed in the caller's context. + EachRule* StylesheetParser::readEachRule(Offset start, Statement* (StylesheetParser::* child)()) + { + // This must be enabled to pass tests + LOCAL_FLAG(inControlDirective, true); + LOCAL_FLAG(inLoopDirective, true); + sass::vector variables; + EnvFrame local(context.varStack, true); + variables.emplace_back(variableName()); + local.createVariable(variables.back()); + scanWhitespace(); + while (scanner.scanChar($comma)) { + scanWhitespace(); + variables.emplace_back(variableName()); + local.createVariable(variables.back()); + scanWhitespace(); + } + expectIdentifier("in", "\"in\""); + scanWhitespace(); + ExpressionObj list = readExpression(); + return withChildren( + child, start, variables, list, local.idxs); + } + + ErrorRule* StylesheetParser::readErrorRule(Offset start) + { + ExpressionObj value = readExpression(); + expectStatementSeparator("@error rule"); + return SASS_MEMORY_NEW(ErrorRule, + scanner.relevantSpanFrom(start), value); + } + // EO readErrorRule + + // Consumes an `@extend` rule. + // [start] should point before the `@`. + ExtendRule* StylesheetParser::readExtendRule(Offset start) + { + if (!inStyleRule && !inMixin && !inContentBlock) { + error("@extend may only be used within style rules.", + scanner.relevantSpanFrom(start)); + } + + InterpolationObj value = readAlmostAnyValue(); + bool optional = scanner.scanChar($exclamation); + if (optional) expectIdentifier("optional", "\"optional\""); + expectStatementSeparator("@extend rule"); + return SASS_MEMORY_NEW(ExtendRule, + scanner.relevantSpanFrom(start), value, optional); + } + // EO readExtendRule + + // Consumes a function declaration. + // [start] should point before the `@`. + FunctionRule* StylesheetParser::readFunctionRule(Offset start) + { + // Variables should not be hoisted through + EnvFrame* parent = context.varStack.back(); + EnvFrame local(context.varStack, false); + + // var precedingComment = lastSilentComment; + // lastSilentComment = null; + sass::string name = readIdentifier(); + sass::string normalized(name); + scanWhitespace(); + + ArgumentDeclarationObj arguments = parseArgumentDeclaration(); + + if (inMixin || inContentBlock) { + error("Mixins may not contain function declarations.", + scanner.relevantSpanFrom(start)); + } + else if (inControlDirective) { + error("Functions may not be declared in control directives.", + scanner.relevantSpanFrom(start)); + } + + sass::string fname(Util::unvendor(name)); + if (fname == "calc" || fname == "element" || fname == "expression" || + fname == "url" || fname == "and" || fname == "or" || fname == "not") { + error("Invalid function name.", + scanner.relevantSpanFrom(start)); + } + + scanWhitespace(); + FunctionRule* rule = withChildren( + &StylesheetParser::readFunctionAtRule, + start, name, arguments, local.idxs); + rule->fidx(parent->createFunction(name)); + return rule; + } + // EO readFunctionRule + + ForRule* StylesheetParser::readForRule(Offset start, Statement* (StylesheetParser::* child)()) + { + LOCAL_FLAG(inControlDirective, true); + LOCAL_FLAG(inLoopDirective, true); + EnvFrame local(context.varStack, true); + sass::string variable = variableName(); + local.createVariable(variable); + scanWhitespace(); + expectIdentifier("from", "\"from\""); + scanWhitespace(); + ExpressionObj from = readSingleExpression(); + scanWhitespace(); + bool inclusive = false; + if (!tryForRuleOperator(inclusive)) { + error("Expected \"to\" or \"through\".", + scanner.relevantSpan()); + } + scanWhitespace(); + ExpressionObj to = readExpression(); + return withChildren(child, start, + variable, from, to, inclusive, local.idxs); + } + + // ToDo: dart-sass stores all else ifs in the same object, smart ... + IfRule* StylesheetParser::readIfRule(Offset start, Statement* (StylesheetParser::* child)()) + { + // var ifIndentation = currentIndentation; + size_t ifIndentation = 0; + LOCAL_FLAG(inControlDirective, true); + ExpressionObj predicate = readExpression(); + + IfRuleObj root; + IfRuleObj cur; + + /* create anonymous lexical scope */ { + EnvFrame local(context.varStack, true); + StatementVector children( + readChildren(child)); + cur = root = SASS_MEMORY_NEW(IfRule, + scanner.relevantSpanFrom(start), local.idxs, + std::move(children), std::move(predicate)); + } + // EO lexical scope + + scanWhitespaceWithoutComments(); + + sass::vector ifs; + ifs.push_back(root); + + while (scanElse(ifIndentation)) { + scanWhitespace(); + // scanned a else if + if (scanIdentifier("if")) { + scanWhitespace(); + + ExpressionObj predicate = readExpression(); + start = scanner.offset; + + EnvFrame local(context.varStack, true); + StatementVector children( + readChildren(child)); + IfRule* alternative = SASS_MEMORY_NEW(IfRule, + scanner.relevantSpanFrom(start), local.idxs, + std::move(children), std::move(predicate)); + cur->alternative(alternative); + cur = alternative; + } + // scanned a pure else + else { + + EnvFrame local(context.varStack, true); + + start = scanner.offset; + StatementVector children( + readChildren(child)); + IfRule* alternative = SASS_MEMORY_NEW(IfRule, + scanner.relevantSpanFrom(start), local.idxs, + std::move(children)); // else has no predicate + cur->alternative(alternative); + break; + } + } + + scanWhitespaceWithoutComments(); + + return root.detach(); + + } + + ImportRule* StylesheetParser::readImportRule(Offset start) + { + + ImportRuleObj rule = SASS_MEMORY_NEW( + ImportRule, scanner.relevantSpanFrom(start)); + + do { + scanWhitespace(); + scanImportArgument(rule); + scanWhitespace(); + } while (scanner.scanChar($comma)); + // Check for expected finalization token + expectStatementSeparator("@import rule"); + return rule.detach(); + + } + + void StylesheetParser::scanImportArgument(ImportRule* rule) + { + const char* startpos = scanner.position; + Offset start(scanner.offset); + uint8_t next = scanner.peekChar(); + if (next == $u || next == $U) { + Expression* url = readFunctionOrStringExpression(); + scanWhitespace(); + auto queries = tryImportQueries(); + rule->append(SASS_MEMORY_NEW(StaticImport, + scanner.relevantSpanFrom(start), + SASS_MEMORY_NEW(Interpolation, + url->pstate(), url), + queries.first, queries.second)); + return; + } + + sass::string url = string(); + const char* rawUrlPos = scanner.position; + SourceSpan pstate = scanner.relevantSpanFrom(start); + scanWhitespace(); + auto queries = tryImportQueries(); + if (isPlainImportUrl(url) || queries.first != nullptr || queries.second != nullptr) { + // Create static import that is never + // resolved by libsass (output as is) + rule->append(SASS_MEMORY_NEW(StaticImport, + scanner.relevantSpanFrom(start), + SASS_MEMORY_NEW(Interpolation, pstate, + SASS_MEMORY_NEW(String, pstate, + sass::string(startpos, rawUrlPos))), + queries.first, queries.second)); + } + // Otherwise return a dynamic import + // Will resolve during the eval stage + else { + // Check for valid dynamic import + if (inControlDirective || inMixin) { + throwDisallowedAtRule(rule->pstate().position); + } + // Call custom importers and check if any of them handled the import + if (!context.callCustomImporters(url, pstate, rule)) { + // Try to load url into context.sheets + resolveDynamicImport(rule, start, url); + } + } + + } + + // Resolve import of [path] and add imports to [rule] + void StylesheetParser::resolveDynamicImport( + ImportRule* rule, Offset start, const sass::string& path) + { + SourceSpan pstate = scanner.relevantSpanFrom(start); + const ImportRequest import(path, scanner.sourceUrl); + callStackFrame frame(context, { pstate, Strings::importRule }); + + // Search for valid imports (e.g. partials) on the file-system + // Returns multiple valid results for ambiguous import path + const sass::vector resolved(context.findIncludes(import)); + + // Error if no file to import was found + if (resolved.empty()) { + context.addFinalStackTrace(pstate); + throw Exception::ParserException(context, + "Can't find stylesheet to import."); + } + // Error if multiple files to import were found + else if (resolved.size() > 1) { + sass::sstream msg_stream; + msg_stream << "It's not clear which file to import. Found:\n"; + for (size_t i = 0, L = resolved.size(); i < L; ++i) + { msg_stream << " " << resolved[i].imp_path << "\n"; } + throw Exception::ParserException(context, msg_stream.str()); + } + + // We made sure exactly one entry was found, load its content + if (ImportObj loaded = context.loadImport(resolved[0])) { + StyleSheet* sheet = context.registerImport(loaded); + rule->append(SASS_MEMORY_NEW(IncludeImport, pstate, sheet)); + } + else { + context.addFinalStackTrace(pstate); + throw Exception::ParserException(context, + "Couldn't read stylesheet for import."); + } + + } + // EO resolveDynamicImport + + + /* + sass::string StylesheetParser::parseImportUrl(sass::string url) + { + + // Backwards-compatibility for implementations that + // allow absolute Windows paths in imports. + if (File::is_absolute_path(url)) { + // return p.windows.toUri(url).toString(); + } + + + using LUrlParser::clParseURL; + clParseURL clURL = clParseURL::ParseURL(url); + + if (clURL.IsValid()) { + + } + + + // Throw a [FormatException] if [url] is invalid. + // Uri.parse(url); + return url; + } + */ + + // Returns whether [url] indicates that an `@import` is a plain CSS import. + bool StylesheetParser::isPlainImportUrl(const sass::string& url) const + { + if (url.length() < 5) return false; + + if (endsWithIgnoreCase(url, ".css", 4)) return true; + + uint8_t first = url[0]; + if (first == $slash) return url[1] == $slash; + if (first != $h) return false; + return startsWithIgnoreCase(url, "http://", 7) + || startsWithIgnoreCase(url, "https://", 6); + } + + // Consumes a supports condition and/or a media query after an `@import`. + std::pair StylesheetParser::tryImportQueries() + { + SupportsConditionObj supports; + if (scanIdentifier("supports")) { + scanner.expectChar($lparen); + Offset start(scanner.offset); + if (scanIdentifier("not")) { + scanWhitespace(); + SupportsCondition* condition = readSupportsConditionInParens(); + supports = SASS_MEMORY_NEW(SupportsNegation, + scanner.relevantSpanFrom(start), condition); + } + else if (scanner.peekChar() == $lparen) { + supports = readSupportsCondition(); + } + else { + Expression* name = readExpression(); + scanner.expectChar($colon); + scanWhitespace(); + Expression* value = readExpression(); + supports = SASS_MEMORY_NEW(SupportsDeclaration, + scanner.relevantSpanFrom(start), name, value); + } + scanner.expectChar($rparen); + scanWhitespace(); + } + + InterpolationObj media; + if (scanner.peekChar() == $lparen) { + media = readMediaQueryList(); + } + else if (lookingAtInterpolatedIdentifier()) { + media = readMediaQueryList(); + } + return std::make_pair(supports, media); + } + // EO tryImportQueries + + // Consumes an `@include` rule. + // [start] should point before the `@`. + IncludeRule* StylesheetParser::readIncludeRule(Offset start) + { + + sass::string ns; + sass::string name = readIdentifier(); + if (scanner.scanChar($dot)) { + ns = name; + name = readPublicIdentifier(); + } + + scanWhitespace(); + ArgumentInvocationObj arguments; + if (scanner.peekChar() == $lparen) { + arguments = readArgumentInvocation(true); + } + scanWhitespace(); + + EnvFrame local(context.varStack, true); + + ArgumentDeclarationObj contentArguments; + if (scanIdentifier("using")) { + scanWhitespace(); + contentArguments = parseArgumentDeclaration(); + scanWhitespace(); + } + + // ToDo: Add checks to allow to omit arguments fully + if (!arguments) { + SourceSpan pstate(scanner.relevantSpanFrom(start)); + arguments = SASS_MEMORY_NEW(ArgumentInvocation, + std::move(pstate), {}, {}); + } + + IncludeRuleObj rule = SASS_MEMORY_NEW(IncludeRule, + scanner.relevantSpanFrom(start), name, arguments); + + if (!name.empty()) { + // Get the function through the whole stack + auto midx = context.varStack.back()->getMixinIdx(name); + rule->midx(midx); + } + + + ContentBlockObj content; + if (contentArguments || lookingAtChildren()) { + LOCAL_FLAG(inContentBlock, true); + // EnvFrame inner(context.varStack); + if (contentArguments.isNull()) { + // Dart-sass creates this one too + contentArguments = SASS_MEMORY_NEW( + ArgumentDeclaration, + scanner.relevantSpan()); + } + Offset start(scanner.offset); + rule->content(withChildren( + &StylesheetParser::readChildStatement, + start, contentArguments, local.idxs)); + } + else { + expectStatementSeparator(); + } + + /* + var span = + scanner.rawSpanFrom(start, start).expand((content ? ? arguments).span); + return IncludeRule(name, arguments, span, + namespace : ns, content : content); + */ + + return rule.detach(); // mixin.detach(); + } + // EO readIncludeRule + + // Consumes a `@media` rule. + // [start] should point before the `@`. + MediaRule* StylesheetParser::readMediaRule(Offset start) + { + InterpolationObj query = readMediaQueryList(); + return withChildren( + &StylesheetParser::readChildStatement, + start, query); + } + + // Consumes a mixin declaration. + // [start] should point before the `@`. + MixinRule* StylesheetParser::readMixinRule(Offset start) + { + + EnvFrame* parent = context.varStack.back(); + EnvFrame local(context.varStack, false); + // Create space for optional content callable + // ToDo: check if this can be conditionally done? + auto cidx = local.createMixin(Keys::contentRule); + // var precedingComment = lastSilentComment; + // lastSilentComment = null; + sass::string name = readIdentifier(); + scanWhitespace(); + + ArgumentDeclarationObj arguments; + if (scanner.peekChar() == $lparen) { + arguments = parseArgumentDeclaration(); + } + else { + // Dart-sass creates this one too + arguments = SASS_MEMORY_NEW(ArgumentDeclaration, + scanner.relevantSpan(), sass::vector()); // empty declaration + } + + if (inMixin || inContentBlock) { + error("Mixins may not contain mixin declarations.", + scanner.relevantSpanFrom(start)); + } + else if (inControlDirective) { + error("Mixins may not be declared in control directives.", + scanner.relevantSpanFrom(start)); + } + + scanWhitespace(); + LOCAL_FLAG(inMixin, true); + LOCAL_FLAG(mixinHasContent, false); + + VarRef fidx = parent->createMixin(name); + MixinRule* rule = withChildren( + &StylesheetParser::readChildStatement, + start, name, arguments, local.idxs); + rule->midx(fidx); // to parent + rule->cidx(cidx); + return rule; + } + // EO _mixinRule + + // Consumes a `@moz-document` rule. Gecko's `@-moz-document` diverges + // from [the specification][] allows the `url-prefix` and `domain` + // functions to omit quotation marks, contrary to the standard. + // [the specification]: http://www.w3.org/TR/css3-conditional/ + AtRule* StylesheetParser::readMozDocumentRule(Offset start, Interpolation* name) + { + + Offset valueStart(scanner.offset); + InterpolationBuffer buffer(scanner); + bool needsDeprecationWarning = false; + + while (true) { + + if (scanner.peekChar() == $hash) { + buffer.add(readSingleInterpolation()); + needsDeprecationWarning = true; + } + else { + + + Offset identifierStart(scanner.offset); + sass::string identifier = this->readIdentifier(); + if (identifier == "url" || identifier == "url-prefix" || identifier == "domain") { + Interpolation* contents = tryUrlContents(identifierStart, /* name: */ identifier); + if (contents != nullptr) { + buffer.addInterpolation(contents); + } + else { + scanner.expectChar($lparen); + scanWhitespace(); + StringExpressionObj argument = readInterpolatedString(); + scanner.expectChar($rparen); + + buffer.write(identifier); + buffer.write($lparen); + buffer.addInterpolation(argument->getAsInterpolation()); + buffer.write($rparen); + } + + // A url-prefix with no argument, or with an empty string as an + // argument, is not (yet) deprecated. + sass::string trailing = buffer.trailingString(); + if (!endsWithIgnoreCase(trailing, "url-prefix()", 12) && + !endsWithIgnoreCase(trailing, "url-prefix('')", 14) && + !endsWithIgnoreCase(trailing, "url-prefix(\"\")", 14)) { + needsDeprecationWarning = true; + } + } + else if (identifier == "regexp") { + buffer.write("regexp("); + scanner.expectChar($lparen); + StringExpressionObj str = readInterpolatedString(); + buffer.addInterpolation(str->getAsInterpolation()); + scanner.expectChar($rparen); + buffer.write($rparen); + needsDeprecationWarning = true; + } + else { + error("Invalid function name.", + scanner.relevantSpanFrom(identifierStart)); + } + } + + scanWhitespace(); + if (!scanner.scanChar($comma)) break; + + buffer.write($comma); + buffer.write(rawText(&StylesheetParser::scanWhitespace)); + + } + + InterpolationObj value = buffer.getInterpolation(scanner.rawSpanFrom(valueStart)); + + + AtRule* atRule = withChildren( + &StylesheetParser::readChildStatement, + start, name, value, false); + + if (needsDeprecationWarning) { + + context.addDeprecation( + "@-moz-document is deprecated and support will be removed from Sass " + "in a future release. For details, see http://bit.ly/moz-document.", + atRule->pstate()); + } + + return atRule; + + } + + // Consumes a `@return` rule. + // [start] should point before the `@`. + ReturnRule* StylesheetParser::readReturnRule(Offset start) + { + ExpressionObj value = readExpression(); + expectStatementSeparator("@return rule"); + return SASS_MEMORY_NEW(ReturnRule, + scanner.relevantSpanFrom(start), value); + } + // EO readReturnRule + + // Consumes a `@supports` rule. + // [start] should point before the `@`. + SupportsRule* StylesheetParser::readSupportsRule(Offset start) + { + auto condition = readSupportsCondition(); + scanWhitespace(); + EnvFrame local(context.varStack, true); + return withChildren( + &StylesheetParser::readChildStatement, + start, condition, local.idxs); + } + // EO readSupportsRule + + + // Consumes a `@debug` rule. + // [start] should point before the `@`. + DebugRule* StylesheetParser::readDebugRule(Offset start) + { + ExpressionObj value(readExpression()); + expectStatementSeparator("@debug rule"); + return SASS_MEMORY_NEW(DebugRule, + scanner.relevantSpanFrom(start), value); + } + // EO readDebugRule + + // Consumes a `@warn` rule. + // [start] should point before the `@`. + WarnRule* StylesheetParser::readWarnRule(Offset start) + { + ExpressionObj value(readExpression()); + expectStatementSeparator("@warn rule"); + return SASS_MEMORY_NEW(WarnRule, + scanner.relevantSpanFrom(start), value); + } + // EO readWarnRule + + // Consumes a `@while` rule. [start] should point before the `@`. [child] is called + // to consume any children that are specifically allowed in the caller's context. + WhileRule* StylesheetParser::readWhileRule(Offset start, Statement* (StylesheetParser::* child)()) + { + LOCAL_FLAG(inControlDirective, true); + LOCAL_FLAG(inLoopDirective, true); + EnvFrame local(context.varStack, true); + ExpressionObj condition(readExpression()); + return withChildren(child, + start, condition.ptr(), local.idxs); + } + // EO readWhileRule + + // Consumes an at-rule that's not explicitly supported by Sass. + // [start] should point before the `@`. [name] is the name of the at-rule. + AtRule* StylesheetParser::readAnyAtRule(Offset start, Interpolation* name) + { + LOCAL_FLAG(inUnknownAtRule, true); + + InterpolationObj value; + uint8_t next = scanner.peekChar(); + if (next != $exclamation && !atEndOfStatement()) { + value = readAlmostAnyValue(); + } + + if (lookingAtChildren()) { + return withChildren( + &StylesheetParser::readChildStatement, + start, name, value, false); + } + expectStatementSeparator(); + return SASS_MEMORY_NEW(AtRule, + scanner.relevantSpanFrom(start), + name, value, true); + } + // EO readAnyAtRule + + // Parse almost any value to report disallowed at-rule + Statement* StylesheetParser::throwDisallowedAtRule(Offset start) + { + InterpolationObj value(readAlmostAnyValue()); + error("This at-rule is not allowed here.", + scanner.relevantSpanFrom(start)); + return nullptr; + } + // EO throwDisallowedAtRule + + // Argument declaration is tricky in terms of scoping. + // The variable before the colon is defined on the new frame. + // But the right side is evaluated in the parent scope. + ArgumentDeclaration* StylesheetParser::parseArgumentDeclaration() + { + + Offset start(scanner.offset); + scanner.expectChar($lparen); + scanWhitespace(); + sass::vector arguments; + EnvKeySet named; + sass::string restArgument; + while (scanner.peekChar() == $dollar) { + Offset variableStart(scanner.offset); + sass::string name(variableName()); + EnvKey norm(name); + scanWhitespace(); + + ExpressionObj defaultValue; + if (scanner.scanChar($colon)) { + scanWhitespace(); + defaultValue = readExpressionUntilComma(); + } + else if (scanner.scanChar($dot)) { + scanner.expectChar($dot); + scanner.expectChar($dot); + scanWhitespace(); + restArgument = name; + // Defer adding variable until we parsed expression + // Just in case the same variable is mentioned again + context.varStack.back()->createVariable(norm); + break; + } + + // Defer adding variable until we parsed expression + // Just in case the same variable is mentioned again + context.varStack.back()->createVariable(norm); + + arguments.emplace_back(SASS_MEMORY_NEW(Argument, + scanner.relevantSpanFrom(variableStart), name, defaultValue)); + + if (named.count(norm) == 1) { + error("Duplicate argument.", + arguments.back()->pstate()); + } + named.insert(std::move(norm)); + + if (!scanner.scanChar($comma)) break; + scanWhitespace(); + } + scanner.expectChar($rparen); + + return SASS_MEMORY_NEW( + ArgumentDeclaration, + scanner.relevantSpanFrom(start), + std::move(arguments), + std::move(restArgument)); + + } + // EO parseArgumentDeclaration + + // Consumes an argument invocation. If [mixin] is `true`, this is parsed + // as a mixin invocation. Mixin invocations don't allow the Microsoft-style + // `=` operator at the top level, but function invocations do. + ArgumentInvocation* StylesheetParser::readArgumentInvocation(bool mixin) + { + + Offset start(scanner.offset); + scanner.expectChar($lparen); + scanWhitespace(); + + ExpressionVector positional; + // Maybe make also optional? + ExpressionFlatMap named; + ExpressionObj restArg; + ExpressionObj kwdRest; + while (lookingAtExpression()) { + Offset start(scanner.offset); + ExpressionObj expression = readExpressionUntilComma(!mixin); + if (expression == nullptr) { + error("Expected expression.", + scanner.rawSpan()); + } + scanWhitespace(); + VariableExpression* var = expression->isaVariableExpression(); + if (var && scanner.scanChar($colon)) { + scanWhitespace(); + if (named.count(var->name()) == 1) { + error("Duplicate argument.", + expression->pstate()); + } + auto ex = readExpressionUntilComma(!mixin); + named[var->name()] = ex; + } + else if (scanner.scanChar($dot)) { + scanner.expectChar($dot); + scanner.expectChar($dot); + if (restArg == nullptr) { + restArg = expression; + } + else { + kwdRest = expression; + scanWhitespace(); + break; + } + } + else if (!named.empty()) { + // Positional before + if (!scanner.scan("...")) { + error("Positional arguments must" + " come before keyword arguments.", + scanner.spanAt(start)); + } + } + else { + positional.emplace_back(expression); + } + + scanWhitespace(); + if (!scanner.scanChar($comma)) break; + scanWhitespace(); + } + scanner.expectChar($rparen); + + return SASS_MEMORY_NEW( + ArgumentInvocation, + scanner.relevantSpanFrom(start), + std::move(positional), + std::move(named), + restArg, kwdRest); + + } + // EO readArgumentInvocation + + // Consumes an expression. If [bracketList] is true, parses this expression as + // the contents of a bracketed list. If [singleEquals] is true, allows the + // Microsoft-style `=` operator at the top level. If [until] is passed, it's + // called each time the expression could end and still be a valid expression. + // When it returns `true`, this returns the expression. + Expression* StylesheetParser::readExpression( + bool bracketList, bool singleEquals, + bool(StylesheetParser::* until)()) + { + + NESTING_GUARD(recursion); + + if (until != nullptr && (this->*until)()) { + SourceSpan span(scanner.rawSpan()); + error("Expected expression.", span); + } + + // StringScannerState beforeBracket; + Offset start(scanner.offset); + if (bracketList) { + // beforeBracket = scanner.position; + scanner.expectChar($lbracket); + scanWhitespace(); + + if (scanner.scanChar($rbracket)) { + ListExpression* list = SASS_MEMORY_NEW(ListExpression, + scanner.relevantSpanFrom(start), SASS_UNDEF); + list->hasBrackets(true); + return list; + } + } + + ExpressionParser ep(*this); + + bool wasInParentheses = inParentheses; + + while (true) { + scanWhitespace(); + if (until != nullptr && (this->*until)()) break; + + uint8_t next, first = scanner.peekChar(); + Offset beforeToken(scanner.offset); + + switch (first) { + case $lparen: + // Parenthesized numbers can't be slash-separated. + ep.addSingleExpression(readParenthesizedExpression()); + break; + + case $lbracket: + ep.addSingleExpression(readExpression(true)); + break; + + case $dollar: + ep.addSingleExpression(readVariableExpression()); + break; + + case $ampersand: + ep.addSingleExpression(readParentExpression()); + break; + + case $single_quote: + case $double_quote: + ep.addSingleExpression(readInterpolatedString()); + break; + + case $hash: + ep.addSingleExpression(readHashExpression()); + break; + + case $equal: + scanner.readChar(); + if (singleEquals && scanner.peekChar() != $equal) { + ep.resolveSpaceExpressions(); + ep.singleEqualsOperand = ep.singleExpression; + ep.singleExpression = {}; + } + else { + scanner.expectChar($equal); + ep.addOperator(SassOperator::EQ, beforeToken); + } + break; + + case $exclamation: + next = scanner.peekChar(1); + if (next == $equal) { + scanner.readChar(); + scanner.readChar(); + ep.addOperator(SassOperator::NEQ, beforeToken); + } + else if (next == $nul || + equalsLetterIgnoreCase($i, next) || + isWhitespace(next)) + { + ep.addSingleExpression(readImportantExpression()); + } + else { + goto endOfLoop; + } + break; + + case $langle: + scanner.readChar(); + ep.addOperator(scanner.scanChar($equal) + ? SassOperator::LTE : SassOperator::LT, beforeToken); + break; + + case $rangle: + scanner.readChar(); + ep.addOperator(scanner.scanChar($equal) + ? SassOperator::GTE : SassOperator::GT, beforeToken); + break; + + case $asterisk: + scanner.readChar(); + ep.addOperator(SassOperator::MUL, beforeToken); + break; + + case $plus: + if (ep.singleExpression == nullptr) { + ep.addSingleExpression(readUnaryOpExpression()); + } + else { + scanner.readChar(); + ep.addOperator(SassOperator::ADD, beforeToken); + } + break; + + case $minus: + next = scanner.peekChar(1); + if ((isDigit(next) || next == $dot) && + // Make sure `1-2` parses as `1 - 2`, not `1 (-2)`. + (ep.singleExpression == nullptr || + isWhitespace(scanner.peekChar(-1)))) { + ep.addSingleExpression(readNumberExpression(), true); + } + else if (lookingAtInterpolatedIdentifier()) { + ep.addSingleExpression(readIdentifierLike()); + } + else if (ep.singleExpression == nullptr) { + ep.addSingleExpression(readUnaryOpExpression()); + } + else { + scanner.readChar(); + ep.addOperator(SassOperator::SUB, beforeToken); + } + break; + + case $slash: + if (ep.singleExpression == nullptr) { + ep.addSingleExpression(readUnaryOpExpression()); + } + else { + scanner.readChar(); + ep.addOperator(SassOperator::DIV, beforeToken); + } + break; + + case $percent: + scanner.readChar(); + ep.addOperator(SassOperator::MOD, beforeToken); + break; + + case $0: + case $1: + case $2: + case $3: + case $4: + case $5: + case $6: + case $7: + case $8: + case $9: + ep.addSingleExpression(readNumberExpression(), true); + break; + + case $dot: + if (scanner.peekChar(1) == $dot) goto endOfLoop; + ep.addSingleExpression(readNumberExpression(), true); + break; + + case $a: + if (!plainCss() && scanIdentifier("and")) { + ep.addOperator(SassOperator::AND, beforeToken); + } + else { + ep.addSingleExpression(readIdentifierLike()); + } + break; + + case $o: + if (!plainCss() && scanIdentifier("or")) { + ep.addOperator(SassOperator::OR, beforeToken); + } + else { + ep.addSingleExpression(readIdentifierLike()); + } + break; + + case $u: + case $U: + if (scanner.peekChar(1) == $plus) { + ep.addSingleExpression(readUnicodeRange()); + } + else { + ep.addSingleExpression(readIdentifierLike()); + } + break; + + case $b: + case $c: + case $d: + case $e: + case $f: + case $g: + case $h: + case $i: + case $j: + case $k: + case $l: + case $m: + case $n: + case $p: + case $q: + case $r: + case $s: + case $t: + case $v: + case $w: + case $x: + case $y: + case $z: + case $A: + case $B: + case $C: + case $D: + case $E: + case $F: + case $G: + case $H: + case $I: + case $J: + case $K: + case $L: + case $M: + case $N: + case $O: + case $P: + case $Q: + case $R: + case $S: + case $T: + case $V: + case $W: + case $X: + case $Y: + case $Z: + case $_: + case $backslash: + ep.addSingleExpression(readIdentifierLike()); + break; + + case $comma: + // If we discover we're parsing a list whose first element is a + // division operation, and we're in parentheses, re-parse outside of a + // parent context. This ensures that `(1/2, 1)` doesn't perform division + // on its first element. + if (inParentheses) { + inParentheses = false; + if (ep.allowSlash) { + ep.resetState(); + break; + } + } + + if (ep.singleExpression == nullptr) { + SourceSpan span(scanner.rawSpan()); + error("Expected expression.", span); + } + + ep.resolveSpaceExpressions(); + ep.commaExpressions.emplace_back(ep.singleExpression); + scanner.readChar(); + ep.allowSlash = true; + ep.singleExpression = {}; + break; + + default: + if (first != $nul && first >= 0x80) { + ep.addSingleExpression(readIdentifierLike()); + break; + } + else { + goto endOfLoop; + } + } + } + + endOfLoop: + + if (bracketList) scanner.expectChar($rbracket); + if (!ep.commaExpressions.empty()) { + ep.resolveSpaceExpressions(); + inParentheses = wasInParentheses; + if (ep.singleExpression != nullptr) { + ep.commaExpressions.emplace_back(ep.singleExpression); + } + ListExpression* list = SASS_MEMORY_NEW(ListExpression, + scanner.relevantSpanFrom(start), SASS_COMMA); + list->concat(std::move(ep.commaExpressions)); + list->hasBrackets(bracketList); + return list; + } + else if (bracketList && + !ep.spaceExpressions.empty() && + ep.singleEqualsOperand == nullptr) { + ep.resolveOperations(); + ListExpression* list = SASS_MEMORY_NEW(ListExpression, + scanner.relevantSpanFrom(start), SASS_SPACE); + ep.spaceExpressions.emplace_back(ep.singleExpression); + list->concat(std::move(ep.spaceExpressions)); + list->hasBrackets(true); + return list; + } + else { + ep.resolveSpaceExpressions(); + if (bracketList) { + ListExpression* list = SASS_MEMORY_NEW(ListExpression, + scanner.relevantSpanFrom(start), SASS_UNDEF); + list->append(ep.singleExpression); + list->hasBrackets(true); + return list; + } + return ep.singleExpression.detach(); + } + + } + // EO expression + + // Returns `true` if scanner reached a `,` + bool StylesheetParser::lookingAtComma() + { + return scanner.peekChar() == $comma; + } + + // Consumes an expression until it reaches a top-level comma. + // If [singleEquals] is true, this will allow the + // Microsoft-style `=` operator at the top level. + Expression* StylesheetParser::readExpressionUntilComma(bool singleEquals) + { + return readExpression(false, singleEquals, + &StylesheetParser::lookingAtComma); + } + + + // Consumes an expression until it reaches a top-level comma. + // If [singleEquals] is true, this will allow the + // Microsoft-style `=` operator at the top level. + Expression* StylesheetParser::readSingleExpression() + { + NESTING_GUARD(recursion); + uint8_t first = scanner.peekChar(); + switch (first) { + // Note: when adding a new case, make sure it's reflected in + // [lookingAtExpression] and [_expression]. + case $lparen: + return readParenthesizedExpression(); + case $slash: + return readUnaryOpExpression(); + case $dot: + return readNumberExpression(); + case $lbracket: + return readExpression(true); + case $dollar: + return readVariableExpression(); + case $ampersand: + return readParentExpression(); + + case $single_quote: + case $double_quote: + return readInterpolatedString(); + + case $hash: + return readHashExpression(); + + case $plus: + return readPlusExpression(); + + case $minus: + return readMinusExpression(); + + case $exclamation: + return readImportantExpression(); + + case $u: + case $U: + if (scanner.peekChar(1) == $plus) { + return readUnicodeRange(); + } + else { + return readIdentifierLike(); + } + break; + + case $0: + case $1: + case $2: + case $3: + case $4: + case $5: + case $6: + case $7: + case $8: + case $9: + return readNumberExpression(); + break; + + case $a: + case $b: + case $c: + case $d: + case $e: + case $f: + case $g: + case $h: + case $i: + case $j: + case $k: + case $l: + case $m: + case $n: + case $o: + case $p: + case $q: + case $r: + case $s: + case $t: + case $v: + case $w: + case $x: + case $y: + case $z: + case $A: + case $B: + case $C: + case $D: + case $E: + case $F: + case $G: + case $H: + case $I: + case $J: + case $K: + case $L: + case $M: + case $N: + case $O: + case $P: + case $Q: + case $R: + case $S: + case $T: + case $V: + case $W: + case $X: + case $Y: + case $Z: + case $_: + case $backslash: + return readIdentifierLike(); + break; + + default: + if (first != $nul && first >= 0x80) { + return readIdentifierLike(); + } + error("Expected expression.", + scanner.relevantSpan()); + return nullptr; + } + } + // EO readSingleExpression + + // Consumes a parenthesized expression. + Expression* StylesheetParser::readParenthesizedExpression() + { + if (plainCss()) { + // This one is needed ... + error("Parentheses aren't allowed in plain CSS.", + scanner.rawSpan()); + } + + LOCAL_FLAG(inParentheses, true); + + Offset start(scanner.offset); + scanner.expectChar($lparen); + scanWhitespace(); + if (!lookingAtExpression()) { + scanner.expectChar($rparen); + return SASS_MEMORY_NEW(ListExpression, + scanner.relevantSpanFrom(start), + SASS_UNDEF); + } + + ExpressionObj first = readExpressionUntilComma(); + if (scanner.scanChar($colon)) { + scanWhitespace(); + return readMapExpression(first, start); + } + + if (!scanner.scanChar($comma)) { + scanner.expectChar($rparen); + return SASS_MEMORY_NEW(ParenthesizedExpression, + scanner.relevantSpanFrom(start), first); + } + scanWhitespace(); + + ExpressionVector + expressions = { first }; + + ListExpressionObj list = SASS_MEMORY_NEW(ListExpression, + scanner.relevantSpanFrom(start), SASS_COMMA); + + while (true) { + if (!lookingAtExpression()) { + break; + } + expressions.emplace_back(readExpressionUntilComma()); + if (!scanner.scanChar($comma)) { + break; + } + list->separator(SASS_COMMA); + scanWhitespace(); + } + + scanner.expectChar($rparen); + list->concat(std::move(expressions)); + list->pstate(scanner.relevantSpanFrom(start)); + return list.detach(); + } + // EO readParenthesizedExpression + + // Consumes a map expression. This expects to be called after the + // first colon in the map, with [first] as the expression before + // the colon and [start] the point before the opening parenthesis. + Expression* StylesheetParser::readMapExpression(Expression* first, Offset start) + { + MapExpressionObj map = SASS_MEMORY_NEW( + MapExpression, scanner.relevantSpanFrom(start)); + + map->append(first); + map->append(readExpressionUntilComma()); + + while (scanner.scanChar($comma)) { + scanWhitespace(); + if (!lookingAtExpression()) break; + + map->append(readExpressionUntilComma()); + scanner.expectChar($colon); + scanWhitespace(); + map->append(readExpressionUntilComma()); + } + + scanner.expectChar($rparen); + map->pstate(scanner.relevantSpanFrom(start)); + return map.detach(); + } + // EO _map + + // Consumes an expression that starts with a `#`. + Expression* StylesheetParser::readHashExpression() + { + // assert(scanner.peekChar() == $hash); + if (scanner.peekChar(1) == $lbrace) { + return readIdentifierLike(); + } + + Offset start(scanner.offset); + StringScannerState state(scanner.state()); + scanner.expectChar($hash); + + uint8_t first = scanner.peekChar(); + if (first != $nul && isDigit(first)) { + // ColorExpression + return readColorExpression(state); + } + + StringScannerState afterHash = scanner.state(); + InterpolationObj identifier = readInterpolatedIdentifier(); + if (isHexColor(identifier)) { + scanner.backtrack(afterHash); + return readColorExpression(state); + } + + InterpolationBuffer buffer(scanner); + buffer.write($hash); + buffer.addInterpolation(identifier); + SourceSpan pstate(scanner.relevantSpanFrom(start)); + return SASS_MEMORY_NEW(StringExpression, + pstate, buffer.getInterpolation(pstate)); + } + + ColorExpression* StylesheetParser::readColorExpression(StringScannerState state) + { + + uint8_t digit1 = readHexDigit(); + uint8_t digit2 = readHexDigit(); + uint8_t digit3 = readHexDigit(); + + uint8_t red; + uint8_t green; + uint8_t blue; + double alpha = 1.0; + + if (!isHex(scanner.peekChar())) { + red = (digit1 << 4) + digit1; + green = (digit2 << 4) + digit2; + blue = (digit3 << 4) + digit3; + } + else { + uint8_t digit4 = readHexDigit(); + if (!isHex(scanner.peekChar())) { + red = (digit1 << 4) + digit1; + green = (digit2 << 4) + digit2; + blue = (digit3 << 4) + digit3; + uint8_t a = (digit4 << 4) + digit4; + alpha = a / 255.0; + } + else { + red = (digit1 << 4) + digit2; + green = (digit3 << 4) + digit4; + uint8_t digit5 = readHexDigit(); + uint8_t digit6 = readHexDigit(); + blue = (digit5 << 4) + digit6; + if (isHex(scanner.peekChar())) { + uint8_t digit7 = readHexDigit(); + uint8_t digit8 = readHexDigit(); + uint8_t a = (digit7 << 4) + digit8; + alpha = a / 255.0; + } + } + } + + SourceSpan pstate(scanner.relevantSpanFrom(state.offset)); + sass::string original(state.position, scanner.position); + return SASS_MEMORY_NEW(ColorExpression, pstate, + SASS_MEMORY_NEW(ColorRgba, pstate, + red, green, blue, alpha, original)); + } + + // Returns whether [interpolation] is a plain + // string that can be parsed as a hex color. + bool StylesheetParser::isHexColor(Interpolation* interpolation) const + { + const sass::string& plain(interpolation->getPlainString()); + if (plain.empty()) return false; + if (plain.length() != 3 && + plain.length() != 4 && + plain.length() != 6 && + plain.length() != 8) + { + return false; + } + // return plain.codeUnits.every(isHex); + for (size_t i = 0; i < plain.length(); i++) { + if (!isHex(plain[i])) return false; + } + return true; + } + // EO isHexColor + + // Consumes a single hexadecimal digit. + uint8_t StylesheetParser::readHexDigit() + { + uint8_t chr = scanner.peekChar(); + if (chr == $nul || !isHex(chr)) { + error("Expected hex digit.", + scanner.relevantSpan()); + } + return asHex(scanner.readChar()); + } + // EO readHexDigit + + // Consumes an expression that starts with a `+`. + Expression* StylesheetParser::readPlusExpression() + { + SASS_ASSERT(scanner.peekChar() == $plus, + "plusExpression expects a plus sign"); + uint8_t next = scanner.peekChar(1); + if (isDigit(next) || next == $dot) { + return readNumberExpression(); + } + else { + return readUnaryOpExpression(); + } + } + // EO readPlusExpression + + // Consumes an expression that starts with a `-`. + Expression* StylesheetParser::readMinusExpression() + { + SASS_ASSERT(scanner.peekChar() == $minus, + "minusExpression expects a minus sign"); + uint8_t next = scanner.peekChar(1); + if (isDigit(next) || next == $dot) return readNumberExpression(); + if (lookingAtInterpolatedIdentifier()) return readIdentifierLike(); + return readUnaryOpExpression(); + } + // EO readMinusExpression + + // Consumes an `!important` expression. + StringExpression* StylesheetParser::readImportantExpression() + { + SASS_ASSERT(scanner.peekChar() == $exclamation, + "importantExpression expects an exclamation"); + Offset start(scanner.offset); + scanner.readChar(); + scanWhitespace(); + expectIdentifier("important", "\"important\""); + return SASS_MEMORY_NEW(StringExpression, + scanner.relevantSpanFrom(start), + sass::string("!important")); + } + // EO readImportantExpression + + // Consumes a unary operation expression. + UnaryOpExpression* StylesheetParser::readUnaryOpExpression() + { + Offset start(scanner.offset); + UnaryOpType op = + UnaryOpType::PLUS; + switch (scanner.readChar()) { + case $plus: + op = UnaryOpType::PLUS; + break; + case $minus: + op = UnaryOpType::MINUS; + break; + case $slash: + op = UnaryOpType::SLASH; + break; + default: + error("Expected unary operator.", + scanner.relevantSpan()); + } + + if (plainCss() && op != UnaryOpType::SLASH) { + error("Operators aren't allowed in plain CSS.", + scanner.relevantSpan()); + } + + scanWhitespace(); + Expression* operand = readSingleExpression(); + return SASS_MEMORY_NEW(UnaryOpExpression, + scanner.relevantSpanFrom(start), + op, operand); + } + // EO readUnaryOpExpression + + // Consumes a number expression. + NumberExpression* StylesheetParser::readNumberExpression() + { + StringScannerState start = scanner.state(); + uint8_t first = scanner.peekChar(); + + double sign = first == $minus ? -1 : 1; + if (first == $plus || first == $minus) scanner.readChar(); + + double number = scanner.peekChar() == $dot ? 0.0 : naturalNumber(); + + // Don't complain about a dot after a number unless the number + // starts with a dot. We don't allow a plain ".", but we need + // to allow "1." so that "1..." will work as a rest argument. + number += tryDecimal(scanner.position != start.position); + number *= tryExponent(); + + sass::string unit; + if (scanner.scanChar($percent)) { + unit = "%"; + } + else if (lookingAtIdentifier() && + // Disallow units beginning with `--`. + (scanner.peekChar() != $dash || scanner.peekChar(1) != $dash)) { + unit = readIdentifier(true); + } + + auto pstate(scanner.relevantSpanFrom(start.offset)); + return SASS_MEMORY_NEW(NumberExpression, pstate, + SASS_MEMORY_NEW(Number, pstate, sign * number, unit)); + } + + /* Locale unspecific atof function. */ + double sass_strtod(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_copy_c_string(str); + *(copy + (found - str)) = separator; + double res = strtod(copy, NULL); + free(copy); + return res; + } + } + + return strtod(str, NULL); + } + + // Consumes the decimal component of a number and returns its value, or 0 + // if there is no decimal component. If [allowTrailingDot] is `false`, this + // will throw an error if there's a dot without any numbers following it. + // Otherwise, it will ignore the dot without consuming it. + double StylesheetParser::tryDecimal(bool allowTrailingDot) + { + Offset start(scanner.offset); + StringScannerState state(scanner.state()); + if (scanner.peekChar() != $dot) return 0.0; + + if (!isDigit(scanner.peekChar(1))) { + if (allowTrailingDot) return 0.0; + error("Expected digit.", + scanner.relevantSpanFrom(start)); + } + + scanner.readChar(); + while (isDigit(scanner.peekChar())) { + scanner.readChar(); + } + + // Use built-in double parsing so that we don't accumulate + // floating-point errors for numbers with lots of digits. + sass::string nr(scanner.substring(state.position)); + return sass_strtod(nr.c_str()); + } + // EO tryDecimal + + // Consumes the exponent component of a number and returns + // its value, or 1 if there is no exponent component. + double StylesheetParser::tryExponent() + { + uint8_t first = scanner.peekChar(); + if (first != $e && first != $E) return 1.0; + + uint8_t next = scanner.peekChar(1); + if (!isDigit(next) && next != $minus && next != $plus) return 1.0; + + scanner.readChar(); + double exponentSign = next == $minus ? -1.0 : 1.0; + if (next == $plus || next == $minus) scanner.readChar(); + if (!isDigit(scanner.peekChar())) { + SourceSpan span(scanner.relevantSpan()); + callStackFrame frame(context, + BackTrace(span)); + error( + "Expected digit.", + scanner.relevantSpan()); + } + + double exponent = 0.0; + while (isDigit(scanner.peekChar())) { + exponent *= 10.0; + exponent += scanner.readChar() - $0; + } + + return pow(10.0, exponentSign * exponent); + } + // EO tryExponent + + // Consumes a unicode range expression. + StringExpression* StylesheetParser::readUnicodeRange() + { + StringScannerState state = scanner.state(); + expectCharIgnoreCase($u); + scanner.expectChar($plus); + + size_t i = 0; + for (; i < 6; i++) { + if (!scanCharIf(isHex)) break; + } + + if (scanner.scanChar($question)) { + i++; + for (; i < 6; i++) { + if (!scanner.scanChar($question)) break; + } + return SASS_MEMORY_NEW(StringExpression, + scanner.rawSpanFrom(state.offset), + scanner.substring(state.position)); + } + if (i == 0) { + error("Expected hex digit or \"?\".", + scanner.relevantSpan()); + } + + if (scanner.scanChar($minus)) { + size_t j = 0; + for (; j < 6; j++) { + if (!scanCharIf(isHex)) break; + } + if (j == 0) { + error("Expected hex digit.", + scanner.relevantSpan()); + } + } + + if (lookingAtInterpolatedIdentifierBody()) { + error("Expected end of identifier.", + scanner.relevantSpan()); + } + + return SASS_MEMORY_NEW(StringExpression, + scanner.relevantSpanFrom(state.offset), + scanner.substring(state.position)); + } + // EO readUnicodeRange + + // Consumes a variable expression. + VariableExpression* StylesheetParser::readVariableExpression(bool hoist) + { + Offset start(scanner.offset); + + sass::string ns, name = variableName(); + if (scanner.peekChar() == $dot && scanner.peekChar(1) != $dot) { + // Skip the dot + scanner.readChar(); + ns = name; + name = readPublicIdentifier(); + } + + if (plainCss()) { + error("Sass variables aren't allowed in plain CSS.", + scanner.relevantSpanFrom(start)); + } + + VariableExpression* expression = + SASS_MEMORY_NEW(VariableExpression, + scanner.relevantSpanFrom(start), + name); + + if (inLoopDirective) { + // Static variable resolution will be done in finalize stage + // Must be postponed since in loops we may reference post vars + context.varStack.back()->variables.push_back(expression); + } + else { + // Otherwise utilize full static optimizations + EnvFrame* frame(context.varStack.back()); + VarRef vidx(frame->getVariableIdx(name, true)); + if (vidx.isValid()) expression->vidxs().push_back(vidx); + else context.varStack.back()->variables.push_back(expression); + } + + return expression; + } + // readVariableExpression + + // Consumes a selector expression. + ParentExpression* StylesheetParser::readParentExpression() + { + if (plainCss()) { + error("The parent selector isn't allowed in plain CSS.", + scanner.rawSpan()); + /* ,length: 1 */ + } + + Offset start(scanner.offset); + scanner.expectChar($ampersand); + + if (scanner.scanChar($ampersand)) { + context.addWarning( + "In Sass, \"&&\" means two copies of the parent selector. You " + "probably want to use \"and\" instead.", + scanner.relevantSpanFrom(start)); + scanner.offset.column -= 1; + scanner.position -= 1; + } + + return SASS_MEMORY_NEW(ParentExpression, + scanner.relevantSpanFrom(start)); + } + // readParentExpression + + // Consumes a quoted string expression. + StringExpression* StylesheetParser::readInterpolatedString() + { + // NOTE: this logic is largely duplicated in ScssParser.readInterpolatedString. + // Most changes here should be mirrored there. + + Offset start(scanner.offset); + uint8_t quote = scanner.readChar(); + uint8_t next = 0, second = 0; + + if (quote != $single_quote && quote != $double_quote) { + error("Expected string.", + scanner.relevantSpanFrom(start)); + } + + InterpolationBuffer buffer(scanner); + while (true) { + if (!scanner.peekChar(next)) { + break; + } + if (next == quote) { + scanner.readChar(); + break; + } + else if (next == $nul || isNewline(next)) { + sass::sstream strm; + strm << "Expected " << quote << "."; + error(strm.str(), + scanner.relevantSpan()); + } + else if (next == $backslash) { + if (!scanner.peekChar(second, 1)) { + break; + } + if (isNewline(second)) { + scanner.readChar(); + scanner.readChar(); + if (second == $cr) scanner.scanChar($lf); + } + else { + buffer.writeCharCode(escapeCharacter()); + } + } + else if (next == $hash) { + if (scanner.peekChar(1) == $lbrace) { + buffer.add(readSingleInterpolation()); + } + else { + buffer.write(scanner.readChar()); + } + } + else { + buffer.write(scanner.readChar()); + } + } + + SourceSpan pstate(scanner.relevantSpanFrom(start)); + Interpolation* itpl(buffer.getInterpolation(pstate)); + return SASS_MEMORY_NEW(StringExpression, pstate, itpl, true); + } + // EO readInterpolatedString + + // Consumes an expression that starts like an identifier. + Expression* StylesheetParser::readIdentifierLike() + { + Offset start(scanner.offset); + InterpolationObj identifier = readInterpolatedIdentifier(); + sass::string plain(identifier->getPlainString()); + + if (!plain.empty()) { + if (plain == "if") { + ArgumentInvocation* invocation = readArgumentInvocation(); + return SASS_MEMORY_NEW(IfExpression, + invocation->pstate(), invocation); + } + else if (plain == "not") { + scanWhitespace(); + Expression* expression = readSingleExpression(); + return SASS_MEMORY_NEW(UnaryOpExpression, + scanner.relevantSpanFrom(start), + UnaryOpType::NOT, + expression); + } + + if (scanner.peekChar() != $lparen) { + if (plain == "false") { + auto pstate(scanner.relevantSpanFrom(start)); + return SASS_MEMORY_NEW(BooleanExpression, pstate, + SASS_MEMORY_NEW(Boolean, pstate, false)); + } + else if (plain == "true") { + auto pstate(scanner.relevantSpanFrom(start)); + return SASS_MEMORY_NEW(BooleanExpression, pstate, + SASS_MEMORY_NEW(Boolean, pstate, true)); + } + else if (plain == "null") { + auto pstate(scanner.relevantSpanFrom(start)); + return SASS_MEMORY_NEW(NullExpression, pstate, + SASS_MEMORY_NEW(Null, pstate)); + } + + // ToDo: dart-sass has ColorExpression(color); + if (const ColorRgba* color = name_to_color(plain)) { + ColorRgba* copy = SASS_MEMORY_COPY(color); + copy->pstate(identifier->pstate()); + copy->disp(plain); + return SASS_MEMORY_NEW(ColorExpression, + copy->pstate(), copy); + } + } + + auto specialFunction = trySpecialFunction(plain, start); + if (specialFunction != nullptr) { + return specialFunction; + } + } + + sass::string ns; + Offset beforeName(scanner.offset); + uint8_t next = scanner.peekChar(); + if (next == $dot) { + + if (scanner.peekChar(1) == $dot) { + return SASS_MEMORY_NEW(StringExpression, + scanner.relevantSpanFrom(beforeName), identifier); + } + + ns = identifier->getPlainString(); + scanner.readChar(); + beforeName = scanner.offset; + + Offset start(scanner.offset); + StringObj ident(SASS_MEMORY_NEW(String, + scanner.relevantSpanFrom(start), + readPublicIdentifier())); + + InterpolationObj itpl = SASS_MEMORY_NEW(Interpolation, + ident->pstate()); + + if (ns.empty()) { + error("Interpolation isn't allowed in namespaces.", + scanner.relevantSpanFrom(start)); + } + + ArgumentInvocation* args = readArgumentInvocation(); + return SASS_MEMORY_NEW(FunctionExpression, + scanner.relevantSpanFrom(start), itpl, args, ns); + + } + else if (next == $lparen) { + ArgumentInvocation* args = readArgumentInvocation(); + FunctionExpression* fn = SASS_MEMORY_NEW(FunctionExpression, + scanner.relevantSpanFrom(start), identifier, args, ns); + sass::string name(identifier->getPlainString()); + if (!name.empty()) { + // Get the function through the whole stack + auto fidx = context.varStack.back()->getFunctionIdx(name); + fn->fidx(fidx); + } + return fn; + } + else { + return SASS_MEMORY_NEW(StringExpression, + identifier->pstate(), identifier); + } + + } + // readIdentifierLike + + // If [name] is the name of a function with special syntax, consumes it. + // Otherwise, returns `null`. [start] is the location before the beginning of [name]. + StringExpression* StylesheetParser::trySpecialFunction(sass::string name, const Offset& start) + { + uint8_t next = 0; + makeLowerCase(name); + InterpolationBuffer buffer(scanner); + sass::string normalized(Util::unvendor(name)); + + if (normalized == "calc" || normalized == "element" || normalized == "expression") { + if (!scanner.scanChar($lparen)) return nullptr; + buffer.write(name); + buffer.write($lparen); + } + else if (normalized == "min" || normalized == "max") { + // min() and max() are parsed as the plain CSS mathematical functions if + // possible, and otherwise are parsed as normal Sass functions. + StringScannerState beginningOfContents = scanner.state(); + if (!scanner.scanChar($lparen)) return nullptr; + scanWhitespace(); + + buffer.write(name); + buffer.write($lparen); + + if (!tryMinMaxContents(buffer)) { + scanner.backtrack(beginningOfContents); + return nullptr; + } + + SourceSpan pstate(scanner.relevantSpanFrom(start)); + return SASS_MEMORY_NEW(StringExpression, + pstate, buffer.getInterpolation(pstate)); + } + else if (normalized == "progid") { + if (!scanner.scanChar($colon)) return nullptr; + buffer.write(name); + buffer.write($colon); + while (scanner.peekChar(next) && + (isAlphabetic(next) || next == $dot)) { + buffer.write(scanner.readChar()); + } + scanner.expectChar($lparen); + buffer.write($lparen); + } + else if (normalized == "url") { + InterpolationObj contents = tryUrlContents(start); + if (contents == nullptr) return nullptr; + return SASS_MEMORY_NEW(StringExpression, + scanner.relevantSpanFrom(start), contents); + } + else { + return nullptr; + } + + buffer.addInterpolation(readInterpolatedDeclarationValue(true)); + scanner.expectChar($rparen); + buffer.write($rparen); + + SourceSpan pstate(scanner.relevantSpanFrom(start)); + return SASS_MEMORY_NEW(StringExpression, + pstate, buffer.getInterpolation(pstate)); + } + // trySpecialFunction + + // Consumes the contents of a plain-CSS `min()` or `max()` function into + // [buffer] if one is available. Returns whether this succeeded. If [allowComma] + // is `true` (the default), this allows `CalcValue` productions separated by commas. + bool StylesheetParser::tryMinMaxContents(InterpolationBuffer& buffer, bool allowComma) + { + uint8_t next = 0; + // The number of open parentheses that need to be closed. + while (true) { + if (!scanner.peekChar(next)) { + return false; + } + switch (next) { + case $minus: + case $plus: + case $0: + case $1: + case $2: + case $3: + case $4: + case $5: + case $6: + case $7: + case $8: + case $9: + buffer.add(readNumberExpression()); + break; + + case $hash: + if (scanner.peekChar(1) != $lbrace) return false; + buffer.add(readSingleInterpolation()); + break; + + case $c: + case $C: + if (!tryMinMaxFunction(buffer, "calc")) return false; + break; + + case $e: + case $E: + if (!tryMinMaxFunction(buffer, "env")) return false; + break; + + case $v: + case $V: + if (!tryMinMaxFunction(buffer, "var")) return false; + break; + + case $lparen: + buffer.write(scanner.readChar()); + if (!tryMinMaxContents(buffer, false)) return false; + break; + + case $m: + case $M: + scanner.readChar(); + if (scanCharIgnoreCase($i)) { + if (!scanCharIgnoreCase($n)) return false; + buffer.write("min("); + } + else if (scanCharIgnoreCase($a)) { + if (!scanCharIgnoreCase($x)) return false; + buffer.write("max("); + } + else { + return false; + } + if (!scanner.scanChar($lparen)) return false; + + if (!tryMinMaxContents(buffer)) return false; + break; + + default: + return false; + } + + scanWhitespace(); + + next = scanner.peekChar(); + switch (next) { + case $rparen: + buffer.write(scanner.readChar()); + return true; + + case $plus: + case $minus: + case $asterisk: + case $slash: + buffer.write($space); + buffer.write(scanner.readChar()); + buffer.write($space); + break; + + case $comma: + if (!allowComma) return false; + buffer.write(scanner.readChar()); + buffer.write($space); + break; + + default: + return false; + } + + scanWhitespace(); + } + } + // EO tryMinMaxContents + + // Consumes a function named [name] containing an + // `InterpolatedDeclarationValue` if possible, and + // adds its text to [buffer]. Returns whether such a + // function could be consumed. + bool StylesheetParser::tryMinMaxFunction(InterpolationBuffer& buffer, sass::string name) + { + if (!scanIdentifier(name)) return false; + if (!scanner.scanChar($lparen)) return false; + buffer.write(name); + buffer.write($lparen); + buffer.addInterpolation(readInterpolatedDeclarationValue(true)); + buffer.write($rparen); + if (!scanner.scanChar($rparen)) return false; + return true; + } + // tryMinMaxFunction + + // Like [_urlContents], but returns `null` if the URL fails to parse. + // [start] is the position before the beginning of the name. + // [name] is the function's name; it defaults to `"url"`. + Interpolation* StylesheetParser::tryUrlContents(const Offset& start, sass::string name) + { + // NOTE: this logic is largely duplicated in Parser.tryUrl. + // Most changes here should be mirrored there. + StringScannerState beginningOfContents = scanner.state(); + if (!scanner.scanChar($lparen)) return nullptr; + scanWhitespaceWithoutComments(); + + // Match Ruby Sass's behavior: parse a raw URL() if possible, and if not + // backtrack and re-parse as a function expression. + InterpolationBuffer buffer(scanner); + buffer.write(name.empty() ? "url" : name); + buffer.write($lparen); + while (true) { + uint8_t next = scanner.peekChar(); + if (next == $nul) { + break; + } + else if (next == $exclamation || + next == $percent || + next == $ampersand || + (next >= $asterisk && next <= $tilde) || + next >= 0x0080) { + buffer.write(scanner.readChar()); + } + else if (next == $backslash) { + escape(buffer.text); + } + else if (next == $hash) { + if (scanner.peekChar(1) == $lbrace) { + buffer.add(readSingleInterpolation()); + } + else { + buffer.write(scanner.readChar()); + } + } + else if (isWhitespace(next)) { + scanWhitespaceWithoutComments(); + if (scanner.peekChar() != $rparen) break; + } + else if (next == $rparen) { + buffer.write(scanner.readChar()); + return buffer.getInterpolation(scanner.relevantSpanFrom(start)); + } + else { + break; + } + } + + scanner.backtrack(beginningOfContents); + return nullptr; + + } + // tryUrlContents + + // Consumes a [url] token that's allowed to contain SassScript. + // Returns either a `StringExpression` or a `FunctionExpression` + Expression* StylesheetParser::readFunctionOrStringExpression() + { + Offset start(scanner.offset); + expectIdentifier("url", "\"url\""); + String* fnName = SASS_MEMORY_NEW(String, + scanner.relevantSpanFrom(start), "url"); + InterpolationObj itpl = SASS_MEMORY_NEW(Interpolation, + scanner.relevantSpanFrom(start), fnName); + InterpolationObj contents = tryUrlContents(start); + if (contents != nullptr) { + return SASS_MEMORY_NEW(StringExpression, + scanner.relevantSpanFrom(start), contents); + } + + SourceSpan pstate(scanner.relevantSpanFrom(start)); + ArgumentInvocation* args = readArgumentInvocation(); + return SASS_MEMORY_NEW(FunctionExpression, + pstate, itpl, args, ""); + } + // readFunctionOrStringExpression + + // Consumes tokens up to "{", "}", ";", or "!". + // This respects string and comment boundaries and supports interpolation. + // Once this interpolation is evaluated, it's expected to be re-parsed. + // Differences from [parseInterpolatedDeclarationValue] include: + // * This does not balance brackets. + // * This does not interpret backslashes, since + // the text is expected to be re-parsed. + // * This supports Sass-style single-line comments. + // * This does not compress adjacent whitespace characters. + Interpolation* StylesheetParser::readAlmostAnyValue() + { + // const char* start = scanner.position; + InterpolationBuffer buffer(scanner); + const char* commentStart; + StringExpressionObj strex; + StringScannerState start = scanner.state(); + Interpolation* contents; + uint8_t next = 0; + + while (true) { + if (!scanner.peekChar(next)) { + goto endOfLoop; + } + switch (next) { + case $backslash: + // Write a literal backslash because this text will be re-parsed. + buffer.write(scanner.readChar()); + buffer.write(scanner.readChar()); + break; + + case $double_quote: + case $single_quote: + strex = readInterpolatedString(); + buffer.addInterpolation(strex->getAsInterpolation()); + break; + + case $slash: + commentStart = scanner.position; + if (scanComment()) { + buffer.write(scanner.substring(commentStart)); + } + else { + buffer.write(scanner.readChar()); + } + break; + + case $hash: + if (scanner.peekChar(1) == $lbrace) { + // Add a full interpolated identifier to handle cases like + // "#{...}--1", since "--1" isn't a valid identifier on its own. + buffer.addInterpolation(readInterpolatedIdentifier()); + } + else { + buffer.write(scanner.readChar()); + } + break; + + case $cr: + case $lf: + case $ff: + if (isIndented()) goto endOfLoop; + buffer.write(scanner.readChar()); + break; + + case $exclamation: + case $semicolon: + case $lbrace: + case $rbrace: + goto endOfLoop; + + case $u: + case $U: + start = scanner.state(); + if (!scanIdentifier("url")) { + buffer.write(scanner.readChar()); + break; + } + contents = tryUrlContents(start.offset); + if (contents == nullptr) { + scanner.backtrack(start); + buffer.write(scanner.readChar()); + } + else { + buffer.addInterpolation(contents); + } + break; + + default: + if (lookingAtIdentifier()) { + buffer.write(readIdentifier()); + } + else { + buffer.write(scanner.readChar()); + } + break; + } + } + + endOfLoop: + // scanner.relevant + // scanner.backtrack(scanner.relevant); + // scanWhitespaceWithoutComments(); // consume trailing white-space + return buffer.getInterpolation(scanner.relevantSpanFrom(start.offset), true); + + } + // readAlmostAnyValue + + // Consumes tokens until it reaches a top-level `";"`, `")"`, `"]"`, or `"}"` and + // returns their contents as a string. If [allowEmpty] is `false` (the default), this + // requires at least one token. Unlike [declarationValue], this allows interpolation. + Interpolation* StylesheetParser::readInterpolatedDeclarationValue(bool allowEmpty) + { + // NOTE: this logic is largely duplicated in Parser.declarationValue and + // isIdentifier in utils.dart. Most changes here should be mirrored there. + StringScannerState beforeUrl = scanner.state(); + Interpolation* contents; + + InterpolationBuffer buffer(scanner); + Offset start(scanner.offset); + sass::vector brackets; + bool wroteNewline = false; + uint8_t next = 0; + + InterpolationObj itpl; + StringExpressionObj strex; + + while (true) { + if (!scanner.peekChar(next)) { + goto endOfLoop; + } + switch (next) { + case $backslash: + escape(buffer.text, true); + wroteNewline = false; + break; + + case $double_quote: + case $single_quote: + strex = readInterpolatedString(); + itpl = strex->getAsInterpolation(); + buffer.addInterpolation(itpl); + wroteNewline = false; + break; + + case $slash: + if (scanner.peekChar(1) == $asterisk) { + buffer.write(rawText(&StylesheetParser::loudComment)); + } + else { + buffer.write(scanner.readChar()); + } + wroteNewline = false; + break; + + case $hash: + if (scanner.peekChar(1) == $lbrace) { + // Add a full interpolated identifier to handle cases like + // "#{...}--1", since "--1" isn't a valid identifier on its own. + itpl = readInterpolatedIdentifier(); + buffer.addInterpolation(itpl); + } + else { + buffer.write(scanner.readChar()); + } + wroteNewline = false; + break; + + case $space: + case $tab: + if (wroteNewline || !isWhitespace(scanner.peekChar(1))) { + buffer.write(scanner.readChar()); + } + else { + scanner.readChar(); + } + break; + + case $lf: + case $cr: + case $ff: + if (isIndented()) goto endOfLoop; + if (!isNewline(scanner.peekChar(-1))) { + buffer.write("\n"); + } + scanner.readChar(); + wroteNewline = true; + break; + + case $lparen: + case $lbrace: + case $lbracket: + buffer.write(next); + brackets.emplace_back(opposite(scanner.readChar())); + wroteNewline = false; + break; + + case $rparen: + case $rbrace: + case $rbracket: + if (brackets.empty()) goto endOfLoop; + buffer.write(next); + scanner.expectChar(brackets.back()); + brackets.pop_back(); + wroteNewline = false; + break; + + case $semicolon: + if (brackets.empty()) goto endOfLoop; + buffer.write(scanner.readChar()); + break; + + case $u: + case $U: + beforeUrl = scanner.state(); + if (!scanIdentifier("url")) { + buffer.write(scanner.readChar()); + wroteNewline = false; + break; + } + + contents = tryUrlContents(beforeUrl.offset); + if (contents == nullptr) { + scanner.backtrack(beforeUrl); + buffer.write(scanner.readChar()); + } + else { + buffer.addInterpolation(contents); + } + wroteNewline = false; + break; + + default: + if (next == $nul) goto endOfLoop; + + if (lookingAtIdentifier()) { + buffer.write(readIdentifier()); + } + else { + buffer.write(scanner.readChar()); + } + wroteNewline = false; + break; + } + } + + endOfLoop: + + if (!brackets.empty()) scanner.expectChar(brackets.back()); + if (!allowEmpty && buffer.empty()) { + error("Expected token.", + scanner.relevantSpan()); + } + SourceSpan pstate(scanner.rawSpanFrom(start)); + return buffer.getInterpolation(pstate); + + } + // parseInterpolatedDeclarationValue + + // Consumes an identifier that may contain interpolation. + Interpolation* StylesheetParser::readInterpolatedIdentifier() + { + InterpolationBuffer buffer(scanner); + Offset start(scanner.offset); + + if (scanner.scanChar($dash)) { + buffer.writeCharCode($dash); + if (scanner.scanChar($dash)) { + buffer.writeCharCode($dash); + consumeInterpolatedIdentifierBody(buffer); + return buffer.getInterpolation(scanner.relevantSpanFrom(start)); + } + } + + uint8_t first = 0; // , next = 0; + if (!scanner.peekChar(first)) { + error("Expected identifier.", + scanner.relevantSpanFrom(start)); + } + else if (isNameStart(first)) { + buffer.write(scanner.readChar()); + } + else if (first == $backslash) { + escape(buffer.text, true); + } + else if (first == $hash && scanner.peekChar(1) == $lbrace) { + ExpressionObj ex = readSingleInterpolation(); + buffer.add(ex); + } + else { + error("Expected identifier.", + scanner.relevantSpan()); + } + + consumeInterpolatedIdentifierBody(buffer); + return buffer.getInterpolation(scanner.relevantSpanFrom(start)); + + } + + void StylesheetParser::consumeInterpolatedIdentifierBody(InterpolationBuffer& buffer) + { + + uint8_t /* first = 0, */ next = 0; + while (true) { + if (!scanner.peekChar(next)) { + break; + } + else if (next == $underscore || + next == $dash || + isAlphanumeric(next) || + next >= 0x0080) { + buffer.write(scanner.readChar()); + } + else if (next == $backslash) { + escape(buffer.text); + } + else if (next == $hash && scanner.peekChar(1) == $lbrace) { + buffer.add(readSingleInterpolation()); + } + else { + break; + } + } + + } + // readInterpolatedIdentifier + + // Consumes interpolation. + Expression* StylesheetParser::readSingleInterpolation() + { + Offset start(scanner.offset); + scanner.expect("#{"); + scanWhitespace(); + ExpressionObj contents(readExpression()); + scanner.expectChar($rbrace); + + if (plainCss()) { + error( + "Interpolation isn't allowed in plain CSS.", + scanner.rawSpanFrom(start)); + } + + return contents.detach(); + } + // readSingleInterpolation + + // Consumes a list of media queries. + Interpolation* StylesheetParser::readMediaQueryList() + { + Offset start(scanner.offset); + InterpolationBuffer buffer(scanner); + while (true) { + scanWhitespace(); + readMediaQuery(buffer); + if (!scanner.scanChar($comma)) break; + buffer.write($comma); + buffer.write($space); + } + return buffer.getInterpolation(scanner.relevantSpanFrom(start)); + } + // readMediaQueryList + + // Consumes a single media query and appends it to [buffer]. + void StylesheetParser::readMediaQuery(InterpolationBuffer& buffer) + { + // This is somewhat duplicated in MediaQueryParser.readMediaQuery. + if (scanner.peekChar() != $lparen) { + buffer.addInterpolation(readInterpolatedIdentifier()); + scanWhitespace(); + + if (!lookingAtInterpolatedIdentifier()) { + // For example, "@media screen {". + return; + } + + buffer.write($space); + InterpolationObj identifier = + readInterpolatedIdentifier(); + scanWhitespace(); + + if (equalsIgnoreCase(identifier->getPlainString(), "and", 3)) { + // For example, "@media screen and ..." + buffer.write(" and "); + } + else { + buffer.addInterpolation(identifier); + if (scanIdentifier("and")) { + // For example, "@media only screen and ..." + scanWhitespace(); + buffer.write(" and "); + } + else { + // For example, "@media only screen {" + return; + } + } + } + + // We've consumed either `IDENTIFIER "and"` or + // `IDENTIFIER IDENTIFIER "and"`. + + while (true) { + scanWhitespace(); + buffer.addInterpolation(readMediaFeature()); + scanWhitespace(); + if (!scanIdentifier("and")) break; + buffer.write(" and "); + } + } + // readMediaQuery + + // Consumes a media query feature. + Interpolation* StylesheetParser::readMediaFeature() + { + if (scanner.peekChar() == $hash) { + Expression* interpolation = readSingleInterpolation(); + Interpolation* itpl = SASS_MEMORY_NEW(Interpolation, + interpolation->pstate()); + itpl->append(interpolation); + return itpl; + } + + Offset start(scanner.offset); + InterpolationBuffer buffer(scanner); + scanner.expectChar($lparen); + buffer.write($lparen); + scanWhitespace(); + + buffer.add(readExpressionUntilComparison()); + if (scanner.scanChar($colon)) { + scanWhitespace(); + buffer.write($colon); + buffer.write($space); + buffer.add(readExpression()); + } + else { + uint8_t next = scanner.peekChar(); + bool isAngle = next == $langle || next == $rangle; + if (isAngle || next == $equal) { + buffer.write($space); + buffer.write(scanner.readChar()); + if (isAngle && scanner.scanChar($equal)) { + buffer.write($equal); + } + buffer.write($space); + + scanWhitespace(); + buffer.add(readExpressionUntilComparison()); + + if (isAngle && scanner.scanChar(next)) { + buffer.write($space); + buffer.write(next); + if (scanner.scanChar($equal)) { + buffer.write($equal); + } + buffer.write($space); + + scanWhitespace(); + buffer.add(readExpressionUntilComparison()); + } + } + } + + scanner.expectChar($rparen); + scanWhitespace(); + buffer.write($rparen); + + return buffer.getInterpolation(scanner.relevantSpanFrom(start)); + + } + // readMediaFeature + + // Helper function for until condition + bool StylesheetParser::lookingAtExpressionEnd() + { + uint8_t next = scanner.peekChar(); + if (next == $equal) return scanner.peekChar(1) != $equal; + return next == $langle || next == $rangle; + } + // lookingAtExpressionEnd + + // Consumes an expression until it reaches a + // top-level `<`, `>`, or a `=` that's not `==`. + Expression* StylesheetParser::readExpressionUntilComparison() + { + return readExpression(false, false, + &StylesheetParser::lookingAtExpressionEnd); + } + + // Consumes a `@supports` condition. + SupportsCondition* StylesheetParser::readSupportsCondition() + { + Offset start(scanner.offset); + uint8_t first = scanner.peekChar(); + if (first != $lparen && first != $hash) { + Offset start(scanner.offset); + expectIdentifier("not", "\"not\""); + scanWhitespace(); + return SASS_MEMORY_NEW(SupportsNegation, + scanner.relevantSpanFrom(start), readSupportsConditionInParens()); + } + + SupportsConditionObj condition = + readSupportsConditionInParens(); + scanWhitespace(); + while (lookingAtIdentifier()) { + SupportsOperation::Operand op; + if (scanIdentifier("or")) { + op = SupportsOperation::OR; + } + else { + expectIdentifier("and", "\"and\""); + op = SupportsOperation::AND; + } + + scanWhitespace(); + SupportsConditionObj right = + readSupportsConditionInParens(); + + condition = SASS_MEMORY_NEW(SupportsOperation, + scanner.relevantSpanFrom(start), condition, right, op); + scanWhitespace(); + } + + return condition.detach(); + } + // EO readSupportsCondition + + // Consumes a parenthesized supports condition, or an interpolation. + SupportsCondition* StylesheetParser::readSupportsConditionInParens() + { + Offset start(scanner.offset); + if (scanner.peekChar() == $hash) { + return SASS_MEMORY_NEW(SupportsInterpolation, + scanner.relevantSpanFrom(start), readSingleInterpolation()); + } + + scanner.expectChar($lparen); + scanWhitespace(); + uint8_t next = scanner.peekChar(); + if (next == $lparen || next == $hash) { + SupportsConditionObj condition + = readSupportsCondition(); + scanWhitespace(); + scanner.expectChar($rparen); + return condition.detach(); + } + + if (next == $n || next == $N) { + SupportsNegationObj negation + = trySupportsNegation(); + if (negation != nullptr) { + scanner.expectChar($rparen); + return negation.detach(); + } + } + + ExpressionObj name(readExpression()); + scanner.expectChar($colon); + scanWhitespace(); + ExpressionObj value(readExpression()); + scanner.expectChar($rparen); + + return SASS_MEMORY_NEW(SupportsDeclaration, + scanner.relevantSpanFrom(start), name, value); + } + // EO readSupportsConditionInParens + + // Tries to consume a negated supports condition. Returns `null` if it fails. + SupportsNegation* StylesheetParser::trySupportsNegation() + { + StringScannerState start = scanner.state(); + if (!scanIdentifier("not") || scanner.isDone()) { + scanner.backtrack(start); + return nullptr; + } + + uint8_t next = scanner.peekChar(); + if (!isWhitespace(next) && next != $lparen) { + scanner.backtrack(start); + return nullptr; + } + + scanWhitespace(); + + return SASS_MEMORY_NEW(SupportsNegation, + scanner.relevantSpanFrom(start.offset), + readSupportsConditionInParens()); + + } + // trySupportsNegation + + // Returns whether the scanner is immediately before an identifier that may contain + // interpolation. This is based on the CSS algorithm, but it assumes all backslashes + // start escapes and it considers interpolation to be valid in an identifier. + // https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier + bool StylesheetParser::lookingAtInterpolatedIdentifier() const + { + // See also [ScssParser._lookingAtIdentifier]. + + uint8_t first = scanner.peekChar(); + if (first == $nul) return false; + if (isNameStart(first) || first == $backslash) return true; + if (first == $hash) return scanner.peekChar(1) == $lbrace; + + if (first != $dash) return false; + uint8_t second = scanner.peekChar(1); + if (second == $nul) return false; + // if (isNameStart(second)) return true; + // if (second == $backslash) return true; + + if (second == $hash) return scanner.peekChar(2) == $lbrace; + // if (second != $dash) return false; + + // uint8_t third = scanner.peekChar(2); + // if (third == $nul) return false; + // if (third != $hash) return isNameStart(third); + // else return scanner.peekChar(3) == $lbrace; + + return isNameStart(second) + || second == $backslash + || second == $dash; + } + // EO lookingAtInterpolatedIdentifier + + // Returns whether the scanner is immediately before a sequence + // of characters that could be part of an CSS identifier body. + // The identifier body may include interpolation. + bool StylesheetParser::lookingAtInterpolatedIdentifierBody() const + { + uint8_t first = scanner.peekChar(); + if (first == $nul) return false; + if (isName(first) || first == $backslash) return true; + return first == $hash && scanner.peekChar(1) == $lbrace; + } + // EO lookingAtInterpolatedIdentifierBody + + // Returns whether the scanner is immediately before a SassScript expression. + bool StylesheetParser::lookingAtExpression() const + { + uint8_t character, next; + if (!scanner.peekChar(character)) { + return false; + } + if (character == $dot) { + return scanner.peekChar(1) != $dot; + } + if (character == $exclamation) { + if (!scanner.peekChar(next, 1)) { + } + return isWhitespace(next) + || equalsLetterIgnoreCase($i, next); + } + + return character == $lparen + || character == $slash + || character == $lbracket + || character == $single_quote + || character == $double_quote + || character == $hash + || character == $plus + || character == $minus + || character == $backslash + || character == $dollar + || character == $ampersand + || isNameStart(character) + || isDigit(character); + } + // EO lookingAtExpression + + // Like [identifier], but rejects identifiers that begin with `_` or `-`. + sass::string StylesheetParser::readPublicIdentifier() + { + Offset start(scanner.offset); + auto result = readIdentifier(); + + uint8_t first = result[0]; + if (first == $dash || first == $underscore) { + error("Private members can't be accessed from outside their modules.", + scanner.rawSpanFrom(start)); + } + + return result; + } + // EO readPublicIdentifier + +} diff --git a/src/parser_stylesheet.hpp b/src/parser_stylesheet.hpp new file mode 100644 index 0000000000..63d9333b5b --- /dev/null +++ b/src/parser_stylesheet.hpp @@ -0,0 +1,506 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_STYLESHEET_HPP +#define SASS_PARSER_STYLESHEET_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser_base.hpp" +#include "ast_statements.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class StylesheetParser : public BaseParser + { + + friend class ExpressionParser; + + protected: + + // Current parsing recursion depth + // Just a counter for nesting guard + size_t recursion = 0; + + // Whether we are inside a loop directive such as `@for`, `@while` or `@each`. + // This will disable some incompatible static variable optimizations. + bool inLoopDirective = false; + + // Whether we've consumed a rule other than `@charset`, `@forward`, or `@use`. + bool isUseAllowed = true; + + // Whether the parser is currently parsing the contents of a mixin declaration. + bool inMixin = false; + + // Whether the current mixin contains at least one `@content` rule. + bool mixinHasContent = false; + + // Whether the parser is currently parsing a content block passed to a mixin. + bool inContentBlock = false; + + // Whether the parser is currently parsing a control directive such as `@if` or `@each`. + bool inControlDirective = false; + + // Whether the parser is currently parsing an unknown rule. + bool inUnknownAtRule = false; + + // Whether the parser is currently parsing a style rule. + bool inStyleRule = false; + + // Whether the parser is currently within a parenthesized expression. + bool inParentheses = false; + + public: + + // Value constructor + StylesheetParser( + Compiler& context, + SourceDataObj source) : + BaseParser( + context, source), + recursion(0), + isUseAllowed(true), + inMixin(false), + mixinHasContent(false), + inContentBlock(false), + inControlDirective(false), + inUnknownAtRule(false), + inStyleRule(false), + inParentheses(false) + {} + + // Main parser entry function + Root* parseRoot(); + + // Parse external callback function + ExternalCallable* parseExternalCallable(); + + // Argument declaration is tricky in terms of scoping. + // The variable before the colon is defined on the new frame. + // But the right side is evaluated in the parent scope. + ArgumentDeclaration* parseArgumentDeclaration(); + + // Whether this is a plain CSS stylesheet. + virtual bool plainCss() const { return false; } + + // Whether this is parsing the indented syntax. + virtual bool isIndented() const { return false; }; + + protected: + + // Parses and returns a selector used in a style rule. + virtual Interpolation* styleRuleSelector() = 0; + + // Asserts that the scanner is positioned before a statement separator, + // or at the end of a list of statements. If the [name] of the parent + // rule is passed, it's used for error reporting. This consumes + // whitespace, but nothing else, including comments. + virtual void expectStatementSeparator(sass::string name = "") = 0; + + // Whether the scanner is positioned at the end of a statement. + virtual bool atEndOfStatement() = 0; + + // Whether the scanner is positioned before a block of + // children that can be parsed with [children]. + virtual bool lookingAtChildren() = 0; + + // Tries to scan an `@else` rule after an `@if` block. Returns whether + // that succeeded. This should just scan the rule name, not anything + // afterwards. [ifIndentation] is the result of [currentIndentation] + // from before the corresponding `@if` was parsed. + virtual bool scanElse(size_t ifIndentation) = 0; + + // Consumes a variable declaration. + virtual Statement* readVariableDeclaration(); + + // Consumes a block of child statements. Unlike most production consumers, + // this does *not* consume trailing whitespace. This is necessary to ensure + // that the source span for the parent rule doesn't cover whitespace after the rule. + virtual StatementVector readChildren(Statement* (StylesheetParser::* child)()) = 0; + + // Consumes top-level statements. The [statement] callback may return `nullptr`, + // indicating that a statement was consumed that shouldn't be added to the AST. + virtual StatementVector readStatements(Statement* (StylesheetParser::* statement)()) = 0; + + // Consumes a statement that's allowed at the top level of the stylesheet or + // within nested style and at rules. If [root] is `true`, this parses at-rules + // that are allowed only at the root of the stylesheet. + Statement* readStatement(bool root = false); + + // Helpers to either parse root or children context + Statement* readRootStatement() { return readStatement(true); } + Statement* readChildStatement() { return readStatement(false); } + + // Consumes a style rule. + StyleRule* readStyleRule(); + + // Consumes a [Declaration] or a [StyleRule]. + // + // When parsing the contents of a style rule, it can be difficult to tell + // declarations apart from nested style rules. Since we don't thoroughly + // parse selectors until after resolving interpolation, we can share a bunch + // of the parsing of the two, but we need to disambiguate them first. We use + // the following criteria: + // + // * If the entity doesn't start with an identifier followed by a colon, + // it's a selector. There are some additional mostly-unimportant cases + // here to support various declaration hacks. + // + // * If the colon is followed by another colon, it's a selector. + // + // * Otherwise, if the colon is followed by anything other than + // interpolation or a character that's valid as the beginning of an + // identifier, it's a declaration. + // + // * If the colon is followed by interpolation or a valid identifier, try + // parsing it as a declaration value. If this fails, backtrack and parse + // it as a selector. + // + // * If the declaration value is valid but is followed by "{", backtrack and + // parse it as a selector anyway. This ensures that ".foo:bar {" is always + // parsed as a selector and never as a property with nested properties + // beneath it. + Statement* readDeclarationOrStyleRule(); + + // Tries to parse a declaration, and returns the value parsed so + // far if it fails. This can return either an [InterpolationBuffer], + // indicating that it couldn't consume a declaration and that selector + // parsing should be attempted; or it can return a [Declaration], + // indicating that it successfully consumed a declaration. + Declaration* tryDeclarationOrBuffer(InterpolationBuffer& buffer); + + // Consumes a property declaration. This is only used in contexts where + // declarations are allowed but style rules are not, such as nested + // declarations. Otherwise, [readDeclarationOrStyleRule] is used instead. + Declaration* readDeclaration(bool parseCustomProperties = true); + + // Consumes a statement that's allowed within a declaration. + Statement* readDeclarationOrAtRule(); + + // Consumes an at-rule. This consumes at-rules that are allowed at all levels + // of the document; the [child] parameter is called to consume any at-rules + // that are specifically allowed in the caller's context. If [root] is `true`, + // this parses at-rules that are allowed only at the root of the stylesheet. + virtual Statement* readAtRule(Statement* (StylesheetParser::* child)(), bool root = false); + + // Consumes an at-rule allowed within a property declaration. + Statement* readDeclarationAtRule(); + + // Consumes a statement allowed within a function. + Statement* readFunctionAtRule(); + + // Consumes an at-rule's name, with interpolation disallowed. + sass::string readPlainAtRuleName(); + + // Consumes an `@at-root` rule. + // [start] should point before the `@`. + AtRootRule* readAtRootRule(Offset start); + + // Consumes a query expression of the form `(foo: bar)`. + Interpolation* readAtRootQuery(); + + // Consumes a `@content` rule. + // [start] should point before the `@`. + ContentRule* readContentRule(Offset start); + + // Consumes a `@debug` rule. + // [start] should point before the `@`. + DebugRule* readDebugRule(Offset start); + + // Consumes an `@each` rule. + // [start] should point before the `@`. [child] is called to consume any + // children that are specifically allowed in the caller's context. + EachRule* readEachRule(Offset start, Statement* (StylesheetParser::* child)()); + + // Consumes a `@error` rule. + // [start] should point before the `@`. + ErrorRule* readErrorRule(Offset start); + + // Consumes a `@extend` rule. + // [start] should point before the `@`. + ExtendRule* readExtendRule(Offset start); + + // Consumes a `@function` rule. + // [start] should point before the `@`. + FunctionRule* readFunctionRule(Offset start); + + // Try to parse either `to` or `through`, if successful + // we will return `true`. The boolean passed via [inclusive] + // will be set to `true` if we parsed `through`. We return + // `false` if neither of the tokens could be parsed. + bool tryForRuleOperator(bool& inclusive); + + // Consumes a `@for` rule. + // [start] should point before the `@`. [child] is called to consume any + // children that are specifically allowed in the caller's context. + ForRule* readForRule(Offset start, Statement* (StylesheetParser::* child)()); + + // Consumes an `@if` rule. + // [start] should point before the `@`. [child] is called to consume any + // children that are specifically allowed in the caller's context. + IfRule* readIfRule(Offset start, Statement* (StylesheetParser::* child)()); + + // Consumes an `@import` rule. + // [start] should point before the `@`. + ImportRule* readImportRule(Offset start); + + // Consumes an argument to an `@import` rule. + // If anything is found it will be added to [rule]. + virtual void scanImportArgument(ImportRule* rule); + + // Returns whether [url] indicates that an `@import` is a plain CSS import. + bool isPlainImportUrl(const sass::string& url) const; + + // Consumes a supports condition and/or a media query after an `@import`. + std::pair tryImportQueries(); + + // Consumes an `@include` rule. + // [start] should point before the `@`. + IncludeRule* readIncludeRule(Offset start); + + // Consumes a `@media` rule. + // [start] should point before the `@`. + MediaRule* readMediaRule(Offset start); + + // Consumes a mixin declaration. + // [start] should point before the `@`. + MixinRule* readMixinRule(Offset start); + + // Consumes a `@moz-document` rule. Gecko's `@-moz-document` diverges + // from [the specification][] allows the `url-prefix` and `domain` + // functions to omit quotation marks, contrary to the standard. + // [the specification]: http://www.w3.org/TR/css3-conditional/ + AtRule* readMozDocumentRule(Offset start, Interpolation* name); + + // Consumes a `@return` rule. + // [start] should point before the `@`. + ReturnRule* readReturnRule(Offset start); + + // Consumes a `@supports` rule. + // [start] should point before the `@`. + SupportsRule* readSupportsRule(Offset start); + + // Consumes a `@use` rule. + // [start] should point before the `@`. + // UseRule* readUseRule(Offset start); + + // Consumes a `@warn` rule. + // [start] should point before the `@`. + WarnRule* readWarnRule(Offset start); + + // Consumes a `@while` rule. [start] should point before the `@`. [child] is called + // to consume any children that are specifically allowed in the caller's context. + WhileRule* readWhileRule(Offset start, Statement* (StylesheetParser::* child)()); + + // Consumes an at-rule that's not explicitly supported by Sass. + // [start] should point before the `@`. [name] is the name of the at-rule. + AtRule* readAnyAtRule(Offset start, Interpolation* name); + + // Parse almost any value to report disallowed at-rule + Statement* throwDisallowedAtRule(Offset start); + + // Consumes an argument invocation. If [mixin] is `true`, this is parsed + // as a mixin invocation. Mixin invocations don't allow the Microsoft-style + // `=` operator at the top level, but function invocations do. + ArgumentInvocation* readArgumentInvocation(bool mixin = false); + + // Consumes an expression. If [bracketList] is true, parses this expression as + // the contents of a bracketed list. If [singleEquals] is true, allows the + // Microsoft-style `=` operator at the top level. If [until] is passed, it's + // called each time the expression could end and still be a valid expression. + // When it returns `true`, this returns the expression. + Expression* readExpression( + bool bracketList = false, bool singleEquals = false, + bool(StylesheetParser::* until)() = nullptr); + + // Returns `true` if scanner reached a `,` + bool lookingAtComma(); + + // Consumes an expression until it reaches a top-level comma. If [singleEquals] + // is true, this will allow the Microsoft-style `=` operator at the top level. + Expression* readExpressionUntilComma(bool singleEquals = false); + + // Consumes an expression that doesn't contain any top-level whitespace. + Expression* readSingleExpression(); + + // Consumes a parenthesized expression. + Expression* readParenthesizedExpression(); + + // Not yet evaluated, therefore not Map yet + Expression* readMapExpression(Expression* first, Offset start); + + // Consumes an expression that starts with a `#`. + Expression* readHashExpression(); + + // Consumes the contents of a hex color, after the `#`. + ColorExpression* readColorExpression(StringScannerState start); + + // Returns whether [interpolation] is a plain + // string that can be parsed as a hex color. + bool isHexColor(Interpolation* interpolation) const; + + // Consumes a single hexadecimal digit. + uint8_t readHexDigit(); + + // Consumes an expression that starts with a `+`. + Expression* readPlusExpression(); + + // Consumes an expression that starts with a `-`. + Expression* readMinusExpression(); + + // Consumes an `!important` expression. + StringExpression* readImportantExpression(); + + // Consumes a unary operation expression. + UnaryOpExpression* readUnaryOpExpression(); + + // Consumes a number expression. + NumberExpression* readNumberExpression(); + + // Consumes the decimal component of a number and returns its value, or 0 + // if there is no decimal component. If [allowTrailingDot] is `false`, this + // will throw an error if there's a dot without any numbers following it. + // Otherwise, it will ignore the dot without consuming it. + double tryDecimal(bool allowTrailingDot = false); + + // Consumes the exponent component of a number and returns + // its value, or 1 if there is no exponent component. + double tryExponent(); + + // Consumes a unicode range expression. + StringExpression* readUnicodeRange(); + + // Consumes a variable expression. + VariableExpression* readVariableExpression(bool hoist = true); + + // Consumes a parent selector expression. + ParentExpression* readParentExpression(); + + // Consumes a quoted string expression. + StringExpression* readInterpolatedString(); + + // Consumes an expression that starts like an identifier. + virtual Expression* readIdentifierLike(); + + // If [name] is the name of a function with special syntax, consumes it. + // Otherwise, returns `null`. [start] is the location before the beginning of [name]. + StringExpression* trySpecialFunction(sass::string name, const Offset& start); + + // Consumes the contents of a plain-CSS `min()` or `max()` function into + // [buffer] if one is available. Returns whether this succeeded. If [allowComma] + // is `true` (the default), this allows `CalcValue` productions separated by commas. + bool tryMinMaxContents(InterpolationBuffer& buffer, bool allowComma = true); + + // Consumes a function named [name] containing an optional `InterpolatedDeclarationValue` + // and adds its text to [buffer]. Returns whether such a function could be consumed. + bool tryMinMaxFunction(InterpolationBuffer& buffer, sass::string name = ""); + + // Like [_urlContents], but returns `null` if the URL fails to parse. + // [start] is the position before the beginning of the name. + // [name] is the function's name; it defaults to `"url"`. + Interpolation* tryUrlContents(const Offset& start, sass::string name = ""); + + // Consumes a [url] token that's allowed to contain SassScript. + // Returns either a `StringExpression` or a `FunctionExpression` + Expression* readFunctionOrStringExpression(); + + // Consumes tokens up to "{", "}", ";", or "!". + // This respects string and comment boundaries and supports interpolation. + // Once this interpolation is evaluated, it's expected to be re-parsed. + // Differences from [parseInterpolatedDeclarationValue] include: + // * This does not balance brackets. + // * This does not interpret backslashes, since + // the text is expected to be re-parsed. + // * This supports Sass-style single-line comments. + // * This does not compress adjacent whitespace characters. + Interpolation* readAlmostAnyValue(); + + // Consumes tokens until it reaches a top-level `";"`, `")"`, `"]"`, or `"}"` and + // returns their contents as a string. If [allowEmpty] is `false` (the default), this + // requires at least one token. Unlike [declarationValue], this allows interpolation. + Interpolation* readInterpolatedDeclarationValue(bool allowEmpty = false); + + // Consumes an identifier that may contain interpolation. + Interpolation* readInterpolatedIdentifier(); + + void consumeInterpolatedIdentifierBody(InterpolationBuffer& buffer); + + // Consumes interpolation. + Expression* readSingleInterpolation(); + + // Consumes a list of media queries. + Interpolation* readMediaQueryList(); + + // Consumes a single media query and appends it to [buffer]. + void readMediaQuery(InterpolationBuffer& buffer); + + // Consumes a media query feature. + Interpolation* readMediaFeature(); + + // Returns `false` until the scanner reaches a + // top-level `<`, `>`, or a `=` that's not `==`. + bool lookingAtExpressionEnd(); + + // Consumes an expression until it reaches a + // top-level `<`, `>`, or a `=` that's not `==`. + Expression* readExpressionUntilComparison(); + + // Consumes a `@supports` condition. + SupportsCondition* readSupportsCondition(); + + // Consumes a parenthesized supports condition, or an interpolation. + SupportsCondition* readSupportsConditionInParens(); + + // Tries to consume a negated supports condition. + // Returns `nullptr` if it fails. + SupportsNegation* trySupportsNegation(); + + // Like [identifier], but rejects identifiers that begin with `_` or `-`. + sass::string readPublicIdentifier(); + + // Returns whether the scanner is immediately before an identifier that may contain + // interpolation. This is based on the CSS algorithm, but it assumes all backslashes + // start escapes and it considers interpolation to be valid in an identifier. + // https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier + bool lookingAtInterpolatedIdentifier() const; + + // Returns whether the scanner is immediately before a sequence + // of characters that could be part of an CSS identifier body. + // The identifier body may include interpolation. + bool lookingAtInterpolatedIdentifierBody() const; + + // Returns whether the scanner is immediately before a SassScript expression. + bool lookingAtExpression() const; + + // Consumes a block of [child] statements and passes them, as well as + // the span from [start] to the end of the child block, to [create]. + template + T* withChildren(Statement* (StylesheetParser::* child)(), + const Offset& start, Args... args) + { + StatementVector elements(readChildren(child)); + SharedImpl result = SASS_MEMORY_NEW(T, + scanner.relevantSpanFrom(start), + args..., std::move(elements)); + scanWhitespaceWithoutComments(); + return result.detach(); + } + + // Resolve import of [path] and add imports to [rule] + void resolveDynamicImport( + ImportRule* rule, Offset start, + const sass::string& path); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/permutate.hpp b/src/permutate.hpp index 387704f510..d2f74a0998 100644 --- a/src/permutate.hpp +++ b/src/permutate.hpp @@ -1,6 +1,11 @@ -#ifndef SASS_PATHS_H -#define SASS_PATHS_H +#ifndef SASS_PATHS_HPP +#define SASS_PATHS_HPP +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include #include namespace Sass { @@ -45,7 +50,7 @@ namespace Sass { sass::vector perm; // Create one permutation for state for (size_t i = 0; i < L; i += 1) { - perm.push_back(in.at(i).at(in[i].size() - state[i] - 1)); + perm.emplace_back(in.at(i).at(in[i].size() - state[i] - 1)); } // Current group finished if (state[n] == 0) { @@ -53,7 +58,7 @@ namespace Sass { while (n < L && state[++n] == 0) {} if (n == L) { - out.push_back(perm); + out.emplace_back(perm); break; } @@ -70,7 +75,7 @@ namespace Sass { else { state[n] -= 1; } - out.push_back(perm); + out.emplace_back(perm); } delete[] state; @@ -95,8 +100,8 @@ namespace Sass { // ``` // template - sass::vector> - permutateAlt(const sass::vector>& in) { + sass::vector> permutateAlt( + const sass::vector>& in) { size_t L = in.size(); size_t n = in.size() - 1; @@ -125,7 +130,7 @@ namespace Sass { sass::vector perm; // Create one permutation for state for (size_t i = 0; i < L; i += 1) { - perm.push_back(in.at(i).at(in[i].size() - state[i] - 1)); + perm.emplace_back(in.at(i).at(in[i].size() - state[i] - 1)); } // Current group finished if (state[n] == 0) { @@ -144,14 +149,14 @@ namespace Sass { n = L - 1; } else { - out.push_back(perm); + out.emplace_back(perm); break; } } else { state[n] -= 1; } - out.push_back(perm); + out.emplace_back(perm); } delete[] state; diff --git a/src/plugins.cpp b/src/plugins.cpp index e5c744ff29..2063c67c0c 100644 --- a/src/plugins.cpp +++ b/src/plugins.cpp @@ -1,11 +1,9 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include "output.hpp" #include "plugins.hpp" -#include "util.hpp" + +#include +#include +#include +#include "string_utils.hpp" #ifdef _WIN32 #include @@ -18,7 +16,13 @@ namespace Sass { - Plugins::Plugins(void) { } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Constructor + Plugins::Plugins(void) {} + + // Destructor Plugins::~Plugins(void) { for (auto function : functions) { @@ -37,7 +41,6 @@ namespace Sass { // 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; @@ -47,7 +50,7 @@ namespace Sass { size_t pos = sass::string(our_version).find('.', 0); if (pos != sass::string::npos) pos = sass::string(our_version).find('.', pos + 1); - // if we do not have two dots we fallback to compare complete string + // if we do not have two dots we fall back to compare complete string if (pos == sass::string::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; } @@ -59,36 +62,58 @@ namespace Sass { { typedef const char* (*__plugin_version__)(void); - typedef Sass_Function_List (*__plugin_load_fns__)(void); - typedef Sass_Importer_List (*__plugin_load_imps__)(void); + typedef const char* (*__plugin_set_seed__)(uint32_t); + typedef struct SassFunctionList* (*__plugin_load_fns__)(void); + typedef struct SassImporterList* (*__plugin_load_imps__)(void); + + // Pass seed by env variable until further notice + sass::sstream strm; strm << getHashSeed(); + // SET_ENV("SASS_HASH_SEED", strm.str().c_str()); if (LOAD_LIB(plugin, path)) { - // try to load initial function to query libsass version suppor + // try to load initial function to query libsass version support 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_set_seed__, plugin_set_seed_function, "libsass_set_seed_function")) + { + plugin_set_seed_function(getHashSeed()); + } + // try to get import address for "libsass_load_functions" if (LOAD_LIB_FN(__plugin_load_fns__, plugin_load_functions, "libsass_load_functions")) { - Sass_Function_List fns = plugin_load_functions(), _p = fns; - while (fns && *fns) { functions.push_back(*fns); ++ fns; } - sass_free_memory(_p); // only delete the container, items not yet + struct SassFunctionList* fns = plugin_load_functions(); + while (sass_function_list_size(fns) > 0) { + functions.emplace_back(sass_function_list_shift(fns)); + } + // only delete the container, items not yet + // albeit the list should be empty by now + sass_delete_function_list(fns); } // try to get import address for "libsass_load_importers" if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_importers, "libsass_load_importers")) { - Sass_Importer_List imps = plugin_load_importers(), _p = imps; - while (imps && *imps) { importers.push_back(*imps); ++ imps; } - sass_free_memory(_p); // only delete the container, items not yet + struct SassImporterList* imps = plugin_load_importers(); + while (sass_importer_list_size(imps) > 0) { + importers.emplace_back(sass_importer_list_shift(imps)); + } + // only delete the container, items not yet + // albeit the list should be empty by now + sass_delete_importer_list(imps); } // try to get import address for "libsass_load_headers" if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_headers, "libsass_load_headers")) { - Sass_Importer_List imps = plugin_load_headers(), _p = imps; - while (imps && *imps) { headers.push_back(*imps); ++ imps; } - sass_free_memory(_p); // only delete the container, items not yet + struct SassImporterList* imps = plugin_load_headers(); + while (sass_importer_list_size(imps) > 0) { + headers.emplace_back(sass_importer_list_shift(imps)); + } + // only delete the container, items not yet + // albeit the list should be empty by now + sass_delete_importer_list(imps); } // success return true; @@ -96,16 +121,16 @@ namespace Sass { else { // print debug message to stderr (should not happen) - std::cerr << "failed loading 'libsass_support' in <" << path << ">" << std::endl; - if (const char* dlsym_error = dlerror()) std::cerr << dlsym_error << std::endl; + std::cerr << "failed loading 'libsass_support' in <" << path << ">" << STRMLF; + if (const char* dlsym_error = dlerror()) std::cerr << dlsym_error << STRMLF; CLOSE_LIB(plugin); } } else { // print debug message to stderr (should not happen) - std::cerr << "failed loading plugin <" << path << ">" << std::endl; - if (const char* dlopen_error = dlerror()) std::cerr << dlopen_error << std::endl; + std::cerr << "failed loading plugin <" << path << ">" << STRMLF; + if (const char* dlopen_error = dlerror()) std::cerr << dlopen_error << STRMLF; } return false; @@ -128,7 +153,7 @@ namespace Sass { // trailing slash is guaranteed sass::string globsrch(path + "*.dll"); // convert to wide chars (utf16) for system call - std::wstring wglobsrch(UTF_8::convert_to_utf16(globsrch)); + sass::wstring wglobsrch(Unicode::utf8to16(globsrch)); HANDLE hFile = FindFirstFileW(wglobsrch.c_str(), &data); // check if system called returned a result // ToDo: maybe we should print a debug message @@ -140,9 +165,9 @@ namespace Sass { try { // the system will report the filenames with wide chars (utf16) - sass::string entry = UTF_8::convert_from_utf16(data.cFileName); + sass::string entry = Unicode::utf16to8(data.cFileName); // check if file ending matches exactly - if (!ends_with(entry, ".dll")) continue; + if (!StringUtils::endsWith(entry, ".dll", 4)) continue; // load the plugin and increase counter if (load_plugin(path + entry)) ++ loaded; // check if there should be more entries @@ -154,7 +179,7 @@ namespace Sass { { // report the error to the console (should not happen) // seems like we got strange data from the system call? - std::cerr << "filename in plugin path has invalid utf8?" << std::endl; + std::cerr << "filename in plugin path has invalid utf8?" << STRMLF; } } } @@ -162,19 +187,20 @@ namespace Sass { { // report the error to the console (should not happen) // implementors should make sure to provide valid utf8 - std::cerr << "plugin path contains invalid utf8" << std::endl; + std::cerr << "plugin path contains invalid utf8" << STRMLF; } #else DIR *dp; struct dirent *dirp; + using namespace StringUtils; if((dp = opendir(path.c_str())) == NULL) return -1; while ((dirp = readdir(dp)) != NULL) { #if __APPLE__ - if (!ends_with(dirp->d_name, ".dylib")) continue; + if (!endsWithIgnoreCase(dirp->d_name, ".dylib", 6)) continue; #else - if (!ends_with(dirp->d_name, ".so")) continue; + if (!endsWithIgnoreCase(dirp->d_name, ".so", 3)) continue; #endif if (load_plugin(path + dirp->d_name)) ++ loaded; } diff --git a/src/plugins.hpp b/src/plugins.hpp index c600df45d2..f7683598f7 100644 --- a/src/plugins.hpp +++ b/src/plugins.hpp @@ -1,14 +1,18 @@ -#ifndef SASS_PLUGINS_H -#define SASS_PLUGINS_H +#ifndef SASS_PLUGINS_HPP +#define SASS_PLUGINS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include #include -#include "utf8_string.hpp" +#include "unicode.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(var, path) HMODULE var = LoadLibraryW(Unicode::utf8to16(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) @@ -31,8 +35,8 @@ namespace Sass { class Plugins { public: // c-tor - Plugins(void); - ~Plugins(void); + Plugins(); + ~Plugins(); public: // methods // load one specific plugin @@ -41,14 +45,29 @@ namespace Sass { size_t load_plugins(const sass::string& path); public: // public accessors - const sass::vector get_headers(void) { return headers; } - const sass::vector get_importers(void) { return importers; } - const sass::vector get_functions(void) { return functions; } - - private: // private vars - sass::vector headers; - sass::vector importers; - sass::vector functions; + void consume_headers(sass::vector& destination) { + destination.insert(destination.end(), + std::make_move_iterator(headers.begin()), + std::make_move_iterator(headers.end())); + headers.clear(); + } + void consume_importers(sass::vector& destination) { + destination.insert(destination.end(), + std::make_move_iterator(importers.begin()), + std::make_move_iterator(importers.end())); + importers.clear(); + } + void consume_functions(sass::vector& destination) { + destination.insert(destination.end(), + std::make_move_iterator(functions.begin()), + std::make_move_iterator(functions.end())); + functions.clear(); + } + + private: // private vars + sass::vector headers; + sass::vector importers; + sass::vector functions; }; diff --git a/src/position.cpp b/src/position.cpp deleted file mode 100644 index 990185332a..0000000000 --- a/src/position.cpp +++ /dev/null @@ -1,163 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "position.hpp" -#include "source.hpp" - -namespace Sass { - - - Offset::Offset(const char chr) - : line(chr == '\n' ? 1 : 0), - column(chr == '\n' ? 0 : 1) - {} - - Offset::Offset(const char* string) - : line(0), column(0) - { - *this = inc(string, string + strlen(string)); - } - - Offset::Offset(const sass::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) { } - - // init/create instance from const char substring - Offset Offset::init(const char* beg, const char* end) - { - Offset offset(0, 0); - if (end == 0) { - end = beg + strlen(beg); - } - offset.add(beg, end); - return offset; - } - - // increase offset by given string (mostly called by lexer) - // increase line counter and count columns on the last line - Offset Offset::add(const char* begin, const char* end) - { - if (end == 0) return *this; - while (begin < end && *begin) { - if (*begin == '\n') { - ++ line; - // start new line - column = 0; - } else { - // do not count any utf8 continuation bytes - // https://stackoverflow.com/a/9356203/1550314 - // https://en.wikipedia.org/wiki/UTF-8#Description - unsigned char chr = *begin; - // Ignore all `10xxxxxx` chars - // '0xxxxxxx' are ASCII chars - // '11xxxxxx' are utf8 starts - // 64 => initial utf8 byte - // 128 => regular ASCII char - if ((chr & 192) != 128) { - // regular ASCII char - column += 1; - } - } - ++ begin; - } - return *this; - } - - // increase offset by given string (mostly called by lexer) - // increase line counter and count columns on the last line - Offset Offset::inc(const char* begin, const char* end) const - { - Offset offset(line, column); - offset.add(begin, end); - return offset; - } - - bool Offset::operator== (const Offset &pos) const - { - return line == pos.line && column == pos.column; - } - - bool Offset::operator!= (const Offset &pos) const - { - return line != pos.line || column != pos.column; - } - - void Offset::operator+= (const Offset &off) - { - *this = Offset(line + off.line, off.line > 0 ? off.column : column + off.column); - } - - Offset Offset::operator+ (const Offset &off) const - { - return Offset(line + off.line, off.line > 0 ? off.column : column + off.column); - } - - Offset Offset::operator- (const Offset &off) const - { - return Offset(line - off.line, off.line == line ? column - off.column : column); - } - - Position::Position(const size_t file) - : Offset(0, 0), file(file) { } - - Position::Position(const size_t file, const Offset& offset) - : Offset(offset), file(file) { } - - Position::Position(const size_t line, const size_t column) - : Offset(line, column), file(-1) { } - - Position::Position(const size_t file, const size_t line, const size_t column) - : Offset(line, column), file(file) { } - - - SourceSpan::SourceSpan(const char* path) - : source(SASS_MEMORY_NEW(SynthFile, path)), position(0, 0), offset(0, 0) { } - - SourceSpan::SourceSpan(SourceDataObj source, const Offset& position, const Offset& offset) - : source(source), position(position), offset(offset) { } - - Position Position::add(const char* begin, const char* end) - { - Offset::add(begin, end); - return *this; - } - - Position Position::inc(const char* begin, const char* end) const - { - Offset offset(line, column); - offset = offset.inc(begin, end); - return Position(file, offset); - } - - bool Position::operator== (const Position &pos) const - { - return file == pos.file && line == pos.line && column == pos.column; - } - - bool Position::operator!= (const Position &pos) const - { - 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 : column + off.column); - } - - const Position Position::operator+ (const Offset &off) const - { - return Position(file, line + off.line, off.line > 0 ? off.column : column + off.column); - } - - const Offset Position::operator- (const Offset &off) const - { - return Offset(line - off.line, off.line == line ? column - off.column : column); - } - -} diff --git a/src/position.hpp b/src/position.hpp deleted file mode 100644 index 45d78a0494..0000000000 --- a/src/position.hpp +++ /dev/null @@ -1,147 +0,0 @@ -#ifndef SASS_POSITION_H -#define SASS_POSITION_H - -#include -#include -#include "source_data.hpp" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - - class Offset { - - public: // c-tor - Offset(const char chr); - Offset(const char* string); - Offset(const sass::string& text); - Offset(const size_t line, const size_t column); - - // return new position, incremented by the given string - Offset add(const char* begin, const char* end); - Offset inc(const char* begin, const char* end) const; - - // init/create instance from const char substring - static Offset init(const char* beg, const char* end); - - public: // overload operators for position - void operator+= (const Offset &pos); - bool operator== (const Offset &pos) const; - bool operator!= (const Offset &pos) const; - Offset operator+ (const Offset &off) const; - Offset operator- (const Offset &off) const; - - public: // overload output stream operator - // friend std::ostream& operator<<(std::ostream& strm, const Offset& off); - - public: - Offset off() { return *this; } - - public: - size_t line; - size_t column; - - }; - - class Position : public Offset { - - public: // c-tor - Position(const size_t file); // line(0), column(0) - Position(const size_t file, const Offset& offset); - Position(const size_t line, const size_t column); // file(-1) - Position(const size_t file, const size_t line, const size_t column); - - 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; - const Offset operator- (const Offset &off) const; - // return new position, incremented by the given string - Position add(const char* begin, const char* end); - Position inc(const char* begin, const char* end) const; - - public: // overload output stream operator - // friend std::ostream& operator<<(std::ostream& strm, const Position& pos); - - public: - size_t file; - - }; - - // Token type for representing lexed chunks of text - class Token { - public: - const char* prefix; - const char* begin; - const char* end; - - Token() - : prefix(0), begin(0), end(0) { } - Token(const char* b, const char* e) - : prefix(b), begin(b), end(e) { } - Token(const char* str) - : prefix(str), begin(str), end(str + strlen(str)) { } - Token(const char* p, const char* b, const char* e) - : prefix(p), begin(b), end(e) { } - - size_t length() const { return end - begin; } - sass::string ws_before() const { return sass::string(prefix, begin); } - sass::string to_string() const { return sass::string(begin, end); } - sass::string time_wspace() const { - sass::string str(to_string()); - sass::string whitespaces(" \t\f\v\n\r"); - return str.erase(str.find_last_not_of(whitespaces)+1); - } - - operator bool() { return begin && end && begin >= end; } - operator sass::string() { return to_string(); } - - bool operator==(Token t) { return to_string() == t.to_string(); } - }; - - class SourceSpan { - - public: - - SourceSpan(const char* path); - - SourceSpan(SourceDataObj source, - const Offset& position = Offset(0, 0), - const Offset& offset = Offset(0, 0)); - - const char* getPath() const { - return source->getPath(); - } - - const char* getRawData() const { - return source->getRawData(); - } - - Offset getPosition() const { - return position; - } - - size_t getLine() const { - return position.line + 1; - } - - size_t getColumn() const { - return position.column + 1; - } - - size_t getSrcId() const { - return source == nullptr - ? std::string::npos - : source->getSrcId(); - } - - SourceDataObj source; - Offset position; - Offset offset; - - }; - -} - -#endif diff --git a/src/position/offset.cpp b/src/position/offset.cpp new file mode 100644 index 0000000000..b3b40f766a --- /dev/null +++ b/src/position/offset.cpp @@ -0,0 +1,216 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include +#include "offset.hpp" +#include "charcode.hpp" +#include "character.hpp" +#include "util_string.hpp" + +namespace Sass { + + using namespace Charcode; + using namespace Character; + + // Empty constructor + Offset::Offset() : + line(0), + column(0) + {} + + // Create an `Offset` from the given string + // Will use `append` internally on all chars + Offset::Offset(uint8_t character) + : line(0), column(0) + { + plus(character); + } + + // Create an `Offset` from the given string + // Will use `append` internally on all chars + Offset::Offset(const sass::string& text) + : line(0), column(0) + { + for (uint8_t character : text) { + plus(character); + } + } + + // Create an `Offset` from the given char star + // Will use `append` internally on all chars + Offset::Offset(const char* text, const char* end) + : line(0), column(0) + { + if (end == nullptr) { + while (*text != 0) { + plus(*text++); + } + } + else { + while (text < end) { + plus(*text++); + } + } + } + + // Append `character` to increment offset + void Offset::plus(uint8_t character) + { + switch (character) { + case $lf: + line += 1; + column = 0; + break; + case $space: + case $tab: + case $vt: + case $ff: + case $cr: + column += 1; + break; + default: + // skip over 10xxxxxx and 01xxxxxx + // count ASCII and initial utf8 bytes + if (Character::isCharacter(character)) { + // 64 => initial utf8 byte + // 128 => regular ASCII char + column += 1; + } + break; + } + } + + // Append `character` to increment offset + void Offset::plus(const sass::string& text) + { + for (uint8_t character : text) { + plus(character); + } + } + + // Create offset with given `line` and `column` + // Needs static constructor to avoid ambiguity + Offset Offset::init(uint32_t line, uint32_t column) + { + Offset offset; + offset.line = line; + offset.column = column; + return offset; + } + + // Calculate the distance between start and end + Offset Offset::distance(const Offset& start, const Offset& end) + { + Offset rv(end); + // Both are on the same line + if (start.line == end.line) { + // Get distance between columns + rv.column -= start.column; + // Line distance is zero + rv.line = 0; + } + else { + // Get distance between lines + rv.line -= start.line; + // Columns don't need to be changed + // Since we land on another line, we + // need to reach the same end column + } + return rv; + } + + // Implement comparison operators + bool Offset::operator<(const Offset& rhs) const + { + if (line == rhs.line) { + return column < rhs.column; + } + return line < rhs.line; + } + + // Implement equality operators + bool Offset::operator==(const Offset& rhs) const + { + return line == rhs.line + && column == rhs.column; + } + + // Implement assign and addition operator + void Offset::operator+= (const Offset& rhs) + { + // lines are always summed up + line += rhs.line; + // columns may need to be reset + if (rhs.line == 0) { + column += rhs.column; + } + else { + column = rhs.column; + } + } + + // Implement addition operator + Offset Offset::operator+ (const Offset& rhs) const + { + Offset rv(*this); + rv += rhs; + return rv; + } + + // Implement multiply operator + Offset Offset::operator* (uint32_t mul) const + { + Offset rv(*this); + if (rv.line == 0) { + rv.column *= mul; + } + else { + rv.line *= mul; + } + return rv; + } + + // Move/increment char star `text` by `offset` + const char* Offset::move(const char* text, Offset offset) + { + while (offset.line > 0) { + while (text[0] != '\n') { + if (text[0] == '\0') { + return nullptr; + } + text += 1; + } + if (text[0] == '\0') { + return nullptr; + } + offset.line -= 1; + text += 1; + } + while (offset.column > 0) { + while (!Character::isCharacter(text[0])) { + if (text[0] == '\0') { + return nullptr; + } + if (text[0] == '\n') { + return nullptr; + } + text += 1; + } + if (text[0] == '\0') { + return nullptr; + } + if (text[0] == '\n') { + return nullptr; + } + offset.column -= 1; + text += 1; + } + if (text[0] == '\n') { + return nullptr; + } + return text; + } + // EO Offset::move + +} diff --git a/src/prelexer.cpp b/src/prelexer.cpp deleted file mode 100644 index acd3061e37..0000000000 --- a/src/prelexer.cpp +++ /dev/null @@ -1,1780 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include "util.hpp" -#include "util_string.hpp" -#include "position.hpp" -#include "prelexer.hpp" -#include "constants.hpp" - - -namespace Sass { - // using namespace Lexer; - using namespace Constants; - - namespace Prelexer { - - - /* - - def string_re(open, close) - /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m - end - end - - # A hash of regular expressions that are used for tokenizing strings. - # - # The key is a `[Symbol, Boolean]` pair. - # The symbol represents which style of quotation to use, - # while the boolean represents whether or not the string - # is following an interpolated segment. - STRING_REGULAR_EXPRESSIONS = { - :double => { - /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m - false => string_re('"', '"'), - true => string_re('', '"') - }, - :single => { - false => string_re("'", "'"), - true => string_re('', "'") - }, - :uri => { - false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - }, - # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a - # non-standard version of http://www.w3.org/TR/css3-conditional/ - :url_prefix => { - false => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - }, - :domain => { - false => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - } - } - */ - - /* - /#{open} - ( - \\. - | - \# (?!\{) - | - [^#{close}\\#] - )* - (#{close}|#\{) - /m - false => string_re('"', '"'), - true => string_re('', '"') - */ - extern const char string_double_negates[] = "\"\\#"; - const char* re_string_double_close(const char* src) - { - return sequence < - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_double_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'"'>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - const char* re_string_double_open(const char* src) - { - return sequence < - // quoted string opener - exactly <'"'>, - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_double_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'"'>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - extern const char string_single_negates[] = "'\\#"; - const char* re_string_single_close(const char* src) - { - return sequence < - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_single_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'\''>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - const char* re_string_single_open(const char* src) - { - return sequence < - // quoted string opener - exactly <'\''>, - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_single_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'\''>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - /* - :uri => { - false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - }, - */ - const char* re_string_uri_close(const char* src) - { - return sequence < - non_greedy< - alternatives< - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - >, - alternatives< - sequence < optional < W >, exactly <')'> >, - lookahead < exactly< hash_lbrace > > - > - >, - optional < - sequence < optional < W >, exactly <')'> > - > - >(src); - } - - const char* re_string_uri_open(const char* src) - { - return sequence < - exactly <'u'>, - exactly <'r'>, - exactly <'l'>, - exactly <'('>, - W, - alternatives< - quoted_string, - non_greedy< - alternatives< - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - >, - alternatives< - sequence < W, exactly <')'> >, - exactly< hash_lbrace > - > - > - > - >(src); - } - - // Match a line comment (/.*?(?=\n|\r\n?|\f|\Z)/. - const char* line_comment(const char* src) - { - return sequence< - exactly < - slash_slash - >, - non_greedy< - any_char, - end_of_line - > - >(src); - } - - // Match a block comment. - const char* block_comment(const char* src) - { - return sequence< - delimited_by< - slash_star, - star_slash, - false - > - >(src); - } - /* not use anymore - remove? - const char* block_comment_prefix(const char* src) { - return exactly(src); - } - // Match either comment. - const char* comment(const char* src) { - return line_comment(src); - } - */ - - // Match zero plus white-space or line_comments - const char* optional_css_whitespace(const char* src) { - return zero_plus< alternatives >(src); - } - const char* css_whitespace(const char* src) { - return one_plus< alternatives >(src); - } - // Match optional_css_whitepace plus block_comments - const char* optional_css_comments(const char* src) { - return zero_plus< alternatives >(src); - } - const char* css_comments(const char* src) { - return one_plus< alternatives >(src); - } - - // Match one backslash escaped char /\\./ - const char* escape_seq(const char* src) - { - return sequence< - exactly<'\\'>, - alternatives < - minmax_range< - 1, 3, xdigit - >, - any_char - >, - optional < - exactly <' '> - > - >(src); - } - - // Match identifier start - const char* identifier_alpha(const char* src) - { - return alternatives< - unicode_seq, - alpha, - nonascii, - exactly<'-'>, - exactly<'_'>, - NONASCII, - ESCAPE, - escape_seq - >(src); - } - - // Match identifier after start - const char* identifier_alnum(const char* src) - { - return alternatives< - unicode_seq, - alnum, - nonascii, - exactly<'-'>, - exactly<'_'>, - NONASCII, - ESCAPE, - escape_seq - >(src); - } - - // Match CSS identifiers. - const char* strict_identifier(const char* src) - { - return sequence< - one_plus < strict_identifier_alpha >, - zero_plus < strict_identifier_alnum > - // word_boundary not needed - >(src); - } - - // Match CSS identifiers. - const char* identifier(const char* src) - { - return sequence< - zero_plus< exactly<'-'> >, - one_plus < identifier_alpha >, - zero_plus < identifier_alnum > - // word_boundary not needed - >(src); - } - - const char* strict_identifier_alpha(const char* src) - { - return alternatives < - alpha, - nonascii, - escape_seq, - exactly<'_'> - >(src); - } - - const char* strict_identifier_alnum(const char* src) - { - return alternatives < - alnum, - nonascii, - escape_seq, - exactly<'_'> - >(src); - } - - // Match a single CSS unit - const char* one_unit(const char* src) - { - return sequence < - optional < exactly <'-'> >, - strict_identifier_alpha, - zero_plus < alternatives< - strict_identifier_alnum, - sequence < - one_plus < exactly<'-'> >, - strict_identifier_alpha - > - > > - >(src); - } - - // Match numerator/denominator CSS units - const char* multiple_units(const char* src) - { - return - sequence < - one_unit, - zero_plus < - sequence < - exactly <'*'>, - one_unit - > - > - >(src); - } - - // Match complex CSS unit identifiers - const char* unit_identifier(const char* src) - { - return sequence < - multiple_units, - optional < - sequence < - exactly <'/'>, - negate < sequence < - exactly < calc_fn_kwd >, - exactly < '(' > - > >, - multiple_units - > > - >(src); - } - - const char* identifier_alnums(const char* src) - { - return one_plus< identifier_alnum >(src); - } - - // Match number prefix ([\+\-]+) - const char* number_prefix(const char* src) { - return alternatives < - exactly < '+' >, - sequence < - exactly < '-' >, - optional_css_whitespace, - exactly< '-' > - > - >(src); - } - - // Match interpolant schemas - const char* identifier_schema(const char* src) { - - return sequence < - one_plus < - sequence < - zero_plus < - alternatives < - sequence < - optional < - exactly <'$'> - >, - identifier - >, - exactly <'-'> - > - >, - interpolant, - zero_plus < - alternatives < - digits, - sequence < - optional < - exactly <'$'> - >, - identifier - >, - quoted_string, - exactly<'-'> - > - > - > - >, - negate < - exactly<'%'> - > - > (src); - } - - // interpolants can be recursive/nested - const char* interpolant(const char* src) { - return recursive_scopes< exactly, exactly >(src); - } - - // $re_squote = /'(?:$re_itplnt|\\.|[^'])*'/ - const char* single_quoted_string(const char* src) { - // match a single quoted string, while skipping interpolants - return sequence < - exactly <'\''>, - zero_plus < - alternatives < - // skip escapes - sequence < - exactly < '\\' >, - re_linebreak - >, - escape_seq, - unicode_seq, - // skip interpolants - interpolant, - // skip non delimiters - any_char_but < '\'' > - > - >, - exactly <'\''> - >(src); - } - - // $re_dquote = /"(?:$re_itp|\\.|[^"])*"/ - const char* double_quoted_string(const char* src) { - // match a single quoted string, while skipping interpolants - return sequence < - exactly <'"'>, - zero_plus < - alternatives < - // skip escapes - sequence < - exactly < '\\' >, - re_linebreak - >, - escape_seq, - unicode_seq, - // skip interpolants - interpolant, - // skip non delimiters - any_char_but < '"' > - > - >, - exactly <'"'> - >(src); - } - - // $re_quoted = /(?:$re_squote|$re_dquote)/ - const char* quoted_string(const char* src) { - // match a quoted string, while skipping interpolants - return alternatives< - single_quoted_string, - double_quoted_string - >(src); - } - - const char* sass_value(const char* src) { - return alternatives < - quoted_string, - identifier, - percentage, - hex, - dimension, - number - >(src); - } - - // this is basically `one_plus < sass_value >` - // takes care to not parse invalid combinations - const char* value_combinations(const char* src) { - // `2px-2px` is invalid combo - bool was_number = false; - const char* pos; - while (src) { - if ((pos = alternatives < quoted_string, identifier, percentage, hex >(src))) { - was_number = false; - src = pos; - } else if (!was_number && !exactly<'+'>(src) && (pos = alternatives < dimension, number >(src))) { - was_number = true; - src = pos; - } else { - break; - } - } - return src; - } - - // must be at least one interpolant - // can be surrounded by sass values - // make sure to never parse (dim)(dim) - // since this wrongly consumes `2px-1px` - // `2px1px` is valid number (unit `px1px`) - const char* value_schema(const char* src) - { - return sequence < - one_plus < - sequence < - optional < value_combinations >, - interpolant, - optional < value_combinations > - > - > - >(src); - } - - // Match CSS '@' keywords. - const char* at_keyword(const char* src) { - return sequence, identifier>(src); - } - - /* - tok(%r{ - ( - \\. - | - (?!url\() - [^"'/\#!;\{\}] # " - | - /(?![\*\/]) - | - \#(?!\{) - | - !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed. - )+ - }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri || - interpolation(:warn_for_color) - */ - const char* re_almost_any_value_token(const char* src) { - - return alternatives < - one_plus < - alternatives < - sequence < - exactly <'\\'>, - any_char - >, - sequence < - negate < - uri_prefix - >, - neg_class_char < - almost_any_value_class - > - >, - sequence < - exactly <'/'>, - negate < - alternatives < - exactly <'/'>, - exactly <'*'> - > - > - >, - sequence < - exactly <'\\'>, - exactly <'#'>, - negate < - exactly <'{'> - > - >, - sequence < - exactly <'!'>, - negate < - alpha - > - > - > - >, - block_comment, - line_comment, - interpolant, - space, - sequence < - exactly<'u'>, - exactly<'r'>, - exactly<'l'>, - exactly<'('>, - zero_plus < - alternatives < - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - > - >, - // false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - // true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - exactly<')'> - > - >(src); - } - - /* - DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for, - :each, :while, :if, :else, :extend, :import, :media, :charset, :content, - :_moz_document, :at_root, :error] - */ - const char* re_special_directive(const char* src) { - return alternatives < - word < mixin_kwd >, - word < include_kwd >, - word < function_kwd >, - word < return_kwd >, - word < debug_kwd >, - word < warn_kwd >, - word < for_kwd >, - word < each_kwd >, - word < while_kwd >, - word < if_kwd >, - word < else_kwd >, - word < extend_kwd >, - word < import_kwd >, - word < media_kwd >, - word < charset_kwd >, - word < content_kwd >, - // exactly < moz_document_kwd >, - word < at_root_kwd >, - word < error_kwd > - >(src); - } - - const char* re_prefixed_directive(const char* src) { - return sequence < - optional < - sequence < - exactly <'-'>, - one_plus < alnum >, - exactly <'-'> - > - >, - exactly < supports_kwd > - >(src); - } - - const char* re_reference_combinator(const char* src) { - return sequence < - optional < - sequence < - zero_plus < - exactly <'-'> - >, - identifier, - exactly <'|'> - > - >, - zero_plus < - exactly <'-'> - >, - identifier - >(src); - } - - const char* static_reference_combinator(const char* src) { - return sequence < - exactly <'/'>, - re_reference_combinator, - exactly <'/'> - >(src); - } - - const char* schema_reference_combinator(const char* src) { - return sequence < - exactly <'/'>, - optional < - sequence < - css_ip_identifier, - exactly <'|'> - > - >, - css_ip_identifier, - exactly <'/'> - > (src); - } - - const char* kwd_import(const char* src) { - return word(src); - } - - const char* kwd_at_root(const char* src) { - return word(src); - } - - const char* kwd_with_directive(const char* src) { - return word(src); - } - - const char* kwd_without_directive(const char* src) { - return word(src); - } - - const char* kwd_media(const char* src) { - return word(src); - } - - const char* kwd_supports_directive(const char* src) { - return word(src); - } - - const char* kwd_mixin(const char* src) { - return word(src); - } - - const char* kwd_function(const char* src) { - return word(src); - } - - const char* kwd_return_directive(const char* src) { - return word(src); - } - - const char* kwd_include_directive(const char* src) { - return word(src); - } - - const char* kwd_content_directive(const char* src) { - return word(src); - } - - const char* kwd_charset_directive(const char* src) { - return word(src); - } - - const char* kwd_extend(const char* src) { - return word(src); - } - - - const char* kwd_if_directive(const char* src) { - return word(src); - } - - const char* kwd_else_directive(const char* src) { - return word(src); - } - const char* elseif_directive(const char* src) { - return sequence< exactly< else_kwd >, - optional_css_comments, - word< if_after_else_kwd > >(src); - } - - const char* kwd_for_directive(const char* src) { - return word(src); - } - - const char* kwd_from(const char* src) { - return word(src); - } - - const char* kwd_to(const char* src) { - return word(src); - } - - const char* kwd_through(const char* src) { - return word(src); - } - - const char* kwd_each_directive(const char* src) { - return word(src); - } - - const char* kwd_in(const char* src) { - return word(src); - } - - const char* kwd_while_directive(const char* src) { - return word(src); - } - - const char* name(const char* src) { - return one_plus< alternatives< alnum, - exactly<'-'>, - exactly<'_'>, - escape_seq > >(src); - } - - const char* kwd_warn(const char* src) { - return word(src); - } - - const char* kwd_err(const char* src) { - return word(src); - } - - const char* kwd_dbg(const char* src) { - return word(src); - } - - /* not used anymore - remove? - const char* directive(const char* src) { - return sequence< exactly<'@'>, identifier >(src); - } */ - - const char* kwd_null(const char* src) { - return word(src); - } - - const char* css_identifier(const char* src) { - return sequence < - zero_plus < - exactly <'-'> - >, - identifier - >(src); - } - - const char* css_ip_identifier(const char* src) { - return sequence < - zero_plus < - exactly <'-'> - >, - alternatives < - identifier, - interpolant - > - >(src); - } - - // Match CSS type selectors - const char* namespace_prefix(const char* src) { - return sequence < - optional < - alternatives < - exactly <'*'>, - css_identifier - > - >, - exactly <'|'>, - negate < - exactly <'='> - > - >(src); - } - - // Match CSS type selectors - const char* namespace_schema(const char* src) { - return sequence < - optional < - alternatives < - exactly <'*'>, - css_ip_identifier - > - >, - exactly<'|'>, - negate < - exactly <'='> - > - >(src); - } - - const char* hyphens_and_identifier(const char* src) { - return sequence< zero_plus< exactly< '-' > >, identifier_alnums >(src); - } - const char* hyphens_and_name(const char* src) { - return sequence< zero_plus< exactly< '-' > >, name >(src); - } - const char* universal(const char* src) { - return sequence< optional, exactly<'*'> >(src); - } - // Match CSS id names. - const char* id_name(const char* src) { - return sequence, identifier_alnums >(src); - } - // Match CSS class names. - const char* class_name(const char* src) { - return sequence, identifier >(src); - } - // Attribute name in an attribute selector. - const char* attribute_name(const char* src) { - return alternatives< sequence< optional, identifier>, - identifier >(src); - } - // match placeholder selectors - const char* placeholder(const char* src) { - return sequence, identifier_alnums >(src); - } - // Match CSS numeric constants. - - const char* op(const char* src) { - return class_char(src); - } - const char* sign(const char* src) { - return class_char(src); - } - const char* unsigned_number(const char* src) { - return alternatives, - exactly<'.'>, - one_plus >, - digits>(src); - } - const char* number(const char* src) { - return sequence< - optional, - unsigned_number, - optional< - sequence< - exactly<'e'>, - optional, - unsigned_number - > - > - >(src); - } - const char* coefficient(const char* src) { - return alternatives< sequence< optional, digits >, - sign >(src); - } - const char* binomial(const char* src) { - return sequence < - optional < sign >, - optional < digits >, - exactly <'n'>, - zero_plus < sequence < - optional_css_whitespace, sign, - optional_css_whitespace, digits - > > - >(src); - } - const char* percentage(const char* src) { - return sequence< number, exactly<'%'> >(src); - } - const char* ampersand(const char* src) { - return exactly<'&'>(src); - } - - /* not used anymore - remove? - const char* em(const char* src) { - return sequence< number, exactly >(src); - } */ - const char* dimension(const char* src) { - return sequence(src); - } - const char* hex(const char* src) { - const char* p = sequence< exactly<'#'>, one_plus >(src); - ptrdiff_t len = p - src; - return (len != 4 && len != 7) ? 0 : p; - } - const char* hexa(const char* src) { - const char* p = sequence< exactly<'#'>, one_plus >(src); - ptrdiff_t len = p - src; - return (len != 5 && len != 9) ? 0 : p; - } - const char* hex0(const char* src) { - const char* p = sequence< exactly<'0'>, exactly<'x'>, one_plus >(src); - ptrdiff_t len = p - src; - return (len != 5 && len != 8) ? 0 : p; - } - - /* no longer used - remove? - const char* rgb_prefix(const char* src) { - return word(src); - }*/ - // Match CSS uri specifiers. - - const char* uri_prefix(const char* src) { - return sequence < - exactly < - url_kwd - >, - zero_plus < - sequence < - exactly <'-'>, - one_plus < - alpha - > - > - >, - exactly <'('> - >(src); - } - - // TODO: rename the following two functions - /* no longer used - remove? - const char* uri(const char* src) { - return sequence< exactly, - optional, - quoted_string, - optional, - exactly<')'> >(src); - }*/ - /* no longer used - remove? - const char* url_value(const char* src) { - return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol - one_plus< sequence< zero_plus< exactly<'/'> >, filename > >, // one or more folders and/or trailing filename - optional< exactly<'/'> > >(src); - }*/ - /* no longer used - remove? - const char* url_schema(const char* src) { - return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol - filename_schema >(src); // optional trailing slash - }*/ - // Match CSS "!important" keyword. - const char* kwd_important(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match CSS "!optional" keyword. - const char* kwd_optional(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match Sass "!default" keyword. - const char* default_flag(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match Sass "!global" keyword. - const char* global_flag(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match CSS pseudo-class/element prefixes. - const char* pseudo_prefix(const char* src) { - return sequence< exactly<':'>, optional< exactly<':'> > >(src); - } - // Match CSS function call openers. - const char* functional_schema(const char* src) { - return sequence < - one_plus < - sequence < - zero_plus < - alternatives < - identifier, - exactly <'-'> - > - >, - one_plus < - sequence < - interpolant, - alternatives < - digits, - identifier, - exactly<'+'>, - exactly<'-'> - > - > - > - > - >, - negate < - exactly <'%'> - >, - lookahead < - exactly <'('> - > - > (src); - } - - const char* re_nothing(const char* src) { - return src; - } - - const char* re_functional(const char* src) { - return sequence< identifier, optional < block_comment >, exactly<'('> >(src); - } - const char* re_pseudo_selector(const char* src) { - return sequence< identifier, optional < block_comment >, exactly<'('> >(src); - } - // Match the CSS negation pseudo-class. - const char* pseudo_not(const char* src) { - return word< pseudo_not_fn_kwd >(src); - } - // Match CSS 'odd' and 'even' keywords for functional pseudo-classes. - const char* even(const char* src) { - return word(src); - } - const char* odd(const char* src) { - return word(src); - } - // Match CSS attribute-matching operators. - const char* exact_match(const char* src) { return exactly<'='>(src); } - const char* class_match(const char* src) { return exactly(src); } - const char* dash_match(const char* src) { return exactly(src); } - const char* prefix_match(const char* src) { return exactly(src); } - const char* suffix_match(const char* src) { return exactly(src); } - const char* substring_match(const char* src) { return exactly(src); } - // Match CSS combinators. - /* not used anymore - remove? - const char* adjacent_to(const char* src) { - return sequence< optional_spaces, exactly<'+'> >(src); - } - const char* precedes(const char* src) { - return sequence< optional_spaces, exactly<'~'> >(src); - } - const char* parent_of(const char* src) { - return sequence< optional_spaces, exactly<'>'> >(src); - } - const char* ancestor_of(const char* src) { - return sequence< spaces, negate< exactly<'{'> > >(src); - }*/ - - // Match SCSS variable names. - const char* variable(const char* src) { - return sequence, identifier>(src); - } - - // parse `calc`, `-a-calc` and `--b-c-calc` - // but do not parse `foocalc` or `foo-calc` - const char* calc_fn_call(const char* src) { - return sequence < - optional < sequence < - hyphens, - one_plus < sequence < - strict_identifier, - hyphens - > > - > >, - exactly < calc_fn_kwd >, - word_boundary - >(src); - } - - // Match Sass boolean keywords. - const char* kwd_true(const char* src) { - return word(src); - } - const char* kwd_false(const char* src) { - return word(src); - } - const char* kwd_only(const char* src) { - return keyword < only_kwd >(src); - } - const char* kwd_and(const char* src) { - return keyword < and_kwd >(src); - } - const char* kwd_or(const char* src) { - return keyword < or_kwd >(src); - } - const char* kwd_not(const char* src) { - return keyword < not_kwd >(src); - } - const char* kwd_eq(const char* src) { - return exactly(src); - } - const char* kwd_neq(const char* src) { - return exactly(src); - } - const char* kwd_gt(const char* src) { - return exactly(src); - } - const char* kwd_gte(const char* src) { - return exactly(src); - } - const char* kwd_lt(const char* src) { - return exactly(src); - } - const char* kwd_lte(const char* src) { - return exactly(src); - } - const char* kwd_using(const char* src) { - return keyword(src); - } - - // match specific IE syntax - const char* ie_progid(const char* src) { - return sequence < - word, - exactly<':'>, - alternatives< identifier_schema, identifier >, - zero_plus< sequence< - exactly<'.'>, - alternatives< identifier_schema, identifier > - > >, - zero_plus < sequence< - exactly<'('>, - optional_css_whitespace, - optional < sequence< - alternatives< variable, identifier_schema, identifier >, - optional_css_whitespace, - exactly<'='>, - optional_css_whitespace, - alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa >, - zero_plus< sequence< - optional_css_whitespace, - exactly<','>, - optional_css_whitespace, - sequence< - alternatives< variable, identifier_schema, identifier >, - optional_css_whitespace, - exactly<'='>, - optional_css_whitespace, - alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa > - > - > > - > >, - optional_css_whitespace, - exactly<')'> - > > - >(src); - } - const char* ie_expression(const char* src) { - return sequence < word, exactly<'('>, skip_over_scopes< exactly<'('>, exactly<')'> > >(src); - } - const char* ie_property(const char* src) { - return alternatives < ie_expression, ie_progid >(src); - } - - // const char* ie_args(const char* src) { - // return sequence< alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by< '(', ')', true> >, - // zero_plus< sequence< optional_css_whitespace, exactly<','>, optional_css_whitespace, alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by<'(', ')', true> > > > >(src); - // } - - const char* ie_keyword_arg_property(const char* src) { - return alternatives < - variable, - identifier_schema, - identifier - >(src); - } - const char* ie_keyword_arg_value(const char* src) { - return alternatives < - variable, - identifier_schema, - identifier, - quoted_string, - number, - hex, - hexa, - sequence < - exactly < '(' >, - skip_over_scopes < - exactly < '(' >, - exactly < ')' > - > - > - >(src); - } - - const char* ie_keyword_arg(const char* src) { - return sequence < - ie_keyword_arg_property, - optional_css_whitespace, - exactly<'='>, - optional_css_whitespace, - ie_keyword_arg_value - >(src); - } - - // Path matching functions. - /* not used anymore - remove? - const char* folder(const char* src) { - return sequence< zero_plus< any_char_except<'/'> >, - exactly<'/'> >(src); - } - const char* folders(const char* src) { - return zero_plus< folder >(src); - }*/ - /* not used anymore - remove? - const char* chunk(const char* src) { - char inside_str = 0; - const char* p = src; - size_t depth = 0; - while (true) { - if (!*p) { - return 0; - } - else if (!inside_str && (*p == '"' || *p == '\'')) { - inside_str = *p; - } - else if (*p == inside_str && *(p-1) != '\\') { - inside_str = 0; - } - else if (*p == '(' && !inside_str) { - ++depth; - } - else if (*p == ')' && !inside_str) { - if (depth == 0) return p; - else --depth; - } - ++p; - } - // unreachable - return 0; - } - */ - - // follow the CSS spec more closely and see if this helps us scan URLs correctly - /* not used anymore - remove? - const char* NL(const char* src) { - return alternatives< exactly<'\n'>, - sequence< exactly<'\r'>, exactly<'\n'> >, - exactly<'\r'>, - exactly<'\f'> >(src); - }*/ - - const char* H(const char* src) { - return Util::ascii_isxdigit(static_cast(*src)) ? src+1 : 0; - } - - const char* W(const char* src) { - return zero_plus< alternatives< - space, - exactly< '\t' >, - exactly< '\r' >, - exactly< '\n' >, - exactly< '\f' > - > >(src); - } - - const char* UUNICODE(const char* src) { - return sequence< exactly<'\\'>, - between, - optional< W > - >(src); - } - - const char* NONASCII(const char* src) { - return nonascii(src); - } - - const char* ESCAPE(const char* src) { - return alternatives< - UUNICODE, - sequence< - exactly<'\\'>, - alternatives< - NONASCII, - escapable_character - > - > - >(src); - } - - const char* list_terminator(const char* src) { - return alternatives < - exactly<';'>, - exactly<'}'>, - exactly<'{'>, - exactly<')'>, - exactly<']'>, - exactly<':'>, - end_of_file, - exactly, - default_flag, - global_flag - >(src); - }; - - const char* space_list_terminator(const char* src) { - return alternatives < - exactly<','>, - list_terminator - >(src); - }; - - - // const char* real_uri_prefix(const char* src) { - // return alternatives< - // exactly< url_kwd >, - // exactly< url_prefix_kwd > - // >(src); - // } - - const char* real_uri(const char* src) { - return sequence< - exactly< url_kwd >, - exactly< '(' >, - W, - real_uri_value, - exactly< ')' > - >(src); - } - - const char* real_uri_suffix(const char* src) { - return sequence< W, exactly< ')' > >(src); - } - - const char* real_uri_value(const char* src) { - return - sequence< - non_greedy< - alternatives< - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - >, - alternatives< - real_uri_suffix, - exactly< hash_lbrace > - > - > - > - (src); - } - - const char* static_string(const char* src) { - const char* pos = src; - const char * s = quoted_string(pos); - Token t(pos, s); - const unsigned int p = count_interval< interpolant >(t.begin, t.end); - return (p == 0) ? t.end : 0; - } - - const char* unicode_seq(const char* src) { - return sequence < - alternatives < - exactly< 'U' >, - exactly< 'u' > - >, - exactly< '+' >, - padded_token < - 6, xdigit, - exactly < '?' > - > - >(src); - } - - const char* static_component(const char* src) { - return alternatives< identifier, - static_string, - percentage, - hex, - hexa, - exactly<'|'>, - // exactly<'+'>, - sequence < number, unit_identifier >, - number, - sequence< exactly<'!'>, word > - >(src); - } - - const char* static_property(const char* src) { - return - sequence < - zero_plus< - sequence < - optional_css_comments, - alternatives < - exactly<','>, - exactly<'('>, - exactly<')'>, - kwd_optional, - quoted_string, - interpolant, - identifier, - percentage, - dimension, - variable, - alnum, - sequence < - exactly <'\\'>, - any_char - > - > - > - >, - lookahead < - sequence < - optional_css_comments, - alternatives < - exactly <';'>, - exactly <'}'>, - end_of_file - > - > - > - >(src); - } - - const char* static_value(const char* src) { - return sequence< sequence< - static_component, - zero_plus< identifier > - >, - zero_plus < sequence< - alternatives< - sequence< optional_spaces, alternatives< - exactly < '/' >, - exactly < ',' >, - exactly < ' ' > - >, optional_spaces >, - spaces - >, - static_component - > >, - zero_plus < spaces >, - alternatives< exactly<';'>, exactly<'}'> > - >(src); - } - - extern const char css_variable_url_negates[] = "()[]{}\"'#/"; - const char* css_variable_value(const char* src) { - return sequence< - alternatives< - sequence< - negate< exactly< url_fn_kwd > >, - one_plus< neg_class_char< css_variable_url_negates > > - >, - sequence< exactly<'#'>, negate< exactly<'{'> > >, - sequence< exactly<'/'>, negate< exactly<'*'> > >, - static_string, - real_uri, - block_comment - > - >(src); - } - - extern const char css_variable_url_top_level_negates[] = "()[]{}\"'#/;"; - const char* css_variable_top_level_value(const char* src) { - return sequence< - alternatives< - sequence< - negate< exactly< url_fn_kwd > >, - one_plus< neg_class_char< css_variable_url_top_level_negates > > - >, - sequence< exactly<'#'>, negate< exactly<'{'> > >, - sequence< exactly<'/'>, negate< exactly<'*'> > >, - static_string, - real_uri, - block_comment - > - >(src); - } - - const char* parenthese_scope(const char* src) { - return sequence < - exactly < '(' >, - skip_over_scopes < - exactly < '(' >, - exactly < ')' > - > - >(src); - } - - const char* re_selector_list(const char* src) { - return alternatives < - // partial bem selector - sequence < - ampersand, - one_plus < - exactly < '-' > - >, - word_boundary, - optional_spaces - >, - // main selector matching - one_plus < - alternatives < - // consume whitespace and comments - spaces, block_comment, line_comment, - // match `/deep/` selector (pass-trough) - // there is no functionality for it yet - schema_reference_combinator, - // match selector ops /[*&%,\[\]]/ - class_char < selector_lookahead_ops >, - // match selector combinators /[>+~]/ - class_char < selector_combinator_ops >, - // match pseudo selectors - sequence < - exactly <'('>, - optional_spaces, - optional , - optional_spaces, - exactly <')'> - >, - // match attribute compare operators - alternatives < - exact_match, class_match, dash_match, - prefix_match, suffix_match, substring_match - >, - // main selector match - sequence < - // allow namespace prefix - optional < namespace_schema >, - // modifiers prefixes - alternatives < - sequence < - exactly <'#'>, - // not for interpolation - negate < exactly <'{'> > - >, - // class match - exactly <'.'>, - // single or double colon - sequence < - optional < pseudo_prefix >, - // fix libsass issue 2376 - negate < uri_prefix > - > - >, - // accept hypens in token - one_plus < sequence < - // can start with hyphens - zero_plus < - sequence < - exactly <'-'>, - optional_spaces - > - >, - // now the main token - alternatives < - kwd_optional, - exactly <'*'>, - quoted_string, - interpolant, - identifier, - variable, - percentage, - binomial, - dimension, - alnum - > - > >, - // can also end with hyphens - zero_plus < exactly<'-'> > - > - > - > - >(src); - } - - const char* type_selector(const char* src) { - return sequence< optional, identifier>(src); - } - const char* re_type_selector(const char* src) { - return alternatives< type_selector, universal, dimension, percentage, number, identifier_alnums >(src); - } - const char* re_static_expression(const char* src) { - return sequence< number, optional_spaces, exactly<'/'>, optional_spaces, number >(src); - } - - // lexer special_fn: these functions cannot be overloaded - // (/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i) - const char* re_special_fun(const char* src) { - - // match this first as we test prefix hyphens - if (const char* calc = calc_fn_call(src)) { - return calc; - } - - return sequence < - optional < - sequence < - exactly <'-'>, - one_plus < - alternatives < - alpha, - exactly <'+'>, - exactly <'-'> - > - > - > - >, - alternatives < - word < expression_kwd >, - sequence < - sequence < - exactly < progid_kwd >, - exactly <':'> - >, - zero_plus < - alternatives < - char_range <'a', 'z'>, - exactly <'.'> - > - > - > - > - >(src); - } - - } -} diff --git a/src/prelexer.hpp b/src/prelexer.hpp deleted file mode 100644 index 07ad09c11b..0000000000 --- a/src/prelexer.hpp +++ /dev/null @@ -1,484 +0,0 @@ -#ifndef SASS_PRELEXER_H -#define SASS_PRELEXER_H - -#include -#include "lexer.hpp" - -namespace Sass { - // using namespace Lexer; - namespace Prelexer { - - //#################################### - // KEYWORD "REGEX" MATCHERS - //#################################### - - // Match Sass boolean keywords. - const char* kwd_true(const char* src); - const char* kwd_false(const char* src); - const char* kwd_only(const char* src); - const char* kwd_and(const char* src); - const char* kwd_or(const char* src); - const char* kwd_not(const char* src); - const char* kwd_eq(const char* src); - const char* kwd_neq(const char* src); - const char* kwd_gt(const char* src); - const char* kwd_gte(const char* src); - const char* kwd_lt(const char* src); - const char* kwd_lte(const char* src); - const char* kwd_using(const char* src); - - // Match standard control chars - const char* kwd_at(const char* src); - const char* kwd_dot(const char* src); - const char* kwd_comma(const char* src); - const char* kwd_colon(const char* src); - const char* kwd_slash(const char* src); - const char* kwd_star(const char* src); - const char* kwd_plus(const char* src); - const char* kwd_minus(const char* src); - - //#################################### - // SPECIAL "REGEX" CONSTRUCTS - //#################################### - - // Match a sequence of characters delimited by the supplied chars. - template - const char* delimited_by(const char* src) { - src = exactly(src); - if (!src) return 0; - const char* stop; - while (true) { - if (!*src) return 0; - stop = exactly(src); - if (stop && (!esc || *(src - 1) != '\\')) return stop; - src = stop ? stop : src + 1; - } - } - - // skip to delimiter (mx) inside given range - // this will savely skip over all quoted strings - // recursive skip stuff delimited by start/stop - // first start/opener must be consumed already! - template - const char* skip_over_scopes(const char* src, const char* end) { - - size_t level = 0; - bool in_squote = false; - bool in_dquote = false; - bool in_backslash_escape = false; - - while ((end == nullptr || src < end) && *src != '\0') { - // has escaped sequence? - if (in_backslash_escape) { - in_backslash_escape = false; - } - else if (*src == '\\') { - in_backslash_escape = true; - } - else if (*src == '"') { - in_dquote = ! in_dquote; - } - else if (*src == '\'') { - in_squote = ! in_squote; - } - else if (in_dquote || in_squote) { - // take everything literally - } - - // find another opener inside? - else if (const char* pos = start(src)) { - ++ level; // increase counter - src = pos - 1; // advance position - } - - // look for the closer (maybe final, maybe not) - else if (const char* final = stop(src)) { - // only close one level? - if (level > 0) -- level; - // return position at end of stop - // delimiter may be multiple chars - else return final; - // advance position - src = final - 1; - } - - // next - ++ src; - } - - return 0; - } - - // skip to a skip delimited by parentheses - // uses smart `skip_over_scopes` internally - const char* parenthese_scope(const char* src); - - // skip to delimiter (mx) inside given range - // this will savely skip over all quoted strings - // recursive skip stuff delimited by start/stop - // first start/opener must be consumed already! - template - const char* skip_over_scopes(const char* src) { - return skip_over_scopes(src, nullptr); - } - - // Match a sequence of characters delimited by the supplied chars. - template - const char* recursive_scopes(const char* src) { - // parse opener - src = start(src); - // abort if not found - if (!src) return 0; - // parse the rest until final closer - return skip_over_scopes(src); - } - - // Match a sequence of characters delimited by the supplied strings. - template - const char* delimited_by(const char* src) { - src = exactly(src); - if (!src) return 0; - const char* stop; - while (true) { - if (!*src) return 0; - stop = exactly(src); - if (stop && (!esc || *(src - 1) != '\\')) return stop; - src = stop ? stop : src + 1; - } - } - - // Tries to match a certain number of times (between the supplied interval). - template - const char* between(const char* src) { - for (size_t i = 0; i < lo; ++i) { - src = mx(src); - if (!src) return 0; - } - for (size_t i = lo; i <= hi; ++i) { - const char* new_src = mx(src); - if (!new_src) return src; - src = new_src; - } - return src; - } - - // equivalent of STRING_REGULAR_EXPRESSIONS - const char* re_string_double_open(const char* src); - const char* re_string_double_close(const char* src); - const char* re_string_single_open(const char* src); - const char* re_string_single_close(const char* src); - const char* re_string_uri_open(const char* src); - const char* re_string_uri_close(const char* src); - - // Match a line comment. - const char* line_comment(const char* src); - - // Match a block comment. - const char* block_comment(const char* src); - // Match either. - const char* comment(const char* src); - // Match double- and single-quoted strings. - const char* double_quoted_string(const char* src); - const char* single_quoted_string(const char* src); - 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); - - // Match zero plus white-space or line_comments - const char* optional_css_whitespace(const char* src); - const char* css_whitespace(const char* src); - // Match optional_css_whitepace plus block_comments - const char* optional_css_comments(const char* src); - const char* css_comments(const char* src); - - // Match one backslash escaped char - const char* escape_seq(const char* src); - - // Match CSS css variables. - const char* custom_property_name(const char* src); - // Match a CSS identifier. - const char* identifier(const char* src); - const char* identifier_alpha(const char* src); - const char* identifier_alnum(const char* src); - const char* strict_identifier(const char* src); - const char* strict_identifier_alpha(const char* src); - const char* strict_identifier_alnum(const char* src); - // Match a CSS unit identifier. - const char* one_unit(const char* src); - const char* multiple_units(const char* src); - const char* unit_identifier(const char* src); - // const char* strict_identifier_alnums(const char* src); - // Match reference selector. - const char* re_reference_combinator(const char* src); - const char* static_reference_combinator(const char* src); - const char* schema_reference_combinator(const char* src); - - // Match interpolant schemas - const char* identifier_schema(const char* src); - const char* value_schema(const char* src); - const char* sass_value(const char* src); - // const char* filename(const char* src); - // const char* filename_schema(const char* src); - // const char* url_schema(const char* src); - // const char* url_value(const char* src); - const char* vendor_prefix(const char* src); - - const char* re_special_directive(const char* src); - const char* re_prefixed_directive(const char* src); - const char* re_almost_any_value_token(const char* src); - - // Match CSS '@' keywords. - const char* at_keyword(const char* src); - const char* kwd_import(const char* src); - const char* kwd_at_root(const char* src); - const char* kwd_with_directive(const char* src); - const char* kwd_without_directive(const char* src); - const char* kwd_media(const char* src); - const char* kwd_supports_directive(const char* src); - // const char* keyframes(const char* src); - // const char* keyf(const char* src); - const char* kwd_mixin(const char* src); - const char* kwd_function(const char* src); - const char* kwd_return_directive(const char* src); - const char* kwd_include_directive(const char* src); - const char* kwd_content_directive(const char* src); - const char* kwd_charset_directive(const char* src); - const char* kwd_extend(const char* src); - - const char* unicode_seq(const char* src); - - const char* kwd_if_directive(const char* src); - const char* kwd_else_directive(const char* src); - const char* elseif_directive(const char* src); - - const char* kwd_for_directive(const char* src); - const char* kwd_from(const char* src); - const char* kwd_to(const char* src); - const char* kwd_through(const char* src); - - const char* kwd_each_directive(const char* src); - const char* kwd_in(const char* src); - - const char* kwd_while_directive(const char* src); - - const char* re_nothing(const char* src); - - const char* re_special_fun(const char* src); - - const char* kwd_warn(const char* src); - const char* kwd_err(const char* src); - const char* kwd_dbg(const char* src); - - const char* kwd_null(const char* src); - - const char* re_selector_list(const char* src); - const char* re_type_selector(const char* src); - const char* re_static_expression(const char* src); - - // identifier that can start with hyphens - const char* css_identifier(const char* src); - const char* css_ip_identifier(const char* src); - - // Match CSS type selectors - const char* namespace_schema(const char* src); - const char* namespace_prefix(const char* src); - const char* type_selector(const char* src); - const char* hyphens_and_identifier(const char* src); - const char* hyphens_and_name(const char* src); - const char* universal(const char* src); - // Match CSS id names. - const char* id_name(const char* src); - // Match CSS class names. - const char* class_name(const char* src); - // Attribute name in an attribute selector - const char* attribute_name(const char* src); - // Match placeholder selectors. - const char* placeholder(const char* src); - // Match CSS numeric constants. - const char* op(const char* src); - const char* sign(const char* src); - const char* unsigned_number(const char* src); - const char* number(const char* src); - const char* coefficient(const char* src); - const char* binomial(const char* src); - const char* percentage(const char* src); - const char* ampersand(const char* src); - const char* dimension(const char* src); - const char* hex(const char* src); - const char* hexa(const char* src); - const char* hex0(const char* src); - // const char* rgb_prefix(const char* src); - // Match CSS uri specifiers. - const char* uri_prefix(const char* src); - // Match CSS "!important" keyword. - const char* kwd_important(const char* src); - // Match CSS "!optional" keyword. - const char* kwd_optional(const char* src); - // Match Sass "!default" keyword. - const char* default_flag(const char* src); - const char* global_flag(const char* src); - // Match CSS pseudo-class/element prefixes - const char* pseudo_prefix(const char* src); - // Match CSS function call openers. - const char* re_functional(const char* src); - const char* re_pseudo_selector(const char* src); - const char* functional_schema(const char* src); - const char* pseudo_not(const char* src); - // Match CSS 'odd' and 'even' keywords for functional pseudo-classes. - const char* even(const char* src); - const char* odd(const char* src); - // Match CSS attribute-matching operators. - const char* exact_match(const char* src); - const char* class_match(const char* src); - const char* dash_match(const char* src); - const char* prefix_match(const char* src); - const char* suffix_match(const char* src); - const char* substring_match(const char* src); - // Match CSS combinators. - // const char* adjacent_to(const char* src); - // const char* precedes(const char* src); - // const char* parent_of(const char* src); - // const char* ancestor_of(const char* src); - - // Match SCSS variable names. - const char* variable(const char* src); - const char* calc_fn_call(const char* src); - - // IE stuff - const char* ie_progid(const char* src); - const char* ie_expression(const char* src); - const char* ie_property(const char* src); - const char* ie_keyword_arg(const char* src); - const char* ie_keyword_arg_value(const char* src); - const char* ie_keyword_arg_property(const char* src); - - // characters that terminate parsing of a list - const char* list_terminator(const char* src); - const char* space_list_terminator(const char* src); - - // match url() - const char* H(const char* src); - const char* W(const char* src); - // `UNICODE` makes VS sad - const char* UUNICODE(const char* src); - const char* NONASCII(const char* src); - const char* ESCAPE(const char* src); - const char* real_uri(const char* src); - const char* real_uri_suffix(const char* src); - // const char* real_uri_prefix(const char* src); - const char* real_uri_value(const char* src); - - // Path matching functions. - // const char* folder(const char* src); - // const char* folders(const char* src); - - - const char* static_string(const char* src); - const char* static_component(const char* src); - const char* static_property(const char* src); - const char* static_value(const char* src); - - const char* css_variable_value(const char* src); - const char* css_variable_top_level_value(const char* src); - - // Utility functions for finding and counting characters in a string. - template - const char* find_first(const char* src) { - while (*src && *src != c) ++src; - return *src ? src : 0; - } - template - const char* find_first(const char* src) { - while (*src && !mx(src)) ++src; - return *src ? src : 0; - } - template - const char* find_first_in_interval(const char* beg, const char* end) { - bool esc = false; - while ((beg < end) && *beg) { - if (esc) esc = false; - else if (*beg == '\\') esc = true; - else if (mx(beg)) return beg; - ++beg; - } - return 0; - } - template - const char* find_first_in_interval(const char* beg, const char* end) { - bool esc = false; - while ((beg < end) && *beg) { - if (esc) esc = false; - else if (*beg == '\\') esc = true; - else if (const char* pos = skip(beg)) beg = pos; - else if (mx(beg)) return beg; - ++beg; - } - return 0; - } - 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 (esc) { - esc = false; - ++beg; - } else if (*beg == '\\') { - esc = true; - ++beg; - } else if ((p = mx(beg))) { - ++counter; - beg = p; - } - else { - ++beg; - } - } - return counter; - } - - template - const char* padded_token(const char* src) - { - size_t got = 0; - const char* pos = src; - while (got < size) { - if (!mx(pos)) break; - ++ pos; ++ got; - } - while (got < size) { - if (!pad(pos)) break; - ++ pos; ++ got; - } - return got ? pos : 0; - } - - template - const char* minmax_range(const char* src) - { - size_t got = 0; - const char* pos = src; - while (got < max) { - if (!mx(pos)) break; - ++ pos; ++ got; - } - if (got < min) return 0; - if (got > max) return 0; - return pos; - } - - template - const char* char_range(const char* src) - { - if (*src < min) return 0; - if (*src > max) return 0; - return src + 1; - } - - } -} - -#endif diff --git a/src/randomize.cpp b/src/randomize.cpp new file mode 100644 index 0000000000..39081c5ace --- /dev/null +++ b/src/randomize.cpp @@ -0,0 +1,112 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "capi_sass.hpp" + +#ifdef USE_WIN_CRYPT +#include +#include +#else +#include +#endif + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Read a truly random seed + // This is probably expensive + uint32_t readHashSeed() + { + // Our hash seed + uint32_t seed = 0; + // Load optional fixed seed from environment + // Mainly used to pass the seed to plugins + if (const char* envseed = GET_ENV("SASS_HASH_SEED")) { + seed = atol(envseed); + } + else { + #ifdef USE_WIN_CRYPT + // Get seed via MS API + HCRYPTPROV hp = 0; + CryptAcquireContext(&hp, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); + CryptGenRandom(hp, sizeof(seed), (BYTE*)&seed); + CryptReleaseContext(hp, 0); + #else + // Get seed via C++11 API + std::random_device rd; + seed = rd(); + #endif + } + // Use some sensible default + if (seed == 0) { + seed = 0x9e3779b9; + } + // Return the seed + return seed; + } + // EO readHashSeed + + // Just a static wrapped around random device + // Creates one true random number to seed us + uint32_t getHashSeed(uint32_t* preset) + { + #ifdef StaticHashSeed + return StaticHashSeed + // return 0x9e3779b9; + #else + // This should be thread safe + static uint32_t seed = preset + ? *preset : readHashSeed(); + if (preset) seed = *preset; + return seed; // thread static + #endif + } + // EO getHashSeed + + // Random number generator only needed in eval phase + // This makes it safe to reset the hash seed before + std::mt19937& getRng() + { + // Lets hope this is indeed thread safe (seed once) + static std::mt19937 rng(getHashSeed()); + return rng; // static twister per thread + } + // EO getRng + + // Create a random `int` between [low] and [high] + int getRandomInt(int low, int high) + { + static std::uniform_int_distribution urd; + return urd(getRng(), decltype(urd)::param_type{ low, high }); + } + // EO getRandomInt + + // Create a random `float` between [low] and [high] + float getRandomFloat(float low, float high) + { + static std::uniform_real_distribution urd; + return urd(getRng(), decltype(urd)::param_type{ low, high }); + } + // EO getRandomFloat + + // Create a random `double` between [low] and [high] + double getRandomDouble(double low, double high) + { + static std::uniform_real_distribution urd; + return urd(getRng(), decltype(urd)::param_type{ low, high }); + } + // EO getRandomDouble + + // Get full 32bit random data + uint32_t getRandomUint32() + { + return getRng()(); + } + // EO getRandomUint32 + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/randomize.hpp b/src/randomize.hpp new file mode 100644 index 0000000000..7f45acee69 --- /dev/null +++ b/src/randomize.hpp @@ -0,0 +1,44 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_RAND_SEED_HPP +#define SASS_RAND_SEED_HPP + +#include + +// Macros for env variables +#ifdef _WIN32 +#define GET_ENV(key) getenv(key) +#define SET_ENV(key, val) _putenv_s(key, val) +#else +#define GET_ENV(key) getenv(key) +#define SET_ENV(key, val) setenv(key, val, 0) +#endif + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Just a static wrapped around random device + // Creates one true random number to seed us + uint32_t getHashSeed(uint32_t* preset = nullptr); + + // Create a random `int` between [low] and [high] + int getRandomInt(int low = 0.0, int high = 1.0); + + // Create a random `float` between [low] and [high] + float getRandomFloat(float low = 0.0, float high = 1.0); + + // Create a random `double` between [low] and [high] + double getRandomDouble(double low = 0.0, double high = 1.0); + + // Get full 32bit random data + uint32_t getRandomUint32(); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +}; + +#endif diff --git a/src/remove_placeholders.cpp b/src/remove_placeholders.cpp index bc99c57e4a..bc09c269b7 100644 --- a/src/remove_placeholders.cpp +++ b/src/remove_placeholders.cpp @@ -1,86 +1,78 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" - #include "remove_placeholders.hpp" -namespace Sass { +#include "ast_selectors.hpp" - Remove_Placeholders::Remove_Placeholders() - { } +namespace Sass { - void Remove_Placeholders::operator()(Block* b) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - if (b->get(i)) b->get(i)->perform(this); - } + bool isInvisibleCss(CssNode* stmt) { + return stmt->isInvisibleCss(); } void Remove_Placeholders::remove_placeholders(SimpleSelector* simple) { - if (PseudoSelector * pseudo = simple->getPseudoSelector()) { + if (PseudoSelector * pseudo = simple->isaPseudoSelector()) { if (pseudo->selector()) remove_placeholders(pseudo->selector()); } } void Remove_Placeholders::remove_placeholders(CompoundSelector* compound) { - for (size_t i = 0, L = compound->length(); i < L; ++i) { + for (size_t i = 0, L = compound->size(); i < L; ++i) { if (compound->get(i)) remove_placeholders(compound->get(i)); } - listEraseItemIf(compound->elements(), listIsEmpty); + // listEraseItemIf(compound->elements(), listIsInvisible); + // listEraseItemIf(compound->elements(), listIsEmpty); } void Remove_Placeholders::remove_placeholders(ComplexSelector* complex) { - if (complex->has_placeholder()) { - complex->clear(); // remove all - } - else { - for (size_t i = 0, L = complex->length(); i < L; ++i) { - if (CompoundSelector * compound = complex->get(i)->getCompound()) { - if (compound) remove_placeholders(compound); - } + for (const SelectorComponentObj& component : complex->elements()) { + if (component->hasPlaceholder()) { + complex->clear(); // remove all + return; + } + if (CompoundSelector* compound = component->isaCompoundSelector()) { + if (compound) remove_placeholders(compound); } - listEraseItemIf(complex->elements(), listIsEmpty); + } + listEraseItemIf(complex->elements(), listIsEmpty); + // ToDo: describe what this means + if (complex->hasInvisible()) { + complex->clear(); // remove all } } - SelectorList* Remove_Placeholders::remove_placeholders(SelectorList* sl) + void Remove_Placeholders::remove_placeholders(SelectorList* sl) { - for (size_t i = 0, L = sl->length(); i < L; ++i) { - if (sl->get(i)) remove_placeholders(sl->get(i)); + for(const ComplexSelectorObj& complex : sl->elements()) { + if (complex) remove_placeholders(complex); } listEraseItemIf(sl->elements(), listIsEmpty); - return sl; } - void Remove_Placeholders::operator()(CssMediaRule* rule) + void Remove_Placeholders::visitCssRoot(CssRoot* b) { - if (rule->block()) operator()(rule->block()); + // Clean up all our children + acceptCssParentNode(b); + // Remove all invisible items + listEraseItemIf(b->elements(), isInvisibleCss); // QQQQQQQQQQQQHHHHHH } - void Remove_Placeholders::operator()(StyleRule* r) + void Remove_Placeholders::visitCssStyleRule(CssStyleRule* rule) { - if (SelectorListObj sl = r->selector()) { - // Set the new placeholder selector list - r->selector((remove_placeholders(sl))); - } - // Iterate into child blocks - Block_Obj b = r->block(); - for (size_t i = 0, L = b->length(); i < L; ++i) { - if (b->get(i)) { b->get(i)->perform(this); } - } - } + // Clean up all our children + acceptCssParentNode(rule); - void Remove_Placeholders::operator()(SupportsRule* m) - { - if (m->block()) operator()(m->block()); + if (SelectorList* sl = rule->selector()) { + remove_placeholders(sl); + } } - void Remove_Placeholders::operator()(AtRule* a) + void Remove_Placeholders::acceptCssParentNode(CssParentNode* m) { - if (a->block()) a->block()->perform(this); + for (CssNodeObj& node : m->elements()) { + node->accept(this); + } } } diff --git a/src/remove_placeholders.hpp b/src/remove_placeholders.hpp index ae0af37f11..dcda79346d 100644 --- a/src/remove_placeholders.hpp +++ b/src/remove_placeholders.hpp @@ -1,34 +1,46 @@ -#ifndef SASS_REMOVE_PLACEHOLDERS_H -#define SASS_REMOVE_PLACEHOLDERS_H +#ifndef SASS_REMOVE_PLACEHOLDERS_HPP +#define SASS_REMOVE_PLACEHOLDERS_HPP +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_css.hpp" #include "ast_fwd_decl.hpp" -#include "operation.hpp" +#include "visitor_css.hpp" namespace Sass { - class Remove_Placeholders : public Operation_CRTP { + class Remove_Placeholders : + public CssVisitor { - public: + private: - SelectorList* remove_placeholders(SelectorList*); - void remove_placeholders(SimpleSelector* simple); - void remove_placeholders(CompoundSelector* complex); - void remove_placeholders(ComplexSelector* complex); + // Helper to traverse the tree recursively + void acceptCssParentNode(CssParentNode*); + // The main functions to do the cleanup + void remove_placeholders(SelectorList*); + void remove_placeholders(SimpleSelector*); + void remove_placeholders(CompoundSelector*); + void remove_placeholders(ComplexSelector*); public: - Remove_Placeholders(); - ~Remove_Placeholders() { } - - void operator()(Block*); - void operator()(StyleRule*); - void operator()(CssMediaRule*); - void operator()(SupportsRule*); - void operator()(AtRule*); - - // ignore missed types - template - void fallback(U x) {} + + // Do not implement anything for these visitors + void visitCssComment(CssComment* css) override final {}; + void visitCssDeclaration(CssDeclaration* css) override final {}; + void visitCssImport(CssImport* css) override final {}; + + // Move further down into children to remove recursively + void visitCssAtRule(CssAtRule* css) override final { acceptCssParentNode(css); }; + void visitCssKeyframeBlock(CssKeyframeBlock* css) override final { acceptCssParentNode(css); }; + void visitCssMediaRule(CssMediaRule* css) override final { acceptCssParentNode(css); }; + void visitCssSupportsRule(CssSupportsRule* css) override final { acceptCssParentNode(css); }; + + // Cleaning only makes sense on those nodes + void visitCssRoot(CssRoot*) override final; + void visitCssStyleRule(CssStyleRule*) override final; }; diff --git a/src/sass.cpp b/src/sass.cpp deleted file mode 100644 index 35383ebf08..0000000000 --- a/src/sass.cpp +++ /dev/null @@ -1,156 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include -#include - -#include "sass.h" -#include "file.hpp" -#include "util.hpp" -#include "context.hpp" -#include "sass_context.hpp" -#include "sass_functions.hpp" - -namespace Sass { - - // helper to convert string list to vector - sass::vector list2vec(struct string_list* cur) - { - sass::vector list; - while (cur) { - list.push_back(cur->string); - cur = cur->next; - } - return list; - } - -} - -extern "C" { - using namespace Sass; - - // Allocate libsass heap memory - // Don't forget string termination! - void* ADDCALL sass_alloc_memory(size_t size) - { - void* ptr = malloc(size); - if (ptr == NULL) { - std::cerr << "Out of memory.\n"; - exit(EXIT_FAILURE); - } - return ptr; - } - - char* ADDCALL sass_copy_c_string(const char* str) - { - if (str == nullptr) return nullptr; - size_t len = strlen(str) + 1; - char* cpy = (char*) sass_alloc_memory(len); - std::memcpy(cpy, str, len); - return cpy; - } - - // Deallocate libsass heap memory - void ADDCALL sass_free_memory(void* ptr) - { - if (ptr) free (ptr); - } - - // caller must free the returned memory - char* ADDCALL sass_string_quote (const char *str, const char quote_mark) - { - sass::string quoted = quote(str, quote_mark); - return sass_copy_c_string(quoted.c_str()); - } - - // caller must free the returned memory - char* ADDCALL sass_string_unquote (const char *str) - { - sass::string unquoted = unquote(str); - return sass_copy_c_string(unquoted.c_str()); - } - - char* ADDCALL sass_compiler_find_include (const char* file, struct Sass_Compiler* compiler) - { - // get the last import entry to get current base directory - Sass_Import_Entry import = sass_compiler_get_last_import(compiler); - const sass::vector& incs = compiler->cpp_ctx->include_paths; - // create the vector with paths to lookup - sass::vector paths(1 + incs.size()); - paths.push_back(File::dir_name(import->abs_path)); - paths.insert( paths.end(), incs.begin(), incs.end() ); - // now resolve the file path relative to lookup paths - sass::string resolved(File::find_include(file, paths)); - return sass_copy_c_string(resolved.c_str()); - } - - char* ADDCALL sass_compiler_find_file (const char* file, struct Sass_Compiler* compiler) - { - // get the last import entry to get current base directory - Sass_Import_Entry import = sass_compiler_get_last_import(compiler); - const sass::vector& incs = compiler->cpp_ctx->include_paths; - // create the vector with paths to lookup - sass::vector paths(1 + incs.size()); - paths.push_back(File::dir_name(import->abs_path)); - paths.insert( paths.end(), incs.begin(), incs.end() ); - // now resolve the file path relative to lookup paths - sass::string resolved(File::find_file(file, paths)); - return sass_copy_c_string(resolved.c_str()); - } - - // Make sure to free the returned value! - // Incs array has to be null terminated! - // this has the original resolve logic for sass include - char* ADDCALL sass_find_include (const char* file, struct Sass_Options* opt) - { - sass::vector vec(list2vec(opt->include_paths)); - sass::string resolved(File::find_include(file, vec)); - return sass_copy_c_string(resolved.c_str()); - } - - // Make sure to free the returned value! - // Incs array has to be null terminated! - char* ADDCALL sass_find_file (const char* file, struct Sass_Options* opt) - { - sass::vector vec(list2vec(opt->include_paths)); - sass::string resolved(File::find_file(file, vec)); - return sass_copy_c_string(resolved.c_str()); - } - - // Get compiled libsass version - const char* ADDCALL libsass_version(void) - { - return LIBSASS_VERSION; - } - - // Get compiled libsass version - const char* ADDCALL libsass_language_version(void) - { - return LIBSASS_LANGUAGE_VERSION; - } - -} - -namespace Sass { - - // helper to aid dreaded MSVC debug mode - char* sass_copy_string(sass::string str) - { - // In MSVC the following can lead to segfault: - // sass_copy_c_string(stream.str().c_str()); - // Reason is that the string returned by str() is disposed before - // sass_copy_c_string is invoked. The string is actually a stack - // object, so indeed nobody is holding on to it. So it seems - // perfectly fair to release it right away. So the const char* - // by c_str will point to invalid memory. I'm not sure if this is - // the behavior for all compiler, but I'm pretty sure we would - // have gotten more issues reported if that would be the case. - // Wrapping it in a functions seems the cleanest approach as the - // function must hold on to the stack variable until it's done. - return sass_copy_c_string(str.c_str()); - } - -} diff --git a/src/sass2scss.cpp b/src/sass2scss.cpp deleted file mode 100644 index 6ba51be28f..0000000000 --- a/src/sass2scss.cpp +++ /dev/null @@ -1,895 +0,0 @@ -/** - * sass2scss - * Licensed under the MIT License - * Copyright (c) Marcel Greter - */ - -#ifdef _MSC_VER -#define _CRT_SECURE_NO_WARNINGS -#define _CRT_NONSTDC_NO_DEPRECATE -#endif - -// include library -#include -#include -#include -#include -#include -#include -#include - -///* -// -// src comments: comments in sass syntax (staring with //) -// css comments: multiline comments in css syntax (starting with /*) -// -// KEEP_COMMENT: keep src comments in the resulting css code -// STRIP_COMMENT: strip out all comments (either src or css) -// CONVERT_COMMENT: convert all src comments to css comments -// -//*/ - -// our own header -#include "sass2scss.h" - -// add namespace for c++ -namespace Sass -{ - - // return the actual prettify value from options - #define PRETTIFY(converter) (converter.options - (converter.options & 248)) - // query the options integer to check if the option is enables - #define KEEP_COMMENT(converter) ((converter.options & SASS2SCSS_KEEP_COMMENT) == SASS2SCSS_KEEP_COMMENT) - #define STRIP_COMMENT(converter) ((converter.options & SASS2SCSS_STRIP_COMMENT) == SASS2SCSS_STRIP_COMMENT) - #define CONVERT_COMMENT(converter) ((converter.options & SASS2SCSS_CONVERT_COMMENT) == SASS2SCSS_CONVERT_COMMENT) - - // some makros to access the indentation stack - #define INDENT(converter) (converter.indents.top()) - - // some makros to query comment parser status - #define IS_PARSING(converter) (converter.comment == "") - #define IS_COMMENT(converter) (converter.comment != "") - #define IS_SRC_COMMENT(converter) (converter.comment == "//" && ! CONVERT_COMMENT(converter)) - #define IS_CSS_COMMENT(converter) (converter.comment == "/*" || (converter.comment == "//" && CONVERT_COMMENT(converter))) - - // pretty printer helper function - static std::string closer (const converter& converter) - { - return PRETTIFY(converter) == 0 ? " }" : - PRETTIFY(converter) <= 1 ? " }" : - "\n" + INDENT(converter) + "}"; - } - - // pretty printer helper function - static std::string opener (const converter& converter) - { - return PRETTIFY(converter) == 0 ? " { " : - PRETTIFY(converter) <= 2 ? " {" : - "\n" + INDENT(converter) + "{"; - } - - // check if the given string is a pseudo selector - // needed to differentiate from sass property syntax - static bool isPseudoSelector (std::string& sel) - { - - size_t len = sel.length(); - if (len < 1) return false; - size_t pos = sel.find_first_not_of("abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1); - if (pos != std::string::npos) sel.erase(pos, std::string::npos); - size_t i = sel.length(); - while (i -- > 0) { sel.at(i) = tolower(sel.at(i)); } - - // CSS Level 1 - Recommendation - if (sel == ":link") return true; - if (sel == ":visited") return true; - if (sel == ":active") return true; - - // CSS Level 2 (Revision 1) - Recommendation - if (sel == ":lang") return true; - if (sel == ":first-child") return true; - if (sel == ":hover") return true; - if (sel == ":focus") return true; - // disabled - also valid properties - // if (sel == ":left") return true; - // if (sel == ":right") return true; - if (sel == ":first") return true; - - // Selectors Level 3 - Recommendation - if (sel == ":target") return true; - if (sel == ":root") return true; - if (sel == ":nth-child") return true; - if (sel == ":nth-last-of-child") return true; - if (sel == ":nth-of-type") return true; - if (sel == ":nth-last-of-type") return true; - if (sel == ":last-child") return true; - if (sel == ":first-of-type") return true; - if (sel == ":last-of-type") return true; - if (sel == ":only-child") return true; - if (sel == ":only-of-type") return true; - if (sel == ":empty") return true; - if (sel == ":not") return true; - - // CSS Basic User Interface Module Level 3 - Working Draft - if (sel == ":default") return true; - if (sel == ":valid") return true; - if (sel == ":invalid") return true; - if (sel == ":in-range") return true; - if (sel == ":out-of-range") return true; - if (sel == ":required") return true; - if (sel == ":optional") return true; - if (sel == ":read-only") return true; - if (sel == ":read-write") return true; - if (sel == ":dir") return true; - if (sel == ":enabled") return true; - if (sel == ":disabled") return true; - if (sel == ":checked") return true; - if (sel == ":indeterminate") return true; - if (sel == ":nth-last-child") return true; - - // Selectors Level 4 - Working Draft - if (sel == ":any-link") return true; - if (sel == ":local-link") return true; - if (sel == ":scope") return true; - if (sel == ":active-drop-target") return true; - if (sel == ":valid-drop-target") return true; - if (sel == ":invalid-drop-target") return true; - if (sel == ":current") return true; - if (sel == ":past") return true; - if (sel == ":future") return true; - if (sel == ":placeholder-shown") return true; - if (sel == ":user-error") return true; - if (sel == ":blank") return true; - if (sel == ":nth-match") return true; - if (sel == ":nth-last-match") return true; - if (sel == ":nth-column") return true; - if (sel == ":nth-last-column") return true; - if (sel == ":matches") return true; - - // Fullscreen API - Living Standard - if (sel == ":fullscreen") return true; - - // not a pseudo selector - return false; - - } - - static size_t findFirstCharacter (std::string& sass, size_t pos) - { - return sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos); - } - - static size_t findLastCharacter (std::string& sass, size_t pos) - { - return sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, pos); - } - - static bool isUrl (std::string& sass, size_t pos) - { - return sass[pos] == 'u' && sass[pos+1] == 'r' && sass[pos+2] == 'l' && sass[pos+3] == '('; - } - - // check if there is some char data - // will ignore everything in comments - static bool hasCharData (std::string& sass) - { - - size_t col_pos = 0; - - while (true) - { - - // try to find some meaningfull char - col_pos = sass.find_first_not_of(" \t\n\v\f\r", col_pos); - - // there was no meaningfull char found - if (col_pos == std::string::npos) return false; - - // found a multiline comment opener - if (sass.substr(col_pos, 2) == "/*") - { - // find the multiline comment closer - col_pos = sass.find("*/", col_pos); - // maybe we did not find the closer here - if (col_pos == std::string::npos) return false; - // skip closer - col_pos += 2; - } - else - { - return true; - } - - } - - } - // EO hasCharData - - // find src comment opener - // correctly skips quoted strings - static size_t findCommentOpener (std::string& sass) - { - - size_t col_pos = 0; - bool apoed = false; - bool quoted = false; - bool comment = false; - size_t brackets = 0; - - while (col_pos != std::string::npos) - { - - // process all interesting chars - col_pos = sass.find_first_of("\"\'/\\*()", col_pos); - - // assertion for valid result - if (col_pos != std::string::npos) - { - char character = sass.at(col_pos); - - if (character == '(') - { - if (!quoted && !apoed) brackets ++; - } - else if (character == ')') - { - if (!quoted && !apoed) brackets --; - } - else if (character == '\"') - { - // invert quote bool - if (!apoed && !comment) quoted = !quoted; - } - else if (character == '\'') - { - // invert quote bool - if (!quoted && !comment) apoed = !apoed; - } - else if (col_pos > 0 && character == '/') - { - if (sass.at(col_pos - 1) == '*') - { - comment = false; - } - // next needs to be a slash too - else if (sass.at(col_pos - 1) == '/') - { - // only found if not in single or double quote, bracket or comment - if (!quoted && !apoed && !comment && brackets == 0) return col_pos - 1; - } - } - else if (character == '\\') - { - // skip next char if in quote - if (quoted || apoed) col_pos ++; - } - // this might be a comment opener - else if (col_pos > 0 && character == '*') - { - // opening a multiline comment - if (sass.at(col_pos - 1) == '/') - { - // we are now in a comment - if (!quoted && !apoed) comment = true; - } - } - - // skip char - col_pos ++; - - } - - } - // EO while - - return col_pos; - - } - // EO findCommentOpener - - // remove multiline comments from sass string - // correctly skips quoted strings - static std::string removeMultilineComment (std::string &sass) - { - - std::string clean = ""; - size_t col_pos = 0; - size_t open_pos = 0; - size_t close_pos = 0; - bool apoed = false; - bool quoted = false; - bool comment = false; - - // process sass til string end - while (col_pos != std::string::npos) - { - - // process all interesting chars - col_pos = sass.find_first_of("\"\'/\\*", col_pos); - - // assertion for valid result - if (col_pos != std::string::npos) - { - char character = sass.at(col_pos); - - // found quoted string delimiter - if (character == '\"') - { - if (!apoed && !comment) quoted = !quoted; - } - else if (character == '\'') - { - if (!quoted && !comment) apoed = !apoed; - } - // found possible comment closer - else if (character == '/') - { - // look back to see if it is actually a closer - if (comment && col_pos > 0 && sass.at(col_pos - 1) == '*') - { - close_pos = col_pos + 1; comment = false; - } - } - else if (character == '\\') - { - // skip escaped char - if (quoted || apoed) col_pos ++; - } - // this might be a comment opener - else if (character == '*') - { - // look back to see if it is actually an opener - if (!quoted && !apoed && col_pos > 0 && sass.at(col_pos - 1) == '/') - { - comment = true; open_pos = col_pos - 1; - clean += sass.substr(close_pos, open_pos - close_pos); - } - } - - // skip char - col_pos ++; - - } - - } - // EO while - - // add final parts (add half open comment text) - if (comment) clean += sass.substr(open_pos); - else clean += sass.substr(close_pos); - - // return string - return clean; - - } - // EO removeMultilineComment - - // right trim a given string - std::string rtrim(const std::string &sass) - { - std::string trimmed = sass; - size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r"); - if (pos_ws != std::string::npos) - { trimmed.erase(pos_ws + 1); } - else { trimmed.clear(); } - return trimmed; - } - // EO rtrim - - // flush whitespace and print additional text, but - // only print additional chars and buffer whitespace - std::string flush (std::string& sass, converter& converter) - { - - // return flushed - std::string scss = ""; - - // print whitespace buffer - scss += PRETTIFY(converter) > 0 ? - converter.whitespace : ""; - // reset whitespace buffer - converter.whitespace = ""; - - // remove possible newlines from string - size_t pos_right = sass.find_last_not_of("\n\r"); - if (pos_right == std::string::npos) return scss; - - // get the linefeeds from the string - std::string lfs = sass.substr(pos_right + 1); - sass = sass.substr(0, pos_right + 1); - - // find some source comment opener - size_t comment_pos = findCommentOpener(sass); - // check if there was a source comment - if (comment_pos != std::string::npos) - { - // convert comment (but only outside other coments) - if (CONVERT_COMMENT(converter) && !IS_COMMENT(converter)) - { - // convert to multiline comment - sass.at(comment_pos + 1) = '*'; - // add comment node to the whitespace - sass += " */"; - } - // not at line start - if (comment_pos > 0) - { - // also include whitespace before the actual comment opener - size_t ws_pos = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, comment_pos - 1); - comment_pos = ws_pos == std::string::npos ? 0 : ws_pos + 1; - } - if (!STRIP_COMMENT(converter)) - { - // add comment node to the whitespace - converter.whitespace += sass.substr(comment_pos); - } - else - { - // sass = removeMultilineComments(sass); - } - // update the actual sass code - sass = sass.substr(0, comment_pos); - } - - // add newline as getline discharged it - converter.whitespace += lfs + "\n"; - - // maybe remove any leading whitespace - if (PRETTIFY(converter) == 0) - { - // remove leading whitespace and update string - size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); - if (pos_left != std::string::npos) sass = sass.substr(pos_left); - } - - // add flushed data - scss += sass; - - // return string - return scss; - - } - // EO flush - - // process a line of the sass text - std::string process (std::string& sass, converter& converter) - { - - // resulting string - std::string scss = ""; - - // strip multi line comments - if (STRIP_COMMENT(converter)) - { - sass = removeMultilineComment(sass); - } - - // right trim input - sass = rtrim(sass); - - // get position of first meaningfull character in string - size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); - - // special case for final run - if (converter.end_of_file) pos_left = 0; - - // maybe has only whitespace - if (pos_left == std::string::npos) - { - // just add complete whitespace - converter.whitespace += sass + "\n"; - } - // have meaningfull first char - else - { - - // extract and store indentation string - std::string indent = sass.substr(0, pos_left); - - // check if current line starts a comment - std::string open = sass.substr(pos_left, 2); - - // line has less or same indentation - // finalize previous open parser context - if (indent.length() <= INDENT(converter).length()) - { - - // close multilinie comment - if (IS_CSS_COMMENT(converter)) - { - // check if comments will be stripped anyway - if (!STRIP_COMMENT(converter)) scss += " */"; - } - // close src comment comment - else if (IS_SRC_COMMENT(converter)) - { - // add a newline to avoid closer on same line - // this would put the bracket in the comment node - // no longer needed since we parse them correctly - // if (KEEP_COMMENT(converter)) scss += "\n"; - } - // close css properties - else if (converter.property) - { - // add closer unless in concat mode - if (!converter.comma) - { - // if there was no colon we have a selector - // looks like there were no inner properties - if (converter.selector) scss += " {}"; - // add final semicolon - else if (!converter.semicolon) scss += ";"; - } - } - - // reset comment state - converter.comment = ""; - - } - - // make sure we close every "higher" block - while (indent.length() < INDENT(converter).length()) - { - // pop stacked context - converter.indents.pop(); - // print close bracket - if (IS_PARSING(converter)) - { scss += closer(converter); } - else { scss += " */"; } - // reset comment state - converter.comment = ""; - } - - // reset converter state - converter.selector = false; - - // looks like some undocumented behavior ... - // https://github.com/mgreter/sass2scss/issues/29 - if (sass.substr(pos_left, 1) == "\\") { - converter.selector = true; - sass[pos_left] = ' '; - } - - // check if we have sass property syntax - if (sass.substr(pos_left, 1) == ":" && sass.substr(pos_left, 2) != "::") - { - - // default to a selector - // change back if property found - converter.selector = true; - // get position of first whitespace char - size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); - // assertion check for valid result - if (pos_wspace != std::string::npos) - { - // get the possible pseudo selector - std::string pseudo = sass.substr(pos_left, pos_wspace - pos_left); - // get position of the first real property value char - // pseudo selectors get this far, but have no actual value - size_t pos_value = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_wspace); - // assertion check for valid result - if (pos_value != std::string::npos) - { - // only process if not (fallowed by a semicolon or is a pseudo selector) - if (!(sass.at(pos_value) == ':' || isPseudoSelector(pseudo))) - { - // create new string by interchanging the colon sign for property and value - sass = indent + sass.substr(pos_left + 1, pos_wspace - pos_left - 1) + ":" + sass.substr(pos_wspace); - // try to find a colon in the current line, but only ... - size_t pos_colon = sass.find_first_not_of(":", pos_left); - // assertion for valid result - if (pos_colon != std::string::npos) - { - // ... after the first word (skip beginning colons) - pos_colon = sass.find_first_of(":", pos_colon); - // it is a selector if there was no colon found - converter.selector = pos_colon == std::string::npos; - } - } - } - } - - // check if we have a BEM property (one colon and no selector) - if (sass.substr(pos_left, 1) == ":" && converter.selector == true) { - size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); - sass = indent + sass.substr(pos_left + 1, pos_wspace) + ":"; - } - - } - - // terminate some statements immediately - else if ( - sass.substr(pos_left, 5) == "@warn" || - sass.substr(pos_left, 6) == "@debug" || - sass.substr(pos_left, 6) == "@error" || - sass.substr(pos_left, 6) == "@value" || - sass.substr(pos_left, 8) == "@charset" || - sass.substr(pos_left, 10) == "@namespace" - ) { sass = indent + sass.substr(pos_left); } - // replace some specific sass shorthand directives (if not fallowed by a white space character) - else if (sass.substr(pos_left, 1) == "=") - { sass = indent + "@mixin " + sass.substr(pos_left + 1); } - else if (sass.substr(pos_left, 1) == "+") - { - // must be followed by a mixin call (no whitespace afterwards or at ending directly) - if (sass[pos_left+1] != 0 && sass[pos_left+1] != ' ' && sass[pos_left+1] != '\t') { - sass = indent + "@include " + sass.substr(pos_left + 1); - } - } - - // add quotes for import if needed - else if (sass.substr(pos_left, 7) == "@import") - { - // get positions for the actual import url - size_t pos_import = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left + 7); - size_t pos = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_import); - size_t start = pos; - bool in_dqstr = false; - bool in_sqstr = false; - bool is_escaped = false; - do { - if (is_escaped) { - is_escaped = false; - } - else if (sass[pos] == '\\') { - is_escaped = true; - } - else if (sass[pos] == '"') { - if (!in_sqstr) in_dqstr = ! in_dqstr; - } - else if (sass[pos] == '\'') { - if (!in_dqstr) in_sqstr = ! in_sqstr; - } - else if (in_dqstr || in_sqstr) { - // skip over quoted stuff - } - else if (sass[pos] == ',' || sass[pos] == 0) { - if (sass[start] != '"' && sass[start] != '\'' && !isUrl(sass, start)) { - size_t end = findLastCharacter(sass, pos - 1) + 1; - sass = sass.replace(end, 0, "\""); - sass = sass.replace(start, 0, "\""); - pos += 2; - } - start = findFirstCharacter(sass, pos + 1); - } - } - while (sass[pos++] != 0); - - } - else if ( - sass.substr(pos_left, 7) != "@return" && - sass.substr(pos_left, 7) != "@extend" && - sass.substr(pos_left, 8) != "@include" && - sass.substr(pos_left, 8) != "@content" - ) { - - // probably a selector anyway - converter.selector = true; - // try to find first colon in the current line - size_t pos_colon = sass.find_first_of(":", pos_left); - // assertion that we have a colon - if (pos_colon != std::string::npos) - { - // it is not a selector if we have a space after a colon - if (sass[pos_colon+1] == ' ') converter.selector = false; - if (sass[pos_colon+1] == ' ') converter.selector = false; - } - - } - - // current line has more indentation - if (indent.length() >= INDENT(converter).length()) - { - // not in comment mode - if (IS_PARSING(converter)) - { - // has meaningfull chars - if (hasCharData(sass)) - { - // is probably a property - // also true for selectors - converter.property = true; - } - } - } - // current line has more indentation - if (indent.length() > INDENT(converter).length()) - { - // not in comment mode - if (IS_PARSING(converter)) - { - // had meaningfull chars - if (converter.property) - { - // print block opener - scss += opener(converter); - // push new stack context - converter.indents.push(""); - // store block indentation - INDENT(converter) = indent; - } - } - // is and will be a src comment - else if (!IS_CSS_COMMENT(converter)) - { - // scss does not allow multiline src comments - // therefore add forward slashes to all lines - sass.at(INDENT(converter).length()+0) = '/'; - // there is an edge case here if indentation - // is minimal (will overwrite the fist char) - sass.at(INDENT(converter).length()+1) = '/'; - // could code around that, but I dont' think - // this will ever be the cause for any trouble - } - } - - // line is opening a new comment - if (open == "/*" || open == "//") - { - // reset the property state - converter.property = false; - // close previous comment - if (IS_CSS_COMMENT(converter) && open != "") - { - if (!STRIP_COMMENT(converter) && !CONVERT_COMMENT(converter)) scss += " */"; - } - // force single line comments - // into a correct css comment - if (CONVERT_COMMENT(converter)) - { - if (IS_PARSING(converter)) - { sass.at(pos_left + 1) = '*'; } - } - // set comment flag - converter.comment = open; - - } - - // flush data only under certain conditions - if (!( - // strip css and src comments if option is set - (IS_COMMENT(converter) && STRIP_COMMENT(converter)) || - // strip src comment even if strip option is not set - // but only if the keep src comment option is not set - (IS_SRC_COMMENT(converter) && ! KEEP_COMMENT(converter)) - )) - { - // flush data and buffer whitespace - scss += flush(sass, converter); - } - - // get position of last meaningfull char - size_t pos_right = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); - - // check for invalid result - if (pos_right != std::string::npos) - { - - // get the last meaningfull char - std::string close = sass.substr(pos_right, 1); - - // check if next line should be concatenated (list mode) - converter.comma = IS_PARSING(converter) && close == ","; - converter.semicolon = IS_PARSING(converter) && close == ";"; - - // check if we have more than - // one meaningfull char - if (pos_right > 0) - { - - // get the last two chars from string - std::string close = sass.substr(pos_right - 1, 2); - // update parser status for expicitly closed comment - if (close == "*/") converter.comment = ""; - - } - - } - // EO have meaningfull chars from end - - } - // EO have meaningfull chars from start - - // return scss - return scss; - - } - // EO process - - // read line with either CR, LF or CR LF format - // http://stackoverflow.com/a/6089413/1550314 - static std::istream& safeGetline(std::istream& is, std::string& t) - { - t.clear(); - - // The characters in the stream are read one-by-one using a std::streambuf. - // That is faster than reading them one-by-one using the std::istream. - // Code that uses streambuf this way must be guarded by a sentry object. - // The sentry object performs various tasks, - // such as thread synchronization and updating the stream state. - - std::istream::sentry se(is, true); - std::streambuf* sb = is.rdbuf(); - - for(;;) { - int c = sb->sbumpc(); - switch (c) { - case '\n': - return is; - case '\r': - if(sb->sgetc() == '\n') - sb->sbumpc(); - return is; - case EOF: - // Also handle the case when the last line has no line ending - if(t.empty()) - is.setstate(std::ios::eofbit); - return is; - default: - t += (char)c; - } - } - } - - // the main converter function for c++ - char* sass2scss (const std::string& sass, const int options) - { - - // local variables - std::string line; - std::string scss = ""; - std::stringstream stream(sass); - - // create converter variable - converter converter; - // initialise all options - converter.comma = false; - converter.property = false; - converter.selector = false; - converter.semicolon = false; - converter.end_of_file = false; - converter.comment = ""; - converter.whitespace = ""; - converter.indents.push(""); - converter.options = options; - - // read line by line and process them - while(safeGetline(stream, line) && !stream.eof()) - { scss += process(line, converter); } - - // create mutable string - std::string closer = ""; - // set the end of file flag - converter.end_of_file = true; - // process to close all open blocks - scss += process(closer, converter); - - // allocate new memory on the heap - // caller has to free it after use - char * cstr = (char*) malloc (scss.length() + 1); - // create a copy of the string - strcpy (cstr, scss.c_str()); - // return pointer - return &cstr[0]; - - } - // EO sass2scss - -} -// EO namespace - -// implement for c -extern "C" -{ - - char* ADDCALL sass2scss (const char* sass, const int options) - { - return Sass::sass2scss(sass, options); - } - - // Get compiled sass2scss version - const char* ADDCALL sass2scss_version(void) { - return SASS2SCSS_VERSION; - } - -} diff --git a/src/sass_context.cpp b/src/sass_context.cpp deleted file mode 100644 index 44a0f14df4..0000000000 --- a/src/sass_context.cpp +++ /dev/null @@ -1,741 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" - -#include "sass_functions.hpp" -#include "json.hpp" - -#define LFEED "\n" - -// C++ helper -namespace Sass { - // see sass_copy_c_string(sass::string str) - static inline JsonNode* json_mkstream(const sass::ostream& stream) - { - // hold on to string on stack! - sass::string str(stream.str()); - return json_mkstring(str.c_str()); - } - - static void handle_string_error(Sass_Context* c_ctx, const sass::string& msg, int severety) - { - sass::ostream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Internal Error: " << msg << std::endl; - json_append_member(json_err, "status", json_mknumber(severety)); - json_append_member(json_err, "message", json_mkstring(msg.c_str())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(msg.c_str()); - c_ctx->error_status = severety; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - - static int handle_error(Sass_Context* c_ctx) { - try { - throw; - } - catch (Exception::Base& e) { - sass::ostream msg_stream; - sass::string cwd(Sass::File::get_cwd()); - sass::string msg_prefix(e.errtype()); - bool got_newline = false; - msg_stream << msg_prefix << ": "; - const char* msg = e.what(); - while (msg && *msg) { - if (*msg == '\r') { - got_newline = true; - } - else if (*msg == '\n') { - got_newline = true; - } - else if (got_newline) { - msg_stream << sass::string(msg_prefix.size() + 2, ' '); - got_newline = false; - } - msg_stream << *msg; - ++msg; - } - if (!got_newline) msg_stream << "\n"; - - if (e.traces.empty()) { - // we normally should have some traces, still here as a fallback - sass::string rel_path(Sass::File::abs2rel(e.pstate.getPath(), cwd, cwd)); - msg_stream << sass::string(msg_prefix.size() + 2, ' '); - msg_stream << " on line " << e.pstate.getLine() << " of " << rel_path << "\n"; - } - else { - sass::string rel_path(Sass::File::abs2rel(e.pstate.getPath(), cwd, cwd)); - msg_stream << traces_to_string(e.traces, " "); - } - - // now create the code trace (ToDo: maybe have util functions?) - if (e.pstate.position.line != sass::string::npos && - e.pstate.position.column != sass::string::npos && - e.pstate.source != nullptr) { - Offset offset(e.pstate.position); - size_t lines = offset.line; - // scan through src until target line - // move line_beg pointer to line start - const char* line_beg; - for (line_beg = e.pstate.getRawData(); *line_beg != '\0'; ++line_beg) { - if (lines == 0) break; - if (*line_beg == '\n') --lines; - } - // move line_end before next newline character - const char* line_end; - for (line_end = line_beg; *line_end != '\0'; ++line_end) { - if (*line_end == '\n' || *line_end == '\r') break; - } - if (*line_end != '\0') ++line_end; - size_t line_len = line_end - line_beg; - size_t move_in = 0; size_t shorten = 0; - size_t left_chars = 42; size_t max_chars = 76; - // reported excerpt should not exceed `max_chars` chars - if (offset.column > line_len) left_chars = offset.column; - if (offset.column > left_chars) move_in = offset.column - left_chars; - if (line_len > max_chars + move_in) shorten = line_len - move_in - max_chars; - utf8::advance(line_beg, move_in, line_end); - utf8::retreat(line_end, shorten, line_beg); - sass::string sanitized; sass::string marker(offset.column - move_in, '-'); - utf8::replace_invalid(line_beg, line_end, std::back_inserter(sanitized)); - msg_stream << ">> " << sanitized << "\n"; - msg_stream << " " << marker << "^\n"; - } - - JsonNode* json_err = json_mkobject(); - json_append_member(json_err, "status", json_mknumber(1)); - json_append_member(json_err, "file", json_mkstring(e.pstate.getPath())); - json_append_member(json_err, "line", json_mknumber((double)(e.pstate.getLine()))); - json_append_member(json_err, "column", json_mknumber((double)(e.pstate.getColumn()))); - json_append_member(json_err, "message", json_mkstring(e.what())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} // silently ignore this error? - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e.what()); - c_ctx->error_status = 1; - c_ctx->error_file = sass_copy_c_string(e.pstate.getPath()); - c_ctx->error_line = e.pstate.getLine(); - c_ctx->error_column = e.pstate.getColumn(); - c_ctx->error_src = sass_copy_c_string(e.pstate.getRawData()); - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - catch (std::bad_alloc& ba) { - sass::ostream msg_stream; - msg_stream << "Unable to allocate memory: " << ba.what(); - handle_string_error(c_ctx, msg_stream.str(), 2); - } - catch (std::exception& e) { - handle_string_error(c_ctx, e.what(), 3); - } - catch (sass::string& e) { - handle_string_error(c_ctx, e, 4); - } - catch (const char* e) { - handle_string_error(c_ctx, e, 4); - } - catch (...) { - handle_string_error(c_ctx, "unknown", 5); - } - return c_ctx->error_status; - } - - // allow one error handler to throw another error - // this can happen with invalid utf8 and json lib - static int handle_errors(Sass_Context* c_ctx) { - try { return handle_error(c_ctx); } - catch (...) { return handle_error(c_ctx); } - } - - static Block_Obj sass_parse_block(Sass_Compiler* compiler) throw() - { - - // assert valid pointer - if (compiler == 0) return {}; - // The cpp context must be set by now - Context* cpp_ctx = compiler->cpp_ctx; - Sass_Context* c_ctx = compiler->c_ctx; - // We will take care to wire up the rest - compiler->cpp_ctx->c_compiler = compiler; - compiler->state = SASS_COMPILER_PARSED; - - try { - - // get input/output path from options - sass::string input_path = safe_str(c_ctx->input_path); - sass::string output_path = safe_str(c_ctx->output_path); - - // maybe skip some entries of included files - // we do not include stdin for data contexts - bool skip = c_ctx->type == SASS_CONTEXT_DATA; - - // dispatch parse call - Block_Obj root(cpp_ctx->parse()); - // abort on errors - if (!root) return {}; - - // skip all prefixed files? (ToDo: check srcmap) - // IMO source-maps should point to headers already - // therefore don't skip it for now. re-enable or - // remove completely once this is tested - size_t headers = cpp_ctx->head_imports; - - // copy the included files on to the context (dont forget to free later) - if (copy_strings(cpp_ctx->get_included_files(skip, headers), &c_ctx->included_files) == NULL) - throw(std::bad_alloc()); - - // return parsed block - return root; - - } - // pass errors to generic error handler - catch (...) { handle_errors(c_ctx); } - - // error - return {}; - - } - -} - -extern "C" { - using namespace Sass; - - static void sass_clear_options (struct Sass_Options* options); - static void sass_reset_options (struct Sass_Options* options); - static void copy_options(struct Sass_Options* to, struct Sass_Options* from) { - // do not overwrite ourself - if (to == from) return; - // free assigned memory - sass_clear_options(to); - // move memory - *to = *from; - // Reset pointers on source - sass_reset_options(from); - } - - #define IMPLEMENT_SASS_OPTION_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) { options->option = option; } - #define IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ - type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return safe_str(options->option, def); } - #define IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) \ - void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) \ - { free(options->option); options->option = option || def ? sass_copy_c_string(option ? option : def) : 0; } - #define IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(type, option, def) \ - IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ - IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) - - #define IMPLEMENT_SASS_CONTEXT_GETTER(type, option) \ - type ADDCALL sass_context_get_##option (struct Sass_Context* ctx) { return ctx->option; } - #define IMPLEMENT_SASS_CONTEXT_TAKER(type, option) \ - type sass_context_take_##option (struct Sass_Context* ctx) \ - { type foo = ctx->option; ctx->option = 0; return foo; } - - - // generic compilation function (not exported, use file/data compile instead) - static Sass_Compiler* sass_prepare_context (Sass_Context* c_ctx, Context* cpp_ctx) throw() - { - try { - // register our custom functions - if (c_ctx->c_functions) { - auto this_func_data = c_ctx->c_functions; - while (this_func_data && *this_func_data) { - cpp_ctx->add_c_function(*this_func_data); - ++this_func_data; - } - } - - // register our custom headers - if (c_ctx->c_headers) { - auto this_head_data = c_ctx->c_headers; - while (this_head_data && *this_head_data) { - cpp_ctx->add_c_header(*this_head_data); - ++this_head_data; - } - } - - // register our custom importers - if (c_ctx->c_importers) { - auto this_imp_data = c_ctx->c_importers; - while (this_imp_data && *this_imp_data) { - cpp_ctx->add_c_importer(*this_imp_data); - ++this_imp_data; - } - } - - // 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 - c_ctx->error_file = 0; - c_ctx->error_src = 0; - c_ctx->error_line = sass::string::npos; - c_ctx->error_column = sass::string::npos; - - // allocate a new compiler instance - void* ctxmem = calloc(1, sizeof(struct Sass_Compiler)); - if (ctxmem == 0) { std::cerr << "Error allocating memory for context" << std::endl; return 0; } - Sass_Compiler* compiler = (struct Sass_Compiler*) ctxmem; - compiler->state = SASS_COMPILER_CREATED; - - // store in sass compiler - compiler->c_ctx = c_ctx; - compiler->cpp_ctx = cpp_ctx; - cpp_ctx->c_compiler = compiler; - - // use to parse block - return compiler; - - } - // pass errors to generic error handler - catch (...) { handle_errors(c_ctx); } - - // error - return 0; - - } - - // generic compilation function (not exported, use file/data compile instead) - static int sass_compile_context (Sass_Context* c_ctx, Context* cpp_ctx) - { - - // prepare sass compiler with context and options - Sass_Compiler* compiler = sass_prepare_context(c_ctx, cpp_ctx); - - try { - // call each compiler step - sass_compiler_parse(compiler); - sass_compiler_execute(compiler); - } - // pass errors to generic error handler - catch (...) { handle_errors(c_ctx); } - - sass_delete_compiler(compiler); - - return c_ctx->error_status; - } - - inline void init_options (struct Sass_Options* options) - { - options->precision = 10; - options->indent = " "; - options->linefeed = LFEED; - } - - Sass_Options* ADDCALL sass_make_options (void) - { - struct Sass_Options* options = (struct Sass_Options*) calloc(1, sizeof(struct Sass_Options)); - if (options == 0) { std::cerr << "Error allocating memory for options" << std::endl; return 0; } - init_options(options); - return options; - } - - Sass_File_Context* ADDCALL sass_make_file_context(const char* input_path) - { - #ifdef DEBUG_SHARED_PTR - SharedObj::setTaint(true); - #endif - struct Sass_File_Context* ctx = (struct Sass_File_Context*) calloc(1, sizeof(struct Sass_File_Context)); - if (ctx == 0) { std::cerr << "Error allocating memory for file context" << std::endl; return 0; } - ctx->type = SASS_CONTEXT_FILE; - init_options(ctx); - try { - if (input_path == 0) { throw(std::runtime_error("File context created without an input path")); } - if (*input_path == 0) { throw(std::runtime_error("File context created with empty input path")); } - sass_option_set_input_path(ctx, input_path); - } catch (...) { - handle_errors(ctx); - } - return ctx; - } - - Sass_Data_Context* ADDCALL sass_make_data_context(char* source_string) - { - #ifdef DEBUG_SHARED_PTR - SharedObj::setTaint(true); - #endif - struct Sass_Data_Context* ctx = (struct Sass_Data_Context*) calloc(1, sizeof(struct Sass_Data_Context)); - if (ctx == 0) { std::cerr << "Error allocating memory for data context" << std::endl; return 0; } - ctx->type = SASS_CONTEXT_DATA; - init_options(ctx); - try { - if (source_string == 0) { throw(std::runtime_error("Data context created without a source string")); } - if (*source_string == 0) { throw(std::runtime_error("Data context created with empty source string")); } - ctx->source_string = source_string; - } catch (...) { - handle_errors(ctx); - } - return ctx; - } - - struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx) - { - if (data_ctx == 0) return 0; - Context* cpp_ctx = new Data_Context(*data_ctx); - return sass_prepare_context(data_ctx, cpp_ctx); - } - - struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx) - { - if (file_ctx == 0) return 0; - Context* cpp_ctx = new File_Context(*file_ctx); - return sass_prepare_context(file_ctx, cpp_ctx); - } - - int ADDCALL sass_compile_data_context(Sass_Data_Context* data_ctx) - { - if (data_ctx == 0) return 1; - if (data_ctx->error_status) - return data_ctx->error_status; - try { - if (data_ctx->source_string == 0) { throw(std::runtime_error("Data context has no source string")); } - // empty source string is a valid case, even if not really useful (different than with file context) - // if (*data_ctx->source_string == 0) { throw(std::runtime_error("Data context has empty source string")); } - } - catch (...) { return handle_errors(data_ctx) | 1; } - Context* cpp_ctx = new Data_Context(*data_ctx); - return sass_compile_context(data_ctx, cpp_ctx); - } - - int ADDCALL sass_compile_file_context(Sass_File_Context* file_ctx) - { - if (file_ctx == 0) return 1; - if (file_ctx->error_status) - return file_ctx->error_status; - try { - if (file_ctx->input_path == 0) { throw(std::runtime_error("File context has no input path")); } - if (*file_ctx->input_path == 0) { throw(std::runtime_error("File context has empty input path")); } - } - catch (...) { return handle_errors(file_ctx) | 1; } - Context* cpp_ctx = new File_Context(*file_ctx); - return sass_compile_context(file_ctx, cpp_ctx); - } - - int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler) - { - if (compiler == 0) return 1; - if (compiler->state == SASS_COMPILER_PARSED) return 0; - if (compiler->state != SASS_COMPILER_CREATED) return -1; - if (compiler->c_ctx == NULL) return 1; - if (compiler->cpp_ctx == NULL) return 1; - if (compiler->c_ctx->error_status) - return compiler->c_ctx->error_status; - // parse the context we have set up (file or data) - compiler->root = sass_parse_block(compiler); - // success - return 0; - } - - int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler) - { - if (compiler == 0) return 1; - if (compiler->state == SASS_COMPILER_EXECUTED) return 0; - if (compiler->state != SASS_COMPILER_PARSED) return -1; - if (compiler->c_ctx == NULL) return 1; - if (compiler->cpp_ctx == NULL) return 1; - if (compiler->root.isNull()) return 1; - if (compiler->c_ctx->error_status) - return compiler->c_ctx->error_status; - compiler->state = SASS_COMPILER_EXECUTED; - Context* cpp_ctx = compiler->cpp_ctx; - Block_Obj root = compiler->root; - // compile the parsed root block - try { compiler->c_ctx->output_string = cpp_ctx->render(root); } - // pass catched errors to generic error handler - catch (...) { return handle_errors(compiler->c_ctx) | 1; } - // generate source map json and store on context - compiler->c_ctx->source_map_string = cpp_ctx->render_srcmap(); - // success - return 0; - } - - // helper function, not exported, only accessible locally - static void sass_reset_options (struct Sass_Options* options) - { - // free pointer before - // or copy/move them - options->input_path = 0; - options->output_path = 0; - options->plugin_path = 0; - options->include_path = 0; - options->source_map_file = 0; - options->source_map_root = 0; - options->c_functions = 0; - options->c_importers = 0; - options->c_headers = 0; - options->plugin_paths = 0; - options->include_paths = 0; - } - - // helper function, not exported, only accessible locally - static void sass_clear_options (struct Sass_Options* options) - { - if (options == 0) return; - // Deallocate custom functions, headers and imports - sass_delete_function_list(options->c_functions); - sass_delete_importer_list(options->c_importers); - sass_delete_importer_list(options->c_headers); - // 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; - cur = options->include_paths; - while (cur) { - next = cur->next; - free(cur->string); - free(cur); - cur = next; - } - } - // Free options strings - free(options->input_path); - free(options->output_path); - free(options->plugin_path); - free(options->include_path); - free(options->source_map_file); - free(options->source_map_root); - // Reset our pointers - options->input_path = 0; - options->output_path = 0; - options->plugin_path = 0; - options->include_path = 0; - options->source_map_file = 0; - options->source_map_root = 0; - options->c_functions = 0; - options->c_importers = 0; - options->c_headers = 0; - options->plugin_paths = 0; - options->include_paths = 0; - } - - // helper function, not exported, only accessible locally - // sass_free_context is also defined in old sass_interface - static void sass_clear_context (struct Sass_Context* ctx) - { - if (ctx == 0) return; - // release the allocated memory (mostly via sass_copy_c_string) - 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->error_src) free(ctx->error_src); - 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->error_src = 0; - ctx->included_files = 0; - // debug leaked memory - #ifdef DEBUG_SHARED_PTR - SharedObj::dumpMemLeaks(); - #endif - // now clear the options - sass_clear_options(ctx); - } - - void ADDCALL sass_delete_compiler (struct Sass_Compiler* compiler) - { - if (compiler == 0) { - return; - } - Context* cpp_ctx = compiler->cpp_ctx; - if (cpp_ctx) delete(cpp_ctx); - compiler->cpp_ctx = NULL; - compiler->c_ctx = NULL; - compiler->root = {}; - free(compiler); - } - - void ADDCALL sass_delete_options (struct Sass_Options* options) - { - sass_clear_options(options); free(options); - } - - // Deallocate all associated memory with file context - void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx) - { - // clear the context and free it - sass_clear_context(ctx); free(ctx); - } - // Deallocate all associated memory with data context - void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx) - { - // clean the source string if it was not passed - // we reset this member once we start parsing - if (ctx->source_string) free(ctx->source_string); - // clear the context and free it - sass_clear_context(ctx); free(ctx); - } - - // Getters for sass context from specific implementations - struct Sass_Context* ADDCALL sass_file_context_get_context(struct Sass_File_Context* ctx) { return ctx; } - struct Sass_Context* ADDCALL sass_data_context_get_context(struct Sass_Data_Context* ctx) { return ctx; } - - // Getters for context options from Sass_Context - struct Sass_Options* ADDCALL sass_context_get_options(struct Sass_Context* ctx) { return ctx; } - struct Sass_Options* ADDCALL sass_file_context_get_options(struct Sass_File_Context* ctx) { return ctx; } - struct Sass_Options* ADDCALL sass_data_context_get_options(struct Sass_Data_Context* ctx) { return ctx; } - void ADDCALL sass_file_context_set_options (struct Sass_File_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } - void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } - - // Getters for Sass_Compiler options (get connected sass context) - enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler) { return compiler->state; } - struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler) { return compiler->c_ctx; } - struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler) { return compiler->c_ctx; } - // Getters for Sass_Compiler options (query import stack) - size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.size(); } - Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.back(); } - Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx) { return compiler->cpp_ctx->import_stack[idx]; } - // Getters for Sass_Compiler options (query function stack) - size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->callee_stack.size(); } - Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler) { return &compiler->cpp_ctx->callee_stack.back(); } - Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx) { return &compiler->cpp_ctx->callee_stack[idx]; } - - // Calculate the size of the stored null terminated array - size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx) - { size_t l = 0; auto i = ctx->included_files; while (i && *i) { ++i; ++l; } return l; } - - // Create getter and setters for options - IMPLEMENT_SASS_OPTION_ACCESSOR(int, precision); - IMPLEMENT_SASS_OPTION_ACCESSOR(enum Sass_Output_Style, output_style); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_comments); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_embed); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_contents); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_file_urls); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, omit_source_map_url); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, is_indented_syntax_src); - IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Function_List, c_functions); - IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_importers); - IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_headers); - IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, indent); - IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, linefeed); - IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, plugin_path, 0); - IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, include_path, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, input_path, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, output_path, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_file, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_root, 0); - - // 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(const char*, error_src); - IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_line); - IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_column); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, output_string); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, source_map_string); - IMPLEMENT_SASS_CONTEXT_GETTER(char**, included_files); - - // 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*, error_src); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, output_string); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, source_map_string); - IMPLEMENT_SASS_CONTEXT_TAKER(char**, included_files); - - // Push function for include paths (no manipulation support for now) - void ADDCALL sass_option_push_include_path(struct Sass_Options* options, const char* path) - { - - struct string_list* include_path = (struct string_list*) calloc(1, sizeof(struct string_list)); - if (include_path == 0) return; - include_path->string = path ? sass_copy_c_string(path) : 0; - struct string_list* last = options->include_paths; - if (!options->include_paths) { - options->include_paths = include_path; - } else { - while (last->next) - last = last->next; - last->next = include_path; - } - - } - - // Push function for include paths (no manipulation support for now) - size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options) - { - size_t len = 0; - struct string_list* cur = options->include_paths; - while (cur) { len ++; cur = cur->next; } - return len; - } - - // Push function for include paths (no manipulation support for now) - const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i) - { - struct string_list* cur = options->include_paths; - while (i) { i--; cur = cur->next; } - return cur->string; - } - - // Push function for plugin paths (no manipulation support for now) - size_t ADDCALL sass_option_get_plugin_path_size(struct Sass_Options* options) - { - size_t len = 0; - struct string_list* cur = options->plugin_paths; - while (cur) { len++; cur = cur->next; } - return len; - } - - // Push function for plugin paths (no manipulation support for now) - const char* ADDCALL sass_option_get_plugin_path(struct Sass_Options* options, size_t i) - { - struct string_list* cur = options->plugin_paths; - while (i) { i--; cur = cur->next; } - return cur->string; - } - - // 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_copy_c_string(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/src/sass_context.hpp b/src/sass_context.hpp deleted file mode 100644 index 16c8f16f51..0000000000 --- a/src/sass_context.hpp +++ /dev/null @@ -1,129 +0,0 @@ -#ifndef SASS_SASS_CONTEXT_H -#define SASS_SASS_CONTEXT_H - -#include "sass/base.h" -#include "sass/context.h" -#include "ast_fwd_decl.hpp" - -// sass config options structure -struct Sass_Options : Sass_Output_Options { - - // embed sourceMappingUrl as data uri - bool source_map_embed; - - // embed include contents in maps - bool source_map_contents; - - // create file urls for sources - bool source_map_file_urls; - - // Disable sourceMappingUrl in css output - bool omit_source_map_url; - - // Treat source_string as sass (as opposed to scss) - bool is_indented_syntax_src; - - // The input path is used for source map - // generation. It can be used to define - // something with string compilation or to - // overload the input file path. It is - // set to "stdin" for data contexts and - // to the input file on file contexts. - char* input_path; - - // The output path is used for source map - // generation. LibSass will not write to - // this file, it is just used to create - // information in source-maps etc. - char* output_path; - - // Colon-separated list of paths - // Semicolon-separated on Windows - // Maybe use array interface instead? - char* include_path; - char* plugin_path; - - // 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_Function_List c_functions; - - // List of custom importers - Sass_Importer_List c_importers; - - // List of custom headers - Sass_Importer_List c_headers; - -}; - - -// base for all contexts -struct Sass_Context : Sass_Options -{ - - // store context type info - enum Sass_Input_Style type; - - // generated output data - char* output_string; - - // generated source map json - char* source_map_string; - - // error status - int error_status; - char* error_json; - char* error_text; - char* error_message; - // error position - char* error_file; - size_t error_line; - size_t error_column; - char* error_src; - - // report imported files - char** included_files; - -}; - -// struct for file compilation -struct Sass_File_Context : Sass_Context { - - // no additional fields required - // input_path is already on options - -}; - -// struct for data compilation -struct Sass_Data_Context : Sass_Context { - - // provided source string - char* source_string; - char* srcmap_string; - -}; - -// link c and cpp context -struct Sass_Compiler { - // progress status - Sass_Compiler_State state; - // original c context - Sass_Context* c_ctx; - // Sass::Context - Sass::Context* cpp_ctx; - // Sass::Block - Sass::Block_Obj root; -}; - -#endif diff --git a/src/sass_functions.cpp b/src/sass_functions.cpp deleted file mode 100644 index e576d47c44..0000000000 --- a/src/sass_functions.cpp +++ /dev/null @@ -1,210 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include "util.hpp" -#include "context.hpp" -#include "values.hpp" -#include "sass/functions.h" -#include "sass_functions.hpp" - -extern "C" { - using namespace Sass; - - Sass_Function_List ADDCALL sass_make_function_list(size_t length) - { - return (Sass_Function_List) calloc(length + 1, sizeof(Sass_Function_Entry)); - } - - Sass_Function_Entry ADDCALL sass_make_function(const char* signature, Sass_Function_Fn function, void* cookie) - { - Sass_Function_Entry cb = (Sass_Function_Entry) calloc(1, sizeof(Sass_Function)); - if (cb == 0) return 0; - cb->signature = sass_copy_c_string(signature); - cb->function = function; - cb->cookie = cookie; - return cb; - } - - void ADDCALL sass_delete_function(Sass_Function_Entry entry) - { - free(entry->signature); - free(entry); - } - - // Deallocator for the allocated memory - void ADDCALL sass_delete_function_list(Sass_Function_List list) - { - Sass_Function_List it = list; - if (list == 0) return; - while(*list) { - sass_delete_function(*list); - ++list; - } - free(it); - } - - // Setters and getters for callbacks on function lists - Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos) { return list[pos]; } - void sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb) { list[pos] = cb; } - - const char* ADDCALL sass_function_get_signature(Sass_Function_Entry cb) { return cb->signature; } - Sass_Function_Fn ADDCALL sass_function_get_function(Sass_Function_Entry cb) { return cb->function; } - void* ADDCALL sass_function_get_cookie(Sass_Function_Entry cb) { return cb->cookie; } - - Sass_Importer_Entry ADDCALL sass_make_importer(Sass_Importer_Fn importer, double priority, void* cookie) - { - Sass_Importer_Entry cb = (Sass_Importer_Entry) calloc(1, sizeof(Sass_Importer)); - if (cb == 0) return 0; - cb->importer = importer; - cb->priority = priority; - cb->cookie = cookie; - return cb; - } - - Sass_Importer_Fn ADDCALL sass_importer_get_function(Sass_Importer_Entry cb) { return cb->importer; } - double ADDCALL sass_importer_get_priority (Sass_Importer_Entry cb) { return cb->priority; } - void* ADDCALL sass_importer_get_cookie(Sass_Importer_Entry cb) { return cb->cookie; } - - // Just in case we have some stray import structs - void ADDCALL sass_delete_importer (Sass_Importer_Entry cb) - { - free(cb); - } - - // Creator for sass custom importer function list - Sass_Importer_List ADDCALL sass_make_importer_list(size_t length) - { - return (Sass_Importer_List) calloc(length + 1, sizeof(Sass_Importer_Entry)); - } - - // Deallocator for the allocated memory - void ADDCALL sass_delete_importer_list(Sass_Importer_List list) - { - Sass_Importer_List it = list; - if (list == 0) return; - while(*list) { - sass_delete_importer(*list); - ++list; - } - free(it); - } - - Sass_Importer_Entry ADDCALL sass_importer_get_list_entry(Sass_Importer_List list, size_t idx) { return list[idx]; } - void ADDCALL sass_importer_set_list_entry(Sass_Importer_List list, size_t idx, Sass_Importer_Entry cb) { list[idx] = cb; } - - // Creator for sass custom importer return argument list - Sass_Import_List ADDCALL sass_make_import_list(size_t length) - { - return (Sass_Import**) calloc(length + 1, sizeof(Sass_Import*)); - } - - // Creator for a single import entry returned by the custom importer inside the list - // We take ownership of the memory for source and srcmap (freed when context is destroyed) - Sass_Import_Entry ADDCALL sass_make_import(const char* imp_path, const char* abs_path, char* source, char* srcmap) - { - Sass_Import* v = (Sass_Import*) calloc(1, sizeof(Sass_Import)); - if (v == 0) return 0; - v->imp_path = imp_path ? sass_copy_c_string(imp_path) : 0; - v->abs_path = abs_path ? sass_copy_c_string(abs_path) : 0; - v->source = source; - v->srcmap = srcmap; - v->error = 0; - v->line = -1; - v->column = -1; - return v; - } - - // Older style, but somehow still valid - keep around or deprecate? - Sass_Import_Entry ADDCALL sass_make_import_entry(const char* path, char* source, char* srcmap) - { - 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) - Sass_Import_Entry ADDCALL sass_import_set_error(Sass_Import_Entry import, const char* error, size_t line, size_t col) - { - if (import == 0) return 0; - if (import->error) free(import->error); - import->error = error ? sass_copy_c_string(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(Sass_Import_List list, size_t idx, Sass_Import_Entry entry) { list[idx] = entry; } - Sass_Import_Entry ADDCALL sass_import_get_list_entry(Sass_Import_List list, size_t idx) { return list[idx]; } - - // Deallocator for the allocated memory - void ADDCALL sass_delete_import_list(Sass_Import_List list) - { - Sass_Import_List it = list; - if (list == 0) return; - while(*list) { - sass_delete_import(*list); - ++list; - } - free(it); - } - - // Just in case we have some stray import structs - void ADDCALL sass_delete_import(Sass_Import_Entry import) - { - free(import->imp_path); - free(import->abs_path); - free(import->source); - free(import->srcmap); - free(import->error); - free(import); - } - - // Getter for callee entry - const char* ADDCALL sass_callee_get_name(Sass_Callee_Entry entry) { return entry->name; } - const char* ADDCALL sass_callee_get_path(Sass_Callee_Entry entry) { return entry->path; } - size_t ADDCALL sass_callee_get_line(Sass_Callee_Entry entry) { return entry->line; } - size_t ADDCALL sass_callee_get_column(Sass_Callee_Entry entry) { return entry->column; } - enum Sass_Callee_Type ADDCALL sass_callee_get_type(Sass_Callee_Entry entry) { return entry->type; } - Sass_Env_Frame ADDCALL sass_callee_get_env (Sass_Callee_Entry entry) { return &entry->env; } - - // Getters and Setters for environments (lexical, local and global) - union Sass_Value* ADDCALL sass_env_get_lexical (Sass_Env_Frame env, const char* name) { - Expression* ex = Cast((*env->frame)[name]); - return ex != NULL ? ast_node_to_sass_value(ex) : NULL; - } - void ADDCALL sass_env_set_lexical (Sass_Env_Frame env, const char* name, union Sass_Value* val) { - (*env->frame)[name] = sass_value_to_ast_node(val); - } - union Sass_Value* ADDCALL sass_env_get_local (Sass_Env_Frame env, const char* name) { - Expression* ex = Cast(env->frame->get_local(name)); - return ex != NULL ? ast_node_to_sass_value(ex) : NULL; - } - void ADDCALL sass_env_set_local (Sass_Env_Frame env, const char* name, union Sass_Value* val) { - env->frame->set_local(name, sass_value_to_ast_node(val)); - } - union Sass_Value* ADDCALL sass_env_get_global (Sass_Env_Frame env, const char* name) { - Expression* ex = Cast(env->frame->get_global(name)); - return ex != NULL ? ast_node_to_sass_value(ex) : NULL; - } - void ADDCALL sass_env_set_global (Sass_Env_Frame env, const char* name, union Sass_Value* val) { - env->frame->set_global(name, sass_value_to_ast_node(val)); - } - - // Getter for import entry - const char* ADDCALL sass_import_get_imp_path(Sass_Import_Entry entry) { return entry->imp_path; } - const char* ADDCALL sass_import_get_abs_path(Sass_Import_Entry entry) { return entry->abs_path; } - const char* ADDCALL sass_import_get_source(Sass_Import_Entry entry) { return entry->source; } - const char* ADDCALL sass_import_get_srcmap(Sass_Import_Entry entry) { return entry->srcmap; } - - // Getter for import error entry - size_t ADDCALL sass_import_get_error_line(Sass_Import_Entry entry) { return entry->line; } - size_t ADDCALL sass_import_get_error_column(Sass_Import_Entry entry) { return entry->column; } - const char* ADDCALL sass_import_get_error_message(Sass_Import_Entry 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(Sass_Import_Entry entry) { char* ptr = entry->source; entry->source = 0; return ptr; } - char* ADDCALL sass_import_take_srcmap(Sass_Import_Entry entry) { char* ptr = entry->srcmap; entry->srcmap = 0; return ptr; } - -} diff --git a/src/sass_functions.hpp b/src/sass_functions.hpp deleted file mode 100644 index 482ed641b3..0000000000 --- a/src/sass_functions.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef SASS_SASS_FUNCTIONS_H -#define SASS_SASS_FUNCTIONS_H - -#include "sass.h" -#include "environment.hpp" -#include "fn_utils.hpp" - -// Struct to hold custom function callback -struct Sass_Function { - char* signature; - Sass_Function_Fn function; - void* cookie; -}; - -// External import entry -struct Sass_Import { - char* imp_path; // path as found in the import statement - char *abs_path; // path after importer has resolved it - char* source; - char* srcmap; - // error handling - char* error; - size_t line; - size_t column; -}; - -// External environments -struct Sass_Env { - // links to parent frames - Sass::Env* frame; -}; - -// External call entry -struct Sass_Callee { - const char* name; - const char* path; - size_t line; - size_t column; - enum Sass_Callee_Type type; - struct Sass_Env env; -}; - -// Struct to hold importer callback -struct Sass_Importer { - Sass_Importer_Fn importer; - double priority; - void* cookie; -}; - -#endif \ No newline at end of file diff --git a/src/sass_values.cpp b/src/sass_values.cpp deleted file mode 100644 index 55bcb4d6a9..0000000000 --- a/src/sass_values.cpp +++ /dev/null @@ -1,362 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include "util.hpp" -#include "eval.hpp" -#include "operators.hpp" -#include "sass/values.h" -#include "sass_values.hpp" - -extern "C" { - using namespace Sass; - - // Return the sass tag for a generic sass value - enum Sass_Tag ADDCALL sass_value_get_tag(const union Sass_Value* v) { return v->unknown.tag; } - - // Check value for specified type - bool ADDCALL sass_value_is_null(const union Sass_Value* v) { return v->unknown.tag == SASS_NULL; } - bool ADDCALL sass_value_is_number(const union Sass_Value* v) { return v->unknown.tag == SASS_NUMBER; } - bool ADDCALL sass_value_is_string(const union Sass_Value* v) { return v->unknown.tag == SASS_STRING; } - bool ADDCALL sass_value_is_boolean(const union Sass_Value* v) { return v->unknown.tag == SASS_BOOLEAN; } - bool ADDCALL sass_value_is_color(const union Sass_Value* v) { return v->unknown.tag == SASS_COLOR; } - bool ADDCALL sass_value_is_list(const union Sass_Value* v) { return v->unknown.tag == SASS_LIST; } - bool ADDCALL sass_value_is_map(const union Sass_Value* v) { return v->unknown.tag == SASS_MAP; } - bool ADDCALL sass_value_is_error(const union Sass_Value* v) { return v->unknown.tag == SASS_ERROR; } - bool ADDCALL sass_value_is_warning(const union Sass_Value* v) { return v->unknown.tag == SASS_WARNING; } - - // Getters and setters for Sass_Number - double ADDCALL sass_number_get_value(const union Sass_Value* v) { return v->number.value; } - void ADDCALL sass_number_set_value(union Sass_Value* v, double value) { v->number.value = value; } - const char* ADDCALL sass_number_get_unit(const union Sass_Value* v) { return v->number.unit; } - void ADDCALL sass_number_set_unit(union Sass_Value* v, char* unit) { v->number.unit = unit; } - - // Getters and setters for Sass_String - const char* ADDCALL sass_string_get_value(const union Sass_Value* v) { return v->string.value; } - void ADDCALL sass_string_set_value(union Sass_Value* v, char* value) { v->string.value = value; } - bool ADDCALL sass_string_is_quoted(const union Sass_Value* v) { return v->string.quoted; } - void ADDCALL sass_string_set_quoted(union Sass_Value* v, bool quoted) { v->string.quoted = quoted; } - - // Getters and setters for Sass_Boolean - bool ADDCALL sass_boolean_get_value(const union Sass_Value* v) { return v->boolean.value; } - void ADDCALL sass_boolean_set_value(union Sass_Value* v, bool value) { v->boolean.value = value; } - - // Getters and setters for Sass_Color - double ADDCALL sass_color_get_r(const union Sass_Value* v) { return v->color.r; } - void ADDCALL sass_color_set_r(union Sass_Value* v, double r) { v->color.r = r; } - double ADDCALL sass_color_get_g(const union Sass_Value* v) { return v->color.g; } - void ADDCALL sass_color_set_g(union Sass_Value* v, double g) { v->color.g = g; } - double ADDCALL sass_color_get_b(const union Sass_Value* v) { return v->color.b; } - void ADDCALL sass_color_set_b(union Sass_Value* v, double b) { v->color.b = b; } - double ADDCALL sass_color_get_a(const union Sass_Value* v) { return v->color.a; } - void ADDCALL sass_color_set_a(union Sass_Value* v, double a) { v->color.a = a; } - - // Getters and setters for Sass_List - size_t ADDCALL sass_list_get_length(const union Sass_Value* v) { return v->list.length; } - enum Sass_Separator ADDCALL sass_list_get_separator(const union Sass_Value* v) { return v->list.separator; } - void ADDCALL sass_list_set_separator(union Sass_Value* v, enum Sass_Separator separator) { v->list.separator = separator; } - bool ADDCALL sass_list_get_is_bracketed(const union Sass_Value* v) { return v->list.is_bracketed; } - void ADDCALL sass_list_set_is_bracketed(union Sass_Value* v, bool is_bracketed) { v->list.is_bracketed = is_bracketed; } - // Getters and setters for Sass_List values - union Sass_Value* ADDCALL sass_list_get_value(const union Sass_Value* v, size_t i) { return v->list.values[i]; } - void ADDCALL sass_list_set_value(union Sass_Value* v, size_t i, union Sass_Value* value) { v->list.values[i] = value; } - - // Getters and setters for Sass_Map - size_t ADDCALL sass_map_get_length(const union Sass_Value* v) { return v->map.length; } - // Getters and setters for Sass_List keys and values - union Sass_Value* ADDCALL sass_map_get_key(const union Sass_Value* v, size_t i) { return v->map.pairs[i].key; } - union Sass_Value* ADDCALL sass_map_get_value(const union Sass_Value* v, size_t i) { return v->map.pairs[i].value; } - void ADDCALL sass_map_set_key(union Sass_Value* v, size_t i, union Sass_Value* key) { v->map.pairs[i].key = key; } - void ADDCALL sass_map_set_value(union Sass_Value* v, size_t i, union Sass_Value* val) { v->map.pairs[i].value = val; } - - // Getters and setters for Sass_Error - char* ADDCALL sass_error_get_message(const union Sass_Value* v) { return v->error.message; }; - void ADDCALL sass_error_set_message(union Sass_Value* v, char* msg) { v->error.message = msg; }; - - // Getters and setters for Sass_Warning - char* ADDCALL sass_warning_get_message(const union Sass_Value* v) { return v->warning.message; }; - void ADDCALL sass_warning_set_message(union Sass_Value* v, char* msg) { v->warning.message = msg; }; - - // Creator functions for all value types - - union Sass_Value* ADDCALL sass_make_boolean(bool val) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->boolean.tag = SASS_BOOLEAN; - v->boolean.value = val; - return v; - } - - union Sass_Value* ADDCALL sass_make_number(double val, const char* unit) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->number.tag = SASS_NUMBER; - v->number.value = val; - v->number.unit = unit ? sass_copy_c_string(unit) : 0; - if (v->number.unit == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_color(double r, double g, double b, double a) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->color.tag = SASS_COLOR; - v->color.r = r; - v->color.g = g; - v->color.b = b; - v->color.a = a; - return v; - } - - union Sass_Value* ADDCALL sass_make_string(const char* val) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->string.quoted = false; - v->string.tag = SASS_STRING; - v->string.value = val ? sass_copy_c_string(val) : 0; - if (v->string.value == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_qstring(const char* val) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->string.quoted = true; - v->string.tag = SASS_STRING; - v->string.value = val ? sass_copy_c_string(val) : 0; - if (v->string.value == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_list(size_t len, enum Sass_Separator sep, bool is_bracketed) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->list.tag = SASS_LIST; - v->list.length = len; - v->list.separator = sep; - v->list.is_bracketed = is_bracketed; - v->list.values = (union Sass_Value**) calloc(len, sizeof(union Sass_Value*)); - if (v->list.values == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_map(size_t len) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->map.tag = SASS_MAP; - v->map.length = len; - v->map.pairs = (struct Sass_MapPair*) calloc(len, sizeof(struct Sass_MapPair)); - if (v->map.pairs == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_null(void) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->null.tag = SASS_NULL; - return v; - } - - union Sass_Value* ADDCALL sass_make_error(const char* msg) - { - union 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_string(msg) : 0; - if (v->error.message == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_warning(const char* msg) - { - union 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_string(msg) : 0; - if (v->warning.message == 0) { free(v); return 0; } - return v; - } - - // will free all associated sass values - void ADDCALL sass_delete_value(union Sass_Value* val) { - - size_t i; - if (val == 0) return; - switch(val->unknown.tag) { - case SASS_NULL: { - } break; - case SASS_BOOLEAN: { - } break; - case SASS_NUMBER: { - free(val->number.unit); - } break; - case SASS_COLOR: { - } break; - case SASS_STRING: { - free(val->string.value); - } break; - case SASS_LIST: { - for (i=0; ilist.length; i++) { - sass_delete_value(val->list.values[i]); - } - free(val->list.values); - } break; - case SASS_MAP: { - for (i=0; imap.length; i++) { - sass_delete_value(val->map.pairs[i].key); - sass_delete_value(val->map.pairs[i].value); - } - free(val->map.pairs); - } break; - case SASS_ERROR: { - free(val->error.message); - } break; - case SASS_WARNING: { - free(val->error.message); - } break; - default: break; - } - - free(val); - - } - - // Make a deep cloned copy of the given sass value - union Sass_Value* ADDCALL sass_clone_value (const union Sass_Value* val) - { - - size_t i; - if (val == 0) return 0; - switch(val->unknown.tag) { - case SASS_NULL: { - return sass_make_null(); - } - case SASS_BOOLEAN: { - return sass_make_boolean(val->boolean.value); - } - case SASS_NUMBER: { - return sass_make_number(val->number.value, val->number.unit); - } - case SASS_COLOR: { - return sass_make_color(val->color.r, val->color.g, val->color.b, val->color.a); - } - case SASS_STRING: { - return sass_string_is_quoted(val) ? sass_make_qstring(val->string.value) : sass_make_string(val->string.value); - } - case SASS_LIST: { - union Sass_Value* list = sass_make_list(val->list.length, val->list.separator, val->list.is_bracketed); - for (i = 0; i < list->list.length; i++) { - list->list.values[i] = sass_clone_value(val->list.values[i]); - } - return list; - } - case SASS_MAP: { - union Sass_Value* map = sass_make_map(val->map.length); - for (i = 0; i < val->map.length; i++) { - map->map.pairs[i].key = sass_clone_value(val->map.pairs[i].key); - map->map.pairs[i].value = sass_clone_value(val->map.pairs[i].value); - } - return map; - } - case SASS_ERROR: { - return sass_make_error(val->error.message); - } - case SASS_WARNING: { - return sass_make_warning(val->warning.message); - } - default: break; - } - - return 0; - - } - - union Sass_Value* ADDCALL sass_value_stringify (const union Sass_Value* v, bool compressed, int precision) - { - ValueObj val = sass_value_to_ast_node(v); - Sass_Inspect_Options options(compressed ? COMPRESSED : NESTED, precision); - sass::string str(val->to_string(options)); - return sass_make_qstring(str.c_str()); - } - - union Sass_Value* ADDCALL sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b) - { - - Sass::ValueObj rv; - - try { - - ValueObj lhs = sass_value_to_ast_node(a); - ValueObj rhs = sass_value_to_ast_node(b); - struct Sass_Inspect_Options options(NESTED, 5); - - // see if it's a relational expression - switch(op) { - case Sass_OP::EQ: return sass_make_boolean(Operators::eq(lhs, rhs)); - case Sass_OP::NEQ: return sass_make_boolean(Operators::neq(lhs, rhs)); - case Sass_OP::GT: return sass_make_boolean(Operators::gt(lhs, rhs)); - case Sass_OP::GTE: return sass_make_boolean(Operators::gte(lhs, rhs)); - case Sass_OP::LT: return sass_make_boolean(Operators::lt(lhs, rhs)); - case Sass_OP::LTE: return sass_make_boolean(Operators::lte(lhs, rhs)); - case Sass_OP::AND: return ast_node_to_sass_value(lhs->is_false() ? lhs : rhs); - case Sass_OP::OR: return ast_node_to_sass_value(lhs->is_false() ? rhs : lhs); - default: break; - } - - if (sass_value_is_number(a) && sass_value_is_number(b)) { - const Number* l_n = Cast(lhs); - const Number* r_n = Cast(rhs); - rv = Operators::op_numbers(op, *l_n, *r_n, options, l_n->pstate()); - } - else if (sass_value_is_number(a) && sass_value_is_color(a)) { - const Number* l_n = Cast(lhs); - // Direct HSLA operations are not supported - // All color maths will be deprecated anyway - Color_RGBA_Obj r_c = Cast(rhs)->toRGBA(); - rv = Operators::op_number_color(op, *l_n, *r_c, options, l_n->pstate()); - } - else if (sass_value_is_color(a) && sass_value_is_number(b)) { - // Direct HSLA operations are not supported - // All color maths will be deprecated anyway - Color_RGBA_Obj l_c = Cast(lhs)->toRGBA(); - const Number* r_n = Cast(rhs); - rv = Operators::op_color_number(op, *l_c, *r_n, options, l_c->pstate()); - } - else if (sass_value_is_color(a) && sass_value_is_color(b)) { - // Direct HSLA operations are not supported - // All color maths will be deprecated anyway - Color_RGBA_Obj l_c = Cast(lhs)->toRGBA(); - Color_RGBA_Obj r_c = Cast(rhs)->toRGBA(); - rv = Operators::op_colors(op, *l_c, *r_c, options, l_c->pstate()); - } - else /* convert other stuff to string and apply operation */ { - rv = Operators::op_strings(op, *lhs, *rhs, options, lhs->pstate()); - } - - // ToDo: maybe we should return null value? - if (!rv) return sass_make_error("invalid return value"); - - // convert result back to ast node - return ast_node_to_sass_value(rv.ptr()); - } - - // simply pass the error message back to the caller for now - catch (Exception::InvalidSass& e) { return sass_make_error(e.what()); } - catch (std::bad_alloc&) { return sass_make_error("memory exhausted"); } - catch (std::exception& e) { return sass_make_error(e.what()); } - catch (sass::string& e) { return sass_make_error(e.c_str()); } - catch (const char* e) { return sass_make_error(e); } - catch (...) { return sass_make_error("unknown"); } - } - -} diff --git a/src/sass_values.hpp b/src/sass_values.hpp deleted file mode 100644 index 9aa5cdb337..0000000000 --- a/src/sass_values.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef SASS_SASS_VALUES_H -#define SASS_SASS_VALUES_H - -#include "sass.h" - -struct Sass_Unknown { - enum Sass_Tag tag; -}; - -struct Sass_Boolean { - enum Sass_Tag tag; - bool value; -}; - -struct Sass_Number { - enum Sass_Tag tag; - double value; - char* unit; -}; - -struct Sass_Color { - enum Sass_Tag tag; - double r; - double g; - double b; - double a; -}; - -struct Sass_String { - enum Sass_Tag tag; - bool quoted; - char* value; -}; - -struct Sass_List { - enum Sass_Tag tag; - enum Sass_Separator separator; - bool is_bracketed; - size_t length; - // null terminated "array" - union Sass_Value** values; -}; - -struct Sass_Map { - enum Sass_Tag tag; - size_t length; - struct Sass_MapPair* pairs; -}; - -struct Sass_Null { - enum Sass_Tag tag; -}; - -struct Sass_Error { - enum Sass_Tag tag; - char* message; -}; - -struct Sass_Warning { - enum Sass_Tag tag; - char* message; -}; - -union Sass_Value { - struct Sass_Unknown unknown; - struct Sass_Boolean boolean; - struct Sass_Number number; - struct Sass_Color color; - struct Sass_String string; - struct Sass_List list; - struct Sass_Map map; - struct Sass_Null null; - struct Sass_Error error; - struct Sass_Warning warning; -}; - -struct Sass_MapPair { - union Sass_Value* key; - union Sass_Value* value; -}; - -#endif diff --git a/src/scanner_string.cpp b/src/scanner_string.cpp new file mode 100644 index 0000000000..42adaf9ace --- /dev/null +++ b/src/scanner_string.cpp @@ -0,0 +1,258 @@ +#include "scanner_string.hpp" + +#include "charcode.hpp" +#include "character.hpp" +#include "exceptions.hpp" +#include "utf8/checked.h" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + StringScanner::StringScanner( + Logger& logger, + SourceDataObj source) : + source(source), + nextMap(sass::string::npos), + startpos(source->contentStart()), + endpos(source->contentEnd()), + position(source->contentStart()), + sourceUrl(source->getAbsPath()), + srcid(source->getSrcIdx()), + offset(), + relevant(), + logger(logger) + { + // consume BOM? + + // Check if we have source mappings + // ToDo: Can we make this API better? + //if (source->hasMapping(0)) { + // nextMap = 0; + //} + + // This can use up to 3% runtime (mostly under 1%) + auto invalid = utf8::find_invalid(startpos, endpos); + if (invalid != endpos) { + SourceSpan pstate(source); + Offset start(startpos, invalid); + pstate.position.line = start.line; + pstate.position.column = start.column; + throw Exception::InvalidUnicode(pstate, {}); + } + } + + // Whether the scanner has completely consumed [string]. + bool StringScanner::isDone() const + { + return position >= endpos; + } + + // Called whenever a character is consumed. + // Used to update scanner line/column position. + void StringScanner::consumedChar(uint8_t character) + { + switch (character) { + case $space: + case $tab: + case $vt: + case $ff: + case $cr: + offset.column += 1; + break; + case $lf: + offset.line += 1; + offset.column = 0; + break; + default: + // skip over 10xxxxxx and 01xxxxxx + // count ASCII and first utf8 bytes + if (Character::isCharacter(character)) { + // 64 => first utf8 byte + // 128 => regular ASCII char + offset.column += 1; + // sync relevant position + relevant = offset; + } + break; + } + } + + + // Consumes a single character and returns its character + // code. This throws a [FormatException] if the string has + // been fully consumed. It doesn't affect [lastMatch]. + uint8_t StringScanner::readChar() + { + if (isDone()) _fail("more input"); + uint8_t ascii = *position; + consumedChar(ascii); + position += 1; + return ascii; + } + + // Returns the character code of the character [offset] away + // from [position]. [offset] defaults to zero, and may be negative + // to inspect already-consumed characters. This returns `null` if + // [offset] points outside the string. It doesn't affect [lastMatch]. + uint8_t StringScanner::peekChar(size_t offset) const + { + const char* cur = position + offset; + if (cur < startpos || cur >= endpos) { + return 0; + } + return *cur; + } + + // Same as above, but stores next char into passed variable. + bool StringScanner::peekChar(uint8_t& chr, size_t offset) const + { + const char* cur = position + offset; + if (cur < startpos || cur >= endpos) { + return false; + } + chr = *cur; + return true; + } + + // If the next character in the string is [character], consumes it. + // Returns whether or not [character] was consumed. + bool StringScanner::scanChar(uint8_t character) + { + if (isDone()) return false; + uint8_t ascii = *position; + if (character != ascii) return false; + consumedChar(character); + position += 1; + return true; + } + + // If the next character in the string is [character], consumes it. + // If [character] could not be consumed, throws a [FormatException] + // describing the position of the failure. [name] is used in this + // error as the expected name of the character being matched; if + // it's empty, the character itself is used instead. + void StringScanner::expectChar(uint8_t character, const sass::string& name) + { + if (!scanChar(character)) { + if (name.empty()) { + if (character == $double_quote) { + _fail("\"\\\"\""); + } + else { + sass::string msg("\""); + msg += character; + _fail(msg + "\""); + } + } + _fail(name); + } + } + + // If [pattern] matches at the current position of the string, scans forward + // until the end of the match. Returns whether or not [pattern] matched. + bool StringScanner::scan(const sass::string& pattern) + { + const char* cur = position; + for (uint8_t code : pattern) { + if (isDone()) return false; + uint8_t ascii = *cur; + if (ascii != code) return false; + consumedChar(ascii); + cur += 1; + } + position = cur; + return true; + } + + // If [pattern] matches at the current position of the string, scans + // forward until the end of the match. If [pattern] did not match, + // throws a [FormatException] describing the position of the failure. + // [name] is used in this error as the expected name of the pattern + // being matched; if it's `null`, the pattern itself is used instead. + void StringScanner::expect(const sass::string& pattern, const sass::string& name) + { + if (!scan(pattern)) { + if (name.empty()) { + _fail(pattern); + } + _fail(name); + } + } + + // If the string has not been fully consumed, + // this throws a [FormatException]. + void StringScanner::expectDone() + { + if (isDone()) return; + SourceSpan span(rawSpan()); + callStackFrame frame(logger, span); + throw Exception::ParserException( + logger, "expected no more input."); + } + + // Returns whether or not [pattern] matches at the current position + // of the string. This doesn't move the scan pointer forward. + bool StringScanner::matches(const sass::string& pattern) + { + const char* cur = position; + for (char chr : pattern) { + if (chr != *cur) { + return false; + } + cur += 1; + } + return true; + } + + // Returns the substring of [string] between [start] and [end]. + // Unlike [String.substring], [end] defaults to [position] + // rather than the end of the string. + sass::string StringScanner::substring(const char* start, const char* end) + { + if (end == 0) end = position; + return sass::string(start, end); + } + + // Throws a [FormatException] describing that [name] is + // expected at the current position in the string. + void StringScanner::_fail( + const sass::string& name) const + { + SourceSpan span(relevantSpan()); + callStackFrame frame(logger, span); + sass::string msg("expected " + name + "."); + throw Exception::ParserException(logger, msg); + } + + // Throws a [FormatException] with [traces] and [pstate]. + void StringScanner::error( + const sass::string& message, + const BackTraces& traces, + const SourceSpan& pstate) const + { + callStackFrame frame(logger, pstate); + throw Exception::ParserException(traces, message); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool StringScanner::hasLineBreak( + const char* before) const + { + const char* cur = before; + while (cur < endpos) { + if (*cur == '\r') return true; + if (*cur == '\n') return true; + cur += 1; + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/scanner_string.hpp b/src/scanner_string.hpp new file mode 100644 index 0000000000..7bb40bf244 --- /dev/null +++ b/src/scanner_string.hpp @@ -0,0 +1,196 @@ +#ifndef SASS_SCANNER_STRING_HPP +#define SASS_SCANNER_STRING_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "source.hpp" +#include "backtrace.hpp" + +namespace Sass { + + struct StringScannerState { + const char* position; + Offset offset; + }; + + // A class that scans through a string using [Pattern]s. + class StringScanner { + + public: + + StringScanner( + Logger& logger, + SourceDataObj source); + + // The source associated with this scanner + SourceDataObj source; + + // Next source mapping + size_t nextMap; + + // The string being scanned through. + const char* startpos; + + // The final position to scan to. + const char* endpos; + + // The current position. + const char* position; + + // The URL of the source of the string being scanned. + // This is used for error reporting. It may be `null`, + // indicating that the source URL is unknown or unavailable. + const char* sourceUrl; + + // The global id for this input file. + size_t srcid; + + // The current line/col offset + Offset offset; + + // Last non whitespace position + // Used to create parser state spans + Offset relevant; + + // Attached logger + Logger& logger; + + public: + + // Whether the scanner has completely consumed [string]. + bool isDone() const; + + // Called whenever a character is consumed. + // Used to update scanner line/column position. + void consumedChar(uint8_t character); + + // Consumes a single character and returns its character + // code. This throws a [FormatException] if the string has + // been fully consumed. It doesn't affect [lastMatch]. + uint8_t readChar(); + + // Returns the character code of the character [offset] away + // from [position]. [offset] defaults to zero, and may be negative + // to inspect already-consumed characters. This returns `null` if + // [offset] points outside the string. It doesn't affect [lastMatch]. + uint8_t peekChar(size_t offset = 0) const; + + bool peekChar(uint8_t& chr, size_t offset = 0) const; + + // If the next character in the string is [character], consumes it. + // Returns whether or not [character] was consumed. + bool scanChar(uint8_t character); + + // If the next character in the string is [character], consumes it. + // If [character] could not be consumed, throws a [FormatException] + // describing the position of the failure. [name] is used in this + // error as the expected name of the character being matched; if + // it's `null`, the character itself is used instead. + void expectChar(uint8_t character, const sass::string& name = Strings::empty); + + // If [pattern] matches at the current position of the string, scans forward + // until the end of the match. Returns whether or not [pattern] matched. + bool scan(const sass::string& pattern); + + // If [pattern] matches at the current position of the string, scans + // forward until the end of the match. If [pattern] did not match, + // throws a [FormatException] describing the position of the failure. + // [name] is used in this error as the expected name of the pattern + // being matched; if it's `null`, the pattern itself is used instead. + void expect(const sass::string& pattern, const sass::string& name = Strings::empty); + + // If the string has not been fully consumed, + // this throws a [FormatException]. + void expectDone(); + + // Returns whether or not [pattern] matches at the current position + // of the string. This doesn't move the scan pointer forward. + bool matches(const sass::string& pattern); + + // Returns the substring of [string] between [start] and [end]. + // Unlike [String.substring], [end] defaults to [position] + // rather than the end of the string. + sass::string substring(const char* start, const char* end = 0); + + // Throws a [FormatException] describing that [name] is + // expected at the current position in the string. + void _fail(const sass::string& name) const; + + // Throws a [FormatException] with [traces] and [pstate]. + void error(const sass::string& name, + const BackTraces& traces, + const SourceSpan& pstate) const; + + bool hasLineBreak(const char* before) const; + + StringScannerState state() { + return { position, offset }; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Backtrack the scanner to the given position + void backtrack(const StringScannerState& state) + { + position = state.position; + offset.line = state.offset.line; + offset.column = state.offset.column; + // We assume we always store states + // only from relevant positions. + relevant = offset; + } + + // Get a source span pointing to raw position + // Raw means whitespace may already be consumed + inline SourceSpan spanAt(Offset& start) const + { + SourceSpan pstate(source, start); + return source ? source->adjustSourceSpan(pstate) : pstate; + } + + // Get a source span pointing to raw position + // Raw means whitespace may already be consumed + inline SourceSpan rawSpan() const + { + SourceSpan pstate(source, offset); + return source ? source->adjustSourceSpan(pstate) : pstate; + } + + // Get a source span pointing to last relevant position + // Last relevant means whitespace is not yet parsed (word ending) + inline SourceSpan relevantSpan() const // 161 + { + SourceSpan pstate(source, relevant); + return source ? source->adjustSourceSpan(pstate) : pstate; + } + + // Create a source span from start to raw position + // Raw means whitespace may already be consumed + inline SourceSpan rawSpanFrom(const Offset& start) // 53 + { + SourceSpan pstate(source, start, Offset::distance(start, offset)); + return source ? source->adjustSourceSpan(pstate) : pstate; + } + + // Create a source span from start to last relevant position + // Last relevant means whitespace is not yet parsed (word ending) + inline SourceSpan relevantSpanFrom(const Offset& start) // 161 + { + SourceSpan pstate(source, start, Offset::distance(start, relevant)); + return source ? source->adjustSourceSpan(pstate) : pstate; + } + + inline SourceSpan relevantSpanFrom(const Offset& start, size_t delta) // 161 + { + SourceSpan pstate(source, start, Offset::distance(start, relevant)); + return source ? source->adjustSourceSpan(pstate) : pstate; + } + + }; + +} + +#endif diff --git a/src/settings.hpp b/src/settings.hpp index e4a1938ba9..02c8695dcc 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -1,19 +1,66 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #ifndef SASS_SETTINGS_H #define SASS_SETTINGS_H // Global compile time settings should go here +// These settings are meant to be customized + +///////////////////////////////////////////////////////////////////////// +// Sass default settings +///////////////////////////////////////////////////////////////////////// + +// Default precision to format floats +#define SassDefaultPrecision 10 + +///////////////////////////////////////////////////////////////////////// +// Logger default settings +///////////////////////////////////////////////////////////////////////// + +// Default output character columns +#define SassDefaultColumns 80 + +///////////////////////////////////////////////////////////////////////// +// Optional static hash seed +///////////////////////////////////////////////////////////////////////// + +// Define static hash seed (random otherwise) +// #define STAStaticHashSeed 0x9e3779b9 + +///////////////////////////////////////////////////////////////////////// +// Optimization configurations +///////////////////////////////////////////////////////////////////////// // When enabled we use our custom memory pool allocator // With intense workloads this can double the performance // Max memory usage mostly only grows by a slight amount -// #define SASS_CUSTOM_ALLOCATOR +// Brings up to 50% improvement with minor memory overhead. +#define SASS_CUSTOM_ALLOCATOR + +// Elide unnecessary value copies in eval, as we don't need to +// make copies already in the eval stage (I think). All further +// operations on those values will create a copy anyway! +// This applies to Number, String, Color and Boolean. +// Brings up to 5% improvement +#define SASS_ELIDE_COPIES + +///////////////////////////////////////////////////////////////////////// +// Self assign optimization is experimental and may break your code +///////////////////////////////////////////////////////////////////////// +// Optimize self assign use-case where certain function would create +// a new copy and then assign to ourself, we can e.g. optimize map-merge +// or list-append in the following case: `$map = map-merge($map, $other)`. +// In order to do this we scan if on any assignment the right hand side +// is a function with the same variable as the first arguments. This flag +// is passed to the function when executed, so it knows to alter in-place. +///////////////////////////////////////////////////////////////////////// +#define SASS_OPTIMIZE_SELF_ASSIGN -// How many buckets should we have for the free-list -// Determines when allocations go directly to malloc/free -// For maximum size of managed items multiply by alignment -#define SassAllocatorBuckets 512 +// Number of references to safely self assign +#define AssignableRefCount 3 -// The size of the memory pool arenas in bytes. -#define SassAllocatorArenaSize (1024 * 256) +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// #endif diff --git a/src/source.cpp b/src/source.cpp index 1ffab30b94..96a96bf5e5 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1,69 +1,272 @@ -#include -#include #include "source.hpp" -#include "utf8/checked.h" -#include "position.hpp" + +#include +#include "unicode.hpp" +#include "charcode.hpp" +#include "character.hpp" namespace Sass { + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + SourceData::SourceData() : SharedObj() + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SourceWithPath::SourceWithPath( + sass::string&& imp_path, + sass::string&& abs_path, + size_t idx) : + imp_path(std::move(imp_path)), + abs_path(std::move(abs_path)), + len_content(0), + len_srcmaps(0), + srcidx(idx), + lfs() + {} + + SourceWithPath::SourceWithPath( + const sass::string& imp_path, + const sass::string& abs_path, + size_t idx) : + imp_path(imp_path), + abs_path(abs_path), + len_content(0), + len_srcmaps(0), + srcidx(idx), + lfs() + {} + + // Standard implementation for raw char API + size_t SourceWithPath::countLines() + { + if (lfs.empty()) { + size_t len = 0; + lfs.emplace_back(len); + const char* data = content(); + while (data[len] != 0) { + if (data[len] == $lf) { + lfs.emplace_back(len + 1); + } + ++len; + } + lfs.emplace_back(len); + } + + return lfs.size() - 1; + } + + // Standard implementation for raw char API + sass::string SourceWithPath::getLine(size_t line) { + countLines(); + if (line > lfs.size()) { + return sass::string(); + } + size_t first = lfs[line]; + size_t last = lfs[line + 1]; + if (first == last) return sass::string(); + const char* beg = content() + first; + const char* end = content() + last; + if (end[-1] == $lf) end -= 1; + if (end[-1] == $cr) end -= 1; + return sass::string(beg, end); } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + SourceFile::SourceFile( - const char* path, - const char* data, - size_t srcid) : - SourceData(), - path(sass_copy_c_string(path)), - data(sass_copy_c_string(data)), - length(0), - srcid(srcid) + const char* imp_path, + const char* abs_path, + char* content, + char* srcmaps, + size_t srcidx) : + SourceWithPath( + imp_path ? imp_path : "", + abs_path ? abs_path : "", + srcidx + ), + _content(content), + _srcmaps(srcmaps) { - length = strlen(data); + if (_content != nullptr) { + len_content = ::strlen(_content); + } + if (_srcmaps != nullptr) { + len_srcmaps = ::strlen(_srcmaps); + } } + // Only one that has to clean-up SourceFile::~SourceFile() { - sass_free_memory(path); - sass_free_memory(data); + sass_free_memory(_content); + sass_free_memory(_srcmaps); } - const char* SourceFile::end() const + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SourceString::SourceString( + const char* abs_path, + sass::string&& content) : + SourceWithPath( + abs_path ? abs_path : "", + abs_path ? abs_path : "", + sass::string::npos + ), + _content(std::move(content)) { - return data + length; + len_content = _content.length(); } - const char* SourceFile::begin() const + SourceString::SourceString( + const char* imp_path, + const char* abs_path, + sass::string&& content, + sass::string&& srcmaps, + size_t srcidx) : + SourceWithPath( + imp_path ? imp_path : "", + abs_path ? abs_path : "", + srcidx + ), + _content(std::move(content)), + _srcmaps(std::move(srcmaps)) { - return data; + len_content = _content.length(); + len_srcmaps = _srcmaps.length(); } - const char* SourceFile::getRawData() const + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SourceItpl::SourceItpl(SourceSpan pstate, + sass::string&& data) : + SourceString( + pstate.getImpPath(), + pstate.getAbsPath(), + std::move(data), "", + pstate.getSrcIdx()), + pstate(pstate) { - return data; } - SourceSpan SourceFile::getSourceSpan() + SourceSpan SourceItpl::adjustSourceSpan(SourceSpan& pstate) const { - return SourceSpan(this); + pstate.position = + this->pstate.position + + pstate.position; + return pstate; } - ItplFile::ItplFile(const char* data, const SourceSpan& pstate) : - SourceFile(pstate.getPath(), - data, pstate.getSrcId()), - pstate(pstate) - {} - - const char* ItplFile::getRawData() const + size_t SourceItpl::countLines() { - return pstate.getRawData(); + return pstate.getSource()->countLines() + // Minus lines to replace + - pstate.span.line - 1 + // Plus lines from insert + + SourceString::countLines(); } - SourceSpan ItplFile::getSourceSpan() + sass::string SourceItpl::getLine(size_t line) { - return SourceSpan(pstate); + SourceData* source(pstate.getSource()); + // Calculate last line of insert + size_t lastLine = pstate.position.line - 1 + + SourceString::countLines(); + + // Calculate line difference + size_t lineDelta = 0 + // Plus lines from insert + + SourceString::countLines() + // Minus lines to replace + - pstate.span.line - 1; + + // Get full line before insert + if (line < pstate.position.line) { + return source->getLine(line); + } + // Fetch first line of insert + else if (line == pstate.position.line) { + // Get the line of around to get before part + sass::string before(source->getLine(line)); + // Check if pstate offset only spans one line + // Therefore we need to insert into the line + // Size of `2` means we have only `start` and `end` + if (lfs.size() == 2) { + // We remove some lines, need to doctor + // those together to one single line + if (pstate.span.line > 0) { + sass::string after(source->getLine( + line + pstate.span.line)); + return Unicode::replace(before, + pstate.position.column, + sass::string::npos, + SourceString::getLine(0)) + + Unicode::substr(after, + pstate.span.column, + sass::string::npos); + } + else { + // Replace in the middle + return Unicode::replace(before, + pstate.position.column, + pstate.span.column, + SourceString::getLine(0)); + } + } + else { + // Otherwise we append to substring + return Unicode::substr(before, + 0, pstate.position.column) + + SourceString::getLine(0); + } + } + // Now we must be in the inserting part + // Only happens if we have a full line + else if (line < lastLine) { + // Get full line of insert + return SourceString::getLine( + line - pstate.position.line); + } + // Fetch last line of insert + else if (line == lastLine) { + // Get line to append + sass::string after( + source->getLine( + line - lineDelta)); + // Calculate column to cut appending line + size_t col = pstate.span.line == 0 + ? pstate.position.column + pstate.span.column + : pstate.span.column; + + // Append to last line to insert + return SourceString::getLine( + line - pstate.position.line) + + Unicode::substr(after, + col, sass::string::npos); + } + else { + return source->getLine( + line - lineDelta); + } + return sass::string(); } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } diff --git a/src/source.hpp b/src/source.hpp index 77c591e250..5a76cc1d7e 100644 --- a/src/source.hpp +++ b/src/source.hpp @@ -1,95 +1,284 @@ -#ifndef SASS_SOURCE_H -#define SASS_SOURCE_H +#ifndef SASS_SOURCE_HPP +#define SASS_SOURCE_HPP -#include "sass.hpp" -#include "memory.hpp" -#include "position.hpp" -#include "source_data.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "file.hpp" +#include "source_span.hpp" namespace Sass { - class SourceFile : - public SourceData { + + // SourceData is the base class to hold loaded sass content. + class SourceData : public SharedObj + { protected: - char* path; - char* data; - size_t length; - size_t srcid; + + friend class SourceItpl; + + // Returns the number of lines. On the first call it will + // calculate the linefeed lookup table. + virtual size_t countLines() = 0; + public: - SourceFile( - const char* path, - const char* data, - size_t srcid); + // Constructor + SourceData(); + + // The source id is uniquely assigned + virtual size_t getSrcIdx() const = 0; - ~SourceFile(); + // The source id is uniquely assigned + virtual void setSrcIdx(size_t idx) = 0; + + // Return path as it was given for import + virtual const char* getImpPath() const = 0; + + // Return path as it was given for import + virtual const char* getAbsPath() const = 0; + + // Returns the requested line. Will take interpolations into + // account to show more accurate debug messages. Calling this + // can be rather expensive, so only use it for debugging. + virtual sass::string getLine(size_t line) = 0; + + // Get raw iterator for raw source + virtual const char* content() const = 0; + virtual const char* srcmaps() const = 0; + + // Get raw iterator for raw source + const char* contentStart() const { return content(); }; + const char* srcmapsStart() const { return srcmaps(); }; + const char* contentEnd() const { return content() + contentSize(); }; + const char* srcmapsEnd() const { return srcmaps() + srcmapsSize(); }; + + // Return raw size in bytes + virtual size_t contentSize() const = 0; + virtual size_t srcmapsSize() const = 0; + + // Returns adjusted source span regarding interpolation. + virtual SourceSpan adjustSourceSpan(SourceSpan& pstate) const { + return pstate; + } - const char* end() const override final; - const char* begin() const override final; - virtual const char* getRawData() const override; - virtual SourceSpan getSourceSpan() override; + CAPI_WRAPPER(SourceData, SassSource); + }; + + ///////////////////////////////////////////////////////////////////////// + // Base class for our two main implementations. + // The main API is `const char*` based. + ///////////////////////////////////////////////////////////////////////// + class SourceWithPath : public SourceData + { + protected: + + // Import path + sass::string imp_path; + + // Resolved path + sass::string abs_path; + + // Raw length in bytes + size_t len_content; + size_t len_srcmaps; + + // Unique source id + size_t srcidx; + + // Store byte offset for every line. + // Lazy calculated within `countLines`. + // Columns per line can be derived from it. + sass::vector lfs; + + // Returns the number of lines. On first call + // it will calculate the linefeed lookup table. + virtual size_t countLines() override; + + public: + + SourceWithPath( + sass::string&& imp_path, + sass::string&& abs_path, + size_t idx = sass::string::npos); + + SourceWithPath( + const sass::string& imp_path, + const sass::string& abs_path, + size_t idx = sass::string::npos); + + // Returns the requested line. Will take interpolations into + // account to show more accurate debug messages. Calling this + // can be rather expensive, so only use it for debugging. + virtual sass::string getLine(size_t line) override; + + // Return path as it was given for import + const char* getImpPath() const override final + { + return imp_path.empty() ? + nullptr : imp_path.c_str(); + } - size_t size() const override final { - return length; + // Return path after it was resolved + const char* getAbsPath() const override final + { + return abs_path.empty() ? + nullptr : abs_path.c_str(); } - virtual const char* getPath() const override { - return path; + // The source id is uniquely assigned + void setSrcIdx(size_t idx) override final + { + srcidx = idx; } - virtual size_t getSrcId() const override { - return srcid; + // The source id is uniquely assigned + size_t getSrcIdx() const override final + { + return srcidx; + } + + size_t contentSize() const override final + { + return len_content; + } + + size_t srcmapsSize() const override final + { + return len_srcmaps; } }; - class SynthFile : - public SourceData { + ///////////////////////////////////////////////////////////////////////// + // A SourceFile is meant to be used for externally loaded resource. + // The resources passed in will be taken over and disposed at the end. + // Resources must have been allocated via `sass_alloc_memory`. + ///////////////////////////////////////////////////////////////////////// + class SourceFile : public SourceWithPath + { protected: - const char* path; + + // Raw source data + char* _content; + + // Raw source data + char* _srcmaps; + public: - SynthFile( - const char* path) : - path(path) - {} + SourceFile( + const char* imp_path, // copy + const char* abs_path, // copy + char* content, // take ownership + char* srcmaps, // take ownership + size_t srcidx = sass::string::npos); - ~SynthFile() {} + // Destructor + ~SourceFile() override final; - const char* end() const override final { return nullptr; } - const char* begin() const override final { return nullptr; }; - virtual const char* getRawData() const override { return nullptr; }; - virtual SourceSpan getSourceSpan() override { return SourceSpan(path); }; + // Get raw iterator for actual source + const char* content() const override final { + return _content; + } - size_t size() const override final { - return 0; + // Get raw iterator for actual source + const char* srcmaps() const override final { + return _srcmaps; } - virtual const char* getPath() const override { - return path; + }; + + ///////////////////////////////////////////////////////////////////////// + // A SourceString is meant to be used internally when we need to + // re-parse evaluated interpolations or static function signatures. + ///////////////////////////////////////////////////////////////////////// + class SourceString : + public SourceWithPath { + + protected: + + // Raw source data + sass::string _content; + + // Raw source data + sass::string _srcmaps; + + public: + + // For built-ins + SourceString( + const char* path, + sass::string&& data); + + // This is for interpolations + // Take details from its parent + SourceString( + const char* imp_path, + const char* abs_path, + sass::string&& data, + sass::string&& srcmap, + size_t srcidx = sass::string::npos); + + // Get raw iterator for actual source + const char* content() const override final { + return _content.c_str(); } - virtual size_t getSrcId() const override { - return std::string::npos; + // Get raw iterator for actual source + const char* srcmaps() const override final { + return _srcmaps.c_str(); } }; - - class ItplFile : - public SourceFile { + ///////////////////////////////////////////////////////////////////////// + // This class helps to report more meaningful errors when interpolations + // are involved. We basically replace the original interpolation with the + // result after evaluation. We can also adjust your parser state, since we + // often only re-parse the partial interpolated object (e.g. selector in + // the middle of a document). The error will be relative to this snippet. + // E.g. on line 1, after adjusting it should be in sync with whatever the + // `getLine` API returns. We do all this only on demand, since this is quite + // expensive, so this is only intended to be used in error/debug cases!! + ///////////////////////////////////////////////////////////////////////// + class SourceItpl : + public SourceString { + + protected: + + // Account additional lines if needed. + size_t countLines() override final; + private: + + // The position where the interpolation occurred. + // We also get the parent source from this state. + // Plus the parent `SourceString` we have it all. SourceSpan pstate; + public: - ItplFile(const char* data, - const SourceSpan& pstate); + // Create a synthetic interpolated source. The `data` is the + // evaluated interpolation, while `around` is the original source + // where the actual interpolation was given at `pstate` position. + SourceItpl(SourceSpan pstate, sass::string&& data); + + // Returns source with this interpolation inserted. + sass::string getLine(size_t line) override final; + + // Returns adjusted source span with interpolation in mind. + // The input `pstate` is relative to the interpolation, will + // return a source span with absolute position in regard of + // the original document with the interpolation inserted. + SourceSpan adjustSourceSpan(SourceSpan& pstate) const override final; - // Offset getPosition() const override final; - const char* getRawData() const override final; - SourceSpan getSourceSpan() override final; }; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } #endif diff --git a/src/source_data.hpp b/src/source_data.hpp deleted file mode 100644 index c52b02141d..0000000000 --- a/src/source_data.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef SASS_SOURCE_DATA_H -#define SASS_SOURCE_DATA_H - -#include "sass.hpp" -#include "memory.hpp" - -namespace Sass { - - class SourceSpan; - - class SourceData : - public SharedObj { - public: - SourceData(); - virtual size_t size() const = 0; - virtual size_t getSrcId() const = 0; - virtual const char* end() const = 0; - virtual const char* begin() const = 0; - virtual const char* getPath() const = 0; - // virtual Offset getPosition() const = 0; - virtual const char* getRawData() const = 0; - virtual SourceSpan getSourceSpan() = 0; - - sass::string to_string() const override { - return sass::string{ begin(), end() }; - } - ~SourceData() {} - }; - -} - -#endif diff --git a/src/source_map.cpp b/src/source_map.cpp index 285d77f839..e4b92cbd3d 100644 --- a/src/source_map.cpp +++ b/src/source_map.cpp @@ -1,150 +1,88 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include -#include - -#include "ast.hpp" -#include "json.hpp" -#include "context.hpp" -#include "position.hpp" #include "source_map.hpp" -namespace Sass { - SourceMap::SourceMap() : current_position(0, 0, 0), file("stdin") { } - SourceMap::SourceMap(const sass::string& file) : current_position(0, 0, 0), file(file) { } - - sass::string SourceMap::render_srcmap(Context &ctx) { - - const bool include_sources = ctx.c_options.source_map_contents; - const sass::vector links = ctx.srcmap_links; - const sass::vector& sources(ctx.resources); - - JsonNode* json_srcmap = json_mkobject(); +#include "source.hpp" +#include "ast_nodes.hpp" - json_append_member(json_srcmap, "version", json_mknumber(3)); - - const char *file_name = file.c_str(); - JsonNode *json_file_name = json_mkstring(file_name); - json_append_member(json_srcmap, "file", json_file_name); - - // 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); - } +namespace Sass { + SourceMap::SourceMap() : current_position(), file("stdin") { } + // SourceMap::SourceMap(const sass::string& file) : current_position(), file(file) { } - JsonNode *json_sources = json_mkarray(); - for (size_t i = 0; i < source_index.size(); ++i) { - sass::string source(links[source_index[i]]); - if (ctx.c_options.source_map_file_urls) { - source = File::rel2abs(source); - // check for windows abs path - if (source[0] == '/') { - // ends up with three slashes - source = "file://" + source; - } else { - // needs an additional slash - source = "file:///" + source; - } - } - const char* source_name = source.c_str(); - JsonNode *json_source_name = json_mkstring(source_name); - json_append_element(json_sources, json_source_name); - } - json_append_member(json_srcmap, "sources", json_sources); - - if (include_sources && source_index.size()) { - JsonNode *json_contents = json_mkarray(); - for (size_t i = 0; i < source_index.size(); ++i) { - const Resource& resource(sources[source_index[i]]); - JsonNode *json_content = json_mkstring(resource.contents); - json_append_element(json_contents, json_content); - } - json_append_member(json_srcmap, "sourcesContent", json_contents); - } - JsonNode *json_names = json_mkarray(); - // so far we have no implementation for names - // no problem as we do not alter any identifiers - json_append_member(json_srcmap, "names", json_names); + sass::string SourceMap::render(const std::unordered_map& idxremap) const + { - sass::string mappings = serialize_mappings(); - JsonNode *json_mappings = json_mkstring(mappings.c_str()); - json_append_member(json_srcmap, "mappings", json_mappings); + sass::string result; - char *str = json_stringify(json_srcmap, "\t"); - sass::string result = sass::string(str); - free(str); - json_delete(json_srcmap); - return result; - } + // We can make an educated guess here + // 3249594 mappings = 17669768 bytes + result.reserve(mappings.size() * 5); - sass::string SourceMap::serialize_mappings() { - sass::string result = ""; + int previous_generated_line = 0; + int previous_generated_column = 0; + int previous_original_line = 0; + int previous_original_column = 0; + int previous_original_file = 0; - size_t previous_generated_line = 0; - size_t previous_generated_column = 0; - size_t previous_original_line = 0; - size_t previous_original_column = 0; - size_t previous_original_file = 0; for (size_t i = 0; i < mappings.size(); ++i) { - const size_t generated_line = mappings[i].generated_position.line; - const size_t generated_column = mappings[i].generated_position.column; - const size_t original_line = mappings[i].original_position.line; - const size_t original_column = mappings[i].original_position.column; - const size_t original_file = mappings[i].original_position.file; + int generated_line = static_cast(mappings[i].target.line); + int generated_column = static_cast(mappings[i].target.column); + int original_line = static_cast(mappings[i].origin.line); + int original_column = static_cast(mappings[i].origin.column); + int original_file = static_cast(idxremap.at(mappings[i].srcidx)); if (generated_line != previous_generated_line) { previous_generated_column = 0; if (generated_line > previous_generated_line) { - result += sass::string(generated_line - previous_generated_line, ';'); + result += sass::string(size_t(generated_line) - previous_generated_line, ';'); previous_generated_line = generated_line; } } else if (i > 0) { - result += ","; + result += ','; } - // generated column - result += base64vlq.encode(static_cast(generated_column) - static_cast(previous_generated_column)); + // maybe we can optimize this a bit in the future? + base64vlq.encode(result, generated_column - previous_generated_column); + base64vlq.encode(result, original_file - previous_original_file); + base64vlq.encode(result, original_line - previous_original_line); + base64vlq.encode(result, original_column - previous_original_column); + previous_generated_column = generated_column; - // file - result += base64vlq.encode(static_cast(original_file) - static_cast(previous_original_file)); - previous_original_file = original_file; - // source line - result += base64vlq.encode(static_cast(original_line) - static_cast(previous_original_line)); - previous_original_line = original_line; - // source column - result += base64vlq.encode(static_cast(original_column) - static_cast(previous_original_column)); previous_original_column = original_column; + previous_original_line = original_line; + previous_original_file = original_file; } + // std::cerr << "RESULT " << result.size() << " from " << mappings.size() << "\n"; + return result; } 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(std::runtime_error("prepend sourcemap has illegal line")); - } - if (mapping.generated_position.line == size.line) { - if (mapping.generated_position.column > size.column) { - throw(std::runtime_error("prepend sourcemap has illegal column")); + if (out.smap) { + Offset size(out.smap->current_position); + for (Mapping mapping : out.smap->mappings) { + if (mapping.target.line > size.line) { + throw(std::runtime_error("prepend sourcemap has illegal line")); + } + if (mapping.target.line == size.line) { + if (mapping.target.column > size.column) { + throw(std::runtime_error("prepend sourcemap has illegal column")); + } } } + // adjust the buffer offset + prepend(Offset(out.buffer)); + // now add the new mappings + mappings.insert(mappings.begin(), + out.smap->mappings.begin(), + out.smap->mappings.end()); } - // adjust the buffer offset - prepend(Offset(out.buffer)); - // now add the new mappings - VECTOR_UNSHIFT(mappings, out.smap.mappings); + } + void SourceMap::append(const OutputBuffer& out) { append(Offset(out.buffer)); @@ -155,11 +93,11 @@ namespace Sass { 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; + if (mapping.target.line == 0) { + mapping.target.column += offset.column; } // make place for the new lines - mapping.generated_position.line += offset.line; + mapping.target.line += offset.line; } } if (current_position.line == 0) { @@ -173,30 +111,45 @@ namespace Sass { current_position += offset; } - void SourceMap::add_open_mapping(const AST_Node* node) + void SourceMap::add_open_mapping(const AstNode* node) { - const SourceSpan& span(node->pstate()); - Position from(span.getSrcId(), span.position); - mappings.push_back(Mapping(from, current_position)); + const SourceSpan& pstate = node->pstate(); + if (pstate.getSrcIdx() != sass::string::npos) { + mappings.emplace_back(Mapping{ + pstate.getSrcIdx(), + pstate.position, + current_position + }); + } } - void SourceMap::add_close_mapping(const AST_Node* node) + void SourceMap::add_close_mapping(const AstNode* node) { - const SourceSpan& span(node->pstate()); - Position to(span.getSrcId(), span.position + span.offset); - mappings.push_back(Mapping(to, current_position)); + const SourceSpan& pstate = node->pstate(); + if (pstate.getSrcIdx() != sass::string::npos) { + mappings.emplace_back(Mapping{ + pstate.getSrcIdx(), + pstate.position + pstate.span, + current_position + }); + } } SourceSpan SourceMap::remap(const SourceSpan& pstate) { - for (size_t i = 0; i < mappings.size(); ++i) { - if ( - mappings[i].generated_position.file == pstate.getSrcId() && - mappings[i].generated_position.line == pstate.position.line && - mappings[i].generated_position.column == pstate.position.column - ) return SourceSpan(pstate.source, mappings[i].original_position, pstate.offset); - } - return SourceSpan(pstate.source, Position(-1, -1, -1), Offset(0, 0)); + // for (size_t i = 0; i < mappings.size(); ++i) { + // if ( + // mappings[i].file == pstate.getSrcId() && + // mappings[i].destination.line == pstate.position.line && + // mappings[i].destination.column == pstate.position.column + // ) return SourceSpan(pstate.path, pstate.src, mappings[i].source, pstate.offset); + // } + return SourceSpan(pstate.getSource()); + } + OutputBuffer::OutputBuffer(OutputBuffer&& old) noexcept : + buffer(std::move(old.buffer)), + smap(std::move(old.smap)) + { } } diff --git a/src/source_map.hpp b/src/source_map.hpp index 6472fd2a60..81db5e55eb 100644 --- a/src/source_map.hpp +++ b/src/source_map.hpp @@ -1,17 +1,16 @@ -#ifndef SASS_SOURCE_MAP_H -#define SASS_SOURCE_MAP_H +#ifndef SASS_SOURCE_MAP_HPP +#define SASS_SOURCE_MAP_HPP -#include -#include +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include "ast_fwd_decl.hpp" +#include "source_span.hpp" +#include "backtrace.hpp" #include "base64vlq.hpp" -#include "position.hpp" #include "mapping.hpp" -#include "backtrace.hpp" -#include "memory.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()) @@ -25,24 +24,32 @@ namespace Sass { public: sass::vector source_index; SourceMap(); - SourceMap(const sass::string& file); + // SourceMap(const sass::string& file); void append(const Offset& offset); void prepend(const Offset& offset); void append(const OutputBuffer& out); void prepend(const OutputBuffer& out); - void add_open_mapping(const AST_Node* node); - void add_close_mapping(const AST_Node* node); + void add_open_mapping(const AstNode* node); + void add_close_mapping(const AstNode* node); - sass::string render_srcmap(Context &ctx); SourceSpan remap(const SourceSpan& pstate); private: - sass::string serialize_mappings(); + public: + sass::string render(const std::unordered_map& remap_srcidx) const; + // Deque is not faster, I checked sass::vector mappings; - Position current_position; + + void reserve(size_t size) { + source_index.reserve(size); + mappings.reserve(size); + } + + private: + Offset current_position; public: sass::string file; private: @@ -50,14 +57,23 @@ namespace Sass { }; class OutputBuffer { - public: - OutputBuffer(void) - : buffer(), - smap() - { } - public: - sass::string buffer; - SourceMap smap; + private: + // Make sure we don't allow any copies + OutputBuffer(const OutputBuffer&) = delete; + OutputBuffer& operator=(const OutputBuffer&) = delete; + public: + + // Allow to move the buffer + OutputBuffer(OutputBuffer&&) noexcept; + // The main buffer string + sass::string buffer; + // The optional source map + SourceMap* smap; + OutputBuffer(bool enabled) + : smap() + { + if (enabled) smap = new SourceMap(); + } }; } diff --git a/src/source_span.cpp b/src/source_span.cpp new file mode 100644 index 0000000000..3cb98d0224 --- /dev/null +++ b/src/source_span.cpp @@ -0,0 +1,46 @@ +#include "source_span.hpp" + +#include "source.hpp" +#include "ast_nodes.hpp" + +namespace Sass { + + SourceSpan::SourceSpan( + SourceDataObj source, + const Offset& position, + const Offset& span) : + SourceState(source, position), + span(span) + {} + + SourceSpan SourceSpan::tmp(const char* label) + { + return SourceSpan(SASS_MEMORY_NEW( + SourceString, "sass://phony", label), + {}, + {} + ); + + } + + SourceSpan SourceSpan::delta(const SourceSpan& lhs, const SourceSpan& rhs) + { + return SourceSpan( + lhs.getSource(), lhs.position, + Offset::distance(lhs.position, + rhs.position + rhs.span)); + } + + SourceSpan SourceSpan::delta(AstNode* lhs, AstNode* rhs) + { + return SourceSpan::delta( + lhs->pstate(), rhs->pstate()); + } + + bool SourceSpan::operator==(const SourceSpan& rhs) const { + return source.ptr() == rhs.source.ptr() + && position == rhs.position + && span == rhs.span; + } + +} diff --git a/src/source_span.hpp b/src/source_span.hpp new file mode 100644 index 0000000000..cd1a427bb1 --- /dev/null +++ b/src/source_span.hpp @@ -0,0 +1,44 @@ +#ifndef SASS_SOURCE_SPAN_HPP +#define SASS_SOURCE_SPAN_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "source_state.hpp" + +namespace Sass { + + // ParseState is SourceSpan + class SourceSpan : public SourceState + { + + public: + + SourceSpan() {} + + SourceSpan(SourceDataObj source, + const Offset& position = Offset(), + const Offset& span = Offset()); + + static SourceSpan tmp(const char* path); + + // Offset size + Offset span; + + // Create span between `lhs.start` and `rhs.end` (must be same source) + static SourceSpan delta(const SourceSpan& lhs, const SourceSpan& rhs); + + static SourceSpan delta(AstNode* lhs, AstNode* rhs); + + bool operator==(const SourceSpan& rhs) const; + + public: // down casts + + CAPI_WRAPPER(SourceSpan, SassSrcSpan); + + }; + +} // namespace Sass + +#endif diff --git a/src/source_state.cpp b/src/source_state.cpp new file mode 100644 index 0000000000..9a640a5527 --- /dev/null +++ b/src/source_state.cpp @@ -0,0 +1,59 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "source_state.hpp" + +#include "source.hpp" + +namespace Sass +{ + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SourceState::SourceState( + SourceData* source, + Offset position) : + source(source), + position(position) + {} + + SourceData* SourceState::getSource() const + { + return source.ptr(); + } + + size_t SourceState::getSrcIdx() const + { + return source->getSrcIdx(); + } + + // Return the requested import path + const char* SourceState::getImpPath() const + { + return source->getImpPath(); + } + + // Return the resolved absolute path + const char* SourceState::getAbsPath() const + { + return source->getAbsPath(); + } + + // Return the attached source + const char* SourceState::getContent() const + { + return source->content(); + } + + sass::string SourceState::getDebugPath() const + { + const char* path = getAbsPath(); + sass::string rel_path(File::abs2rel(path, CWD, CWD)); + return File::rel2dbg(rel_path, path); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/source_state.hpp b/src/source_state.hpp new file mode 100644 index 0000000000..f7f72931b0 --- /dev/null +++ b/src/source_state.hpp @@ -0,0 +1,72 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_SOURCE_STATE_HPP +#define SASS_SOURCE_STATE_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" +#include "offset.hpp" + +namespace Sass +{ + + // Stores a reference (shared ptr) to the source code + // and one offset position (line and column information). + class SourceState + { + protected: + + // The source code reference + SourceDataObj source; + + public: + + // The position within the source + Offset position; + + // Regular value constructor + SourceState( + SourceData* source = {}, + Offset position = Offset()); + + // Return the attach source id + size_t getSrcIdx() const; + + // Return the requested import path + const char* getImpPath() const; + + // Return the resolved absolute path + const char* getAbsPath() const; + + // Return the attached source + SourceData* getSource() const; + + // Return the attached source + const char* getContent() const; + + // Return line as human readable + // Starting from one instead of zero + uint32_t getLine() const + { + return position.line + 1; + } + + // Return line as human readable + // Starting from one instead of zero + uint32_t getColumn() const + { + return position.column + 1; + } + + // Either return path relative to cwd if path is + // inside cwd, otherwise return absolute path. + sass::string getDebugPath() const; + }; + +} // namespace Sass + +#endif diff --git a/src/string_utils.cpp b/src/string_utils.cpp new file mode 100644 index 0000000000..ea24e1f940 --- /dev/null +++ b/src/string_utils.cpp @@ -0,0 +1,189 @@ +#include "string_utils.hpp" + +#include +#include + +namespace Sass { + + namespace StringUtils { + + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool _equalsIgnoreCase(const char a, const char b) { + return Character::characterEqualsIgnoreCase(a, b); + } + + // Optimized version where we know one side is already lowercase + bool _equalsIgnoreCaseConst(const char a, const char b) { + return a == b || a == Character::toLowerCase(b); + } + + bool startsWith(const sass::string& str, const char* prefix, size_t len) { + return len <= str.size() && std::equal(prefix, prefix + len, str.begin()); + } + + bool startsWith(const sass::string& str, const sass::string& prefix) { + return prefix.size() <= str.size() && std::equal(prefix.begin(), prefix.end(), str.begin()); + } + + bool endsWith(const sass::string& str, const char* suffix, size_t len) { + return len <= str.size() && std::equal(suffix, suffix + len, str.end() - len); + } + + bool endsWith(const sass::string& str, const sass::string& suffix) { + return suffix.size() <= str.size() && std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()); + } + + // Optimized version when you pass `const char*` you know is already lowercase. + bool startsWithIgnoreCase(const sass::string& str, const char* prefix, size_t len) { + return len <= str.size() && std::equal(prefix, prefix + len, str.begin(), _equalsIgnoreCaseConst); + } + + bool startsWithIgnoreCase(const sass::string& str, const sass::string& prefix) { + return prefix.size() <= str.size() && std::equal(prefix.begin(), prefix.end(), str.begin(), _equalsIgnoreCase); + } + + // Optimized version when you pass `const char*` you know is already lowercase. + bool endsWithIgnoreCase(const sass::string& str, const char* suffix, size_t len) { + return len <= str.size() && std::equal(suffix, suffix + len, str.end() - len, _equalsIgnoreCaseConst); + } + + bool endsWithIgnoreCase(const sass::string& str, const sass::string& suffix) { + return suffix.size() <= str.size() && std::equal(suffix.rbegin(), suffix.rend(), str.rbegin(), _equalsIgnoreCase); + } + + bool equalsIgnoreCase(const sass::string& a, const char* b, size_t len) { + return len == a.size() && std::equal(b, b + len, a.begin(), _equalsIgnoreCaseConst); + } + + bool equalsIgnoreCase(const sass::string& a, const sass::string& b) { + return a.size() == b.size() && std::equal(b.begin(), b.end(), a.begin(), _equalsIgnoreCaseConst); + } + + // Make the passed string whitespace trimmed. + void makeTrimmed(sass::string& str) { + makeLeftTrimmed(str); + makeRightTrimmed(str); + } + // EO makeTrimmed + + // Trim the left side of passed string. + void makeLeftTrimmed(sass::string& str) { + if (str.begin() != str.end()) { + auto pos = std::find_if_not( + str.begin(), str.end(), + Character::isWhitespace); + str.erase(str.begin(), pos); + } + } + // EO makeLeftTrimmed + + // Trim the right side of passed string. + void makeRightTrimmed(sass::string& str) { + if (str.begin() != str.end()) { + auto pos = std::find_if_not( + str.rbegin(), str.rend(), + Character::isWhitespace); + str.erase(str.rend() - pos); + } + } + // EO makeRightTrimmed + + // Make the passed string lowercase. + void makeLowerCase(sass::string& str) { + for (char& character : str) { + if (character >= $A && character <= $Z) + character |= asciiCaseBit; + } + } + // EO makeLowerCase + + // Make the passed string uppercase. + void makeUpperCase(sass::string& str) { + for (char& character : str) { + if (character >= $a && character <= $z) + character &= ~asciiCaseBit; + } + } + // EO makeUpperCase + + // Return new string converted to lowercase. + sass::string toLowerCase(const sass::string& str) { + sass::string rv(str); + for (char& character : rv) { + if (character >= $A && character <= $Z) + character |= asciiCaseBit; + } + return rv; + } + // EO toLowerCase + + // Return new string converted to uppercase. + sass::string toUpperCase(const sass::string& str) { + sass::string rv(str); + for (char& character : rv) { + if (character >= $a && character <= $z) + character &= ~asciiCaseBit; + } + return rv; + } + // EO toUpperCase + + // Return list of strings split by `delimiter`. + // Optionally `trim` all results (default behavior). + sass::vector split(sass::string str, char delimiter, bool trim) + { + sass::vector rv; + if (trim) StringUtils::makeTrimmed(str); + if (str.empty()) return rv; + size_t start = 0, end = str.find_first_of(delimiter); + // search until we are at the end + while (end != sass::string::npos) { + // add substring from current position to delimiter + sass::string item(str.substr(start, end - start)); + if (trim) StringUtils::makeTrimmed(item); + if (!trim || !item.empty()) rv.emplace_back(item); + start = end + 1; // skip delimiter + end = str.find_first_of(delimiter, start); + } + // add last substring from current position to end + sass::string item(str.substr(start, end - start)); + if (trim) StringUtils::makeTrimmed(item); + if (!trim || !item.empty()) rv.emplace_back(item); + // return back + return rv; + } + // EO split + + // Return joined string from all passed strings, delimited by separator. + sass::string join(const sass::vector& strings, const char* separator) + { + switch (strings.size()) + { + case 0: + return ""; + case 1: + return strings[0]; + default: + size_t size = strings[0].size(); + size_t sep_len = ::strlen(separator); + for (size_t i = 1; i < strings.size(); i++) { + size += sep_len + strings[i].size(); + } + sass::string os; + os.reserve(size); + os += strings[0]; + for (size_t i = 1; i < strings.size(); i++) { + os += separator; + os += strings[i]; + } + return os; + } + } + // EO join + + } +} diff --git a/src/string_utils.hpp b/src/string_utils.hpp new file mode 100644 index 0000000000..dc3dcd129f --- /dev/null +++ b/src/string_utils.hpp @@ -0,0 +1,60 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_STRING_UTILS_HPP +#define SASS_STRING_UTILS_HPP + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "character.hpp" + +namespace Sass { + namespace StringUtils { + + bool endsWith(const sass::string& str, const sass::string& suffix); + bool startsWith(const sass::string& str, const sass::string& prefix); + bool equalsIgnoreCase(const sass::string& a, const sass::string& b); + bool endsWithIgnoreCase(const sass::string& str, const sass::string& suffix); + bool startsWithIgnoreCase(const sass::string& str, const sass::string& prefix); + + // Optimized versions when you pass `const char*` you know is already lowercase. + bool endsWith(const sass::string& str, const char* suffix, size_t len); + bool startsWith(const sass::string& str, const char* prefix, size_t len); + bool equalsIgnoreCase(const sass::string& a, const char* b, size_t len); + bool endsWithIgnoreCase(const sass::string& str, const char* suffix, size_t len); + bool startsWithIgnoreCase(const sass::string& str, const char* prefix, size_t len); + + // Make the passed string whitespace trimmed. + void makeTrimmed(sass::string& str); + + // Trim the left side of passed string. + void makeLeftTrimmed(sass::string& str); + + // Trim the right side of passed string. + void makeRightTrimmed(sass::string& str); + + // Make the passed string lowercase. + void makeLowerCase(sass::string& str); + + // Make the passed string uppercase. + void makeUpperCase(sass::string& str); + + // Return new string converted to lowercase. + sass::string toUpperCase(const sass::string& str); + + // Return new string converted to uppercase. + sass::string toLowerCase(const sass::string& str); + + // Return list of strings split by `delimiter`. + // Optionally `trim` all results (default behavior). + sass::vector split(sass::string str, char delimiter, bool trim = true); + + // Return joined string from all passed strings, delimited by separator. + sass::string join(const sass::vector& strings, const char* separator); + + } +} + +#endif diff --git a/src/strings.cpp b/src/strings.cpp new file mode 100644 index 0000000000..98e0d49de7 --- /dev/null +++ b/src/strings.cpp @@ -0,0 +1,130 @@ +#include "strings.hpp" + +namespace Sass +{ + + namespace Strings + { + + const sass::string empty(""); + + const sass::string plus("+"); + const sass::string minus("-"); + const sass::string percent("%"); + + const sass::string rgb("rgb"); + const sass::string hsl("hsl"); + const sass::string hwb("hwb"); + const sass::string rgba("rgba"); + const sass::string hsla("hsla"); + const sass::string hwba("hwba"); + + const sass::string deg("deg"); + const sass::string red("red"); + const sass::string hue("hue"); + const sass::string blue("blue"); + const sass::string green("green"); + const sass::string alpha("alpha"); + const sass::string color("color"); + const sass::string weight("weight"); + const sass::string number("number"); + const sass::string amount("amount"); + const sass::string invert("invert"); + const sass::string degrees("degrees"); + const sass::string saturate("saturate"); + const sass::string grayscale("grayscale"); + const sass::string whiteness("whiteness"); + const sass::string blackness("blackness"); + + const sass::string $whiteness("$whiteness"); + const sass::string $blackness("$blackness"); + + + + + const sass::string lightness("lightness"); + const sass::string saturation("saturation"); + + const sass::string key("key"); + const sass::string map("map"); + const sass::string map1("map1"); + const sass::string map2("map2"); + const sass::string args("args"); + const sass::string null("null"); + const sass::string boolean("bool"); + const sass::string error("error"); + const sass::string warning("warning"); + const sass::string string("string"); + const sass::string function("function"); + const sass::string arglist("arglist"); + + const sass::string list("list"); + const sass::string name("name"); + const sass::string media("media"); + const sass::string module("module"); + const sass::string supports("supports"); + const sass::string keyframes("keyframes"); + + + + const sass::string forRule("@for"); + const sass::string warnRule("@warn"); + const sass::string errorRule("@error"); + const sass::string debugRule("@debug"); + const sass::string extendRule("@extend"); + const sass::string importRule("@import"); + const sass::string contentRule("@content"); + + const sass::string scaleColor("scale-color"); + const sass::string colorAdjust("color-adjust"); + const sass::string colorChange("color-change"); + + const sass::string condition("condition"); + const sass::string ifFalse("if-false"); + const sass::string ifTrue("if-true"); + + const sass::string $red("$red"); + const sass::string $green("$green"); + const sass::string $blue("$blue"); + + const sass::string $hue("$hue"); + const sass::string $saturation("$saturation"); + const sass::string $lightness("$lightness"); + + const sass::string utf8bom("\xEF\xBB\xBF"); + + const sass::string argument("argument"); + const sass::string _and_("and"); + const sass::string _or_("or"); + + } // namespace Strings + + namespace Keys { + + const EnvKey red(Strings::red); + const EnvKey hue(Strings::hue); + const EnvKey blue(Strings::blue); + const EnvKey green(Strings::green); + const EnvKey alpha(Strings::alpha); + const EnvKey color(Strings::color); + const EnvKey lightness(Strings::lightness); + const EnvKey saturation(Strings::saturation); + + const EnvKey whiteness(Strings::whiteness); + const EnvKey blackness(Strings::blackness); + + + + + const EnvKey warnRule(Strings::warnRule); + const EnvKey errorRule(Strings::errorRule); + const EnvKey debugRule(Strings::debugRule); + const EnvKey contentRule(Strings::contentRule); + + const EnvKey condition(Strings::condition); + const EnvKey ifFalse(Strings::ifFalse); + const EnvKey ifTrue(Strings::ifTrue); + + } // namespace Keys + +} // namespace Sass diff --git a/src/strings.hpp b/src/strings.hpp new file mode 100644 index 0000000000..40f2c4388c --- /dev/null +++ b/src/strings.hpp @@ -0,0 +1,134 @@ +#ifndef SASS_STRINGS_HPP +#define SASS_STRINGS_HPP + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +// Include normalized keys +#include "environment_key.hpp" + +namespace Sass { + + namespace Strings { + + extern const sass::string empty; + + extern const sass::string plus; + extern const sass::string minus; + extern const sass::string percent; + + extern const sass::string rgb; + extern const sass::string hsl; + extern const sass::string hwb; + extern const sass::string rgba; + extern const sass::string hsla; + extern const sass::string hwba; + + extern const sass::string deg; + extern const sass::string red; + extern const sass::string hue; + extern const sass::string blue; + extern const sass::string green; + extern const sass::string alpha; + extern const sass::string color; + extern const sass::string weight; + extern const sass::string number; + extern const sass::string amount; + extern const sass::string invert; + extern const sass::string degrees; + extern const sass::string saturate; + extern const sass::string grayscale; + + extern const sass::string whiteness; + extern const sass::string blackness; + extern const sass::string $whiteness; + extern const sass::string $blackness; + + + + extern const sass::string lightness; + extern const sass::string saturation; + + + extern const sass::string key; + extern const sass::string map; + extern const sass::string map1; + extern const sass::string map2; + extern const sass::string args; + extern const sass::string list; + extern const sass::string name; + extern const sass::string null; + extern const sass::string media; + extern const sass::string module; + extern const sass::string supports; + extern const sass::string keyframes; + + extern const sass::string boolean; + extern const sass::string string; + extern const sass::string arglist; + extern const sass::string function; + extern const sass::string error; + extern const sass::string warning; + + extern const sass::string forRule; + extern const sass::string warnRule; + extern const sass::string errorRule; + extern const sass::string debugRule; + extern const sass::string extendRule; + extern const sass::string importRule; + extern const sass::string contentRule; + + extern const sass::string scaleColor; + extern const sass::string colorAdjust; + extern const sass::string colorChange; + + extern const sass::string condition; + extern const sass::string ifFalse; + extern const sass::string ifTrue; + + extern const sass::string $red; + extern const sass::string $green; + extern const sass::string $blue; + + extern const sass::string $hue; + extern const sass::string $saturation; + extern const sass::string $lightness; + + extern const sass::string utf8bom; + + extern const sass::string argument; + extern const sass::string _and_; + extern const sass::string _or_; + + } + + namespace Keys { + + extern const EnvKey red; + extern const EnvKey hue; + extern const EnvKey blue; + extern const EnvKey green; + extern const EnvKey alpha; + extern const EnvKey color; + extern const EnvKey lightness; + extern const EnvKey saturation; + extern const EnvKey whiteness; + extern const EnvKey blackness; + + + extern const EnvKey warnRule; + extern const EnvKey errorRule; + extern const EnvKey debugRule; + extern const EnvKey contentRule; + + extern const EnvKey condition; + extern const EnvKey ifFalse; + extern const EnvKey ifTrue; + + } + +} + + +#endif diff --git a/src/stylesheet.cpp b/src/stylesheet.cpp index e0e4345c29..a4d6d0c98a 100644 --- a/src/stylesheet.cpp +++ b/src/stylesheet.cpp @@ -1,22 +1,13 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - #include "stylesheet.hpp" namespace Sass { // Constructor - Sass::StyleSheet::StyleSheet(const Resource& res, Block_Obj root) : - Resource(res), - root(root) - { - } + Sass::StyleSheet::StyleSheet(ImportObj import, RootObj root) : + import(import), + root2(root) + {} - StyleSheet::StyleSheet(const StyleSheet& sheet) : - Resource(sheet), - root(sheet.root) - { - } + // We expect more code here once we support modules } diff --git a/src/stylesheet.hpp b/src/stylesheet.hpp index 37e85772a8..e2f0046de8 100644 --- a/src/stylesheet.hpp +++ b/src/stylesheet.hpp @@ -1,24 +1,30 @@ -#ifndef SASS_STYLESHEET_H -#define SASS_STYLESHEET_H +#ifndef SASS_STYLESHEET_HPP +#define SASS_STYLESHEET_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include "ast_fwd_decl.hpp" -#include "extender.hpp" #include "file.hpp" namespace Sass { // parsed stylesheet from loaded resource // this should be a `Module` for sass 4.0 - class StyleSheet : public Resource { + class StyleSheet : public SharedObj { public: + // Whether this was parsed from a plain CSS stylesheet. + // bool plainCss33; + + // SourceDataObj source; + // The canonical URL for this module's source file. This may be `null` // if the module was loaded from a string without a URL provided. - // Uri get url; + // struct SassImport* import; + + // the import type + // SassImportFormat syntax; // Modules that this module uses. // List get upstream; @@ -37,17 +43,15 @@ namespace Sass { // The extensions defined in this module, which is also able to update // [css]'s style rules in-place based on downstream extensions. // Extender extender; + ImportObj import; // The module's CSS tree. - Block_Obj root; + RootObj root2; public: // default argument constructor - StyleSheet(const Resource& res, Block_Obj root); - - // Copy constructor - StyleSheet(const StyleSheet& res); + StyleSheet(ImportObj import, RootObj root); }; diff --git a/src/terminal.cpp b/src/terminal.cpp new file mode 100644 index 0000000000..4a1076419d --- /dev/null +++ b/src/terminal.cpp @@ -0,0 +1,184 @@ +#include "terminal.hpp" + +// Minimal terminal abstraction for cross compatibility. +// Its main purpose is to let us print stuff with colors. +namespace Terminal { + +#ifdef _WIN32 + + // Query number of available console columns + // Useful to shorten our output to fit nicely + short getColumns(bool error) + { + DWORD fd = error + ? STD_ERROR_HANDLE + : STD_OUTPUT_HANDLE; + // First check if console is attached + if (!isConsoleAttached(error)) { + return SassDefaultColumns; + } + // Get information of the screen buffer + CONSOLE_SCREEN_BUFFER_INFO csbi; + HANDLE handle = GetStdHandle(fd); + GetConsoleScreenBufferInfo(handle, &csbi); + // csbi.srWindow.Right - csbi.srWindow.Left + return csbi.dwMaximumWindowSize.X; + } + + // Check if we are actually printing to the console + // In all other cases we want monochrome ASCII output + bool isConsoleAttached(bool error) + { + DWORD fd = error + ? STD_ERROR_HANDLE + : STD_OUTPUT_HANDLE; + if (GetConsoleCP() == 0) return false; + HANDLE handle = GetStdHandle(fd); + if (handle == INVALID_HANDLE_VALUE) return false; + DWORD filetype = GetFileType(handle); + return filetype == FILE_TYPE_CHAR; + } + + // Check if we are actually printing to the console + // In all other cases we want monochrome ASCII output + bool hasUnicodeSupport(bool error) + { + return false; + UINT cp = GetConsoleOutputCP(); + if (cp == 0) return false; + return cp == 65001; + } + + bool hasColorSupport(bool error) + { + return false; + return isConsoleAttached(true); + } + + // This function is able to print a line with colors + // It translates the Unix terminal codes to windows + void print(const char* output, bool error) + { + + if (output == 0) return; + + DWORD fd = error + ? STD_ERROR_HANDLE + : STD_OUTPUT_HANDLE; + + HANDLE handle = GetStdHandle(fd); + CONSOLE_SCREEN_BUFFER_INFO info = {}; + GetConsoleScreenBufferInfo(handle, &info); + WORD attribute = info.wAttributes; + + while (output[0] != '\0') { + + if (output[0] == '\x1B' && output[1] == '[') { + + output += 2; + int fg = 0; + int bg = 0; + + while (output[0] != '\0' && output[0] != ';' && output[0] != 'm') { + fg *= 10; + switch (*output) { + case '0': fg += 0; break; + case '1': fg += 1; break; + case '2': fg += 2; break; + case '3': fg += 3; break; + case '4': fg += 4; break; + case '5': fg += 5; break; + case '6': fg += 6; break; + case '7': fg += 7; break; + case '8': fg += 8; break; + case '9': fg += 9; break; + default: break; + } + output += 1; + } + + attribute &= ~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED); + if (fg == 31 || fg == 33 || fg == 35 || fg == 37 || fg == 0) attribute |= FOREGROUND_RED; + if (fg == 32 || fg == 33 || fg == 36 || fg == 37 || fg == 0) attribute |= FOREGROUND_GREEN; + if (fg == 34 || fg == 35 || fg == 36 || fg == 37 || fg == 0) attribute |= FOREGROUND_BLUE; + if (fg != 37 && fg != 0) attribute |= FOREGROUND_INTENSITY; + if (output[0] == ';') { + + output += 1; + + while (output[0] != '\0' && output[0] != ';' && output[0] != 'm') { + bg *= 10; + switch (*output) { + case '0': bg += 0; break; + case '1': bg += 1; break; + case '2': bg += 2; break; + case '3': bg += 3; break; + case '4': bg += 4; break; + case '5': bg += 5; break; + case '6': bg += 6; break; + case '7': bg += 7; break; + case '8': bg += 8; break; + case '9': bg += 9; break; + default: break; + } + output += 1; + } + + attribute &= ~(BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED); + if (bg == 31 || bg == 33 || bg == 35 || bg == 37) attribute |= BACKGROUND_RED; + if (bg == 32 || bg == 33 || bg == 36 || bg == 37) attribute |= BACKGROUND_GREEN; + if (bg == 34 || bg == 35 || bg == 36 || bg == 37) attribute |= BACKGROUND_BLUE; + if (fg != 37 && fg != 0) attribute |= BACKGROUND_INTENSITY; + + } + + if (output[0] == 'm') output += 1; + // Flush before setting new color + // ToDo: there might be a better way + fflush(error ? stderr : stdout); + SetConsoleTextAttribute(handle, attribute); + + } + else { + + if (error) { + std::cerr << output[0]; + } + else { + std::cout << output[0]; + } + output += 1; + } + + } + + fflush(error ? stderr : stdout); + + } + +#else + + short getColumns(bool error) + { + return 80; + } + + // Check if we are actually printing to the console + // In all other cases we want monochrome ASCII output + bool isConsoleAttached(bool error) { return true; } + bool hasUnicodeSupport(bool error) { return true; } + bool hasColorSupport(bool error) { return true; } + + void print(const char* output, bool error) + { + if (error) { + std::cerr << output; + } + else { + std::cout << output; + } + } + +#endif + +} diff --git a/src/terminal.hpp b/src/terminal.hpp new file mode 100644 index 0000000000..d0c8357144 --- /dev/null +++ b/src/terminal.hpp @@ -0,0 +1,43 @@ +#ifndef SASS_TERMINAL_HPP +#define SASS_TERMINAL_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#endif + +#include "constants.hpp" + +// Minimal terminal abstraction for cross compatibility. +// Its main purpose is to let us print stuff with colors. +namespace Terminal { + + // Import constant terminal color definition + using namespace Sass::Constants::Terminal; + + short getColumns(bool error = false); + + // Check if we are actually printing to the console + // In all other cases we want monochrome ASCII output + bool isConsoleAttached(bool error = false); + bool hasUnicodeSupport(bool error = false); + bool hasColorSupport(bool error = false); + + void print(const char* output, bool error = false); + + +#ifdef _WIN32 + /* + */ +#endif + +} + +#endif diff --git a/src/terminal/windows.hpp b/src/terminal/windows.hpp new file mode 100644 index 0000000000..e4afffa808 --- /dev/null +++ b/src/terminal/windows.hpp @@ -0,0 +1,15 @@ +#include +#include + +#define LOG_COLOR_WHITE 7 +#define COLOR_GREEN 10 +#define COLOR_YELLOW 14 +#define COLOR_MAGENTA 13 + +hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + +namespace Sass { + + + +} diff --git a/src/tessil/bhopscotch_map.h b/src/tessil/bhopscotch_map.h new file mode 100644 index 0000000000..582fefd81d --- /dev/null +++ b/src/tessil/bhopscotch_map.h @@ -0,0 +1,706 @@ +/** + * MIT License + * + * Copyright (c) 2017 Tessil + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_BHOPSCOTCH_MAP_H +#define TSL_BHOPSCOTCH_MAP_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "hopscotch_hash.h" + + +namespace tsl { + + +/** + * Similar to tsl::hopscotch_map but instead of using a list for overflowing elements it uses + * a binary search tree. It thus needs an additional template parameter Compare. Compare should + * be arithmetically coherent with KeyEqual. + * + * The binary search tree allows the map to have a worst-case scenario of O(log n) for search + * and delete, even if the hash function maps all the elements to the same bucket. + * For insert, the amortized worst case is O(log n), but the worst case is O(n) in case of rehash. + * + * This makes the map resistant to DoS attacks (but doesn't preclude you to have a good hash function, + * as an element in the bucket array is faster to retrieve than in the tree). + * + * @copydoc hopscotch_map + */ +template, + class KeyEqual = std::equal_to, + class Compare = std::less, + class Allocator = std::allocator>, + unsigned int NeighborhoodSize = 62, + bool StoreHash = false, + class GrowthPolicy = tsl::hh::power_of_two_growth_policy<2>> +class bhopscotch_map { +private: + template + using has_is_transparent = tsl::detail_hopscotch_hash::has_is_transparent; + + class KeySelect { + public: + using key_type = Key; + + const key_type& operator()(const std::pair& key_value) const { + return key_value.first; + } + + const key_type& operator()(std::pair& key_value) { + return key_value.first; + } + }; + + class ValueSelect { + public: + using value_type = T; + + const value_type& operator()(const std::pair& key_value) const { + return key_value.second; + } + + value_type& operator()(std::pair& key_value) { + return key_value.second; + } + }; + + + // TODO Not optimal as we have to use std::pair as ValueType which forbid + // us to move the key in the bucket array, we have to use copy. Optimize. + using overflow_container_type = std::map; + using ht = detail_hopscotch_hash::hopscotch_hash, KeySelect, ValueSelect, + Hash, KeyEqual, + Allocator, NeighborhoodSize, + StoreHash, GrowthPolicy, + overflow_container_type>; + +public: + using key_type = typename ht::key_type; + using mapped_type = T; + using value_type = typename ht::value_type; + using size_type = typename ht::size_type; + using difference_type = typename ht::difference_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using key_compare = Compare; + using allocator_type = typename ht::allocator_type; + using reference = typename ht::reference; + using const_reference = typename ht::const_reference; + using pointer = typename ht::pointer; + using const_pointer = typename ht::const_pointer; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + + + /* + * Constructors + */ + bhopscotch_map() : bhopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE) { + } + + explicit bhopscotch_map(size_type bucket_count, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator(), + const Compare& comp = Compare()) : + m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR, comp) + { + } + + bhopscotch_map(size_type bucket_count, + const Allocator& alloc) : bhopscotch_map(bucket_count, Hash(), KeyEqual(), alloc) + { + } + + bhopscotch_map(size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : bhopscotch_map(bucket_count, hash, KeyEqual(), alloc) + { + } + + explicit bhopscotch_map(const Allocator& alloc) : bhopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { + } + + template + bhopscotch_map(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()) : bhopscotch_map(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + + template + bhopscotch_map(InputIt first, InputIt last, + size_type bucket_count, + const Allocator& alloc) : bhopscotch_map(first, last, bucket_count, Hash(), KeyEqual(), alloc) + { + } + + template + bhopscotch_map(InputIt first, InputIt last, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : bhopscotch_map(first, last, bucket_count, hash, KeyEqual(), alloc) + { + } + + bhopscotch_map(std::initializer_list init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()) : + bhopscotch_map(init.begin(), init.end(), bucket_count, hash, equal, alloc) + { + } + + bhopscotch_map(std::initializer_list init, + size_type bucket_count, + const Allocator& alloc) : + bhopscotch_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) + { + } + + bhopscotch_map(std::initializer_list init, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : + bhopscotch_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) + { + } + + + bhopscotch_map& operator=(std::initializer_list ilist) { + m_ht.clear(); + + m_ht.reserve(ilist.size()); + m_ht.insert(ilist.begin(), ilist.end()); + + return *this; + } + + allocator_type get_allocator() const { return m_ht.get_allocator(); } + + + /* + * Iterators + */ + iterator begin() noexcept { return m_ht.begin(); } + const_iterator begin() const noexcept { return m_ht.begin(); } + const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + + iterator end() noexcept { return m_ht.end(); } + const_iterator end() const noexcept { return m_ht.end(); } + const_iterator cend() const noexcept { return m_ht.cend(); } + + + /* + * Capacity + */ + bool empty() const noexcept { return m_ht.empty(); } + size_type size() const noexcept { return m_ht.size(); } + size_type max_size() const noexcept { return m_ht.max_size(); } + + /* + * Modifiers + */ + void clear() noexcept { m_ht.clear(); } + + + + + std::pair insert(const value_type& value) { + return m_ht.insert(value); + } + + template::value>::type* = nullptr> + std::pair insert(P&& value) { + return m_ht.insert(std::forward

(value)); + } + + std::pair insert(value_type&& value) { + return m_ht.insert(std::move(value)); + } + + + iterator insert(const_iterator hint, const value_type& value) { + return m_ht.insert(hint, value); + } + + template::value>::type* = nullptr> + iterator insert(const_iterator hint, P&& value) { + return m_ht.insert(hint, std::forward

(value)); + } + + iterator insert(const_iterator hint, value_type&& value) { + return m_ht.insert(hint, std::move(value)); + } + + + template + void insert(InputIt first, InputIt last) { + m_ht.insert(first, last); + } + + void insert(std::initializer_list ilist) { + m_ht.insert(ilist.begin(), ilist.end()); + } + + + + + template + std::pair insert_or_assign(const key_type& k, M&& obj) { + return m_ht.insert_or_assign(k, std::forward(obj)); + } + + template + std::pair insert_or_assign(key_type&& k, M&& obj) { + return m_ht.insert_or_assign(std::move(k), std::forward(obj)); + } + + template + iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) { + return m_ht.insert_or_assign(hint, k, std::forward(obj)); + } + + template + iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) { + return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); + } + + + + /** + * Due to the way elements are stored, emplace will need to move or copy the key-value once. + * The method is equivalent to insert(value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + std::pair emplace(Args&&... args) { + return m_ht.emplace(std::forward(args)...); + } + + + + + /** + * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. + * The method is equivalent to insert(hint, value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + iterator emplace_hint(const_iterator hint, Args&&... args) { + return m_ht.emplace_hint(hint, std::forward(args)...); + } + + + + + template + std::pair try_emplace(const key_type& k, Args&&... args) { + return m_ht.try_emplace(k, std::forward(args)...); + } + + template + std::pair try_emplace(key_type&& k, Args&&... args) { + return m_ht.try_emplace(std::move(k), std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) { + return m_ht.try_emplace(hint, k, std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) { + return m_ht.try_emplace(hint, std::move(k), std::forward(args)...); + } + + + + + iterator erase(iterator pos) { return m_ht.erase(pos); } + iterator erase(const_iterator pos) { return m_ht.erase(pos); } + iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } + size_type erase(const key_type& key) { return m_ht.erase(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. + */ + size_type erase(const key_type& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent + * and Compare::is_transparent exist. + * If so, K must be hashable and comparable to Key. + */ + template::value && has_is_transparent::value>::type* = nullptr> + size_type erase(const K& key) { return m_ht.erase(key); } + + /** + * @copydoc erase(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. + */ + template::value && has_is_transparent::value>::type* = nullptr> + size_type erase(const K& key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } + + + + + void swap(bhopscotch_map& other) { other.m_ht.swap(m_ht); } + + /* + * Lookup + */ + T& at(const Key& key) { return m_ht.at(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + T& at(const Key& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } + + const T& at(const Key& key) const { return m_ht.at(key); } + + /** + * @copydoc at(const Key& key, std::size_t precalculated_hash) + */ + const T& at(const Key& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent + * and Compare::is_transparent exist. + * If so, K must be hashable and comparable to Key. + */ + template::value && has_is_transparent::value>::type* = nullptr> + T& at(const K& key) { return m_ht.at(key); } + + /** + * @copydoc at(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value && has_is_transparent::value>::type* = nullptr> + T& at(const K& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } + + /** + * @copydoc at(const K& key) + */ + template::value && has_is_transparent::value>::type* = nullptr> + const T& at(const K& key) const { return m_ht.at(key); } + + /** + * @copydoc at(const K& key, std::size_t precalculated_hash) + */ + template::value && has_is_transparent::value>::type* = nullptr> + const T& at(const K& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } + + + + + T& operator[](const Key& key) { return m_ht[key]; } + T& operator[](Key&& key) { return m_ht[std::move(key)]; } + + + + + size_type count(const Key& key) const { return m_ht.count(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + size_type count(const Key& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent + * and Compare::is_transparent exist. + * If so, K must be hashable and comparable to Key. + */ + template::value && has_is_transparent::value>::type* = nullptr> + size_type count(const K& key) const { return m_ht.count(key); } + + /** + * @copydoc count(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value && has_is_transparent::value>::type* = nullptr> + size_type count(const K& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } + + + + + iterator find(const Key& key) { return m_ht.find(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + const_iterator find(const Key& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const Key& key, std::size_t precalculated_hash) + */ + const_iterator find(const Key& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent + * and Compare::is_transparent exist. + * If so, K must be hashable and comparable to Key. + */ + template::value && has_is_transparent::value>::type* = nullptr> + iterator find(const K& key) { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value && has_is_transparent::value>::type* = nullptr> + iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + /** + * @copydoc find(const K& key) + */ + template::value && has_is_transparent::value>::type* = nullptr> + const_iterator find(const K& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const K& key, std::size_t precalculated_hash) + */ + template::value && has_is_transparent::value>::type* = nullptr> + const_iterator find(const K& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } + + + + + bool contains(const Key& key) const { return m_ht.contains(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + bool contains(const Key& key, std::size_t precalculated_hash) const { + return m_ht.contains(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + bool contains(const K& key) const { return m_ht.contains(key); } + + /** + * @copydoc contains(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + bool contains(const K& key, std::size_t precalculated_hash) const { + return m_ht.contains(key, precalculated_hash); + } + + + + + std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent + * and Compare::is_transparent exist. + * If so, K must be hashable and comparable to Key. + */ + template::value && has_is_transparent::value>::type* = nullptr> + std::pair equal_range(const K& key) { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value && has_is_transparent::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * @copydoc equal_range(const K& key) + */ + template::value && has_is_transparent::value>::type* = nullptr> + std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key, std::size_t precalculated_hash) + */ + template::value && has_is_transparent::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + + + + /* + * Bucket interface + */ + size_type bucket_count() const { return m_ht.bucket_count(); } + size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + + + /* + * Hash policy + */ + float load_factor() const { return m_ht.load_factor(); } + float max_load_factor() const { return m_ht.max_load_factor(); } + void max_load_factor(float ml) { m_ht.max_load_factor(ml); } + + void rehash(size_type count_) { m_ht.rehash(count_); } + void reserve(size_type count_) { m_ht.reserve(count_); } + + + /* + * Observers + */ + hasher hash_function() const { return m_ht.hash_function(); } + key_equal key_eq() const { return m_ht.key_eq(); } + key_compare key_comp() const { return m_ht.key_comp(); } + + /* + * Other + */ + + /** + * Convert a const_iterator to an iterator. + */ + iterator mutable_iterator(const_iterator pos) { + return m_ht.mutable_iterator(pos); + } + + size_type overflow_size() const noexcept { return m_ht.overflow_size(); } + + friend bool operator==(const bhopscotch_map& lhs, const bhopscotch_map& rhs) { + if(lhs.size() != rhs.size()) { + return false; + } + + for(const auto& element_lhs : lhs) { + const auto it_element_rhs = rhs.find(element_lhs.first); + if(it_element_rhs == rhs.cend() || element_lhs.second != it_element_rhs->second) { + return false; + } + } + + return true; + } + + friend bool operator!=(const bhopscotch_map& lhs, const bhopscotch_map& rhs) { + return !operator==(lhs, rhs); + } + + friend void swap(bhopscotch_map& lhs, bhopscotch_map& rhs) { + lhs.swap(rhs); + } + + + +private: + ht m_ht; +}; + + +/** + * Same as `tsl::bhopscotch_map`. + */ +template, + class KeyEqual = std::equal_to, + class Compare = std::less, + class Allocator = std::allocator>, + unsigned int NeighborhoodSize = 62, + bool StoreHash = false> +using bhopscotch_pg_map = bhopscotch_map; + +} // end namespace tsl + +#endif diff --git a/src/tessil/bhopscotch_set.h b/src/tessil/bhopscotch_set.h new file mode 100644 index 0000000000..9a6619841f --- /dev/null +++ b/src/tessil/bhopscotch_set.h @@ -0,0 +1,560 @@ +/** + * MIT License + * + * Copyright (c) 2017 Tessil + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_BHOPSCOTCH_SET_H +#define TSL_BHOPSCOTCH_SET_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "hopscotch_hash.h" + + +namespace tsl { + + +/** + * Similar to tsl::hopscotch_set but instead of using a list for overflowing elements it uses + * a binary search tree. It thus needs an additional template parameter Compare. Compare should + * be arithmetically coherent with KeyEqual. + * + * The binary search tree allows the set to have a worst-case scenario of O(log n) for search + * and delete, even if the hash function maps all the elements to the same bucket. + * For insert, the amortized worst case is O(log n), but the worst case is O(n) in case of rehash. + * + * This makes the set resistant to DoS attacks (but doesn't preclude you to have a good hash function, + * as an element in the bucket array is faster to retrieve than in the tree). + * + * @copydoc hopscotch_set + */ +template, + class KeyEqual = std::equal_to, + class Compare = std::less, + class Allocator = std::allocator, + unsigned int NeighborhoodSize = 62, + bool StoreHash = false, + class GrowthPolicy = tsl::hh::power_of_two_growth_policy<2>> +class bhopscotch_set { +private: + template + using has_is_transparent = tsl::detail_hopscotch_hash::has_is_transparent; + + class KeySelect { + public: + using key_type = Key; + + const key_type& operator()(const Key& key) const { + return key; + } + + key_type& operator()(Key& key) { + return key; + } + }; + + + using overflow_container_type = std::set; + using ht = tsl::detail_hopscotch_hash::hopscotch_hash; + +public: + using key_type = typename ht::key_type; + using value_type = typename ht::value_type; + using size_type = typename ht::size_type; + using difference_type = typename ht::difference_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using key_compare = Compare; + using allocator_type = typename ht::allocator_type; + using reference = typename ht::reference; + using const_reference = typename ht::const_reference; + using pointer = typename ht::pointer; + using const_pointer = typename ht::const_pointer; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + + + /* + * Constructors + */ + bhopscotch_set() : bhopscotch_set(ht::DEFAULT_INIT_BUCKETS_SIZE) { + } + + explicit bhopscotch_set(size_type bucket_count, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator(), + const Compare& comp = Compare()) : + m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR, comp) + { + } + + bhopscotch_set(size_type bucket_count, + const Allocator& alloc) : bhopscotch_set(bucket_count, Hash(), KeyEqual(), alloc) + { + } + + bhopscotch_set(size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : bhopscotch_set(bucket_count, hash, KeyEqual(), alloc) + { + } + + explicit bhopscotch_set(const Allocator& alloc) : bhopscotch_set(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { + } + + template + bhopscotch_set(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()) : bhopscotch_set(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + + template + bhopscotch_set(InputIt first, InputIt last, + size_type bucket_count, + const Allocator& alloc) : bhopscotch_set(first, last, bucket_count, Hash(), KeyEqual(), alloc) + { + } + + template + bhopscotch_set(InputIt first, InputIt last, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : bhopscotch_set(first, last, bucket_count, hash, KeyEqual(), alloc) + { + } + + bhopscotch_set(std::initializer_list init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()) : + bhopscotch_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) + { + } + + bhopscotch_set(std::initializer_list init, + size_type bucket_count, + const Allocator& alloc) : + bhopscotch_set(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) + { + } + + bhopscotch_set(std::initializer_list init, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : + bhopscotch_set(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) + { + } + + + bhopscotch_set& operator=(std::initializer_list ilist) { + m_ht.clear(); + + m_ht.reserve(ilist.size()); + m_ht.insert(ilist.begin(), ilist.end()); + + return *this; + } + + allocator_type get_allocator() const { return m_ht.get_allocator(); } + + + /* + * Iterators + */ + iterator begin() noexcept { return m_ht.begin(); } + const_iterator begin() const noexcept { return m_ht.begin(); } + const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + + iterator end() noexcept { return m_ht.end(); } + const_iterator end() const noexcept { return m_ht.end(); } + const_iterator cend() const noexcept { return m_ht.cend(); } + + + /* + * Capacity + */ + bool empty() const noexcept { return m_ht.empty(); } + size_type size() const noexcept { return m_ht.size(); } + size_type max_size() const noexcept { return m_ht.max_size(); } + + /* + * Modifiers + */ + void clear() noexcept { m_ht.clear(); } + + + + + std::pair insert(const value_type& value) { return m_ht.insert(value); } + std::pair insert(value_type&& value) { return m_ht.insert(std::move(value)); } + + iterator insert(const_iterator hint, const value_type& value) { return m_ht.insert(hint, value); } + iterator insert(const_iterator hint, value_type&& value) { return m_ht.insert(hint, std::move(value)); } + + template + void insert(InputIt first, InputIt last) { m_ht.insert(first, last); } + void insert(std::initializer_list ilist) { m_ht.insert(ilist.begin(), ilist.end()); } + + + + + /** + * Due to the way elements are stored, emplace will need to move or copy the key-value once. + * The method is equivalent to insert(value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + std::pair emplace(Args&&... args) { return m_ht.emplace(std::forward(args)...); } + + + + + /** + * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. + * The method is equivalent to insert(hint, value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + iterator emplace_hint(const_iterator hint, Args&&... args) { + return m_ht.emplace_hint(hint, std::forward(args)...); + } + + + + + iterator erase(iterator pos) { return m_ht.erase(pos); } + iterator erase(const_iterator pos) { return m_ht.erase(pos); } + iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } + size_type erase(const key_type& key) { return m_ht.erase(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. + */ + size_type erase(const key_type& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent + * and Compare::is_transparent exist. + * If so, K must be hashable and comparable to Key. + */ + template::value && has_is_transparent::value>::type* = nullptr> + size_type erase(const K& key) { return m_ht.erase(key); } + + /** + * @copydoc erase(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. + */ + template::value && has_is_transparent::value>::type* = nullptr> + size_type erase(const K& key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } + + + + + void swap(bhopscotch_set& other) { other.m_ht.swap(m_ht); } + + + /* + * Lookup + */ + size_type count(const Key& key) const { return m_ht.count(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + size_type count(const Key& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent + * and Compare::is_transparent exist. + * If so, K must be hashable and comparable to Key. + */ + template::value && has_is_transparent::value>::type* = nullptr> + size_type count(const K& key) const { return m_ht.count(key); } + + /** + * @copydoc count(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value && has_is_transparent::value>::type* = nullptr> + size_type count(const K& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } + + + + + iterator find(const Key& key) { return m_ht.find(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + const_iterator find(const Key& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const Key& key, std::size_t precalculated_hash) + */ + const_iterator find(const Key& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent + * and Compare::is_transparent exist. + * If so, K must be hashable and comparable to Key. + */ + template::value && has_is_transparent::value>::type* = nullptr> + iterator find(const K& key) { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value && has_is_transparent::value>::type* = nullptr> + iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + /** + * @copydoc find(const K& key) + */ + template::value && has_is_transparent::value>::type* = nullptr> + const_iterator find(const K& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value && has_is_transparent::value>::type* = nullptr> + const_iterator find(const K& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } + + + + + bool contains(const Key& key) const { return m_ht.contains(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + bool contains(const Key& key, std::size_t precalculated_hash) const { + return m_ht.contains(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + bool contains(const K& key) const { return m_ht.contains(key); } + + /** + * @copydoc contains(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + bool contains(const K& key, std::size_t precalculated_hash) const { + return m_ht.contains(key, precalculated_hash); + } + + + + + std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent + * and Compare::is_transparent exist. + * If so, K must be hashable and comparable to Key. + */ + template::value && has_is_transparent::value>::type* = nullptr> + std::pair equal_range(const K& key) { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value && has_is_transparent::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * @copydoc equal_range(const K& key) + */ + template::value && has_is_transparent::value>::type* = nullptr> + std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key, std::size_t precalculated_hash) + */ + template::value && has_is_transparent::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + + + + /* + * Bucket interface + */ + size_type bucket_count() const { return m_ht.bucket_count(); } + size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + + + /* + * Hash policy + */ + float load_factor() const { return m_ht.load_factor(); } + float max_load_factor() const { return m_ht.max_load_factor(); } + void max_load_factor(float ml) { m_ht.max_load_factor(ml); } + + void rehash(size_type count_) { m_ht.rehash(count_); } + void reserve(size_type count_) { m_ht.reserve(count_); } + + + /* + * Observers + */ + hasher hash_function() const { return m_ht.hash_function(); } + key_equal key_eq() const { return m_ht.key_eq(); } + key_compare key_comp() const { return m_ht.key_comp(); } + + + /* + * Other + */ + + /** + * Convert a const_iterator to an iterator. + */ + iterator mutable_iterator(const_iterator pos) { + return m_ht.mutable_iterator(pos); + } + + size_type overflow_size() const noexcept { return m_ht.overflow_size(); } + + friend bool operator==(const bhopscotch_set& lhs, const bhopscotch_set& rhs) { + if(lhs.size() != rhs.size()) { + return false; + } + + for(const auto& element_lhs : lhs) { + const auto it_element_rhs = rhs.find(element_lhs); + if(it_element_rhs == rhs.cend()) { + return false; + } + } + + return true; + } + + friend bool operator!=(const bhopscotch_set& lhs, const bhopscotch_set& rhs) { + return !operator==(lhs, rhs); + } + + friend void swap(bhopscotch_set& lhs, bhopscotch_set& rhs) { + lhs.swap(rhs); + } + +private: + ht m_ht; +}; + + +/** + * Same as `tsl::bhopscotch_set`. + */ +template, + class KeyEqual = std::equal_to, + class Compare = std::less, + class Allocator = std::allocator, + unsigned int NeighborhoodSize = 62, + bool StoreHash = false> +using bhopscotch_pg_set = bhopscotch_set; + +} // end namespace tsl + +#endif diff --git a/src/tessil/hopscotch_growth_policy.h b/src/tessil/hopscotch_growth_policy.h new file mode 100644 index 0000000000..6ca8cca4c0 --- /dev/null +++ b/src/tessil/hopscotch_growth_policy.h @@ -0,0 +1,295 @@ +/** + * MIT License + * + * Copyright (c) 2018 Tessil + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_HOPSCOTCH_GROWTH_POLICY_H +#define TSL_HOPSCOTCH_GROWTH_POLICY_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace tsl { +namespace hh { + +/** + * Grow the hash table by a factor of GrowthFactor keeping the bucket count to a power of two. It allows + * the table to use a mask operation instead of a modulo operation to map a hash to a bucket. + * + * GrowthFactor must be a power of two >= 2. + */ +template +class power_of_two_growth_policy { +public: + /** + * Called on the hash table creation and on rehash. The number of buckets for the table is passed in parameter. + * This number is a minimum, the policy may update this value with a higher value if needed (but not lower). + * + * If 0 is given, min_bucket_count_in_out must still be 0 after the policy creation and + * bucket_for_hash must always return 0 in this case. + */ + explicit power_of_two_growth_policy(std::size_t& min_bucket_count_in_out) { + if(min_bucket_count_in_out > max_bucket_count()) { + throw std::length_error("The hash table exceeds its maxmimum size."); + } + + if(min_bucket_count_in_out > 0) { + min_bucket_count_in_out = round_up_to_power_of_two(min_bucket_count_in_out); + m_mask = min_bucket_count_in_out - 1; + } + else { + m_mask = 0; + } + } + + /** + * Return the bucket [0, bucket_count()) to which the hash belongs. + * If bucket_count() is 0, it must always return 0. + */ + std::size_t bucket_for_hash(std::size_t hash) const noexcept { + return hash & m_mask; + } + + /** + * Return the bucket count to use when the bucket array grows on rehash. + */ + std::size_t next_bucket_count() const { + if((m_mask + 1) > max_bucket_count() / GrowthFactor) { + throw std::length_error("The hash table exceeds its maxmimum size."); + } + + return (m_mask + 1) * GrowthFactor; + } + + /** + * Return the maximum number of buckets supported by the policy. + */ + std::size_t max_bucket_count() const { + // Largest power of two. + return (std::numeric_limits::max() / 2) + 1; + } + + /** + * Reset the growth policy as if it was created with a bucket count of 0. + * After a clear, the policy must always return 0 when bucket_for_hash is called. + */ + void clear() noexcept { + m_mask = 0; + } + +private: + static std::size_t round_up_to_power_of_two(std::size_t value) { + if(is_power_of_two(value)) { + return value; + } + + if(value == 0) { + return 1; + } + + --value; + for(std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) { + value |= value >> i; + } + + return value + 1; + } + + static constexpr bool is_power_of_two(std::size_t value) { + return value != 0 && (value & (value - 1)) == 0; + } + +private: + static_assert(is_power_of_two(GrowthFactor) && GrowthFactor >= 2, "GrowthFactor must be a power of two >= 2."); + + std::size_t m_mask; +}; + + +/** + * Grow the hash table by GrowthFactor::num / GrowthFactor::den and use a modulo to map a hash + * to a bucket. Slower but it can be useful if you want a slower growth. + */ +template> +class mod_growth_policy { +public: + explicit mod_growth_policy(std::size_t& min_bucket_count_in_out) { + if(min_bucket_count_in_out > max_bucket_count()) { + throw std::length_error("The hash table exceeds its maxmimum size."); + } + + if(min_bucket_count_in_out > 0) { + m_mod = min_bucket_count_in_out; + } + else { + m_mod = 1; + } + } + + std::size_t bucket_for_hash(std::size_t hash) const noexcept { + return hash % m_mod; + } + + std::size_t next_bucket_count() const { + if(m_mod == max_bucket_count()) { + throw std::length_error("The hash table exceeds its maxmimum size."); + } + + const double next_bucket_count = std::ceil(double(m_mod) * REHASH_SIZE_MULTIPLICATION_FACTOR); + if(!std::isnormal(next_bucket_count)) { + throw std::length_error("The hash table exceeds its maxmimum size."); + } + + if(next_bucket_count > double(max_bucket_count())) { + return max_bucket_count(); + } + else { + return std::size_t(next_bucket_count); + } + } + + std::size_t max_bucket_count() const { + return MAX_BUCKET_COUNT; + } + + void clear() noexcept { + m_mod = 1; + } + +private: + static constexpr double REHASH_SIZE_MULTIPLICATION_FACTOR = 1.0 * GrowthFactor::num / GrowthFactor::den; + static const std::size_t MAX_BUCKET_COUNT = + std::size_t(double( + std::numeric_limits::max() / REHASH_SIZE_MULTIPLICATION_FACTOR + )); + + static_assert(REHASH_SIZE_MULTIPLICATION_FACTOR >= 1.1, "Growth factor should be >= 1.1."); + + std::size_t m_mod; +}; + + + +namespace detail { + +static constexpr const std::array PRIMES = {{ + 1ul, 5ul, 17ul, 29ul, 37ul, 53ul, 67ul, 79ul, 97ul, 131ul, 193ul, 257ul, 389ul, 521ul, 769ul, 1031ul, + 1543ul, 2053ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, + 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, + 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291 +}}; + +template +static constexpr std::size_t mod(std::size_t hash) { return hash % PRIMES[IPrime]; } + +// MOD_PRIME[iprime](hash) returns hash % PRIMES[iprime]. This table allows for faster modulo as the +// compiler can optimize the modulo code better with a constant known at the compilation. +static constexpr const std::array MOD_PRIME = {{ + &mod<0>, &mod<1>, &mod<2>, &mod<3>, &mod<4>, &mod<5>, &mod<6>, &mod<7>, &mod<8>, &mod<9>, &mod<10>, + &mod<11>, &mod<12>, &mod<13>, &mod<14>, &mod<15>, &mod<16>, &mod<17>, &mod<18>, &mod<19>, &mod<20>, + &mod<21>, &mod<22>, &mod<23>, &mod<24>, &mod<25>, &mod<26>, &mod<27>, &mod<28>, &mod<29>, &mod<30>, + &mod<31>, &mod<32>, &mod<33>, &mod<34>, &mod<35>, &mod<36>, &mod<37> , &mod<38>, &mod<39> +}}; + +} + +/** + * Grow the hash table by using prime numbers as bucket count. Slower than tsl::hh::power_of_two_growth_policy in + * general but will probably distribute the values around better in the buckets with a poor hash function. + * + * To allow the compiler to optimize the modulo operation, a lookup table is used with constant primes numbers. + * + * With a switch the code would look like: + * \code + * switch(iprime) { // iprime is the current prime of the hash table + * case 0: hash % 5ul; + * break; + * case 1: hash % 17ul; + * break; + * case 2: hash % 29ul; + * break; + * ... + * } + * \endcode + * + * Due to the constant variable in the modulo the compiler is able to optimize the operation + * by a series of multiplications, substractions and shifts. + * + * The 'hash % 5' could become something like 'hash - (hash * 0xCCCCCCCD) >> 34) * 5' in a 64 bits environement. + */ +class prime_growth_policy { +public: + explicit prime_growth_policy(std::size_t& min_bucket_count_in_out) { + auto it_prime = std::lower_bound(detail::PRIMES.begin(), + detail::PRIMES.end(), min_bucket_count_in_out); + if(it_prime == detail::PRIMES.end()) { + throw std::length_error("The hash table exceeds its maxmimum size."); + } + + m_iprime = static_cast(std::distance(detail::PRIMES.begin(), it_prime)); + if(min_bucket_count_in_out > 0) { + min_bucket_count_in_out = *it_prime; + } + else { + min_bucket_count_in_out = 0; + } + } + + std::size_t bucket_for_hash(std::size_t hash) const noexcept { + return detail::MOD_PRIME[m_iprime](hash); + } + + std::size_t next_bucket_count() const { + if(m_iprime + 1 >= detail::PRIMES.size()) { + throw std::length_error("The hash table exceeds its maxmimum size."); + } + + return detail::PRIMES[m_iprime + 1]; + } + + std::size_t max_bucket_count() const { + return detail::PRIMES.back(); + } + + void clear() noexcept { + m_iprime = 0; + } + +private: + unsigned int m_iprime; + + static_assert(std::numeric_limits::max() >= detail::PRIMES.size(), + "The type of m_iprime is not big enough."); +}; + +} +} + +#endif diff --git a/src/tessil/hopscotch_hash.h b/src/tessil/hopscotch_hash.h new file mode 100644 index 0000000000..f775c181fc --- /dev/null +++ b/src/tessil/hopscotch_hash.h @@ -0,0 +1,1822 @@ +/** + * MIT License + * + * Copyright (c) 2017 Tessil + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_HOPSCOTCH_HASH_H +#define TSL_HOPSCOTCH_HASH_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hopscotch_growth_policy.h" + + + +#if (defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ < 9)) +# define TSL_HH_NO_RANGE_ERASE_WITH_CONST_ITERATOR +#endif + + +/* + * Only activate tsl_hh_assert if TSL_DEBUG is defined. + * This way we avoid the performance hit when NDEBUG is not defined with assert as tsl_hh_assert is used a lot + * (people usually compile with "-O3" and not "-O3 -DNDEBUG"). + */ +#ifdef TSL_DEBUG +# define tsl_hh_assert(expr) assert(expr) +#else +# define tsl_hh_assert(expr) (static_cast(0)) +#endif + + +namespace tsl { + +namespace detail_hopscotch_hash { + + +template +struct make_void { + using type = void; +}; + + +template +struct has_is_transparent : std::false_type { +}; + +template +struct has_is_transparent::type> : std::true_type { +}; + + +template +struct has_key_compare : std::false_type { +}; + +template +struct has_key_compare::type> : std::true_type { +}; + + +template +struct is_power_of_two_policy: std::false_type { +}; + +template +struct is_power_of_two_policy>: std::true_type { +}; + + + + + +/* + * smallest_type_for_min_bits::type returns the smallest type that can fit MinBits. + */ +static const std::size_t SMALLEST_TYPE_MAX_BITS_SUPPORTED = 64; +template +class smallest_type_for_min_bits { +}; + +template +class smallest_type_for_min_bits 0) && (MinBits <= 8)>::type> { +public: + using type = std::uint_least8_t; +}; + +template +class smallest_type_for_min_bits 8) && (MinBits <= 16)>::type> { +public: + using type = std::uint_least16_t; +}; + +template +class smallest_type_for_min_bits 16) && (MinBits <= 32)>::type> { +public: + using type = std::uint_least32_t; +}; + +template +class smallest_type_for_min_bits 32) && (MinBits <= 64)>::type> { +public: + using type = std::uint_least64_t; +}; + + + +/* + * Each bucket may store up to three elements: + * - An aligned storage to store a value_type object with placement-new. + * - An (optional) hash of the value in the bucket. + * - An unsigned integer of type neighborhood_bitmap used to tell us which buckets in the neighborhood of the + * current bucket contain a value with a hash belonging to the current bucket. + * + * For a bucket 'bct', a bit 'i' (counting from 0 and from the least significant bit to the most significant) + * set to 1 means that the bucket 'bct + i' contains a value with a hash belonging to bucket 'bct'. + * The bits used for that, start from the third least significant bit. + * The two least significant bits are reserved: + * - The least significant bit is set to 1 if there is a value in the bucket storage. + * - The second least significant bit is set to 1 if there is an overflow. More than NeighborhoodSize values + * give the same hash, all overflow values are stored in the m_overflow_elements list of the map. + * + * Details regarding hopscotch hashing an its implementation can be found here: + * https://tessil.github.io/2016/08/29/hopscotch-hashing.html + */ +static const std::size_t NB_RESERVED_BITS_IN_NEIGHBORHOOD = 2; + + +using truncated_hash_type = std::uint_least32_t; + +/** + * Helper class that stores a truncated hash if StoreHash is true and nothing otherwise. + */ +template +class hopscotch_bucket_hash { +public: + bool bucket_hash_equal(std::size_t /*hash*/) const noexcept { + return true; + } + + truncated_hash_type truncated_bucket_hash() const noexcept { + return 0; + } + +protected: + void copy_hash(const hopscotch_bucket_hash& ) noexcept { + } + + void set_hash(truncated_hash_type /*hash*/) noexcept { + } +}; + +template<> +class hopscotch_bucket_hash { +public: + bool bucket_hash_equal(std::size_t hash) const noexcept { + return m_hash == truncated_hash_type(hash); + } + + truncated_hash_type truncated_bucket_hash() const noexcept { + return m_hash; + } + +protected: + void copy_hash(const hopscotch_bucket_hash& bucket) noexcept { + m_hash = bucket.m_hash; + } + + void set_hash(truncated_hash_type hash) noexcept { + m_hash = hash; + } + +private: + truncated_hash_type m_hash; +}; + + +template +class hopscotch_bucket: public hopscotch_bucket_hash { +private: + static const std::size_t MIN_NEIGHBORHOOD_SIZE = 4; + static const std::size_t MAX_NEIGHBORHOOD_SIZE = SMALLEST_TYPE_MAX_BITS_SUPPORTED - NB_RESERVED_BITS_IN_NEIGHBORHOOD; + + + static_assert(NeighborhoodSize >= 4, "NeighborhoodSize should be >= 4."); + // We can't put a variable in the message, ensure coherence + static_assert(MIN_NEIGHBORHOOD_SIZE == 4, ""); + + static_assert(NeighborhoodSize <= 62, "NeighborhoodSize should be <= 62."); + // We can't put a variable in the message, ensure coherence + static_assert(MAX_NEIGHBORHOOD_SIZE == 62, ""); + + + static_assert(!StoreHash || NeighborhoodSize <= 30, + "NeighborhoodSize should be <= 30 if StoreHash is true."); + // We can't put a variable in the message, ensure coherence + static_assert(MAX_NEIGHBORHOOD_SIZE - 32 == 30, ""); + + using bucket_hash = hopscotch_bucket_hash; + +public: + using value_type = ValueType; + using neighborhood_bitmap = + typename smallest_type_for_min_bits::type; + + + hopscotch_bucket() noexcept: bucket_hash(), m_neighborhood_infos(0) { + tsl_hh_assert(empty()); + } + + + hopscotch_bucket(const hopscotch_bucket& bucket) + noexcept(std::is_nothrow_copy_constructible::value): bucket_hash(bucket), + m_neighborhood_infos(0) + { + if(!bucket.empty()) { + ::new (static_cast(std::addressof(m_value))) value_type(bucket.value()); + } + + m_neighborhood_infos = bucket.m_neighborhood_infos; + } + + hopscotch_bucket(hopscotch_bucket&& bucket) + noexcept(std::is_nothrow_move_constructible::value) : bucket_hash(std::move(bucket)), + m_neighborhood_infos(0) + { + if(!bucket.empty()) { + ::new (static_cast(std::addressof(m_value))) value_type(std::move(bucket.value())); + } + + m_neighborhood_infos = bucket.m_neighborhood_infos; + } + + hopscotch_bucket& operator=(const hopscotch_bucket& bucket) + noexcept(std::is_nothrow_copy_constructible::value) + { + if(this != &bucket) { + remove_value(); + + bucket_hash::operator=(bucket); + if(!bucket.empty()) { + ::new (static_cast(std::addressof(m_value))) value_type(bucket.value()); + } + + m_neighborhood_infos = bucket.m_neighborhood_infos; + } + + return *this; + } + + hopscotch_bucket& operator=(hopscotch_bucket&& ) = delete; + + ~hopscotch_bucket() noexcept { + if(!empty()) { + destroy_value(); + } + } + + neighborhood_bitmap neighborhood_infos() const noexcept { + return neighborhood_bitmap(m_neighborhood_infos >> NB_RESERVED_BITS_IN_NEIGHBORHOOD); + } + + void set_overflow(bool has_overflow) noexcept { + if(has_overflow) { + m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos | 2); + } + else { + m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos & ~2); + } + } + + bool has_overflow() const noexcept { + return (m_neighborhood_infos & 2) != 0; + } + + bool empty() const noexcept { + return (m_neighborhood_infos & 1) == 0; + } + + void toggle_neighbor_presence(std::size_t ineighbor) noexcept { + tsl_hh_assert(ineighbor <= NeighborhoodSize); + m_neighborhood_infos = neighborhood_bitmap( + m_neighborhood_infos ^ (1ull << (ineighbor + NB_RESERVED_BITS_IN_NEIGHBORHOOD))); + } + + bool check_neighbor_presence(std::size_t ineighbor) const noexcept { + tsl_hh_assert(ineighbor <= NeighborhoodSize); + if(((m_neighborhood_infos >> (ineighbor + NB_RESERVED_BITS_IN_NEIGHBORHOOD)) & 1) == 1) { + return true; + } + + return false; + } + + value_type& value() noexcept { + tsl_hh_assert(!empty()); + return *reinterpret_cast(std::addressof(m_value)); + } + + const value_type& value() const noexcept { + tsl_hh_assert(!empty()); + return *reinterpret_cast(std::addressof(m_value)); + } + + template + void set_value_of_empty_bucket(truncated_hash_type hash, Args&&... value_type_args) { + tsl_hh_assert(empty()); + + ::new (static_cast(std::addressof(m_value))) value_type(std::forward(value_type_args)...); + set_empty(false); + this->set_hash(hash); + } + + void swap_value_into_empty_bucket(hopscotch_bucket& empty_bucket) { + tsl_hh_assert(empty_bucket.empty()); + if(!empty()) { + ::new (static_cast(std::addressof(empty_bucket.m_value))) value_type(std::move(value())); + empty_bucket.copy_hash(*this); + empty_bucket.set_empty(false); + + destroy_value(); + set_empty(true); + } + } + + void remove_value() noexcept { + if(!empty()) { + destroy_value(); + set_empty(true); + } + } + + void clear() noexcept { + if(!empty()) { + destroy_value(); + } + + m_neighborhood_infos = 0; + tsl_hh_assert(empty()); + } + + static truncated_hash_type truncate_hash(std::size_t hash) noexcept { + return truncated_hash_type(hash); + } + +private: + void set_empty(bool is_empty) noexcept { + if(is_empty) { + m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos & ~1); + } + else { + m_neighborhood_infos = neighborhood_bitmap(m_neighborhood_infos | 1); + } + } + + void destroy_value() noexcept { + tsl_hh_assert(!empty()); + value().~value_type(); + } + +private: + using storage = typename std::aligned_storage::type; + + neighborhood_bitmap m_neighborhood_infos; + storage m_value; +}; + + +/** + * Internal common class used by (b)hopscotch_map and (b)hopscotch_set. + * + * ValueType is what will be stored by hopscotch_hash (usually std::pair for a map and Key for a set). + * + * KeySelect should be a FunctionObject which takes a ValueType in parameter and returns a reference to the key. + * + * ValueSelect should be a FunctionObject which takes a ValueType in parameter and returns a reference to the value. + * ValueSelect should be void if there is no value (in a set for example). + * + * OverflowContainer will be used as containers for overflown elements. Usually it should be a list + * or a set/map. + */ +template +class hopscotch_hash: private Hash, private KeyEqual, private GrowthPolicy { +private: + template + using has_mapped_type = typename std::integral_constant::value>; + + static_assert(noexcept(std::declval().bucket_for_hash(std::size_t(0))), "GrowthPolicy::bucket_for_hash must be noexcept."); + static_assert(noexcept(std::declval().clear()), "GrowthPolicy::clear must be noexcept."); + +public: + template + class hopscotch_iterator; + + using key_type = typename KeySelect::key_type; + using value_type = ValueType; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using hasher = Hash; + using key_equal = KeyEqual; + using allocator_type = Allocator; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = hopscotch_iterator; + using const_iterator = hopscotch_iterator; + +private: + using hopscotch_bucket = tsl::detail_hopscotch_hash::hopscotch_bucket; + using neighborhood_bitmap = typename hopscotch_bucket::neighborhood_bitmap; + + using buckets_allocator = typename std::allocator_traits::template rebind_alloc; + using buckets_container_type = std::vector; + + using overflow_container_type = OverflowContainer; + + static_assert(std::is_same::value, + "OverflowContainer should have ValueType as type."); + + static_assert(std::is_same::value, + "Invalid allocator, not the same type as the value_type."); + + + using iterator_buckets = typename buckets_container_type::iterator; + using const_iterator_buckets = typename buckets_container_type::const_iterator; + + using iterator_overflow = typename overflow_container_type::iterator; + using const_iterator_overflow = typename overflow_container_type::const_iterator; + +public: + /** + * The `operator*()` and `operator->()` methods return a const reference and const pointer respectively to the + * stored value type. + * + * In case of a map, to get a modifiable reference to the value associated to a key (the `.second` in the + * stored pair), you have to call `value()`. + */ + template + class hopscotch_iterator { + friend class hopscotch_hash; + private: + using iterator_bucket = typename std::conditional::type; + using iterator_overflow = typename std::conditional::type; + + + hopscotch_iterator(iterator_bucket buckets_iterator, iterator_bucket buckets_end_iterator, + iterator_overflow overflow_iterator) noexcept : + m_buckets_iterator(buckets_iterator), m_buckets_end_iterator(buckets_end_iterator), + m_overflow_iterator(overflow_iterator) + { + } + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = const typename hopscotch_hash::value_type; + using difference_type = std::ptrdiff_t; + using reference = value_type&; + using pointer = value_type*; + + + hopscotch_iterator() noexcept { + } + + // Copy constructor from iterator to const_iterator. + template::type* = nullptr> + hopscotch_iterator(const hopscotch_iterator& other) noexcept : + m_buckets_iterator(other.m_buckets_iterator), m_buckets_end_iterator(other.m_buckets_end_iterator), + m_overflow_iterator(other.m_overflow_iterator) + { + } + + hopscotch_iterator(const hopscotch_iterator& other) = default; + hopscotch_iterator(hopscotch_iterator&& other) = default; + hopscotch_iterator& operator=(const hopscotch_iterator& other) = default; + hopscotch_iterator& operator=(hopscotch_iterator&& other) = default; + + const typename hopscotch_hash::key_type& key() const { + if(m_buckets_iterator != m_buckets_end_iterator) { + return KeySelect()(m_buckets_iterator->value()); + } + + return KeySelect()(*m_overflow_iterator); + } + + template::value>::type* = nullptr> + typename std::conditional< + IsConst, + const typename U::value_type&, + typename U::value_type&>::type value() const + { + if(m_buckets_iterator != m_buckets_end_iterator) { + return U()(m_buckets_iterator->value()); + } + + return U()(*m_overflow_iterator); + } + + reference operator*() const { + if(m_buckets_iterator != m_buckets_end_iterator) { + return m_buckets_iterator->value(); + } + + return *m_overflow_iterator; + } + + pointer operator->() const { + if(m_buckets_iterator != m_buckets_end_iterator) { + return std::addressof(m_buckets_iterator->value()); + } + + return std::addressof(*m_overflow_iterator); + } + + hopscotch_iterator& operator++() { + if(m_buckets_iterator == m_buckets_end_iterator) { + ++m_overflow_iterator; + return *this; + } + + do { + ++m_buckets_iterator; + } while(m_buckets_iterator != m_buckets_end_iterator && m_buckets_iterator->empty()); + + return *this; + } + + hopscotch_iterator operator++(int) { + hopscotch_iterator tmp(*this); + ++*this; + + return tmp; + } + + friend bool operator==(const hopscotch_iterator& lhs, const hopscotch_iterator& rhs) { + return lhs.m_buckets_iterator == rhs.m_buckets_iterator && + lhs.m_overflow_iterator == rhs.m_overflow_iterator; + } + + friend bool operator!=(const hopscotch_iterator& lhs, const hopscotch_iterator& rhs) { + return !(lhs == rhs); + } + + private: + iterator_bucket m_buckets_iterator; + iterator_bucket m_buckets_end_iterator; + iterator_overflow m_overflow_iterator; + }; + +public: + template::value>::type* = nullptr> + hopscotch_hash(size_type bucket_count, + const Hash& hash, + const KeyEqual& equal, + const Allocator& alloc, + float max_load_factor) : Hash(hash), + KeyEqual(equal), + GrowthPolicy(bucket_count), + m_buckets_data(alloc), + m_overflow_elements(alloc), + m_buckets(static_empty_bucket_ptr()), + m_nb_elements(0) + { + if(bucket_count > max_bucket_count()) { + throw std::length_error("The map exceeds its maxmimum size."); + } + + if(bucket_count > 0) { + static_assert(NeighborhoodSize - 1 > 0, ""); + + // Can't directly construct with the appropriate size in the initializer + // as m_buckets_data(bucket_count, alloc) is not supported by GCC 4.8 + m_buckets_data.resize(bucket_count + NeighborhoodSize - 1); + m_buckets = m_buckets_data.data(); + } + + + this->max_load_factor(max_load_factor); + + + // Check in the constructor instead of outside of a function to avoi compilation issues + // when value_type is not complete. + static_assert(std::is_nothrow_move_constructible::value || + std::is_copy_constructible::value, + "value_type must be either copy constructible or nothrow move constructible."); + } + + template::value>::type* = nullptr> + hopscotch_hash(size_type bucket_count, + const Hash& hash, + const KeyEqual& equal, + const Allocator& alloc, + float max_load_factor, + const typename OC::key_compare& comp) : Hash(hash), + KeyEqual(equal), + GrowthPolicy(bucket_count), + m_buckets_data(alloc), + m_overflow_elements(comp, alloc), + m_buckets(static_empty_bucket_ptr()), + m_nb_elements(0) + { + + if(bucket_count > max_bucket_count()) { + throw std::length_error("The map exceeds its maxmimum size."); + } + + if(bucket_count > 0) { + static_assert(NeighborhoodSize - 1 > 0, ""); + + // Can't directly construct with the appropriate size in the initializer + // as m_buckets_data(bucket_count, alloc) is not supported by GCC 4.8 + m_buckets_data.resize(bucket_count + NeighborhoodSize - 1); + m_buckets = m_buckets_data.data(); + } + + + this->max_load_factor(max_load_factor); + + + // Check in the constructor instead of outside of a function to avoi compilation issues + // when value_type is not complete. + static_assert(std::is_nothrow_move_constructible::value || + std::is_copy_constructible::value, + "value_type must be either copy constructible or nothrow move constructible."); + } + + hopscotch_hash(const hopscotch_hash& other): + Hash(other), + KeyEqual(other), + GrowthPolicy(other), + m_buckets_data(other.m_buckets_data), + m_overflow_elements(other.m_overflow_elements), + m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr(): + m_buckets_data.data()), + m_nb_elements(other.m_nb_elements), + m_max_load_factor(other.m_max_load_factor), + m_max_load_threshold_rehash(other.m_max_load_threshold_rehash), + m_min_load_threshold_rehash(other.m_min_load_threshold_rehash) + { + } + + hopscotch_hash(hopscotch_hash&& other) + noexcept( + std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value + ): + Hash(std::move(static_cast(other))), + KeyEqual(std::move(static_cast(other))), + GrowthPolicy(std::move(static_cast(other))), + m_buckets_data(std::move(other.m_buckets_data)), + m_overflow_elements(std::move(other.m_overflow_elements)), + m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr(): + m_buckets_data.data()), + m_nb_elements(other.m_nb_elements), + m_max_load_factor(other.m_max_load_factor), + m_max_load_threshold_rehash(other.m_max_load_threshold_rehash), + m_min_load_threshold_rehash(other.m_min_load_threshold_rehash) + { + other.GrowthPolicy::clear(); + other.m_buckets_data.clear(); + other.m_overflow_elements.clear(); + other.m_buckets = static_empty_bucket_ptr(); + other.m_nb_elements = 0; + other.m_max_load_threshold_rehash = 0; + other.m_min_load_threshold_rehash = 0; + } + + hopscotch_hash& operator=(const hopscotch_hash& other) { + if(&other != this) { + Hash::operator=(other); + KeyEqual::operator=(other); + GrowthPolicy::operator=(other); + + m_buckets_data = other.m_buckets_data; + m_overflow_elements = other.m_overflow_elements; + m_buckets = m_buckets_data.empty()?static_empty_bucket_ptr(): + m_buckets_data.data(); + m_nb_elements = other.m_nb_elements; + m_max_load_factor = other.m_max_load_factor; + m_max_load_threshold_rehash = other.m_max_load_threshold_rehash; + m_min_load_threshold_rehash = other.m_min_load_threshold_rehash; + } + + return *this; + } + + hopscotch_hash& operator=(hopscotch_hash&& other) { + other.swap(*this); + other.clear(); + + return *this; + } + + allocator_type get_allocator() const { + return m_buckets_data.get_allocator(); + } + + + /* + * Iterators + */ + iterator begin() noexcept { + auto begin = m_buckets_data.begin(); + while(begin != m_buckets_data.end() && begin->empty()) { + ++begin; + } + + return iterator(begin, m_buckets_data.end(), m_overflow_elements.begin()); + } + + const_iterator begin() const noexcept { + return cbegin(); + } + + const_iterator cbegin() const noexcept { + auto begin = m_buckets_data.cbegin(); + while(begin != m_buckets_data.cend() && begin->empty()) { + ++begin; + } + + return const_iterator(begin, m_buckets_data.cend(), m_overflow_elements.cbegin()); + } + + iterator end() noexcept { + return iterator(m_buckets_data.end(), m_buckets_data.end(), m_overflow_elements.end()); + } + + const_iterator end() const noexcept { + return cend(); + } + + const_iterator cend() const noexcept { + return const_iterator(m_buckets_data.cend(), m_buckets_data.cend(), m_overflow_elements.cend()); + } + + + /* + * Capacity + */ + bool empty() const noexcept { + return m_nb_elements == 0; + } + + size_type size() const noexcept { + return m_nb_elements; + } + + size_type max_size() const noexcept { + return m_buckets_data.max_size(); + } + + /* + * Modifiers + */ + void clear() noexcept { + for(auto& bucket: m_buckets_data) { + bucket.clear(); + } + + m_overflow_elements.clear(); + m_nb_elements = 0; + } + + + std::pair insert(const value_type& value) { + return insert_impl(value); + } + + template::value>::type* = nullptr> + std::pair insert(P&& value) { + return insert_impl(value_type(std::forward

(value))); + } + + std::pair insert(value_type&& value) { + return insert_impl(std::move(value)); + } + + + iterator insert(const_iterator hint, const value_type& value) { + if(hint != cend() && compare_keys(KeySelect()(*hint), KeySelect()(value))) { + return mutable_iterator(hint); + } + + return insert(value).first; + } + + template::value>::type* = nullptr> + iterator insert(const_iterator hint, P&& value) { + return emplace_hint(hint, std::forward

(value)); + } + + iterator insert(const_iterator hint, value_type&& value) { + if(hint != cend() && compare_keys(KeySelect()(*hint), KeySelect()(value))) { + return mutable_iterator(hint); + } + + return insert(std::move(value)).first; + } + + + template + void insert(InputIt first, InputIt last) { + if(std::is_base_of::iterator_category>::value) + { + const auto nb_elements_insert = std::distance(first, last); + const std::size_t nb_elements_in_buckets = m_nb_elements - m_overflow_elements.size(); + const std::size_t nb_free_buckets = m_max_load_threshold_rehash - nb_elements_in_buckets; + tsl_hh_assert(m_nb_elements >= m_overflow_elements.size()); + tsl_hh_assert(m_max_load_threshold_rehash >= nb_elements_in_buckets); + + if(nb_elements_insert > 0 && nb_free_buckets < std::size_t(nb_elements_insert)) { + reserve(nb_elements_in_buckets + std::size_t(nb_elements_insert)); + } + } + + for(; first != last; ++first) { + insert(*first); + } + } + + + template + std::pair insert_or_assign(const key_type& k, M&& obj) { + return insert_or_assign_impl(k, std::forward(obj)); + } + + template + std::pair insert_or_assign(key_type&& k, M&& obj) { + return insert_or_assign_impl(std::move(k), std::forward(obj)); + } + + + template + iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) { + if(hint != cend() && compare_keys(KeySelect()(*hint), k)) { + auto it = mutable_iterator(hint); + it.value() = std::forward(obj); + + return it; + } + + return insert_or_assign(k, std::forward(obj)).first; + } + + template + iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) { + if(hint != cend() && compare_keys(KeySelect()(*hint), k)) { + auto it = mutable_iterator(hint); + it.value() = std::forward(obj); + + return it; + } + + return insert_or_assign(std::move(k), std::forward(obj)).first; + } + + + template + std::pair emplace(Args&&... args) { + return insert(value_type(std::forward(args)...)); + } + + template + iterator emplace_hint(const_iterator hint, Args&&... args) { + return insert(hint, value_type(std::forward(args)...)); + } + + template + std::pair try_emplace(const key_type& k, Args&&... args) { + return try_emplace_impl(k, std::forward(args)...); + } + + template + std::pair try_emplace(key_type&& k, Args&&... args) { + return try_emplace_impl(std::move(k), std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) { + if(hint != cend() && compare_keys(KeySelect()(*hint), k)) { + return mutable_iterator(hint); + } + + return try_emplace(k, std::forward(args)...).first; + } + + template + iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) { + if(hint != cend() && compare_keys(KeySelect()(*hint), k)) { + return mutable_iterator(hint); + } + + return try_emplace(std::move(k), std::forward(args)...).first; + } + + + /** + * Here to avoid `template size_type erase(const K& key)` being used when + * we use an iterator instead of a const_iterator. + */ + iterator erase(iterator pos) { + return erase(const_iterator(pos)); + } + + iterator erase(const_iterator pos) { + const std::size_t ibucket_for_hash = bucket_for_hash(hash_key(pos.key())); + + if(pos.m_buckets_iterator != pos.m_buckets_end_iterator) { + auto it_bucket = m_buckets_data.begin() + std::distance(m_buckets_data.cbegin(), pos.m_buckets_iterator); + erase_from_bucket(*it_bucket, ibucket_for_hash); + + return ++iterator(it_bucket, m_buckets_data.end(), m_overflow_elements.begin()); + } + else { + auto it_next_overflow = erase_from_overflow(pos.m_overflow_iterator, ibucket_for_hash); + return iterator(m_buckets_data.end(), m_buckets_data.end(), it_next_overflow); + } + } + + iterator erase(const_iterator first, const_iterator last) { + if(first == last) { + return mutable_iterator(first); + } + + auto to_delete = erase(first); + while(to_delete != last) { + to_delete = erase(to_delete); + } + + return to_delete; + } + + template + size_type erase(const K& key) { + return erase(key, hash_key(key)); + } + + template + size_type erase(const K& key, std::size_t hash) { + const std::size_t ibucket_for_hash = bucket_for_hash(hash); + + hopscotch_bucket* bucket_found = find_in_buckets(key, hash, m_buckets + ibucket_for_hash); + if(bucket_found != nullptr) { + erase_from_bucket(*bucket_found, ibucket_for_hash); + + return 1; + } + + if(m_buckets[ibucket_for_hash].has_overflow()) { + auto it_overflow = find_in_overflow(key); + if(it_overflow != m_overflow_elements.end()) { + erase_from_overflow(it_overflow, ibucket_for_hash); + + return 1; + } + } + + return 0; + } + + void swap(hopscotch_hash& other) { + using std::swap; + + swap(static_cast(*this), static_cast(other)); + swap(static_cast(*this), static_cast(other)); + swap(static_cast(*this), static_cast(other)); + swap(m_buckets_data, other.m_buckets_data); + swap(m_overflow_elements, other.m_overflow_elements); + swap(m_buckets, other.m_buckets); + swap(m_nb_elements, other.m_nb_elements); + swap(m_max_load_factor, other.m_max_load_factor); + swap(m_max_load_threshold_rehash, other.m_max_load_threshold_rehash); + swap(m_min_load_threshold_rehash, other.m_min_load_threshold_rehash); + } + + + /* + * Lookup + */ + template::value>::type* = nullptr> + typename U::value_type& at(const K& key) { + return at(key, hash_key(key)); + } + + template::value>::type* = nullptr> + typename U::value_type& at(const K& key, std::size_t hash) { + return const_cast(static_cast(this)->at(key, hash)); + } + + + template::value>::type* = nullptr> + const typename U::value_type& at(const K& key) const { + return at(key, hash_key(key)); + } + + template::value>::type* = nullptr> + const typename U::value_type& at(const K& key, std::size_t hash) const { + using T = typename U::value_type; + + const T* value = find_value_impl(key, hash, m_buckets + bucket_for_hash(hash)); + if(value == nullptr) { + throw std::out_of_range("Couldn't find key."); + } + else { + return *value; + } + } + + + template::value>::type* = nullptr> + typename U::value_type& operator[](K&& key) { + using T = typename U::value_type; + + const std::size_t hash = hash_key(key); + const std::size_t ibucket_for_hash = bucket_for_hash(hash); + + T* value = find_value_impl(key, hash, m_buckets + ibucket_for_hash); + if(value != nullptr) { + return *value; + } + else { + return insert_value(ibucket_for_hash, hash, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple()).first.value(); + } + } + + + template + size_type count(const K& key) const { + return count(key, hash_key(key)); + } + + template + size_type count(const K& key, std::size_t hash) const { + return count_impl(key, hash, m_buckets + bucket_for_hash(hash)); + } + + + template + iterator find(const K& key) { + return find(key, hash_key(key)); + } + + template + iterator find(const K& key, std::size_t hash) { + return find_impl(key, hash, m_buckets + bucket_for_hash(hash)); + } + + + template + const_iterator find(const K& key) const { + return find(key, hash_key(key)); + } + + template + const_iterator find(const K& key, std::size_t hash) const { + return find_impl(key, hash, m_buckets + bucket_for_hash(hash)); + } + + + template + bool contains(const K& key) const { + return contains(key, hash_key(key)); + } + + template + bool contains(const K& key, std::size_t hash) const { + return count(key, hash) != 0; + } + + + template + std::pair equal_range(const K& key) { + return equal_range(key, hash_key(key)); + } + + template + std::pair equal_range(const K& key, std::size_t hash) { + iterator it = find(key, hash); + return std::make_pair(it, (it == end())?it:std::next(it)); + } + + + template + std::pair equal_range(const K& key) const { + return equal_range(key, hash_key(key)); + } + + template + std::pair equal_range(const K& key, std::size_t hash) const { + const_iterator it = find(key, hash); + return std::make_pair(it, (it == cend())?it:std::next(it)); + } + + /* + * Bucket interface + */ + size_type bucket_count() const { + /* + * So that the last bucket can have NeighborhoodSize neighbors, the size of the bucket array is a little + * bigger than the real number of buckets when not empty. + * We could use some of the buckets at the beginning, but it is faster this way as we avoid extra checks. + */ + if(m_buckets_data.empty()) { + return 0; + } + + return m_buckets_data.size() - NeighborhoodSize + 1; + } + + size_type max_bucket_count() const { + const std::size_t max_bucket_count = std::min(GrowthPolicy::max_bucket_count(), m_buckets_data.max_size()); + return max_bucket_count - NeighborhoodSize + 1; + } + + + /* + * Hash policy + */ + float load_factor() const { + if(bucket_count() == 0) { + return 0; + } + + return float(m_nb_elements)/float(bucket_count()); + } + + float max_load_factor() const { + return m_max_load_factor; + } + + void max_load_factor(float ml) { + m_max_load_factor = std::max(0.1f, std::min(ml, 0.95f)); + m_max_load_threshold_rehash = size_type(float(bucket_count())*m_max_load_factor); + m_min_load_threshold_rehash = size_type(float(bucket_count())*MIN_LOAD_FACTOR_FOR_REHASH); + } + + void rehash(size_type count_) { + count_ = std::max(count_, size_type(std::ceil(float(size())/max_load_factor()))); + rehash_impl(count_); + } + + void reserve(size_type count_) { + rehash(size_type(std::ceil(float(count_)/max_load_factor()))); + } + + + /* + * Observers + */ + hasher hash_function() const { + return static_cast(*this); + } + + key_equal key_eq() const { + return static_cast(*this); + } + + /* + * Other + */ + iterator mutable_iterator(const_iterator pos) { + if(pos.m_buckets_iterator != pos.m_buckets_end_iterator) { + // Get a non-const iterator + auto it = m_buckets_data.begin() + std::distance(m_buckets_data.cbegin(), pos.m_buckets_iterator); + return iterator(it, m_buckets_data.end(), m_overflow_elements.begin()); + } + else { + // Get a non-const iterator + auto it = mutable_overflow_iterator(pos.m_overflow_iterator); + return iterator(m_buckets_data.end(), m_buckets_data.end(), it); + } + } + + size_type overflow_size() const noexcept { + return m_overflow_elements.size(); + } + + template::value>::type* = nullptr> + typename U::key_compare key_comp() const { + return m_overflow_elements.key_comp(); + } + + +private: + template + std::size_t hash_key(const K& key) const { + return Hash::operator()(key); + } + + template + bool compare_keys(const K1& key1, const K2& key2) const { + return KeyEqual::operator()(key1, key2); + } + + std::size_t bucket_for_hash(std::size_t hash) const { + const std::size_t bucket = GrowthPolicy::bucket_for_hash(hash); + tsl_hh_assert(bucket < m_buckets_data.size() || (bucket == 0 && m_buckets_data.empty())); + + return bucket; + } + + template::value>::type* = nullptr> + void rehash_impl(size_type count_) { + hopscotch_hash new_map = new_hopscotch_hash(count_); + + if(!m_overflow_elements.empty()) { + new_map.m_overflow_elements.swap(m_overflow_elements); + new_map.m_nb_elements += new_map.m_overflow_elements.size(); + + for(const value_type& value : new_map.m_overflow_elements) { + const std::size_t ibucket_for_hash = new_map.bucket_for_hash(new_map.hash_key(KeySelect()(value))); + new_map.m_buckets[ibucket_for_hash].set_overflow(true); + } + } + + try { + const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(new_map.bucket_count()); + for(auto it_bucket = m_buckets_data.begin(); it_bucket != m_buckets_data.end(); ++it_bucket) { + if(it_bucket->empty()) { + continue; + } + + const std::size_t hash = use_stored_hash? + it_bucket->truncated_bucket_hash(): + new_map.hash_key(KeySelect()(it_bucket->value())); + const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash); + + new_map.insert_value(ibucket_for_hash, hash, std::move(it_bucket->value())); + + + erase_from_bucket(*it_bucket, bucket_for_hash(hash)); + } + } + /* + * The call to insert_value may throw an exception if an element is added to the overflow + * list. Rollback the elements in this case. + */ + catch(...) { + m_overflow_elements.swap(new_map.m_overflow_elements); + + const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(new_map.bucket_count()); + for(auto it_bucket = new_map.m_buckets_data.begin(); it_bucket != new_map.m_buckets_data.end(); ++it_bucket) { + if(it_bucket->empty()) { + continue; + } + + const std::size_t hash = use_stored_hash? + it_bucket->truncated_bucket_hash(): + hash_key(KeySelect()(it_bucket->value())); + const std::size_t ibucket_for_hash = bucket_for_hash(hash); + + // The elements we insert were not in the overflow list before the switch. + // They will not be go in the overflow list if we rollback the switch. + insert_value(ibucket_for_hash, hash, std::move(it_bucket->value())); + } + + throw; + } + + new_map.swap(*this); + } + + template::value && + !std::is_nothrow_move_constructible::value>::type* = nullptr> + void rehash_impl(size_type count_) { + hopscotch_hash new_map = new_hopscotch_hash(count_); + + const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(new_map.bucket_count()); + for(const hopscotch_bucket& bucket: m_buckets_data) { + if(bucket.empty()) { + continue; + } + + const std::size_t hash = use_stored_hash? + bucket.truncated_bucket_hash(): + new_map.hash_key(KeySelect()(bucket.value())); + const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash); + + new_map.insert_value(ibucket_for_hash, hash, bucket.value()); + } + + for(const value_type& value: m_overflow_elements) { + const std::size_t hash = new_map.hash_key(KeySelect()(value)); + const std::size_t ibucket_for_hash = new_map.bucket_for_hash(hash); + + new_map.insert_value(ibucket_for_hash, hash, value); + } + + new_map.swap(*this); + } + +#ifdef TSL_HH_NO_RANGE_ERASE_WITH_CONST_ITERATOR + iterator_overflow mutable_overflow_iterator(const_iterator_overflow it) { + return std::next(m_overflow_elements.begin(), std::distance(m_overflow_elements.cbegin(), it)); + } +#else + iterator_overflow mutable_overflow_iterator(const_iterator_overflow it) { + return m_overflow_elements.erase(it, it); + } +#endif + + // iterator is in overflow list + iterator_overflow erase_from_overflow(const_iterator_overflow pos, std::size_t ibucket_for_hash) { +#ifdef TSL_HH_NO_RANGE_ERASE_WITH_CONST_ITERATOR + auto it_next = m_overflow_elements.erase(mutable_overflow_iterator(pos)); +#else + auto it_next = m_overflow_elements.erase(pos); +#endif + m_nb_elements--; + + + // Check if we can remove the overflow flag + tsl_hh_assert(m_buckets[ibucket_for_hash].has_overflow()); + for(const value_type& value: m_overflow_elements) { + const std::size_t bucket_for_value = bucket_for_hash(hash_key(KeySelect()(value))); + if(bucket_for_value == ibucket_for_hash) { + return it_next; + } + } + + m_buckets[ibucket_for_hash].set_overflow(false); + return it_next; + } + + + /** + * bucket_for_value is the bucket in which the value is. + * ibucket_for_hash is the bucket where the value belongs. + */ + void erase_from_bucket(hopscotch_bucket& bucket_for_value, std::size_t ibucket_for_hash) noexcept { + const std::size_t ibucket_for_value = std::distance(m_buckets_data.data(), &bucket_for_value); + tsl_hh_assert(ibucket_for_value >= ibucket_for_hash); + + bucket_for_value.remove_value(); + m_buckets[ibucket_for_hash].toggle_neighbor_presence(ibucket_for_value - ibucket_for_hash); + m_nb_elements--; + } + + + + template + std::pair insert_or_assign_impl(K&& key, M&& obj) { + auto it = try_emplace_impl(std::forward(key), std::forward(obj)); + if(!it.second) { + it.first.value() = std::forward(obj); + } + + return it; + } + + template + std::pair try_emplace_impl(P&& key, Args&&... args_value) { + const std::size_t hash = hash_key(key); + const std::size_t ibucket_for_hash = bucket_for_hash(hash); + + // Check if already presents + auto it_find = find_impl(key, hash, m_buckets + ibucket_for_hash); + if(it_find != end()) { + return std::make_pair(it_find, false); + } + + return insert_value(ibucket_for_hash, hash, std::piecewise_construct, + std::forward_as_tuple(std::forward

(key)), + std::forward_as_tuple(std::forward(args_value)...)); + } + + template + std::pair insert_impl(P&& value) { + const std::size_t hash = hash_key(KeySelect()(value)); + const std::size_t ibucket_for_hash = bucket_for_hash(hash); + + // Check if already presents + auto it_find = find_impl(KeySelect()(value), hash, m_buckets + ibucket_for_hash); + if(it_find != end()) { + return std::make_pair(it_find, false); + } + + + return insert_value(ibucket_for_hash, hash, std::forward

(value)); + } + + template + std::pair insert_value(std::size_t ibucket_for_hash, std::size_t hash, Args&&... value_type_args) { + if((m_nb_elements - m_overflow_elements.size()) >= m_max_load_threshold_rehash) { + rehash(GrowthPolicy::next_bucket_count()); + ibucket_for_hash = bucket_for_hash(hash); + } + + std::size_t ibucket_empty = find_empty_bucket(ibucket_for_hash); + if(ibucket_empty < m_buckets_data.size()) { + do { + tsl_hh_assert(ibucket_empty >= ibucket_for_hash); + + // Empty bucket is in range of NeighborhoodSize, use it + if(ibucket_empty - ibucket_for_hash < NeighborhoodSize) { + auto it = insert_in_bucket(ibucket_empty, ibucket_for_hash, + hash, std::forward(value_type_args)...); + return std::make_pair(iterator(it, m_buckets_data.end(), m_overflow_elements.begin()), true); + } + } + // else, try to swap values to get a closer empty bucket + while(swap_empty_bucket_closer(ibucket_empty)); + } + + // Load factor is too low or a rehash will not change the neighborhood, put the value in overflow list + if(size() < m_min_load_threshold_rehash || !will_neighborhood_change_on_rehash(ibucket_for_hash)) { + auto it = insert_in_overflow(ibucket_for_hash, std::forward(value_type_args)...); + return std::make_pair(iterator(m_buckets_data.end(), m_buckets_data.end(), it), true); + } + + rehash(GrowthPolicy::next_bucket_count()); + ibucket_for_hash = bucket_for_hash(hash); + + return insert_value(ibucket_for_hash, hash, std::forward(value_type_args)...); + } + + /* + * Return true if a rehash will change the position of a key-value in the neighborhood of + * ibucket_neighborhood_check. In this case a rehash is needed instead of puting the value in overflow list. + */ + bool will_neighborhood_change_on_rehash(size_t ibucket_neighborhood_check) const { + std::size_t expand_bucket_count = GrowthPolicy::next_bucket_count(); + GrowthPolicy expand_growth_policy(expand_bucket_count); + + const bool use_stored_hash = USE_STORED_HASH_ON_REHASH(expand_bucket_count); + for(size_t ibucket = ibucket_neighborhood_check; + ibucket < m_buckets_data.size() && (ibucket - ibucket_neighborhood_check) < NeighborhoodSize; + ++ibucket) + { + tsl_hh_assert(!m_buckets[ibucket].empty()); + + const size_t hash = use_stored_hash? + m_buckets[ibucket].truncated_bucket_hash(): + hash_key(KeySelect()(m_buckets[ibucket].value())); + if(bucket_for_hash(hash) != expand_growth_policy.bucket_for_hash(hash)) { + return true; + } + } + + return false; + } + + /* + * Return the index of an empty bucket in m_buckets_data. + * If none, the returned index equals m_buckets_data.size() + */ + std::size_t find_empty_bucket(std::size_t ibucket_start) const { + const std::size_t limit = std::min(ibucket_start + MAX_PROBES_FOR_EMPTY_BUCKET, m_buckets_data.size()); + for(; ibucket_start < limit; ibucket_start++) { + if(m_buckets[ibucket_start].empty()) { + return ibucket_start; + } + } + + return m_buckets_data.size(); + } + + /* + * Insert value in ibucket_empty where value originally belongs to ibucket_for_hash + * + * Return bucket iterator to ibucket_empty + */ + template + iterator_buckets insert_in_bucket(std::size_t ibucket_empty, std::size_t ibucket_for_hash, + std::size_t hash, Args&&... value_type_args) + { + tsl_hh_assert(ibucket_empty >= ibucket_for_hash ); + tsl_hh_assert(m_buckets[ibucket_empty].empty()); + m_buckets[ibucket_empty].set_value_of_empty_bucket(hopscotch_bucket::truncate_hash(hash), std::forward(value_type_args)...); + + tsl_hh_assert(!m_buckets[ibucket_for_hash].empty()); + m_buckets[ibucket_for_hash].toggle_neighbor_presence(ibucket_empty - ibucket_for_hash); + m_nb_elements++; + + return m_buckets_data.begin() + ibucket_empty; + } + + template::value>::type* = nullptr> + iterator_overflow insert_in_overflow(std::size_t ibucket_for_hash, Args&&... value_type_args) { + auto it = m_overflow_elements.emplace(m_overflow_elements.end(), std::forward(value_type_args)...); + + m_buckets[ibucket_for_hash].set_overflow(true); + m_nb_elements++; + + return it; + } + + template::value>::type* = nullptr> + iterator_overflow insert_in_overflow(std::size_t ibucket_for_hash, Args&&... value_type_args) { + auto it = m_overflow_elements.emplace(std::forward(value_type_args)...).first; + + m_buckets[ibucket_for_hash].set_overflow(true); + m_nb_elements++; + + return it; + } + + /* + * Try to swap the bucket ibucket_empty_in_out with a bucket preceding it while keeping the neighborhood + * conditions correct. + * + * If a swap was possible, the position of ibucket_empty_in_out will be closer to 0 and true will re returned. + */ + bool swap_empty_bucket_closer(std::size_t& ibucket_empty_in_out) { + tsl_hh_assert(ibucket_empty_in_out >= NeighborhoodSize); + const std::size_t neighborhood_start = ibucket_empty_in_out - NeighborhoodSize + 1; + + for(std::size_t to_check = neighborhood_start; to_check < ibucket_empty_in_out; to_check++) { + neighborhood_bitmap neighborhood_infos = m_buckets[to_check].neighborhood_infos(); + std::size_t to_swap = to_check; + + while(neighborhood_infos != 0 && to_swap < ibucket_empty_in_out) { + if((neighborhood_infos & 1) == 1) { + tsl_hh_assert(m_buckets[ibucket_empty_in_out].empty()); + tsl_hh_assert(!m_buckets[to_swap].empty()); + + m_buckets[to_swap].swap_value_into_empty_bucket(m_buckets[ibucket_empty_in_out]); + + tsl_hh_assert(!m_buckets[to_check].check_neighbor_presence(ibucket_empty_in_out - to_check)); + tsl_hh_assert(m_buckets[to_check].check_neighbor_presence(to_swap - to_check)); + + m_buckets[to_check].toggle_neighbor_presence(ibucket_empty_in_out - to_check); + m_buckets[to_check].toggle_neighbor_presence(to_swap - to_check); + + + ibucket_empty_in_out = to_swap; + + return true; + } + + to_swap++; + neighborhood_infos = neighborhood_bitmap(neighborhood_infos >> 1); + } + } + + return false; + } + + + + template::value>::type* = nullptr> + typename U::value_type* find_value_impl(const K& key, std::size_t hash, hopscotch_bucket* bucket_for_hash) { + return const_cast( + static_cast(this)->find_value_impl(key, hash, bucket_for_hash)); + } + + /* + * Avoid the creation of an iterator to just get the value for operator[] and at() in maps. Faster this way. + * + * Return null if no value for the key (TODO use std::optional when available). + */ + template::value>::type* = nullptr> + const typename U::value_type* find_value_impl(const K& key, std::size_t hash, + const hopscotch_bucket* bucket_for_hash) const + { + const hopscotch_bucket* bucket_found = find_in_buckets(key, hash, bucket_for_hash); + if(bucket_found != nullptr) { + return std::addressof(ValueSelect()(bucket_found->value())); + } + + if(bucket_for_hash->has_overflow()) { + auto it_overflow = find_in_overflow(key); + if(it_overflow != m_overflow_elements.end()) { + return std::addressof(ValueSelect()(*it_overflow)); + } + } + + return nullptr; + } + + template + size_type count_impl(const K& key, std::size_t hash, const hopscotch_bucket* bucket_for_hash) const { + if(find_in_buckets(key, hash, bucket_for_hash) != nullptr) { + return 1; + } + else if(bucket_for_hash->has_overflow() && find_in_overflow(key) != m_overflow_elements.cend()) { + return 1; + } + else { + return 0; + } + } + + template + iterator find_impl(const K& key, std::size_t hash, hopscotch_bucket* bucket_for_hash) { + hopscotch_bucket* bucket_found = find_in_buckets(key, hash, bucket_for_hash); + if(bucket_found != nullptr) { + return iterator(m_buckets_data.begin() + std::distance(m_buckets_data.data(), bucket_found), + m_buckets_data.end(), m_overflow_elements.begin()); + } + + if(!bucket_for_hash->has_overflow()) { + return end(); + } + + return iterator(m_buckets_data.end(), m_buckets_data.end(), find_in_overflow(key)); + } + + template + const_iterator find_impl(const K& key, std::size_t hash, const hopscotch_bucket* bucket_for_hash) const { + const hopscotch_bucket* bucket_found = find_in_buckets(key, hash, bucket_for_hash); + if(bucket_found != nullptr) { + return const_iterator(m_buckets_data.cbegin() + std::distance(m_buckets_data.data(), bucket_found), + m_buckets_data.cend(), m_overflow_elements.cbegin()); + } + + if(!bucket_for_hash->has_overflow()) { + return cend(); + } + + + return const_iterator(m_buckets_data.cend(), m_buckets_data.cend(), find_in_overflow(key)); + } + + template + hopscotch_bucket* find_in_buckets(const K& key, std::size_t hash, hopscotch_bucket* bucket_for_hash) { + const hopscotch_bucket* bucket_found = + static_cast(this)->find_in_buckets(key, hash, bucket_for_hash); + return const_cast(bucket_found); + } + + + /** + * Return a pointer to the bucket which has the value, nullptr otherwise. + */ + template + const hopscotch_bucket* find_in_buckets(const K& key, std::size_t hash, const hopscotch_bucket* bucket_for_hash) const { + (void) hash; // Avoid warning of unused variable when StoreHash is false; + + // TODO Try to optimize the function. + // I tried to use ffs and __builtin_ffs functions but I could not reduce the time the function + // takes with -march=native + + neighborhood_bitmap neighborhood_infos = bucket_for_hash->neighborhood_infos(); + while(neighborhood_infos != 0) { + if((neighborhood_infos & 1) == 1) { + // Check StoreHash before calling bucket_hash_equal. Functionally it doesn't change anythin. + // If StoreHash is false, bucket_hash_equal is a no-op. Avoiding the call is there to help + // GCC optimizes `hash` parameter away, it seems to not be able to do without this hint. + if((!StoreHash || bucket_for_hash->bucket_hash_equal(hash)) && + compare_keys(KeySelect()(bucket_for_hash->value()), key)) + { + return bucket_for_hash; + } + } + + ++bucket_for_hash; + neighborhood_infos = neighborhood_bitmap(neighborhood_infos >> 1); + } + + return nullptr; + } + + + + template::value>::type* = nullptr> + iterator_overflow find_in_overflow(const K& key) { + return std::find_if(m_overflow_elements.begin(), m_overflow_elements.end(), + [&](const value_type& value) { + return compare_keys(key, KeySelect()(value)); + }); + } + + template::value>::type* = nullptr> + const_iterator_overflow find_in_overflow(const K& key) const { + return std::find_if(m_overflow_elements.cbegin(), m_overflow_elements.cend(), + [&](const value_type& value) { + return compare_keys(key, KeySelect()(value)); + }); + } + + template::value>::type* = nullptr> + iterator_overflow find_in_overflow(const K& key) { + return m_overflow_elements.find(key); + } + + template::value>::type* = nullptr> + const_iterator_overflow find_in_overflow(const K& key) const { + return m_overflow_elements.find(key); + } + + + + template::value>::type* = nullptr> + hopscotch_hash new_hopscotch_hash(size_type bucket_count) { + return hopscotch_hash(bucket_count, static_cast(*this), static_cast(*this), + get_allocator(), m_max_load_factor); + } + + template::value>::type* = nullptr> + hopscotch_hash new_hopscotch_hash(size_type bucket_count) { + return hopscotch_hash(bucket_count, static_cast(*this), static_cast(*this), + get_allocator(), m_max_load_factor, m_overflow_elements.key_comp()); + } + +public: + static const size_type DEFAULT_INIT_BUCKETS_SIZE = 0; + static constexpr float DEFAULT_MAX_LOAD_FACTOR = (NeighborhoodSize <= 30)?0.8f:0.9f; + +private: + static const std::size_t MAX_PROBES_FOR_EMPTY_BUCKET = 12*NeighborhoodSize; + static constexpr float MIN_LOAD_FACTOR_FOR_REHASH = 0.1f; + + /** + * We can only use the hash on rehash if the size of the hash type is the same as the stored one or + * if we use a power of two modulo. In the case of the power of two modulo, we just mask + * the least significant bytes, we just have to check that the truncated_hash_type didn't truncated + * too much bytes. + */ + template::value>::type* = nullptr> + static bool USE_STORED_HASH_ON_REHASH(size_type /*bucket_count*/) { + return StoreHash; + } + + template::value>::type* = nullptr> + static bool USE_STORED_HASH_ON_REHASH(size_type bucket_count) { + (void) bucket_count; + if(StoreHash && is_power_of_two_policy::value) { + tsl_hh_assert(bucket_count > 0); + return (bucket_count - 1) <= std::numeric_limits::max(); + } + else { + return false; + } + } + + /** + * Return an always valid pointer to an static empty hopscotch_bucket. + */ + hopscotch_bucket* static_empty_bucket_ptr() { + static hopscotch_bucket empty_bucket; + return &empty_bucket; + } + +private: + buckets_container_type m_buckets_data; + overflow_container_type m_overflow_elements; + + /** + * Points to m_buckets_data.data() if !m_buckets_data.empty() otherwise points to static_empty_bucket_ptr. + * This variable is useful to avoid the cost of checking if m_buckets_data is empty when trying + * to find an element. + * + * TODO Remove m_buckets_data and only use a pointer+size instead of a pointer+vector to save some space in the hopscotch_hash object. + */ + hopscotch_bucket* m_buckets; + + size_type m_nb_elements; + + float m_max_load_factor; + + /** + * Max size of the hash table before a rehash occurs automatically to grow the table. + */ + size_type m_max_load_threshold_rehash; + + /** + * Min size of the hash table before a rehash can occurs automatically (except if m_max_load_threshold_rehash os reached). + * If the neighborhood of a bucket is full before the min is reacher, the elements are put into m_overflow_elements. + */ + size_type m_min_load_threshold_rehash; +}; + +} // end namespace detail_hopscotch_hash + + +} // end namespace tsl + +#endif diff --git a/src/tessil/hopscotch_map.h b/src/tessil/hopscotch_map.h new file mode 100644 index 0000000000..405ecf32fe --- /dev/null +++ b/src/tessil/hopscotch_map.h @@ -0,0 +1,710 @@ +/** + * MIT License + * + * Copyright (c) 2017 Tessil + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_HOPSCOTCH_MAP_H +#define TSL_HOPSCOTCH_MAP_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "hopscotch_hash.h" + + +namespace tsl { + +/** + * Implementation of a hash map using the hopscotch hashing algorithm. + * + * The Key and the value T must be either nothrow move-constructible, copy-constuctible or both. + * + * The size of the neighborhood (NeighborhoodSize) must be > 0 and <= 62 if StoreHash is false. + * When StoreHash is true, 32-bits of the hash will be stored alongside the neighborhood limiting + * the NeighborhoodSize to <= 30. There is no memory usage difference between + * 'NeighborhoodSize 62; StoreHash false' and 'NeighborhoodSize 30; StoreHash true'. + * + * Storing the hash may improve performance on insert during the rehash process if the hash takes time + * to compute. It may also improve read performance if the KeyEqual function takes time (or incurs a cache-miss). + * If used with simple Hash and KeyEqual it may slow things down. + * + * StoreHash can only be set if the GrowthPolicy is set to tsl::power_of_two_growth_policy. + * + * GrowthPolicy defines how the map grows and consequently how a hash value is mapped to a bucket. + * By default the map uses tsl::power_of_two_growth_policy. This policy keeps the number of buckets + * to a power of two and uses a mask to map the hash to a bucket instead of the slow modulo. + * You may define your own growth policy, check tsl::power_of_two_growth_policy for the interface. + * + * If the destructors of Key or T throw an exception, behaviour of the class is undefined. + * + * Iterators invalidation: + * - clear, operator=, reserve, rehash: always invalidate the iterators. + * - insert, emplace, emplace_hint, operator[]: if there is an effective insert, invalidate the iterators + * if a displacement is needed to resolve a collision (which mean that most of the time, + * insert will invalidate the iterators). Or if there is a rehash. + * - erase: iterator on the erased element is the only one which become invalid. + */ +template, + class KeyEqual = std::equal_to, + class Allocator = std::allocator>, + unsigned int NeighborhoodSize = 62, + bool StoreHash = false, + class GrowthPolicy = tsl::hh::power_of_two_growth_policy<2>> +class hopscotch_map { +private: + template + using has_is_transparent = tsl::detail_hopscotch_hash::has_is_transparent; + + class KeySelect { + public: + using key_type = Key; + + const key_type& operator()(const std::pair& key_value) const { + return key_value.first; + } + + key_type& operator()(std::pair& key_value) { + return key_value.first; + } + }; + + class ValueSelect { + public: + using value_type = T; + + const value_type& operator()(const std::pair& key_value) const { + return key_value.second; + } + + value_type& operator()(std::pair& key_value) { + return key_value.second; + } + }; + + + using overflow_container_type = std::list, Allocator>; + using ht = detail_hopscotch_hash::hopscotch_hash, KeySelect, ValueSelect, + Hash, KeyEqual, + Allocator, NeighborhoodSize, + StoreHash, GrowthPolicy, + overflow_container_type>; + +public: + using key_type = typename ht::key_type; + using mapped_type = T; + using value_type = typename ht::value_type; + using size_type = typename ht::size_type; + using difference_type = typename ht::difference_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using allocator_type = typename ht::allocator_type; + using reference = typename ht::reference; + using const_reference = typename ht::const_reference; + using pointer = typename ht::pointer; + using const_pointer = typename ht::const_pointer; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + + + + /* + * Constructors + */ + hopscotch_map() : hopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE) { + } + + explicit hopscotch_map(size_type bucket_count, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()) : + m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) + { + } + + hopscotch_map(size_type bucket_count, + const Allocator& alloc) : hopscotch_map(bucket_count, Hash(), KeyEqual(), alloc) + { + } + + hopscotch_map(size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : hopscotch_map(bucket_count, hash, KeyEqual(), alloc) + { + } + + explicit hopscotch_map(const Allocator& alloc) : hopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { + } + + template + hopscotch_map(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()) : hopscotch_map(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + + template + hopscotch_map(InputIt first, InputIt last, + size_type bucket_count, + const Allocator& alloc) : hopscotch_map(first, last, bucket_count, Hash(), KeyEqual(), alloc) + { + } + + template + hopscotch_map(InputIt first, InputIt last, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : hopscotch_map(first, last, bucket_count, hash, KeyEqual(), alloc) + { + } + + hopscotch_map(std::initializer_list init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()) : + hopscotch_map(init.begin(), init.end(), bucket_count, hash, equal, alloc) + { + } + + hopscotch_map(std::initializer_list init, + size_type bucket_count, + const Allocator& alloc) : + hopscotch_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) + { + } + + hopscotch_map(std::initializer_list init, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : + hopscotch_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) + { + } + + + hopscotch_map& operator=(std::initializer_list ilist) { + m_ht.clear(); + + m_ht.reserve(ilist.size()); + m_ht.insert(ilist.begin(), ilist.end()); + + return *this; + } + + allocator_type get_allocator() const { return m_ht.get_allocator(); } + + + /* + * Iterators + */ + iterator begin() noexcept { return m_ht.begin(); } + const_iterator begin() const noexcept { return m_ht.begin(); } + const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + + iterator end() noexcept { return m_ht.end(); } + const_iterator end() const noexcept { return m_ht.end(); } + const_iterator cend() const noexcept { return m_ht.cend(); } + + + /* + * Capacity + */ + bool empty() const noexcept { return m_ht.empty(); } + size_type size() const noexcept { return m_ht.size(); } + size_type max_size() const noexcept { return m_ht.max_size(); } + + /* + * Modifiers + */ + void clear() noexcept { m_ht.clear(); } + + + + + std::pair insert(const value_type& value) { + return m_ht.insert(value); + } + + template::value>::type* = nullptr> + std::pair insert(P&& value) { + return m_ht.insert(std::forward

(value)); + } + + std::pair insert(value_type&& value) { + return m_ht.insert(std::move(value)); + } + + + iterator insert(const_iterator hint, const value_type& value) { + return m_ht.insert(hint, value); + } + + template::value>::type* = nullptr> + iterator insert(const_iterator hint, P&& value) { + return m_ht.insert(hint, std::forward

(value)); + } + + iterator insert(const_iterator hint, value_type&& value) { + return m_ht.insert(hint, std::move(value)); + } + + + template + void insert(InputIt first, InputIt last) { + m_ht.insert(first, last); + } + + void insert(std::initializer_list ilist) { + m_ht.insert(ilist.begin(), ilist.end()); + } + + + + + template + std::pair insert_or_assign(const key_type& k, M&& obj) { + return m_ht.insert_or_assign(k, std::forward(obj)); + } + + template + std::pair insert_or_assign(key_type&& k, M&& obj) { + return m_ht.insert_or_assign(std::move(k), std::forward(obj)); + } + + template + iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) { + return m_ht.insert_or_assign(hint, k, std::forward(obj)); + } + + template + iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) { + return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); + } + + + + + /** + * Due to the way elements are stored, emplace will need to move or copy the key-value once. + * The method is equivalent to insert(value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + std::pair emplace(Args&&... args) { + return m_ht.emplace(std::forward(args)...); + } + + + + + /** + * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. + * The method is equivalent to insert(hint, value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + iterator emplace_hint(const_iterator hint, Args&&... args) { + return m_ht.emplace_hint(hint, std::forward(args)...); + } + + + + + template + std::pair try_emplace(const key_type& k, Args&&... args) { + return m_ht.try_emplace(k, std::forward(args)...); + } + + template + std::pair try_emplace(key_type&& k, Args&&... args) { + return m_ht.try_emplace(std::move(k), std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) { + return m_ht.try_emplace(hint, k, std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) { + return m_ht.try_emplace(hint, std::move(k), std::forward(args)...); + } + + + + + iterator erase(iterator pos) { return m_ht.erase(pos); } + iterator erase(const_iterator pos) { return m_ht.erase(pos); } + iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } + size_type erase(const key_type& key) { return m_ht.erase(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. + */ + size_type erase(const key_type& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type erase(const K& key) { return m_ht.erase(key); } + + /** + * @copydoc erase(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. + */ + template::value>::type* = nullptr> + size_type erase(const K& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + + + + void swap(hopscotch_map& other) { other.m_ht.swap(m_ht); } + + /* + * Lookup + */ + T& at(const Key& key) { return m_ht.at(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + T& at(const Key& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } + + + const T& at(const Key& key) const { return m_ht.at(key); } + + /** + * @copydoc at(const Key& key, std::size_t precalculated_hash) + */ + const T& at(const Key& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } + + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + T& at(const K& key) { return m_ht.at(key); } + + /** + * @copydoc at(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + T& at(const K& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } + + + /** + * @copydoc at(const K& key) + */ + template::value>::type* = nullptr> + const T& at(const K& key) const { return m_ht.at(key); } + + /** + * @copydoc at(const K& key, std::size_t precalculated_hash) + */ + template::value>::type* = nullptr> + const T& at(const K& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } + + + + + T& operator[](const Key& key) { return m_ht[key]; } + T& operator[](Key&& key) { return m_ht[std::move(key)]; } + + + + + size_type count(const Key& key) const { return m_ht.count(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + size_type count(const Key& key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type count(const K& key) const { return m_ht.count(key); } + + /** + * @copydoc count(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + size_type count(const K& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } + + + + + iterator find(const Key& key) { return m_ht.find(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + const_iterator find(const Key& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const Key& key, std::size_t precalculated_hash) + */ + const_iterator find(const Key& key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + iterator find(const K& key) { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + /** + * @copydoc find(const K& key) + */ + template::value>::type* = nullptr> + const_iterator find(const K& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + const_iterator find(const K& key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + + + + bool contains(const Key& key) const { return m_ht.contains(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + bool contains(const Key& key, std::size_t precalculated_hash) const { + return m_ht.contains(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + bool contains(const K& key) const { return m_ht.contains(key); } + + /** + * @copydoc contains(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + bool contains(const K& key, std::size_t precalculated_hash) const { + return m_ht.contains(key, precalculated_hash); + } + + + + + std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) { return m_ht.equal_range(key); } + + + /** + * @copydoc equal_range(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * @copydoc equal_range(const K& key) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key, std::size_t precalculated_hash) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + + + + /* + * Bucket interface + */ + size_type bucket_count() const { return m_ht.bucket_count(); } + size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + + + /* + * Hash policy + */ + float load_factor() const { return m_ht.load_factor(); } + float max_load_factor() const { return m_ht.max_load_factor(); } + void max_load_factor(float ml) { m_ht.max_load_factor(ml); } + + void rehash(size_type count_) { m_ht.rehash(count_); } + void reserve(size_type count_) { m_ht.reserve(count_); } + + + /* + * Observers + */ + hasher hash_function() const { return m_ht.hash_function(); } + key_equal key_eq() const { return m_ht.key_eq(); } + + /* + * Other + */ + + /** + * Convert a const_iterator to an iterator. + */ + iterator mutable_iterator(const_iterator pos) { + return m_ht.mutable_iterator(pos); + } + + size_type overflow_size() const noexcept { return m_ht.overflow_size(); } + + friend bool operator==(const hopscotch_map& lhs, const hopscotch_map& rhs) { + if(lhs.size() != rhs.size()) { + return false; + } + + for(const auto& element_lhs : lhs) { + const auto it_element_rhs = rhs.find(element_lhs.first); + if(it_element_rhs == rhs.cend() || element_lhs.second != it_element_rhs->second) { + return false; + } + } + + return true; + } + + friend bool operator!=(const hopscotch_map& lhs, const hopscotch_map& rhs) { + return !operator==(lhs, rhs); + } + + friend void swap(hopscotch_map& lhs, hopscotch_map& rhs) { + lhs.swap(rhs); + } + + + +private: + ht m_ht; +}; + + +/** + * Same as `tsl::hopscotch_map`. + */ +template, + class KeyEqual = std::equal_to, + class Allocator = std::allocator>, + unsigned int NeighborhoodSize = 62, + bool StoreHash = false> +using hopscotch_pg_map = hopscotch_map; + +} // end namespace tsl + +#endif diff --git a/src/tessil/hopscotch_set.h b/src/tessil/hopscotch_set.h new file mode 100644 index 0000000000..acfbf8db8a --- /dev/null +++ b/src/tessil/hopscotch_set.h @@ -0,0 +1,556 @@ +/** + * MIT License + * + * Copyright (c) 2017 Tessil + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_HOPSCOTCH_SET_H +#define TSL_HOPSCOTCH_SET_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "hopscotch_hash.h" + + +namespace tsl { + +/** + * Implementation of a hash set using the hopscotch hashing algorithm. + * + * The Key must be either nothrow move-constructible, copy-constuctible or both. + * + * The size of the neighborhood (NeighborhoodSize) must be > 0 and <= 62 if StoreHash is false. + * When StoreHash is true, 32-bits of the hash will be stored alongside the neighborhood limiting + * the NeighborhoodSize to <= 30. There is no memory usage difference between + * 'NeighborhoodSize 62; StoreHash false' and 'NeighborhoodSize 30; StoreHash true'. + * + * Storing the hash may improve performance on insert during the rehash process if the hash takes time + * to compute. It may also improve read performance if the KeyEqual function takes time (or incurs a cache-miss). + * If used with simple Hash and KeyEqual it may slow things down. + * + * StoreHash can only be set if the GrowthPolicy is set to tsl::power_of_two_growth_policy. + * + * GrowthPolicy defines how the set grows and consequently how a hash value is mapped to a bucket. + * By default the set uses tsl::power_of_two_growth_policy. This policy keeps the number of buckets + * to a power of two and uses a mask to set the hash to a bucket instead of the slow modulo. + * You may define your own growth policy, check tsl::power_of_two_growth_policy for the interface. + * + * If the destructor of Key throws an exception, behaviour of the class is undefined. + * + * Iterators invalidation: + * - clear, operator=, reserve, rehash: always invalidate the iterators. + * - insert, emplace, emplace_hint, operator[]: if there is an effective insert, invalidate the iterators + * if a displacement is needed to resolve a collision (which mean that most of the time, + * insert will invalidate the iterators). Or if there is a rehash. + * - erase: iterator on the erased element is the only one which become invalid. + */ +template, + class KeyEqual = std::equal_to, + class Allocator = std::allocator, + unsigned int NeighborhoodSize = 62, + bool StoreHash = false, + class GrowthPolicy = tsl::hh::power_of_two_growth_policy<2>> +class hopscotch_set { +private: + template + using has_is_transparent = tsl::detail_hopscotch_hash::has_is_transparent; + + class KeySelect { + public: + using key_type = Key; + + const key_type& operator()(const Key& key) const { + return key; + } + + key_type& operator()(Key& key) { + return key; + } + }; + + + using overflow_container_type = std::list; + using ht = detail_hopscotch_hash::hopscotch_hash; + +public: + using key_type = typename ht::key_type; + using value_type = typename ht::value_type; + using size_type = typename ht::size_type; + using difference_type = typename ht::difference_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using allocator_type = typename ht::allocator_type; + using reference = typename ht::reference; + using const_reference = typename ht::const_reference; + using pointer = typename ht::pointer; + using const_pointer = typename ht::const_pointer; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + + + /* + * Constructors + */ + hopscotch_set() : hopscotch_set(ht::DEFAULT_INIT_BUCKETS_SIZE) { + } + + explicit hopscotch_set(size_type bucket_count, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()) : + m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) + { + } + + hopscotch_set(size_type bucket_count, + const Allocator& alloc) : hopscotch_set(bucket_count, Hash(), KeyEqual(), alloc) + { + } + + hopscotch_set(size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : hopscotch_set(bucket_count, hash, KeyEqual(), alloc) + { + } + + explicit hopscotch_set(const Allocator& alloc) : hopscotch_set(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { + } + + template + hopscotch_set(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()) : hopscotch_set(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + + template + hopscotch_set(InputIt first, InputIt last, + size_type bucket_count, + const Allocator& alloc) : hopscotch_set(first, last, bucket_count, Hash(), KeyEqual(), alloc) + { + } + + template + hopscotch_set(InputIt first, InputIt last, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : hopscotch_set(first, last, bucket_count, hash, KeyEqual(), alloc) + { + } + + hopscotch_set(std::initializer_list init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()) : + hopscotch_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) + { + } + + hopscotch_set(std::initializer_list init, + size_type bucket_count, + const Allocator& alloc) : + hopscotch_set(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) + { + } + + hopscotch_set(std::initializer_list init, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : + hopscotch_set(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) + { + } + + + hopscotch_set& operator=(std::initializer_list ilist) { + m_ht.clear(); + + m_ht.reserve(ilist.size()); + m_ht.insert(ilist.begin(), ilist.end()); + + return *this; + } + + allocator_type get_allocator() const { return m_ht.get_allocator(); } + + + /* + * Iterators + */ + iterator begin() noexcept { return m_ht.begin(); } + const_iterator begin() const noexcept { return m_ht.begin(); } + const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + + iterator end() noexcept { return m_ht.end(); } + const_iterator end() const noexcept { return m_ht.end(); } + const_iterator cend() const noexcept { return m_ht.cend(); } + + + /* + * Capacity + */ + bool empty() const noexcept { return m_ht.empty(); } + size_type size() const noexcept { return m_ht.size(); } + size_type max_size() const noexcept { return m_ht.max_size(); } + + /* + * Modifiers + */ + void clear() noexcept { m_ht.clear(); } + + + + + std::pair insert(const value_type& value) { return m_ht.insert(value); } + std::pair insert(value_type&& value) { return m_ht.insert(std::move(value)); } + + iterator insert(const_iterator hint, const value_type& value) { return m_ht.insert(hint, value); } + iterator insert(const_iterator hint, value_type&& value) { return m_ht.insert(hint, std::move(value)); } + + template + void insert(InputIt first, InputIt last) { m_ht.insert(first, last); } + void insert(std::initializer_list ilist) { m_ht.insert(ilist.begin(), ilist.end()); } + + + + + /** + * Due to the way elements are stored, emplace will need to move or copy the key-value once. + * The method is equivalent to insert(value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + std::pair emplace(Args&&... args) { return m_ht.emplace(std::forward(args)...); } + + + + + /** + * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. + * The method is equivalent to insert(hint, value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + iterator emplace_hint(const_iterator hint, Args&&... args) { + return m_ht.emplace_hint(hint, std::forward(args)...); + } + + + + + iterator erase(iterator pos) { return m_ht.erase(pos); } + iterator erase(const_iterator pos) { return m_ht.erase(pos); } + iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } + size_type erase(const key_type& key) { return m_ht.erase(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. + */ + size_type erase(const key_type& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type erase(const K& key) { return m_ht.erase(key); } + + /** + * @copydoc erase(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. + */ + template::value>::type* = nullptr> + size_type erase(const K& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + + + + void swap(hopscotch_set& other) { other.m_ht.swap(m_ht); } + + + /* + * Lookup + */ + size_type count(const Key& key) const { return m_ht.count(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + size_type count(const Key& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type count(const K& key) const { return m_ht.count(key); } + + /** + * @copydoc count(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + size_type count(const K& key, std::size_t precalculated_hash) const { return m_ht.count(key, precalculated_hash); } + + + + + iterator find(const Key& key) { return m_ht.find(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + const_iterator find(const Key& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const Key& key, std::size_t precalculated_hash) + */ + const_iterator find(const Key& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + iterator find(const K& key) { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + /** + * @copydoc find(const K& key) + */ + template::value>::type* = nullptr> + const_iterator find(const K& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + const_iterator find(const K& key, std::size_t precalculated_hash) const { return m_ht.find(key, precalculated_hash); } + + + + + bool contains(const Key& key) const { return m_ht.contains(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + bool contains(const Key& key, std::size_t precalculated_hash) const { + return m_ht.contains(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + bool contains(const K& key) const { return m_ht.contains(key); } + + /** + * @copydoc contains(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + bool contains(const K& key, std::size_t precalculated_hash) const { + return m_ht.contains(key, precalculated_hash); + } + + + + + std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * @copydoc equal_range(const K& key) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key, std::size_t precalculated_hash) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + + + + /* + * Bucket interface + */ + size_type bucket_count() const { return m_ht.bucket_count(); } + size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + + + /* + * Hash policy + */ + float load_factor() const { return m_ht.load_factor(); } + float max_load_factor() const { return m_ht.max_load_factor(); } + void max_load_factor(float ml) { m_ht.max_load_factor(ml); } + + void rehash(size_type count_) { m_ht.rehash(count_); } + void reserve(size_type count_) { m_ht.reserve(count_); } + + + /* + * Observers + */ + hasher hash_function() const { return m_ht.hash_function(); } + key_equal key_eq() const { return m_ht.key_eq(); } + + + /* + * Other + */ + + /** + * Convert a const_iterator to an iterator. + */ + iterator mutable_iterator(const_iterator pos) { + return m_ht.mutable_iterator(pos); + } + + size_type overflow_size() const noexcept { return m_ht.overflow_size(); } + + friend bool operator==(const hopscotch_set& lhs, const hopscotch_set& rhs) { + if(lhs.size() != rhs.size()) { + return false; + } + + for(const auto& element_lhs : lhs) { + const auto it_element_rhs = rhs.find(element_lhs); + if(it_element_rhs == rhs.cend()) { + return false; + } + } + + return true; + } + + friend bool operator!=(const hopscotch_set& lhs, const hopscotch_set& rhs) { + return !operator==(lhs, rhs); + } + + friend void swap(hopscotch_set& lhs, hopscotch_set& rhs) { + lhs.swap(rhs); + } + +private: + ht m_ht; +}; + + +/** + * Same as `tsl::hopscotch_set`. + */ +template, + class KeyEqual = std::equal_to, + class Allocator = std::allocator, + unsigned int NeighborhoodSize = 62, + bool StoreHash = false> +using hopscotch_pg_set = hopscotch_set; + +} // end namespace tsl + +#endif diff --git a/src/tessil/ordered_hash.h b/src/tessil/ordered_hash.h new file mode 100644 index 0000000000..d5a9d45703 --- /dev/null +++ b/src/tessil/ordered_hash.h @@ -0,0 +1,1604 @@ +/** + * MIT License + * + * Copyright (c) 2017 Tessil + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_ORDERED_HASH_H +#define TSL_ORDERED_HASH_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + * Macros for compatibility with GCC 4.8 + */ +#if (defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ < 9)) +# define TSL_OH_NO_CONTAINER_ERASE_CONST_ITERATOR +# define TSL_OH_NO_CONTAINER_EMPLACE_CONST_ITERATOR +#endif + +/** + * Only activate tsl_oh_assert if TSL_DEBUG is defined. + * This way we avoid the performance hit when NDEBUG is not defined with assert as tsl_oh_assert is used a lot + * (people usually compile with "-O3" and not "-O3 -DNDEBUG"). + */ +#ifdef TSL_DEBUG +# define tsl_oh_assert(expr) assert(expr) +#else +# define tsl_oh_assert(expr) (static_cast(0)) +#endif + +/** + * If exceptions are enabled, throw the exception passed in parameter, otherwise call std::terminate. + */ +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (defined (_MSC_VER) && defined (_CPPUNWIND))) && !defined(TSL_NO_EXCEPTIONS) +# define TSL_OH_THROW_OR_TERMINATE(ex, msg) throw ex(msg) +#else +# define TSL_OH_NO_EXCEPTIONS +# ifdef NDEBUG +# define TSL_OH_THROW_OR_TERMINATE(ex, msg) std::terminate() +# else +# include +# define TSL_OH_THROW_OR_TERMINATE(ex, msg) do { std::cerr << msg << std::endl; std::terminate(); } while(0) +# endif +#endif + + +namespace tsl { + +namespace detail_ordered_hash { + +template +struct make_void { + using type = void; +}; + +template +struct has_is_transparent: std::false_type { +}; + +template +struct has_is_transparent::type>: std::true_type { +}; + + +template +struct is_vector: std::false_type { +}; + +template +struct is_vector>::value + >::type>: std::true_type { +}; + +template +static T numeric_cast(U value, const char* error_message = "numeric_cast() failed.") { + T ret = static_cast(value); + if(static_cast(ret) != value) { + TSL_OH_THROW_OR_TERMINATE(std::runtime_error, error_message); + } + + const bool is_same_signedness = (std::is_unsigned::value && std::is_unsigned::value) || + (std::is_signed::value && std::is_signed::value); + if(!is_same_signedness && (ret < T{}) != (value < U{})) { + TSL_OH_THROW_OR_TERMINATE(std::runtime_error, error_message); + } + + return ret; +} + + +/** + * Fixed size type used to represent size_type values on serialization. Need to be big enough + * to represent a std::size_t on 32 and 64 bits platforms, and must be the same size on both platforms. + */ +using slz_size_type = std::uint64_t; +// static_assert(std::numeric_limits::max() >= std::numeric_limits::max(), +// "slz_size_type must be >= std::size_t"); + +template +static T deserialize_value(Deserializer& deserializer) { + // MSVC < 2017 is not conformant, circumvent the problem by removing the template keyword +#if defined (_MSC_VER) && _MSC_VER < 1910 + return deserializer.Deserializer::operator()(); +#else + return deserializer.Deserializer::template operator()(); +#endif +} + + +/** + * Each bucket entry stores an index which is the index in m_values corresponding to the bucket's value + * and a hash (which may be truncated to 32 bits depending on IndexType) corresponding to the hash of the value. + * + * The size of IndexType limits the size of the hash table to std::numeric_limits::max() - 1 elements (-1 due to + * a reserved value used to mark a bucket as empty). + */ +template +class bucket_entry { + static_assert(std::is_unsigned::value, "IndexType must be an unsigned value."); + static_assert(std::numeric_limits::max() <= std::numeric_limits::max(), + "std::numeric_limits::max() must be <= std::numeric_limits::max()."); + +public: + using index_type = IndexType; + using truncated_hash_type = typename std::conditional::max() <= + std::numeric_limits::max(), + std::uint_least32_t, + std::size_t>::type; + + bucket_entry() noexcept: m_index(EMPTY_MARKER_INDEX), m_hash(0) { + } + + bool empty() const noexcept { + return m_index == EMPTY_MARKER_INDEX; + } + + void clear() noexcept { + m_index = EMPTY_MARKER_INDEX; + } + + index_type index() const noexcept { + tsl_oh_assert(!empty()); + return m_index; + } + + index_type& index_ref() noexcept { + tsl_oh_assert(!empty()); + return m_index; + } + + void set_index(index_type index) noexcept { + tsl_oh_assert(index <= max_size()); + + m_index = index; + } + + truncated_hash_type truncated_hash() const noexcept { + tsl_oh_assert(!empty()); + return m_hash; + } + + truncated_hash_type& truncated_hash_ref() noexcept { + tsl_oh_assert(!empty()); + return m_hash; + } + + void set_hash(std::size_t hash) noexcept { + m_hash = truncate_hash(hash); + } + + template + void serialize(Serializer& serializer) const { + const slz_size_type index = m_index; + serializer(index); + + const slz_size_type hash = m_hash; + serializer(hash); + } + + template + static bucket_entry deserialize(Deserializer& deserializer) { + const slz_size_type index = deserialize_value(deserializer); + const slz_size_type hash = deserialize_value(deserializer); + + bucket_entry bentry; + bentry.m_index = numeric_cast(index, "Deserialized index is too big."); + bentry.m_hash = numeric_cast(hash, "Deserialized hash is too big."); + + return bentry; + } + + + + static truncated_hash_type truncate_hash(std::size_t hash) noexcept { + return truncated_hash_type(hash); + } + + static std::size_t max_size() noexcept { + return static_cast(std::numeric_limits::max()) - NB_RESERVED_INDEXES; + } + +private: + static const index_type EMPTY_MARKER_INDEX = std::numeric_limits::max(); + static const std::size_t NB_RESERVED_INDEXES = 1; + + index_type m_index; + truncated_hash_type m_hash; +}; + + + +/** + * Internal common class used by ordered_map and ordered_set. + * + * ValueType is what will be stored by ordered_hash (usually std::pair for map and Key for set). + * + * KeySelect should be a FunctionObject which takes a ValueType in parameter and return a reference to the key. + * + * ValueSelect should be a FunctionObject which takes a ValueType in parameter and return a reference to the value. + * ValueSelect should be void if there is no value (in set for example). + * + * ValueTypeContainer is the container which will be used to store ValueType values. + * Usually a std::deque or std::vector. + * + * + * + * The orderd_hash structure is a hash table which preserves the order of insertion of the elements. + * To do so, it stores the values in the ValueTypeContainer (m_values) using emplace_back at each + * insertion of a new element. Another structure (m_buckets of type std::vector) will + * serve as buckets array for the hash table part. Each bucket stores an index which corresponds to + * the index in m_values where the bucket's value is and the (truncated) hash of this value. An index + * is used instead of a pointer to the value to reduce the size of each bucket entry. + * + * To resolve collisions in the buckets array, the structures use robin hood linear probing with + * backward shift deletion. + */ +template +class ordered_hash: private Hash, private KeyEqual { +private: + template + using has_mapped_type = typename std::integral_constant::value>; + + static_assert(std::is_same::value, + "ValueTypeContainer::value_type != ValueType. " + "Check that the ValueTypeContainer has 'Key' as type for a set or 'std::pair' as type for a map."); + + static_assert(std::is_same::value, + "ValueTypeContainer::allocator_type != Allocator. " + "Check that the allocator for ValueTypeContainer is the same as Allocator."); + + static_assert(std::is_same::value, + "Allocator::value_type != ValueType. " + "Check that the allocator has 'Key' as type for a set or 'std::pair' as type for a map."); + + +public: + template + class ordered_iterator; + + using key_type = typename KeySelect::key_type; + using value_type = ValueType; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using hasher = Hash; + using key_equal = KeyEqual; + using allocator_type = Allocator; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = ordered_iterator; + using const_iterator = ordered_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + using values_container_type = ValueTypeContainer; + +public: + template + class ordered_iterator { + friend class ordered_hash; + + private: + using iterator = typename std::conditional::type; + + + ordered_iterator(iterator it) noexcept: m_iterator(it) { + } + + public: + using iterator_category = std::random_access_iterator_tag; + using value_type = const typename ordered_hash::value_type; + using difference_type = typename iterator::difference_type; + using reference = value_type&; + using pointer = value_type*; + + + ordered_iterator() noexcept { + } + + // Copy constructor from iterator to const_iterator. + template::type* = nullptr> + ordered_iterator(const ordered_iterator& other) noexcept: m_iterator(other.m_iterator) { + } + + ordered_iterator(const ordered_iterator& other) = default; + ordered_iterator(ordered_iterator&& other) = default; + ordered_iterator& operator=(const ordered_iterator& other) = default; + ordered_iterator& operator=(ordered_iterator&& other) = default; + + const typename ordered_hash::key_type& key() const { + return KeySelect()(*m_iterator); + } + + template::value && IsConst>::type* = nullptr> + const typename U::value_type& value() const { + return U()(*m_iterator); + } + + template::value && !IsConst>::type* = nullptr> + typename U::value_type& value() { + return U()(*m_iterator); + } + + reference operator*() const { return *m_iterator; } + pointer operator->() const { return m_iterator.operator->(); } + + ordered_iterator& operator++() { ++m_iterator; return *this; } + ordered_iterator& operator--() { --m_iterator; return *this; } + + ordered_iterator operator++(int) { ordered_iterator tmp(*this); ++(*this); return tmp; } + ordered_iterator operator--(int) { ordered_iterator tmp(*this); --(*this); return tmp; } + + reference operator[](difference_type n) const { return m_iterator[n]; } + + ordered_iterator& operator+=(difference_type n) { m_iterator += n; return *this; } + ordered_iterator& operator-=(difference_type n) { m_iterator -= n; return *this; } + + ordered_iterator operator+(difference_type n) { ordered_iterator tmp(*this); tmp += n; return tmp; } + ordered_iterator operator-(difference_type n) { ordered_iterator tmp(*this); tmp -= n; return tmp; } + + friend bool operator==(const ordered_iterator& lhs, const ordered_iterator& rhs) { + return lhs.m_iterator == rhs.m_iterator; + } + + friend bool operator!=(const ordered_iterator& lhs, const ordered_iterator& rhs) { + return lhs.m_iterator != rhs.m_iterator; + } + + friend bool operator<(const ordered_iterator& lhs, const ordered_iterator& rhs) { + return lhs.m_iterator < rhs.m_iterator; + } + + friend bool operator>(const ordered_iterator& lhs, const ordered_iterator& rhs) { + return lhs.m_iterator > rhs.m_iterator; + } + + friend bool operator<=(const ordered_iterator& lhs, const ordered_iterator& rhs) { + return lhs.m_iterator <= rhs.m_iterator; + } + + friend bool operator>=(const ordered_iterator& lhs, const ordered_iterator& rhs) { + return lhs.m_iterator >= rhs.m_iterator; + } + + friend ordered_iterator operator+(difference_type n, const ordered_iterator& it) { + return n + it.m_iterator; + } + + friend difference_type operator-(const ordered_iterator& lhs, const ordered_iterator& rhs) { + return lhs.m_iterator - rhs.m_iterator; + } + + private: + iterator m_iterator; + }; + + +private: + using bucket_entry = tsl::detail_ordered_hash::bucket_entry; + + using buckets_container_allocator = typename + std::allocator_traits::template rebind_alloc; + + using buckets_container_type = std::vector; + + + using truncated_hash_type = typename bucket_entry::truncated_hash_type; + using index_type = typename bucket_entry::index_type; + +public: + ordered_hash(size_type bucket_count, + const Hash& hash, + const KeyEqual& equal, + const Allocator& alloc, + float max_load_factor): Hash(hash), + KeyEqual(equal), + m_buckets_data(alloc), + m_buckets(static_empty_bucket_ptr()), + m_mask(0), + m_values(alloc), + m_grow_on_next_insert(false) + { + if(bucket_count > max_bucket_count()) { + TSL_OH_THROW_OR_TERMINATE(std::length_error, "The map exceeds its maxmimum size."); + } + + if(bucket_count > 0) { + bucket_count = round_up_to_power_of_two(bucket_count); + + m_buckets_data.resize(bucket_count); + m_buckets = m_buckets_data.data(), + m_mask = bucket_count - 1; + } + + this->max_load_factor(max_load_factor); + } + + ordered_hash(const ordered_hash& other): Hash(other), + KeyEqual(other), + m_buckets_data(other.m_buckets_data), + m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr(): + m_buckets_data.data()), + m_mask(other.m_mask), + m_values(other.m_values), + m_grow_on_next_insert(other.m_grow_on_next_insert), + m_max_load_factor(other.m_max_load_factor), + m_load_threshold(other.m_load_threshold) + { + } + + ordered_hash(ordered_hash&& other) noexcept(std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value) + : Hash(std::move(static_cast(other))), + KeyEqual(std::move(static_cast(other))), + m_buckets_data(std::move(other.m_buckets_data)), + m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr(): + m_buckets_data.data()), + m_mask(other.m_mask), + m_values(std::move(other.m_values)), + m_grow_on_next_insert(other.m_grow_on_next_insert), + m_max_load_factor(other.m_max_load_factor), + m_load_threshold(other.m_load_threshold) + { + other.m_buckets_data.clear(); + other.m_buckets = static_empty_bucket_ptr(); + other.m_mask = 0; + other.m_values.clear(); + other.m_grow_on_next_insert = false; + other.m_load_threshold = 0; + } + + ordered_hash& operator=(const ordered_hash& other) { + if(&other != this) { + Hash::operator=(other); + KeyEqual::operator=(other); + + m_buckets_data = other.m_buckets_data; + m_buckets = m_buckets_data.empty()?static_empty_bucket_ptr(): + m_buckets_data.data(); + + m_mask = other.m_mask; + m_values = other.m_values; + m_grow_on_next_insert = other.m_grow_on_next_insert; + m_max_load_factor = other.m_max_load_factor; + m_load_threshold = other.m_load_threshold; + } + + return *this; + } + + ordered_hash& operator=(ordered_hash&& other) { + other.swap(*this); + other.clear(); + + return *this; + } + + allocator_type get_allocator() const { + return m_values.get_allocator(); + } + + + /* + * Iterators + */ + iterator begin() noexcept { + return iterator(m_values.begin()); + } + + const_iterator begin() const noexcept { + return cbegin(); + } + + const_iterator cbegin() const noexcept { + return const_iterator(m_values.cbegin()); + } + + iterator end() noexcept { + return iterator(m_values.end()); + } + + const_iterator end() const noexcept { + return cend(); + } + + const_iterator cend() const noexcept { + return const_iterator(m_values.cend()); + } + + + reverse_iterator rbegin() noexcept { + return reverse_iterator(m_values.end()); + } + + const_reverse_iterator rbegin() const noexcept { + return rcbegin(); + } + + const_reverse_iterator rcbegin() const noexcept { + return const_reverse_iterator(m_values.cend()); + } + + reverse_iterator rend() noexcept { + return reverse_iterator(m_values.begin()); + } + + const_reverse_iterator rend() const noexcept { + return rcend(); + } + + const_reverse_iterator rcend() const noexcept { + return const_reverse_iterator(m_values.cbegin()); + } + + + /* + * Capacity + */ + bool empty() const noexcept { + return m_values.empty(); + } + + size_type size() const noexcept { + return m_values.size(); + } + + size_type max_size() const noexcept { + return std::min(bucket_entry::max_size(), m_values.max_size()); + } + + + /* + * Modifiers + */ + void clear() noexcept { + for(auto& bucket: m_buckets_data) { + bucket.clear(); + } + + m_values.clear(); + m_grow_on_next_insert = false; + } + + template + std::pair insert(P&& value) { + return insert_impl(KeySelect()(value), std::forward

(value)); + } + + template + iterator insert_hint(const_iterator hint, P&& value) { + if(hint != cend() && compare_keys(KeySelect()(*hint), KeySelect()(value))) { + return mutable_iterator(hint); + } + + return insert(std::forward

(value)).first; + } + + template + void insert(InputIt first, InputIt last) { + if(std::is_base_of::iterator_category>::value) + { + const auto nb_elements_insert = std::distance(first, last); + const size_type nb_free_buckets = m_load_threshold - size(); + tsl_oh_assert(m_load_threshold >= size()); + + if(nb_elements_insert > 0 && nb_free_buckets < size_type(nb_elements_insert)) { + reserve(size() + size_type(nb_elements_insert)); + } + } + + for(; first != last; ++first) { + insert(*first); + } + } + + + + template + std::pair insert_or_assign(K&& key, M&& value) { + auto it = try_emplace(std::forward(key), std::forward(value)); + if(!it.second) { + it.first.value() = std::forward(value); + } + + return it; + } + + template + iterator insert_or_assign(const_iterator hint, K&& key, M&& obj) { + if(hint != cend() && compare_keys(KeySelect()(*hint), key)) { + auto it = mutable_iterator(hint); + it.value() = std::forward(obj); + + return it; + } + + return insert_or_assign(std::forward(key), std::forward(obj)).first; + } + + + + template + std::pair emplace(Args&&... args) { + return insert(value_type(std::forward(args)...)); + } + + template + iterator emplace_hint(const_iterator hint, Args&&... args) { + return insert_hint(hint, value_type(std::forward(args)...)); + } + + + + template + std::pair try_emplace(K&& key, Args&&... value_args) { + return insert_impl(key, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(value_args)...)); + } + + template + iterator try_emplace_hint(const_iterator hint, K&& key, Args&&... args) { + if(hint != cend() && compare_keys(KeySelect()(*hint), key)) { + return mutable_iterator(hint); + } + + return try_emplace(std::forward(key), std::forward(args)...).first; + } + + + + /** + * Here to avoid `template size_type erase(const K& key)` being used when + * we use an `iterator` instead of a `const_iterator`. + */ + iterator erase(iterator pos) { + return erase(const_iterator(pos)); + } + + iterator erase(const_iterator pos) { + tsl_oh_assert(pos != cend()); + + const std::size_t index_erase = iterator_to_index(pos); + + auto it_bucket = find_key(pos.key(), hash_key(pos.key())); + tsl_oh_assert(it_bucket != m_buckets_data.end()); + + erase_value_from_bucket(it_bucket); + + /* + * One element was removed from m_values, due to the left shift the next element + * is now at the position of the previous element (or end if none). + */ + return begin() + index_erase; + } + + iterator erase(const_iterator first, const_iterator last) { + if(first == last) { + return mutable_iterator(first); + } + + tsl_oh_assert(std::distance(first, last) > 0); + const std::size_t start_index = iterator_to_index(first); + const std::size_t nb_values = std::size_t(std::distance(first, last)); + const std::size_t end_index = start_index + nb_values; + + // Delete all values +#ifdef TSL_OH_NO_CONTAINER_ERASE_CONST_ITERATOR + auto next_it = m_values.erase(mutable_iterator(first).m_iterator, mutable_iterator(last).m_iterator); +#else + auto next_it = m_values.erase(first.m_iterator, last.m_iterator); +#endif + + /* + * Mark the buckets corresponding to the values as empty and do a backward shift. + * + * Also, the erase operation on m_values has shifted all the values on the right of last.m_iterator. + * Adapt the indexes for these values. + */ + std::size_t ibucket = 0; + while(ibucket < m_buckets_data.size()) { + if(m_buckets[ibucket].empty()) { + ibucket++; + } + else if(m_buckets[ibucket].index() >= start_index && m_buckets[ibucket].index() < end_index) { + m_buckets[ibucket].clear(); + backward_shift(ibucket); + // Don't increment ibucket, backward_shift may have replaced current bucket. + } + else if(m_buckets[ibucket].index() >= end_index) { + m_buckets[ibucket].set_index(index_type(m_buckets[ibucket].index() - nb_values)); + ibucket++; + } + else { + ibucket++; + } + } + + return iterator(next_it); + } + + + template + size_type erase(const K& key) { + return erase(key, hash_key(key)); + } + + template + size_type erase(const K& key, std::size_t hash) { + return erase_impl(key, hash); + } + + void swap(ordered_hash& other) { + using std::swap; + + swap(static_cast(*this), static_cast(other)); + swap(static_cast(*this), static_cast(other)); + swap(m_buckets_data, other.m_buckets_data); + swap(m_buckets, other.m_buckets); + swap(m_mask, other.m_mask); + swap(m_values, other.m_values); + swap(m_grow_on_next_insert, other.m_grow_on_next_insert); + swap(m_max_load_factor, other.m_max_load_factor); + swap(m_load_threshold, other.m_load_threshold); + } + + + + + /* + * Lookup + */ + template::value>::type* = nullptr> + typename U::value_type& at(const K& key) { + return at(key, hash_key(key)); + } + + template::value>::type* = nullptr> + typename U::value_type& at(const K& key, std::size_t hash) { + return const_cast(static_cast(this)->at(key, hash)); + } + + template::value>::type* = nullptr> + const typename U::value_type& at(const K& key) const { + return at(key, hash_key(key)); + } + + template::value>::type* = nullptr> + const typename U::value_type& at(const K& key, std::size_t hash) const { + auto it = find(key, hash); + if(it != end()) { + return it.value(); + } + else { + TSL_OH_THROW_OR_TERMINATE(std::out_of_range, "Couldn't find the key."); + } + } + + + template::value>::type* = nullptr> + typename U::value_type& operator[](K&& key) { + return try_emplace(std::forward(key)).first.value(); + } + + + template + size_type count(const K& key) const { + return count(key, hash_key(key)); + } + + template + size_type count(const K& key, std::size_t hash) const { + if(find(key, hash) == cend()) { + return 0; + } + else { + return 1; + } + } + + template + iterator find(const K& key) { + return find(key, hash_key(key)); + } + + template + iterator find(const K& key, std::size_t hash) { + auto it_bucket = find_key(key, hash); + return (it_bucket != m_buckets_data.end())?iterator(m_values.begin() + it_bucket->index()):end(); + } + + template + const_iterator find(const K& key) const { + return find(key, hash_key(key)); + } + + template + const_iterator find(const K& key, std::size_t hash) const { + auto it_bucket = find_key(key, hash); + return (it_bucket != m_buckets_data.cend())?const_iterator(m_values.begin() + it_bucket->index()):end(); + } + + + template + std::pair equal_range(const K& key) { + return equal_range(key, hash_key(key)); + } + + template + std::pair equal_range(const K& key, std::size_t hash) { + iterator it = find(key, hash); + return std::make_pair(it, (it == end())?it:std::next(it)); + } + + template + std::pair equal_range(const K& key) const { + return equal_range(key, hash_key(key)); + } + + template + std::pair equal_range(const K& key, std::size_t hash) const { + const_iterator it = find(key, hash); + return std::make_pair(it, (it == cend())?it:std::next(it)); + } + + + /* + * Bucket interface + */ + size_type bucket_count() const { + return m_buckets_data.size(); + } + + size_type max_bucket_count() const { + return m_buckets_data.max_size(); + } + + /* + * Hash policy + */ + float load_factor() const { + if(bucket_count() == 0) { + return 0; + } + + return float(size())/float(bucket_count()); + } + + float max_load_factor() const { + return m_max_load_factor; + } + + void max_load_factor(float ml) { + m_max_load_factor = std::max(0.1f, std::min(ml, 0.95f)); + m_load_threshold = size_type(double(float(bucket_count()))*m_max_load_factor); + } + + void rehash(size_type count) { + count = std::max(count, size_type(std::ceil(float(size())/max_load_factor()))); + rehash_impl(count); + } + + void reserve(size_type count) { + reserve_space_for_values(count); + + count = size_type(std::ceil(float(count)/max_load_factor())); + rehash(count); + } + + + /* + * Observers + */ + hasher hash_function() const { + return static_cast(*this); + } + + key_equal key_eq() const { + return static_cast(*this); + } + + + /* + * Other + */ + iterator mutable_iterator(const_iterator pos) { + return iterator(m_values.begin() + iterator_to_index(pos)); + } + + iterator nth(size_type index) { + tsl_oh_assert(index <= size()); + return iterator(m_values.begin() + index); + } + + const_iterator nth(size_type index) const { + tsl_oh_assert(index <= size()); + return const_iterator(m_values.cbegin() + index); + } + + const_reference front() const { + tsl_oh_assert(!empty()); + return m_values.front(); + } + + const_reference back() const { + tsl_oh_assert(!empty()); + return m_values.back(); + } + + const values_container_type& values_container() const noexcept { + return m_values; + } + + template::value>::type* = nullptr> + const typename values_container_type::value_type* data() const noexcept { + return m_values.data(); + } + + template::value>::type* = nullptr> + size_type capacity() const noexcept { + return m_values.capacity(); + } + + void shrink_to_fit() { + m_values.shrink_to_fit(); + } + + + template + std::pair insert_at_position(const_iterator pos, P&& value) { + return insert_at_position_impl(pos.m_iterator, KeySelect()(value), std::forward

(value)); + } + + template + std::pair emplace_at_position(const_iterator pos, Args&&... args) { + return insert_at_position(pos, value_type(std::forward(args)...)); + } + + template + std::pair try_emplace_at_position(const_iterator pos, K&& key, Args&&... value_args) { + return insert_at_position_impl(pos.m_iterator, key, + std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(value_args)...)); + } + + + void pop_back() { + tsl_oh_assert(!empty()); + erase(std::prev(end())); + } + + + /** + * Here to avoid `template size_type unordered_erase(const K& key)` being used when + * we use a iterator instead of a const_iterator. + */ + iterator unordered_erase(iterator pos) { + return unordered_erase(const_iterator(pos)); + } + + iterator unordered_erase(const_iterator pos) { + const std::size_t index_erase = iterator_to_index(pos); + unordered_erase(pos.key()); + + /* + * One element was deleted, index_erase now points to the next element as the elements after + * the deleted value were shifted to the left in m_values (will be end() if we deleted the last element). + */ + return begin() + index_erase; + } + + template + size_type unordered_erase(const K& key) { + return unordered_erase(key, hash_key(key)); + } + + template + size_type unordered_erase(const K& key, std::size_t hash) { + auto it_bucket_key = find_key(key, hash); + if(it_bucket_key == m_buckets_data.end()) { + return 0; + } + + /** + * If we are not erasing the last element in m_values, we swap + * the element we are erasing with the last element. We then would + * just have to do a pop_back() in m_values. + */ + if(!compare_keys(key, KeySelect()(back()))) { + auto it_bucket_last_elem = find_key(KeySelect()(back()), hash_key(KeySelect()(back()))); + tsl_oh_assert(it_bucket_last_elem != m_buckets_data.end()); + tsl_oh_assert(it_bucket_last_elem->index() == m_values.size() - 1); + + using std::swap; + swap(m_values[it_bucket_key->index()], m_values[it_bucket_last_elem->index()]); + swap(it_bucket_key->index_ref(), it_bucket_last_elem->index_ref()); + } + + erase_value_from_bucket(it_bucket_key); + + return 1; + } + + template + void serialize(Serializer& serializer) const { + serialize_impl(serializer); + } + + template + void deserialize(Deserializer& deserializer, bool hash_compatible) { + deserialize_impl(deserializer, hash_compatible); + } + + friend bool operator==(const ordered_hash& lhs, const ordered_hash& rhs) { + return lhs.m_values == rhs.m_values; + } + + friend bool operator!=(const ordered_hash& lhs, const ordered_hash& rhs) { + return lhs.m_values != rhs.m_values; + } + + friend bool operator<(const ordered_hash& lhs, const ordered_hash& rhs) { + return lhs.m_values < rhs.m_values; + } + + friend bool operator<=(const ordered_hash& lhs, const ordered_hash& rhs) { + return lhs.m_values <= rhs.m_values; + } + + friend bool operator>(const ordered_hash& lhs, const ordered_hash& rhs) { + return lhs.m_values > rhs.m_values; + } + + friend bool operator>=(const ordered_hash& lhs, const ordered_hash& rhs) { + return lhs.m_values >= rhs.m_values; + } + + +private: + template + std::size_t hash_key(const K& key) const { + return Hash::operator()(key); + } + + template + bool compare_keys(const K1& key1, const K2& key2) const { + return KeyEqual::operator()(key1, key2); + } + + template + typename buckets_container_type::iterator find_key(const K& key, std::size_t hash) { + auto it = static_cast(this)->find_key(key, hash); + return m_buckets_data.begin() + std::distance(m_buckets_data.cbegin(), it); + } + + /** + * Return bucket which has the key 'key' or m_buckets_data.end() if none. + * + * From the bucket_for_hash, search for the value until we either find an empty bucket + * or a bucket which has a value with a distance from its ideal bucket longer + * than the probe length for the value we are looking for. + */ + template + typename buckets_container_type::const_iterator find_key(const K& key, std::size_t hash) const { + for(std::size_t ibucket = bucket_for_hash(hash), dist_from_ideal_bucket = 0; ; + ibucket = next_bucket(ibucket), dist_from_ideal_bucket++) + { + if(m_buckets[ibucket].empty()) { + return m_buckets_data.end(); + } + else if(m_buckets[ibucket].truncated_hash() == bucket_entry::truncate_hash(hash) && + compare_keys(key, KeySelect()(m_values[m_buckets[ibucket].index()]))) + { + return m_buckets_data.begin() + ibucket; + } + else if(dist_from_ideal_bucket > distance_from_ideal_bucket(ibucket)) { + return m_buckets_data.end(); + } + } + } + + void rehash_impl(size_type bucket_count) { + tsl_oh_assert(bucket_count >= size_type(std::ceil(float(size())/max_load_factor()))); + + if(bucket_count > max_bucket_count()) { + TSL_OH_THROW_OR_TERMINATE(std::length_error, "The map exceeds its maxmimum size."); + } + + if(bucket_count > 0) { + bucket_count = round_up_to_power_of_two(bucket_count); + } + + if(bucket_count == this->bucket_count()) { + return; + } + + + buckets_container_type old_buckets(bucket_count); + m_buckets_data.swap(old_buckets); + m_buckets = m_buckets_data.empty()?static_empty_bucket_ptr(): + m_buckets_data.data(); + // Everything should be noexcept from here. + + m_mask = (bucket_count > 0)?(bucket_count - 1):0; + this->max_load_factor(m_max_load_factor); + m_grow_on_next_insert = false; + + + + for(const bucket_entry& old_bucket: old_buckets) { + if(old_bucket.empty()) { + continue; + } + + truncated_hash_type insert_hash = old_bucket.truncated_hash(); + index_type insert_index = old_bucket.index(); + + for(std::size_t ibucket = bucket_for_hash(insert_hash), dist_from_ideal_bucket = 0; ; + ibucket = next_bucket(ibucket), dist_from_ideal_bucket++) + { + if(m_buckets[ibucket].empty()) { + m_buckets[ibucket].set_index(insert_index); + m_buckets[ibucket].set_hash(insert_hash); + break; + } + + const std::size_t distance = distance_from_ideal_bucket(ibucket); + if(dist_from_ideal_bucket > distance) { + std::swap(insert_index, m_buckets[ibucket].index_ref()); + std::swap(insert_hash, m_buckets[ibucket].truncated_hash_ref()); + dist_from_ideal_bucket = distance; + } + } + } + } + + template::value>::type* = nullptr> + void reserve_space_for_values(size_type count) { + m_values.reserve(count); + } + + template::value>::type* = nullptr> + void reserve_space_for_values(size_type /*count*/) { + } + + /** + * Swap the empty bucket with the values on its right until we cross another empty bucket + * or if the other bucket has a distance_from_ideal_bucket == 0. + */ + void backward_shift(std::size_t empty_ibucket) noexcept { + tsl_oh_assert(m_buckets[empty_ibucket].empty()); + + std::size_t previous_ibucket = empty_ibucket; + for(std::size_t current_ibucket = next_bucket(previous_ibucket); + !m_buckets[current_ibucket].empty() && distance_from_ideal_bucket(current_ibucket) > 0; + previous_ibucket = current_ibucket, current_ibucket = next_bucket(current_ibucket)) + { + std::swap(m_buckets[current_ibucket], m_buckets[previous_ibucket]); + } + } + + void erase_value_from_bucket(typename buckets_container_type::iterator it_bucket) { + tsl_oh_assert(it_bucket != m_buckets_data.end() && !it_bucket->empty()); + + m_values.erase(m_values.begin() + it_bucket->index()); + + /* + * m_values.erase shifted all the values on the right of the erased value, + * shift the indexes by -1 in the buckets array for these values. + */ + if(it_bucket->index() != m_values.size()) { + shift_indexes_in_buckets(it_bucket->index(), -1); + } + + // Mark the bucket as empty and do a backward shift of the values on the right + it_bucket->clear(); + backward_shift(std::size_t(std::distance(m_buckets_data.begin(), it_bucket))); + } + + /** + * Go through each value from [from_ivalue, m_values.size()) in m_values and for each + * bucket corresponding to the value, shift the index by delta. + * + * delta must be equal to 1 or -1. + */ + void shift_indexes_in_buckets(index_type from_ivalue, int delta) noexcept { + tsl_oh_assert(delta == 1 || delta == -1); + + for(std::size_t ivalue = from_ivalue; ivalue < m_values.size(); ivalue++) { + // All the values in m_values have been shifted by delta. Find the bucket corresponding + // to the value m_values[ivalue] + const index_type old_index = static_cast(ivalue - delta); + + std::size_t ibucket = bucket_for_hash(hash_key(KeySelect()(m_values[ivalue]))); + while(m_buckets[ibucket].index() != old_index) { + ibucket = next_bucket(ibucket); + } + + m_buckets[ibucket].set_index(index_type(ivalue)); + } + } + + template + size_type erase_impl(const K& key, std::size_t hash) { + auto it_bucket = find_key(key, hash); + if(it_bucket != m_buckets_data.end()) { + erase_value_from_bucket(it_bucket); + + return 1; + } + else { + return 0; + } + } + + /** + * Insert the element at the end. + */ + template + std::pair insert_impl(const K& key, Args&&... value_type_args) { + const std::size_t hash = hash_key(key); + + std::size_t ibucket = bucket_for_hash(hash); + std::size_t dist_from_ideal_bucket = 0; + + while(!m_buckets[ibucket].empty() && dist_from_ideal_bucket <= distance_from_ideal_bucket(ibucket)) { + if(m_buckets[ibucket].truncated_hash() == bucket_entry::truncate_hash(hash) && + compare_keys(key, KeySelect()(m_values[m_buckets[ibucket].index()]))) + { + return std::make_pair(begin() + m_buckets[ibucket].index(), false); + } + + ibucket = next_bucket(ibucket); + dist_from_ideal_bucket++; + } + + if(size() >= max_size()) { + TSL_OH_THROW_OR_TERMINATE(std::length_error, "We reached the maximum size for the hash table."); + } + + + if(grow_on_high_load()) { + ibucket = bucket_for_hash(hash); + dist_from_ideal_bucket = 0; + } + + + m_values.emplace_back(std::forward(value_type_args)...); + insert_index(ibucket, dist_from_ideal_bucket, + index_type(m_values.size() - 1), bucket_entry::truncate_hash(hash)); + + + return std::make_pair(std::prev(end()), true); + } + + /** + * Insert the element before insert_position. + */ + template + std::pair insert_at_position_impl(typename values_container_type::const_iterator insert_position, + const K& key, Args&&... value_type_args) + { + const std::size_t hash = hash_key(key); + + std::size_t ibucket = bucket_for_hash(hash); + std::size_t dist_from_ideal_bucket = 0; + + while(!m_buckets[ibucket].empty() && dist_from_ideal_bucket <= distance_from_ideal_bucket(ibucket)) { + if(m_buckets[ibucket].truncated_hash() == bucket_entry::truncate_hash(hash) && + compare_keys(key, KeySelect()(m_values[m_buckets[ibucket].index()]))) + { + return std::make_pair(begin() + m_buckets[ibucket].index(), false); + } + + ibucket = next_bucket(ibucket); + dist_from_ideal_bucket++; + } + + if(size() >= max_size()) { + TSL_OH_THROW_OR_TERMINATE(std::length_error, "We reached the maximum size for the hash table."); + } + + + if(grow_on_high_load()) { + ibucket = bucket_for_hash(hash); + dist_from_ideal_bucket = 0; + } + + + const index_type index_insert_position = index_type(std::distance(m_values.cbegin(), insert_position)); + +#ifdef TSL_OH_NO_CONTAINER_EMPLACE_CONST_ITERATOR + m_values.emplace(m_values.begin() + std::distance(m_values.cbegin(), insert_position), std::forward(value_type_args)...); +#else + m_values.emplace(insert_position, std::forward(value_type_args)...); +#endif + + insert_index(ibucket, dist_from_ideal_bucket, + index_insert_position, bucket_entry::truncate_hash(hash)); + + /* + * The insertion didn't happend at the end of the m_values container, + * we need to shift the indexes in m_buckets_data. + */ + if(index_insert_position != m_values.size() - 1) { + shift_indexes_in_buckets(index_insert_position + 1, 1); + } + + return std::make_pair(iterator(m_values.begin() + index_insert_position), true); + } + + void insert_index(std::size_t ibucket, std::size_t dist_from_ideal_bucket, + index_type index_insert, truncated_hash_type hash_insert) noexcept + { + while(!m_buckets[ibucket].empty()) { + const std::size_t distance = distance_from_ideal_bucket(ibucket); + if(dist_from_ideal_bucket > distance) { + std::swap(index_insert, m_buckets[ibucket].index_ref()); + std::swap(hash_insert, m_buckets[ibucket].truncated_hash_ref()); + + dist_from_ideal_bucket = distance; + } + + + ibucket = next_bucket(ibucket); + dist_from_ideal_bucket++; + + + if(dist_from_ideal_bucket > REHASH_ON_HIGH_NB_PROBES__NPROBES && !m_grow_on_next_insert && + load_factor() >= REHASH_ON_HIGH_NB_PROBES__MIN_LOAD_FACTOR) + { + // We don't want to grow the map now as we need this method to be noexcept. + // Do it on next insert. + m_grow_on_next_insert = true; + } + } + + + m_buckets[ibucket].set_index(index_insert); + m_buckets[ibucket].set_hash(hash_insert); + } + + std::size_t distance_from_ideal_bucket(std::size_t ibucket) const noexcept { + const std::size_t ideal_bucket = bucket_for_hash(m_buckets[ibucket].truncated_hash()); + + if(ibucket >= ideal_bucket) { + return ibucket - ideal_bucket; + } + // If the bucket is smaller than the ideal bucket for the value, there was a wrapping at the end of the + // bucket array due to the modulo. + else { + return (bucket_count() + ibucket) - ideal_bucket; + } + } + + std::size_t next_bucket(std::size_t index) const noexcept { + tsl_oh_assert(index < m_buckets_data.size()); + + index++; + return (index < m_buckets_data.size())?index:0; + } + + std::size_t bucket_for_hash(std::size_t hash) const noexcept { + return hash & m_mask; + } + + std::size_t iterator_to_index(const_iterator it) const noexcept { + const auto dist = std::distance(cbegin(), it); + tsl_oh_assert(dist >= 0); + + return std::size_t(dist); + } + + /** + * Return true if the map has been rehashed. + */ + bool grow_on_high_load() { + if(m_grow_on_next_insert || size() >= m_load_threshold) { + rehash_impl(std::max(size_type(1), bucket_count() * 2)); + m_grow_on_next_insert = false; + + return true; + } + else { + return false; + } + } + + template + void serialize_impl(Serializer& serializer) const { + const slz_size_type version = SERIALIZATION_PROTOCOL_VERSION; + serializer(version); + + const slz_size_type nb_elements = m_values.size(); + serializer(nb_elements); + + const slz_size_type bucket_count = m_buckets_data.size(); + serializer(bucket_count); + + const float max_load_factor = m_max_load_factor; + serializer(max_load_factor); + + + for(const value_type& value: m_values) { + serializer(value); + } + + for(const bucket_entry& bucket: m_buckets_data) { + bucket.serialize(serializer); + } + } + + template + void deserialize_impl(Deserializer& deserializer, bool hash_compatible) { + tsl_oh_assert(m_buckets_data.empty()); // Current hash table must be empty + + const slz_size_type version = deserialize_value(deserializer); + // For now we only have one version of the serialization protocol. + // If it doesn't match there is a problem with the file. + if(version != SERIALIZATION_PROTOCOL_VERSION) { + TSL_OH_THROW_OR_TERMINATE(std::runtime_error, "Can't deserialize the ordered_map/set. " + "The protocol version header is invalid."); + } + + const slz_size_type nb_elements = deserialize_value(deserializer); + const slz_size_type bucket_count_ds = deserialize_value(deserializer); + const float max_load_factor = deserialize_value(deserializer); + + + this->max_load_factor(max_load_factor); + + if(bucket_count_ds == 0) { + tsl_oh_assert(nb_elements == 0); + return; + } + + + if(!hash_compatible) { + reserve(numeric_cast(nb_elements, "Deserialized nb_elements is too big.")); + for(slz_size_type el = 0; el < nb_elements; el++) { + insert(deserialize_value(deserializer)); + } + } + else { + m_buckets_data.reserve(numeric_cast(bucket_count_ds, "Deserialized bucket_count is too big.")); + m_buckets = m_buckets_data.data(), + m_mask = m_buckets_data.capacity() - 1; + + reserve_space_for_values(numeric_cast(nb_elements, "Deserialized nb_elements is too big.")); + for(slz_size_type el = 0; el < nb_elements; el++) { + m_values.push_back(deserialize_value(deserializer)); + } + + for(slz_size_type b = 0; b < bucket_count_ds; b++) { + m_buckets_data.push_back(bucket_entry::deserialize(deserializer)); + } + + if(load_factor() > this->max_load_factor()) { + TSL_OH_THROW_OR_TERMINATE(std::runtime_error, "Invalid max_load_factor. Check that the serializer " + "and deserializer supports floats correctly as they " + "can be converted implicitely to ints."); + } + } + } + + static std::size_t round_up_to_power_of_two(std::size_t value) { + if(is_power_of_two(value)) { + return value; + } + + if(value == 0) { + return 1; + } + + --value; + for(std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) { + value |= value >> i; + } + + return value + 1; + } + + static constexpr bool is_power_of_two(std::size_t value) { + return value != 0 && (value & (value - 1)) == 0; + } + + +public: + static const size_type DEFAULT_INIT_BUCKETS_SIZE = 0; + static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.75f; + +private: + static const size_type REHASH_ON_HIGH_NB_PROBES__NPROBES = 128; + static constexpr float REHASH_ON_HIGH_NB_PROBES__MIN_LOAD_FACTOR = 0.15f; + + /** + * Protocol version currenlty used for serialization. + */ + static const slz_size_type SERIALIZATION_PROTOCOL_VERSION = 1; + + /** + * Return an always valid pointer to an static empty bucket_entry with last_bucket() == true. + */ + bucket_entry* static_empty_bucket_ptr() { + static bucket_entry empty_bucket; + return &empty_bucket; + } + +private: + buckets_container_type m_buckets_data; + + /** + * Points to m_buckets_data.data() if !m_buckets_data.empty() otherwise points to static_empty_bucket_ptr. + * This variable is useful to avoid the cost of checking if m_buckets_data is empty when trying + * to find an element. + * + * TODO Remove m_buckets_data and only use a pointer+size instead of a pointer+vector to save some space in the ordered_hash object. + */ + bucket_entry* m_buckets; + + size_type m_mask; + + values_container_type m_values; + + bool m_grow_on_next_insert; + float m_max_load_factor; + size_type m_load_threshold; +}; + + +} // end namespace detail_ordered_hash + +} // end namespace tsl + +#endif diff --git a/src/tessil/ordered_map.h b/src/tessil/ordered_map.h new file mode 100644 index 0000000000..ea21283d1e --- /dev/null +++ b/src/tessil/ordered_map.h @@ -0,0 +1,833 @@ +/** + * MIT License + * + * Copyright (c) 2017 Tessil + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_ORDERED_MAP_H +#define TSL_ORDERED_MAP_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ordered_hash.h" + + +namespace tsl { + + +/** + * Implementation of an hash map using open adressing with robin hood with backshift delete to resolve collisions. + * + * The particularity of this hash map is that it remembers the order in which the elements were added and + * provide a way to access the structure which stores these values through the 'values_container()' method. + * The used container is defined by ValueTypeContainer, by default a std::deque is used (grows faster) but + * a std::vector may be used. In this case the map provides a 'data()' method which give a direct access + * to the memory used to store the values (which can be usefull to communicate with C API's). + * + * The Key and T must be copy constructible and/or move constructible. To use `unordered_erase` they both + * must be swappable. + * + * The behaviour of the hash map is undefinded if the destructor of Key or T throws an exception. + * + * By default the maximum size of a map is limited to 2^32 - 1 values, if needed this can be changed through + * the IndexType template parameter. Using an `uint64_t` will raise this limit to 2^64 - 1 values but each + * bucket will use 16 bytes instead of 8 bytes in addition to the space needed to store the values. + * + * Iterators invalidation: + * - clear, operator=, reserve, rehash: always invalidate the iterators (also invalidate end()). + * - insert, emplace, emplace_hint, operator[]: when a std::vector is used as ValueTypeContainer + * and if size() < capacity(), only end(). + * Otherwise all the iterators are invalidated if an insert occurs. + * - erase, unordered_erase: when a std::vector is used as ValueTypeContainer invalidate the iterator of + * the erased element and all the ones after the erased element (including end()). + * Otherwise all the iterators are invalidated if an erase occurs. + */ +template, + class KeyEqual = std::equal_to, + class Allocator = std::allocator>, + class ValueTypeContainer = std::vector, Allocator>, + class IndexType = std::uint_least32_t> +class ordered_map { +private: + template + using has_is_transparent = tsl::detail_ordered_hash::has_is_transparent; + + class KeySelect { + public: + using key_type = Key; + + const key_type& operator()(const std::pair& key_value) const noexcept { + return key_value.first; + } + + key_type& operator()(std::pair& key_value) noexcept { + return key_value.first; + } + }; + + class ValueSelect { + public: + using value_type = T; + + const value_type& operator()(const std::pair& key_value) const noexcept { + return key_value.second; + } + + value_type& operator()(std::pair& key_value) noexcept { + return key_value.second; + } + }; + + using ht = detail_ordered_hash::ordered_hash, KeySelect, ValueSelect, + Hash, KeyEqual, Allocator, ValueTypeContainer, IndexType>; + +public: + using key_type = typename ht::key_type; + using mapped_type = T; + using value_type = typename ht::value_type; + using size_type = typename ht::size_type; + using difference_type = typename ht::difference_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using allocator_type = typename ht::allocator_type; + using reference = typename ht::reference; + using const_reference = typename ht::const_reference; + using pointer = typename ht::pointer; + using const_pointer = typename ht::const_pointer; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + using reverse_iterator = typename ht::reverse_iterator; + using const_reverse_iterator = typename ht::const_reverse_iterator; + + using values_container_type = typename ht::values_container_type; + + + /* + * Constructors + */ + ordered_map(): ordered_map(ht::DEFAULT_INIT_BUCKETS_SIZE) { + } + + explicit ordered_map(size_type bucket_count, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()): + m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) + { + } + + ordered_map(size_type bucket_count, + const Allocator& alloc): ordered_map(bucket_count, Hash(), KeyEqual(), alloc) + { + } + + ordered_map(size_type bucket_count, + const Hash& hash, + const Allocator& alloc): ordered_map(bucket_count, hash, KeyEqual(), alloc) + { + } + + explicit ordered_map(const Allocator& alloc): ordered_map(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { + } + + template + ordered_map(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()): ordered_map(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + + template + ordered_map(InputIt first, InputIt last, + size_type bucket_count, + const Allocator& alloc): ordered_map(first, last, bucket_count, Hash(), KeyEqual(), alloc) + { + } + + template + ordered_map(InputIt first, InputIt last, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc): ordered_map(first, last, bucket_count, hash, KeyEqual(), alloc) + { + } + + ordered_map(std::initializer_list init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()): + ordered_map(init.begin(), init.end(), bucket_count, hash, equal, alloc) + { + } + + ordered_map(std::initializer_list init, + size_type bucket_count, + const Allocator& alloc): + ordered_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) + { + } + + ordered_map(std::initializer_list init, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc): + ordered_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) + { + } + + + ordered_map& operator=(std::initializer_list ilist) { + m_ht.clear(); + + m_ht.reserve(ilist.size()); + m_ht.insert(ilist.begin(), ilist.end()); + + return *this; + } + + allocator_type get_allocator() const { return m_ht.get_allocator(); } + + + + /* + * Iterators + */ + iterator begin() noexcept { return m_ht.begin(); } + const_iterator begin() const noexcept { return m_ht.begin(); } + const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + + iterator end() noexcept { return m_ht.end(); } + const_iterator end() const noexcept { return m_ht.end(); } + const_iterator cend() const noexcept { return m_ht.cend(); } + + reverse_iterator rbegin() noexcept { return m_ht.rbegin(); } + const_reverse_iterator rbegin() const noexcept { return m_ht.rbegin(); } + const_reverse_iterator rcbegin() const noexcept { return m_ht.rcbegin(); } + + reverse_iterator rend() noexcept { return m_ht.rend(); } + const_reverse_iterator rend() const noexcept { return m_ht.rend(); } + const_reverse_iterator rcend() const noexcept { return m_ht.rcend(); } + + + /* + * Capacity + */ + bool empty() const noexcept { return m_ht.empty(); } + size_type size() const noexcept { return m_ht.size(); } + size_type max_size() const noexcept { return m_ht.max_size(); } + + /* + * Modifiers + */ + void clear() noexcept { m_ht.clear(); } + + + + std::pair insert(const value_type& value) { return m_ht.insert(value); } + + template::value>::type* = nullptr> + std::pair insert(P&& value) { return m_ht.emplace(std::forward

(value)); } + + std::pair insert(value_type&& value) { return m_ht.insert(std::move(value)); } + + + iterator insert(const_iterator hint, const value_type& value) { + return m_ht.insert_hint(hint, value); + } + + template::value>::type* = nullptr> + iterator insert(const_iterator hint, P&& value) { + return m_ht.emplace_hint(hint, std::forward

(value)); + } + + iterator insert(const_iterator hint, value_type&& value) { + return m_ht.insert_hint(hint, std::move(value)); + } + + + template + void insert(InputIt first, InputIt last) { m_ht.insert(first, last); } + void insert(std::initializer_list ilist) { m_ht.insert(ilist.begin(), ilist.end()); } + + + + + template + std::pair insert_or_assign(const key_type& k, M&& obj) { + return m_ht.insert_or_assign(k, std::forward(obj)); + } + + template + std::pair insert_or_assign(key_type&& k, M&& obj) { + return m_ht.insert_or_assign(std::move(k), std::forward(obj)); + } + + + template + iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) { + return m_ht.insert_or_assign(hint, k, std::forward(obj)); + } + + template + iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) { + return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); + } + + /** + * Due to the way elements are stored, emplace will need to move or copy the key-value once. + * The method is equivalent to insert(value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + std::pair emplace(Args&&... args) { return m_ht.emplace(std::forward(args)...); } + + /** + * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. + * The method is equivalent to insert(hint, value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + iterator emplace_hint(const_iterator hint, Args&&... args) { + return m_ht.emplace_hint(hint, std::forward(args)...); + } + + + + + template + std::pair try_emplace(const key_type& k, Args&&... args) { + return m_ht.try_emplace(k, std::forward(args)...); + } + + template + std::pair try_emplace(key_type&& k, Args&&... args) { + return m_ht.try_emplace(std::move(k), std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) { + return m_ht.try_emplace_hint(hint, k, std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) { + return m_ht.try_emplace_hint(hint, std::move(k), std::forward(args)...); + } + + + + + /** + * When erasing an element, the insert order will be preserved and no holes will be present in the container + * returned by 'values_container()'. + * + * The method is in O(n), if the order is not important 'unordered_erase(...)' method is faster with an O(1) + * average complexity. + */ + iterator erase(iterator pos) { return m_ht.erase(pos); } + + /** + * @copydoc erase(iterator pos) + */ + iterator erase(const_iterator pos) { return m_ht.erase(pos); } + + /** + * @copydoc erase(iterator pos) + */ + iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } + + /** + * @copydoc erase(iterator pos) + */ + size_type erase(const key_type& key) { return m_ht.erase(key); } + + /** + * @copydoc erase(iterator pos) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. + */ + size_type erase(const key_type& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + /** + * @copydoc erase(iterator pos) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type erase(const K& key) { return m_ht.erase(key); } + + /** + * @copydoc erase(const key_type& key, std::size_t precalculated_hash) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type erase(const K& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + + + void swap(ordered_map& other) { other.m_ht.swap(m_ht); } + + /* + * Lookup + */ + T& at(const Key& key) { return m_ht.at(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + T& at(const Key& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } + + + const T& at(const Key& key) const { return m_ht.at(key); } + + /** + * @copydoc at(const Key& key, std::size_t precalculated_hash) + */ + const T& at(const Key& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } + + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + T& at(const K& key) { return m_ht.at(key); } + + /** + * @copydoc at(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + T& at(const K& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } + + /** + * @copydoc at(const K& key) + */ + template::value>::type* = nullptr> + const T& at(const K& key) const { return m_ht.at(key); } + + /** + * @copydoc at(const K& key, std::size_t precalculated_hash) + */ + template::value>::type* = nullptr> + const T& at(const K& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } + + + + T& operator[](const Key& key) { return m_ht[key]; } + T& operator[](Key&& key) { return m_ht[std::move(key)]; } + + + + size_type count(const Key& key) const { return m_ht.count(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + size_type count(const Key& key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type count(const K& key) const { return m_ht.count(key); } + + /** + * @copydoc count(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + size_type count(const K& key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + + + iterator find(const Key& key) { return m_ht.find(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + const_iterator find(const Key& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const Key& key, std::size_t precalculated_hash) + */ + const_iterator find(const Key& key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + iterator find(const K& key) { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + /** + * @copydoc find(const K& key) + */ + template::value>::type* = nullptr> + const_iterator find(const K& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + const_iterator find(const K& key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + + + std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * @copydoc equal_range(const K& key) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key, std::size_t precalculated_hash) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + + + /* + * Bucket interface + */ + size_type bucket_count() const { return m_ht.bucket_count(); } + size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + + + /* + * Hash policy + */ + float load_factor() const { return m_ht.load_factor(); } + float max_load_factor() const { return m_ht.max_load_factor(); } + void max_load_factor(float ml) { m_ht.max_load_factor(ml); } + + void rehash(size_type count) { m_ht.rehash(count); } + void reserve(size_type count) { m_ht.reserve(count); } + + + /* + * Observers + */ + hasher hash_function() const { return m_ht.hash_function(); } + key_equal key_eq() const { return m_ht.key_eq(); } + + + + /* + * Other + */ + + /** + * Convert a const_iterator to an iterator. + */ + iterator mutable_iterator(const_iterator pos) { + return m_ht.mutable_iterator(pos); + } + + /** + * Requires index <= size(). + * + * Return an iterator to the element at index. Return end() if index == size(). + */ + iterator nth(size_type index) { return m_ht.nth(index); } + + /** + * @copydoc nth(size_type index) + */ + const_iterator nth(size_type index) const { return m_ht.nth(index); } + + + /** + * Return const_reference to the first element. Requires the container to not be empty. + */ + const_reference front() const { return m_ht.front(); } + + /** + * Return const_reference to the last element. Requires the container to not be empty. + */ + const_reference back() const { return m_ht.back(); } + + + /** + * Only available if ValueTypeContainer is a std::vector. Same as calling 'values_container().data()'. + */ + template::value>::type* = nullptr> + const typename values_container_type::value_type* data() const noexcept { return m_ht.data(); } + + /** + * Return the container in which the values are stored. The values are in the same order as the insertion order + * and are contiguous in the structure, no holes (size() == values_container().size()). + */ + const values_container_type& values_container() const noexcept { return m_ht.values_container(); } + + template::value>::type* = nullptr> + size_type capacity() const noexcept { return m_ht.capacity(); } + + void shrink_to_fit() { m_ht.shrink_to_fit(); } + + + + /** + * Insert the value before pos shifting all the elements on the right of pos (including pos) one position + * to the right. + * + * Amortized linear time-complexity in the distance between pos and end(). + */ + std::pair insert_at_position(const_iterator pos, const value_type& value) { + return m_ht.insert_at_position(pos, value); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + */ + std::pair insert_at_position(const_iterator pos, value_type&& value) { + return m_ht.insert_at_position(pos, std::move(value)); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + * + * Same as insert_at_position(pos, value_type(std::forward(args)...), mainly + * here for coherence. + */ + template + std::pair emplace_at_position(const_iterator pos, Args&&... args) { + return m_ht.emplace_at_position(pos, std::forward(args)...); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + */ + template + std::pair try_emplace_at_position(const_iterator pos, const key_type& k, Args&&... args) { + return m_ht.try_emplace_at_position(pos, k, std::forward(args)...); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + */ + template + std::pair try_emplace_at_position(const_iterator pos, key_type&& k, Args&&... args) { + return m_ht.try_emplace_at_position(pos, std::move(k), std::forward(args)...); + } + + + + void pop_back() { m_ht.pop_back(); } + + /** + * Faster erase operation with an O(1) average complexity but it doesn't preserve the insertion order. + * + * If an erasure occurs, the last element of the map will take the place of the erased element. + */ + iterator unordered_erase(iterator pos) { return m_ht.unordered_erase(pos); } + + /** + * @copydoc unordered_erase(iterator pos) + */ + iterator unordered_erase(const_iterator pos) { return m_ht.unordered_erase(pos); } + + /** + * @copydoc unordered_erase(iterator pos) + */ + size_type unordered_erase(const key_type& key) { return m_ht.unordered_erase(key); } + + /** + * @copydoc unordered_erase(iterator pos) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + size_type unordered_erase(const key_type& key, std::size_t precalculated_hash) { + return m_ht.unordered_erase(key, precalculated_hash); + } + + /** + * @copydoc unordered_erase(iterator pos) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type unordered_erase(const K& key) { return m_ht.unordered_erase(key); } + + /** + * @copydoc unordered_erase(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + size_type unordered_erase(const K& key, std::size_t precalculated_hash) { + return m_ht.unordered_erase(key, precalculated_hash); + } + + /** + * Serialize the map through the `serializer` parameter. + * + * The `serializer` parameter must be a function object that supports the following call: + * - `template void operator()(const U& value);` where the types `std::uint64_t`, `float` and `std::pair` must be supported for U. + * + * The implementation leaves binary compatibilty (endianness, IEEE 754 for floats, ...) of the types it serializes + * in the hands of the `Serializer` function object if compatibilty is required. + */ + template + void serialize(Serializer& serializer) const { + m_ht.serialize(serializer); + } + + /** + * Deserialize a previouly serialized map through the `deserializer` parameter. + * + * The `deserializer` parameter must be a function object that supports the following calls: + * - `template U operator()();` where the types `std::uint64_t`, `float` and `std::pair` must be supported for U. + * + * If the deserialized hash map type is hash compatible with the serialized map, the deserialization process can be + * sped up by setting `hash_compatible` to true. To be hash compatible, the Hash and KeyEqual must behave the same way + * than the ones used on the serialized map. The `std::size_t` must also be of the same size as the one on the platform used + * to serialize the map, the same apply for `IndexType`. If these criteria are not met, the behaviour is undefined with + * `hash_compatible` sets to true. + * + * The behaviour is undefined if the type `Key` and `T` of the `ordered_map` are not the same as the + * types used during serialization. + * + * The implementation leaves binary compatibilty (endianness, IEEE 754 for floats, size of int, ...) of the types it + * deserializes in the hands of the `Deserializer` function object if compatibilty is required. + */ + template + static ordered_map deserialize(Deserializer& deserializer, bool hash_compatible = false) { + ordered_map map(0); + map.m_ht.deserialize(deserializer, hash_compatible); + + return map; + } + + + + friend bool operator==(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht == rhs.m_ht; } + friend bool operator!=(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht != rhs.m_ht; } + friend bool operator<(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht < rhs.m_ht; } + friend bool operator<=(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht <= rhs.m_ht; } + friend bool operator>(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht > rhs.m_ht; } + friend bool operator>=(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht >= rhs.m_ht; } + + friend void swap(ordered_map& lhs, ordered_map& rhs) { lhs.swap(rhs); } + +private: + ht m_ht; +}; + +} // end namespace tsl + +#endif diff --git a/src/tessil/ordered_set.h b/src/tessil/ordered_set.h new file mode 100644 index 0000000000..617cbbb57e --- /dev/null +++ b/src/tessil/ordered_set.h @@ -0,0 +1,688 @@ +/** + * MIT License + * + * Copyright (c) 2017 Tessil + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_ORDERED_SET_H +#define TSL_ORDERED_SET_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ordered_hash.h" + + +namespace tsl { + + +/** + * Implementation of an hash set using open adressing with robin hood with backshift delete to resolve collisions. + * + * The particularity of this hash set is that it remembers the order in which the elements were added and + * provide a way to access the structure which stores these values through the 'values_container()' method. + * The used container is defined by ValueTypeContainer, by default a std::deque is used (grows faster) but + * a std::vector may be used. In this case the set provides a 'data()' method which give a direct access + * to the memory used to store the values (which can be usefull to communicate with C API's). + * + * The Key must be copy constructible and/or move constructible. To use `unordered_erase` it also must be swappable. + * + * The behaviour of the hash set is undefinded if the destructor of Key throws an exception. + * + * By default the maximum size of a set is limited to 2^32 - 1 values, if needed this can be changed through + * the IndexType template parameter. Using an `uint64_t` will raise this limit to 2^64 - 1 values but each + * bucket will use 16 bytes instead of 8 bytes in addition to the space needed to store the values. + * + * Iterators invalidation: + * - clear, operator=, reserve, rehash: always invalidate the iterators (also invalidate end()). + * - insert, emplace, emplace_hint, operator[]: when a std::vector is used as ValueTypeContainer + * and if size() < capacity(), only end(). + * Otherwise all the iterators are invalidated if an insert occurs. + * - erase, unordered_erase: when a std::vector is used as ValueTypeContainer invalidate the iterator of + * the erased element and all the ones after the erased element (including end()). + * Otherwise all the iterators are invalidated if an erase occurs. + */ +template, + class KeyEqual = std::equal_to, + class Allocator = std::allocator, + class ValueTypeContainer = std::vector, + class IndexType = std::uint_least32_t> +class ordered_set { +private: + template + using has_is_transparent = tsl::detail_ordered_hash::has_is_transparent; + + class KeySelect { + public: + using key_type = Key; + + const key_type& operator()(const Key& key) const noexcept { + return key; + } + + key_type& operator()(Key& key) noexcept { + return key; + } + }; + + using ht = detail_ordered_hash::ordered_hash; + +public: + using key_type = typename ht::key_type; + using value_type = typename ht::value_type; + using size_type = typename ht::size_type; + using difference_type = typename ht::difference_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using allocator_type = typename ht::allocator_type; + using reference = typename ht::reference; + using const_reference = typename ht::const_reference; + using pointer = typename ht::pointer; + using const_pointer = typename ht::const_pointer; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + using reverse_iterator = typename ht::reverse_iterator; + using const_reverse_iterator = typename ht::const_reverse_iterator; + + using values_container_type = typename ht::values_container_type; + + + /* + * Constructors + */ + ordered_set(): ordered_set(ht::DEFAULT_INIT_BUCKETS_SIZE) { + } + + explicit ordered_set(size_type bucket_count, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()): + m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) + { + } + + ordered_set(size_type bucket_count, + const Allocator& alloc): ordered_set(bucket_count, Hash(), KeyEqual(), alloc) + { + } + + ordered_set(size_type bucket_count, + const Hash& hash, + const Allocator& alloc): ordered_set(bucket_count, hash, KeyEqual(), alloc) + { + } + + explicit ordered_set(const Allocator& alloc): ordered_set(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { + } + + template + ordered_set(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()): ordered_set(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + + template + ordered_set(InputIt first, InputIt last, + size_type bucket_count, + const Allocator& alloc): ordered_set(first, last, bucket_count, Hash(), KeyEqual(), alloc) + { + } + + template + ordered_set(InputIt first, InputIt last, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc): ordered_set(first, last, bucket_count, hash, KeyEqual(), alloc) + { + } + + ordered_set(std::initializer_list init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()): + ordered_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) + { + } + + ordered_set(std::initializer_list init, + size_type bucket_count, + const Allocator& alloc): + ordered_set(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) + { + } + + ordered_set(std::initializer_list init, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc): + ordered_set(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) + { + } + + + ordered_set& operator=(std::initializer_list ilist) { + m_ht.clear(); + + m_ht.reserve(ilist.size()); + m_ht.insert(ilist.begin(), ilist.end()); + + return *this; + } + + allocator_type get_allocator() const { return m_ht.get_allocator(); } + + + /* + * Iterators + */ + iterator begin() noexcept { return m_ht.begin(); } + const_iterator begin() const noexcept { return m_ht.begin(); } + const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + + iterator end() noexcept { return m_ht.end(); } + const_iterator end() const noexcept { return m_ht.end(); } + const_iterator cend() const noexcept { return m_ht.cend(); } + + reverse_iterator rbegin() noexcept { return m_ht.rbegin(); } + const_reverse_iterator rbegin() const noexcept { return m_ht.rbegin(); } + const_reverse_iterator rcbegin() const noexcept { return m_ht.rcbegin(); } + + reverse_iterator rend() noexcept { return m_ht.rend(); } + const_reverse_iterator rend() const noexcept { return m_ht.rend(); } + const_reverse_iterator rcend() const noexcept { return m_ht.rcend(); } + + + /* + * Capacity + */ + bool empty() const noexcept { return m_ht.empty(); } + size_type size() const noexcept { return m_ht.size(); } + size_type max_size() const noexcept { return m_ht.max_size(); } + + /* + * Modifiers + */ + void clear() noexcept { m_ht.clear(); } + + + + std::pair insert(const value_type& value) { return m_ht.insert(value); } + std::pair insert(value_type&& value) { return m_ht.insert(std::move(value)); } + + iterator insert(const_iterator hint, const value_type& value) { + return m_ht.insert_hint(hint, value); + } + + iterator insert(const_iterator hint, value_type&& value) { + return m_ht.insert_hint(hint, std::move(value)); + } + + template + void insert(InputIt first, InputIt last) { m_ht.insert(first, last); } + void insert(std::initializer_list ilist) { m_ht.insert(ilist.begin(), ilist.end()); } + + + + /** + * Due to the way elements are stored, emplace will need to move or copy the key-value once. + * The method is equivalent to insert(value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + std::pair emplace(Args&&... args) { return m_ht.emplace(std::forward(args)...); } + + /** + * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. + * The method is equivalent to insert(hint, value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + iterator emplace_hint(const_iterator hint, Args&&... args) { + return m_ht.emplace_hint(hint, std::forward(args)...); + } + + /** + * When erasing an element, the insert order will be preserved and no holes will be present in the container + * returned by 'values_container()'. + * + * The method is in O(n), if the order is not important 'unordered_erase(...)' method is faster with an O(1) + * average complexity. + */ + iterator erase(iterator pos) { return m_ht.erase(pos); } + + /** + * @copydoc erase(iterator pos) + */ + iterator erase(const_iterator pos) { return m_ht.erase(pos); } + + /** + * @copydoc erase(iterator pos) + */ + iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } + + /** + * @copydoc erase(iterator pos) + */ + size_type erase(const key_type& key) { return m_ht.erase(key); } + + /** + * @copydoc erase(iterator pos) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. + */ + size_type erase(const key_type& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + /** + * @copydoc erase(iterator pos) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type erase(const K& key) { return m_ht.erase(key); } + + /** + * @copydoc erase(const key_type& key, std::size_t precalculated_hash) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type erase(const K& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + + + void swap(ordered_set& other) { other.m_ht.swap(m_ht); } + + /* + * Lookup + */ + size_type count(const Key& key) const { return m_ht.count(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + size_type count(const Key& key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type count(const K& key) const { return m_ht.count(key); } + + /** + * @copydoc count(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + size_type count(const K& key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + + + + iterator find(const Key& key) { return m_ht.find(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + const_iterator find(const Key& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const Key& key, std::size_t precalculated_hash) + */ + const_iterator find(const Key& key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + iterator find(const K& key) { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + /** + * @copydoc find(const K& key) + */ + template::value>::type* = nullptr> + const_iterator find(const K& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + const_iterator find(const K& key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + + + std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * @copydoc equal_range(const K& key) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key, std::size_t precalculated_hash) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + + /* + * Bucket interface + */ + size_type bucket_count() const { return m_ht.bucket_count(); } + size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + + + /* + * Hash policy + */ + float load_factor() const { return m_ht.load_factor(); } + float max_load_factor() const { return m_ht.max_load_factor(); } + void max_load_factor(float ml) { m_ht.max_load_factor(ml); } + + void rehash(size_type count) { m_ht.rehash(count); } + void reserve(size_type count) { m_ht.reserve(count); } + + + /* + * Observers + */ + hasher hash_function() const { return m_ht.hash_function(); } + key_equal key_eq() const { return m_ht.key_eq(); } + + + /* + * Other + */ + + /** + * Convert a const_iterator to an iterator. + */ + iterator mutable_iterator(const_iterator pos) { + return m_ht.mutable_iterator(pos); + } + + /** + * Requires index <= size(). + * + * Return an iterator to the element at index. Return end() if index == size(). + */ + iterator nth(size_type index) { return m_ht.nth(index); } + + /** + * @copydoc nth(size_type index) + */ + const_iterator nth(size_type index) const { return m_ht.nth(index); } + + + /** + * Return const_reference to the first element. Requires the container to not be empty. + */ + const_reference front() const { return m_ht.front(); } + + /** + * Return const_reference to the last element. Requires the container to not be empty. + */ + const_reference back() const { return m_ht.back(); } + + + /** + * Only available if ValueTypeContainer is a std::vector. Same as calling 'values_container().data()'. + */ + template::value>::type* = nullptr> + const typename values_container_type::value_type* data() const noexcept { return m_ht.data(); } + + /** + * Return the container in which the values are stored. The values are in the same order as the insertion order + * and are contiguous in the structure, no holes (size() == values_container().size()). + */ + const values_container_type& values_container() const noexcept { return m_ht.values_container(); } + + template::value>::type* = nullptr> + size_type capacity() const noexcept { return m_ht.capacity(); } + + void shrink_to_fit() { m_ht.shrink_to_fit(); } + + + + /** + * Insert the value before pos shifting all the elements on the right of pos (including pos) one position + * to the right. + * + * Amortized linear time-complexity in the distance between pos and end(). + */ + std::pair insert_at_position(const_iterator pos, const value_type& value) { + return m_ht.insert_at_position(pos, value); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + */ + std::pair insert_at_position(const_iterator pos, value_type&& value) { + return m_ht.insert_at_position(pos, std::move(value)); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + * + * Same as insert_at_position(pos, value_type(std::forward(args)...), mainly + * here for coherence. + */ + template + std::pair emplace_at_position(const_iterator pos, Args&&... args) { + return m_ht.emplace_at_position(pos, std::forward(args)...); + } + + + + void pop_back() { m_ht.pop_back(); } + + /** + * Faster erase operation with an O(1) average complexity but it doesn't preserve the insertion order. + * + * If an erasure occurs, the last element of the map will take the place of the erased element. + */ + iterator unordered_erase(iterator pos) { return m_ht.unordered_erase(pos); } + + /** + * @copydoc unordered_erase(iterator pos) + */ + iterator unordered_erase(const_iterator pos) { return m_ht.unordered_erase(pos); } + + /** + * @copydoc unordered_erase(iterator pos) + */ + size_type unordered_erase(const key_type& key) { return m_ht.unordered_erase(key); } + + /** + * @copydoc unordered_erase(iterator pos) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + size_type unordered_erase(const key_type& key, std::size_t precalculated_hash) { + return m_ht.unordered_erase(key, precalculated_hash); + } + + /** + * @copydoc unordered_erase(iterator pos) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type unordered_erase(const K& key) { return m_ht.unordered_erase(key); } + + /** + * @copydoc unordered_erase(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + size_type unordered_erase(const K& key, std::size_t precalculated_hash) { + return m_ht.unordered_erase(key, precalculated_hash); + } + + /** + * Serialize the set through the `serializer` parameter. + * + * The `serializer` parameter must be a function object that supports the following call: + * - `void operator()(const U& value);` where the types `std::uint64_t`, `float` and `Key` must be supported for U. + * + * The implementation leaves binary compatibilty (endianness, IEEE 754 for floats, ...) of the types it serializes + * in the hands of the `Serializer` function object if compatibilty is required. + */ + template + void serialize(Serializer& serializer) const { + m_ht.serialize(serializer); + } + + /** + * Deserialize a previouly serialized set through the `deserializer` parameter. + * + * The `deserializer` parameter must be a function object that supports the following calls: + * - `template U operator()();` where the types `std::uint64_t`, `float` and `Key` must be supported for U. + * + * If the deserialized hash set type is hash compatible with the serialized set, the deserialization process can be + * sped up by setting `hash_compatible` to true. To be hash compatible, the Hash and KeyEqual must behave the same way + * than the ones used on the serialized map. The `std::size_t` must also be of the same size as the one on the platform used + * to serialize the map, the same apply for `IndexType`. If these criteria are not met, the behaviour is undefined with + * `hash_compatible` sets to true. + * + * The behaviour is undefined if the type `Key` of the `ordered_set` is not the same as the + * type used during serialization. + * + * The implementation leaves binary compatibilty (endianness, IEEE 754 for floats, size of int, ...) of the types it + * deserializes in the hands of the `Deserializer` function object if compatibilty is required. + */ + template + static ordered_set deserialize(Deserializer& deserializer, bool hash_compatible = false) { + ordered_set set(0); + set.m_ht.deserialize(deserializer, hash_compatible); + + return set; + } + + + + friend bool operator==(const ordered_set& lhs, const ordered_set& rhs) { return lhs.m_ht == rhs.m_ht; } + friend bool operator!=(const ordered_set& lhs, const ordered_set& rhs) { return lhs.m_ht != rhs.m_ht; } + friend bool operator<(const ordered_set& lhs, const ordered_set& rhs) { return lhs.m_ht < rhs.m_ht; } + friend bool operator<=(const ordered_set& lhs, const ordered_set& rhs) { return lhs.m_ht <= rhs.m_ht; } + friend bool operator>(const ordered_set& lhs, const ordered_set& rhs) { return lhs.m_ht > rhs.m_ht; } + friend bool operator>=(const ordered_set& lhs, const ordered_set& rhs) { return lhs.m_ht >= rhs.m_ht; } + + friend void swap(ordered_set& lhs, ordered_set& rhs) { lhs.swap(rhs); } + +private: + ht m_ht; +}; + +} // end namespace tsl + +#endif diff --git a/src/to_value.cpp b/src/to_value.cpp deleted file mode 100644 index fa2b174d97..0000000000 --- a/src/to_value.cpp +++ /dev/null @@ -1,114 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "ast.hpp" -#include "to_value.hpp" - -namespace Sass { - - // Custom_Error is a valid value - Value* To_Value::operator()(Custom_Error* e) - { - return e; - } - - // Custom_Warning is a valid value - Value* To_Value::operator()(Custom_Warning* w) - { - return w; - } - - // Boolean is a valid value - Value* To_Value::operator()(Boolean* b) - { - return b; - } - - // Number is a valid value - Value* To_Value::operator()(Number* n) - { - return n; - } - - // Color is a valid value - Value* To_Value::operator()(Color_RGBA* c) - { - return c; - } - - // Color is a valid value - Value* To_Value::operator()(Color_HSLA* c) - { - return c; - } - - // String_Constant is a valid value - Value* To_Value::operator()(String_Constant* s) - { - return s; - } - - // String_Quoted is a valid value - Value* To_Value::operator()(String_Quoted* s) - { - return s; - } - - // List is a valid value - Value* To_Value::operator()(List* l) - { - List_Obj ll = SASS_MEMORY_NEW(List, - l->pstate(), - l->length(), - l->separator(), - l->is_arglist(), - l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - ll->append((*l)[i]->perform(this)); - } - return ll.detach(); - } - - // Map is a valid value - Value* To_Value::operator()(Map* m) - { - return m; - } - - // Null is a valid value - Value* To_Value::operator()(Null* n) - { - return n; - } - - // Function is a valid value - Value* To_Value::operator()(Function* n) - { - return n; - } - - // Argument returns its value - Value* To_Value::operator()(Argument* arg) - { - if (!arg->name().empty()) return 0; - return arg->value()->perform(this); - } - - // SelectorList is converted to a string - Value* To_Value::operator()(SelectorList* s) - { - return SASS_MEMORY_NEW(String_Quoted, - s->pstate(), - s->to_string(ctx.c_options)); - } - - // Binary_Expression is converted to a string - Value* To_Value::operator()(Binary_Expression* s) - { - return SASS_MEMORY_NEW(String_Quoted, - s->pstate(), - s->to_string(ctx.c_options)); - } - -}; diff --git a/src/to_value.hpp b/src/to_value.hpp deleted file mode 100644 index 736e17defb..0000000000 --- a/src/to_value.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef SASS_TO_VALUE_H -#define SASS_TO_VALUE_H - -#include "operation.hpp" -#include "sass/values.h" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - class To_Value : public Operation_CRTP { - - private: - - Context& ctx; - - public: - - To_Value(Context& ctx) - : ctx(ctx) - { } - ~To_Value() { } - using Operation::operator(); - - Value* operator()(Argument*); - Value* operator()(Boolean*); - Value* operator()(Number*); - Value* operator()(Color_RGBA*); - Value* operator()(Color_HSLA*); - Value* operator()(String_Constant*); - Value* operator()(String_Quoted*); - Value* operator()(Custom_Warning*); - Value* operator()(Custom_Error*); - Value* operator()(List*); - Value* operator()(Map*); - Value* operator()(Null*); - Value* operator()(Function*); - - // convert to string via `To_String` - Value* operator()(SelectorList*); - Value* operator()(Binary_Expression*); - - }; - -} - -#endif diff --git a/src/unicode.cpp b/src/unicode.cpp new file mode 100644 index 0000000000..69d20a8bef --- /dev/null +++ b/src/unicode.cpp @@ -0,0 +1,108 @@ +#include "unicode.hpp" + +namespace Sass { + namespace Unicode { + + // naming conventions: + // bytes: raw byte offset (0 based) + // position: code point offset (0 based) + + // Return number of code points in utf8 string + size_t codePointCount(const sass::string& utf8) { + return utf8::distance(utf8.begin(), utf8.end()); + } + // EO codePointCount + + // Return number of code points in utf8 string up to bytes offset. + size_t codePointCount(const sass::string& utf8, size_t bytes) { + return utf8::distance(utf8.begin(), utf8.begin() + bytes); + } + // EO codePointCount + + // Return the byte offset at a code point position + size_t byteOffsetAtPosition(const sass::string& utf8, size_t position) { + sass::string::const_iterator it = utf8.begin(); + utf8::advance(it, position, utf8.end()); + return std::distance(utf8.begin(), it); + } + // EO byteOffsetAtPosition + + // Returns utf8 aware substring. + // Parameters are in code points. + sass::string substr( + sass::string& utf8, + size_t start, + size_t len) + { + auto first = utf8.begin(); + utf8::advance(first, + start, utf8.end()); + auto last = first; + if (len != sass::string::npos) { + utf8::advance(last, + len, utf8.end()); + } + else { + last = utf8.end(); + } + return sass::string( + first, last); + } + // EO substr + + // Utf8 aware string replacement. + // Parameters are in code points. + // Inserted text must be valid utf8. + sass::string replace( + sass::string& text, + size_t start, size_t len, + const sass::string& insert) + { + auto first = text.begin(); + utf8::advance(first, + start, text.end()); + auto last = first; + if (len != sass::string::npos) { + utf8::advance(last, + len, text.end()); + } + else { + last = text.end(); + } + return text.replace( + first, last, + insert); + } + // EO replace + + #ifdef _WIN32 + + // utf16 functions + using std::wstring; + + // convert from utf16/wide string to utf8 string + sass::string utf16to8(const sass::wstring& utf16) + { + sass::string utf8; + // preallocate expected memory + utf8.reserve(sizeof(utf16)/2); + utf8::utf16to8(utf16.begin(), utf16.end(), + back_inserter(utf8)); + return utf8; + } + + // convert from utf8 string to utf16/wide string + sass::wstring utf8to16(const sass::string& utf8) + { + sass::wstring utf16; + // preallocate expected memory + utf16.reserve(codePointCount(utf8)*2); + utf8::utf8to16(utf8.begin(), utf8.end(), + back_inserter(utf16)); + return utf16; + } + + #endif + + } +} diff --git a/src/unicode.hpp b/src/unicode.hpp new file mode 100644 index 0000000000..02091fb369 --- /dev/null +++ b/src/unicode.hpp @@ -0,0 +1,45 @@ +#ifndef SASS_UTF8_STRING_HPP +#define SASS_UTF8_STRING_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "utf8/checked.h" +#include "utf8/unchecked.h" + +namespace Sass { + namespace Unicode { + + // naming conventions: + // bytes: raw byte offset (0 based) + // position: code point offset (0 based) + + // Return number of code points in utf8 string up to bytes offset. + size_t codePointCount(const sass::string& utf8, size_t bytes); + + // Return number of code points in utf8 string + size_t codePointCount(const sass::string& utf8); + + // function that will return the byte offset of a code point in a + size_t byteOffsetAtPosition(const sass::string& utf8, size_t position); + + // Returns utf8 aware substring. + // Parameters are in code points. + sass::string substr(sass::string& utf8, size_t start, size_t len); + + // Utf8 aware string replacement. + // Parameters are in code points. + // Inserted text must be valid utf8. + sass::string replace(sass::string& utf8, size_t start, size_t len, const sass::string& insert); + + #ifdef _WIN32 + // functions to handle unicode paths on windows + sass::string utf16to8(const sass::wstring& utf16); + sass::wstring utf8to16(const sass::string& utf8); + #endif + + } +} + +#endif diff --git a/src/units.cpp b/src/units.cpp index bce92f9905..28d08312be 100644 --- a/src/units.cpp +++ b/src/units.cpp @@ -1,16 +1,20 @@ -#include "sass.hpp" -#include -#include -#include +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "units.hpp" -#include "error_handling.hpp" + +#include "flat_map.hpp" namespace Sass { - /* the conversion matrix can be readed the following way */ + ///////////////////////////////////////////////////////////////////////// + // Old but trusted implementation following: + ///////////////////////////////////////////////////////////////////////// + /* the conversion matrix can be read the following way */ /* if you go down, the factor is for the numerator (multiply) */ /* if you go right, the factor is for the denominator (divide) */ /* and yes, we actually use both, not sure why, but why not!? */ + ///////////////////////////////////////////////////////////////////////// const double size_conversion_factors[6][6] = { @@ -52,6 +56,9 @@ namespace Sass { /* dppx */ { 96, 96/2.54, 1 } }; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + UnitClass get_unit_type(UnitType unit) { switch (unit & 0xFF00) @@ -65,20 +72,7 @@ namespace Sass { } }; - sass::string get_unit_class(UnitType unit) - { - switch (unit & 0xFF00) - { - case UnitClass::LENGTH: return "LENGTH"; - case UnitClass::ANGLE: return "ANGLE"; - case UnitClass::TIME: return "TIME"; - case UnitClass::FREQUENCY: return "FREQUENCY"; - case UnitClass::RESOLUTION: return "RESOLUTION"; - default: return "INCOMMENSURABLE"; - } - }; - - UnitType get_main_unit(const UnitClass unit) + UnitType get_standard_unit(const UnitClass unit) { switch (unit) { @@ -99,7 +93,7 @@ namespace Sass { else if (s == "pc") return UnitType::PC; else if (s == "mm") return UnitType::MM; else if (s == "cm") return UnitType::CM; - else if (s == "in") return UnitType::IN; + else if (s == "in") return UnitType::INCH; // angle units else if (s == "deg") return UnitType::DEG; else if (s == "grad") return UnitType::GRAD; @@ -128,7 +122,7 @@ namespace Sass { case UnitType::PC: return "pc"; case UnitType::MM: return "mm"; case UnitType::CM: return "cm"; - case UnitType::IN: return "in"; + case UnitType::INCH: return "in"; // angle units case UnitType::DEG: return "deg"; case UnitType::GRAD: return "grad"; @@ -149,32 +143,8 @@ namespace Sass { } } - sass::string unit_to_class(const sass::string& s) - { - if (s == "px") return "LENGTH"; - else if (s == "pt") return "LENGTH"; - else if (s == "pc") return "LENGTH"; - else if (s == "mm") return "LENGTH"; - else if (s == "cm") return "LENGTH"; - else if (s == "in") return "LENGTH"; - // angle units - else if (s == "deg") return "ANGLE"; - else if (s == "grad") return "ANGLE"; - else if (s == "rad") return "ANGLE"; - else if (s == "turn") return "ANGLE"; - // time units - else if (s == "s") return "TIME"; - else if (s == "ms") return "TIME"; - // frequency units - else if (s == "Hz") return "FREQUENCY"; - else if (s == "kHz") return "FREQUENCY"; - // resolutions units - else if (s == "dpi") return "RESOLUTION"; - else if (s == "dpcm") return "RESOLUTION"; - else if (s == "dppx") return "RESOLUTION"; - // for unknown units - return "CUSTOM:" + s; - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // throws incompatibleUnits exceptions double conversion_factor(const sass::string& s1, const sass::string& s2) @@ -197,9 +167,9 @@ namespace Sass { // can't convert between groups if (t1 != t2) return 0; // get absolute offset - // used for array acces - size_t i1 = u1 - t1; - size_t i2 = u2 - t2; + // used for array access + size_t i1 = size_t(u1) > size_t(t1) ? u1 - t1 : t1 - u1; + size_t i2 = size_t(u2) > size_t(t2) ? u2 - t2 : t2 - u2; // process known units switch (t1) { case LENGTH: @@ -215,7 +185,7 @@ namespace Sass { case INCOMMENSURABLE: return 0; } - // fallback + // fall-back return 0; } @@ -242,7 +212,7 @@ namespace Sass { if (rhsexp < 0 && lhsexp > 0 && - rhsexp > lhsexp) { // get the conversion factor for units f = conversion_factor(urhs, ulhs, clhs, crhs); - // left hand side has been consumned + // left hand side has been consumed f = std::pow(f, lhsexp); rhsexp += lhsexp; lhsexp = 0; @@ -250,7 +220,7 @@ namespace Sass { else { // get the conversion factor for units f = conversion_factor(ulhs, urhs, clhs, crhs); - // right hand side has been consumned + // right hand side has been consumed f = std::pow(f, rhsexp); lhsexp += rhsexp; rhsexp = 0; @@ -258,24 +228,16 @@ namespace Sass { return f; } - bool Units::operator< (const Units& rhs) const - { - return (numerators < rhs.numerators) && - (denominators < rhs.denominators); - } bool Units::operator== (const Units& rhs) const { return (numerators == rhs.numerators) && (denominators == rhs.denominators); } - bool Units::operator!= (const Units& rhs) const - { - return ! (*this == rhs); - } double Units::normalize() { + stringified.clear(); size_t iL = numerators.size(); size_t nL = denominators.size(); @@ -287,7 +249,7 @@ namespace Sass { UnitType ulhs = string_to_unit(lhs); if (ulhs == UNKNOWN) continue; UnitClass clhs = get_unit_type(ulhs); - UnitType umain = get_main_unit(clhs); + UnitType umain = get_standard_unit(clhs); if (ulhs == umain) continue; double f(conversion_factor(umain, ulhs, clhs, clhs)); if (f == 0) throw std::runtime_error("INVALID"); @@ -300,8 +262,9 @@ namespace Sass { UnitType urhs = string_to_unit(rhs); if (urhs == UNKNOWN) continue; UnitClass crhs = get_unit_type(urhs); - UnitType umain = get_main_unit(crhs); + UnitType umain = get_standard_unit(crhs); if (urhs == umain) continue; + // this is never hit via spec-tests!? double f(conversion_factor(umain, urhs, crhs, crhs)); if (f == 0) throw std::runtime_error("INVALID"); denominators[n] = unit_to_string(umain); @@ -318,6 +281,7 @@ namespace Sass { double Units::reduce() { + stringified.clear(); size_t iL = numerators.size(); size_t nL = denominators.size(); @@ -327,8 +291,9 @@ namespace Sass { // first make sure same units cancel each other out // it seems that a map table will fit nicely to do this // we basically construct exponents for each unit - // has the advantage that they will be pre-sorted - std::map exponents; + // has the advantage that they will be presorted + // ToDo: use fast map implementation? + FlatMap exponents; // initialize by summing up occurrences in unit vectors // this will already cancel out equivalent units (e.q. px/px) @@ -354,12 +319,12 @@ namespace Sass { denominators.clear(); // recreate sorted units vectors - for (auto exp : exponents) { - int &exponent = exp.second; + for (auto kv : exponents) { + int &exponent = kv.second; while (exponent > 0 && exponent --) - numerators.push_back(exp.first); + numerators.emplace_back(kv.first); while (exponent < 0 && exponent ++) - denominators.push_back(exp.first); + denominators.emplace_back(kv.first); } // return for conversion @@ -367,37 +332,90 @@ namespace Sass { } - sass::string Units::unit() const + // Reset unit without conversion factor + void Units::unit(const sass::string& u) { - sass::string u; - size_t iL = numerators.size(); - size_t nL = denominators.size(); - for (size_t i = 0; i < iL; i += 1) { - if (i) u += '*'; - u += numerators[i]; + size_t l = 0; + size_t r; + stringified.clear(); + numerators.clear(); + denominators.clear(); + if (!u.empty()) { + bool nominator = true; + while (true) { + r = u.find_first_of("*/", l); + sass::string unit(u.substr(l, r == sass::string::npos ? r : r - l)); + if (!unit.empty()) { + if (nominator) numerators.emplace_back(unit); + else denominators.emplace_back(unit); + } + if (r == sass::string::npos) break; + // ToDo: should error for multiple slashes + // if (!nominator && u[r] == '/') error(...) + if (u[r] == '/') + nominator = false; + // strange math parsing? + // else if (u[r] == '*') + // nominator = true; + l = r + 1; + } } - if (nL != 0) u += '/'; - for (size_t n = 0; n < nL; n += 1) { - if (n) u += '*'; - u += denominators[n]; + } + + const sass::string& Units::unit() const + { + // Units are expected to be short, so we hopefully + // can profit from small objects optimization. This + // is not guaranteed, but still safe to assume that + // any mature implementation utilizes it. + if (stringified.empty()) { + size_t iL = numerators.size(); + size_t nL = denominators.size(); + for (size_t i = 0; i < iL; i += 1) { + if (i) stringified += '*'; + stringified += numerators[i]; + } + if (iL == 0) { + if (nL > 1) stringified += '('; + for (size_t n = 0; n < nL; n += 1) { + if (n) stringified += '*'; + stringified += denominators[n]; + } + if (nL > 1) stringified += ')'; + if (nL != 0) stringified += "^-1"; + } + else { + if (nL != 0) stringified += '/'; + for (size_t n = 0; n < nL; n += 1) { + if (n) stringified += '*'; + stringified += denominators[n]; + } + } } - return u; + return stringified; } - bool Units::is_unitless() const + bool Units::hasUnit(sass::string unit) + { + return numerators.size() == 1 && + denominators.empty() && + numerators[0] == unit; + } + + bool Units::isUnitless() const { return numerators.empty() && denominators.empty(); } - bool Units::is_valid_css_unit() const + bool Units::isValidCssUnit() const { return numerators.size() <= 1 && denominators.size() == 0; } // this does not cover all cases (multiple preferred units) - double Units::convert_factor(const Units& r) const + double Units::getUnitConvertFactor(const Units& r) const { sass::vector miss_nums(0); @@ -409,8 +427,8 @@ namespace Sass { auto l_num_it = numerators.begin(); auto l_num_end = numerators.end(); - bool l_unitless = is_unitless(); - auto r_unitless = r.is_unitless(); + bool l_unitless = isUnitless(); + auto r_unitless = r.isUnitless(); // overall conversion double factor = 1; @@ -421,6 +439,9 @@ namespace Sass { // get and increment afterwards const sass::string l_num = *(l_num_it ++); + // ToDo: we erase from base vector in the loop. + // Iterators might get invalid during the loop + // ToDo: refactor to use index access instead. auto r_num_it = r_nums.begin(), r_num_end = r_nums.end(); bool found = false; @@ -446,7 +467,7 @@ namespace Sass { } // maybe we did not find any // left numerator is leftover - if (!found) miss_nums.push_back(l_num); + if (!found) miss_nums.emplace_back(l_num); } auto l_den_it = denominators.begin(); @@ -484,24 +505,27 @@ namespace Sass { } // maybe we did not find any // left denominator is leftover - if (!found) miss_dens.push_back(l_den); + if (!found) miss_dens.emplace_back(l_den); } // check left-overs (ToDo: might cancel out?) if (miss_nums.size() > 0 && !r_unitless) { - throw Exception::IncompatibleUnits(r, *this); + return 0.0; } else if (miss_dens.size() > 0 && !r_unitless) { - throw Exception::IncompatibleUnits(r, *this); + return 0.0; } else if (r_nums.size() > 0 && !l_unitless) { - throw Exception::IncompatibleUnits(r, *this); + return 0.0; } else if (r_dens.size() > 0 && !l_unitless) { - throw Exception::IncompatibleUnits(r, *this); + return 0.0; } return factor; } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } diff --git a/src/units.hpp b/src/units.hpp index 98848849cc..6026c36dbc 100644 --- a/src/units.hpp +++ b/src/units.hpp @@ -1,10 +1,14 @@ -#ifndef SASS_UNITS_H -#define SASS_UNITS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_UNITS_HPP +#define SASS_UNITS_HPP -#include -#include -#include -#include +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_def_macros.hpp" namespace Sass { @@ -22,7 +26,7 @@ namespace Sass { enum UnitType { // size units - IN = UnitClass::LENGTH, + INCH = UnitClass::LENGTH, CM, PC, MM, @@ -54,26 +58,59 @@ namespace Sass { }; class Units { + + private: + + mutable sass::string stringified; + public: + sass::vector numerators; sass::vector denominators; - public: + // default constructor Units() : numerators(), denominators() { } + + Units(const sass::string& u) : + numerators(), + denominators() + { + unit(u); + } + + // copy constructor Units(const Units* ptr) : numerators(ptr->numerators), denominators(ptr->denominators) { } + + // copy constructor + Units(const Units& ptr) : + numerators(ptr.numerators), + denominators(ptr.denominators) + { } + + // move constructor + Units(Units&& other) noexcept : + numerators(std::move(other.numerators)), + denominators(std::move(other.denominators)) + { } + // convert to string - sass::string unit() const; + const sass::string& unit() const; + void unit(const sass::string& unit); // get if units are empty - bool is_unitless() const; + bool isUnitless() const; + + bool hasUnits() const { + return !isUnitless(); + } // return if valid for css - bool is_valid_css_unit() const; + bool isValidCssUnit() const; // reduce units for output // returns conversion factor double reduce(); @@ -81,25 +118,32 @@ namespace Sass { // returns conversion factor double normalize(); // compare operations - bool operator< (const Units& rhs) const; - bool operator== (const Units& rhs) const; - bool operator!= (const Units& rhs) const; + bool operator==(const Units& rhs) const; + // Delete other operators to make implementation more clear + // Helps us spot cases where we use undefined implementations + // bool operator!=(const Units& rhs) const = delete; + // bool operator>=(const Units& rhs) const = delete; + // bool operator<=(const Units& rhs) const = delete; + // bool operator>(const Units& rhs) const = delete; + // bool operator<(const Units& rhs) const = delete; + // factor to convert into given units - double convert_factor(const Units&) const; + double getUnitConvertFactor(const Units&) const; + // + bool hasUnit(sass::string numerator); }; + /* Declare matrix tables for unit conversion factors*/ extern const double size_conversion_factors[6][6]; extern const double angle_conversion_factors[4][4]; extern const double time_conversion_factors[2][2]; extern const double frequency_conversion_factors[2][2]; extern const double resolution_conversion_factors[3][3]; - UnitType get_main_unit(const UnitClass unit); + UnitType get_standard_unit(const UnitClass unit); enum Sass::UnitType string_to_unit(const sass::string&); const char* unit_to_string(Sass::UnitType unit); enum Sass::UnitClass get_unit_type(Sass::UnitType unit); - sass::string get_unit_class(Sass::UnitType unit); - sass::string unit_to_class(const sass::string&); // throws incompatibleUnits exceptions double conversion_factor(const sass::string&, const sass::string&); double conversion_factor(UnitType, UnitType, UnitClass, UnitClass); diff --git a/src/utf8.h b/src/utf8.h deleted file mode 100644 index 82b13f59f9..0000000000 --- a/src/utf8.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2006 Nemanja Trifunovic - -/* -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -*/ - - -#ifndef UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 -#define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 - -#include "utf8/checked.h" -#include "utf8/unchecked.h" - -#endif // header guard diff --git a/src/utf8/checked.h b/src/utf8/checked.h index ce9d27c8ab..5749acaf54 100644 --- a/src/utf8/checked.h +++ b/src/utf8/checked.h @@ -50,7 +50,8 @@ namespace utf8 uint8_t u8; public: invalid_utf8 (uint8_t u) : u8(u) {} - virtual const char* what() const throw() { return "Invalid UTF-8"; } + virtual const char* what() const throw() { + return "Invalid UTF-8"; } uint8_t utf8_octet() const {return u8;} }; diff --git a/src/utf8_string.cpp b/src/utf8_string.cpp deleted file mode 100644 index 3464d93214..0000000000 --- a/src/utf8_string.cpp +++ /dev/null @@ -1,104 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include -#include - -#include "utf8.h" - -namespace Sass { - namespace UTF_8 { - - // naming conventions: - // offset: raw byte offset (0 based) - // position: code point offset (0 based) - // index: code point offset (1 based or negative) - - // function that will count the number of code points (utf-8 characters) from the given beginning to the given end - size_t code_point_count(const sass::string& str, size_t start, size_t end) { - return utf8::distance(str.begin() + start, str.begin() + end); - } - - size_t code_point_count(const sass::string& str) { - return utf8::distance(str.begin(), str.end()); - } - - // function that will return the byte offset at a code point position - size_t offset_at_position(const sass::string& str, size_t position) { - sass::string::const_iterator it = str.begin(); - utf8::advance(it, position, str.end()); - return std::distance(str.begin(), it); - } - - // function that returns number of bytes in a character at offset - size_t code_point_size_at_offset(const sass::string& str, size_t offset) { - // get iterator from string and forward by offset - sass::string::const_iterator stop = str.begin() + offset; - // check if beyond boundary - if (stop == str.end()) return 0; - // advance by one code point - utf8::advance(stop, 1, str.end()); - // calculate offset for code point - return stop - str.begin() - offset; - } - - // function that will return a normalized index, given a crazy one - size_t normalize_index(int index, size_t len) { - long signed_len = static_cast(len); - // assuming the index is 1-based - // we are returning a 0-based index - if (index > 0 && index <= signed_len) { - // positive and within string length - return index-1; - } - else if (index > signed_len) { - // positive and past string length - return len; - } - else if (index == 0) { - return 0; - } - else if (std::abs((double)index) <= signed_len) { - // negative and within string length - return index + signed_len; - } - else { - // negative and past string length - return 0; - } - } - - #ifdef _WIN32 - - // utf16 functions - using std::wstring; - - // convert from utf16/wide string to utf8 string - sass::string convert_from_utf16(const wstring& utf16) - { - sass::string utf8; - // pre-allocate expected memory - utf8.reserve(sizeof(utf16)/2); - utf8::utf16to8(utf16.begin(), utf16.end(), - back_inserter(utf8)); - return utf8; - } - - // convert from utf8 string to utf16/wide string - wstring convert_to_utf16(const sass::string& utf8) - { - wstring utf16; - // pre-allocate expected memory - utf16.reserve(code_point_count(utf8)*2); - utf8::utf8to16(utf8.begin(), utf8.end(), - back_inserter(utf16)); - return utf16; - } - - #endif - - } -} diff --git a/src/utf8_string.hpp b/src/utf8_string.hpp deleted file mode 100644 index 88b10f9b0b..0000000000 --- a/src/utf8_string.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef SASS_UTF8_STRING_H -#define SASS_UTF8_STRING_H - -#include -#include "utf8.h" -#include "memory.hpp" - -namespace Sass { - namespace UTF_8 { - - // naming conventions: - // offset: raw byte offset (0 based) - // position: code point offset (0 based) - // index: code point offset (1 based or negative) - - // function that will count the number of code points (utf-8 characters) from the beginning to the given end - size_t code_point_count(const sass::string& str, size_t start, size_t end); - size_t code_point_count(const sass::string& str); - - // function that will return the byte offset of a code point in a - size_t offset_at_position(const sass::string& str, size_t position); - - // function that returns number of bytes in a character in a string - size_t code_point_size_at_offset(const sass::string& str, size_t offset); - - // function that will return a normalized index, given a crazy one - size_t normalize_index(int index, size_t len); - - #ifdef _WIN32 - // functions to handle unicode paths on windows - sass::string convert_from_utf16(const std::wstring& wstr); - std::wstring convert_to_utf16(const sass::string& str); - #endif - - } -} - -#endif diff --git a/src/util.cpp b/src/util.cpp deleted file mode 100644 index e2941edc1a..0000000000 --- a/src/util.cpp +++ /dev/null @@ -1,723 +0,0 @@ -#include "sass.hpp" -#include "sass.h" -#include "ast.hpp" -#include "util.hpp" -#include "util_string.hpp" -#include "lexer.hpp" -#include "prelexer.hpp" -#include "constants.hpp" -#include "utf8/checked.h" - -#include -#include -#if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64) -#include -#endif - -namespace Sass { - - double round(double val, size_t precision) - { - // Disable FMA3-optimized implementation when compiling with VS2013 for x64 targets - // See https://github.com/sass/node-sass/issues/1854 for details - // FIXME: Remove this workaround when we switch to VS2015+ - #if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64) - static std::once_flag flag; - std::call_once(flag, []() { _set_FMA3_enable(0); }); - #endif - - // https://github.com/sass/sass/commit/4e3e1d5684cc29073a507578fc977434ff488c93 - if (std::fmod(val, 1) - 0.5 > - std::pow(0.1, precision + 1)) return std::ceil(val); - else if (std::fmod(val, 1) - 0.5 > std::pow(0.1, precision)) return std::floor(val); - // work around some compiler issue - // cygwin has it not defined in std - using namespace std; - return ::round(val); - } - - /* Locale unspecific atof function. */ - double sass_strtod(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_copy_c_string(str); - *(copy + (found - str)) = separator; - double res = strtod(copy, NULL); - free(copy); - return res; - } - } - - return strtod(str, NULL); - } - - // helper for safe access to c_ctx - const char* safe_str (const char* str, const char* alt) { - return str == NULL ? alt : str; - } - - void free_string_array(char ** arr) { - if(!arr) - return; - - char **it = arr; - while (it && (*it)) { - free(*it); - ++it; - } - - free(arr); - } - - char **copy_strings(const sass::vector& strings, char*** array, int skip) { - int num = static_cast(strings.size()) - skip; - char** arr = (char**) calloc(num + 1, sizeof(char*)); - if (arr == 0) - return *array = (char **)NULL; - - for(int i = 0; i < num; i++) { - arr[i] = (char*) malloc(sizeof(char) * (strings[i + skip].size() + 1)); - if (arr[i] == 0) { - free_string_array(arr); - return *array = (char **)NULL; - } - std::copy(strings[i + skip].begin(), strings[i + skip].end(), arr[i]); - arr[i][strings[i + skip].size()] = '\0'; - } - - arr[num] = 0; - return *array = arr; - } - - // read css string (handle multiline DELIM) - sass::string read_css_string(const sass::string& str, bool css) - { - if (!css) return str; - sass::string out(""); - bool esc = false; - for (auto i : str) { - if (i == '\\') { - esc = ! esc; - } else if (esc && i == '\r') { - continue; - } else if (esc && i == '\n') { - out.resize (out.size () - 1); - esc = false; - continue; - } else { - esc = false; - } - out.push_back(i); - } - // happens when parsing does not correctly skip - // over escaped sequences for ie. interpolations - // one example: foo\#{interpolate} - // if (esc) out += '\\'; - return out; - } - - // double escape all escape sequences - // keep unescaped quotes and backslashes - sass::string evacuate_escapes(const sass::string& str) - { - sass::string out(""); - bool esc = false; - for (auto i : str) { - if (i == '\\' && !esc) { - out += '\\'; - out += '\\'; - esc = true; - } else if (esc && i == '"') { - out += '\\'; - out += i; - esc = false; - } else if (esc && i == '\'') { - out += '\\'; - out += i; - esc = false; - } else if (esc && i == '\\') { - out += '\\'; - out += i; - esc = false; - } else { - esc = false; - out += i; - } - } - // happens when parsing does not correctly skip - // over escaped sequences for ie. interpolations - // one example: foo\#{interpolate} - // if (esc) out += '\\'; - return out; - } - - // bell characters are replaced with spaces - void newline_to_space(sass::string& str) - { - std::replace(str.begin(), str.end(), '\n', ' '); - } - - // 1. Removes whitespace after newlines. - // 2. Replaces newlines with spaces. - // - // This method only considers LF and CRLF as newlines. - sass::string string_to_output(const sass::string& str) - { - sass::string result; - result.reserve(str.size()); - std::size_t pos = 0; - while (true) { - const std::size_t newline = str.find_first_of("\n\r", pos); - if (newline == sass::string::npos) break; - result.append(str, pos, newline - pos); - if (str[newline] == '\r') { - if (str[newline + 1] == '\n') { - pos = newline + 2; - } else { - // CR without LF: append as-is and continue. - result += '\r'; - pos = newline + 1; - continue; - } - } else { - pos = newline + 1; - } - result += ' '; - const std::size_t non_space = str.find_first_not_of(" \f\n\r\t\v", pos); - if (non_space != sass::string::npos) { - pos = non_space; - } - } - result.append(str, pos, sass::string::npos); - return result; - } - - sass::string escape_string(const sass::string& str) - { - sass::string out; - out.reserve(str.size()); - for (char c : str) { - switch (c) { - case '\n': - out.append("\\n"); - break; - case '\r': - out.append("\\r"); - break; - case '\f': - out.append("\\f"); - break; - default: - out += c; - } - } - return out; - } - - sass::string comment_to_compact_string(const sass::string& text) - { - sass::string str = ""; - size_t has = 0; - char prev = 0; - bool clean = false; - for (auto i : text) { - if (clean) { - if (i == '\n') { has = 0; } - else if (i == '\t') { ++ has; } - else if (i == ' ') { ++ has; } - else if (i == '*') {} - else { - clean = false; - str += ' '; - if (prev == '*' && i == '/') str += "*/"; - else str += i; - } - } else if (i == '\n') { - clean = true; - } else { - str += i; - } - prev = i; - } - if (has) return str; - else return text; - } - - // find best quote_mark by detecting if the string contains any single - // or double quotes. When a single quote is found, we not we want a double - // quote as quote_mark. Otherwise we check if the string cotains any double - // quotes, which will trigger the use of single quotes as best quote_mark. - char detect_best_quotemark(const char* s, char qm) - { - // ensure valid fallback quote_mark - char quote_mark = qm && qm != '*' ? qm : '"'; - while (*s) { - // force double quotes as soon - // as one single quote is found - if (*s == '\'') { return '"'; } - // a single does not force quote_mark - // maybe we see a double quote later - else if (*s == '"') { quote_mark = '\''; } - ++ s; - } - return quote_mark; - } - - sass::string read_hex_escapes(const sass::string& s) - { - - sass::string result; - bool skipped = false; - - for (size_t i = 0, L = s.length(); i < L; ++i) { - - // implement the same strange ruby sass behavior - // an escape sequence can also mean a unicode char - if (s[i] == '\\' && !skipped) { - - // remember - skipped = true; - - // escape length - size_t len = 1; - - // parse as many sequence chars as possible - // ToDo: Check if ruby aborts after possible max - while (i + len < L && s[i + len] && Util::ascii_isxdigit(static_cast(s[i + len]))) ++ len; - - if (len > 1) { - - // convert the extracted hex string to code point value - // ToDo: Maybe we could do this without creating a substring - uint32_t cp = strtol(s.substr (i + 1, len - 1).c_str(), NULL, 16); - - if (s[i + len] == ' ') ++ len; - - // assert invalid code points - if (cp == 0) cp = 0xFFFD; - // replace bell character - // if (cp == '\n') cp = 32; - - // use a very simple approach to convert via utf8 lib - // maybe there is a more elegant way; maybe we shoud - // convert the whole output from string to a stream!? - // allocate memory for utf8 char and convert to utf8 - unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u); - for(size_t m = 0; m < 5 && u[m]; m++) result.push_back(u[m]); - - // skip some more chars? - i += len - 1; skipped = false; - - } - - else { - - skipped = false; - - result.push_back(s[i]); - - } - - } - - else { - - result.push_back(s[i]); - - } - - } - - return result; - - } - - sass::string unquote(const sass::string& s, char* qd, bool keep_utf8_sequences, bool strict) - { - - // not enough room for quotes - // no possibility to unquote - if (s.length() < 2) return s; - - char q; - bool skipped = false; - - // this is no guarantee that the unquoting will work - // what about whitespace before/after the quote_mark? - if (*s.begin() == '"' && *s.rbegin() == '"') q = '"'; - else if (*s.begin() == '\'' && *s.rbegin() == '\'') q = '\''; - else return s; - - sass::string unq; - unq.reserve(s.length()-2); - - for (size_t i = 1, L = s.length() - 1; i < L; ++i) { - - // implement the same strange ruby sass behavior - // an escape sequence can also mean a unicode char - if (s[i] == '\\' && !skipped) { - // remember - skipped = true; - - // skip it - // ++ i; - - // if (i == L) break; - - // escape length - size_t len = 1; - - // parse as many sequence chars as possible - // ToDo: Check if ruby aborts after possible max - while (i + len < L && s[i + len] && Util::ascii_isxdigit(static_cast(s[i + len]))) ++ len; - - // hex string? - if (keep_utf8_sequences) { - unq.push_back(s[i]); - } else if (len > 1) { - - // convert the extracted hex string to code point value - // ToDo: Maybe we could do this without creating a substring - uint32_t cp = strtol(s.substr (i + 1, len - 1).c_str(), NULL, 16); - - if (s[i + len] == ' ') ++ len; - - // assert invalid code points - if (cp == 0) cp = 0xFFFD; - // replace bell character - // if (cp == '\n') cp = 32; - - // use a very simple approach to convert via utf8 lib - // maybe there is a more elegant way; maybe we shoud - // convert the whole output from string to a stream!? - // allocate memory for utf8 char and convert to utf8 - unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u); - for(size_t m = 0; m < 5 && u[m]; m++) unq.push_back(u[m]); - - // skip some more chars? - i += len - 1; skipped = false; - - } - - - } - // check for unexpected delimiter - // be strict and throw error back - // else if (!skipped && q == s[i]) { - // // don't be that strict - // return s; - // // this basically always means an internal error and not users fault - // error("Unescaped delimiter in string to unquote found. [" + s + "]", SourceSpan("[UNQUOTE]")); - // } - else { - if (strict && !skipped) { - if (s[i] == q) return s; - } - skipped = false; - unq.push_back(s[i]); - } - - } - if (skipped) { return s; } - if (qd) *qd = q; - return unq; - - } - - sass::string quote(const sass::string& s, char q) - { - - // autodetect with fallback to given quote - q = detect_best_quotemark(s.c_str(), q); - - // return an empty quoted string - if (s.empty()) return sass::string(2, q ? q : '"'); - - sass::string quoted; - quoted.reserve(s.length()+2); - quoted.push_back(q); - - const char* it = s.c_str(); - const char* end = it + strlen(it) + 1; - while (*it && it < end) { - const char* now = it; - - if (*it == q) { - quoted.push_back('\\'); - } else if (*it == '\\') { - quoted.push_back('\\'); - } - - int cp = utf8::next(it, end); - - // in case of \r, check if the next in sequence - // is \n and then advance the iterator and skip \r - if (cp == '\r' && it < end && utf8::peek_next(it, end) == '\n') { - cp = utf8::next(it, end); - } - - if (cp == '\n') { - quoted.push_back('\\'); - quoted.push_back('a'); - // we hope we can remove this flag once we figure out - // why ruby sass has these different output behaviors - // gsub(/\n(?![a-fA-F0-9\s])/, "\\a").gsub("\n", "\\a ") - using namespace Prelexer; - if (alternatives < - Prelexer::char_range<'a', 'f'>, - Prelexer::char_range<'A', 'F'>, - Prelexer::char_range<'0', '9'>, - space - >(it) != NULL) { - quoted.push_back(' '); - } - } else if (cp < 127) { - quoted.push_back((char) cp); - } else { - while (now < it) { - quoted.push_back(*now); - ++ now; - } - } - } - - quoted.push_back(q); - return quoted; - } - - bool is_hex_doublet(double n) - { - return n == 0x00 || n == 0x11 || n == 0x22 || n == 0x33 || - n == 0x44 || n == 0x55 || n == 0x66 || n == 0x77 || - n == 0x88 || n == 0x99 || n == 0xAA || n == 0xBB || - n == 0xCC || n == 0xDD || n == 0xEE || n == 0xFF ; - } - - bool is_color_doublet(double r, double g, double b) - { - return is_hex_doublet(r) && is_hex_doublet(g) && is_hex_doublet(b); - } - - bool peek_linefeed(const char* start) - { - using namespace Prelexer; - using namespace Constants; - return sequence < - zero_plus < - alternatives < - exactly <' '>, - exactly <'\t'>, - line_comment, - block_comment, - delimited_by < - slash_star, - star_slash, - false - > - > - >, - re_linebreak - >(start) != 0; - } - - namespace Util { - - bool isPrintable(StyleRule* r, Sass_Output_Style style) { - if (r == NULL) { - return false; - } - - Block_Obj b = r->block(); - - SelectorList* sl = r->selector(); - bool hasSelectors = sl ? sl->length() > 0 : false; - - if (!hasSelectors) { - return false; - } - - bool hasDeclarations = false; - bool hasPrintableChildBlocks = false; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm)) { - return true; - } else if (Declaration* d = Cast(stm)) { - return isPrintable(d, style); - } else if (ParentStatement* p = Cast(stm)) { - Block_Obj pChildBlock = p->block(); - if (isPrintable(pChildBlock, style)) { - hasPrintableChildBlocks = true; - } - } else if (Comment* c = Cast(stm)) { - // keep for uncompressed - if (style != COMPRESSED) { - hasDeclarations = true; - } - // output style compressed - if (c->is_important()) { - hasDeclarations = c->is_important(); - } - } else { - hasDeclarations = true; - } - - if (hasDeclarations || hasPrintableChildBlocks) { - return true; - } - } - - return false; - } - - bool isPrintable(String_Constant* s, Sass_Output_Style style) - { - return ! s->value().empty(); - } - - bool isPrintable(String_Quoted* s, Sass_Output_Style style) - { - return true; - } - - bool isPrintable(Declaration* d, Sass_Output_Style style) - { - ExpressionObj val = d->value(); - if (String_Quoted_Obj sq = Cast(val)) return isPrintable(sq.ptr(), style); - if (String_Constant_Obj sc = Cast(val)) return isPrintable(sc.ptr(), style); - return true; - } - - bool isPrintable(SupportsRule* f, Sass_Output_Style style) { - if (f == NULL) { - return false; - } - - Block_Obj b = f->block(); - - bool hasDeclarations = false; - bool hasPrintableChildBlocks = false; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm) || Cast(stm)) { - hasDeclarations = true; - } - else if (ParentStatement* b = Cast(stm)) { - Block_Obj pChildBlock = b->block(); - if (!b->is_invisible()) { - if (isPrintable(pChildBlock, style)) { - hasPrintableChildBlocks = true; - } - } - } - - if (hasDeclarations || hasPrintableChildBlocks) { - return true; - } - } - - return false; - } - - bool isPrintable(CssMediaRule* m, Sass_Output_Style style) - { - if (m == nullptr) return false; - Block_Obj b = m->block(); - if (b == nullptr) return false; - if (m->empty()) return false; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm)) return true; - else if (Cast(stm)) return true; - else if (Comment* c = Cast(stm)) { - if (isPrintable(c, style)) { - return true; - } - } - else if (StyleRule* r = Cast(stm)) { - if (isPrintable(r, style)) { - return true; - } - } - else if (SupportsRule* f = Cast(stm)) { - if (isPrintable(f, style)) { - return true; - } - } - else if (CssMediaRule* mb = Cast(stm)) { - if (isPrintable(mb, style)) { - return true; - } - } - else if (ParentStatement* b = Cast(stm)) { - if (isPrintable(b->block(), style)) { - return true; - } - } - } - return false; - } - - bool isPrintable(Comment* c, Sass_Output_Style style) - { - // keep for uncompressed - if (style != COMPRESSED) { - return true; - } - // output style compressed - if (c->is_important()) { - return true; - } - // not printable - return false; - }; - - bool isPrintable(Block_Obj b, Sass_Output_Style style) { - if (!b) { - return false; - } - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm) || Cast(stm)) { - return true; - } - else if (Comment* c = Cast(stm)) { - if (isPrintable(c, style)) { - return true; - } - } - else if (StyleRule* r = Cast(stm)) { - if (isPrintable(r, style)) { - return true; - } - } - else if (SupportsRule* f = Cast(stm)) { - if (isPrintable(f, style)) { - return true; - } - } - else if (CssMediaRule * m = Cast(stm)) { - if (isPrintable(m, style)) { - return true; - } - } - else if (ParentStatement* b = Cast(stm)) { - if (isPrintable(b->block(), style)) { - return true; - } - } - } - - return false; - } - - } -} diff --git a/src/util.hpp b/src/util.hpp deleted file mode 100644 index 04b0e78b2f..0000000000 --- a/src/util.hpp +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef SASS_UTIL_H -#define SASS_UTIL_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "sass/base.h" -#include "ast_fwd_decl.hpp" - -#include -#include -#include -#include -#include - -#define SASS_ASSERT(cond, msg) assert(cond && msg) - -namespace Sass { - - template - T clip(const T& n, const T& lower, const T& upper) { - return std::max(lower, std::min(n, upper)); - } - - template - T absmod(const T& n, const T& r) { - T m = std::fmod(n, r); - if (m < 0.0) m += r; - return m; - } - - double round(double val, size_t precision = 0); - double sass_strtod(const char* str); - const char* safe_str(const char *, const char* = ""); - void free_string_array(char **); - char **copy_strings(const sass::vector&, char ***, int = 0); - sass::string read_css_string(const sass::string& str, bool css = true); - sass::string evacuate_escapes(const sass::string& str); - sass::string string_to_output(const sass::string& str); - sass::string comment_to_compact_string(const sass::string& text); - sass::string read_hex_escapes(const sass::string& str); - sass::string escape_string(const sass::string& str); - void newline_to_space(sass::string& str); - - sass::string quote(const sass::string&, char q = 0); - sass::string unquote(const sass::string&, char* q = 0, bool keep_utf8_sequences = false, bool strict = true); - char detect_best_quotemark(const char* s, char qm = '"'); - - bool is_hex_doublet(double n); - bool is_color_doublet(double r, double g, double b); - - bool peek_linefeed(const char* start); - - // Returns true iff `elements` ⊆ `container`. - template - bool contains_all(C container, T elements) { - for (const auto &el : elements) { - if (container.find(el) == container.end()) return false; - } - return true; - } - - // C++20 `starts_with` equivalent. - // See https://en.cppreference.com/w/cpp/string/basic_string/starts_with - inline bool starts_with(const sass::string& str, const char* prefix, size_t prefix_len) { - return str.compare(0, prefix_len, prefix) == 0; - } - - inline bool starts_with(const sass::string& str, const char* prefix) { - return starts_with(str, prefix, std::strlen(prefix)); - } - - // C++20 `ends_with` equivalent. - // See https://en.cppreference.com/w/cpp/string/basic_string/ends_with - inline bool ends_with(const sass::string& str, const sass::string& suffix) { - return suffix.size() <= str.size() && std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()); - } - - inline bool ends_with(const sass::string& str, const char* suffix, size_t suffix_len) { - if (suffix_len > str.size()) return false; - const char* suffix_it = suffix + suffix_len; - const char* str_it = str.c_str() + str.size(); - while (suffix_it != suffix) if (*(--suffix_it) != *(--str_it)) return false; - return true; - } - - inline bool ends_with(const sass::string& str, const char* suffix) { - return ends_with(str, suffix, std::strlen(suffix)); - } - - namespace Util { - - bool isPrintable(StyleRule* r, Sass_Output_Style style = NESTED); - bool isPrintable(SupportsRule* r, Sass_Output_Style style = NESTED); - bool isPrintable(CssMediaRule* r, Sass_Output_Style style = NESTED); - bool isPrintable(Comment* b, Sass_Output_Style style = NESTED); - bool isPrintable(Block_Obj b, Sass_Output_Style style = NESTED); - bool isPrintable(String_Constant* s, Sass_Output_Style style = NESTED); - bool isPrintable(String_Quoted* s, Sass_Output_Style style = NESTED); - bool isPrintable(Declaration* d, Sass_Output_Style style = NESTED); - - } -} -#endif diff --git a/src/util_string.cpp b/src/util_string.cpp index d16c4cfcc4..f014a3903a 100644 --- a/src/util_string.cpp +++ b/src/util_string.cpp @@ -1,54 +1,14 @@ #include "util_string.hpp" -#include -#include +#include "character.hpp" namespace Sass { namespace Util { - // ########################################################################## - // Special case insensitive string matcher. We can optimize - // the more general compare case quite a bit by requiring - // consumers to obey some rules (lowercase and no space). - // - `literal` must only contain lower case ascii characters - // there is one edge case where this could give false positives - // test could contain a (non-ascii) char exactly 32 below literal - // ########################################################################## - bool equalsLiteral(const char* lit, const sass::string& test) { - // Work directly on characters - const char* src = test.c_str(); - // There is a small chance that the search string - // Is longer than the rest of the string to look at - while (*lit && (*src == *lit || *src + 32 == *lit)) { - ++src, ++lit; - } - // True if literal is at end - // If not test was too long - return *lit == 0; - } - - void ascii_str_tolower(sass::string* s) { - for (auto& ch : *s) { - ch = ascii_tolower(static_cast(ch)); - } - } - - void ascii_str_toupper(sass::string* s) { - for (auto& ch : *s) { - ch = ascii_toupper(static_cast(ch)); - } - } - - sass::string rtrim(sass::string str) { - auto it = std::find_if_not(str.rbegin(), str.rend(), ascii_isspace); - str.erase(str.rend() - it); - return str; - } - - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // Returns [name] without a vendor prefix. // If [name] has no vendor prefix, it's returned as-is. - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# sass::string unvendor(const sass::string& name) { if (name.size() < 2) return name; @@ -61,63 +21,6 @@ namespace Sass { } // EO unvendor - sass::string normalize_newlines(const sass::string& str) { - sass::string result; - result.reserve(str.size()); - std::size_t pos = 0; - while (true) { - const std::size_t newline = str.find_first_of("\n\f\r", pos); - if (newline == sass::string::npos) break; - result.append(str, pos, newline - pos); - result += '\n'; - if (str[newline] == '\r' && str[newline + 1] == '\n') { - pos = newline + 2; - } - else { - pos = newline + 1; - } - } - result.append(str, pos, sass::string::npos); - return result; - } - - sass::string normalize_underscores(const sass::string& str) { - sass::string normalized = str; - std::replace(normalized.begin(), normalized.end(), '_', '-'); - return normalized; - } - - sass::string normalize_decimals(const sass::string& str) { - sass::string normalized; - if (!str.empty() && str[0] == '.') { - normalized.reserve(str.size() + 1); - normalized += '0'; - normalized += str; - } - else { - normalized = str; - } - return normalized; - } - - char opening_bracket_for(char closing_bracket) { - switch (closing_bracket) { - case ')': return '('; - case ']': return '['; - case '}': return '{'; - default: return '\0'; - } - } - - char closing_bracket_for(char opening_bracket) { - switch (opening_bracket) { - case '(': return ')'; - case '[': return ']'; - case '{': return '}'; - default: return '\0'; - } - } - } // namespace Util diff --git a/src/util_string.hpp b/src/util_string.hpp index 837230d362..446d40b714 100644 --- a/src/util_string.hpp +++ b/src/util_string.hpp @@ -1,73 +1,65 @@ -#ifndef SASS_UTIL_STRING_H -#define SASS_UTIL_STRING_H +#ifndef SASS_UTIL_STRING_HPP +#define SASS_UTIL_STRING_HPP -#include "sass.hpp" -#include +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "character.hpp" +#include +#include +#include +#include +#include +#include namespace Sass { - namespace Util { - // ########################################################################## - // Special case insensitive string matcher. We can optimize - // the more general compare case quite a bit by requiring - // consumers to obey some rules (lowercase and no space). - // - `literal` must only contain lower case ascii characters - // there is one edge case where this could give false positives - // test could contain a (non-ascii) char exactly 32 below literal - // ########################################################################## - bool equalsLiteral(const char* lit, const sass::string& test); + ////////////////////////////////////////////////////////// + // `hash_combine` comes from boost (functional/hash): + // http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html + // Boost Software License - Version 1.0 + // http://www.boost.org/users/license.html + template + void hash_combine(std::size_t& hash, const T& val) + { + hash ^= std::hash()(val) + getHashSeed() + + (hash << 6) + (hash >> 2); + } + ////////////////////////////////////////////////////////// + + template + // Micro-optimization to avoid one call to `hash_combine` + void hash_start(std::size_t& hash, const T& val) + { + hash = std::hash()(val); + } + + // Not sure if calling std::hash has any overhead!? + inline void hash_combine(std::size_t& hash, std::size_t val) + { + hash ^= val + getHashSeed() + + (hash << 6) + (hash >> 2); + } + + // Not sure if calling std::hash has any overhead!? + inline void hash_start(std::size_t& hash, std::size_t val) + { + hash = val; + } + + namespace Util { - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // Returns [name] without a vendor prefix. // If [name] has no vendor prefix, it's returned as-is. - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# sass::string unvendor(const sass::string& name); - sass::string rtrim(sass::string str); - sass::string normalize_newlines(const sass::string& str); - sass::string normalize_underscores(const sass::string& str); - sass::string normalize_decimals(const sass::string& str); - char opening_bracket_for(char closing_bracket); - char closing_bracket_for(char opening_bracket); - - // Locale-independent ASCII character routines. - - inline bool ascii_isalpha(unsigned char c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); - } - - inline bool ascii_isdigit(unsigned char c) { - return (c >= '0' && c <= '9'); - } - - inline bool ascii_isalnum(unsigned char c) { - return ascii_isalpha(c) || ascii_isdigit(c); - } - - inline bool ascii_isascii(unsigned char c) { return c < 128; } - - inline bool ascii_isxdigit(unsigned char c) { - return ascii_isdigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); - } - - inline bool ascii_isspace(unsigned char c) { - return c == ' ' || c == '\t' || c == '\v' || c == '\f' || c == '\r' || c == '\n'; - } - - inline char ascii_tolower(unsigned char c) { - if (c >= 'A' && c <= 'Z') return c + 32; - return c; - } - - void ascii_str_tolower(sass::string* s); - - inline char ascii_toupper(unsigned char c) { - if (c >= 'a' && c <= 'z') return c - 32; - return c; - } + } + // EO namespace Sass - void ascii_str_toupper(sass::string* s); +} +// EO namespace Util - } // namespace Sass -} // namespace Util #endif // SASS_UTIL_STRING_H diff --git a/src/values.cpp b/src/values.cpp deleted file mode 100644 index 092bfd89cf..0000000000 --- a/src/values.cpp +++ /dev/null @@ -1,140 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "sass.h" -#include "values.hpp" - -#include - -namespace Sass { - - // convert value from C++ side to C-API - union Sass_Value* ast_node_to_sass_value (const Expression* val) - { - if (val->concrete_type() == Expression::NUMBER) - { - const Number* res = Cast(val); - return sass_make_number(res->value(), res->unit().c_str()); - } - else if (val->concrete_type() == Expression::COLOR) - { - if (const Color_RGBA* rgba = Cast(val)) { - return sass_make_color(rgba->r(), rgba->g(), rgba->b(), rgba->a()); - } else { - // ToDo: allow to also use HSLA colors!! - Color_RGBA_Obj col = Cast(val)->copyAsRGBA(); - return sass_make_color(col->r(), col->g(), col->b(), col->a()); - } - } - else if (val->concrete_type() == Expression::LIST) - { - const List* l = Cast(val); - union Sass_Value* list = sass_make_list(l->size(), l->separator(), l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - ExpressionObj obj = l->at(i); - auto val = ast_node_to_sass_value(obj); - sass_list_set_value(list, i, val); - } - return list; - } - else if (val->concrete_type() == Expression::MAP) - { - const Map* m = Cast(val); - union Sass_Value* map = sass_make_map(m->length()); - size_t i = 0; for (ExpressionObj key : m->keys()) { - sass_map_set_key(map, i, ast_node_to_sass_value(key)); - sass_map_set_value(map, i, ast_node_to_sass_value(m->at(key))); - ++ i; - } - return map; - } - else if (val->concrete_type() == Expression::NULL_VAL) - { - return sass_make_null(); - } - else if (val->concrete_type() == Expression::BOOLEAN) - { - const Boolean* res = Cast(val); - return sass_make_boolean(res->value()); - } - else if (val->concrete_type() == Expression::STRING) - { - if (const String_Quoted* qstr = Cast(val)) - { - return sass_make_qstring(qstr->value().c_str()); - } - else if (const String_Constant* cstr = Cast(val)) - { - return sass_make_string(cstr->value().c_str()); - } - } - return sass_make_error("unknown sass value type"); - } - - // convert value from C-API to C++ side - Value* sass_value_to_ast_node (const union Sass_Value* val) - { - switch (sass_value_get_tag(val)) { - case SASS_NUMBER: - return SASS_MEMORY_NEW(Number, - SourceSpan("[C-VALUE]"), - sass_number_get_value(val), - sass_number_get_unit(val)); - case SASS_BOOLEAN: - return SASS_MEMORY_NEW(Boolean, - SourceSpan("[C-VALUE]"), - sass_boolean_get_value(val)); - case SASS_COLOR: - // ToDo: allow to also use HSLA colors!! - return SASS_MEMORY_NEW(Color_RGBA, - SourceSpan("[C-VALUE]"), - sass_color_get_r(val), - sass_color_get_g(val), - sass_color_get_b(val), - sass_color_get_a(val)); - case SASS_STRING: - if (sass_string_is_quoted(val)) { - return SASS_MEMORY_NEW(String_Quoted, - SourceSpan("[C-VALUE]"), - sass_string_get_value(val)); - } - return SASS_MEMORY_NEW(String_Constant, - SourceSpan("[C-VALUE]"), - sass_string_get_value(val)); - case SASS_LIST: { - List* l = SASS_MEMORY_NEW(List, - SourceSpan("[C-VALUE]"), - sass_list_get_length(val), - sass_list_get_separator(val)); - for (size_t i = 0, L = sass_list_get_length(val); i < L; ++i) { - l->append(sass_value_to_ast_node(sass_list_get_value(val, i))); - } - l->is_bracketed(sass_list_get_is_bracketed(val)); - return l; - } - case SASS_MAP: { - Map* m = SASS_MEMORY_NEW(Map, SourceSpan("[C-VALUE]")); - for (size_t i = 0, L = sass_map_get_length(val); i < L; ++i) { - *m << std::make_pair( - sass_value_to_ast_node(sass_map_get_key(val, i)), - sass_value_to_ast_node(sass_map_get_value(val, i))); - } - return m; - } - case SASS_NULL: - return SASS_MEMORY_NEW(Null, SourceSpan("[C-VALUE]")); - case SASS_ERROR: - return SASS_MEMORY_NEW(Custom_Error, - SourceSpan("[C-VALUE]"), - sass_error_get_message(val)); - case SASS_WARNING: - return SASS_MEMORY_NEW(Custom_Warning, - SourceSpan("[C-VALUE]"), - sass_warning_get_message(val)); - default: break; - } - return 0; - } - -} diff --git a/src/values.hpp b/src/values.hpp deleted file mode 100644 index 3c4c687b0e..0000000000 --- a/src/values.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef SASS_VALUES_H -#define SASS_VALUES_H - -#include "ast.hpp" - -namespace Sass { - - union Sass_Value* ast_node_to_sass_value (const Expression* val); - Value* sass_value_to_ast_node (const union Sass_Value* val); - -} -#endif diff --git a/src/visitor_css.hpp b/src/visitor_css.hpp new file mode 100644 index 0000000000..c7c2383541 --- /dev/null +++ b/src/visitor_css.hpp @@ -0,0 +1,44 @@ +#ifndef SASS_VISITOR_CSS_HPP +#define SASS_VISITOR_CSS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" + +namespace Sass { + + // An interface for [visitors][] that traverse CSS statements. + // [visitors]: https://en.wikipedia.org/wiki/Visitor_pattern + + template + class CssVisitor { + public: + + virtual T visitCssAtRule(CssAtRule* css) = 0; + virtual T visitCssComment(CssComment* css) = 0; + virtual T visitCssDeclaration(CssDeclaration* css) = 0; + virtual T visitCssImport(CssImport* css) = 0; + virtual T visitCssKeyframeBlock(CssKeyframeBlock* css) = 0; + virtual T visitCssMediaRule(CssMediaRule* css) = 0; + virtual T visitCssRoot(CssRoot* css) = 0; // LibSass only + virtual T visitCssStyleRule(CssStyleRule* css) = 0; + virtual T visitCssSupportsRule(CssSupportsRule* css) = 0; + + }; + + template + class CssVisitable { + public: + virtual T accept(CssVisitor* visitor) = 0; + }; + +} + +#define DECLARE_CSS_ACCEPT(T, name)\ + T accept(CssVisitor* visitor) override final {\ + return visitor->visit##name(this);\ + }\ + +#endif diff --git a/src/visitor_expression.hpp b/src/visitor_expression.hpp new file mode 100644 index 0000000000..1ff8618d32 --- /dev/null +++ b/src/visitor_expression.hpp @@ -0,0 +1,48 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_VISITOR_EXPRESSION_HPP +#define SASS_VISITOR_EXPRESSION_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" + +namespace Sass { + + // An interface for [visitors][] that traverse SassScript expressions. + // [visitors]: https://en.wikipedia.org/wiki/Visitor_pattern + + template + class ExpressionVisitor { + public: + + virtual T visitBinaryOpExpression(BinaryOpExpression*) = 0; + virtual T visitBooleanExpression(BooleanExpression*) = 0; + virtual T visitColorExpression(ColorExpression*) = 0; + virtual T visitFunctionExpression(FunctionExpression*) = 0; + virtual T visitIfExpression(IfExpression*) = 0; + virtual T visitListExpression(ListExpression*) = 0; + virtual T visitMapExpression(MapExpression*) = 0; + virtual T visitNullExpression(NullExpression*) = 0; + virtual T visitNumberExpression(NumberExpression*) = 0; + virtual T visitParenthesizedExpression(ParenthesizedExpression*) = 0; + virtual T visitParentExpression(ParentExpression*) = 0; + virtual T visitStringExpression(StringExpression*) = 0; + virtual T visitUnaryOpExpression(UnaryOpExpression*) = 0; + virtual T visitValueExpression(ValueExpression*) = 0; + virtual T visitVariableExpression(VariableExpression*) = 0; + + }; + + template + class ExpressionVisitable { + public: + virtual T accept(ExpressionVisitor* visitor) = 0; + }; + +} + +#endif diff --git a/src/visitor_selector.hpp b/src/visitor_selector.hpp new file mode 100644 index 0000000000..2cb5f92868 --- /dev/null +++ b/src/visitor_selector.hpp @@ -0,0 +1,43 @@ +#ifndef SASS_VISITOR_SELECTOR_HPP +#define SASS_VISITOR_SELECTOR_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" + +namespace Sass { + + // An interface for [visitors][] that traverse selectors. + // [visitors]: https://en.wikipedia.org/wiki/Visitor_pattern + + template + class SelectorVisitor { + public: + + virtual T visitAttributeSelector(AttributeSelector* attribute) = 0; + virtual T visitClassSelector(ClassSelector* klass) = 0; + virtual T visitComplexSelector(ComplexSelector* complex) = 0; + virtual T visitCompoundSelector(CompoundSelector* compound) = 0; + virtual T visitIDSelector(IDSelector* id) = 0; + virtual T visitPlaceholderSelector(PlaceholderSelector* placeholder) = 0; + virtual T visitPseudoSelector(PseudoSelector* pseudo) = 0; + virtual T visitSelectorList(SelectorList* list) = 0; + virtual T visitTypeSelector(TypeSelector* type) = 0; + // The following two types have been optimized out in libsass + // virtual T visitParentSelector(ParentSelector* parent) = 0; + // virtual T visitUniversalSelector(UniversalSelector* universal) = 0; + virtual T visitSelectorCombinator(SelectorCombinator* combinator) = 0; + + }; + + template + class SelectorVisitable { + public: + virtual T accept(SelectorVisitor* visitor) = 0; + }; + +} + +#endif diff --git a/src/visitor_statement.hpp b/src/visitor_statement.hpp new file mode 100644 index 0000000000..b7fbf1b9f4 --- /dev/null +++ b/src/visitor_statement.hpp @@ -0,0 +1,63 @@ +#ifndef SASS_VISITOR_STATEMENT_HPP +#define SASS_VISITOR_STATEMENT_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" + +namespace Sass { + + // An interface for [visitors][] that traverse Sass statements. + // [visitors]: https://en.wikipedia.org/wiki/Visitor_pattern + + template + class StatementVisitor { + public: + + virtual T visitAtRootRule(AtRootRule*) = 0; + virtual T visitAtRule(AtRule*) = 0; + virtual T visitContentBlock(ContentBlock*) = 0; + virtual T visitContentRule(ContentRule*) = 0; + virtual T visitDebugRule(DebugRule*) = 0; + virtual T visitDeclaration(Declaration*) = 0; + virtual T visitEachRule(EachRule*) = 0; + virtual T visitErrorRule(ErrorRule*) = 0; + virtual T visitExtendRule(ExtendRule*) = 0; + virtual T visitForRule(ForRule*) = 0; + // virtual T visitForwardRule(ForwardRule*) = 0; + virtual T visitFunctionRule(FunctionRule*) = 0; + virtual T visitIfRule(IfRule*) = 0; + virtual T visitImportRule(ImportRule*) = 0; + virtual T visitIncludeRule(IncludeRule*) = 0; + virtual T visitLoudComment(LoudComment*) = 0; + virtual T visitMediaRule(MediaRule*) = 0; + virtual T visitMixinRule(MixinRule*) = 0; + virtual T visitReturnRule(ReturnRule*) = 0; + virtual T visitSilentComment(SilentComment*) = 0; + virtual T visitStyleRule(StyleRule*) = 0; + //virtual T visitStylesheet(Stylesheet*) = 0; + virtual T visitSupportsRule(SupportsRule*) = 0; + // virtual T visitUseRule(UseRule*) = 0; + // Renamed from visitVariableDeclaration + virtual T visitAssignRule(AssignRule*) = 0; + virtual T visitWarnRule(WarnRule*) = 0; + virtual T visitWhileRule(WhileRule*) = 0; + + }; + + template + class StatementVisitable { + public: + virtual T accept(StatementVisitor* visitor) = 0; + }; + +} + +#define DECLARE_STATEMENT_ACCEPT(T, name)\ + T accept(StatementVisitor* visitor) override final {\ + return visitor->visit##name(this);\ + }\ + +#endif diff --git a/src/visitor_value.hpp b/src/visitor_value.hpp new file mode 100644 index 0000000000..a071608a22 --- /dev/null +++ b/src/visitor_value.hpp @@ -0,0 +1,43 @@ +#ifndef SASS_VISITOR_VALUE_HPP +#define SASS_VISITOR_VALUE_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" + +namespace Sass { + + // An interface for [visitors][] that traverse SassScript values. + // [visitors]: https://en.wikipedia.org/wiki/Visitor_pattern + + template + class ValueVisitor { + public: + + virtual T visitBoolean(Boolean* value) = 0; + virtual T visitColor(Color* value) = 0; + virtual T visitFunction(Function* value) = 0; + virtual T visitList(List* value) = 0; + virtual T visitMap(Map* value) = 0; + virtual T visitNull(Null* value) = 0; + virtual T visitNumber(Number* value) = 0; + virtual T visitString(String* value) = 0; + + }; + + template + class ValueVisitable { + public: + virtual T accept(ValueVisitor* visitor) = 0; + }; + +} + +#define DECLARE_VALUE_ACCEPT(T, name)\ + T accept(ValueVisitor* visitor) override final {\ + return visitor->visit##name(this);\ + }\ + +#endif diff --git a/test/Makefile b/test/Makefile index 0f9aa30bcf..2c807b5c73 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,13 +1,31 @@ CXX ?= c++ -CXXFLAGS := -I ../include/ -std=c++11 -fsanitize=address -g -O1 -fno-omit-frame-pointer +CXXFLAGS := -I ../include/ -std=c++11 -fno-omit-frame-pointer -test: test_shared_ptr test_util_string +test: test_character test_string_utils test_shared_ptr: build/test_shared_ptr - @ASAN_OPTIONS="symbolize=1" build/test_shared_ptr + build/test_shared_ptr + +test_ordered_map: build/test_ordered_map + build/test_ordered_map + +test_character: build/test_character + build/test_character test_util_string: build/test_util_string - @ASAN_OPTIONS="symbolize=1" build/test_util_string + build/test_util_string + +test_string_utils: build/test_string_utils + build/test_string_utils + +test_source_data: build/test_source_data + build/test_source_data + +test_lurlparser: build/test_lurlparser + build/test_lurlparser + +test_offset: build/test_offset + build/test_offset build: @mkdir build @@ -15,10 +33,28 @@ build: build/test_shared_ptr: test_shared_ptr.cpp ../src/memory/shared_ptr.cpp | build $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp ../src/memory/shared_ptr.cpp -o build/test_shared_ptr test_shared_ptr.cpp -build/test_util_string: test_util_string.cpp ../src/util_string.cpp | build - $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp ../src/util_string.cpp -o build/test_util_string test_util_string.cpp +build/test_ordered_map: test_ordered_map.cpp ../src/memory/shared_ptr.cpp | build + $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp -o build/test_ordered_map test_ordered_map.cpp + +# build/test_util_string: test_util_string.cpp ../src/util_string.cpp | build +# $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp ../src/util_string.cpp -o build/test_util_string test_util_string.cpp + +build/test_character: test_character.cpp ../src/character.cpp | build + $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp ../src/character.cpp -o build/test_character test_character.cpp + +build/test_string_utils: test_string_utils.cpp ../src/string_utils.cpp | build + $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp ../src/character.cpp ../src/string_utils.cpp -o build/test_string_utils test_string_utils.cpp + +build/test_source_data: test_source_data.cpp ../src/source.cpp ../src/offset.cpp ../src/position.cpp | build + $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp ../src/randomize.cpp -o build/test_source_data test_source_data.cpp ../src/source.cpp ../src/source_span.cpp ../src/source_state.cpp ../src/offset.cpp ../src/position.cpp + +build/test_lurlparser: test_lurlparser.cpp ../src/LUrlParser/LUrlParser.cpp | build + $(CXX) $(CXXFLAGS) -o build/test_lurlparser test_lurlparser.cpp ../src/LUrlParser/LUrlParser.cpp + +build/test_offset: test_offset.cpp ../src/offset.cpp | build + $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp -o build/test_offset test_offset.cpp ../src/offset.cpp clean: | build rm -rf build -.PHONY: test test_shared_ptr test_util_string clean +.PHONY: test test_shared_ptr test_ordered_map test_string_utils test_source_data test_lurlparser test_offset clean diff --git a/test/assert.hpp b/test/assert.hpp new file mode 100644 index 0000000000..8f6dd469b2 --- /dev/null +++ b/test/assert.hpp @@ -0,0 +1,86 @@ +#ifndef ASSERT_HPP + +#include +#include +#include "../src/memory/allocator.hpp" +#include "../src/offset.hpp" + +Sass::sass::string escape_string(const Sass::sass::string& str) { + Sass::sass::string out; + out.reserve(str.size()); + for (char c : str) { + switch (c) { + case '\n': + out.append("\\n"); + break; + case '\r': + out.append("\\r"); + break; + case '\f': + out.append("\\f"); + break; + default: + out += c; + } + } + return out; +} + +#define ASSERT(cond) \ + if (!(cond)) { \ + std::cerr << "Assertion failed: " #cond " at " __FILE__ << ":" << __LINE__ << std::endl; \ + return false; \ + } \ + +#define ASSERT_TRUE(cond) \ + if (!(cond)) { \ + std::cerr << \ + "Expected condition to be true at " << __FILE__ << ":" << __LINE__ << \ + std::endl; \ + return false; \ + } \ + +#define ASSERT_FALSE(cond) \ + ASSERT_TRUE(!(cond)) \ + +#define ASSERT_NR_EQ(a, b) \ + if (a != b) { \ + std::cerr << \ + "Expected LHS == RHS at " << __FILE__ << ":" << __LINE__ << \ + "\n LHS: [" << a << "]" \ + "\n RHS: [" << b << "]" << \ + std::endl; \ + return false; \ + } \ + +#define ASSERT_STR_EQ(a, b) \ + if (Sass::sass::string(b) != a) { \ + std::cerr << \ + "Expected LHS == RHS at " << __FILE__ << ":" << __LINE__ << \ + "\n LHS: [" << escape_string(a) << "]" \ + "\n RHS: [" << escape_string(b) << "]" << \ + std::endl; \ + return false; \ + } \ + +#define INIT_TEST_RESULTS \ + Sass::sass::vector passed; \ + Sass::sass::vector failed; \ + +#define TEST(fn) \ + if (fn()) { \ + passed.push_back(#fn); \ + } else { \ + failed.push_back(#fn); \ + std::cerr << "Failed: " #fn << std::endl; \ + } \ + +#define REPORT_TEST_RESULTS \ + std::cerr << argv[0] << ": Passed: " << passed.size() \ + << ", failed: " << failed.size() \ + << "." << std::endl; \ + return failed.size(); \ + + + +#endif diff --git a/test/test_lurlparser.cpp b/test/test_lurlparser.cpp new file mode 100644 index 0000000000..45e87d27a3 --- /dev/null +++ b/test/test_lurlparser.cpp @@ -0,0 +1,32 @@ +#include "../src/memory/shared_ptr.hpp" +#include "../src/LUrlParser/LUrlParser.hpp" +#include "assert.hpp" + +#include +#include +#include +#include + +bool TestLUrlParser() { + using LUrlParser::clParseURL; + clParseURL URL = clParseURL::ParseURL( + "https://John:Dow@github.com:443/corporateshark/LUrlParser?query#hash"); + if (URL.IsValid()) + { + ASSERT_STR_EQ(URL.m_Scheme, "https"); + ASSERT_STR_EQ(URL.m_Host, "github.com"); + ASSERT_STR_EQ(URL.m_Port, "443"); + ASSERT_STR_EQ(URL.m_Path, "corporateshark/LUrlParser"); + ASSERT_STR_EQ(URL.m_Query, "query"); + ASSERT_STR_EQ(URL.m_Fragment, "hash"); + ASSERT_STR_EQ(URL.m_UserName, "John"); + ASSERT_STR_EQ(URL.m_Password, "Dow"); + } + return true; +} + +int main(int argc, char **argv) { + INIT_TEST_RESULTS; + TEST(TestLUrlParser); + REPORT_TEST_RESULTS; +} diff --git a/test/test_offset.cpp b/test/test_offset.cpp new file mode 100644 index 0000000000..acaee1ff1e --- /dev/null +++ b/test/test_offset.cpp @@ -0,0 +1,33 @@ +#include "../src/position.hpp" +#include "assert.hpp" + +#include +#include +#include +#include + +using namespace Sass; + +bool testOffsetMove() { + const char* text1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + ASSERT_STR_EQ(Offset::move(text1, Offset::init(0, 8)), + "IJKLMNOPQRSTUVWXYZ"); + const char* text2 = "ABC\nDEFGHIJKLMNOPQRSTUVW\nXYZ"; + ASSERT_STR_EQ(Offset::move(text2, Offset::init(1, 5)), + "IJKLMNOPQRSTUVW\nXYZ"); + ASSERT_STR_EQ(Offset::move(text2, Offset::init(1, 0)), + "DEFGHIJKLMNOPQRSTUVW\nXYZ"); + ASSERT_STR_EQ(Offset::move(text2, Offset::init(2, 0)), + "XYZ"); + ASSERT_STR_EQ(Offset::move(text2, Offset::init(2, 3)), ""); + ASSERT_NR_EQ(Offset::move(text2, Offset::init(3, 5)), 0); + ASSERT_NR_EQ(Offset::move(text2, Offset::init(2, 4)), 0); + ASSERT_NR_EQ(Offset::move(text2, Offset::init(1, 20)), 0); + return true; +} + +int main(int argc, char **argv) { + INIT_TEST_RESULTS; + TEST(testOffsetMove); + REPORT_TEST_RESULTS; +} diff --git a/test/test_ordered_map.cpp b/test/test_ordered_map.cpp new file mode 100644 index 0000000000..e2ca9f93ae --- /dev/null +++ b/test/test_ordered_map.cpp @@ -0,0 +1,76 @@ +#include "../src/ordered_map.hpp" +#include "assert.hpp" + +#include +#include +#include +#include + +bool TestOrderedMap() { + Sass::ordered_map map; + ASSERT_NR_EQ(map.size(), 0); + map.push_back("first", 42); + // Test size getter + ASSERT_NR_EQ(map.size(), 1); + // Test front/back accessor + ASSERT_NR_EQ(map.front().first, "first"); + ASSERT_NR_EQ(map.front().second, 42); + ASSERT_NR_EQ(map.back().first, "first"); + ASSERT_NR_EQ(map.back().second, 42); + // Test array accessor + map["first"] = 1; // change + map["second"] = 2; // append + map["third"] = 3; // append + // Test count getter + ASSERT_NR_EQ(map.count("first"), 1); + ASSERT_NR_EQ(map.count("seven"), 0); + // Test array accessors + ASSERT_NR_EQ(map[0].first, "first"); + ASSERT_NR_EQ(map[1].first, "second"); + ASSERT_NR_EQ(map[2].first, "third"); + ASSERT_NR_EQ(map["first"], 1); + ASSERT_NR_EQ(map["second"], 2); + ASSERT_NR_EQ(map["third"], 3); + // Test size getter + ASSERT_NR_EQ(map.size(), 3); + // Test front/back accessor + ASSERT_NR_EQ(map.front().first, "first"); + ASSERT_NR_EQ(map.front().second, 1); + ASSERT_NR_EQ(map.back().first, "third"); + ASSERT_NR_EQ(map.back().second, 3); + // Erase front item by key + ASSERT_TRUE(map.erase("first")); + // Test size getter + ASSERT_NR_EQ(map.size(), 2); + // Test front/back accessor + ASSERT_NR_EQ(map.front().first, "second"); + ASSERT_NR_EQ(map.front().second, 2); + // Erase front item by index + ASSERT_TRUE(map.erase(0)); + // Test size getter + ASSERT_NR_EQ(map.size(), 1); + // Test front/back accessor + ASSERT_NR_EQ(map.front().first, "third"); + ASSERT_NR_EQ(map.front().second, 3); + // Search iterator by key + auto it = map.find("third"); + // Test iterator accessor + ASSERT_NR_EQ(it->first, "third"); + ASSERT_NR_EQ(it->second, 3); + // Test iterator modifier + it->second = 42; + // Check again by fetching front + ASSERT_NR_EQ(map[0].second, 42); + ASSERT_NR_EQ(map.front().second, 42); + // Erase front item by index + ASSERT_TRUE(map.erase("third")); + ASSERT_TRUE(map.empty()); + // All have passed + return true; +} + +int main(int argc, char **argv) { + INIT_TEST_RESULTS; + TEST(TestOrderedMap); + REPORT_TEST_RESULTS; +} diff --git a/test/test_shared_ptr.cpp b/test/test_shared_ptr.cpp index 7355c1abd8..e317795ddd 100644 --- a/test/test_shared_ptr.cpp +++ b/test/test_shared_ptr.cpp @@ -1,23 +1,17 @@ -#include "../src/memory/allocator.hpp" #include "../src/memory/shared_ptr.hpp" +#include "assert.hpp" #include #include #include #include -#define ASSERT(cond) \ - if (!(cond)) { \ - std::cerr << "Assertion failed: " #cond " at " __FILE__ << ":" << __LINE__ << std::endl; \ - return false; \ - } \ - class TestObj : public Sass::SharedObj { public: TestObj(bool *destroyed) : destroyed_(destroyed) {} ~TestObj() { *destroyed_ = true; } Sass::sass::string to_string() const { - Sass::sass::ostream result; + Sass::sass::sstream result; result << "refcount=" << refcount << " destroyed=" << *destroyed_; return result.str(); } @@ -30,7 +24,7 @@ using SharedTestObj = Sass::SharedImpl; bool TestOneSharedPtr() { bool destroyed = false; { - SharedTestObj a = SASS_MEMORY_NEW(TestObj, &destroyed); + SharedTestObj a = new TestObj(&destroyed); } ASSERT(destroyed); return true; @@ -39,7 +33,7 @@ bool TestOneSharedPtr() { bool TestTwoSharedPtrs() { bool destroyed = false; { - SharedTestObj a = SASS_MEMORY_NEW(TestObj, &destroyed); + SharedTestObj a = new TestObj(&destroyed); { SharedTestObj b = a; } @@ -52,7 +46,7 @@ bool TestTwoSharedPtrs() { bool TestSelfAssignment() { bool destroyed = false; { - SharedTestObj a = SASS_MEMORY_NEW(TestObj, &destroyed); + SharedTestObj a = new TestObj(&destroyed); a = a; ASSERT(!destroyed); } @@ -162,17 +156,8 @@ bool TestComparisonWithNullptr() { return true; } -#define TEST(fn) \ - if (fn()) { \ - passed.push_back(#fn); \ - } else { \ - failed.push_back(#fn); \ - std::cerr << "Failed: " #fn << std::endl; \ - } \ - int main(int argc, char **argv) { - std::vector passed; - std::vector failed; + INIT_TEST_RESULTS; TEST(TestOneSharedPtr); TEST(TestTwoSharedPtrs); TEST(TestSelfAssignment); @@ -184,8 +169,5 @@ int main(int argc, char **argv) { TEST(TestDetachNull); TEST(TestComparisonWithSharedPtr); TEST(TestComparisonWithNullptr); - std::cerr << argv[0] << ": Passed: " << passed.size() - << ", failed: " << failed.size() - << "." << std::endl; - return failed.size(); + REPORT_TEST_RESULTS; } diff --git a/test/test_source_data.cpp b/test/test_source_data.cpp new file mode 100644 index 0000000000..c02b1517c4 --- /dev/null +++ b/test/test_source_data.cpp @@ -0,0 +1,235 @@ +#include "../src/source.hpp" +#include "assert.hpp" + +#include +#include +#include +#include + +namespace { + +using namespace Sass; + +bool testSourceFileBasic() { + const char* txt = + "Line A\n" + "Line B\n" + "Line C"; + SourceFile source("sass://", txt, -1); + ASSERT_NR_EQ(source.countLines(), 3); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "Line B"); + ASSERT_STR_EQ(source.getLine(2), "Line C"); + return true; +} + +bool testSourceFileCrLf() { + const char* txt = + "Line A\r\n" + "Line B\r\n" + "Line C\r"; + SourceFile source("sass://", txt, -1); + ASSERT_NR_EQ(source.countLines(), 3); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "Line B"); + ASSERT_STR_EQ(source.getLine(2), "Line C"); + return true; +} + +bool testSourceFileEmpty() { + const char* txt = + "\n" + "\n" + ""; + SourceFile source("sass://", txt, -1); + ASSERT_NR_EQ(source.countLines(), 3); + ASSERT_STR_EQ(source.getLine(0), ""); + ASSERT_STR_EQ(source.getLine(1), ""); + ASSERT_STR_EQ(source.getLine(2), ""); + return true; +} + +bool testSourceFileEmptyCrLf() { + const char* txt = + "\r\n" + "\r\n" + "\r"; + SourceFile source("sass://", txt, -1); + ASSERT_NR_EQ(source.countLines(), 3); + ASSERT_STR_EQ(source.getLine(0), ""); + ASSERT_STR_EQ(source.getLine(1), ""); + ASSERT_STR_EQ(source.getLine(2), ""); + return true; +} + +bool testSourceFileEmptyTrail() { + const char* txt = + "\n" + "\n" + "\n"; + SourceFile source("sass://", txt, -1); + ASSERT_NR_EQ(source.countLines(), 4); + ASSERT_STR_EQ(source.getLine(0), ""); + ASSERT_STR_EQ(source.getLine(1), ""); + ASSERT_STR_EQ(source.getLine(2), ""); + ASSERT_STR_EQ(source.getLine(3), ""); + return true; +} + +bool testSyntheticFileBasic() { + const char* around = + "Line A\n" + "Line B\n" + "Line C\n" + "Line D\n" + "Line E"; + SourceFileObj parent = SASS_MEMORY_NEW( + SourceFile, "sass://", around, -1); + SourceSpan pstate(parent, + Offset::init(1, 4), + Offset::init(0, 0)); + SyntheticFile source("[ADD]", parent, pstate); + ASSERT_NR_EQ(source.countLines(), 5); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "Line[ADD] B"); + ASSERT_STR_EQ(source.getLine(2), "Line C"); + ASSERT_STR_EQ(source.getLine(3), "Line D"); + ASSERT_STR_EQ(source.getLine(4), "Line E"); + return true; +} + +bool testSyntheticFileBasicMulti() { + const char* around = + "Line A\n" + "Line B\n" + "Line C\n" + "Line D\n" + "Line E"; + SourceFileObj parent = SASS_MEMORY_NEW( + SourceFile, "sass://", around, -1); + SourceSpan pstate(parent, + Offset::init(1, 1), + Offset::init(1, 2)); + SyntheticFile source("[ADD]", parent, pstate); + ASSERT_NR_EQ(source.countLines(), 4); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "L[ADD]ne C"); + ASSERT_STR_EQ(source.getLine(2), "Line D"); + ASSERT_STR_EQ(source.getLine(3), "Line E"); + return true; +} + +bool testSyntheticFileMulti() { + const char* around = + "Line A\n" + "Line B\n" + "Line C\n" + "Line D\n" + "Line E"; + SourceFileObj parent = SASS_MEMORY_NEW( + SourceFile, "sass://", around, -1); + SourceSpan pstate(parent, + Offset::init(1, 4), + Offset::init(0, 0)); + SyntheticFile source("[ADD]\n[ANOTHER]\n[MORE]", parent, pstate); + ASSERT_NR_EQ(source.countLines(), 7); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "Line[ADD]"); + ASSERT_STR_EQ(source.getLine(2), "[ANOTHER]"); + ASSERT_STR_EQ(source.getLine(3), "[MORE] B"); + ASSERT_STR_EQ(source.getLine(4), "Line C"); + ASSERT_STR_EQ(source.getLine(5), "Line D"); + ASSERT_STR_EQ(source.getLine(6), "Line E"); + return true; +} + +bool testSyntheticFileMultiMulti() { + const char* around = + "Line A\n" + "Line B\n" + "Line C\n" + "Line D\n" + "Line E"; + SourceFileObj parent = SASS_MEMORY_NEW( + SourceFile, "sass://", around, -1); + SourceSpan pstate(parent, + Offset::init(1, 4), + Offset::init(1, 5)); + SyntheticFile source("[ADD]\n[ANOTHER]\n[MORE]", parent, pstate); + ASSERT_NR_EQ(source.countLines(), 6); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "Line[ADD]"); + ASSERT_STR_EQ(source.getLine(2), "[ANOTHER]"); + ASSERT_STR_EQ(source.getLine(3), "[MORE]C"); + ASSERT_STR_EQ(source.getLine(4), "Line D"); + ASSERT_STR_EQ(source.getLine(5), "Line E"); + return true; +} + +bool testSourceFileUnicode() { + const char* txt = + "Line A\n" + "[a=b ï]\n" + "Line C"; + SourceFile source("sass://", txt, -1); + ASSERT_NR_EQ(source.countLines(), 3); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "[a=b ï]"); + ASSERT_STR_EQ(source.getLine(2), "Line C"); + return true; +} + +bool testSyntheticFileUnicode1() { + const char* around = + "Line A\n" + "[ä=ö ï]\n" + "Line C"; + SourceFileObj parent = SASS_MEMORY_NEW( + SourceFile, "sass://", around, -1); + SourceSpan pstate(parent, + Offset::init(1, 1), + Offset::init(0, 4)); + SyntheticFile source("[ADD]", parent, pstate); + ASSERT_NR_EQ(source.countLines(), 3); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "[[ADD]ï]"); + ASSERT_STR_EQ(source.getLine(2), "Line C"); + return true; +} + +bool testSyntheticFileUnicode2() { + const char* around = + "Line A\n" + "[ä=ö ï]\n" + "Line C"; + SourceFileObj parent = SASS_MEMORY_NEW( + SourceFile, "sass://", around, -1); + SourceSpan pstate(parent, + Offset::init(1, 2), + Offset::init(0, 3)); + SyntheticFile source("[ADD]", parent, pstate); + ASSERT_NR_EQ(source.countLines(), 3); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "[ä[ADD]ï]"); + ASSERT_STR_EQ(source.getLine(2), "Line C"); + return true; +} + +} // namespace + +int main(int argc, char **argv) { + INIT_TEST_RESULTS; + TEST(testSourceFileBasic); + TEST(testSourceFileCrLf); + TEST(testSourceFileEmpty); + TEST(testSourceFileEmptyCrLf); + TEST(testSourceFileEmptyTrail); + TEST(testSourceFileUnicode); + TEST(testSyntheticFileBasic); + TEST(testSyntheticFileMulti); + TEST(testSyntheticFileBasicMulti); + TEST(testSyntheticFileMultiMulti); + TEST(testSyntheticFileUnicode1); + TEST(testSyntheticFileUnicode2); + REPORT_TEST_RESULTS; +} diff --git a/test/test_util_string.cpp b/test/test_util_string.cpp index a44e6e1670..a180afcee0 100644 --- a/test/test_util_string.cpp +++ b/test/test_util_string.cpp @@ -1,4 +1,5 @@ #include "../src/util_string.hpp" +#include "assert.hpp" #include #include @@ -7,104 +8,6 @@ namespace { -Sass::sass::string escape_string(const Sass::sass::string& str) { - Sass::sass::string out; - out.reserve(str.size()); - for (char c : str) { - switch (c) { - case '\n': - out.append("\\n"); - break; - case '\r': - out.append("\\r"); - break; - case '\f': - out.append("\\f"); - break; - default: - out += c; - } - } - return out; -} - -#define ASSERT_TRUE(cond) \ - if (!cond) { \ - std::cerr << \ - "Expected condition to be true at " << __FILE__ << ":" << __LINE__ << \ - std::endl; \ - return false; \ - } \ - -#define ASSERT_FALSE(cond) \ - ASSERT_TRUE(!(cond)) \ - -#define ASSERT_STR_EQ(a, b) \ - if (a != b) { \ - std::cerr << \ - "Expected LHS == RHS at " << __FILE__ << ":" << __LINE__ << \ - "\n LHS: [" << escape_string(a) << "]" \ - "\n RHS: [" << escape_string(b) << "]" << \ - std::endl; \ - return false; \ - } \ - -bool TestNormalizeNewlinesNoNewline() { - Sass::sass::string input = "a"; - Sass::sass::string normalized = Sass::Util::normalize_newlines(input); - ASSERT_STR_EQ(input, normalized); - return true; -} - -bool TestNormalizeNewlinesLF() { - Sass::sass::string input = "a\nb"; - Sass::sass::string normalized = Sass::Util::normalize_newlines(input); - ASSERT_STR_EQ(input, normalized); - return true; -} - -bool TestNormalizeNewlinesCR() { - Sass::sass::string normalized = Sass::Util::normalize_newlines("a\rb"); - ASSERT_STR_EQ("a\nb", normalized); - return true; -} - -bool TestNormalizeNewlinesCRLF() { - Sass::sass::string normalized = Sass::Util::normalize_newlines("a\r\nb\r\n"); - ASSERT_STR_EQ("a\nb\n", normalized); - return true; -} - -bool TestNormalizeNewlinesFF() { - Sass::sass::string normalized = Sass::Util::normalize_newlines("a\fb\f"); - ASSERT_STR_EQ("a\nb\n", normalized); - return true; -} - -bool TestNormalizeNewlinesMixed() { - Sass::sass::string normalized = Sass::Util::normalize_newlines("a\fb\nc\rd\r\ne\ff"); - ASSERT_STR_EQ("a\nb\nc\nd\ne\nf", normalized); - return true; -} - -bool TestNormalizeUnderscores() { - Sass::sass::string normalized = Sass::Util::normalize_underscores("a_b_c"); - ASSERT_STR_EQ("a-b-c", normalized); - return true; -} - -bool TestNormalizeDecimalsLeadingZero() { - Sass::sass::string normalized = Sass::Util::normalize_decimals("0.5"); - ASSERT_STR_EQ("0.5", normalized); - return true; -} - -bool TestNormalizeDecimalsNoLeadingZero() { - Sass::sass::string normalized = Sass::Util::normalize_decimals(".5"); - ASSERT_STR_EQ("0.5", normalized); - return true; -} - bool testEqualsLiteral() { ASSERT_TRUE(Sass::Util::equalsLiteral("moz", "moz")); ASSERT_TRUE(Sass::Util::equalsLiteral(":moz", ":moz")); @@ -142,6 +45,33 @@ bool TestUnvendor() { return true; } +bool TestSplitString1() { + Sass::sass::vector list = + Sass::Util::split_string("a,b,c", ','); + ASSERT_NR_EQ(3, list.size()); + ASSERT_STR_EQ("a", list[0]); + ASSERT_STR_EQ("b", list[1]); + ASSERT_STR_EQ("c", list[2]); + return true; +} + +bool TestSplitString2() { + Sass::sass::vector list = + Sass::Util::split_string("a,b,", ','); + ASSERT_NR_EQ(3, list.size()); + ASSERT_STR_EQ("a", list[0]); + ASSERT_STR_EQ("b", list[1]); + ASSERT_STR_EQ("", list[2]); + return true; +} + +bool TestSplitStringEmpty() { + Sass::sass::vector list = + Sass::Util::split_string("", ','); + ASSERT_NR_EQ(0, list.size()); + return true; +} + bool Test_ascii_str_to_lower() { Sass::sass::string str = "A B"; Sass::Util::ascii_str_tolower(&str); @@ -151,6 +81,7 @@ bool Test_ascii_str_to_lower() { bool Test_ascii_str_to_upper() { Sass::sass::string str = "a b"; + ASSERT_STR_EQ("A B", Sass::Util::ascii_str_toupper(str)); Sass::Util::ascii_str_toupper(&str); ASSERT_STR_EQ("A B", str); return true; @@ -181,37 +112,35 @@ bool Test_ascii_isspace() { return true; } -} // namespace +bool TestEqualsIgnoreSeparator() { + ASSERT_TRUE(Sass::Util::ascii_str_equals_ignore_separator("fOo", "FoO")); + ASSERT_TRUE(Sass::Util::ascii_str_equals_ignore_separator("fOo-BaR", "FoO_bAr")); + ASSERT_TRUE(Sass::Util::ascii_str_equals_ignore_separator("FoO_bAr", "fOo-BaR")); + return true; +} -#define TEST(fn) \ - if (fn()) { \ - passed.push_back(#fn); \ - } else { \ - failed.push_back(#fn); \ - std::cerr << "Failed: " #fn << std::endl; \ - } \ +bool TestHashIgnoreSeparator() { + ASSERT_TRUE(Sass::Util::hash_ignore_separator("fOo") == Sass::Util::hash_ignore_separator("FoO")); + ASSERT_TRUE(Sass::Util::hash_ignore_separator("fOo-BaR") == Sass::Util::hash_ignore_separator("FoO_bAr")); + ASSERT_TRUE(Sass::Util::hash_ignore_separator("FoO_bAr") == Sass::Util::hash_ignore_separator("fOo-BaR")); + return true; +} + +} // namespace int main(int argc, char **argv) { - std::vector passed; - std::vector failed; - TEST(TestNormalizeNewlinesNoNewline); - TEST(TestNormalizeNewlinesLF); - TEST(TestNormalizeNewlinesCR); - TEST(TestNormalizeNewlinesCRLF); - TEST(TestNormalizeNewlinesFF); - TEST(TestNormalizeNewlinesMixed); - TEST(TestNormalizeUnderscores); - TEST(TestNormalizeDecimalsLeadingZero); - TEST(TestNormalizeDecimalsNoLeadingZero); + INIT_TEST_RESULTS; TEST(testEqualsLiteral); TEST(TestUnvendor); + TEST(TestSplitStringEmpty); + TEST(TestSplitString1); + TEST(TestSplitString2); TEST(Test_ascii_str_to_lower); TEST(Test_ascii_str_to_upper); TEST(Test_ascii_isalpha); TEST(Test_ascii_isxdigit); TEST(Test_ascii_isspace); - std::cerr << argv[0] << ": Passed: " << passed.size() - << ", failed: " << failed.size() - << "." << std::endl; - return failed.size(); + TEST(TestEqualsIgnoreSeparator); + // TEST(TestHashIgnoreSeparator); + REPORT_TEST_RESULTS; } diff --git a/utils/build-skeletons/libsass.targets b/utils/build-skeletons/libsass.targets index 77bcf669b4..642234a28a 100644 --- a/utils/build-skeletons/libsass.targets +++ b/utils/build-skeletons/libsass.targets @@ -2,7 +2,4 @@ {{includes}} {{headers}} {{sources}} - - - diff --git a/utils/update-build.pl b/utils/update-build.pl new file mode 100644 index 0000000000..b125a7b9f6 --- /dev/null +++ b/utils/update-build.pl @@ -0,0 +1,81 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use File::Slurp qw(read_file write_file); + +my $tmpl_msvc_head = < +EOTMPL + +my $tmpl_msvc_inc = < +EOTMPL + +my $tmpl_msvc_src = < +EOTMPL + +my $tmpl_msvc_filter_head = < + Library Includes + +EOTMPL + +my $tmpl_msvc_filter_inc = < + LibSass Headers + +EOTMPL + +my $tmpl_msvc_filter_src = < + LibSass Sources + +EOTMPL + +# parse source files directly from libsass makefile +open(my $fh, "<", "../Makefile.conf") or + die "../Makefile.conf not found"; +my $srcfiles = join "", <$fh>; close $fh; + +my (@INCFILES, @HPPFILES, @SOURCES, @CSOURCES); +# parse variable out (this is hopefully tolerant enough) +if ($srcfiles =~ /^\s*INCFILES\s*=\s*((?:.*(?:\\\r?\n))*.*)/m) { + @INCFILES = grep { $_ } split /(?:\s|\\\r?\n)+/, $1; +} else { die "Did not find c++ INCFILES in libsass/Makefile.conf"; } +if ($srcfiles =~ /^\s*HPPFILES\s*=\s*((?:.*(?:\\\r?\n))*.*)/m) { + @HPPFILES = grep { $_ } split /(?:\s|\\\r?\n)+/, $1; +} else { die "Did not find c++ HPPFILES in libsass/Makefile.conf"; } +if ($srcfiles =~ /^\s*SOURCES\s*=\s*((?:.*(?:\\\r?\n))*.*)/m) { + @SOURCES = grep { $_ } split /(?:\s|\\\r?\n)+/, $1; +} else { die "Did not find c++ SOURCES in libsass/Makefile.conf"; } +if ($srcfiles =~ /^\s*CSOURCES\s*=\s*((?:.*(?:\\\r?\n))*.*)/m) { + @CSOURCES = grep { $_ } split /(?:\s|\\\r?\n)+/, $1; +} else { die "Did not find c++ CSOURCES in libsass/Makefile.conf"; } + +sub renderTemplate($@) { + my $str = "\n"; + my $tmpl = shift; + foreach my $inc (@_) { + $str .= sprintf($tmpl, $inc); + } + $str .= " "; + return $str; +} + +my $targets = read_file("build-skeletons/libsass.targets"); +$targets =~s /\{\{includes\}\}/renderTemplate($tmpl_msvc_inc, @INCFILES)/eg; +$targets =~s /\{\{headers\}\}/renderTemplate($tmpl_msvc_head, @HPPFILES)/eg; +$targets =~s /\{\{sources\}\}/renderTemplate($tmpl_msvc_src, @SOURCES, @CSOURCES)/eg; +warn "Generating ../win/libsass.targets\n"; +write_file("../win/libsass.targets", $targets); + +my $filters = read_file("build-skeletons/libsass.vcxproj.filters"); +$filters =~s /\{\{includes\}\}/renderTemplate($tmpl_msvc_filter_inc, @INCFILES)/eg; +$filters =~s /\{\{headers\}\}/renderTemplate($tmpl_msvc_filter_head, @HPPFILES)/eg; +$filters =~s /\{\{sources\}\}/renderTemplate($tmpl_msvc_filter_src, @SOURCES, @CSOURCES)/eg; +warn "Generating ../win/libsass.vcxproj.filters\n"; +write_file("../win/libsass.vcxproj.filters", $filters); + diff --git a/win/libsass.targets b/win/libsass.targets index a60a27e5c0..052ed0ac50 100644 --- a/win/libsass.targets +++ b/win/libsass.targets @@ -1,92 +1,123 @@ - - - - - - + + + + + + + - + + + + + + - - - + + - - + + + + + + + - + - + - + + + + + - - + - - - - + + + + + + + + + + + - - + - - - - - - - + + + + + + + + + + + - - - + - - - - - + + + + + + + + + + + + + + + @@ -94,63 +125,69 @@ - - + - + + - - + + + - - - - - - + - - - + + + + + + + + + + + + + + - + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - + + + + + + + + diff --git a/win/libsass.vcxproj.filters b/win/libsass.vcxproj.filters index 2d6248c96b..3dfe600cb1 100644 --- a/win/libsass.vcxproj.filters +++ b/win/libsass.vcxproj.filters @@ -18,22 +18,25 @@ LibSass Headers - + LibSass Headers - + LibSass Headers - + LibSass Headers - + LibSass Headers - + LibSass Headers - + + LibSass Headers + + LibSass Headers @@ -41,7 +44,10 @@ Library Includes - + + Library Includes + + Library Includes @@ -56,9 +62,21 @@ Library Includes + + Library Includes + Library Includes + + Library Includes + + + Library Includes + + + Library Includes + Library Includes @@ -68,13 +86,10 @@ Library Includes - - Library Includes - - + Library Includes - + Library Includes @@ -92,9 +107,6 @@ Library Includes - - Library Includes - Library Includes @@ -104,13 +116,28 @@ Library Includes - + Library Includes - + + Library Includes + + + Library Includes + + + Library Includes + + + Library Includes + + + Library Includes + + Library Includes - + Library Includes @@ -122,6 +149,9 @@ Library Includes + + Library Includes + Library Includes @@ -131,7 +161,7 @@ Library Includes - + Library Includes @@ -140,49 +170,79 @@ Library Includes - + Library Includes Library Includes + + Library Includes + Library Includes + + Library Includes + + + Library Includes + + + Library Includes + Library Includes Library Includes - + Library Includes - + Library Includes - + Library Includes - + Library Includes - + Library Includes - + Library Includes - + Library Includes - + Library Includes - + Library Includes - + + Library Includes + + + Library Includes + + + Library Includes + + + Library Includes + + + Library Includes + + + Library Includes + + Library Includes @@ -191,70 +251,88 @@ Library Includes - + Library Includes - + Library Includes - + Library Includes - + Library Includes - + Library Includes - + Library Includes - + Library Includes - + Library Includes - + Library Includes - + + Library Includes + + Library Includes Library Includes - + Library Includes - + Library Includes - + Library Includes - + Library Includes - + Library Includes Library Includes - + + Library Includes + + Library Includes - + Library Includes - + Library Includes - + Library Includes - + + Library Includes + + + Library Includes + + + Library Includes + + + Library Includes + + Library Includes @@ -262,12 +340,27 @@ LibSass Sources + + LibSass Sources + + + LibSass Sources + LibSass Sources LibSass Sources + + LibSass Sources + + + LibSass Sources + + + LibSass Sources + LibSass Sources @@ -289,10 +382,7 @@ LibSass Sources - - LibSass Sources - - + LibSass Sources @@ -307,12 +397,15 @@ LibSass Sources - + LibSass Sources LibSass Sources + + LibSass Sources + LibSass Sources @@ -322,25 +415,25 @@ LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources @@ -349,43 +442,61 @@ LibSass Sources - + LibSass Sources - + + LibSass Sources + + + LibSass Sources + + + LibSass Sources + + + LibSass Sources + + + LibSass Sources + + + LibSass Sources + + LibSass Sources LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources @@ -394,64 +505,73 @@ LibSass Sources + + LibSass Sources + LibSass Sources - + LibSass Sources LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources LibSass Sources - + + LibSass Sources + + + LibSass Sources + + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources